From a1bf107ddca8488e19cd99c3740fd4333aa759f5 Mon Sep 17 00:00:00 2001 From: Ward from fusion-voyager-3 Date: Sat, 4 Nov 2023 21:37:27 +0300 Subject: [PATCH] update to 6.5.9 --- VERSION | 2 +- config | 224 +- patches/0001-pikaos-base-all.patch | 48677 ++++++++++++++++ .../0001-cachyos-base-all.patch} | 890 +- patches/{ => cachyos}/0002-eevdf.patch | 0 patches/{ => cachyos}/0002-eevdfbore.patch | 0 ...om-USB-pollrate-for-specific-device.patch} | 17 +- ...-REBAR-size-quirk-for-Sapphire-RX-56.patch | 34 + ...drop-redundant-pci_enable_pcie_error.patch | 46 + .../0001-acpi-proc-idle-skip-dummy-wait.patch | 125 + ...disable-async-flipping-on-specific-d.patch | 48 + ...nel-parameter-to-disable-async-page-.patch | 54 + patches/nobara/OpenRGB.patch | 719 + .../amdgpu-si-cik-default.patch} | 17 +- patches/nobara/asus-linux.patch | 2177 + patches/nobara/chimera-ALSA.patch | 1170 + patches/nobara/lenovo-legion-laptop.patch | 5913 ++ patches/nobara/linux-surface.patch | 8136 +++ ...isable-powersave-features-by-default.patch | 43 + patches/nobara/rog-ally-alsa.patch | 1007 + patches/nobara/rog-ally-bmc150.patch | 2672 + patches/nobara/rog-ally-side-keys-fix.patch | 51 + .../nobara/set-ps4-bt-poll-rate-1000hz.patch | 27 + patches/nobara/steam-deck.patch | 2575 + patches/series | 33 +- 25 files changed, 73961 insertions(+), 696 deletions(-) create mode 100644 patches/0001-pikaos-base-all.patch rename patches/{0001-cachy-all.patch => cachyos/0001-cachyos-base-all.patch} (98%) rename patches/{ => cachyos}/0002-eevdf.patch (100%) rename patches/{ => cachyos}/0002-eevdfbore.patch (100%) rename patches/{0004-Allow-to-set-custom-USB-pollrate-for-specific-device.patch => nobara/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch} (99%) create mode 100644 patches/nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch create mode 100644 patches/nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch create mode 100644 patches/nobara/0001-acpi-proc-idle-skip-dummy-wait.patch create mode 100644 patches/nobara/0001-drm-i915-quirks-disable-async-flipping-on-specific-d.patch create mode 100644 patches/nobara/0002-drm-i915-add-kernel-parameter-to-disable-async-page-.patch create mode 100644 patches/nobara/OpenRGB.patch rename patches/{0005-amdgpu-si-cik-default.patch => nobara/amdgpu-si-cik-default.patch} (99%) create mode 100644 patches/nobara/asus-linux.patch create mode 100644 patches/nobara/chimera-ALSA.patch create mode 100644 patches/nobara/lenovo-legion-laptop.patch create mode 100644 patches/nobara/linux-surface.patch create mode 100644 patches/nobara/mt76:-mt7921:-Disable-powersave-features-by-default.patch create mode 100644 patches/nobara/rog-ally-alsa.patch create mode 100644 patches/nobara/rog-ally-bmc150.patch create mode 100644 patches/nobara/rog-ally-side-keys-fix.patch create mode 100644 patches/nobara/set-ps4-bt-poll-rate-1000hz.patch create mode 100644 patches/nobara/steam-deck.patch mode change 100755 => 100644 patches/series diff --git a/VERSION b/VERSION index cc81d71..c418811 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.5.5 +6.5.9 diff --git a/config b/config index e193761..dcbc1c6 100644 --- a/config +++ b/config @@ -1,8 +1,8 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/x86 6.6.0 Kernel Configuration +# Linux/x86 6.5.0 Kernel Configuration # -CONFIG_CC_VERSION_TEXT="gcc (GCC) 13.2.1 20230801" +CONFIG_CC_VERSION_TEXT="gcc (GCC) 13.2.1 20230730" CONFIG_CC_IS_GCC=y CONFIG_GCC_VERSION=130201 CONFIG_CLANG_VERSION=0 @@ -305,6 +305,7 @@ CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y CONFIG_KCMP=y CONFIG_RSEQ=y CONFIG_CACHESTAT_SYSCALL=y +# CONFIG_EMBEDDED is not set CONFIG_HAVE_PERF_EVENTS=y CONFIG_GUEST_PERF_EVENTS=y @@ -318,22 +319,6 @@ CONFIG_PERF_EVENTS=y CONFIG_SYSTEM_DATA_VERIFICATION=y CONFIG_PROFILING=y CONFIG_TRACEPOINTS=y - -# -# Kexec and crash features -# -CONFIG_CRASH_CORE=y -CONFIG_KEXEC_CORE=y -CONFIG_KEXEC=y -CONFIG_KEXEC_FILE=y -CONFIG_KEXEC_SIG=y -# CONFIG_KEXEC_SIG_FORCE is not set -CONFIG_KEXEC_BZIMAGE_VERIFY_SIG=y -CONFIG_KEXEC_JUMP=y -CONFIG_CRASH_DUMP=y -CONFIG_CRASH_HOTPLUG=y -CONFIG_CRASH_MAX_MEMORY_RANGES=8192 -# end of Kexec and crash features # end of General setup CONFIG_64BIT=y @@ -507,6 +492,8 @@ CONFIG_X86_ESPFIX64=y CONFIG_X86_VSYSCALL_EMULATION=y CONFIG_X86_IOPL_IOPERM=y CONFIG_MICROCODE=y +CONFIG_MICROCODE_INTEL=y +CONFIG_MICROCODE_AMD=y # CONFIG_MICROCODE_LATE_LOADING is not set CONFIG_X86_MSR=y CONFIG_X86_CPUID=y @@ -538,14 +525,12 @@ CONFIG_X86_PAT=y CONFIG_ARCH_USES_PG_UNCACHED=y CONFIG_X86_UMIP=y CONFIG_CC_HAS_IBT=n -CONFIG_X86_CET=y CONFIG_X86_KERNEL_IBT=n CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS=y # CONFIG_X86_INTEL_TSX_MODE_OFF is not set # CONFIG_X86_INTEL_TSX_MODE_ON is not set CONFIG_X86_INTEL_TSX_MODE_AUTO=y CONFIG_X86_SGX=y -CONFIG_X86_USER_SHADOW_STACK=y CONFIG_EFI=y CONFIG_EFI_STUB=y CONFIG_EFI_HANDOVER_PROTOCOL=y @@ -561,16 +546,14 @@ CONFIG_HZ_300=y # CONFIG_HZ_1000 is not set CONFIG_HZ=300 CONFIG_SCHED_HRTICK=y -CONFIG_ARCH_SUPPORTS_KEXEC=y -CONFIG_ARCH_SUPPORTS_KEXEC_FILE=y -CONFIG_ARCH_SELECTS_KEXEC_FILE=y -CONFIG_ARCH_SUPPORTS_KEXEC_PURGATORY=y -CONFIG_ARCH_SUPPORTS_KEXEC_SIG=y -CONFIG_ARCH_SUPPORTS_KEXEC_SIG_FORCE=y -CONFIG_ARCH_SUPPORTS_KEXEC_BZIMAGE_VERIFY_SIG=y -CONFIG_ARCH_SUPPORTS_KEXEC_JUMP=y -CONFIG_ARCH_SUPPORTS_CRASH_DUMP=y -CONFIG_ARCH_SUPPORTS_CRASH_HOTPLUG=y +CONFIG_KEXEC=y +CONFIG_KEXEC_FILE=y +CONFIG_ARCH_HAS_KEXEC_PURGATORY=y +CONFIG_KEXEC_SIG=y +# CONFIG_KEXEC_SIG_FORCE is not set +CONFIG_KEXEC_BZIMAGE_VERIFY_SIG=y +CONFIG_CRASH_DUMP=y +CONFIG_KEXEC_JUMP=y CONFIG_PHYSICAL_START=0x1000000 CONFIG_RELOCATABLE=y CONFIG_RANDOMIZE_BASE=y @@ -613,6 +596,7 @@ CONFIG_CPU_SRSO=y CONFIG_SLS=y # CONFIG_GDS_FORCE_MITIGATION is not set CONFIG_ARCH_HAS_ADD_PAGES=y +CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y # # Power management and ACPI options @@ -827,11 +811,12 @@ CONFIG_AS_SHA1_NI=y CONFIG_AS_SHA256_NI=y CONFIG_AS_TPAUSE=y CONFIG_AS_GFNI=y -CONFIG_AS_WRUSS=y # # General architecture-dependent options # +CONFIG_CRASH_CORE=y +CONFIG_KEXEC_CORE=y CONFIG_HOTPLUG_SMT=y CONFIG_HOTPLUG_CORE_SYNC=y CONFIG_HOTPLUG_CORE_SYNC_DEAD=y @@ -929,7 +914,6 @@ CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y CONFIG_HAVE_ARCH_HUGE_VMAP=y CONFIG_HAVE_ARCH_HUGE_VMALLOC=y CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y -CONFIG_ARCH_WANT_PMD_MKWRITE=y CONFIG_HAVE_ARCH_SOFT_DIRTY=y CONFIG_HAVE_MOD_ARCH_SPECIFIC=y CONFIG_MODULES_USE_ELF_RELA=y @@ -1158,7 +1142,6 @@ CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_SLAB_FREELIST_HARDENED=y # CONFIG_SLUB_STATS is not set CONFIG_SLUB_CPU_PARTIAL=y -# CONFIG_RANDOM_KMALLOC_CACHES is not set # end of SLAB allocator options CONFIG_SHUFFLE_PAGE_ALLOCATOR=y @@ -1167,8 +1150,7 @@ CONFIG_SPARSEMEM=y CONFIG_SPARSEMEM_EXTREME=y CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y CONFIG_SPARSEMEM_VMEMMAP=y -CONFIG_ARCH_WANT_OPTIMIZE_DAX_VMEMMAP=y -CONFIG_ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP=y +CONFIG_ARCH_WANT_OPTIMIZE_VMEMMAP=y CONFIG_HAVE_FAST_GUP=y CONFIG_NUMA_KEEP_MEMINFO=y CONFIG_MEMORY_ISOLATION=y @@ -1180,7 +1162,6 @@ CONFIG_MEMORY_HOTPLUG=y CONFIG_MEMORY_HOTPLUG_DEFAULT_ONLINE=y CONFIG_MEMORY_HOTREMOVE=y CONFIG_MHP_MEMMAP_ON_MEMORY=y -CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y CONFIG_SPLIT_PTLOCK_CPUS=4 CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK=y CONFIG_MEMORY_BALLOON=y @@ -1211,6 +1192,7 @@ CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y CONFIG_USE_PERCPU_NUMA_NODE_ID=y CONFIG_HAVE_SETUP_PER_CPU_AREA=y +CONFIG_FRONTSWAP=y CONFIG_CMA=y # CONFIG_CMA_DEBUG is not set CONFIG_CMA_DEBUGFS=y @@ -1239,7 +1221,6 @@ CONFIG_VM_EVENT_COUNTERS=y # CONFIG_DMAPOOL_TEST is not set CONFIG_ARCH_HAS_PTE_SPECIAL=y CONFIG_MAPPING_DIRTY_HELPERS=y -CONFIG_MEMFD_CREATE=y CONFIG_SECRETMEM=y CONFIG_ANON_VMA_NAME=y CONFIG_USERFAULTFD=y @@ -1264,7 +1245,6 @@ CONFIG_NET=y CONFIG_COMPAT_NETLINK_MESSAGES=y CONFIG_NET_INGRESS=y CONFIG_NET_EGRESS=y -CONFIG_NET_XGRESS=y CONFIG_NET_REDIRECT=y CONFIG_SKB_EXTENSIONS=y @@ -2395,11 +2375,6 @@ CONFIG_MHI_BUS_PCI_GENERIC=m CONFIG_MHI_BUS_EP=m # end of Bus devices -# -# Cache Drivers -# -# end of Cache Drivers - CONFIG_CONNECTOR=y CONFIG_PROC_EVENTS=y @@ -2431,7 +2406,6 @@ CONFIG_GOOGLE_CBMEM=m CONFIG_GOOGLE_COREBOOT_TABLE=m CONFIG_GOOGLE_MEMCONSOLE=m # CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY is not set -CONFIG_GOOGLE_FRAMEBUFFER_COREBOOT=m CONFIG_GOOGLE_MEMCONSOLE_COREBOOT=m CONFIG_GOOGLE_VPD=m @@ -3042,7 +3016,6 @@ CONFIG_ATA_GENERIC=m CONFIG_PATA_LEGACY=m CONFIG_MD=y CONFIG_BLK_DEV_MD=m -CONFIG_MD_BITMAP_FILE=y CONFIG_MD_LINEAR=m CONFIG_MD_RAID0=m CONFIG_MD_RAID1=m @@ -3156,7 +3129,6 @@ CONFIG_AMT=m CONFIG_MACSEC=m CONFIG_NETCONSOLE=m CONFIG_NETCONSOLE_DYNAMIC=y -# CONFIG_NETCONSOLE_EXTENDED_LOG is not set CONFIG_NETPOLL=y CONFIG_NET_POLL_CONTROLLER=y CONFIG_NTB_NETDEV=m @@ -3430,7 +3402,7 @@ CONFIG_MLX5_TC_CT=y CONFIG_MLX5_TC_SAMPLE=y CONFIG_MLX5_CORE_EN_DCB=y CONFIG_MLX5_CORE_IPOIB=y -CONFIG_MLX5_MACSEC=y +CONFIG_MLX5_EN_MACSEC=y CONFIG_MLX5_EN_IPSEC=y CONFIG_MLX5_EN_TLS=y CONFIG_MLX5_SW_STEERING=y @@ -3631,11 +3603,9 @@ CONFIG_INTEL_XWAY_PHY=m CONFIG_LSI_ET1011C_PHY=m CONFIG_MARVELL_PHY=m CONFIG_MARVELL_10G_PHY=m -CONFIG_MARVELL_88Q2XXX_PHY=m CONFIG_MARVELL_88X2222_PHY=m CONFIG_MAXLINEAR_GPHY=m CONFIG_MEDIATEK_GE_PHY=m -# CONFIG_MEDIATEK_GE_SOC_PHY is not set CONFIG_MICREL_PHY=m CONFIG_MICROCHIP_T1S_PHY=m CONFIG_MICROCHIP_PHY=m @@ -4032,8 +4002,6 @@ CONFIG_MT76_SDIO=m CONFIG_MT76x02_LIB=m CONFIG_MT76x02_USB=m CONFIG_MT76_CONNAC_LIB=m -CONFIG_MT792x_LIB=m -CONFIG_MT792x_USB=m CONFIG_MT76x0_COMMON=m CONFIG_MT76x0U=m CONFIG_MT76x0E=m @@ -4477,7 +4445,6 @@ CONFIG_TOUCHSCREEN_ZFORCE=m CONFIG_TOUCHSCREEN_COLIBRI_VF50=m CONFIG_TOUCHSCREEN_ROHM_BU21023=m CONFIG_TOUCHSCREEN_IQS5XX=m -CONFIG_TOUCHSCREEN_IQS7211=m CONFIG_TOUCHSCREEN_ZINITIX=m CONFIG_TOUCHSCREEN_HIMAX_HX83112B=m CONFIG_INPUT_MISC=y @@ -4578,6 +4545,8 @@ CONFIG_HYPERV_KEYBOARD=m CONFIG_SERIO_GPIO_PS2=m CONFIG_USERIO=m CONFIG_GAMEPORT=m +CONFIG_GAMEPORT_NS558=m +CONFIG_GAMEPORT_L4=m CONFIG_GAMEPORT_EMU10K1=m CONFIG_GAMEPORT_FM801=m # end of Hardware I/O ports @@ -4664,6 +4633,7 @@ CONFIG_SERIAL_MCTRL_GPIO=y CONFIG_SERIAL_NONSTANDARD=y CONFIG_MOXA_INTELLIO=m CONFIG_MOXA_SMARTIO=m +CONFIG_SYNCLINK_GT=m CONFIG_N_HDLC=m CONFIG_IPWIRELESS=m CONFIG_N_GSM=m @@ -4733,6 +4703,7 @@ CONFIG_XILLYBUS_CLASS=m CONFIG_XILLYBUS=m CONFIG_XILLYBUS_PCIE=m CONFIG_XILLYUSB=m +CONFIG_DDCCI=m # end of Character devices # @@ -4860,7 +4831,6 @@ CONFIG_SPI_AXI_SPI_ENGINE=m CONFIG_SPI_BITBANG=m CONFIG_SPI_BUTTERFLY=m CONFIG_SPI_CADENCE=m -CONFIG_SPI_CS42L43=m CONFIG_SPI_DESIGNWARE=m CONFIG_SPI_DW_DMA=y CONFIG_SPI_DW_PCI=m @@ -4928,7 +4898,6 @@ CONFIG_PTP_1588_CLOCK_INES=m CONFIG_PTP_1588_CLOCK_KVM=m CONFIG_PTP_1588_CLOCK_IDT82P33=m CONFIG_PTP_1588_CLOCK_IDTCM=m -CONFIG_PTP_1588_CLOCK_MOCK=m CONFIG_PTP_1588_CLOCK_VMW=m CONFIG_PTP_1588_CLOCK_OCP=m CONFIG_PTP_DFL_TOD=m @@ -4946,7 +4915,6 @@ CONFIG_PINCTRL_MCP23S08_I2C=m CONFIG_PINCTRL_MCP23S08_SPI=m CONFIG_PINCTRL_MCP23S08=m CONFIG_PINCTRL_SX150X=y -CONFIG_PINCTRL_CS42L43=m CONFIG_PINCTRL_MADERA=m CONFIG_PINCTRL_CS47L15=y CONFIG_PINCTRL_CS47L35=y @@ -5026,7 +4994,6 @@ CONFIG_GPIO_WS16C48=m # I2C GPIO expanders # CONFIG_GPIO_FXL6408=m -CONFIG_GPIO_DS4520=m CONFIG_GPIO_MAX7300=m CONFIG_GPIO_MAX732X=m CONFIG_GPIO_PCA953X=m @@ -5286,7 +5253,6 @@ CONFIG_SENSORS_GL520SM=m CONFIG_SENSORS_G760A=m CONFIG_SENSORS_G762=m CONFIG_SENSORS_HIH6130=m -CONFIG_SENSORS_HS3001=m CONFIG_SENSORS_IBMAEM=m CONFIG_SENSORS_IBMPEX=m CONFIG_SENSORS_IIO_HWMON=m @@ -5398,7 +5364,6 @@ CONFIG_SENSORS_MAX34440=m CONFIG_SENSORS_MAX8688=m CONFIG_SENSORS_MP2888=m CONFIG_SENSORS_MP2975=m -CONFIG_SENSORS_MP2975_REGULATOR=y CONFIG_SENSORS_MP5023=m CONFIG_SENSORS_MPQ7932_REGULATOR=y CONFIG_SENSORS_MPQ7932=m @@ -5441,6 +5406,7 @@ CONFIG_SENSORS_SCH56XX_COMMON=m CONFIG_SENSORS_SCH5627=m CONFIG_SENSORS_SCH5636=m CONFIG_SENSORS_STTS751=m +CONFIG_SENSORS_SMM665=m CONFIG_SENSORS_ADC128D818=m CONFIG_SENSORS_ADS7828=m CONFIG_SENSORS_ADS7871=m @@ -5665,9 +5631,6 @@ CONFIG_MFD_BD9571MWV=m CONFIG_MFD_AXP20X=m CONFIG_MFD_AXP20X_I2C=m CONFIG_MFD_CROS_EC_DEV=m -CONFIG_MFD_CS42L43=m -CONFIG_MFD_CS42L43_I2C=m -CONFIG_MFD_CS42L43_SDW=m CONFIG_MFD_MADERA=m CONFIG_MFD_MADERA_I2C=m CONFIG_MFD_MADERA_SPI=m @@ -5803,7 +5766,6 @@ CONFIG_REGULATOR_ARIZONA_LDO1=m CONFIG_REGULATOR_ARIZONA_MICSUPP=m CONFIG_REGULATOR_AS3711=m CONFIG_REGULATOR_ATC260X=m -CONFIG_REGULATOR_AW37503=m CONFIG_REGULATOR_AXP20X=m CONFIG_REGULATOR_BCM590XX=m CONFIG_REGULATOR_BD9571MWV=m @@ -5828,7 +5790,6 @@ CONFIG_REGULATOR_LTC3676=m CONFIG_REGULATOR_MAX14577=m CONFIG_REGULATOR_MAX1586=m CONFIG_REGULATOR_MAX77541=m -CONFIG_REGULATOR_MAX77857=m CONFIG_REGULATOR_MAX8649=m CONFIG_REGULATOR_MAX8660=m CONFIG_REGULATOR_MAX8893=m @@ -5879,7 +5840,6 @@ CONFIG_REGULATOR_RT6245=m CONFIG_REGULATOR_RTQ2134=m CONFIG_REGULATOR_RTMV20=m CONFIG_REGULATOR_RTQ6752=m -CONFIG_REGULATOR_RTQ2208=m CONFIG_REGULATOR_SKY81452=m CONFIG_REGULATOR_SLG51000=m CONFIG_REGULATOR_SY7636A=m @@ -5988,8 +5948,8 @@ CONFIG_V4L2_MEM2MEM_DEV=m CONFIG_V4L2_FLASH_LED_CLASS=m CONFIG_V4L2_FWNODE=m CONFIG_V4L2_ASYNC=m -CONFIG_V4L2_CCI=m -CONFIG_V4L2_CCI_I2C=m +CONFIG_VIDEOBUF_GEN=m +CONFIG_VIDEOBUF_DMA_SG=m # end of Video4Linux options # @@ -6237,9 +6197,9 @@ CONFIG_DVB_BUDGET_CORE=m CONFIG_DVB_BUDGET=m CONFIG_DVB_BUDGET_CI=m CONFIG_DVB_BUDGET_AV=m -CONFIG_VIDEO_IPU3_CIO2=m -CONFIG_INTEL_VSC=m CONFIG_IPU_BRIDGE=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_CIO2_BRIDGE=y CONFIG_RADIO_ADAPTERS=m CONFIG_RADIO_MAXIRADIO=m CONFIG_RADIO_SAA7706H=m @@ -6492,7 +6452,6 @@ CONFIG_VIDEO_ET8EK8=m CONFIG_VIDEO_AD5820=m CONFIG_VIDEO_AK7375=m CONFIG_VIDEO_DW9714=m -CONFIG_VIDEO_DW9719=m CONFIG_VIDEO_DW9768=m CONFIG_VIDEO_DW9807_VCM=m # end of Lens drivers @@ -6540,11 +6499,6 @@ CONFIG_VIDEO_UPD64083=m CONFIG_VIDEO_SAA6752HS=m CONFIG_VIDEO_M52790=m -# -# Video serializers and deserializers -# -# end of Video serializers and deserializers - # # SPI I2C drivers auto-selected by 'Autoselect ancillary drivers' # @@ -6767,8 +6721,6 @@ CONFIG_DVB_DUMMY_FE=m CONFIG_APERTURE_HELPERS=y CONFIG_VIDEO_CMDLINE=y CONFIG_VIDEO_NOMODESET=y -# CONFIG_AUXDISPLAY is not set -# CONFIG_PANEL is not set CONFIG_AGP=y CONFIG_AGP_AMD64=m CONFIG_AGP_INTEL=m @@ -6791,7 +6743,6 @@ CONFIG_DRM_DISPLAY_HDMI_HELPER=y CONFIG_DRM_DP_AUX_CHARDEV=y CONFIG_DRM_DP_CEC=y CONFIG_DRM_TTM=m -CONFIG_DRM_EXEC=m CONFIG_DRM_BUDDY=m CONFIG_DRM_VRAM_HELPER=m CONFIG_DRM_TTM_HELPER=m @@ -6894,7 +6845,6 @@ CONFIG_DRM_ANALOGIX_ANX78XX=m CONFIG_DRM_ANALOGIX_DP=m # end of Display Interface Bridges -CONFIG_DRM_LOONGSON=m # CONFIG_DRM_ETNAVIV is not set CONFIG_DRM_BOCHS=m CONFIG_DRM_CIRRUS_QEMU=m @@ -6925,7 +6875,27 @@ CONFIG_DRM_PRIVACY_SCREEN=y # # Frame buffer Devices # +CONFIG_FB_NOTIFY=y CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +CONFIG_FB_SYS_FILLRECT=y +CONFIG_FB_SYS_COPYAREA=y +CONFIG_FB_SYS_IMAGEBLIT=y +# CONFIG_FB_FOREIGN_ENDIAN is not set +CONFIG_FB_SYS_FOPS=y +CONFIG_FB_DEFERRED_IO=y +CONFIG_FB_IO_HELPERS=y +CONFIG_FB_SYS_HELPERS=y +CONFIG_FB_SYS_HELPERS_DEFERRED=y +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# # CONFIG_FB_CIRRUS is not set # CONFIG_FB_PM2 is not set # CONFIG_FB_CYBER2000 is not set @@ -6972,25 +6942,6 @@ CONFIG_XEN_FBDEV_FRONTEND=m # CONFIG_FB_HYPERV is not set # CONFIG_FB_SSD1307 is not set # CONFIG_FB_SM712 is not set -CONFIG_FB_CORE=y -CONFIG_FB_NOTIFY=y -# CONFIG_FIRMWARE_EDID is not set -CONFIG_FB_DEVICE=y -CONFIG_FB_CFB_FILLRECT=y -CONFIG_FB_CFB_COPYAREA=y -CONFIG_FB_CFB_IMAGEBLIT=y -CONFIG_FB_SYS_FILLRECT=y -CONFIG_FB_SYS_COPYAREA=y -CONFIG_FB_SYS_IMAGEBLIT=y -# CONFIG_FB_FOREIGN_ENDIAN is not set -CONFIG_FB_SYS_FOPS=y -CONFIG_FB_DEFERRED_IO=y -CONFIG_FB_DMAMEM_HELPERS=y -CONFIG_FB_IOMEM_HELPERS=y -CONFIG_FB_SYSMEM_HELPERS=y -CONFIG_FB_SYSMEM_HELPERS_DEFERRED=y -# CONFIG_FB_MODE_HELPERS is not set -# CONFIG_FB_TILEBLITTING is not set # end of Frame buffer Devices # @@ -7020,6 +6971,7 @@ CONFIG_BACKLIGHT_MAX8925=m CONFIG_BACKLIGHT_MT6370=m CONFIG_BACKLIGHT_APPLE=m CONFIG_BACKLIGHT_QCOM_WLED=m +CONFIG_BACKLIGHT_DDCCI=m CONFIG_BACKLIGHT_RT4831=m CONFIG_BACKLIGHT_SAHARA=m CONFIG_BACKLIGHT_WM831X=m @@ -7080,7 +7032,7 @@ CONFIG_SND_HWDEP=m CONFIG_SND_SEQ_DEVICE=m CONFIG_SND_RAWMIDI=m CONFIG_SND_UMP=m -CONFIG_SND_UMP_LEGACY_RAWMIDI=y +# CONFIG_SND_UMP_LEGACY_RAWMIDI is not set CONFIG_SND_COMPRESS_OFFLOAD=m CONFIG_SND_JACK=y CONFIG_SND_JACK_INPUT_DEV=y @@ -7224,10 +7176,6 @@ CONFIG_SND_HDA_SCODEC_CS35L41=m CONFIG_SND_HDA_CS_DSP_CONTROLS=m CONFIG_SND_HDA_SCODEC_CS35L41_I2C=m CONFIG_SND_HDA_SCODEC_CS35L41_SPI=m -CONFIG_SND_HDA_SCODEC_CS35L56=m -CONFIG_SND_HDA_SCODEC_CS35L56_I2C=m -CONFIG_SND_HDA_SCODEC_CS35L56_SPI=m -CONFIG_SND_HDA_SCODEC_TAS2781_I2C=m CONFIG_SND_HDA_CODEC_REALTEK=m CONFIG_SND_HDA_CODEC_ANALOG=m CONFIG_SND_HDA_CODEC_SIGMATEL=m @@ -7313,7 +7261,6 @@ CONFIG_SND_SOC_AMD_YC_MACH=m CONFIG_SND_AMD_ACP_CONFIG=m CONFIG_SND_SOC_AMD_ACP_COMMON=m CONFIG_SND_SOC_AMD_ACP_PDM=m -CONFIG_SND_SOC_AMD_ACP_LEGACY_COMMON=m CONFIG_SND_SOC_AMD_ACP_I2S=m CONFIG_SND_SOC_AMD_ACP_PCM=m CONFIG_SND_SOC_AMD_ACP_PCI=m @@ -7389,7 +7336,6 @@ CONFIG_SND_SOC_INTEL_AVS=m # CONFIG_SND_SOC_INTEL_AVS_MACH_DA7219=m CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC=m -CONFIG_SND_SOC_INTEL_AVS_MACH_ES8336=m CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO=m CONFIG_SND_SOC_INTEL_AVS_MACH_I2S_TEST=m CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98927=m @@ -7400,7 +7346,6 @@ CONFIG_SND_SOC_INTEL_AVS_MACH_PROBE=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT274=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT286=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT298=m -CONFIG_SND_SOC_INTEL_AVS_MACH_RT5663=m CONFIG_SND_SOC_INTEL_AVS_MACH_RT5682=m CONFIG_SND_SOC_INTEL_AVS_MACH_SSM4567=m # end of Intel AVS Machine drivers @@ -7468,9 +7413,7 @@ CONFIG_SND_SOC_SOF_INTEL_IPC4=y CONFIG_SND_SOC_SOF_AMD_TOPLEVEL=m CONFIG_SND_SOC_SOF_AMD_COMMON=m CONFIG_SND_SOC_SOF_AMD_RENOIR=m -CONFIG_SND_SOC_SOF_AMD_VANGOGH=m CONFIG_SND_SOC_SOF_AMD_REMBRANDT=m -CONFIG_SND_SOC_SOF_ACP_PROBES=m CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL=y CONFIG_SND_SOC_SOF_INTEL_HIFI_EP_IPC=m CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP=m @@ -7496,8 +7439,6 @@ CONFIG_SND_SOC_SOF_ELKHARTLAKE=m CONFIG_SND_SOC_SOF_ALDERLAKE=m CONFIG_SND_SOC_SOF_INTEL_MTL=m CONFIG_SND_SOC_SOF_METEORLAKE=m -CONFIG_SND_SOC_SOF_INTEL_LNL=m -CONFIG_SND_SOC_SOF_LUNARLAKE=m CONFIG_SND_SOC_SOF_HDA_COMMON=m CONFIG_SND_SOC_SOF_HDA_MLINK=m CONFIG_SND_SOC_SOF_HDA_LINK=y @@ -7549,11 +7490,9 @@ CONFIG_SND_SOC_AK4642=m CONFIG_SND_SOC_AK5386=m CONFIG_SND_SOC_AK5558=m CONFIG_SND_SOC_ALC5623=m -CONFIG_SND_SOC_AUDIO_IIO_AUX=m CONFIG_SND_SOC_AW8738=m CONFIG_SND_SOC_AW88395_LIB=m CONFIG_SND_SOC_AW88395=m -CONFIG_SND_SOC_AW88261=m CONFIG_SND_SOC_BD28623=m # CONFIG_SND_SOC_BT_SCO is not set CONFIG_SND_SOC_CHV3_CODEC=m @@ -7578,8 +7517,6 @@ CONFIG_SND_SOC_CS35L56_SDW=m CONFIG_SND_SOC_CS42L42_CORE=m CONFIG_SND_SOC_CS42L42=m CONFIG_SND_SOC_CS42L42_SDW=m -CONFIG_SND_SOC_CS42L43=m -CONFIG_SND_SOC_CS42L43_SDW=m CONFIG_SND_SOC_CS42L51=m CONFIG_SND_SOC_CS42L51_I2C=m CONFIG_SND_SOC_CS42L52=m @@ -7662,7 +7599,6 @@ CONFIG_SND_SOC_RT298=m CONFIG_SND_SOC_RT1011=m CONFIG_SND_SOC_RT1015=m CONFIG_SND_SOC_RT1015P=m -CONFIG_SND_SOC_RT1017_SDCA_SDW=m CONFIG_SND_SOC_RT1019=m CONFIG_SND_SOC_RT1308=m CONFIG_SND_SOC_RT1308_SDW=m @@ -7750,7 +7686,6 @@ CONFIG_SND_SOC_TS3A227E=m CONFIG_SND_SOC_TSCS42XX=m CONFIG_SND_SOC_TSCS454=m CONFIG_SND_SOC_UDA1334=m -CONFIG_SND_SOC_WCD_CLASSH=m CONFIG_SND_SOC_WCD9335=m CONFIG_SND_SOC_WCD_MBHC=m CONFIG_SND_SOC_WCD934X=m @@ -7863,7 +7798,6 @@ CONFIG_HID_HOLTEK=m CONFIG_HOLTEK_FF=y CONFIG_HID_VIVALDI_COMMON=m CONFIG_HID_GOOGLE_HAMMER=m -CONFIG_HID_GOOGLE_STADIA_FF=m CONFIG_HID_VIVALDI=m CONFIG_HID_GT683R=m CONFIG_HID_KEYTOUCH=m @@ -8318,7 +8252,6 @@ CONFIG_USB_F_UAC1_LEGACY=m CONFIG_USB_F_UAC2=m CONFIG_USB_F_UVC=m CONFIG_USB_F_MIDI=m -CONFIG_USB_F_MIDI2=m CONFIG_USB_F_HID=m CONFIG_USB_F_PRINTER=m CONFIG_USB_F_TCM=m @@ -8339,7 +8272,6 @@ CONFIG_USB_CONFIGFS_F_UAC1=y CONFIG_USB_CONFIGFS_F_UAC1_LEGACY=y CONFIG_USB_CONFIGFS_F_UAC2=y CONFIG_USB_CONFIGFS_F_MIDI=y -CONFIG_USB_CONFIGFS_F_MIDI2=y CONFIG_USB_CONFIGFS_F_HID=y CONFIG_USB_CONFIGFS_F_UVC=y CONFIG_USB_CONFIGFS_F_PRINTER=y @@ -8456,6 +8388,7 @@ CONFIG_MMC_SDHCI_XENON=m CONFIG_SCSI_UFSHCD=m CONFIG_SCSI_UFS_BSG=y CONFIG_SCSI_UFS_CRYPTO=y +CONFIG_SCSI_UFS_HPB=y CONFIG_SCSI_UFS_HWMON=y CONFIG_SCSI_UFSHCD_PCI=m # CONFIG_SCSI_UFS_DWC_TC_PCI is not set @@ -8507,7 +8440,6 @@ CONFIG_LEDS_LP8788=m CONFIG_LEDS_PCA955X=m CONFIG_LEDS_PCA955X_GPIO=y CONFIG_LEDS_PCA963X=m -CONFIG_LEDS_PCA995X=m CONFIG_LEDS_WM831X_STATUS=m CONFIG_LEDS_WM8350=m CONFIG_LEDS_DA903X=m @@ -8587,7 +8519,6 @@ CONFIG_LEDS_TRIGGER_BLKDEV=m CONFIG_LEDS_SIEMENS_SIMATIC_IPC=m CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE=m CONFIG_LEDS_SIEMENS_SIMATIC_IPC_F7188X=m -CONFIG_LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE=m CONFIG_ACCESSIBILITY=y CONFIG_A11Y_BRAILLE_CONSOLE=y @@ -8790,6 +8721,7 @@ CONFIG_RTC_DRV_M48T86=m CONFIG_RTC_DRV_M48T35=m CONFIG_RTC_DRV_M48T59=m CONFIG_RTC_DRV_MSM6242=m +CONFIG_RTC_DRV_BQ4802=m CONFIG_RTC_DRV_RP5C01=m CONFIG_RTC_DRV_WM831X=m CONFIG_RTC_DRV_WM8350=m @@ -8828,7 +8760,6 @@ CONFIG_INTEL_IDXD_SVM=y CONFIG_INTEL_IDXD_PERFMON=y CONFIG_INTEL_IOATDMA=m CONFIG_PLX_DMA=m -CONFIG_XILINX_DMA=m CONFIG_XILINX_XDMA=m CONFIG_AMD_PTDMA=m CONFIG_QCOM_HIDMA_MGMT=m @@ -8865,6 +8796,8 @@ CONFIG_DMABUF_HEAPS_CMA=y # end of DMABUF options CONFIG_DCA=m +# CONFIG_AUXDISPLAY is not set +# CONFIG_PANEL is not set CONFIG_UIO=m CONFIG_UIO_CIF=m CONFIG_UIO_PDRV_GENIRQ=m @@ -8878,8 +8811,6 @@ CONFIG_UIO_MF624=m CONFIG_UIO_HV_GENERIC=m CONFIG_UIO_DFL=m CONFIG_VFIO=m -CONFIG_VFIO_DEVICE_CDEV=y -CONFIG_VFIO_GROUP=y CONFIG_VFIO_CONTAINER=y CONFIG_VFIO_IOMMU_TYPE1=m # CONFIG_VFIO_NOIOMMU is not set @@ -8895,7 +8826,6 @@ CONFIG_VFIO_PCI=m CONFIG_VFIO_PCI_VGA=y CONFIG_VFIO_PCI_IGD=y CONFIG_MLX5_VFIO_PCI=m -CONFIG_PDS_VFIO_PCI=m # end of VFIO support for PCI devices CONFIG_VFIO_MDEV=m @@ -8981,7 +8911,6 @@ CONFIG_XEN_PVCALLS_FRONTEND=m CONFIG_XEN_PVCALLS_BACKEND=y CONFIG_XEN_SCSI_BACKEND=m CONFIG_XEN_PRIVCMD=m -CONFIG_XEN_PRIVCMD_IRQFD=y CONFIG_XEN_ACPI_PROCESSOR=m CONFIG_XEN_MCE_LOG=y CONFIG_XEN_HAVE_PVMMU=y @@ -9178,7 +9107,6 @@ CONFIG_GPD_POCKET_FAN=m CONFIG_X86_PLATFORM_DRIVERS_HP=y CONFIG_HP_ACCEL=m CONFIG_HP_WMI=m -CONFIG_HP_BIOSCFG=m CONFIG_WIRELESS_HOTKEY=m CONFIG_IBM_RTL=m CONFIG_IDEAPAD_LAPTOP=m @@ -9271,12 +9199,7 @@ CONFIG_INTEL_SCU_PCI=y CONFIG_INTEL_SCU_PLATFORM=m CONFIG_INTEL_SCU_IPC_UTIL=m CONFIG_SIEMENS_SIMATIC_IPC=m -CONFIG_SIEMENS_SIMATIC_IPC_BATT=m -CONFIG_SIEMENS_SIMATIC_IPC_BATT_APOLLOLAKE=m -CONFIG_SIEMENS_SIMATIC_IPC_BATT_ELKHARTLAKE=m -CONFIG_SIEMENS_SIMATIC_IPC_BATT_F7188X=m CONFIG_WINMATE_FM07_KEYS=m -CONFIG_SEL3350_PLATFORM=m CONFIG_STEAMDECK=m CONFIG_P2SB=y CONFIG_HAVE_CLK=y @@ -9689,7 +9612,6 @@ CONFIG_HID_SENSOR_IIO_COMMON=m CONFIG_HID_SENSOR_IIO_TRIGGER=m # end of Hid Sensor IIO Common -CONFIG_IIO_INV_SENSORS_TIMESTAMP=m CONFIG_IIO_MS_SENSORS_I2C=m # @@ -9746,7 +9668,6 @@ CONFIG_MAX517=m CONFIG_MAX5522=m CONFIG_MAX5821=m CONFIG_MCP4725=m -CONFIG_MCP4728=m CONFIG_MCP4922=m CONFIG_TI_DAC082S085=m CONFIG_TI_DAC5571=m @@ -10060,7 +9981,6 @@ CONFIG_AS3935=m # Proximity and distance sensors # CONFIG_CROS_EC_MKBP_PROXIMITY=m -CONFIG_IRSD200=m CONFIG_ISL29501=m CONFIG_LIDAR_LITE_V2=m CONFIG_MB1232=m @@ -10302,7 +10222,6 @@ CONFIG_HTE=y CONFIG_DCACHE_WORD_ACCESS=y CONFIG_VALIDATE_FS_PARSER=y CONFIG_FS_IOMAP=y -CONFIG_BUFFER_HEAD=y CONFIG_LEGACY_DIRECT_IO=y # CONFIG_EXT2_FS is not set # CONFIG_EXT3_FS is not set @@ -10333,7 +10252,6 @@ CONFIG_XFS_POSIX_ACL=y CONFIG_XFS_RT=y CONFIG_XFS_DRAIN_INTENTS=y CONFIG_XFS_ONLINE_SCRUB=y -# CONFIG_XFS_ONLINE_SCRUB_STATS is not set CONFIG_XFS_ONLINE_REPAIR=y # CONFIG_XFS_WARN is not set # CONFIG_XFS_DEBUG is not set @@ -10398,6 +10316,7 @@ CONFIG_QUOTA_TREE=m CONFIG_QFMT_V1=m CONFIG_QFMT_V2=m CONFIG_QUOTACTL=y +CONFIG_AUTOFS4_FS=y CONFIG_AUTOFS_FS=y CONFIG_FUSE_FS=m CONFIG_CUSE=m @@ -10409,7 +10328,6 @@ CONFIG_OVERLAY_FS_REDIRECT_DIR=y CONFIG_OVERLAY_FS_INDEX=y CONFIG_OVERLAY_FS_XINO_AUTO=y CONFIG_OVERLAY_FS_METACOPY=y -# CONFIG_OVERLAY_FS_DEBUG is not set # # Caches @@ -10470,11 +10388,11 @@ CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_TMPFS_XATTR=y CONFIG_TMPFS_INODE64=y -CONFIG_TMPFS_QUOTA=y CONFIG_HUGETLBFS=y CONFIG_HUGETLB_PAGE=y CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y # CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON is not set +CONFIG_MEMFD_CREATE=y CONFIG_ARCH_HAS_GIGANTIC_PAGE=y CONFIG_CONFIGFS_FS=y CONFIG_EFIVAR_FS=y @@ -10545,7 +10463,19 @@ CONFIG_ROMFS_BACKED_BY_BLOCK=y CONFIG_ROMFS_ON_BLOCK=y CONFIG_PSTORE=y CONFIG_PSTORE_DEFAULT_KMSG_BYTES=10240 +CONFIG_PSTORE_DEFLATE_COMPRESS=m +CONFIG_PSTORE_LZO_COMPRESS=m +CONFIG_PSTORE_LZ4_COMPRESS=m +CONFIG_PSTORE_LZ4HC_COMPRESS=m +# CONFIG_PSTORE_842_COMPRESS is not set +CONFIG_PSTORE_ZSTD_COMPRESS=y CONFIG_PSTORE_COMPRESS=y +# CONFIG_PSTORE_DEFLATE_COMPRESS_DEFAULT is not set +# CONFIG_PSTORE_LZO_COMPRESS_DEFAULT is not set +# CONFIG_PSTORE_LZ4_COMPRESS_DEFAULT is not set +# CONFIG_PSTORE_LZ4HC_COMPRESS_DEFAULT is not set +CONFIG_PSTORE_ZSTD_COMPRESS_DEFAULT=y +CONFIG_PSTORE_COMPRESS_DEFAULT="zstd" # CONFIG_PSTORE_CONSOLE is not set # CONFIG_PSTORE_PMSG is not set # CONFIG_PSTORE_FTRACE is not set @@ -10566,7 +10496,6 @@ CONFIG_EROFS_FS_POSIX_ACL=y CONFIG_EROFS_FS_SECURITY=y CONFIG_EROFS_FS_ZIP=y CONFIG_EROFS_FS_ZIP_LZMA=y -CONFIG_EROFS_FS_ZIP_DEFLATE=y CONFIG_EROFS_FS_ONDEMAND=y CONFIG_EROFS_FS_PCPU_KTHREAD=y CONFIG_EROFS_FS_PCPU_KTHREAD_HIPRI=y @@ -10613,6 +10542,8 @@ CONFIG_SUNRPC_GSS=m CONFIG_SUNRPC_BACKCHANNEL=y CONFIG_SUNRPC_SWAP=y CONFIG_RPCSEC_GSS_KRB5=m +CONFIG_RPCSEC_GSS_KRB5_CRYPTOSYSTEM=y +# CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_DES is not set CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1=y CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA=y CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2=y @@ -10700,7 +10631,6 @@ CONFIG_NLS_MAC_INUIT=m CONFIG_NLS_MAC_ROMANIAN=m CONFIG_NLS_MAC_TURKISH=m CONFIG_NLS_UTF8=m -CONFIG_NLS_UCS2_UTILS=m CONFIG_DLM=m CONFIG_DLM_DEBUG=y CONFIG_UNICODE=y @@ -10739,7 +10669,6 @@ CONFIG_SECURITY_SELINUX_DEVELOP=y CONFIG_SECURITY_SELINUX_AVC_STATS=y CONFIG_SECURITY_SELINUX_SIDTAB_HASH_BITS=9 CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE=256 -# CONFIG_SECURITY_SELINUX_DEBUG is not set CONFIG_SECURITY_SMACK=y CONFIG_SECURITY_SMACK_BRINGUP=y CONFIG_SECURITY_SMACK_NETFILTER=y @@ -10778,6 +10707,7 @@ CONFIG_INTEGRITY_MACHINE_KEYRING=y CONFIG_LOAD_UEFI_KEYS=y CONFIG_INTEGRITY_AUDIT=y # CONFIG_IMA is not set +# CONFIG_IMA_KEYRINGS_PERMIT_SIGNED_BY_BUILTIN_OR_SECONDARY is not set # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set # CONFIG_EVM is not set # CONFIG_DEFAULT_SECURITY_SELINUX is not set @@ -10807,13 +10737,6 @@ CONFIG_CC_HAS_ZERO_CALL_USED_REGS=y # CONFIG_ZERO_CALL_USED_REGS is not set # end of Memory initialization -# -# Hardening of kernel data structures -# -CONFIG_LIST_HARDENED=y -# CONFIG_BUG_ON_DATA_CORRUPTION is not set -# end of Hardening of kernel data structures - CONFIG_RANDSTRUCT_NONE=y # CONFIG_RANDSTRUCT_FULL is not set # CONFIG_RANDSTRUCT_PERFORMANCE is not set @@ -11218,10 +11141,9 @@ CONFIG_NEED_DMA_MAP_STATE=y CONFIG_ARCH_DMA_ADDR_T_64BIT=y CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y CONFIG_SWIOTLB=y -# CONFIG_SWIOTLB_DYNAMIC is not set CONFIG_DMA_COHERENT_POOL=y CONFIG_DMA_CMA=y -# CONFIG_DMA_NUMA_CMA is not set +# CONFIG_DMA_PERNUMA_CMA is not set # # Default contiguous memory area size: @@ -11486,6 +11408,7 @@ CONFIG_DEBUG_LIST=y # CONFIG_DEBUG_PLIST is not set # CONFIG_DEBUG_SG is not set # CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_BUG_ON_DATA_CORRUPTION is not set # CONFIG_DEBUG_MAPLE_TREE is not set # end of Debug kernel data structures @@ -11691,4 +11614,3 @@ CONFIG_MEMTEST=y # # end of Rust hacking # end of Kernel hacking - diff --git a/patches/0001-pikaos-base-all.patch b/patches/0001-pikaos-base-all.patch new file mode 100644 index 0000000..24e3240 --- /dev/null +++ b/patches/0001-pikaos-base-all.patch @@ -0,0 +1,48677 @@ +diff '--color=auto' -uraN a/Documentation/ABI/stable/sysfs-block b/Documentation/ABI/stable/sysfs-block +--- a/Documentation/ABI/stable/sysfs-block 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/ABI/stable/sysfs-block 2023-11-04 16:35:57.824650332 +0300 +@@ -101,6 +101,16 @@ + devices that support receiving integrity metadata. + + ++What: /sys/block//linked_leds ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Directory that contains symbolic links to all LEDs that ++ are associated with (linked to) this block device by the ++ blkdev LED trigger. Only present when at least one LED ++ is linked. (See Documentation/leds/ledtrig-blkdev.rst.) ++ ++ + What: /sys/block///alignment_offset + Date: April 2009 + Contact: Martin K. Petersen +diff '--color=auto' -uraN a/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev +--- a/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev 1970-01-01 03:00:00.000000000 +0300 ++++ b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev 2023-11-04 16:35:57.824650332 +0300 +@@ -0,0 +1,78 @@ ++What: /sys/class/leds//blink_time ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Time (in milliseconds) that the LED will be on during a single ++ "blink". ++ ++What: /sys/class/leds//check_interval ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Interval (in milliseconds) between checks of the block devices ++ linked to this LED. The LED will be blinked if the correct type ++ of activity (see blink_on_{read,write,discard,flush} attributes) ++ has occurred on any of the linked devices since the previous ++ check. ++ ++What: /sys/class/leds//blink_on_read ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Boolean that determines whether the LED will blink in response ++ to read activity on any of its linked block devices. ++ ++What: /sys/class/leds//blink_on_write ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Boolean that determines whether the LED will blink in response ++ to write activity on any of its linked block devices. ++ ++What: /sys/class/leds//blink_on_discard ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Boolean that determines whether the LED will blink in response ++ to discard activity on any of its linked block devices. ++ ++What: /sys/class/leds//blink_on_flush ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Boolean that determines whether the LED will blink in response ++ to cache flush activity on any of its linked block devices. ++ ++What: /sys/class/leds//link_dev_by_path ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Associate a block device with this LED by writing the path to ++ the device special file (e.g. /dev/sda) to this attribute. ++ Symbolic links are followed. ++ ++What: /sys/class/leds//unlink_dev_by_path ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Remove the association between this LED and a block device by ++ writing the path to the device special file (e.g. /dev/sda) to ++ this attribute. Symbolic links are followed. ++ ++What: /sys/class/leds//unlink_dev_by_name ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Remove the association between this LED and a block device by ++ writing the kernel name of the device (e.g. sda) to this ++ attribute. ++ ++What: /sys/class/leds//linked_devices ++Date: January 2023 ++Contact: Ian Pilcher ++Description: ++ Directory containing links to all block devices that are ++ associated with this LED. (Note that the names of the ++ symbolic links in this directory are *kernel* names, which ++ may not match the device special file paths written to ++ link_device and unlink_device.) +diff '--color=auto' -uraN a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi +--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi 2023-11-04 16:36:53.525649209 +0300 +@@ -98,3 +98,89 @@ + Enable an LCD response-time boost to reduce or remove ghosting: + * 0 - Disable, + * 1 - Enable ++ ++What: /sys/devices/platform//charge_mode ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Get the current charging mode being used: ++ * 1 - Barrel connected charger, ++ * 2 - USB-C charging ++ * 3 - Both connected, barrel used for charging ++ ++What: /sys/devices/platform//egpu_connected ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Show if the egpu (XG Mobile) is correctly connected: ++ * 0 - False, ++ * 1 - True ++ ++What: /sys/devices/platform//mini_led_mode ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Change the mini-LED mode: ++ * 0 - Single-zone, ++ * 1 - Multi-zone ++ ++What: /sys/devices/platform//ppt_pl1_spl ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD. ++ Shown on Intel+Nvidia or AMD+Nvidia based systems. ++ * min=5, max=250 ++ ++What: /sys/devices/platform//ppt_pl2_sppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT, ++ on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems. ++ * min=5, max=250 ++ ++What: /sys/devices/platform//ppt_fppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only. ++ * min=5, max=250 ++ ++What: /sys/devices/platform//ppt_apu_sppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the APU SPPT limit. Shown on full AMD systems only. ++ * min=5, max=130 ++ ++What: /sys/devices/platform//ppt_platform_sppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the platform SPPT limit. Shown on full AMD systems only. ++ * min=5, max=130 ++ ++What: /sys/devices/platform//nv_dynamic_boost ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the dynamic boost limit of the Nvidia dGPU: ++ * min=5, max=25 ++ ++What: /sys/devices/platform//nv_temp_target ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the target temperature limit of the Nvidia dGPU: ++ * min=75, max=87 +diff '--color=auto' -uraN a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst +--- a/Documentation/admin-guide/cgroup-v2.rst 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/admin-guide/cgroup-v2.rst 2023-11-04 16:36:05.664790922 +0300 +@@ -1121,6 +1121,16 @@ + values similar to the sched_setattr(2). This maximum utilization + value is used to clamp the task specific maximum utilization clamp. + ++ cpu.latency.nice ++ A read-write single value file which exists on non-root ++ cgroups. The default is "0". ++ ++ The nice value is in the range [-20, 19]. ++ ++ This interface file allows reading and setting latency using the ++ same values used by sched_setattr(2). The latency_nice of a group is ++ used to limit the impact of the latency_nice of a task outside the ++ group. + + + Memory +diff '--color=auto' -uraN a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt +--- a/Documentation/admin-guide/kernel-parameters.txt 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/admin-guide/kernel-parameters.txt 2023-11-04 16:36:22.128419484 +0300 +@@ -363,6 +363,11 @@ + selects a performance level in this range and appropriate + to the current workload. + ++ amd_prefcore= ++ [X86] ++ disable ++ Disable amd-pstate preferred core. ++ + amijoy.map= [HW,JOY] Amiga joystick support + Map of devices attached to JOY0DAT and JOY1DAT + Format: , +@@ -4271,6 +4276,15 @@ + nomsi [MSI] If the PCI_MSI kernel config parameter is + enabled, this kernel boot option can be used to + disable the use of MSI interrupts system-wide. ++ pcie_acs_override = ++ [PCIE] Override missing PCIe ACS support for: ++ downstream ++ All downstream ports - full ACS capabilities ++ multfunction ++ All multifunction devices - multifunction ACS subset ++ id:nnnn:nnnn ++ Specfic device - full ACS capabilities ++ Specified as vid:did (vendor/device ID) in hex + noioapicquirk [APIC] Disable all boot interrupt quirks. + Safety option to keep boot IRQs enabled. This + should never be necessary. +@@ -6735,6 +6749,14 @@ + delay after resetting its port); + Example: quirks=0781:5580:bk,0a5c:5834:gij + ++ usbcore.interrupt_interval_override= ++ [USB] A list of USB devices for which a different polling ++ interval than the default shall be used on all interrupt-type ++ endpoints. The format is VendorID:ProductID:interval, with ++ the vendor and product ids specified hexadecimally, and the ++ interval decimally in milliseconds. ++ Example: interrupt_interval_override=045e:00db:16,1bcf:0005:2 ++ + usbhid.mousepoll= + [USBHID] The interval which mice are to be polled at. + +diff '--color=auto' -uraN a/Documentation/admin-guide/mm/ksm.rst b/Documentation/admin-guide/mm/ksm.rst +--- a/Documentation/admin-guide/mm/ksm.rst 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/admin-guide/mm/ksm.rst 2023-11-04 16:35:57.831317118 +0300 +@@ -173,6 +173,13 @@ + the number of KSM pages that hit the ``max_page_sharing`` limit + stable_node_dups + number of duplicated KSM pages ++ksm_zero_pages ++ how many zero pages that are still mapped into processes were mapped by ++ KSM when deduplicating. ++ ++When ``use_zero_pages`` is/was enabled, the sum of ``pages_sharing`` + ++``ksm_zero_pages`` represents the actual number of pages saved by KSM. ++if ``use_zero_pages`` has never been enabled, ``ksm_zero_pages`` is 0. + + A high ratio of ``pages_sharing`` to ``pages_shared`` indicates good + sharing, but a high ratio of ``pages_unshared`` to ``pages_sharing`` +@@ -196,21 +203,25 @@ + 1) How to determine whether KSM save memory or consume memory in system-wide + range? Here is a simple approximate calculation for reference:: + +- general_profit =~ pages_sharing * sizeof(page) - (all_rmap_items) * ++ general_profit =~ ksm_saved_pages * sizeof(page) - (all_rmap_items) * + sizeof(rmap_item); + +- where all_rmap_items can be easily obtained by summing ``pages_sharing``, +- ``pages_shared``, ``pages_unshared`` and ``pages_volatile``. ++ where ksm_saved_pages equals to the sum of ``pages_sharing`` + ++ ``ksm_zero_pages`` of the system, and all_rmap_items can be easily ++ obtained by summing ``pages_sharing``, ``pages_shared``, ``pages_unshared`` ++ and ``pages_volatile``. + + 2) The KSM profit inner a single process can be similarly obtained by the + following approximate calculation:: + +- process_profit =~ ksm_merging_pages * sizeof(page) - ++ process_profit =~ ksm_saved_pages * sizeof(page) - + ksm_rmap_items * sizeof(rmap_item). + +- where ksm_merging_pages is shown under the directory ``/proc//``, +- and ksm_rmap_items is shown in ``/proc//ksm_stat``. The process profit +- is also shown in ``/proc//ksm_stat`` as ksm_process_profit. ++ where ksm_saved_pages equals to the sum of ``ksm_merging_pages`` and ++ ``ksm_zero_pages``, both of which are shown under the directory ++ ``/proc//ksm_stat``, and ksm_rmap_items is also shown in ++ ``/proc//ksm_stat``. The process profit is also shown in ++ ``/proc//ksm_stat`` as ksm_process_profit. + + From the perspective of application, a high ratio of ``ksm_rmap_items`` to + ``ksm_merging_pages`` means a bad madvise-applied policy, so developers or +diff '--color=auto' -uraN a/Documentation/admin-guide/pm/amd-pstate.rst b/Documentation/admin-guide/pm/amd-pstate.rst +--- a/Documentation/admin-guide/pm/amd-pstate.rst 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/admin-guide/pm/amd-pstate.rst 2023-11-04 16:35:57.797983186 +0300 +@@ -300,8 +300,8 @@ + efficiency frequency management method on AMD processors. + + +-AMD Pstate Driver Operation Modes +-================================= ++``amd-pstate`` Driver Operation Modes ++====================================== + + ``amd_pstate`` CPPC has 3 operation modes: autonomous (active) mode, + non-autonomous (passive) mode and guided autonomous (guided) mode. +@@ -353,6 +353,48 @@ + level and the platform autonomously selects a performance level in this range + and appropriate to the current workload. + ++``amd-pstate`` Preferred Core ++================================= ++ ++The core frequency is subjected to the process variation in semiconductors. ++Not all cores are able to reach the maximum frequency respecting the ++infrastructure limits. Consequently, AMD has redefined the concept of ++maximum frequency of a part. This means that a fraction of cores can reach ++maximum frequency. To find the best process scheduling policy for a given ++scenario, OS needs to know the core ordering informed by the platform through ++highest performance capability register of the CPPC interface. ++ ++``amd-pstate`` preferred core enables the scheduler to prefer scheduling on ++cores that can achieve a higher frequency with lower voltage. The preferred ++core rankings can dynamically change based on the workload, platform conditions, ++thermals and ageing. ++ ++The priority metric will be initialized by the ``amd-pstate`` driver. The ``amd-pstate`` ++driver will also determine whether or not ``amd-pstate`` preferred core is ++supported by the platform. ++ ++``amd-pstate`` driver will provide an initial core ordering when the system boots. ++The platform uses the CPPC interfaces to communicate the core ranking to the ++operating system and scheduler to make sure that OS is choosing the cores ++with highest performance firstly for scheduling the process. When ``amd-pstate`` ++driver receives a message with the highest performance change, it will ++update the core ranking and set the cpu's priority. ++ ++``amd-pstate`` Preferred Core Switch ++================================= ++Kernel Parameters ++----------------- ++ ++``amd-pstate`` peferred core`` has two states: enable and disable. ++Enable/disable states can be chosen by different kernel parameters. ++Default enable ``amd-pstate`` preferred core. ++ ++``amd_prefcore=disable`` ++ ++For systems that support ``amd-pstate`` preferred core, the core rankings will ++always be advertised by the platform. But OS can choose to ignore that via the ++kernel parameter ``amd_prefcore=disable``. ++ + User Space Interface in ``sysfs`` - General + =========================================== + +@@ -385,6 +427,19 @@ + to the operation mode represented by that string - or to be + unregistered in the "disable" case. + ++``prefcore`` ++ Preferred core state of the driver: "enabled" or "disabled". ++ ++ "enabled" ++ Enable the ``amd-pstate`` preferred core. ++ ++ "disabled" ++ Disable the ``amd-pstate`` preferred core ++ ++ ++ This attribute is read-only to check the state of preferred core set ++ by the kernel parameter. ++ + ``cpupower`` tool support for ``amd-pstate`` + =============================================== + +diff '--color=auto' -uraN a/Documentation/leds/index.rst b/Documentation/leds/index.rst +--- a/Documentation/leds/index.rst 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/leds/index.rst 2023-11-04 16:35:57.824650332 +0300 +@@ -10,6 +10,7 @@ + leds-class + leds-class-flash + leds-class-multicolor ++ ledtrig-blkdev + ledtrig-oneshot + ledtrig-transient + ledtrig-usbport +diff '--color=auto' -uraN a/Documentation/leds/ledtrig-blkdev.rst b/Documentation/leds/ledtrig-blkdev.rst +--- a/Documentation/leds/ledtrig-blkdev.rst 1970-01-01 03:00:00.000000000 +0300 ++++ b/Documentation/leds/ledtrig-blkdev.rst 2023-11-04 16:35:57.824650332 +0300 +@@ -0,0 +1,158 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++================================= ++Block Device (blkdev) LED Trigger ++================================= ++ ++Available when ``CONFIG_LEDS_TRIGGER_BLKDEV=y`` or ++``CONFIG_LEDS_TRIGGER_BLKDEV=m``. ++ ++See also: ++ ++* ``Documentation/ABI/testing/sysfs-class-led-trigger-blkdev`` ++* ``Documentation/ABI/stable/sysfs-block`` (``/sys/block//linked_leds``) ++ ++Overview ++======== ++ ++.. note:: ++ The examples below use ```` to refer to the name of a ++ system-specific LED. If no suitable LED is available on a test ++ system (in a virtual machine, for example), it is possible to ++ use a userspace LED. (See ``Documentation/leds/uleds.rst``.) ++ ++Verify that the ``blkdev`` LED trigger is available:: ++ ++ # grep blkdev /sys/class/leds//trigger ++ ... rfkill-none blkdev ++ ++(If the previous command produces no output, you may need to load the trigger ++module - ``modprobe ledtrig_blkdev``. If the module is not available, check ++the value of ``CONFIG_LEDS_TRIGGER_BLKDEV`` in your kernel configuration.) ++ ++Associate the LED with the ``blkdev`` LED trigger:: ++ ++ # echo blkdev > /sys/class/leds//trigger ++ ++ # cat /sys/class/leds//trigger ++ ... rfkill-none [blkdev] ++ ++Note that several new device attributes are available in the ++``/sys/class/leds/`` directory. ++ ++* ``link_dev_by_path``, ``unlink_dev_by_path``, and ``unlink_dev_by_name`` are ++ used to manage the set of block devices associated with this LED. The LED ++ will blink when activity occurs on any of its linked devices. ++ ++* ``blink_on_read``, ``blink_on_write``, ``blink_on_discard``, and ++ ``blink_on_flush`` are boolean values that determine whether the LED will ++ blink when a particular type of activity is detected on one of its linked ++ block devices. ++ ++* ``blink_time`` is the duration (in milliseconds) of each blink of this LED. ++ (The minimum value is 10 milliseconds.) ++ ++* ``check_interval`` is the frequency (in milliseconds) with which block devices ++ linked to this LED will be checked for activity and the LED blinked (if the ++ correct type of activity has occurred). ++ ++* The ``linked_devices`` directory will contain a symbolic link to every device ++ that is associated with this LED. ++ ++Link a block device to the LED:: ++ ++ # echo /dev/sda > /sys/class/leds//link_dev_by_path ++ ++ # ls /sys/class/leds//linked_devices ++ sda ++ ++(The value written to ``link_dev_by_path`` must be the path of the device ++special file, such as ``/dev/sda``, that represents the block device - or the ++path of a symbolic link to such a device special file.) ++ ++Activity on the device will now cause the LED to blink. The duration of each ++blink (in milliseconds) can be adjusted by setting ++``/sys/class/leds//blink_time``. (But see **check_interval and ++blink_time** below.) ++ ++Associate a second device with the LED:: ++ ++ # echo /dev/sdb > /sys/class/leds//link_dev_by_path ++ ++ # ls /sys/class/leds//linked_devices ++ sda sdb ++ ++When a block device is linked to one or more LEDs, the LEDs are linked from ++the device's ``linked_leds`` directory:: ++ ++ # ls /sys/class/block/sd{a,b}/linked_leds ++ /sys/class/block/sda/linked_leds: ++ ++ ++ /sys/class/block/sdb/linked_leds: ++ ++ ++(The ``linked_leds`` directory only exists when the block device is linked to ++at least one LED.) ++ ++``check_interval`` and ``blink_time`` ++===================================== ++ ++* By default, linked block devices are checked for activity every 100 ++ milliseconds. This frequency can be changed for an LED via the ++ ``/sys/class/leds//check_interval`` attribute. (The minimum value is 25 ++ milliseconds.) ++ ++* All block devices associated with an LED are checked for activity every ++ ``check_interval`` milliseconds, and a blink is triggered if the correct type ++ of activity (as determined by the LED's ``blink_on_*`` attributes) is ++ detected. The duration of an LED's blink is determined by its ``blink_time`` ++ attribute. Thus (when the correct type of activity is detected), the LED will ++ be on for ``blink_time`` milliseconds and off for ++ ``check_interval - blink_time`` milliseconds. ++ ++* The LED subsystem ignores new blink requests for an LED that is already in ++ in the process of blinking, so setting a ``blink_time`` greater than or equal ++ to ``check_interval`` will cause some blinks to be missed. ++ ++* Because of processing times, scheduling latencies, etc., avoiding missed ++ blinks actually requires a difference of at least a few milliseconds between ++ the ``blink_time`` and ``check_interval``. The required difference is likely ++ to vary from system to system. As a reference, a Thecus N5550 NAS requires a ++ difference of 7 milliseconds (e.g. ``check_interval == 100``, ++ ``blink_time == 93``). ++ ++* The default values (``check_interval == 100``, ``blink_time == 75``) cause the ++ LED associated with a continuously active device to blink rapidly. For a more ++ "always on" effect, increase the ``blink_time`` (but not too much; see the ++ previous bullet). ++ ++Other Notes ++=========== ++ ++* Many (possibly all) types of block devices work with this trigger, including: ++ ++ * SCSI (including SATA and USB) hard disk drives and SSDs ++ * SCSI (including SATA and USB) optical drives ++ * NVMe SSDs ++ * SD cards ++ * loopback block devices (``/dev/loop*``) ++ * device mapper devices, such as LVM logical volumes ++ * MD RAID devices ++ * zRAM compressed RAM-disks ++ * partitions on block devices that support them ++ ++* The names of the symbolic links in ``/sys/class/leds//linked_devices`` ++ are **kernel** names, which may not match the paths used for ++ ``link_dev_by_path`` and ``unlink_dev_by_path``. This is most likely when a ++ symbolic link is used to refer to the device (as is common with logical ++ volumes), but it can be true for any device, because nothing prevents the ++ creation of device special files with arbitrary names (e.g. ++ ``sudo mknod /foo b 8 0``). ++ ++ Kernel names can be used to unlink block devices from LEDs by writing them to ++ the LED's ``unlink_dev_by_name`` attribute. ++ ++* The ``blkdev`` LED trigger supports many-to-many device/LED associations. ++ A device can be associated with multiple LEDs, and an LED can be associated ++ with multiple devices. +diff '--color=auto' -uraN a/Documentation/scheduler/sched-design-CFS.rst b/Documentation/scheduler/sched-design-CFS.rst +--- a/Documentation/scheduler/sched-design-CFS.rst 2023-10-25 13:16:30.000000000 +0300 ++++ b/Documentation/scheduler/sched-design-CFS.rst 2023-11-04 16:36:05.664790922 +0300 +@@ -94,7 +94,7 @@ + way the previous scheduler had, and has no heuristics whatsoever. There is + only one central tunable (you have to switch on CONFIG_SCHED_DEBUG): + +- /sys/kernel/debug/sched/min_granularity_ns ++ /sys/kernel/debug/sched/base_slice_ns + + which can be used to tune the scheduler from "desktop" (i.e., low latencies) to + "server" (i.e., good batching) workloads. It defaults to a setting suitable +diff '--color=auto' -uraN a/MAINTAINERS b/MAINTAINERS +--- a/MAINTAINERS 2023-10-25 13:16:30.000000000 +0300 ++++ b/MAINTAINERS 2023-11-04 16:36:53.555649747 +0300 +@@ -6251,6 +6251,13 @@ + F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.yaml + F: drivers/media/i2c/dw9714.c + ++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 ++ + DONGWOON DW9768 LENS VOICE COIL DRIVER + M: Dongchun Zhu + L: linux-media@vger.kernel.org +diff '--color=auto' -uraN a/Makefile b/Makefile +--- a/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/Makefile 2023-11-04 16:35:57.807983366 +0300 +@@ -831,6 +831,9 @@ + ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE + KBUILD_CFLAGS += -O2 + KBUILD_RUSTFLAGS += -Copt-level=2 ++else ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3 ++KBUILD_CFLAGS += -O3 ++KBUILD_RUSTFLAGS += -Copt-level=3 + else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE + KBUILD_CFLAGS += -Os + KBUILD_RUSTFLAGS += -Copt-level=s +@@ -1076,11 +1079,6 @@ + # Make sure -fstack-check isn't enabled (like gentoo apparently did) + KBUILD_CFLAGS += -fno-stack-check + +-# conserve stack if available +-ifdef CONFIG_CC_IS_GCC +-KBUILD_CFLAGS += -fconserve-stack +-endif +- + # Prohibit date/time macros, which would make the build non-deterministic + KBUILD_CFLAGS += -Werror=date-time + +diff '--color=auto' -uraN a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl +--- a/arch/alpha/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/alpha/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.831317118 +0300 +@@ -491,3 +491,6 @@ + 559 common futex_waitv sys_futex_waitv + 560 common set_mempolicy_home_node sys_ni_syscall + 561 common cachestat sys_cachestat ++562 common process_ksm_enable sys_process_ksm_enable ++563 common process_ksm_disable sys_process_ksm_disable ++564 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/arc/configs/axs101_defconfig b/arch/arc/configs/axs101_defconfig +--- a/arch/arc/configs/axs101_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/axs101_defconfig 2023-11-04 16:35:57.807983366 +0300 +@@ -9,6 +9,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y + # CONFIG_VM_EVENT_COUNTERS is not set +diff '--color=auto' -uraN a/arch/arc/configs/axs103_defconfig b/arch/arc/configs/axs103_defconfig +--- a/arch/arc/configs/axs103_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/axs103_defconfig 2023-11-04 16:35:57.807983366 +0300 +@@ -9,6 +9,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y + # CONFIG_VM_EVENT_COUNTERS is not set +diff '--color=auto' -uraN a/arch/arc/configs/axs103_smp_defconfig b/arch/arc/configs/axs103_smp_defconfig +--- a/arch/arc/configs/axs103_smp_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/axs103_smp_defconfig 2023-11-04 16:35:57.807983366 +0300 +@@ -9,6 +9,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y + # CONFIG_VM_EVENT_COUNTERS is not set +diff '--color=auto' -uraN a/arch/arc/configs/haps_hs_defconfig b/arch/arc/configs/haps_hs_defconfig +--- a/arch/arc/configs/haps_hs_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/haps_hs_defconfig 2023-11-04 16:35:57.807983366 +0300 +@@ -11,6 +11,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EXPERT=y + CONFIG_PERF_EVENTS=y + # CONFIG_COMPAT_BRK is not set +diff '--color=auto' -uraN a/arch/arc/configs/haps_hs_smp_defconfig b/arch/arc/configs/haps_hs_smp_defconfig +--- a/arch/arc/configs/haps_hs_smp_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/haps_hs_smp_defconfig 2023-11-04 16:35:57.807983366 +0300 +@@ -11,6 +11,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y + # CONFIG_VM_EVENT_COUNTERS is not set +diff '--color=auto' -uraN a/arch/arc/configs/hsdk_defconfig b/arch/arc/configs/hsdk_defconfig +--- a/arch/arc/configs/hsdk_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/hsdk_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -9,6 +9,7 @@ + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y + CONFIG_BLK_DEV_RAM=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y + # CONFIG_VM_EVENT_COUNTERS is not set +diff '--color=auto' -uraN a/arch/arc/configs/nsim_700_defconfig b/arch/arc/configs/nsim_700_defconfig +--- a/arch/arc/configs/nsim_700_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/nsim_700_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -11,6 +11,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_KALLSYMS_ALL=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y +diff '--color=auto' -uraN a/arch/arc/configs/nsimosci_defconfig b/arch/arc/configs/nsimosci_defconfig +--- a/arch/arc/configs/nsimosci_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/nsimosci_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -10,6 +10,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_KALLSYMS_ALL=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y +diff '--color=auto' -uraN a/arch/arc/configs/nsimosci_hs_defconfig b/arch/arc/configs/nsimosci_hs_defconfig +--- a/arch/arc/configs/nsimosci_hs_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/nsimosci_hs_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -10,6 +10,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_KALLSYMS_ALL=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y +diff '--color=auto' -uraN a/arch/arc/configs/nsimosci_hs_smp_defconfig b/arch/arc/configs/nsimosci_hs_smp_defconfig +--- a/arch/arc/configs/nsimosci_hs_smp_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/nsimosci_hs_smp_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -8,6 +8,7 @@ + # CONFIG_UTS_NS is not set + # CONFIG_PID_NS is not set + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_PERF_EVENTS=y + # CONFIG_COMPAT_BRK is not set + CONFIG_KPROBES=y +diff '--color=auto' -uraN a/arch/arc/configs/tb10x_defconfig b/arch/arc/configs/tb10x_defconfig +--- a/arch/arc/configs/tb10x_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/tb10x_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -14,6 +14,7 @@ + CONFIG_INITRAMFS_ROOT_UID=2100 + CONFIG_INITRAMFS_ROOT_GID=501 + # CONFIG_RD_GZIP is not set ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_KALLSYMS_ALL=y + # CONFIG_AIO is not set + CONFIG_EMBEDDED=y +diff '--color=auto' -uraN a/arch/arc/configs/vdk_hs38_defconfig b/arch/arc/configs/vdk_hs38_defconfig +--- a/arch/arc/configs/vdk_hs38_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/vdk_hs38_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -4,6 +4,7 @@ + CONFIG_IKCONFIG=y + CONFIG_IKCONFIG_PROC=y + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y + # CONFIG_VM_EVENT_COUNTERS is not set +diff '--color=auto' -uraN a/arch/arc/configs/vdk_hs38_smp_defconfig b/arch/arc/configs/vdk_hs38_smp_defconfig +--- a/arch/arc/configs/vdk_hs38_smp_defconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arc/configs/vdk_hs38_smp_defconfig 2023-11-04 16:35:57.811316759 +0300 +@@ -4,6 +4,7 @@ + CONFIG_IKCONFIG=y + CONFIG_IKCONFIG_PROC=y + CONFIG_BLK_DEV_INITRD=y ++CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3=y + CONFIG_EMBEDDED=y + CONFIG_PERF_EVENTS=y + # CONFIG_VM_EVENT_COUNTERS is not set +diff '--color=auto' -uraN a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl +--- a/arch/arm/tools/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arm/tools/syscall.tbl 2023-11-04 16:35:57.831317118 +0300 +@@ -465,3 +465,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/arm64/include/asm/unistd.h b/arch/arm64/include/asm/unistd.h +--- a/arch/arm64/include/asm/unistd.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arm64/include/asm/unistd.h 2023-11-04 16:35:57.831317118 +0300 +@@ -39,7 +39,7 @@ + #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5) + #define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800) + +-#define __NR_compat_syscalls 452 ++#define __NR_compat_syscalls 455 + #endif + + #define __ARCH_WANT_SYS_CLONE +diff '--color=auto' -uraN a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h +--- a/arch/arm64/include/asm/unistd32.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/arm64/include/asm/unistd32.h 2023-11-04 16:35:57.831317118 +0300 +@@ -909,6 +909,12 @@ + __SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node) + #define __NR_cachestat 451 + __SYSCALL(__NR_cachestat, sys_cachestat) ++#define __NR_process_ksm_enable 452 ++__SYSCALL(__NR_process_ksm_enable, sys_process_ksm_enable) ++#define __NR_process_ksm_disable 453 ++__SYSCALL(__NR_process_ksm_disable, sys_process_ksm_disable) ++#define __NR_process_ksm_status 454 ++__SYSCALL(__NR_process_ksm_status, sys_process_ksm_status) + + /* + * Please add new compat syscalls above this comment and update +diff '--color=auto' -uraN a/arch/ia64/kernel/syscalls/syscall.tbl b/arch/ia64/kernel/syscalls/syscall.tbl +--- a/arch/ia64/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/ia64/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.831317118 +0300 +@@ -372,3 +372,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl +--- a/arch/m68k/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/m68k/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.831317118 +0300 +@@ -451,3 +451,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl +--- a/arch/microblaze/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/microblaze/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.831317118 +0300 +@@ -457,3 +457,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl +--- a/arch/mips/kernel/syscalls/syscall_n32.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/mips/kernel/syscalls/syscall_n32.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -390,3 +390,6 @@ + 449 n32 futex_waitv sys_futex_waitv + 450 n32 set_mempolicy_home_node sys_set_mempolicy_home_node + 451 n32 cachestat sys_cachestat ++452 n32 process_ksm_enable sys_process_ksm_enable ++453 n32 process_ksm_disable sys_process_ksm_disable ++454 n32 process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl +--- a/arch/mips/kernel/syscalls/syscall_n64.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/mips/kernel/syscalls/syscall_n64.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -366,3 +366,6 @@ + 449 n64 futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 n64 cachestat sys_cachestat ++452 n64 process_ksm_enable sys_process_ksm_enable ++453 n64 process_ksm_disable sys_process_ksm_disable ++454 n64 process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl +--- a/arch/mips/kernel/syscalls/syscall_o32.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/mips/kernel/syscalls/syscall_o32.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -439,3 +439,6 @@ + 449 o32 futex_waitv sys_futex_waitv + 450 o32 set_mempolicy_home_node sys_set_mempolicy_home_node + 451 o32 cachestat sys_cachestat ++452 o32 process_ksm_enable sys_process_ksm_enable ++453 o32 process_ksm_disable sys_process_ksm_disable ++454 o32 process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl +--- a/arch/parisc/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/parisc/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -450,3 +450,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl +--- a/arch/powerpc/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/powerpc/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -538,3 +538,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 nospu set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl +--- a/arch/s390/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/s390/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -454,3 +454,6 @@ + 449 common futex_waitv sys_futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl +--- a/arch/sh/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/sh/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -454,3 +454,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl +--- a/arch/sparc/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/sparc/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -497,3 +497,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/x86/Kconfig b/arch/x86/Kconfig +--- a/arch/x86/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/Kconfig 2023-11-04 16:35:57.797983186 +0300 +@@ -1052,8 +1052,9 @@ + + config SCHED_MC_PRIO + bool "CPU core priorities scheduler support" +- depends on SCHED_MC && CPU_SUP_INTEL +- select X86_INTEL_PSTATE ++ depends on SCHED_MC ++ select X86_INTEL_PSTATE if CPU_SUP_INTEL ++ select X86_AMD_PSTATE if CPU_SUP_AMD && ACPI + select CPU_FREQ + default y + help +diff '--color=auto' -uraN a/arch/x86/Kconfig.cpu b/arch/x86/Kconfig.cpu +--- a/arch/x86/Kconfig.cpu 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/Kconfig.cpu 2023-11-04 16:35:57.811316759 +0300 +@@ -157,7 +157,7 @@ + + + config MK6 +- bool "K6/K6-II/K6-III" ++ bool "AMD K6/K6-II/K6-III" + depends on X86_32 + help + Select this for an AMD K6-family processor. Enables use of +@@ -165,7 +165,7 @@ + flags to GCC. + + config MK7 +- bool "Athlon/Duron/K7" ++ bool "AMD Athlon/Duron/K7" + depends on X86_32 + help + Select this for an AMD Athlon K7-family processor. Enables use of +@@ -173,12 +173,106 @@ + flags to GCC. + + config MK8 +- bool "Opteron/Athlon64/Hammer/K8" ++ bool "AMD Opteron/Athlon64/Hammer/K8" + help + Select this for an AMD Opteron or Athlon64 Hammer-family processor. + Enables use of some extended instructions, and passes appropriate + optimization flags to GCC. + ++config MK8SSE3 ++ bool "AMD Opteron/Athlon64/Hammer/K8 with SSE3" ++ help ++ Select this for improved AMD Opteron or Athlon64 Hammer-family processors. ++ Enables use of some extended instructions, and passes appropriate ++ optimization flags to GCC. ++ ++config MK10 ++ bool "AMD 61xx/7x50/PhenomX3/X4/II/K10" ++ help ++ Select this for an AMD 61xx Eight-Core Magny-Cours, Athlon X2 7x50, ++ Phenom X3/X4/II, Athlon II X2/X3/X4, or Turion II-family processor. ++ Enables use of some extended instructions, and passes appropriate ++ optimization flags to GCC. ++ ++config MBARCELONA ++ bool "AMD Barcelona" ++ help ++ Select this for AMD Family 10h Barcelona processors. ++ ++ Enables -march=barcelona ++ ++config MBOBCAT ++ bool "AMD Bobcat" ++ help ++ Select this for AMD Family 14h Bobcat processors. ++ ++ Enables -march=btver1 ++ ++config MJAGUAR ++ bool "AMD Jaguar" ++ help ++ Select this for AMD Family 16h Jaguar processors. ++ ++ Enables -march=btver2 ++ ++config MBULLDOZER ++ bool "AMD Bulldozer" ++ help ++ Select this for AMD Family 15h Bulldozer processors. ++ ++ Enables -march=bdver1 ++ ++config MPILEDRIVER ++ bool "AMD Piledriver" ++ help ++ Select this for AMD Family 15h Piledriver processors. ++ ++ Enables -march=bdver2 ++ ++config MSTEAMROLLER ++ bool "AMD Steamroller" ++ help ++ Select this for AMD Family 15h Steamroller processors. ++ ++ Enables -march=bdver3 ++ ++config MEXCAVATOR ++ bool "AMD Excavator" ++ help ++ Select this for AMD Family 15h Excavator processors. ++ ++ Enables -march=bdver4 ++ ++config MZEN ++ bool "AMD Zen" ++ help ++ Select this for AMD Family 17h Zen processors. ++ ++ Enables -march=znver1 ++ ++config MZEN2 ++ bool "AMD Zen 2" ++ help ++ Select this for AMD Family 17h Zen 2 processors. ++ ++ Enables -march=znver2 ++ ++config MZEN3 ++ bool "AMD Zen 3" ++ depends on (CC_IS_GCC && GCC_VERSION >= 100300) || (CC_IS_CLANG && CLANG_VERSION >= 120000) ++ help ++ Select this for AMD Family 19h Zen 3 processors. ++ ++ Enables -march=znver3 ++ ++config MZEN4 ++ bool "AMD Zen 4" ++ depends on (CC_IS_GCC && GCC_VERSION >= 130000) || (CC_IS_CLANG && CLANG_VERSION >= 160000) ++ help ++ Select this for AMD Family 19h Zen 4 processors. ++ ++ Enables -march=znver4 ++ + config MCRUSOE + bool "Crusoe" + depends on X86_32 +@@ -270,7 +364,7 @@ + in /proc/cpuinfo. Family 15 is an older Xeon, Family 6 a newer one. + + config MCORE2 +- bool "Core 2/newer Xeon" ++ bool "Intel Core 2" + help + + Select this for Intel Core 2 and newer Core 2 Xeons (Xeon 51xx and +@@ -278,6 +372,8 @@ + family in /proc/cpuinfo. Newer ones have 6 and older ones 15 + (not a typo) + ++ Enables -march=core2 ++ + config MATOM + bool "Intel Atom" + help +@@ -287,6 +383,212 @@ + accordingly optimized code. Use a recent GCC with specific Atom + support in order to fully benefit from selecting this option. + ++config MNEHALEM ++ bool "Intel Nehalem" ++ select X86_P6_NOP ++ help ++ ++ Select this for 1st Gen Core processors in the Nehalem family. ++ ++ Enables -march=nehalem ++ ++config MWESTMERE ++ bool "Intel Westmere" ++ select X86_P6_NOP ++ help ++ ++ Select this for the Intel Westmere formerly Nehalem-C family. ++ ++ Enables -march=westmere ++ ++config MSILVERMONT ++ bool "Intel Silvermont" ++ select X86_P6_NOP ++ help ++ ++ Select this for the Intel Silvermont platform. ++ ++ Enables -march=silvermont ++ ++config MGOLDMONT ++ bool "Intel Goldmont" ++ select X86_P6_NOP ++ help ++ ++ Select this for the Intel Goldmont platform including Apollo Lake and Denverton. ++ ++ Enables -march=goldmont ++ ++config MGOLDMONTPLUS ++ bool "Intel Goldmont Plus" ++ select X86_P6_NOP ++ help ++ ++ Select this for the Intel Goldmont Plus platform including Gemini Lake. ++ ++ Enables -march=goldmont-plus ++ ++config MSANDYBRIDGE ++ bool "Intel Sandy Bridge" ++ select X86_P6_NOP ++ help ++ ++ Select this for 2nd Gen Core processors in the Sandy Bridge family. ++ ++ Enables -march=sandybridge ++ ++config MIVYBRIDGE ++ bool "Intel Ivy Bridge" ++ select X86_P6_NOP ++ help ++ ++ Select this for 3rd Gen Core processors in the Ivy Bridge family. ++ ++ Enables -march=ivybridge ++ ++config MHASWELL ++ bool "Intel Haswell" ++ select X86_P6_NOP ++ help ++ ++ Select this for 4th Gen Core processors in the Haswell family. ++ ++ Enables -march=haswell ++ ++config MBROADWELL ++ bool "Intel Broadwell" ++ select X86_P6_NOP ++ help ++ ++ Select this for 5th Gen Core processors in the Broadwell family. ++ ++ Enables -march=broadwell ++ ++config MSKYLAKE ++ bool "Intel Skylake" ++ select X86_P6_NOP ++ help ++ ++ Select this for 6th Gen Core processors in the Skylake family. ++ ++ Enables -march=skylake ++ ++config MSKYLAKEX ++ bool "Intel Skylake X" ++ select X86_P6_NOP ++ help ++ ++ Select this for 6th Gen Core processors in the Skylake X family. ++ ++ Enables -march=skylake-avx512 ++ ++config MCANNONLAKE ++ bool "Intel Cannon Lake" ++ select X86_P6_NOP ++ help ++ ++ Select this for 8th Gen Core processors ++ ++ Enables -march=cannonlake ++ ++config MICELAKE ++ bool "Intel Ice Lake" ++ select X86_P6_NOP ++ help ++ ++ Select this for 10th Gen Core processors in the Ice Lake family. ++ ++ Enables -march=icelake-client ++ ++config MCASCADELAKE ++ bool "Intel Cascade Lake" ++ select X86_P6_NOP ++ help ++ ++ Select this for Xeon processors in the Cascade Lake family. ++ ++ Enables -march=cascadelake ++ ++config MCOOPERLAKE ++ bool "Intel Cooper Lake" ++ depends on (CC_IS_GCC && GCC_VERSION > 100100) || (CC_IS_CLANG && CLANG_VERSION >= 100000) ++ select X86_P6_NOP ++ help ++ ++ Select this for Xeon processors in the Cooper Lake family. ++ ++ Enables -march=cooperlake ++ ++config MTIGERLAKE ++ bool "Intel Tiger Lake" ++ depends on (CC_IS_GCC && GCC_VERSION > 100100) || (CC_IS_CLANG && CLANG_VERSION >= 100000) ++ select X86_P6_NOP ++ help ++ ++ Select this for third-generation 10 nm process processors in the Tiger Lake family. ++ ++ Enables -march=tigerlake ++ ++config MSAPPHIRERAPIDS ++ bool "Intel Sapphire Rapids" ++ depends on (CC_IS_GCC && GCC_VERSION > 110000) || (CC_IS_CLANG && CLANG_VERSION >= 120000) ++ select X86_P6_NOP ++ help ++ ++ Select this for fourth-generation 10 nm process processors in the Sapphire Rapids family. ++ ++ Enables -march=sapphirerapids ++ ++config MROCKETLAKE ++ bool "Intel Rocket Lake" ++ depends on (CC_IS_GCC && GCC_VERSION > 110000) || (CC_IS_CLANG && CLANG_VERSION >= 120000) ++ select X86_P6_NOP ++ help ++ ++ Select this for eleventh-generation processors in the Rocket Lake family. ++ ++ Enables -march=rocketlake ++ ++config MALDERLAKE ++ bool "Intel Alder Lake" ++ depends on (CC_IS_GCC && GCC_VERSION > 110000) || (CC_IS_CLANG && CLANG_VERSION >= 120000) ++ select X86_P6_NOP ++ help ++ ++ Select this for twelfth-generation processors in the Alder Lake family. ++ ++ Enables -march=alderlake ++ ++config MRAPTORLAKE ++ bool "Intel Raptor Lake" ++ depends on (CC_IS_GCC && GCC_VERSION >= 130000) || (CC_IS_CLANG && CLANG_VERSION >= 150500) ++ select X86_P6_NOP ++ help ++ ++ Select this for thirteenth-generation processors in the Raptor Lake family. ++ ++ Enables -march=raptorlake ++ ++config MMETEORLAKE ++ bool "Intel Meteor Lake" ++ depends on (CC_IS_GCC && GCC_VERSION >= 130000) || (CC_IS_CLANG && CLANG_VERSION >= 150500) ++ select X86_P6_NOP ++ help ++ ++ Select this for fourteenth-generation processors in the Meteor Lake family. ++ ++ Enables -march=meteorlake ++ ++config MEMERALDRAPIDS ++ bool "Intel Emerald Rapids" ++ depends on (CC_IS_GCC && GCC_VERSION > 130000) || (CC_IS_CLANG && CLANG_VERSION >= 150500) ++ select X86_P6_NOP ++ help ++ ++ Select this for fifth-generation 10 nm process processors in the Emerald Rapids family. ++ ++ Enables -march=emeraldrapids ++ + config GENERIC_CPU + bool "Generic-x86-64" + depends on X86_64 +@@ -294,6 +596,50 @@ + Generic x86-64 CPU. + Run equally well on all x86-64 CPUs. + ++config GENERIC_CPU2 ++ bool "Generic-x86-64-v2" ++ depends on (CC_IS_GCC && GCC_VERSION > 110000) || (CC_IS_CLANG && CLANG_VERSION >= 120000) ++ depends on X86_64 ++ help ++ Generic x86-64 CPU. ++ Run equally well on all x86-64 CPUs with min support of x86-64-v2. ++ ++config GENERIC_CPU3 ++ bool "Generic-x86-64-v3" ++ depends on (CC_IS_GCC && GCC_VERSION > 110000) || (CC_IS_CLANG && CLANG_VERSION >= 120000) ++ depends on X86_64 ++ help ++ Generic x86-64-v3 CPU with v3 instructions. ++ Run equally well on all x86-64 CPUs with min support of x86-64-v3. ++ ++config GENERIC_CPU4 ++ bool "Generic-x86-64-v4" ++ depends on (CC_IS_GCC && GCC_VERSION > 110000) || (CC_IS_CLANG && CLANG_VERSION >= 120000) ++ depends on X86_64 ++ help ++ Generic x86-64 CPU with v4 instructions. ++ Run equally well on all x86-64 CPUs with min support of x86-64-v4. ++ ++config MNATIVE_INTEL ++ bool "Intel-Native optimizations autodetected by the compiler" ++ help ++ ++ Clang 3.8, GCC 4.2 and above support -march=native, which automatically detects ++ the optimum settings to use based on your processor. Do NOT use this ++ for AMD CPUs. Intel Only! ++ ++ Enables -march=native ++ ++config MNATIVE_AMD ++ bool "AMD-Native optimizations autodetected by the compiler" ++ help ++ ++ Clang 3.8, GCC 4.2 and above support -march=native, which automatically detects ++ the optimum settings to use based on your processor. Do NOT use this ++ for Intel CPUs. AMD Only! ++ ++ Enables -march=native ++ + endchoice + + config X86_GENERIC +@@ -318,9 +664,17 @@ + config X86_L1_CACHE_SHIFT + int + default "7" if MPENTIUM4 || MPSC +- default "6" if MK7 || MK8 || MPENTIUMM || MCORE2 || MATOM || MVIAC7 || X86_GENERIC || GENERIC_CPU ++ default "6" if MK7 || MK8 || MPENTIUMM || MCORE2 || MATOM || MVIAC7 || MK8SSE3 || MK10 \ ++ || MBARCELONA || MBOBCAT || MJAGUAR || MBULLDOZER || MPILEDRIVER || MSTEAMROLLER \ ++ || MEXCAVATOR || MZEN || MZEN2 || MZEN3 || MZEN4 || MNEHALEM || MWESTMERE || MSILVERMONT \ ++ || MGOLDMONT || MGOLDMONTPLUS || MSANDYBRIDGE || MIVYBRIDGE || MHASWELL || MBROADWELL \ ++ || MSKYLAKE || MSKYLAKEX || MCANNONLAKE || MICELAKE || MCASCADELAKE || MCOOPERLAKE \ ++ || MTIGERLAKE || MSAPPHIRERAPIDS || MROCKETLAKE || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE \ ++ || MEMERALDRAPIDS || MNATIVE_INTEL || MNATIVE_AMD || X86_GENERIC || GENERIC_CPU || GENERIC_CPU2 \ ++ || GENERIC_CPU3 || GENERIC_CPU4 + default "4" if MELAN || M486SX || M486 || MGEODEGX1 +- default "5" if MWINCHIP3D || MWINCHIPC6 || MCRUSOE || MEFFICEON || MCYRIXIII || MK6 || MPENTIUMIII || MPENTIUMII || M686 || M586MMX || M586TSC || M586 || MVIAC3_2 || MGEODE_LX ++ default "5" if MWINCHIP3D || MWINCHIPC6 || MCRUSOE || MEFFICEON || MCYRIXIII || MK6 || MPENTIUMIII \ ++ || MPENTIUMII || M686 || M586MMX || M586TSC || M586 || MVIAC3_2 || MGEODE_LX + + config X86_F00F_BUG + def_bool y +@@ -332,15 +686,27 @@ + + config X86_ALIGNMENT_16 + def_bool y +- depends on MWINCHIP3D || MWINCHIPC6 || MCYRIXIII || MELAN || MK6 || M586MMX || M586TSC || M586 || M486SX || M486 || MVIAC3_2 || MGEODEGX1 ++ depends on MWINCHIP3D || MWINCHIPC6 || MCYRIXIII || MELAN || MK6 || M586MMX || M586TSC \ ++ || M586 || M486SX || M486 || MVIAC3_2 || MGEODEGX1 + + config X86_INTEL_USERCOPY + def_bool y +- depends on MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M586MMX || X86_GENERIC || MK8 || MK7 || MEFFICEON || MCORE2 ++ depends on MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M586MMX || X86_GENERIC \ ++ || MK8 || MK7 || MEFFICEON || MCORE2 || MNEHALEM || MWESTMERE || MSILVERMONT || MGOLDMONT \ ++ || MGOLDMONTPLUS || MSANDYBRIDGE || MIVYBRIDGE || MHASWELL || MBROADWELL || MSKYLAKE || MSKYLAKEX \ ++ || MCANNONLAKE || MICELAKE || MCASCADELAKE || MCOOPERLAKE || MTIGERLAKE || MSAPPHIRERAPIDS \ ++ || MROCKETLAKE || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE || MEMERALDRAPIDS || MNATIVE_INTEL + + config X86_USE_PPRO_CHECKSUM + def_bool y +- depends on MWINCHIP3D || MWINCHIPC6 || MCYRIXIII || MK7 || MK6 || MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 || MK8 || MVIAC3_2 || MVIAC7 || MEFFICEON || MGEODE_LX || MCORE2 || MATOM ++ depends on MWINCHIP3D || MWINCHIPC6 || MCYRIXIII || MK7 || MK6 || MPENTIUM4 || MPENTIUMM \ ++ || MPENTIUMIII || MPENTIUMII || M686 || MK8 || MVIAC3_2 || MVIAC7 || MEFFICEON || MGEODE_LX \ ++ || MCORE2 || MATOM || MK8SSE3 || MK10 || MBARCELONA || MBOBCAT || MJAGUAR || MBULLDOZER \ ++ || MPILEDRIVER || MSTEAMROLLER || MEXCAVATOR || MZEN || MZEN2 || MZEN3 || MZEN4 || MNEHALEM \ ++ || MWESTMERE || MSILVERMONT || MGOLDMONT || MGOLDMONTPLUS || MSANDYBRIDGE || MIVYBRIDGE \ ++ || MHASWELL || MBROADWELL || MSKYLAKE || MSKYLAKEX || MCANNONLAKE || MICELAKE \ ++ || MCASCADELAKE || MCOOPERLAKE || MTIGERLAKE || MSAPPHIRERAPIDS || MROCKETLAKE \ ++ || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE || MEMERALDRAPIDS || MNATIVE_INTEL || MNATIVE_AMD + + # + # P6_NOPs are a relatively minor optimization that require a family >= +@@ -356,32 +722,63 @@ + config X86_P6_NOP + def_bool y + depends on X86_64 +- depends on (MCORE2 || MPENTIUM4 || MPSC) ++ depends on (MCORE2 || MPENTIUM4 || MPSC || MNEHALEM || MWESTMERE || MSILVERMONT || MGOLDMONT \ ++ || MGOLDMONTPLUS || MSANDYBRIDGE || MIVYBRIDGE || MHASWELL || MBROADWELL || MSKYLAKE \ ++ || MSKYLAKEX || MCANNONLAKE || MICELAKE || MCASCADELAKE || MCOOPERLAKE || MTIGERLAKE \ ++ || MSAPPHIRERAPIDS || MROCKETLAKE || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE || MEMERALDRAPIDS \ ++ || MNATIVE_INTEL) + + config X86_TSC + def_bool y +- depends on (MWINCHIP3D || MCRUSOE || MEFFICEON || MCYRIXIII || MK7 || MK6 || MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 || M586MMX || M586TSC || MK8 || MVIAC3_2 || MVIAC7 || MGEODEGX1 || MGEODE_LX || MCORE2 || MATOM) || X86_64 ++ depends on (MWINCHIP3D || MCRUSOE || MEFFICEON || MCYRIXIII || MK7 || MK6 || MPENTIUM4 || MPENTIUMM \ ++ || MPENTIUMIII || MPENTIUMII || M686 || M586MMX || M586TSC || MK8 || MVIAC3_2 || MVIAC7 || MGEODEGX1 \ ++ || MGEODE_LX || MCORE2 || MATOM || MK8SSE3 || MK10 || MBARCELONA || MBOBCAT || MJAGUAR || MBULLDOZER \ ++ || MPILEDRIVER || MSTEAMROLLER || MEXCAVATOR || MZEN || MZEN2 || MZEN3 || MZEN4 || MNEHALEM \ ++ || MWESTMERE || MSILVERMONT || MGOLDMONT || MGOLDMONTPLUS || MSANDYBRIDGE || MIVYBRIDGE || MHASWELL \ ++ || MBROADWELL || MSKYLAKE || MSKYLAKEX || MCANNONLAKE || MICELAKE || MCASCADELAKE || MCOOPERLAKE \ ++ || MTIGERLAKE || MSAPPHIRERAPIDS || MROCKETLAKE || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE || MEMERALDRAPIDS \ ++ || MNATIVE_INTEL || MNATIVE_AMD) || X86_64 + + config X86_CMPXCHG64 + def_bool y +- depends on X86_PAE || X86_64 || MCORE2 || MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 || M586TSC || M586MMX || MATOM || MGEODE_LX || MGEODEGX1 || MK6 || MK7 || MK8 ++ depends on X86_PAE || X86_64 || MCORE2 || MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 \ ++ || M586TSC || M586MMX || MATOM || MGEODE_LX || MGEODEGX1 || MK6 || MK7 || MK8 || MK8SSE3 || MK10 \ ++ || MBARCELONA || MBOBCAT || MJAGUAR || MBULLDOZER || MPILEDRIVER || MSTEAMROLLER || MEXCAVATOR || MZEN \ ++ || MZEN2 || MZEN3 || MZEN4 || MNEHALEM || MWESTMERE || MSILVERMONT || MGOLDMONT || MGOLDMONTPLUS \ ++ || MSANDYBRIDGE || MIVYBRIDGE || MHASWELL || MBROADWELL || MSKYLAKE || MSKYLAKEX || MCANNONLAKE \ ++ || MICELAKE || MCASCADELAKE || MCOOPERLAKE || MTIGERLAKE || MSAPPHIRERAPIDS || MROCKETLAKE \ ++ || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE || MEMERALDRAPIDS || MNATIVE_INTEL || MNATIVE_AMD + + # this should be set for all -march=.. options where the compiler + # generates cmov. + config X86_CMOV + def_bool y +- depends on (MK8 || MK7 || MCORE2 || MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 || MVIAC3_2 || MVIAC7 || MCRUSOE || MEFFICEON || X86_64 || MATOM || MGEODE_LX) ++ depends on (MK8 || MK7 || MCORE2 || MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 \ ++ || MVIAC3_2 || MVIAC7 || MCRUSOE || MEFFICEON || X86_64 || MATOM || MGEODE_LX || MK8SSE3 || MK10 \ ++ || MBARCELONA || MBOBCAT || MJAGUAR || MBULLDOZER || MPILEDRIVER || MSTEAMROLLER || MEXCAVATOR \ ++ || MZEN || MZEN2 || MZEN3 || MZEN4 || MNEHALEM || MWESTMERE || MSILVERMONT || MGOLDMONT \ ++ || MGOLDMONTPLUS || MSANDYBRIDGE || MIVYBRIDGE || MHASWELL || MBROADWELL || MSKYLAKE || MSKYLAKEX \ ++ || MCANNONLAKE || MICELAKE || MCASCADELAKE || MCOOPERLAKE || MTIGERLAKE || MSAPPHIRERAPIDS \ ++ || MROCKETLAKE || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE || MEMERALDRAPIDS || MNATIVE_INTEL || MNATIVE_AMD) + + config X86_MINIMUM_CPU_FAMILY + int + default "64" if X86_64 +- default "6" if X86_32 && (MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 || MVIAC3_2 || MVIAC7 || MEFFICEON || MATOM || MCRUSOE || MCORE2 || MK7 || MK8) ++ default "6" if X86_32 && (MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 \ ++ || MVIAC3_2 || MVIAC7 || MEFFICEON || MATOM || MCRUSOE || MCORE2 || MK7 || MK8 || MK8SSE3 \ ++ || MK10 || MBARCELONA || MBOBCAT || MJAGUAR || MBULLDOZER || MPILEDRIVER || MSTEAMROLLER \ ++ || MEXCAVATOR || MZEN || MZEN2 || MZEN3 || MZEN4 || MNEHALEM || MWESTMERE || MSILVERMONT \ ++ || MGOLDMONT || MGOLDMONTPLUS || MSANDYBRIDGE || MIVYBRIDGE || MHASWELL || MBROADWELL \ ++ || MSKYLAKE || MSKYLAKEX || MCANNONLAKE || MICELAKE || MCASCADELAKE || MCOOPERLAKE \ ++ || MTIGERLAKE || MSAPPHIRERAPIDS || MROCKETLAKE || MALDERLAKE || MRAPTORLAKE || MRAPTORLAKE \ ++ || MNATIVE_INTEL || MNATIVE_AMD) + default "5" if X86_32 && X86_CMPXCHG64 + default "4" + + config X86_DEBUGCTLMSR + def_bool y +- depends on !(MK6 || MWINCHIPC6 || MWINCHIP3D || MCYRIXIII || M586MMX || M586TSC || M586 || M486SX || M486) && !UML ++ depends on !(MK6 || MWINCHIPC6 || MWINCHIP3D || MCYRIXIII || M586MMX || M586TSC || M586 \ ++ || M486SX || M486) && !UML + + config IA32_FEAT_CTL + def_bool y +diff '--color=auto' -uraN a/arch/x86/Makefile b/arch/x86/Makefile +--- a/arch/x86/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/Makefile 2023-11-04 16:35:57.811316759 +0300 +@@ -67,7 +67,7 @@ + # + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53383 + # +-KBUILD_CFLAGS += -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx ++KBUILD_CFLAGS += -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -O3 + KBUILD_RUSTFLAGS += -Ctarget-feature=-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2 + + ifeq ($(CONFIG_X86_KERNEL_IBT),y) +@@ -151,8 +151,48 @@ + # FIXME - should be integrated in Makefile.cpu (Makefile_32.cpu) + cflags-$(CONFIG_MK8) += -march=k8 + cflags-$(CONFIG_MPSC) += -march=nocona +- cflags-$(CONFIG_MCORE2) += -march=core2 +- cflags-$(CONFIG_MATOM) += -march=atom ++ cflags-$(CONFIG_MK8SSE3) += -march=k8-sse3 ++ cflags-$(CONFIG_MK10) += -march=amdfam10 ++ cflags-$(CONFIG_MBARCELONA) += -march=barcelona ++ cflags-$(CONFIG_MBOBCAT) += -march=btver1 ++ cflags-$(CONFIG_MJAGUAR) += -march=btver2 ++ cflags-$(CONFIG_MBULLDOZER) += -march=bdver1 ++ cflags-$(CONFIG_MPILEDRIVER) += -march=bdver2 -mno-tbm ++ cflags-$(CONFIG_MSTEAMROLLER) += -march=bdver3 -mno-tbm ++ cflags-$(CONFIG_MEXCAVATOR) += -march=bdver4 -mno-tbm ++ cflags-$(CONFIG_MZEN) += -march=znver1 ++ cflags-$(CONFIG_MZEN2) += -march=znver2 ++ cflags-$(CONFIG_MZEN3) += -march=znver3 ++ cflags-$(CONFIG_MZEN4) += -march=znver4 ++ cflags-$(CONFIG_MNATIVE_INTEL) += -march=native ++ cflags-$(CONFIG_MNATIVE_AMD) += -march=native ++ cflags-$(CONFIG_MATOM) += -march=bonnell ++ cflags-$(CONFIG_MCORE2) += -march=core2 ++ cflags-$(CONFIG_MNEHALEM) += -march=nehalem ++ cflags-$(CONFIG_MWESTMERE) += -march=westmere ++ cflags-$(CONFIG_MSILVERMONT) += -march=silvermont ++ cflags-$(CONFIG_MGOLDMONT) += -march=goldmont ++ cflags-$(CONFIG_MGOLDMONTPLUS) += -march=goldmont-plus ++ cflags-$(CONFIG_MSANDYBRIDGE) += -march=sandybridge ++ cflags-$(CONFIG_MIVYBRIDGE) += -march=ivybridge ++ cflags-$(CONFIG_MHASWELL) += -march=haswell ++ cflags-$(CONFIG_MBROADWELL) += -march=broadwell ++ cflags-$(CONFIG_MSKYLAKE) += -march=skylake ++ cflags-$(CONFIG_MSKYLAKEX) += -march=skylake-avx512 ++ cflags-$(CONFIG_MCANNONLAKE) += -march=cannonlake ++ cflags-$(CONFIG_MICELAKE) += -march=icelake-client ++ cflags-$(CONFIG_MCASCADELAKE) += -march=cascadelake ++ cflags-$(CONFIG_MCOOPERLAKE) += -march=cooperlake ++ cflags-$(CONFIG_MTIGERLAKE) += -march=tigerlake ++ cflags-$(CONFIG_MSAPPHIRERAPIDS) += -march=sapphirerapids ++ cflags-$(CONFIG_MROCKETLAKE) += -march=rocketlake ++ cflags-$(CONFIG_MALDERLAKE) += -march=alderlake ++ cflags-$(CONFIG_MRAPTORLAKE) += -march=raptorlake ++ cflags-$(CONFIG_MMETEORLAKE) += -march=meteorlake ++ cflags-$(CONFIG_MEMERALDRAPIDS) += -march=emeraldrapids ++ cflags-$(CONFIG_GENERIC_CPU2) += -march=x86-64-v2 ++ cflags-$(CONFIG_GENERIC_CPU3) += -march=x86-64-v3 ++ cflags-$(CONFIG_GENERIC_CPU4) += -march=x86-64-v4 + cflags-$(CONFIG_GENERIC_CPU) += -mtune=generic + KBUILD_CFLAGS += $(cflags-y) + +diff '--color=auto' -uraN a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl +--- a/arch/x86/entry/syscalls/syscall_32.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/entry/syscalls/syscall_32.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -456,3 +456,6 @@ + 449 i386 futex_waitv sys_futex_waitv + 450 i386 set_mempolicy_home_node sys_set_mempolicy_home_node + 451 i386 cachestat sys_cachestat ++452 i386 process_ksm_enable sys_process_ksm_enable ++453 i386 process_ksm_disable sys_process_ksm_disable ++454 i386 process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl +--- a/arch/x86/entry/syscalls/syscall_64.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/entry/syscalls/syscall_64.tbl 2023-11-04 16:35:57.834650510 +0300 +@@ -373,6 +373,9 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status + + # + # Due to a historical design error, certain syscalls are numbered differently +diff '--color=auto' -uraN a/arch/x86/include/asm/pci.h b/arch/x86/include/asm/pci.h +--- a/arch/x86/include/asm/pci.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/include/asm/pci.h 2023-11-04 16:35:57.811316759 +0300 +@@ -27,6 +27,7 @@ + #if IS_ENABLED(CONFIG_VMD) + struct pci_dev *vmd_dev; /* VMD Device if in Intel VMD domain */ + #endif ++ struct pci_dev *nvme_remap_dev; /* AHCI Device if NVME remapped bus */ + }; + + extern int pci_routeirq; +@@ -70,6 +71,11 @@ + #define is_vmd(bus) false + #endif /* CONFIG_VMD */ + ++static inline bool is_nvme_remap(struct pci_bus *bus) ++{ ++ return to_pci_sysdata(bus)->nvme_remap_dev != NULL; ++} ++ + /* Can be used to override the logic in pci_scan_bus for skipping + already-configured bus numbers - to be used for buggy BIOSes + or architectures with incomplete PCI setup by the loader */ +diff '--color=auto' -uraN a/arch/x86/include/asm/vermagic.h b/arch/x86/include/asm/vermagic.h +--- a/arch/x86/include/asm/vermagic.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/include/asm/vermagic.h 2023-11-04 16:35:57.811316759 +0300 +@@ -17,6 +17,54 @@ + #define MODULE_PROC_FAMILY "586MMX " + #elif defined CONFIG_MCORE2 + #define MODULE_PROC_FAMILY "CORE2 " ++#elif defined CONFIG_MNATIVE_INTEL ++#define MODULE_PROC_FAMILY "NATIVE_INTEL " ++#elif defined CONFIG_MNATIVE_AMD ++#define MODULE_PROC_FAMILY "NATIVE_AMD " ++#elif defined CONFIG_MNEHALEM ++#define MODULE_PROC_FAMILY "NEHALEM " ++#elif defined CONFIG_MWESTMERE ++#define MODULE_PROC_FAMILY "WESTMERE " ++#elif defined CONFIG_MSILVERMONT ++#define MODULE_PROC_FAMILY "SILVERMONT " ++#elif defined CONFIG_MGOLDMONT ++#define MODULE_PROC_FAMILY "GOLDMONT " ++#elif defined CONFIG_MGOLDMONTPLUS ++#define MODULE_PROC_FAMILY "GOLDMONTPLUS " ++#elif defined CONFIG_MSANDYBRIDGE ++#define MODULE_PROC_FAMILY "SANDYBRIDGE " ++#elif defined CONFIG_MIVYBRIDGE ++#define MODULE_PROC_FAMILY "IVYBRIDGE " ++#elif defined CONFIG_MHASWELL ++#define MODULE_PROC_FAMILY "HASWELL " ++#elif defined CONFIG_MBROADWELL ++#define MODULE_PROC_FAMILY "BROADWELL " ++#elif defined CONFIG_MSKYLAKE ++#define MODULE_PROC_FAMILY "SKYLAKE " ++#elif defined CONFIG_MSKYLAKEX ++#define MODULE_PROC_FAMILY "SKYLAKEX " ++#elif defined CONFIG_MCANNONLAKE ++#define MODULE_PROC_FAMILY "CANNONLAKE " ++#elif defined CONFIG_MICELAKE ++#define MODULE_PROC_FAMILY "ICELAKE " ++#elif defined CONFIG_MCASCADELAKE ++#define MODULE_PROC_FAMILY "CASCADELAKE " ++#elif defined CONFIG_MCOOPERLAKE ++#define MODULE_PROC_FAMILY "COOPERLAKE " ++#elif defined CONFIG_MTIGERLAKE ++#define MODULE_PROC_FAMILY "TIGERLAKE " ++#elif defined CONFIG_MSAPPHIRERAPIDS ++#define MODULE_PROC_FAMILY "SAPPHIRERAPIDS " ++#elif defined CONFIG_ROCKETLAKE ++#define MODULE_PROC_FAMILY "ROCKETLAKE " ++#elif defined CONFIG_MALDERLAKE ++#define MODULE_PROC_FAMILY "ALDERLAKE " ++#elif defined CONFIG_MRAPTORLAKE ++#define MODULE_PROC_FAMILY "RAPTORLAKE " ++#elif defined CONFIG_MMETEORLAKE ++#define MODULE_PROC_FAMILY "METEORLAKE " ++#elif defined CONFIG_MEMERALDRAPIDS ++#define MODULE_PROC_FAMILY "EMERALDRAPIDS " + #elif defined CONFIG_MATOM + #define MODULE_PROC_FAMILY "ATOM " + #elif defined CONFIG_M686 +@@ -35,6 +83,32 @@ + #define MODULE_PROC_FAMILY "K7 " + #elif defined CONFIG_MK8 + #define MODULE_PROC_FAMILY "K8 " ++#elif defined CONFIG_MK8SSE3 ++#define MODULE_PROC_FAMILY "K8SSE3 " ++#elif defined CONFIG_MK10 ++#define MODULE_PROC_FAMILY "K10 " ++#elif defined CONFIG_MBARCELONA ++#define MODULE_PROC_FAMILY "BARCELONA " ++#elif defined CONFIG_MBOBCAT ++#define MODULE_PROC_FAMILY "BOBCAT " ++#elif defined CONFIG_MBULLDOZER ++#define MODULE_PROC_FAMILY "BULLDOZER " ++#elif defined CONFIG_MPILEDRIVER ++#define MODULE_PROC_FAMILY "PILEDRIVER " ++#elif defined CONFIG_MSTEAMROLLER ++#define MODULE_PROC_FAMILY "STEAMROLLER " ++#elif defined CONFIG_MJAGUAR ++#define MODULE_PROC_FAMILY "JAGUAR " ++#elif defined CONFIG_MEXCAVATOR ++#define MODULE_PROC_FAMILY "EXCAVATOR " ++#elif defined CONFIG_MZEN ++#define MODULE_PROC_FAMILY "ZEN " ++#elif defined CONFIG_MZEN2 ++#define MODULE_PROC_FAMILY "ZEN2 " ++#elif defined CONFIG_MZEN3 ++#define MODULE_PROC_FAMILY "ZEN3 " ++#elif defined CONFIG_MZEN4 ++#define MODULE_PROC_FAMILY "ZEN4 " + #elif defined CONFIG_MELAN + #define MODULE_PROC_FAMILY "ELAN " + #elif defined CONFIG_MCRUSOE +diff '--color=auto' -uraN a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +--- a/arch/x86/kernel/acpi/boot.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/kernel/acpi/boot.c 2023-11-04 16:36:53.558983140 +0300 +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1256,6 +1257,24 @@ + } + } + ++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 +@@ -1311,6 +1330,11 @@ + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ 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(); + +diff '--color=auto' -uraN a/arch/x86/pci/common.c b/arch/x86/pci/common.c +--- a/arch/x86/pci/common.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/x86/pci/common.c 2023-11-04 16:35:57.811316759 +0300 +@@ -723,12 +723,15 @@ + return 0; + } + +-#if IS_ENABLED(CONFIG_VMD) + struct pci_dev *pci_real_dma_dev(struct pci_dev *dev) + { ++#if IS_ENABLED(CONFIG_VMD) + if (is_vmd(dev->bus)) + return to_pci_sysdata(dev->bus)->vmd_dev; ++#endif ++ ++ if (is_nvme_remap(dev->bus)) ++ return to_pci_sysdata(dev->bus)->nvme_remap_dev; + + return dev; + } +-#endif +diff '--color=auto' -uraN a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl +--- a/arch/xtensa/kernel/syscalls/syscall.tbl 2023-10-25 13:16:30.000000000 +0300 ++++ b/arch/xtensa/kernel/syscalls/syscall.tbl 2023-11-04 16:35:57.837983905 +0300 +@@ -422,3 +422,6 @@ + 449 common futex_waitv sys_futex_waitv + 450 common set_mempolicy_home_node sys_set_mempolicy_home_node + 451 common cachestat sys_cachestat ++452 common process_ksm_enable sys_process_ksm_enable ++453 common process_ksm_disable sys_process_ksm_disable ++454 common process_ksm_status sys_process_ksm_status +diff '--color=auto' -uraN a/block/bfq-iosched.c b/block/bfq-iosched.c +--- a/block/bfq-iosched.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/block/bfq-iosched.c 2023-11-04 16:35:57.811316759 +0300 +@@ -7627,6 +7627,7 @@ + static int __init bfq_init(void) + { + int ret; ++ char msg[60] = "BFQ I/O-scheduler: BFQ-CachyOS v6.5"; + + #ifdef CONFIG_BFQ_GROUP_IOSCHED + ret = blkcg_policy_register(&blkcg_policy_bfq); +@@ -7658,6 +7659,11 @@ + if (ret) + goto slab_kill; + ++#ifdef CONFIG_BFQ_GROUP_IOSCHED ++ strcat(msg, " (with cgroups support)"); ++#endif ++ pr_info("%s", msg); ++ + return 0; + + slab_kill: +diff '--color=auto' -uraN a/drivers/Makefile b/drivers/Makefile +--- a/drivers/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/Makefile 2023-11-04 16:35:57.811316759 +0300 +@@ -64,15 +64,8 @@ + # iommu/ comes before gpu as gpu are using iommu controllers + obj-y += iommu/ + +-# gpu/ comes after char for AGP vs DRM startup and after iommu +-obj-y += gpu/ +- + obj-$(CONFIG_CONNECTOR) += connector/ + +-# i810fb and intelfb depend on char/agp/ +-obj-$(CONFIG_FB_I810) += video/fbdev/i810/ +-obj-$(CONFIG_FB_INTEL) += video/fbdev/intelfb/ +- + obj-$(CONFIG_PARPORT) += parport/ + obj-y += base/ block/ misc/ mfd/ nfc/ + obj-$(CONFIG_LIBNVDIMM) += nvdimm/ +@@ -84,6 +77,14 @@ + obj-y += scsi/ + obj-y += nvme/ + obj-$(CONFIG_ATA) += ata/ ++ ++# gpu/ comes after char for AGP vs DRM startup and after iommu ++obj-y += gpu/ ++ ++# i810fb and intelfb depend on char/agp/ ++obj-$(CONFIG_FB_I810) += video/fbdev/i810/ ++obj-$(CONFIG_FB_INTEL) += video/fbdev/intelfb/ ++ + obj-$(CONFIG_TARGET_CORE) += target/ + obj-$(CONFIG_MTD) += mtd/ + obj-$(CONFIG_SPI) += spi/ +diff '--color=auto' -uraN a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +--- a/drivers/acpi/acpi_tad.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/acpi/acpi_tad.c 2023-11-04 16:36:53.558983140 +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) + { +@@ -480,15 +488,14 @@ + + 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, +@@ -563,13 +570,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); +@@ -604,11 +616,6 @@ + return -ENODEV; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- return -ENODEV; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; +@@ -637,6 +644,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; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +diff '--color=auto' -uraN a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c +--- a/drivers/acpi/cppc_acpi.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/acpi/cppc_acpi.c 2023-11-04 16:35:57.797983186 +0300 +@@ -1155,6 +1155,19 @@ + } + + /** ++ * cppc_get_highest_perf - Get the highest performance register value. ++ * @cpunum: CPU from which to get highest performance. ++ * @highest_perf: Return address. ++ * ++ * Return: 0 for success, -EIO otherwise. ++ */ ++int cppc_get_highest_perf(int cpunum, u64 *highest_perf) ++{ ++ return cppc_get_perf(cpunum, HIGHEST_PERF, highest_perf); ++} ++EXPORT_SYMBOL_GPL(cppc_get_highest_perf); ++ ++/** + * cppc_get_epp_perf - Get the epp register value. + * @cpunum: CPU from which to get epp preference value. + * @epp_perf: Return address. +diff '--color=auto' -uraN a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c +--- a/drivers/acpi/processor_driver.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/acpi/processor_driver.c 2023-11-04 16:35:57.797983186 +0300 +@@ -27,6 +27,7 @@ + #define ACPI_PROCESSOR_NOTIFY_PERFORMANCE 0x80 + #define ACPI_PROCESSOR_NOTIFY_POWER 0x81 + #define ACPI_PROCESSOR_NOTIFY_THROTTLING 0x82 ++#define ACPI_PROCESSOR_NOTIFY_HIGEST_PERF_CHANGED 0x85 + + MODULE_AUTHOR("Paul Diefenbaugh"); + MODULE_DESCRIPTION("ACPI Processor Driver"); +@@ -83,6 +84,11 @@ + acpi_bus_generate_netlink_event(device->pnp.device_class, + dev_name(&device->dev), event, 0); + break; ++ case ACPI_PROCESSOR_NOTIFY_HIGEST_PERF_CHANGED: ++ cpufreq_update_highest_perf(pr->id); ++ acpi_bus_generate_netlink_event(device->pnp.device_class, ++ dev_name(&device->dev), event, 0); ++ break; + default: + acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event); + break; +diff '--color=auto' -uraN a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c +--- a/drivers/acpi/processor_idle.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/acpi/processor_idle.c 2023-11-04 16:36:53.515649029 +0300 +@@ -529,9 +529,11 @@ + inb(addr); + + #ifdef CONFIG_X86 +- /* No delay is needed if we are in guest */ +- if (boot_cpu_has(X86_FEATURE_HYPERVISOR)) +- return; ++ /* ++ * No delay is needed if we are in guest or on a processor ++ * based on the Zen microarchitecture. ++ */ ++ if (boot_cpu_has(X86_FEATURE_HYPERVISOR) || boot_cpu_has(X86_FEATURE_ZEN)) + /* + * Modern (>=Nehalem) Intel systems use ACPI via intel_idle, + * not this code. Assume that any Intel systems using this +diff '--color=auto' -uraN a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/acpi/scan.c 2023-11-04 16:36:53.552316353 +0300 +@@ -2105,6 +2105,9 @@ + + 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 a/drivers/ata/ahci.c b/drivers/ata/ahci.c +--- a/drivers/ata/ahci.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/ata/ahci.c 2023-11-04 16:35:57.814650153 +0300 +@@ -1524,7 +1524,7 @@ + } + #endif + +-static void ahci_remap_check(struct pci_dev *pdev, int bar, ++static int ahci_remap_check(struct pci_dev *pdev, int bar, + struct ahci_host_priv *hpriv) + { + int i; +@@ -1537,7 +1537,7 @@ + pci_resource_len(pdev, bar) < SZ_512K || + bar != AHCI_PCI_BAR_STANDARD || + !(readl(hpriv->mmio + AHCI_VSCAP) & 1)) +- return; ++ return 0; + + cap = readq(hpriv->mmio + AHCI_REMAP_CAP); + for (i = 0; i < AHCI_MAX_REMAP; i++) { +@@ -1552,18 +1552,11 @@ + } + + if (!hpriv->remapped_nvme) +- return; ++ return 0; + +- dev_warn(&pdev->dev, "Found %u remapped NVMe devices.\n", +- hpriv->remapped_nvme); +- dev_warn(&pdev->dev, +- "Switch your BIOS from RAID to AHCI mode to use them.\n"); +- +- /* +- * Don't rely on the msi-x capability in the remap case, +- * share the legacy interrupt across ahci and remapped devices. +- */ +- hpriv->flags |= AHCI_HFLAG_NO_MSI; ++ /* Abort probe, allowing intel-nvme-remap to step in when available */ ++ dev_info(&pdev->dev, "Device will be handled by intel-nvme-remap.\n"); ++ return -ENODEV; + } + + static int ahci_get_irq_vector(struct ata_host *host, int port) +@@ -1783,7 +1776,9 @@ + hpriv->mmio = pcim_iomap_table(pdev)[ahci_pci_bar]; + + /* detect remapped nvme devices */ +- ahci_remap_check(pdev, ahci_pci_bar, hpriv); ++ rc = ahci_remap_check(pdev, ahci_pci_bar, hpriv); ++ if (rc) ++ return rc; + + sysfs_add_file_to_group(&pdev->dev.kobj, + &dev_attr_remapped_nvme.attr, +diff '--color=auto' -uraN a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +--- a/drivers/bluetooth/btusb.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/bluetooth/btusb.c 2023-11-04 16:36:53.538982781 +0300 +@@ -65,6 +65,7 @@ + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) + #define BTUSB_ACTIONS_SEMI BIT(27) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -468,6 +469,7 @@ + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -960,7 +962,7 @@ + } + + gpiod_set_value_cansleep(reset_gpio, 0); +- msleep(200); ++ usleep_range(USEC_PER_SEC / 2, USEC_PER_SEC); + gpiod_set_value_cansleep(reset_gpio, 1); + + return; +@@ -4317,6 +4319,19 @@ + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +diff '--color=auto' -uraN a/drivers/cpufreq/Kconfig.x86 b/drivers/cpufreq/Kconfig.x86 +--- a/drivers/cpufreq/Kconfig.x86 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/cpufreq/Kconfig.x86 2023-11-04 16:35:57.814650153 +0300 +@@ -9,7 +9,6 @@ + select ACPI_PROCESSOR if ACPI + select ACPI_CPPC_LIB if X86_64 && ACPI && SCHED_MC_PRIO + select CPU_FREQ_GOV_PERFORMANCE +- select CPU_FREQ_GOV_SCHEDUTIL if SMP + help + This driver provides a P state for Intel core processors. + The driver implements an internal governor and will become +@@ -39,7 +38,6 @@ + depends on X86 && ACPI + select ACPI_PROCESSOR + select ACPI_CPPC_LIB if X86_64 +- select CPU_FREQ_GOV_SCHEDUTIL if SMP + help + This driver adds a CPUFreq driver which utilizes a fine grain + processor performance frequency control range instead of legacy +diff '--color=auto' -uraN a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +--- a/drivers/cpufreq/amd-pstate-ut.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/cpufreq/amd-pstate-ut.c 2023-11-04 16:35:57.797983186 +0300 +@@ -127,8 +127,6 @@ + struct cpufreq_policy *policy = NULL; + struct amd_cpudata *cpudata = NULL; + +- highest_perf = amd_get_highest_perf(); +- + for_each_possible_cpu(cpu) { + policy = cpufreq_cpu_get(cpu); + if (!policy) +@@ -143,6 +141,7 @@ + goto skip_test; + } + ++ highest_perf = cppc_perf.highest_perf; + nominal_perf = cppc_perf.nominal_perf; + lowest_nonlinear_perf = cppc_perf.lowest_nonlinear_perf; + lowest_perf = cppc_perf.lowest_perf; +@@ -154,6 +153,7 @@ + goto skip_test; + } + ++ highest_perf = AMD_CPPC_HIGHEST_PERF(cap1); + nominal_perf = AMD_CPPC_NOMINAL_PERF(cap1); + lowest_nonlinear_perf = AMD_CPPC_LOWNONLIN_PERF(cap1); + lowest_perf = AMD_CPPC_LOWEST_PERF(cap1); +diff '--color=auto' -uraN a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/cpufreq/amd-pstate.c 2023-11-04 16:35:57.797983186 +0300 +@@ -37,6 +37,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -49,6 +50,8 @@ + + #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_epp_driver; + static int cppc_state = AMD_PSTATE_UNDEFINED; + static bool cppc_enabled; ++static bool amd_pstate_prefcore = true; + + /* + * AMD Energy Preference Performance (EPP) +@@ -290,27 +294,26 @@ + 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); + if (ret) + return ret; + +- /* +- * TODO: Introduce AMD specific power feature. +- * +- * CPPC entry doesn't indicate the highest performance in some ASICs. ++ /* For platforms that do not support the preferred core feature, the ++ * highest_pef may be configured with 166 or 255, to avoid max frequency ++ * calculated wrongly. we take the AMD_CPPC_HIGHEST_PERF(cap1) value as ++ * the default max perf. + */ +- 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); ++ else ++ WRITE_ONCE(cpudata->highest_perf, AMD_CPPC_HIGHEST_PERF(cap1)); + + 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)); + + return 0; + } +@@ -318,22 +321,21 @@ + 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); + 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); ++ else ++ WRITE_ONCE(cpudata->highest_perf, cppc_perf.highest_perf); + + WRITE_ONCE(cpudata->nominal_perf, cppc_perf.nominal_perf); + 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); + + if (cppc_state == AMD_PSTATE_ACTIVE) + return 0; +@@ -540,7 +542,7 @@ + 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 @@ + wrmsrl_on_cpu(cpu, MSR_AMD_PERF_CTL, 0); + } + ++/* ++ * Set amd-pstate preferred core enable can't be done directly from cpufreq callbacks ++ * due to locking, so queue the work for later. ++ */ ++static void amd_pstste_sched_prefcore_workfn(struct work_struct *work) ++{ ++ sched_set_itmt_support(); ++} ++static DECLARE_WORK(sched_prefcore_work, amd_pstste_sched_prefcore_workfn); ++ ++/* ++ * Get the highest performance register value. ++ * @cpu: CPU from which to get highest performance. ++ * @highest_perf: Return address. ++ * ++ * Return: 0 for success, -EIO otherwise. ++ */ ++static int amd_pstate_get_highest_perf(int cpu, u32 *highest_perf) ++{ ++ int ret; ++ ++ if (boot_cpu_has(X86_FEATURE_CPPC)) { ++ u64 cap1; ++ ++ ret = rdmsrl_safe_on_cpu(cpu, MSR_AMD_CPPC_CAP1, &cap1); ++ if (ret) ++ return ret; ++ WRITE_ONCE(*highest_perf, AMD_CPPC_HIGHEST_PERF(cap1)); ++ } else { ++ u64 cppc_highest_perf; ++ ++ ret = cppc_get_highest_perf(cpu, &cppc_highest_perf); ++ WRITE_ONCE(*highest_perf, cppc_highest_perf); ++ } ++ ++ return (ret); ++} ++ ++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) ++ return; ++ ++ cpudata->hw_prefcore = true; ++ /* check if CPPC preferred core feature is enabled*/ ++ if (highest_perf == AMD_PSTATE_MAX_CPPC_PERF) { ++ pr_debug("AMD CPPC preferred core is unsupported!\n"); ++ cpudata->hw_prefcore = false; ++ return; ++ } ++ ++ 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 ++ * update them at any time after it has been called. ++ */ ++ 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); ++ } ++ } ++} ++ ++static void amd_pstate_update_highest_perf(unsigned int cpu) ++{ ++ struct cpufreq_policy *policy; ++ struct amd_cpudata *cpudata; ++ u32 prev_high = 0, cur_high = 0; ++ int ret; ++ ++ if ((!amd_pstate_prefcore) || (!cpudata->hw_prefcore)) ++ return; ++ ++ ret = amd_pstate_get_highest_perf(cpu, &cur_high); ++ if (ret) ++ return; ++ ++ 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); ++ } ++ ++ 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 @@ + + cpudata->cpu = policy->cpu; + ++ amd_pstate_init_prefcore(cpudata); ++ + ret = amd_pstate_init_perf(cpudata); + if (ret) + goto free_cpudata1; +@@ -845,6 +967,28 @@ + return sysfs_emit(buf, "%u\n", perf); + } + ++static ssize_t show_amd_pstate_prefcore_ranking(struct cpufreq_policy *policy, ++ char *buf) ++{ ++ u32 perf; ++ struct amd_cpudata *cpudata = policy->driver_data; ++ ++ perf = READ_ONCE(cpudata->prefcore_ranking); ++ ++ return sysfs_emit(buf, "%u\n", perf); ++} ++ ++static ssize_t show_amd_pstate_hw_prefcore(struct cpufreq_policy *policy, ++ char *buf) ++{ ++ bool hw_prefcore; ++ struct amd_cpudata *cpudata = policy->driver_data; ++ ++ hw_prefcore = READ_ONCE(cpudata->hw_prefcore); ++ ++ return sysfs_emit(buf, "%s\n", hw_prefcore ? "supported" : "unsupported"); ++} ++ + static ssize_t show_energy_performance_available_preferences( + struct cpufreq_policy *policy, char *buf) + { +@@ -1037,18 +1181,29 @@ + 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"); ++} ++ + cpufreq_freq_attr_ro(amd_pstate_max_freq); + cpufreq_freq_attr_ro(amd_pstate_lowest_nonlinear_freq); + + cpufreq_freq_attr_ro(amd_pstate_highest_perf); ++cpufreq_freq_attr_ro(amd_pstate_prefcore_ranking); ++cpufreq_freq_attr_ro(amd_pstate_hw_prefcore); + cpufreq_freq_attr_rw(energy_performance_preference); + cpufreq_freq_attr_ro(energy_performance_available_preferences); + static DEVICE_ATTR_RW(status); ++static DEVICE_ATTR_RO(prefcore); + + static struct freq_attr *amd_pstate_attr[] = { + &amd_pstate_max_freq, + &amd_pstate_lowest_nonlinear_freq, + &amd_pstate_highest_perf, ++ &amd_pstate_prefcore_ranking, ++ &amd_pstate_hw_prefcore, + NULL, + }; + +@@ -1056,6 +1211,8 @@ + &amd_pstate_max_freq, + &amd_pstate_lowest_nonlinear_freq, + &amd_pstate_highest_perf, ++ &amd_pstate_prefcore_ranking, ++ &amd_pstate_hw_prefcore, + &energy_performance_preference, + &energy_performance_available_preferences, + NULL, +@@ -1063,6 +1220,7 @@ + + static struct attribute *pstate_global_attributes[] = { + &dev_attr_status.attr, ++ &dev_attr_prefcore.attr, + NULL + }; + +@@ -1114,6 +1272,8 @@ + cpudata->cpu = policy->cpu; + cpudata->epp_policy = 0; + ++ amd_pstate_init_prefcore(cpudata); ++ + ret = amd_pstate_init_perf(cpudata); + if (ret) + goto free_cpudata1; +@@ -1392,6 +1552,7 @@ + .suspend = amd_pstate_cpu_suspend, + .resume = amd_pstate_cpu_resume, + .set_boost = amd_pstate_set_boost, ++ .update_highest_perf = amd_pstate_update_highest_perf, + .name = "amd-pstate", + .attr = amd_pstate_attr, + }; +@@ -1406,6 +1567,7 @@ + .online = amd_pstate_epp_cpu_online, + .suspend = amd_pstate_epp_suspend, + .resume = amd_pstate_epp_resume, ++ .update_highest_perf = amd_pstate_update_highest_perf, + .name = "amd-pstate-epp", + .attr = amd_pstate_epp_attr, + }; +@@ -1527,7 +1689,17 @@ + + return amd_pstate_set_driver(mode_idx); + } ++ ++static int __init amd_prefcore_param(char *str) ++{ ++ if (!strcmp(str, "disable")) ++ amd_pstate_prefcore = false; ++ ++ return 0; ++} ++ + early_param("amd_pstate", amd_pstate_param); ++early_param("amd_prefcore", amd_prefcore_param); + + MODULE_AUTHOR("Huang Rui "); + MODULE_DESCRIPTION("AMD Processor P-state Frequency Driver"); +diff '--color=auto' -uraN a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/cpufreq/cpufreq.c 2023-11-04 16:35:57.801316580 +0300 +@@ -2677,6 +2677,19 @@ + } + EXPORT_SYMBOL_GPL(cpufreq_update_limits); + ++/** ++ * cpufreq_update_highest_perf - Update highest performance for a given CPU. ++ * @cpu: CPU to update the highest performance for. ++ * ++ * Invoke the driver's ->update_highest_perf callback if present ++ */ ++void cpufreq_update_highest_perf(unsigned int cpu) ++{ ++ if (cpufreq_driver->update_highest_perf) ++ cpufreq_driver->update_highest_perf(cpu); ++} ++EXPORT_SYMBOL_GPL(cpufreq_update_highest_perf); ++ + /********************************************************************* + * BOOST * + *********************************************************************/ +diff '--color=auto' -uraN a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig +--- a/drivers/extcon/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/extcon/Kconfig 2023-11-04 16:36:53.572316712 +0300 +@@ -191,4 +191,11 @@ + 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 '--color=auto' -uraN a/drivers/extcon/Makefile b/drivers/extcon/Makefile +--- a/drivers/extcon/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/extcon/Makefile 2023-11-04 16:36:53.572316712 +0300 +@@ -25,3 +25,4 @@ + 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 '--color=auto' -uraN a/drivers/extcon/extcon-steamdeck.c b/drivers/extcon/extcon-steamdeck.c +--- a/drivers/extcon/extcon-steamdeck.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/extcon/extcon-steamdeck.c 2023-11-04 16:36:53.572316712 +0300 +@@ -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"); +diff '--color=auto' -uraN a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c 2023-11-04 16:36:28.765205166 +0300 +@@ -584,15 +584,10 @@ + */ + #ifdef CONFIG_DRM_AMDGPU_SI + +-#if defined(CONFIG_DRM_RADEON) || defined(CONFIG_DRM_RADEON_MODULE) +-int amdgpu_si_support = 0; +-MODULE_PARM_DESC(si_support, "SI support (1 = enabled, 0 = disabled (default))"); +-#else + 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 + + /** +@@ -603,15 +598,10 @@ + */ + #ifdef CONFIG_DRM_AMDGPU_CIK + +-#if defined(CONFIG_DRM_RADEON) || defined(CONFIG_DRM_RADEON_MODULE) +-int amdgpu_cik_support = 0; +-MODULE_PARM_DESC(cik_support, "CIK support (1 = enabled, 0 = disabled (default))"); +-#else + 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 a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h 2023-11-04 16:35:57.757982469 +0300 +@@ -343,6 +343,77 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c 2023-11-04 16:35:57.787983008 +0300 +@@ -4015,6 +4015,11 @@ + 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); +@@ -5064,7 +5069,9 @@ + * 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; + +@@ -8079,6 +8086,10 @@ + 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, +@@ -8289,6 +8300,10 @@ + &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; +@@ -9479,6 +9494,7 @@ + * 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) +@@ -9546,6 +9562,10 @@ + */ + 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; +@@ -9582,6 +9602,18 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h 2023-11-04 16:35:57.787983008 +0300 +@@ -51,6 +51,8 @@ + + #define AMDGPU_DMUB_NOTIFICATION_MAX 5 + ++#define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL) ++ + /* + #include "include/amdgpu_dal_power_if.h" + #include "amdgpu_dm_irq.h" +@@ -702,9 +704,91 @@ + + 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 { +@@ -729,6 +813,14 @@ + 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) +@@ -790,14 +882,22 @@ + + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c 2023-11-04 16:35:57.787983008 +0300 +@@ -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 @@ + 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_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, + struct fixed31_32 *matrix) + { +- int64_t val; + int i; + + /* +@@ -201,12 +408,33 @@ + } + + /* 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 @@ + 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 @@ + * 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 @@ + * 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 @@ + 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 @@ + 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 @@ + 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 @@ + °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 @@ + 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 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c 2023-11-04 16:35:57.787983008 +0300 +@@ -253,6 +253,7 @@ + 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 */ +@@ -289,6 +290,70 @@ + } + #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, +@@ -307,6 +372,10 @@ + #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) +@@ -470,6 +539,9 @@ + + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c 2023-11-04 16:35:57.791316401 +0300 +@@ -1324,8 +1324,14 @@ + 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 * +@@ -1345,6 +1351,22 @@ + 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; + } + +@@ -1412,12 +1434,203 @@ + { + 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, +@@ -1426,6 +1639,10 @@ + .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, +@@ -1496,6 +1713,9 @@ + + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c +--- a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c 2023-11-04 16:35:57.791316401 +0300 +@@ -349,20 +349,37 @@ + * 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; ++ 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; ++ } ++ + +- region_start = -10; +- region_end = 1; + } + + for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++) +@@ -375,16 +392,56 @@ + + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c +--- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c 2023-11-04 16:35:57.791316401 +0300 +@@ -186,6 +186,43 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h +--- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h 2023-11-04 16:35:57.791316401 +0300 +@@ -58,6 +58,9 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c +--- a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c 2023-11-04 16:35:57.791316401 +0300 +@@ -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 '--color=auto' -uraN a/drivers/gpu/drm/amd/display/include/fixed31_32.h b/drivers/gpu/drm/amd/display/include/fixed31_32.h +--- a/drivers/gpu/drm/amd/display/include/fixed31_32.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/amd/display/include/fixed31_32.h 2023-11-04 16:35:57.791316401 +0300 +@@ -69,6 +69,18 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c +--- a/drivers/gpu/drm/arm/malidp_crtc.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/arm/malidp_crtc.c 2023-11-04 16:35:57.794649794 +0300 +@@ -221,7 +221,7 @@ + + /* + * 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 '--color=auto' -uraN a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c +--- a/drivers/gpu/drm/drm_atomic.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/drm_atomic.c 2023-11-04 16:35:57.794649794 +0300 +@@ -733,6 +733,7 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c +--- a/drivers/gpu/drm/drm_atomic_state_helper.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/drm_atomic_state_helper.c 2023-11-04 16:35:57.794649794 +0300 +@@ -338,6 +338,7 @@ + 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 '--color=auto' -uraN a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c +--- a/drivers/gpu/drm/drm_atomic_uapi.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/drm_atomic_uapi.c 2023-11-04 16:35:57.794649794 +0300 +@@ -362,39 +362,6 @@ + return fence_ptr; + } + +-static int +-drm_atomic_replace_property_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; +-} +- + static int drm_atomic_crtc_set_property(struct drm_crtc *crtc, + struct drm_crtc_state *state, struct drm_property *property, + uint64_t val) +@@ -415,7 +382,7 @@ + } else if (property == config->prop_vrr_enabled) { + state->vrr_enabled = val; + } else if (property == config->degamma_lut_property) { +- ret = drm_atomic_replace_property_blob_from_id(dev, ++ ret = drm_property_replace_blob_from_id(dev, + &state->degamma_lut, + val, + -1, sizeof(struct drm_color_lut), +@@ -423,7 +390,7 @@ + state->color_mgmt_changed |= replaced; + return ret; + } else if (property == config->ctm_property) { +- ret = drm_atomic_replace_property_blob_from_id(dev, ++ ret = drm_property_replace_blob_from_id(dev, + &state->ctm, + val, + sizeof(struct drm_color_ctm), -1, +@@ -431,7 +398,7 @@ + state->color_mgmt_changed |= replaced; + return ret; + } else if (property == config->gamma_lut_property) { +- ret = drm_atomic_replace_property_blob_from_id(dev, ++ ret = drm_property_replace_blob_from_id(dev, + &state->gamma_lut, + val, + -1, sizeof(struct drm_color_lut), +@@ -563,7 +530,7 @@ + } else if (property == plane->color_range_property) { + state->color_range = val; + } else if (property == config->prop_fb_damage_clips) { +- ret = drm_atomic_replace_property_blob_from_id(dev, ++ ret = drm_property_replace_blob_from_id(dev, + &state->fb_damage_clips, + val, + -1, +@@ -729,7 +696,7 @@ + if (state->link_status != DRM_LINK_STATUS_GOOD) + state->link_status = val; + } else if (property == config->hdr_output_metadata_property) { +- ret = drm_atomic_replace_property_blob_from_id(dev, ++ ret = drm_property_replace_blob_from_id(dev, + &state->hdr_output_metadata, + val, + sizeof(struct hdr_output_metadata), -1, +diff '--color=auto' -uraN a/drivers/gpu/drm/drm_property.c b/drivers/gpu/drm/drm_property.c +--- a/drivers/gpu/drm/drm_property.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/drm_property.c 2023-11-04 16:35:57.794649794 +0300 +@@ -751,6 +751,55 @@ + } + 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 '--color=auto' -uraN a/drivers/gpu/drm/i915/display/intel_display_driver.c b/drivers/gpu/drm/i915/display/intel_display_driver.c +--- a/drivers/gpu/drm/i915/display/intel_display_driver.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/i915/display/intel_display_driver.c 2023-11-04 16:36:53.518982422 +0300 +@@ -121,7 +121,7 @@ + mode_config->funcs = &intel_mode_funcs; + mode_config->helper_private = &intel_mode_config_funcs; + +- mode_config->async_page_flip = HAS_ASYNC_FLIPS(i915); ++ mode_config->async_page_flip = HAS_ASYNC_FLIPS(i915) && !i915->params.disable_async_page_flip; + + /* + * Maximum framebuffer dimensions, chosen to match +diff '--color=auto' -uraN a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c 2023-11-04 16:36:53.518982422 +0300 +@@ -14,6 +14,12 @@ + i915->display.quirks.mask |= BIT(quirk); + } + ++static void quirk_async_page_flips_force_disable(struct drm_i915_private *i915) ++{ ++ i915->drm.mode_config.async_page_flip = false; ++ drm_info(&i915->drm, "applying async flip disable quirk\n"); ++} ++ + /* + * Some machines (Lenovo U160) do not work with SSC on LVDS for some reason + */ +@@ -136,6 +142,20 @@ + }, + .hook = quirk_no_pps_backlight_power_hook, + }, ++ { ++ .dmi_id_list = &(const struct dmi_system_id[]) { ++ { ++ .callback = NULL, ++ .ident = "ASUS TUF DASH F15", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "ASUS TUF Dash F15 FX516PC_FX516PC"), ++ }, ++ }, ++ { } ++ }, ++ .hook = quirk_async_page_flips_force_disable, ++ }, + }; + + static struct intel_quirk intel_quirks[] = { +diff '--color=auto' -uraN a/drivers/gpu/drm/i915/i915_params.c b/drivers/gpu/drm/i915/i915_params.c +--- a/drivers/gpu/drm/i915/i915_params.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/i915/i915_params.c 2023-11-04 16:36:53.518982422 +0300 +@@ -228,6 +228,10 @@ + i915_param_named_unsafe(lmem_bar_size, uint, 0400, + "Set the lmem bar size(in MiB)."); + ++i915_param_named_unsafe(disable_async_page_flip, bool, 0400, ++ "Disable async page flipping" ++ "(0=disabled [default], 1=enabled)"); ++ + static void _param_print_bool(struct drm_printer *p, const char *name, + bool val) + { +diff '--color=auto' -uraN a/drivers/gpu/drm/i915/i915_params.h b/drivers/gpu/drm/i915/i915_params.h +--- a/drivers/gpu/drm/i915/i915_params.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/i915/i915_params.h 2023-11-04 16:36:53.518982422 +0300 +@@ -87,7 +87,8 @@ + param(bool, verbose_state_checks, true, 0) \ + param(bool, nuclear_pageflip, false, 0400) \ + param(bool, enable_dp_mst, true, 0600) \ +- param(bool, enable_gvt, false, IS_ENABLED(CONFIG_DRM_I915_GVT) ? 0400 : 0) ++ param(bool, enable_gvt, false, IS_ENABLED(CONFIG_DRM_I915_GVT) ? 0400 : 0) \ ++ param(bool, disable_async_page_flip, false, 0400) + + #define MEMBER(T, member, ...) T member; + struct i915_params { +diff '--color=auto' -uraN a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c +--- a/drivers/gpu/drm/radeon/radeon_drv.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/gpu/drm/radeon/radeon_drv.c 2023-11-04 16:36:28.765205166 +0300 +@@ -266,12 +266,22 @@ + 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))"); ++#else + int radeon_si_support = 1; + 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))"); ++#else + int radeon_cik_support = 1; + 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 '--color=auto' -uraN a/drivers/hid/Kconfig b/drivers/hid/Kconfig +--- a/drivers/hid/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hid/Kconfig 2023-11-04 16:36:53.545649567 +0300 +@@ -1335,4 +1335,8 @@ + + source "drivers/hid/surface-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID_SUPPORT +diff '--color=auto' -uraN a/drivers/hid/Makefile b/drivers/hid/Makefile +--- a/drivers/hid/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hid/Makefile 2023-11-04 16:36:53.545649567 +0300 +@@ -168,3 +168,6 @@ + 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 a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c +--- a/drivers/hid/hid-asus.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hid/hid-asus.c 2023-11-04 16:36:53.568983319 +0300 +@@ -899,6 +899,11 @@ + 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. */ +@@ -1253,6 +1258,9 @@ + 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 }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, +diff '--color=auto' -uraN a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +--- a/drivers/hid/hid-ids.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hid/hid-ids.h 2023-11-04 16:36:53.568983319 +0300 +@@ -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 + +diff '--color=auto' -uraN a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +--- a/drivers/hid/hid-multitouch.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hid/hid-multitouch.c 2023-11-04 16:36:53.548982960 +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 a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c +--- a/drivers/hid/hid-playstation.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hid/hid-playstation.c 2023-11-04 16:36:53.568983319 +0300 +@@ -330,8 +330,8 @@ + * 0x3F - disabled + */ + #define DS4_OUTPUT_HWCTL_BT_POLL_MASK 0x3F +-/* Default to 4ms poll interval, which is same as USB (not adjustable). */ +-#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 4 ++/* Default to 1ms poll interval (1000Hz, lower latency). */ ++#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 1 + #define DS4_OUTPUT_HWCTL_CRC32 0x40 + #define DS4_OUTPUT_HWCTL_HID 0x80 + +diff '--color=auto' -uraN a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c +--- a/drivers/hid/hid-steam.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hid/hid-steam.c 2023-11-04 16:36:53.575650105 +0300 +@@ -71,7 +71,7 @@ + + /* + * 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 @@ + #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,23 +179,43 @@ + #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 ++ ++/* 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 ++#define STEAM_SERIAL_LEN 0x15 + + struct steam_device { + struct list_head list; + spinlock_t lock; + struct hid_device *hdev, *client_hdev; +- struct mutex mutex; +- bool client_opened; ++ struct mutex report_mutex; ++ unsigned long client_opened; + struct input_dev __rcu *input; + unsigned long quirks; + struct work_struct work_connect; +@@ -135,6 +226,9 @@ + 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; +@@ -232,7 +326,7 @@ + /* 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; + +@@ -265,23 +359,28 @@ + { + /* + * Send: 0xae 0x15 0x01 +- * Recv: 0xae 0x15 0x01 serialnumber (10 chars) ++ * Recv: 0xae 0x15 0x01 serialnumber + */ +- int ret; +- u8 cmd[] = {STEAM_CMD_GET_SERIAL, 0x15, 0x01}; ++ int ret = 0; ++ 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); + 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] != 0x01) +- return -EIO; ++ goto out; ++ 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)); +- return 0; ++ strscpy(steam->serial_no, reply + 3, reply[1]); ++out: ++ mutex_unlock(&steam->report_mutex); ++ return ret; + } + + /* +@@ -291,13 +390,49 @@ + */ + static inline int steam_request_conn_status(struct steam_device *steam) + { +- return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS); ++ 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; ++} ++ ++/* ++ * 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) ++{ ++ int ret; ++ 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; ++ ++ 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; +@@ -309,7 +444,10 @@ + 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) +@@ -335,40 +473,47 @@ + + static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + { ++ if (steam->gamepad_mode) ++ enable = false; ++ + if (enable) { ++ mutex_lock(&steam->report_mutex); + /* 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); ++ 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); + + 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); ++ 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_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); ++ mutex_unlock(&steam->report_mutex); + } + } + } +@@ -376,22 +521,29 @@ + 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[] = { +@@ -635,7 +787,8 @@ + static int steam_register(struct steam_device *steam) + { + int ret; +- bool client_opened; ++ unsigned long client_opened; ++ unsigned long flags; + + /* + * This function can be called several times in a row with the +@@ -648,11 +801,9 @@ + * 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); +@@ -667,11 +818,11 @@ + 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); +@@ -719,6 +870,34 @@ + } + } + ++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; ++ ++ if (steam->gamepad_mode) ++ steam_set_lizard_mode(steam, false); ++ 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) { ++ 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; +@@ -742,16 +921,21 @@ + { + 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_RPAD_MODE, 0x07, /* disable mouse */ ++ 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) +@@ -774,10 +958,11 @@ + static int steam_client_ll_open(struct hid_device *hdev) + { + struct steam_device *steam = hdev->driver_data; ++ unsigned long flags; + +- mutex_lock(&steam->mutex); +- steam->client_opened = true; +- mutex_unlock(&steam->mutex); ++ spin_lock_irqsave(&steam->lock, flags); ++ steam->client_opened++; ++ spin_unlock_irqrestore(&steam->lock, flags); + + steam_input_unregister(steam); + +@@ -792,14 +977,12 @@ + bool connected; + + spin_lock_irqsave(&steam->lock, flags); +- connected = steam->connected; ++ steam->client_opened--; ++ 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); +@@ -888,20 +1071,14 @@ + 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); + INIT_LIST_HEAD(&steam->list); + 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. +@@ -910,10 +1087,6 @@ + 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, +@@ -939,17 +1112,29 @@ + } + } + ++ 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); + cancel_work_sync(&steam->rumble_work); + steam_alloc_fail: + hid_err(hdev, "%s: failed with error %d\n", +@@ -966,13 +1151,12 @@ + return; + } + +- 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_delayed_work_sync(&steam->mode_switch); + cancel_work_sync(&steam->work_connect); ++ hid_destroy_device(steam->client_hdev); ++ steam->client_hdev = NULL; ++ steam->client_opened = 0; + if (steam->quirks & STEAM_QUIRK_WIRELESS) { + hid_info(hdev, "Steam wireless receiver disconnected"); + } +@@ -1307,6 +1491,14 @@ + 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); ++ } + } + + /* +@@ -1439,10 +1631,8 @@ + + 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; +diff '--color=auto' -uraN a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +--- a/drivers/hid/ipts/Kconfig 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/Kconfig 2023-11-04 16:36:53.542316174 +0300 +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff '--color=auto' -uraN a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +--- a/drivers/hid/ipts/Makefile 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/Makefile 2023-11-04 16:36:53.542316174 +0300 +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff '--color=auto' -uraN a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +--- a/drivers/hid/ipts/cmd.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/cmd.c 2023-11-04 16:36:53.542316174 +0300 +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ 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 a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +--- a/drivers/hid/ipts/cmd.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/cmd.h 2023-11-04 16:36:53.542316174 +0300 +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++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 a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +--- a/drivers/hid/ipts/context.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/context.h 2023-11-04 16:36:53.542316174 +0300 +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +--- a/drivers/hid/ipts/control.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/control.c 2023-11-04 16:36:53.542316174 +0300 +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +--- a/drivers/hid/ipts/control.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/control.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +--- a/drivers/hid/ipts/desc.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/desc.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +--- a/drivers/hid/ipts/eds1.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/eds1.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++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) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +--- a/drivers/hid/ipts/eds1.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/eds1.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++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 a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +--- a/drivers/hid/ipts/eds2.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/eds2.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,144 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++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) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +--- a/drivers/hid/ipts/eds2.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/eds2.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++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 a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +--- a/drivers/hid/ipts/hid.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/hid.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +--- a/drivers/hid/ipts/hid.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/hid.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +--- a/drivers/hid/ipts/main.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/main.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff '--color=auto' -uraN a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +--- a/drivers/hid/ipts/mei.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/mei.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +--- a/drivers/hid/ipts/mei.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/mei.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +--- a/drivers/hid/ipts/receiver.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/receiver.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,250 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +--- a/drivers/hid/ipts/receiver.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/receiver.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +--- a/drivers/hid/ipts/resources.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/resources.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +--- a/drivers/hid/ipts/resources.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/resources.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +--- a/drivers/hid/ipts/spec-data.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/spec-data.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +--- a/drivers/hid/ipts/spec-device.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/spec-device.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +--- a/drivers/hid/ipts/spec-hid.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/spec-hid.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff '--color=auto' -uraN a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +--- a/drivers/hid/ipts/thread.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/thread.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff '--color=auto' -uraN a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +--- a/drivers/hid/ipts/thread.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ipts/thread.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +diff '--color=auto' -uraN a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +--- a/drivers/hid/ithc/Kbuild 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/Kbuild 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff '--color=auto' -uraN a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +--- a/drivers/hid/ithc/Kconfig 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/Kconfig 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff '--color=auto' -uraN a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +--- a/drivers/hid/ithc/ithc-debug.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/ithc-debug.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,96 @@ ++#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++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, loff_t *offset) { ++ 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; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ 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; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ 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]); ++ 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)); ++ 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; ++ 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]); ++ 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; ++ 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"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) { ++ struct dentry **dbgm = res; ++ 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; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); ++ 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); ++ ++ return 0; ++} ++ +diff '--color=auto' -uraN a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +--- a/drivers/hid/ithc/ithc-dma.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/ithc-dma.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,258 @@ ++#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) { ++ p->num_pages = num_pages; ++ p->dir = dir; ++ 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; ++ return 0; ++} ++ ++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) { ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) { ++ struct ithc_sg_table *sgt = res; ++ 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). ++ struct page *pages[16]; ++ 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; ++ 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; } ++ 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; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ 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) { ++ 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; } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ 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) { ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ 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) { ++ 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; } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ 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; ++ 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; } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname) { ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ 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); ++ 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++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ 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++) ++ 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); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) { ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ 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); ++ 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); ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ 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) { ++ 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) { ++ CHECK(ithc_reset, ithc); ++ } 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. ++ // 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); ++ } ++ } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { ++ 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) { ++ CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); ++ } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { ++ 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; ++ 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); ++ } 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); ++ } ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) { ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ 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; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++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); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) { ++ pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); ++ struct ithc_dma_tx_header *hdr; ++ 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; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ ithc->dma_tx.buf.data_size = fullsize; ++ hdr = ithc->dma_tx.buf.addr; ++ hdr->code = cmdcode; ++ hdr->data_size = datasize; ++ u8 *dest = (void *)(hdr + 1); ++ memcpy(dest, data, datasize); ++ dest += datasize; ++ 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); ++ ++ 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) { ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff '--color=auto' -uraN a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +--- a/drivers/hid/ithc/ithc-dma.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/ithc-dma.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,67 @@ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++#define DMA_RX_CODE_INPUT_REPORT 3 ++#define DMA_RX_CODE_FEATURE_REPORT 4 ++#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 ++#define DMA_RX_CODE_RESET 7 ++ ++struct ithc_dma_rx_header { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++}; ++ ++#define DMA_TX_CODE_SET_FEATURE 3 ++#define DMA_TX_CODE_GET_FEATURE 4 ++#define DMA_TX_CODE_OUTPUT_REPORT 5 ++#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++struct ithc_dma_tx_header { ++ u32 code; ++ u32 data_size; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ u32 max_size; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname); ++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); ++int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); ++ +diff '--color=auto' -uraN a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +--- a/drivers/hid/ithc/ithc-main.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/ithc-main.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,534 @@ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, ++ { 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) }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++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) { ++ return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; ++} ++ ++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; ++ 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) { ++ struct ithc *ithc = dev_get_drvdata(dev); ++ 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) { ++ struct ithc *ithc = dev_get_drvdata(dev); ++ 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) { ++ struct ithc *ithc = dev_get_drvdata(dev); ++ 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); ++} ++static DEVICE_ATTR_RO(fw_version); ++ ++static const struct attribute_group *ithc_attribute_groups[] = { ++ &(const struct attribute_group){ ++ .name = DEVNAME, ++ .attrs = (struct attribute *[]){ ++ &dev_attr_vendor.attr, ++ &dev_attr_product.attr, ++ &dev_attr_revision.attr, ++ &dev_attr_fw_version.attr, ++ NULL ++ }, ++ }, ++ NULL ++}; ++ ++// HID setup ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++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) { ++ 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; ++} ++ ++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; ++ 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); ++ return -EINVAL; ++ } ++ buf[0] = reportnum; ++ if (reqtype == HID_REQ_GET_REPORT) { ++ 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); ++ 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; ++ } ++ mutex_lock(&ithc->hid_get_feature_mutex); ++ ithc->hid_get_feature_buf = NULL; ++ if (!r) r = ithc->hid_get_feature_size; ++ mutex_unlock(&ithc->hid_get_feature_mutex); ++ return r; ++ } ++ CHECK_RET(ithc_dma_tx, ithc, code, len, buf); ++ return 0; ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) { ++ struct hid_device **hidm = res; ++ 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; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->config.vendor_id; ++ hid->product = ithc->config.product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid = hid; ++ return 0; ++} ++ ++// 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); ++} ++ ++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 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); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); ++ 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_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); ++} ++ ++static void ithc_process(struct ithc *ithc) { ++ ithc_log_regs(ithc); ++ ++ // 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); ++ } ++ ++ // process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++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), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) { ++ struct ithc *ithc = arg; ++ unsigned 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); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++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); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(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); ++ 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; ++ 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 (retries > 5) { ++ pci_err(ithc->pci, "too many retries, failed to reset device\n"); ++ return -ETIMEDOUT; ++ } ++ pci_err(ithc->pci, "invalid state, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); ++ ++ // read config ++ 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); ++ 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 (retries > 10) { ++ 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; ++ } ++ 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)); ++ 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? ++ 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); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++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); ++ CHECK(ithc_set_device_enabled, ithc, false); ++ ithc_disable(ithc); ++ del_timer_sync(&ithc->activity_timer); ++ cpu_latency_qos_remove_request(&ithc->activity_qos); ++ // clear dma config ++ for(unsigned 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); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ 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) { ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++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; ++ ++ 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)); ++ 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); ++ ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ 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; ++ } ++ ++ 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); ++ 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); ++ ++ // 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); ++ ++ 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 ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ 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); ++ ++ // 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); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++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) { ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++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) { ++ 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) { ++ 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) { ++ 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) { ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ //.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) { ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) { ++ pci_unregister_driver(&ithc_driver); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff '--color=auto' -uraN a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +--- a/drivers/hid/ithc/ithc-regs.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/ithc-regs.c 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,64 @@ ++#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); ++ 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); ++ 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); ++ 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); ++ 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); ++ 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); ++ return -ETIMEDOUT; ++ } ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++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; ++ 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) { ++ 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; ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ 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]); ++ 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]); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ return 0; ++} ++ +diff '--color=auto' -uraN a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +--- a/drivers/hid/ithc/ithc-regs.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/ithc-regs.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,186 @@ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) ++#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) ++#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#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_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[1024]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ /* 1014 */ u32 _unknown_1014[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ } dma_tx; ++ /* 10a0 */ u32 _unknown_10a0[7]; ++ /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 _unknown_10ec[5]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[23]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) ++#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) ++#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) ++#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) ++#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#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_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) ++ ++struct ithc_device_config { ++ u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 _unknown_04; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 _unknown_10; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ 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 _unknown_28; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 ++ 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) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++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 a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +--- a/drivers/hid/ithc/ithc.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hid/ithc/ithc.h 2023-11-04 16:36:53.545649567 +0300 +@@ -0,0 +1,60 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#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 NUM_RX_BUF 16 ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-dma.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct pm_qos_request activity_qos; ++ struct timer_list activity_timer; ++ ++ struct hid_device *hid; ++ bool hid_parse_done; ++ wait_queue_head_t wait_hid_parse; ++ wait_queue_head_t wait_hid_get_feature; ++ struct mutex hid_get_feature_mutex; ++ void *hid_get_feature_buf; ++ size_t hid_get_feature_size; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_device_config config; ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++void ithc_set_active(struct ithc *ithc); ++int ithc_debug_init(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +diff '--color=auto' -uraN a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hwmon/Kconfig 2023-11-04 16:36:53.572316712 +0300 +@@ -1931,6 +1931,17 @@ + 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 '--color=auto' -uraN a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +--- a/drivers/hwmon/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/hwmon/Makefile 2023-11-04 16:36:53.572316712 +0300 +@@ -197,6 +197,7 @@ + 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 '--color=auto' -uraN a/drivers/hwmon/steamdeck-hwmon.c b/drivers/hwmon/steamdeck-hwmon.c +--- a/drivers/hwmon/steamdeck-hwmon.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/hwmon/steamdeck-hwmon.c 2023-11-04 16:36:53.572316712 +0300 +@@ -0,0 +1,294 @@ ++// 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 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; ++ 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, ++ steamdeck_hwmon_groups); ++ 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"); +diff '--color=auto' -uraN a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +--- a/drivers/i2c/busses/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/i2c/busses/Kconfig 2023-11-04 16:35:57.814650153 +0300 +@@ -229,6 +229,15 @@ + 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 '--color=auto' -uraN a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +--- a/drivers/i2c/busses/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/i2c/busses/Makefile 2023-11-04 16:35:57.814650153 +0300 +@@ -20,6 +20,7 @@ + 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 '--color=auto' -uraN a/drivers/i2c/busses/i2c-nct6775.c b/drivers/i2c/busses/i2c-nct6775.c +--- a/drivers/i2c/busses/i2c-nct6775.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/i2c/busses/i2c-nct6775.c 2023-11-04 16:35:57.814650153 +0300 +@@ -0,0 +1,648 @@ ++/* ++ * 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; ++ fallthrough; ++ 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 '--color=auto' -uraN a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c +--- a/drivers/i2c/busses/i2c-piix4.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/i2c/busses/i2c-piix4.c 2023-11-04 16:35:57.814650153 +0300 +@@ -568,11 +568,11 @@ + 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) { +diff '--color=auto' -uraN a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +--- a/drivers/i2c/i2c-core-acpi.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/i2c/i2c-core-acpi.c 2023-11-04 16:36:53.545649567 +0300 +@@ -628,6 +628,28 @@ + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret = AE_OK; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + 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); + } + break; ++ ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ 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 a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c +--- a/drivers/iio/accel/bmc150-accel-core.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/iio/accel/bmc150-accel-core.c 2023-11-04 16:36:53.565649926 +0300 +@@ -130,6 +130,73 @@ + #define BMC150_ACCEL_REG_FIFO_DATA 0x3F + #define BMC150_ACCEL_FIFO_LENGTH 32 + ++#define BMC150_BMI323_TEMPER_CENTER_VAL 23 ++#define BMC150_BMI323_TEMPER_LSB_PER_KELVIN_VAL 512 ++ ++#define BMC150_BMI323_AUTO_SUSPEND_DELAY_MS 2000 ++ ++#define BMC150_BMI323_CHIP_ID_REG 0x00 ++#define BMC150_BMI323_SOFT_RESET_REG 0x7E ++#define BMC150_BMI323_SOFT_RESET_VAL 0xDEAFU ++#define BMC150_BMI323_DATA_BASE_REG 0x03 ++#define BMC150_BMI323_TEMPERATURE_DATA_REG 0x09 ++#define BMC150_BMI323_FIFO_FILL_LEVEL_REG 0x15 ++#define BMC150_BMI323_FIFO_DATA_REG 0x16 ++#define BMC150_BMI323_ACC_CONF_REG 0x20 ++#define BMC150_BMI323_GYR_CONF_REG 0x21 ++#define BMC150_BMI323_FIFO_CONF_REG 0x36 ++ ++// these are bits [0:3] of ACC_CONF.acc_odr, sample rate in Hz for the accel part of the chip ++#define BMC150_BMI323_ACCEL_ODR_0_78123_VAL 0x0001 ++#define BMC150_BMI323_ACCEL_ODR_1_5625_VAL 0x0002 ++#define BMC150_BMI323_ACCEL_ODR_3_125_VAL 0x0003 ++#define BMC150_BMI323_ACCEL_ODR_6_25_VAL 0x0004 ++#define BMC150_BMI323_ACCEL_ODR_12_5_VAL 0x0005 ++#define BMC150_BMI323_ACCEL_ODR_25_VAL 0x0006 ++#define BMC150_BMI323_ACCEL_ODR_50_VAL 0x0007 ++#define BMC150_BMI323_ACCEL_ODR_100_VAL 0x0008 ++#define BMC150_BMI323_ACCEL_ODR_200_VAL 0x0009 ++#define BMC150_BMI323_ACCEL_ODR_400_VAL 0x000A ++#define BMC150_BMI323_ACCEL_ODR_800_VAL 0x000B ++#define BMC150_BMI323_ACCEL_ODR_1600_VAL 0x000C ++#define BMC150_BMI323_ACCEL_ODR_3200_VAL 0x000D ++#define BMC150_BMI323_ACCEL_ODR_6400_VAL 0x000E ++ ++#define BMC150_BMI323_ACCEL_BW_ODR_2_VAL 0x0000 ++#define BMC150_BMI323_ACCEL_BW_ODR_4_VAL 0x0001 ++ ++// these are bits [4:6] of ACC_CONF.acc_range, full scale resolution ++#define BMC150_BMI323_ACCEL_RANGE_2_VAL 0x0000 // +/-2g, 16.38 LSB/mg ++#define BMC150_BMI323_ACCEL_RANGE_4_VAL 0x0001 // +/-4g, 8.19 LSB/mg ++#define BMC150_BMI323_ACCEL_RANGE_8_VAL 0x0002 // +/-8g, 4.10 LSB/mg ++#define BMC150_BMI323_ACCEL_RANGE_16_VAL 0x0003 // +/-4g, 2.05 LSB/mg ++ ++// these are bits [0:3] of GYR_CONF.gyr_odr, sample rate in Hz for the gyro part of the chip ++#define BMC150_BMI323_GYRO_ODR_0_78123_VAL 0x0001 ++#define BMC150_BMI323_GYRO_ODR_1_5625_VAL 0x0002 ++#define BMC150_BMI323_GYRO_ODR_3_125_VAL 0x0003 ++#define BMC150_BMI323_GYRO_ODR_6_25_VAL 0x0004 ++#define BMC150_BMI323_GYRO_ODR_12_5_VAL 0x0005 ++#define BMC150_BMI323_GYRO_ODR_25_VAL 0x0006 ++#define BMC150_BMI323_GYRO_ODR_50_VAL 0x0007 ++#define BMC150_BMI323_GYRO_ODR_100_VAL 0x0008 ++#define BMC150_BMI323_GYRO_ODR_200_VAL 0x0009 ++#define BMC150_BMI323_GYRO_ODR_400_VAL 0x000A ++#define BMC150_BMI323_GYRO_ODR_800_VAL 0x000B ++#define BMC150_BMI323_GYRO_ODR_1600_VAL 0x000C ++#define BMC150_BMI323_GYRO_ODR_3200_VAL 0x000D ++#define BMC150_BMI323_GYRO_ODR_6400_VAL 0x000E ++ ++#define BMC150_BMI323_GYRO_BW_ODR_2_VAL 0x0000 ++#define BMC150_BMI323_GYRO_BW_ODR_4_VAL 0x0001 ++ ++// these are bits [4:6] of GYR_CONF.gyr_range, full scale resolution ++#define BMC150_BMI323_GYRO_RANGE_125_VAL 0x0000 // +/-125°/s, 262.144 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_250_VAL 0x0001 // +/-250°/s, 131.2 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_500_VAL 0x0002 // +/-500°/s, 65.6 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_1000_VAL 0x0003 // +/-1000°/s, 32.8 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_2000_VAL 0x0004 // +/-2000°/s, 16.4 LSB/°/s ++ + enum bmc150_accel_axis { + AXIS_X, + AXIS_Y, +@@ -149,6 +216,654 @@ + u8 reg_range; + }; + ++/* ++ * This enum MUST not be altered as there are parts in the code that ++ * uses an int conversion to get the correct device register to read. ++ */ ++enum bmi323_axis { ++ BMI323_ACCEL_AXIS_X = 0, ++ BMI323_ACCEL_AXIS_Y, ++ BMI323_ACCEL_AXIS_Z, ++ BMI323_GYRO_AXIS_X, ++ BMI323_GYRO_AXIS_Y, ++ BMI323_GYRO_AXIS_Z, ++ BMI323_TEMP, ++ BMI323_AXIS_MAX, ++}; ++ ++static const struct bmi323_scale_accel_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_accel_scale_map[] = { ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_2_VAL << (u16)4, ++ .val = 0, ++ .val2 = 598, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_4_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1196, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_8_VAL << (u16)4, ++ .val = 0, ++ .val2 = 2392, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_16_VAL << (u16)4, ++ .val = 0, ++ .val2 = 4785, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++}; ++ ++static const struct bmi323_scale_gyro_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_gyro_scale_map[] = { ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4, ++ .val = 0, ++ .val2 = 66545, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4, ++ .val = 0, ++ .val2 = 66, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_250_VAL << (u16)4, ++ .val = 0, ++ .val2 = 133090, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_250_VAL << (u16)4, ++ .val = 0, ++ .val2 = 133, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_500_VAL << (u16)4, ++ .val = 0, ++ .val2 = 266181, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_500_VAL << (u16)4, ++ .val = 0, ++ .val2 = 266, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_1000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 532362, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_1000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 532, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1064724, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ // this shouldn't be necessary, but iio seems to have a wrong rounding of this value... ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1064, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1065, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++}; ++ ++/* ++ * this reflects the frequency map that is following. ++ * For each index i of that map index i*2 and i*2+1 of of this ++ * holds ODR/2 and ODR/4 ++ */ ++static const struct bmi323_3db_freq_cutoff_accel_info { ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_accel_3db_freq_cutoff[] = { ++ { ++ .val = 0, ++ .val2 = 390615, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 195308, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 781300, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 390650, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 562500, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 78125, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 250000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++}; ++ ++static const struct bmi323_freq_accel_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ s64 time_ns; ++} bmi323_accel_odr_map[] = { ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_0_78123_VAL, ++ .val = 0, ++ .val2 = 781230, ++ .time_ns = 1280032769, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_1_5625_VAL, ++ .val = 1, ++ .val2 = 562600, ++ .time_ns = 886522247, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_3_125_VAL, ++ .val = 3, ++ .val2 = 125000, ++ .time_ns = 320000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_6_25_VAL, ++ .val = 6, ++ .val2 = 250000, ++ .time_ns = 160000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_12_5_VAL, ++ .val = 12, ++ .val2 = 500000, ++ .time_ns = 80000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_25_VAL, ++ .val = 25, ++ .val2 = 0, ++ .time_ns = 40000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_50_VAL, ++ .val = 50, ++ .val2 = 0, ++ .time_ns = 20000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_100_VAL, ++ .val = 100, ++ .val2 = 0, ++ .time_ns = 10000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_200_VAL, ++ .val = 200, ++ .val2 = 0, ++ .time_ns = 5000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_400_VAL, ++ .val = 400, ++ .val2 = 0, ++ .time_ns = 2500000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_800_VAL, ++ .val = 800, ++ .val2 = 0, ++ .time_ns = 1250000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_1600_VAL, ++ .val = 1600, ++ .val2 = 0, ++ .time_ns = 625000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_3200_VAL, ++ .val = 3200, ++ .val2 = 0, ++ .time_ns = 312500, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_6400_VAL, ++ .val = 6400, ++ .val2 = 0, ++ .time_ns = 156250, ++ }, ++}; ++ ++static const struct bmi323_freq_gyro_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ s64 time_ns; ++} bmi323_gyro_odr_map[] = { ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_0_78123_VAL, ++ .val = 0, ++ .val2 = 781230, ++ .time_ns = 1280032769, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_1_5625_VAL, ++ .val = 1, ++ .val2 = 562600, ++ .time_ns = 886522247, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_3_125_VAL, ++ .val = 3, ++ .val2 = 125000, ++ .time_ns = 320000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_6_25_VAL, ++ .val = 6, ++ .val2 = 250000, ++ .time_ns = 160000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_12_5_VAL, ++ .val = 12, ++ .val2 = 500000, ++ .time_ns = 80000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_25_VAL, ++ .val = 25, ++ .val2 = 0, ++ .time_ns = 40000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_50_VAL, ++ .val = 50, ++ .val2 = 0, ++ .time_ns = 20000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_100_VAL, ++ .val = 100, ++ .val2 = 0, ++ .time_ns = 10000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_200_VAL, ++ .val = 200, ++ .val2 = 0, ++ .time_ns = 5000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_400_VAL, ++ .val = 400, ++ .val2 = 0, ++ .time_ns = 2500000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_800_VAL, ++ .val = 800, ++ .val2 = 0, ++ .time_ns = 1250000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_1600_VAL, ++ .val = 1600, ++ .val2 = 0, ++ .time_ns = 625000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_3200_VAL, ++ .val = 3200, ++ .val2 = 0, ++ .time_ns = 312500, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_6400_VAL, ++ .val = 6400, ++ .val2 = 0, ++ .time_ns = 156250, ++ }, ++}; ++ ++static const struct bmi323_3db_freq_cutoff_gyro_info { ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_gyro_3db_freq_cutoff[] = { ++ { ++ .val = 0, ++ .val2 = 390615, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 1953075, // TODO: check if this gets reported correctly... ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 781300, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 390650, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 562500, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 78125, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 250000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++}; ++ ++static const int bmi323_accel_scales[] = { ++ 0, 598, 0, 1196, 0, 2392, 0, 4785, ++}; ++ ++static const int bmi323_gyro_scales[] = { ++ 0, 66545, 0, 133090, 0, 266181, 0, 532362, 0, 1064724, ++}; ++ ++static const int bmi323_sample_freqs[] = { ++ 0, 781230, 1, 562600, 3, 125000, 6, 250000, 12, 500000, ++ 25, 0, 50, 0, 100, 0, 200, 0, 400, 0, ++ 800, 0, 1600, 0, 3200, 0, 6400, 0, ++}; ++ ++static const struct { ++ int val; ++ int val2; // IIO_VAL_INT_PLUS_MICRO ++ u8 bw_bits; ++} bmi323_samp_freq_table[] = { { 15, 620000, 0x08 }, { 31, 260000, 0x09 }, ++ { 62, 500000, 0x0A }, { 125, 0, 0x0B }, ++ { 250, 0, 0x0C }, { 500, 0, 0x0D }, ++ { 1000, 0, 0x0E }, { 2000, 0, 0x0F } }; ++ + struct bmc150_accel_chip_info { + const char *name; + u8 chip_id; +@@ -1113,6 +1828,52 @@ + .num_event_specs = 1 \ + } + ++#define BMI323_ACCEL_CHANNEL(_axis, bits) \ ++ { \ ++ .type = IIO_ACCEL, .modified = 1, .channel2 = IIO_MOD_##_axis, \ ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ ++ .info_mask_shared_by_type = \ ++ BIT(IIO_CHAN_INFO_SCALE) | \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ ++ .info_mask_shared_by_type_available = \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_SCALE), \ ++ .scan_index = BMI323_ACCEL_AXIS_##_axis, \ ++ .scan_type = { \ ++ .sign = 's', \ ++ .realbits = (bits), \ ++ .storagebits = 16, \ ++ .shift = 16 - (bits), \ ++ .endianness = IIO_LE, \ ++ }, \ ++ } ++ ++#define BMI323_GYRO_CHANNEL(_axis, bits) \ ++ { \ ++ .type = IIO_ANGL_VEL, .modified = 1, \ ++ .channel2 = IIO_MOD_##_axis, \ ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ ++ .info_mask_shared_by_type = \ ++ BIT(IIO_CHAN_INFO_SCALE) | \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ ++ .info_mask_shared_by_type_available = \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_SCALE), \ ++ .scan_index = BMI323_GYRO_AXIS_##_axis, \ ++ .scan_type = { \ ++ .sign = 's', \ ++ .realbits = (bits), \ ++ .storagebits = 16, \ ++ .shift = 16 - (bits), \ ++ .endianness = IIO_LE, \ ++ }, \ ++ /*.ext_info = bmi323_accel_ext_info,*/ \ ++ /*.event_spec = &bmi323_accel_event,*/ \ ++ /*.num_event_specs = 1*/ \ ++ } ++ + #define BMC150_ACCEL_CHANNELS(bits) { \ + { \ + .type = IIO_TEMP, \ +@@ -1595,7 +2356,7 @@ + struct device *dev = regmap_get_device(data->regmap); + int ret, i; + unsigned int val; +- ++ + /* + * Reset chip to get it in a known good state. A delay of 1.8ms after + * reset is required according to the data sheets of supported chips. +@@ -1677,6 +2438,11 @@ + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + ++ /* ++ * Setting the dev_type here is necessary to avoid having it left uninitialized ++ * and therefore potentially executing bmi323 functions for the original bmc150 model. ++ */ ++ data->dev_type = BMC150; + data->regmap = regmap; + data->type = type; + +@@ -1826,12 +2592,1407 @@ + } + EXPORT_SYMBOL_NS_GPL(bmc150_accel_core_remove, IIO_BMC150); + ++struct device *bmi323_get_managed_device(struct bmi323_private_data *bmi323) ++{ ++ if (bmi323->i2c_client != NULL) ++ return &bmi323->i2c_client->dev; ++ ++ return &bmi323->spi_client->dev; ++} ++ ++static int bmi323_set_power_state(struct bmi323_private_data *bmi323, bool on) ++{ ++#ifdef CONFIG_PM ++ struct device *dev = bmi323_get_managed_device(bmi323); ++ int ret; ++ ++ if (on) ++ ret = pm_runtime_get_sync(dev); ++ else { ++ pm_runtime_mark_last_busy(dev); ++ ret = pm_runtime_put_autosuspend(dev); ++ } ++ ++ if (ret < 0) { ++ dev_err(dev, "bmi323_set_power_state failed with %d\n", on); ++ ++ if (on) ++ pm_runtime_put_noidle(dev); ++ ++ return ret; ++ } ++#endif ++ ++ return 0; ++} ++ ++int bmi323_write_u16(struct bmi323_private_data *bmi323, u8 in_reg, ++ u16 in_value) ++{ ++ s32 ret; ++ ++ if (bmi323->i2c_client != NULL) { ++ ret = i2c_smbus_write_i2c_block_data(bmi323->i2c_client, in_reg, ++ sizeof(in_value), ++ (u8 *)(&in_value)); ++ if (ret != 0) { ++ return -2; ++ } ++ ++ return 0; ++ } else if (bmi323->spi_client != NULL) { ++ /* ++ * To whoever may need this: implementing this should be straightforward: ++ * it's specular to the i2c part. ++ */ ++ ++ return -EINVAL; // TODO: change with 0 once implemented ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_write_u16, IIO_BMC150); ++ ++int bmi323_read_u16(struct bmi323_private_data *bmi323, u8 in_reg, ++ u16 *out_value) ++{ ++ s32 ret; ++ u8 read_bytes[4]; ++ ++ if (bmi323->i2c_client != NULL) { ++ ret = i2c_smbus_read_i2c_block_data(bmi323->i2c_client, in_reg, ++ sizeof(read_bytes), ++ &read_bytes[0]); ++ if (ret != 4) { ++ return ret; ++ } ++ ++ // DUMMY = read_bytes[0] ++ // DUMMY = read_bytes[1] ++ // LSB = read_bytes[2] ++ // MSB = read_bytes[3] ++ u8 *o = (u8 *)out_value; ++ o[0] = read_bytes[2]; ++ o[1] = read_bytes[3]; ++ ++ return 0; ++ } else if (bmi323->spi_client != NULL) { ++ printk(KERN_CRIT ++ "bmi323: SPI interface is not yet implemented.\n"); ++ ++ /* ++ * To whoever may need this: implementing this should be straightforward: ++ * it's specular to the i2c part except that the dummy data is just 1 byte. ++ */ ++ ++ return -EINVAL; // TODO: change with 0 once implemented ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_read_u16, IIO_BMC150); ++ ++int bmi323_chip_check(struct bmi323_private_data *bmi323) ++{ ++ u16 chip_id; ++ int ret; ++ ++ ret = bmi323_read_u16(bmi323, BMC150_BMI323_CHIP_ID_REG, &chip_id); ++ if (ret != 0) { ++ return ret; ++ } ++ ++ if (((chip_id)&0x00FF) != cpu_to_le16((u16)0x0043U)) { ++ dev_err(bmi323->dev, ++ "bmi323_chip_check failed with: %d; chip_id = 0x%04x", ++ ret, chip_id); ++ ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_chip_check, IIO_BMC150); ++ ++static int bmi323_buffer_preenable(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ const int ret = bmi323_set_power_state(&data->bmi323, true); ++ ++ if (ret == 0) { ++ mutex_lock(&data->bmi323.mutex); ++ data->bmi323.fifo_frame_time_diff_ns = ++ (data->bmi323.acc_odr_time_ns >= ++ data->bmi323.gyr_odr_time_ns) ? ++ data->bmi323.acc_odr_time_ns : ++ data->bmi323.gyr_odr_time_ns; ++ mutex_unlock(&data->bmi323.mutex); ++ } ++ ++ return ret; ++} ++ ++static int bmi323_buffer_postenable(struct iio_dev *indio_dev) ++{ ++ //struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ /* ++ * This code is a placeholder until I can get a way to test it ++ */ ++ ++ return 0; ++} ++ ++static int bmi323_buffer_predisable(struct iio_dev *indio_dev) ++{ ++ //struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ /* ++ * This code is a placeholder until I can get a way to test it ++ */ ++ ++ return 0; ++} ++ ++static int bmi323_buffer_postdisable(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ return bmi323_set_power_state(&data->bmi323, true); ++} ++ ++static const struct iio_buffer_setup_ops bmi323_buffer_ops = { ++ .preenable = bmi323_buffer_preenable, ++ .postenable = bmi323_buffer_postenable, ++ .predisable = bmi323_buffer_predisable, ++ .postdisable = bmi323_buffer_postdisable, ++}; ++ ++int bmi323_chip_rst(struct bmi323_private_data *bmi323) ++{ ++ u16 sensor_status = 0x0000, device_status = 0x0000; ++ int ret; ++ ++ ret = bmi323_write_u16(bmi323, BMC150_BMI323_SOFT_RESET_REG, ++ cpu_to_le16((u16)BMC150_BMI323_SOFT_RESET_VAL)); ++ if (ret != 0) { ++ dev_err(bmi323->dev, ++ "bmi323: error while issuing the soft-reset command: %d", ++ ret); ++ return ret; ++ } ++ ++ /* wait the specified amount of time... I agree with the bmc150 module: better safe than sorry. */ ++ msleep(5); ++ ++ // if the device is connected over SPI a dummy read is to be performed once after each reset ++ if (bmi323->spi_client != NULL) { ++ dev_info(bmi323->dev, ++ "issuing the dummy read to switch mode to SPI"); ++ ++ // do not even check the result of that... it's just a dummy read ++ bmi323_chip_check(bmi323); ++ } ++ ++ ret = bmi323_chip_check(bmi323); ++ if (ret != 0) { ++ return ret; ++ } ++ ++ /* now check the correct initialization status as per datasheet */ ++ ret = bmi323_read_u16(bmi323, 0x01, &device_status); ++ if (ret != 0) { ++ return -EINVAL; ++ } ++ ++ if ((device_status & cpu_to_le16((u16)0x00FFU)) != ++ cpu_to_le16((u16)0x0000U)) { ++ dev_err(bmi323->dev, ++ "bmi323: device_status incorrect: %d; device_status = 0x%04x", ++ ret, device_status); ++ ++ /* from the datasheet: power error */ ++ return -EINVAL; ++ } ++ ++ /* from the datasheet: power ok */ ++ ret = bmi323_read_u16(bmi323, 0x02, &sensor_status); ++ if (ret != 0) { ++ return -EINVAL; ++ } ++ ++ if ((sensor_status & cpu_to_le16((u16)0x00FFU)) != ++ cpu_to_le16((u16)0x0001U)) { ++ dev_err(bmi323->dev, ++ "bmi323: sensor_status incorrect: %d; sensor_status = 0x%04x", ++ ret, sensor_status); ++ ++ /* from the datasheet: initialization error */ ++ return -EINVAL; ++ } ++ ++ /* from the datasheet: initialization ok */ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_chip_rst, IIO_BMC150); ++ ++static const struct iio_chan_spec bmi323_channels[] = { ++ BMI323_ACCEL_CHANNEL(X, 16), ++ BMI323_ACCEL_CHANNEL(Y, 16), ++ BMI323_ACCEL_CHANNEL(Z, 16), ++ BMI323_GYRO_CHANNEL(X, 16), ++ BMI323_GYRO_CHANNEL(Y, 16), ++ BMI323_GYRO_CHANNEL(Z, 16), ++ { ++ .type = IIO_TEMP, ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | ++ BIT(IIO_CHAN_INFO_SCALE) | ++ BIT(IIO_CHAN_INFO_OFFSET), ++ .scan_index = BMI323_TEMP, ++ }, ++ IIO_CHAN_SOFT_TIMESTAMP(BMI323_AXIS_MAX), ++}; ++ ++static int bmi323_read_raw(struct iio_dev *indio_dev, ++ struct iio_chan_spec const *chan, int *val, ++ int *val2, long mask) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ int ret = -EINVAL, was_sleep_modified = -1; ++ u16 raw_read = 0x8000; ++ ++ mutex_lock(&data->bmi323.mutex); ++ ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ dev_err(data->bmi323.dev, ++ "bmi323 error: device has not being woken up correctly."); ++ mutex_unlock(&data->bmi323.mutex); ++ return -EBUSY; ++ } ++ ++ switch (mask) { ++ case IIO_CHAN_INFO_RAW: { ++ switch (chan->type) { ++ case IIO_TEMP: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_read_raw_error; ++ } ++ ++ was_sleep_modified = ++ bmi323_set_power_state(&data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_read_raw_error_power; ++ } ++ ++ ret = iio_device_claim_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_TEMP iio_device_claim_direct_mode returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ ret = bmi323_read_u16( ++ &data->bmi323, ++ BMC150_BMI323_TEMPERATURE_DATA_REG, &raw_read); ++ iio_device_release_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_TEMP bmi323_read_u16 returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ *val = sign_extend32(le16_to_cpu(raw_read), 15); ++ bmi323_set_power_state(&data->bmi323, false); ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ case IIO_ACCEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_read_raw_error; ++ } ++ ++ was_sleep_modified = ++ bmi323_set_power_state(&data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_read_raw_error_power; ++ } ++ ++ ret = iio_device_claim_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ACCEL iio_device_claim_direct_mode returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ ret = bmi323_read_u16(&data->bmi323, ++ BMC150_BMI323_DATA_BASE_REG + ++ (u8)(chan->scan_index), ++ &raw_read); ++ iio_device_release_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ACCEL bmi323_read_u16 returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ *val = sign_extend32(le16_to_cpu(raw_read), 15); ++ bmi323_set_power_state(&data->bmi323, false); ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ case IIO_ANGL_VEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_read_raw_error; ++ } ++ ++ was_sleep_modified = ++ bmi323_set_power_state(&data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_read_raw_error_power; ++ } ++ ++ ret = iio_device_claim_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ANGL_VEL iio_device_claim_direct_mode returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ ret = bmi323_read_u16(&data->bmi323, ++ BMC150_BMI323_DATA_BASE_REG + ++ (u8)(chan->scan_index), ++ &raw_read); ++ iio_device_release_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ANGL_VEL bmi323_read_u16 returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ *val = sign_extend32(le16_to_cpu(raw_read), 15); ++ bmi323_set_power_state(&data->bmi323, false); ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ default: ++ goto bmi323_read_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_OFFSET: { ++ switch (chan->type) { ++ case IIO_TEMP: ++ *val = BMC150_BMI323_TEMPER_CENTER_VAL; ++ *val2 = 0; ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_SCALE: ++ switch (chan->type) { ++ case IIO_TEMP: { ++ *val = 0; ++ *val2 = BMC150_BMI323_TEMPER_LSB_PER_KELVIN_VAL; ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_FRACTIONAL; ++ } ++ case IIO_ACCEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.acc_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_scale_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0b01110000U)) == ++ (bmi323_accel_scale_map[s].hw_val)) { ++ *val = bmi323_accel_scale_map[s].val; ++ *val2 = bmi323_accel_scale_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return bmi323_accel_scale_map[s] ++ .ret_type; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_ANGL_VEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.gyr_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_scale_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0b01110000U)) == ++ (bmi323_gyro_scale_map[s].hw_val)) { ++ *val = bmi323_gyro_scale_map[s].val; ++ *val2 = bmi323_gyro_scale_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return bmi323_gyro_scale_map[s].ret_type; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: ++ switch (chan->type) { ++ case IIO_ACCEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.acc_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_accel_odr_map[s].hw_val)) { ++ /* ++ * from tha datasheed: -3dB cut-off frequency can be configured with the bit 7 of GYR_confm, ++ * also called acc_bw that can either be 0 or 1, where 1 means odr/4 and 0 means odr/2 ++ */ ++ int freq_adj_idx = ++ (((le_raw_read[0]) & ++ ((u8)0x80U)) == (u8)0x00U) ? ++ (s * 2) + 0 : ++ (s * 2) + 1; ++ *val = bmi323_accel_3db_freq_cutoff ++ [freq_adj_idx] ++ .val; ++ *val2 = bmi323_accel_3db_freq_cutoff ++ [freq_adj_idx] ++ .val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_ANGL_VEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.gyr_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_gyro_odr_map[s].hw_val)) { ++ /* ++ * from tha datasheed: -3dB cut-off frequency can be configured with the bit 7 of GYR_confm, ++ * also called acc_bw that can either be 0 or 1, where 1 means odr/4 and 0 means odr/2 ++ */ ++ int freq_adj_idx = ++ (((le_raw_read[0]) & ++ ((u8)0x80U)) == (u8)0x0000U) ? ++ (s * 2) + 0 : ++ (s * 2) + 1; ++ *val = bmi323_gyro_3db_freq_cutoff ++ [freq_adj_idx] ++ .val; ++ *val2 = bmi323_gyro_3db_freq_cutoff ++ [freq_adj_idx] ++ .val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return bmi323_gyro_3db_freq_cutoff ++ [freq_adj_idx] ++ .ret_type; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: { ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_SAMP_FREQ: ++ switch (chan->type) { ++ case IIO_TEMP: { ++ ++ // while in normal or power mode the temperature sensur has a 50Hz sampling frequency ++ *val = 50; ++ *val2 = 0; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ case IIO_ACCEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.acc_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_accel_odr_map[s].hw_val)) { ++ *val = bmi323_accel_odr_map[s].val; ++ *val2 = bmi323_accel_odr_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_ANGL_VEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.gyr_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_gyro_odr_map[s].hw_val)) { ++ *val = bmi323_gyro_odr_map[s].val; ++ *val2 = bmi323_gyro_odr_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ ++bmi323_read_raw_error: ++ if (was_sleep_modified == 0) { ++ bmi323_set_power_state(&data->bmi323, false); ++ } ++ ++bmi323_read_raw_error_power: ++ mutex_unlock(&data->bmi323.mutex); ++ return ret; ++} ++ ++static int bmi323_write_raw(struct iio_dev *indio_dev, ++ struct iio_chan_spec const *chan, int val, int val2, ++ long mask) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ int ret = -EINVAL, was_sleep_modified = -1; ++ ++ mutex_lock(&data->bmi323.mutex); ++ ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ dev_err(data->bmi323.dev, ++ "bmi323 error: device has not being woken up correctly."); ++ mutex_unlock(&data->bmi323.mutex); ++ return -EBUSY; ++ } ++ ++ switch (mask) { ++ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: ++ switch (chan->type) { ++ default: { ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_SAMP_FREQ: ++ switch (chan->type) { ++ case IIO_ACCEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); ++ ++s) { ++ if ((bmi323_accel_odr_map[s].val == val) && ++ (bmi323_accel_odr_map[s].val2 == val2)) { ++ const u16 conf_backup = ++ data->bmi323.acc_conf_reg_value; ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .acc_conf_reg_value; ++ le_raw_read[0] &= (u8)0b11110000U; ++ le_raw_read[0] |= ++ ((u8)bmi323_gyro_odr_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ data->bmi323.acc_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ data->bmi323.acc_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error; ++ } ++ ++ data->bmi323.acc_odr_time_ns = ++ bmi323_accel_odr_map[s].time_ns; ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ case IIO_ANGL_VEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); ++ ++s) { ++ if ((bmi323_gyro_odr_map[s].val == val) && ++ (bmi323_gyro_odr_map[s].val2 == val2)) { ++ const u16 conf_backup = ++ data->bmi323.gyr_conf_reg_value; ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .gyr_conf_reg_value; ++ le_raw_read[0] &= (u8)0b11110000U; ++ le_raw_read[0] |= ++ ((u8)bmi323_gyro_odr_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ data->bmi323.gyr_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ data->bmi323.gyr_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error; ++ } ++ ++ data->bmi323.gyr_odr_time_ns = ++ bmi323_gyro_odr_map[s].time_ns; ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ ++ /* Termometer also ends up here: its sampling frequency depends on the chip configuration and cannot be changed */ ++ default: ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ ++ break; ++ case IIO_CHAN_INFO_SCALE: ++ switch (chan->type) { ++ case IIO_ACCEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_scale_map); ++ ++s) { ++ if ((bmi323_accel_scale_map[s].val == val) && ++ (bmi323_accel_scale_map[s].val2 == val2)) { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .acc_conf_reg_value; ++ le_raw_read[0] &= (u8)0b10001111U; ++ le_raw_read[0] |= ++ ((u8)bmi323_accel_scale_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_write_raw_error; ++ } ++ ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ dev_warn( ++ data->bmi323.dev, ++ "bmi323 error: accel scale val=%d,val2=%d unavailable: ignoring.", ++ val, val2); ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ case IIO_ANGL_VEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_scale_map); ++ ++s) { ++ if ((bmi323_gyro_scale_map[s].val == val) && ++ (bmi323_gyro_scale_map[s].val2 == val2)) { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .gyr_conf_reg_value; ++ le_raw_read[0] &= (u8)0b10001111U; ++ le_raw_read[0] |= ++ ((u8)bmi323_gyro_scale_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_write_raw_error; ++ } ++ ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ dev_warn( ++ data->bmi323.dev, ++ "bmi323 error: gyro scale val=%d,val2=%d unavailable: ignoring.", ++ val, val2); ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ ++ default: ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ ++ default: ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ ++bmi323_write_raw_error: ++ if (was_sleep_modified == 0) { ++ bmi323_set_power_state(&data->bmi323, false); ++ } ++ ++bmi323_write_raw_error_power: ++ mutex_unlock(&data->bmi323.mutex); ++ return ret; ++} ++ ++static int bmi323_read_avail(struct iio_dev *indio_dev, ++ struct iio_chan_spec const *chan, const int **vals, ++ int *type, int *length, long mask) ++{ ++ switch (mask) { ++ case IIO_CHAN_INFO_SCALE: ++ switch (chan->type) { ++ case IIO_ACCEL: ++ *type = IIO_VAL_INT_PLUS_MICRO; ++ *vals = bmi323_accel_scales; ++ *length = ARRAY_SIZE(bmi323_accel_scales); ++ return IIO_AVAIL_LIST; ++ case IIO_ANGL_VEL: ++ *type = IIO_VAL_INT_PLUS_NANO; ++ *vals = bmi323_gyro_scales; ++ *length = ARRAY_SIZE(bmi323_gyro_scales); ++ return IIO_AVAIL_LIST; ++ default: ++ return -EINVAL; ++ } ++ case IIO_CHAN_INFO_SAMP_FREQ: ++ *type = IIO_VAL_INT_PLUS_MICRO; ++ *vals = bmi323_sample_freqs; ++ *length = ARRAY_SIZE(bmi323_sample_freqs); ++ return IIO_AVAIL_LIST; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static const struct iio_info bmi323_accel_info = { ++ .read_raw = bmi323_read_raw, ++ .write_raw = bmi323_write_raw, ++ .read_avail = bmi323_read_avail, ++ //.hwfifo_flush_to_buffer = bmi323_fifo_flush, ++}; ++ ++static int bmi323_fifo_flush(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ int ret; ++ ++ ret = bmi323_write_u16(&data->bmi323, 0x37, cpu_to_le16(0x01)); ++ ++ return ret; ++} ++ ++static const u16 stub_value = 0x8000; ++ ++#define ADVANCE_AT_REQ_OR_AVAIL(req, avail, dst, dst_offset, src, src_offset) \ ++ if (req) { \ ++ if (gyr_avail) { \ ++ memcpy((void *)(dst + dst_offset), \ ++ (const void *)(src + src_offset), 2); \ ++ src_offset += 2; \ ++ } else { \ ++ memcpy((void *)(dst + dst_offset), \ ++ (const void *)((const u8 *)(&stub_value)), 2); \ ++ } \ ++ dst_offset += 2; \ ++ } else { \ ++ if (avail) { \ ++ src_offset += 2; \ ++ } \ ++ } ++ ++static irqreturn_t iio_bmi323_trigger_h(int irq, void *p) ++{ ++ printk(KERN_WARNING "bmi323 executed iio_bmi323_trigger_h"); ++ ++ struct iio_poll_func *pf = p; ++ struct iio_dev *indio_dev = pf->indio_dev; ++ struct bmc150_accel_data *indio_data = iio_priv(indio_dev); ++ ++ mutex_lock(&indio_data->bmi323.mutex); ++ ++ const bool temp_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000100000000000))) != 0); ++ const bool gyr_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000010000000000))) != 0); ++ const bool acc_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000001000000000))) != 0); ++ const bool time_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000000100000000))) != 0); ++ ++ /* Calculate the number of bytes for a frame */ ++ const u16 frames_aggregate_size_in_words = ++ /* 2 * */ ((temp_avail ? 1 : 0) + (gyr_avail ? 3 : 0) + ++ (acc_avail ? 3 : 0) + (time_avail ? 1 : 0)); ++ ++ u16 available_words = 0; ++ const int available_words_read_res = bmi323_read_u16( ++ &indio_data->bmi323, BMC150_BMI323_FIFO_FILL_LEVEL_REG, ++ &available_words); ++ if (available_words_read_res != 0) { ++ goto bmi323_irq_done; ++ } ++ ++ const u16 available_frame_aggregates = (le16_to_cpu(available_words)) / ++ (frames_aggregate_size_in_words); ++ ++ const s64 current_timestamp_ns = iio_get_time_ns(indio_dev); ++ const s64 fifo_frame_time_ns = ++ indio_data->bmi323.fifo_frame_time_diff_ns; ++ const s64 first_sample_timestamp_ns = ++ current_timestamp_ns - ++ (fifo_frame_time_ns * (s64)(available_frame_aggregates)); ++ ++ /* This can hold one full block */ ++ u8 temp_data[16]; ++ ++ /* This is fifo data as read from the sensor */ ++ u8 fifo_data[32]; ++ ++ /* ++ | CHANNEL | scan_index ++ |============================ ++ | | | ++ | ACCEL_X | 0 | ++ | ACCEL_Y | 1 | ++ | ACCEL_Y | 2 | ++ | GYRO_X | 3 | ++ | GYRO_Y | 4 | ++ | GYRO_Z | 5 | ++ | TEMP | 6 | ++ | TIMESTAMP | ? | ++ */ ++ bool accel_x_requested = false; ++ bool accel_y_requested = false; ++ bool accel_z_requested = false; ++ bool gyro_x_requested = false; ++ bool gyro_y_requested = false; ++ bool gyro_z_requested = false; ++ bool temp_requested = false; ++ ++ int j = 0; ++ for_each_set_bit(j, indio_dev->active_scan_mask, ++ indio_dev->masklength) { ++ switch (j) { ++ case 0: ++ accel_x_requested = true; ++ break; ++ case 1: ++ accel_y_requested = true; ++ break; ++ case 2: ++ accel_z_requested = true; ++ break; ++ case 3: ++ gyro_x_requested = true; ++ break; ++ case 4: ++ gyro_y_requested = true; ++ break; ++ case 5: ++ gyro_z_requested = true; ++ break; ++ case 6: ++ temp_requested = true; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ u16 current_fifo_buffer_offset_bytes = 0; ++ for (u16 f = 0; f < available_frame_aggregates; ++f) { ++ u16 current_sample_buffer_offset = 0; ++ ++ /* Read data from the raw device */ ++ if (indio_data->bmi323.i2c_client != NULL) { ++ const int bytes_to_read = ++ 2 + (2 * frames_aggregate_size_in_words); ++ int read_block_ret = i2c_smbus_read_i2c_block_data( ++ indio_data->bmi323.i2c_client, ++ BMC150_BMI323_FIFO_DATA_REG, bytes_to_read, ++ &fifo_data[0]); ++ if (read_block_ret < bytes_to_read) { ++ dev_warn( ++ &indio_data->bmi323.i2c_client->dev, ++ "bmi323: i2c_smbus_read_i2c_block_data wrong return: expected %d bytes, %d arrived. Doing what is possible with recovered data.\n", ++ bytes_to_read, read_block_ret); ++ ++ /* at this point FIFO buffer must be flushed to avoid interpreting data incorrectly the next trigger */ ++ const int flush_res = ++ bmi323_fifo_flush(indio_dev); ++ if (flush_res != 0) { ++ dev_err(&indio_data->bmi323.i2c_client ++ ->dev, ++ "bmi323: Could not flush FIFO (%d). Following buffer data might be corrupted.\n", ++ flush_res); ++ } ++ ++ goto bmi323_irq_done; ++ } ++ ++ /* Discard 2-bytes dummy data from I2C */ ++ current_fifo_buffer_offset_bytes = 2; ++ } else if (indio_data->bmi323.spi_client != NULL) { ++ printk(KERN_CRIT ++ "bmi323: SPI interface is not yet implemented.\n"); ++ ++ /* ++ * To whoever may need this: implementing this should be straightforward: ++ * it's specular to the i2c part. ++ */ ++ ++ /* Discard 1-byte dummy data from SPI */ ++ current_fifo_buffer_offset_bytes = 1; ++ ++ goto bmi323_irq_done; ++ } ++ ++ ADVANCE_AT_REQ_OR_AVAIL(accel_x_requested, acc_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(accel_y_requested, acc_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(accel_z_requested, acc_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(gyro_x_requested, gyr_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(gyro_y_requested, gyr_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(gyro_z_requested, gyr_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(temp_requested, temp_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ++#ifdef BMC150_BMI232_DEBUG_EN ++ /* The following is code only used for debugging */ ++ u16 timestamp = 0; ++ if (time_avail) { ++ memcpy((u8 *)×tamp, ++ (const u8 ++ *)(&fifo_data ++ [current_fifo_buffer_offset_bytes]), ++ 2); ++ current_fifo_buffer_offset_bytes += 2; ++ } ++ ++ u16 *debg = (u16 *)&temp_data[0]; ++ if (!time_avail) { ++ printk(KERN_WARNING ++ "bmi323 pushing to buffer %d/%d -- accel: %d %d %d gyro: %d %d %d", ++ (int)(f + 1), (int)available_frame_aggregates, ++ (int)(*((s16 *)&debg[0])), ++ (int)(*((s16 *)&debg[1])), ++ (int)(*((s16 *)&debg[2])), ++ (int)(*((s16 *)&debg[3])), ++ (int)(*((s16 *)&debg[4])), ++ (int)(*((s16 *)&debg[5]))); ++ } else { ++ printk(KERN_WARNING ++ "bmi323 pushing to buffer %d/%d -- time: %d accel: %d %d %d gyro: %d %d %d", ++ (int)(f + 1), (int)available_frame_aggregates, ++ (int)timestamp, (int)(*((s16 *)&debg[0])), ++ (int)(*((s16 *)&debg[1])), ++ (int)(*((s16 *)&debg[2])), ++ (int)(*((s16 *)&debg[3])), ++ (int)(*((s16 *)&debg[4])), ++ (int)(*((s16 *)&debg[5]))); ++ } ++#endif ++ ++ iio_push_to_buffers_with_timestamp( ++ indio_dev, &temp_data[0], ++ first_sample_timestamp_ns + ++ (fifo_frame_time_ns * (s64)j)); ++ } ++ ++bmi323_irq_done: ++ mutex_unlock(&indio_data->bmi323.mutex); ++ ++ /* ++ * Tell the core we are done with this trigger and ready for the ++ * next one. ++ */ ++ iio_trigger_notify_done(indio_dev->trig); ++ ++ return IRQ_HANDLED; ++} ++ ++int bmi323_set_trigger_state(struct iio_trigger *trig, bool state) ++{ ++ return 0; ++} ++ ++/* ++// The following is meant to be used in a IRQ-enabled hardware ++static const struct iio_trigger_ops time_trigger_ops = { ++ .set_trigger_state = &bmi323_set_trigger_state, ++ //.reenable = NULL, ++ .validate_device = &iio_trigger_validate_own_device, ++}; ++*/ ++ ++/* ++ * A very basic scan mask: everything can work in conjunction with everything else so no need to worry about ++ * managing conbinations of mutually exclusive data sources... ++ */ ++static const unsigned long bmi323_accel_scan_masks[] = { ++ BIT(BMI323_ACCEL_AXIS_X) | BIT(BMI323_ACCEL_AXIS_Y) | ++ BIT(BMI323_ACCEL_AXIS_Z) | BIT(BMI323_GYRO_AXIS_X) | ++ BIT(BMI323_GYRO_AXIS_Y) | ++ BIT(BMI323_GYRO_AXIS_Z) /*| BIT(BMI323_TEMP)*/, ++ 0 ++}; ++ ++int bmi323_iio_init(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ struct irq_data *irq_desc = NULL; ++ ++ if (data->bmi323.i2c_client != NULL) { ++ data->bmi323.dev = &data->bmi323.i2c_client->dev; ++ } else if (data->bmi323.spi_client != NULL) { ++ data->bmi323.dev = &data->bmi323.spi_client->dev; ++ } else { ++ return -ENODEV; ++ } ++ ++ int ret = 0; ++ ++ /* change to 8 for a default 200Hz sampling rate */ ++ const int gyr_odr_conf_idx = 7; ++ const int acc_odr_conf_idx = 7; ++ ++ mutex_init(&data->bmi323.mutex); ++ ++ data->bmi323.acc_odr_time_ns = ++ bmi323_accel_odr_map[acc_odr_conf_idx].time_ns; ++ data->bmi323.gyr_odr_time_ns = ++ bmi323_gyro_odr_map[gyr_odr_conf_idx].time_ns; ++ ++ // FIFO enabled for gyro, accel and temp. Overwrite older samples. ++ data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0F00U); ++ //data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0E00U); ++ //data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0600U); // working ++ ++ // now set the (default) normal mode... ++ // normal mode: 0x4000 ++ // no averaging: 0x0000 ++ data->bmi323.acc_conf_reg_value = cpu_to_le16( ++ 0x4000 | ((u16)BMC150_BMI323_ACCEL_RANGE_2_VAL << (u16)4U) | ++ ((u16)bmi323_accel_odr_map[acc_odr_conf_idx].hw_val)); ++ ++ // now set the (default) normal mode... ++ // normal mode: 0x4000 ++ // no averaging: 0x0000 ++ // filtering to ODR/2: 0x0000 ++ data->bmi323.gyr_conf_reg_value = cpu_to_le16( ++ 0x4000 | ((u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4U) | ++ ((u16)bmi323_gyro_odr_map[gyr_odr_conf_idx].hw_val)); ++ ++ // the datasheet states that FIFO buffer MUST be enabled before enabling any sensor ++ ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_FIFO_CONF_REG, ++ data->bmi323.fifo_conf_reg_value); ++ if (ret != 0) { ++ return -1; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ return -1; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ return -2; ++ } ++ ++ indio_dev->channels = bmi323_channels; ++ indio_dev->num_channels = ARRAY_SIZE(bmi323_channels); ++ indio_dev->name = "bmi323"; ++ indio_dev->available_scan_masks = bmi323_accel_scan_masks; ++ indio_dev->modes = INDIO_DIRECT_MODE; ++ indio_dev->info = &bmi323_accel_info; ++ indio_dev->label = "bmi323-accel_base"; ++ ++ if (data->bmi323.irq > 0) { ++ dev_info(data->bmi323.dev, "IRQ pin reported as connected: %d", ++ data->bmi323.irq); ++ ++ irq_desc = irq_get_irq_data(data->bmi323.irq); ++ if (!irq_desc) { ++ dev_err(data->bmi323.dev, ++ "Could not find IRQ %d. ignoring it.\n", ++ data->bmi323.irq); ++ goto bmi323_iio_init_missing_irq_pin; ++ } ++ ++ //data->bmi323.trig[0] = devm_iio_trigger_alloc(data->bmi323.dev, "trig-fifo_full-%s-%d", indio_dev->name, iio_device_id(indio_dev)); ++ //if (data->bmi323.trig[0] == NULL) { ++ // ret = -ENOMEM; ++ // goto bmi323_iio_init_err_trigger_unregister; ++ //} ++ // ++ //data->bmi323.trig[0]->ops = &time_trigger_ops; ++ //iio_trigger_set_drvdata(data->bmi323.trig[0], indio_dev); ++ //ret = devm_iio_trigger_register(data->bmi323.dev, data->bmi323.trig[0]); ++ //if (ret) { ++ // dev_err(data->bmi323.dev, "iio trigger register failed\n"); ++ // goto bmi323_iio_init_err_trigger_unregister; ++ //} ++ ++ /* ++ * register triggers BEFORE buffer setup so that they are cleared ++ * on emergence exit by bmi323_iio_init_err_trigger_unregister. ++ * ++ * This is just a placeholder until I can get my hands on a bmi323 ++ * device that has the IRQ pin actually connected to the CPU. ++ */ ++ ++ /* here resume operation with the module part common to irq and non-irq enabled code. */ ++ goto bmi323_iio_init_common_irq_and_not_irq; ++ } ++ ++bmi323_iio_init_missing_irq_pin: ++ dev_info( ++ data->bmi323.dev, ++ "IRQ pin NOT connected (irq=%d). Will continue normally without triggers.", ++ data->bmi323.irq); ++ ++bmi323_iio_init_common_irq_and_not_irq: ++ ++ /* Once orientation matrix is implemented switch this to iio_triggered_buffer_setup_ext. */ ++ ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, ++ iio_bmi323_trigger_h, ++ &bmi323_buffer_ops); ++ if (ret < 0) { ++ dev_err(data->bmi323.dev, ++ "Failed: iio triggered buffer setup: %d\n", ret); ++ goto bmi323_iio_init_err_trigger_unregister; ++ } ++ ++ ret = pm_runtime_set_active(data->bmi323.dev); ++ if (ret) { ++ dev_err(data->bmi323.dev, ++ "bmi323 unable to initialize runtime PD: pm_runtime_set_active returned %d\n", ++ ret); ++ goto bmi323_iio_init_err_buffer_cleanup; ++ } ++ ++ pm_runtime_enable(data->bmi323.dev); ++ pm_runtime_set_autosuspend_delay(data->bmi323.dev, ++ BMC150_BMI323_AUTO_SUSPEND_DELAY_MS); ++ pm_runtime_use_autosuspend(data->bmi323.dev); ++ ++ ret = iio_device_register(indio_dev); ++ if (ret < 0) { ++ dev_err(data->bmi323.dev, ++ "bmi323 unable to register iio device: %d\n", ret); ++ goto bmi323_iio_init_err_pm_cleanup; ++ } ++ ++ return 0; ++ ++bmi323_iio_init_err_pm_cleanup: ++ pm_runtime_dont_use_autosuspend(data->bmi323.dev); ++ pm_runtime_disable(data->bmi323.dev); ++bmi323_iio_init_err_buffer_cleanup: ++ iio_triggered_buffer_cleanup(indio_dev); ++bmi323_iio_init_err_trigger_unregister: ++ /* ++ * unregister triggers if they have been setup already. ++ * iio_trigger_unregister shall be used in that regard. ++ * ++ * This is just a placeholder until I can get my hands on a bmi323 ++ * device that has the IRQ pin actually connected to the CPU. ++ */ ++ //if (data->bmi323.trig[0] != NULL) { ++ // iio_trigger_unregister(data->bmi323.trig[0]); ++ //} ++ ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_iio_init, IIO_BMC150); ++ ++void bmi323_iio_deinit(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ struct device *dev = bmi323_get_managed_device(&data->bmi323); ++ ++ iio_device_unregister(indio_dev); ++ ++ pm_runtime_disable(dev); ++ pm_runtime_set_suspended(dev); ++ pm_runtime_put_noidle(dev); ++ ++ iio_triggered_buffer_cleanup(indio_dev); ++ ++ //iio_device_free(indio_dev); // this isn't done in the bmg160 driver nor in other drivers so I guess I shouldn't do it too ++ ++ mutex_unlock(&data->bmi323.mutex); ++ bmi323_chip_rst(&data->bmi323); ++ mutex_unlock(&data->bmi323.mutex); ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_iio_deinit, IIO_BMC150); ++ + #ifdef CONFIG_PM_SLEEP + static int bmc150_accel_suspend(struct device *dev) + { + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + ++ if (data->dev_type == BMI323) { ++ int ret; ++ ++ //dev_warn(dev, "bmi323 suspending driver..."); ++ ++ // here push the register GYRO & ACCEL configuration and issue a reset so that chip goes to sleep mode (the default one after a reset) ++ mutex_unlock(&data->bmi323.mutex); ++ ++ ret = bmi323_chip_rst(&data->bmi323); ++ mutex_unlock(&data->bmi323.mutex); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 error in suspend on bmi323_chip_rst: %d\n", ++ ret); ++ data->bmi323.flags |= BMI323_FLAGS_RESET_FAILED; ++ return -EAGAIN; ++ } ++ ++ return 0; ++ } ++ + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + mutex_unlock(&data->mutex); +@@ -1844,6 +4005,63 @@ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + ++ if (data->dev_type == BMI323) { ++ int ret; ++ ++ //dev_warn(dev, "bmi323 resuming driver..."); ++ ++ // here pop the register GYRO & ACCEL configuration and issue a reset so that chip goes to sleep mode (the default one after a reset) ++ mutex_lock(&data->bmi323.mutex); ++ ++ // this was done already in runtime_sleep function. ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ ret = bmi323_chip_rst(&data->bmi323); ++ if (ret == 0) { ++ data->bmi323.flags &= ++ ~BMI323_FLAGS_RESET_FAILED; ++ } else { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_FIFO_CONF_REG, ++ data->bmi323.fifo_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ ++bmi323_bmc150_accel_resume_terminate: ++ mutex_unlock(&data->bmi323.mutex); ++ if (ret != 0) { ++ return -EAGAIN; ++ } ++ ++ /* ++ * datasheet says "Start-up time": suspend to high performance mode is tipically 30ms, ++ * however when setting this to 32 or even higher the first reading from the gyro (unlike accel part) ++ * is actually the (wrong) default value 0x8000 so it is better to sleep a bit longer ++ * to prevent issues and give time to the sensor to pick up first readings... ++ */ ++ msleep_interruptible(64); ++ ++ return 0; ++ } ++ + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + bmc150_accel_fifo_set_mode(data); +@@ -1863,6 +4081,25 @@ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + ++ if (data->dev_type == BMI323) { ++ //dev_warn(dev, "bmi323 suspending runtime..."); ++ ++ /* ++ * Every operation requiring this function have the mutex locked already: ++ * with mutex_lock(&data->bmi323.mutex); ++ */ ++ ret = bmi323_chip_rst(&data->bmi323); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 error in runtime_suspend on bmi323_chip_rst: %d\n", ++ ret); ++ data->bmi323.flags |= BMI323_FLAGS_RESET_FAILED; ++ return -EAGAIN; ++ } ++ ++ return 0; ++ } ++ + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + if (ret < 0) + return -EAGAIN; +@@ -1877,6 +4114,70 @@ + int ret; + int sleep_val; + ++ if (data->dev_type == BMI323) { ++ //dev_warn(dev, "bmi323 resuming runtime..."); ++ ++ /* ++ * Every operation requiring this function have the mutex locked already: ++ * with mutex_lock(&data->bmi323.mutex); ++ */ ++ ++ // recover from a bad state if it was left that way on reuntime_suspend ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ ret = bmi323_chip_rst(&data->bmi323); ++ if (ret == 0) { ++ data->bmi323.flags &= ++ ~BMI323_FLAGS_RESET_FAILED; ++ } else { ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_FIFO_CONF_REG, ++ data->bmi323.fifo_conf_reg_value); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 writing to GYR_CONF register failed"); ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 writing to GYR_CONF register failed"); ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 writing to ACC_CONF register failed"); ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ ++bmi323_bmc150_accel_runtime_resume_terminate: ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 bmc150_accel_runtime_resume -EAGAIN"); ++ return -EAGAIN; ++ } ++ ++ /* ++ * datasheet says "Start-up time": suspend to high performance mode is tipically 30ms, ++ * however when setting this to 32 or even higher the first reading from the gyro (unlike accel part) ++ * is actually the (wrong) default value 0x8000 so it is better to sleep a bit longer ++ * to prevent issues and give time to the sensor to pick up first readings... ++ */ ++ msleep_interruptible(64); ++ ++ return 0; ++ } ++ + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + if (ret < 0) + return ret; +diff '--color=auto' -uraN a/drivers/iio/accel/bmc150-accel-i2c.c b/drivers/iio/accel/bmc150-accel-i2c.c +--- a/drivers/iio/accel/bmc150-accel-i2c.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/iio/accel/bmc150-accel-i2c.c 2023-11-04 16:36:53.565649926 +0300 +@@ -173,15 +173,102 @@ + + static int bmc150_accel_probe(struct i2c_client *client) + { ++ int ret; ++ u8 chip_id_first[4] = { 0x00, 0x00, 0x00, 0x00 }; ++ enum bmc150_device_type dev_type = BMC150; + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name = NULL; + enum bmc150_type type = BOSCH_UNKNOWN; ++ ++ /* reads 4 bytes (2 dummy + 2 good) from the i2c CHIP_ID device register */ ++ ret = i2c_smbus_read_i2c_block_data(client, 0x00, 4, &chip_id_first[0]); ++ if (ret != 4) { ++ dev_info( ++ &client->dev, ++ "error checking if the bmc150 is in fact a bmi323: i2c_smbus_read_i2c_block_data = %d: reg = 0x%02x.\n\tIt probably is a bmc150 as correctly reported by the ACPI entry.", ++ (int)ret, 0x00); ++ goto bmi150_old_probe; ++ } ++ ++ // at this point we have enough data to know what chip we are handling ++ dev_type = (chip_id_first[2] == 0x43) ? BMI323 : dev_type; ++ ++ if (dev_type == BMI323) { ++ dev_warn( ++ &client->dev, ++ "bmc323: what the ACPI table reported as a bmc150 is in fact a bmc323\n"); ++ ++ struct iio_dev *indio_dev = devm_iio_device_alloc( ++ &client->dev, sizeof(struct bmc150_accel_data)); ++ if (!indio_dev) { ++ dev_err(&client->dev, ++ "bmc323 init process failed: out of memory\n"); ++ ++ return -ENOMEM; ++ } ++ ++ dev_set_drvdata(&client->dev, indio_dev); ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ data->dev_type = dev_type; ++ ++ struct bmi323_private_data *bmi323_data = &data->bmi323; ++ bmi323_data->i2c_client = client; ++ bmi323_data->spi_client = NULL; ++ bmi323_data->irq = client->irq; ++ ++ /* ++ * VDD is the analog and digital domain voltage supply ++ * VDDIO is the digital I/O voltage supply ++ */ ++ bmi323_data->regulators[0].supply = "vdd"; ++ bmi323_data->regulators[1].supply = "vddio"; ++ ret = devm_regulator_bulk_get( ++ &client->dev, ARRAY_SIZE(bmi323_data->regulators), ++ bmi323_data->regulators); ++ if (ret) { ++ return dev_err_probe(&client->dev, ret, ++ "failed to get regulators\n"); ++ } ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(bmi323_data->regulators), ++ bmi323_data->regulators); ++ if (ret) { ++ iio_device_free(indio_dev); ++ ++ dev_err(&client->dev, ++ "failed to enable regulators: %d\n", ret); ++ return ret; ++ } ++ ++ ret = bmi323_chip_rst(bmi323_data); ++ if (ret != 0) { ++ dev_err(&client->dev, ++ "bmc323: error issuing the chip reset: %d\n", ++ ret); ++ return ret; ++ } ++ ++ dev_info( ++ &client->dev, ++ "bmc323: chip reset success: starting the iio subsystem binding\n"); ++ ++ ret = bmi323_iio_init(indio_dev); ++ if (ret != 0) { ++ return ret; ++ } ++ ++ return 0; ++ } ++ ++bmi150_old_probe: ++ dev_info(&client->dev, ++ "executing the normal procedure for a bmc150..."); ++ + bool block_supported = + i2c_check_functionality(client->adapter, I2C_FUNC_I2C) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK); +- int ret; + + regmap = devm_regmap_init_i2c(client, &bmc150_regmap_conf); + if (IS_ERR(regmap)) { +@@ -198,7 +285,7 @@ + type, name, block_supported); + if (ret) + return ret; +- ++ + /* + * The !id check avoids recursion when probe() gets called + * for the second client. +@@ -211,6 +298,15 @@ + + static void bmc150_accel_remove(struct i2c_client *client) + { ++ struct iio_dev *indio_dev = dev_get_drvdata(&client->dev); ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ if (data->dev_type == BMI323) { ++ bmi323_iio_deinit(indio_dev); ++ ++ return; ++ } ++ + bmc150_acpi_dual_accel_remove(client); + + bmc150_accel_core_remove(&client->dev); +diff '--color=auto' -uraN a/drivers/iio/accel/bmc150-accel.h b/drivers/iio/accel/bmc150-accel.h +--- a/drivers/iio/accel/bmc150-accel.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/iio/accel/bmc150-accel.h 2023-11-04 16:36:53.565649926 +0300 +@@ -8,6 +8,14 @@ + #include + #include + ++/* ++ * the bmi323 needs raw access to spi and i2c: I cannot use regmap ++ * as this device expects i2c writes to be 2 bytes, ++ * spi reads to be 3 bytes and i2c reads to be 4 bytes. ++ */ ++#include ++#include ++ + struct regmap; + struct i2c_client; + struct bmc150_accel_chip_info; +@@ -34,6 +42,11 @@ + atomic_t users; + }; + ++enum bmc150_device_type { ++ BMC150, ++ BMI323, ++}; ++ + struct bmc150_accel_trigger { + struct bmc150_accel_data *data; + struct iio_trigger *indio_trig; +@@ -55,6 +68,25 @@ + BMC150_ACCEL_TRIGGERS, + }; + ++#define BMI323_FLAGS_RESET_FAILED 0x00000001U ++ ++struct bmi323_private_data { ++ struct regulator_bulk_data regulators[2]; ++ struct i2c_client *i2c_client; ++ struct spi_device *spi_client; ++ struct device *dev; /* pointer at i2c_client->dev or spi_client->dev */ ++ struct mutex mutex; ++ int irq; ++ u32 flags; ++ u16 acc_conf_reg_value; ++ u16 gyr_conf_reg_value; ++ u16 fifo_conf_reg_value; ++ struct iio_trigger *trig[1]; ++ s64 fifo_frame_time_diff_ns; ++ s64 acc_odr_time_ns; ++ s64 gyr_odr_time_ns; ++}; ++ + struct bmc150_accel_data { + struct regmap *regmap; + struct regulator_bulk_data regulators[2]; +@@ -83,7 +115,67 @@ + void (*resume_callback)(struct device *dev); + struct delayed_work resume_work; + struct iio_mount_matrix orientation; +-}; ++ enum bmc150_device_type dev_type; ++ struct bmi323_private_data bmi323; ++ }; ++ ++/** ++ * This function performs a write of a u16 little-endian (regardless of CPU architecture) integer ++ * to a device register. Returns 0 on success or an error code otherwise. ++ * ++ * PRE: in_value holds the data to be sent to the sensor, in little endian format even on big endian ++ * architectures. ++ * ++ * NOTE: bmi323->dev can be NULL (not yet initialized) when this function is called ++ * therefore it is not needed and is not used inside the function ++ * ++ * WARNING: this function does not lock any mutex and synchronization MUST be performed by the caller ++ */ ++int bmi323_write_u16(struct bmi323_private_data *bmi323, u8 in_reg, u16 in_value); ++ ++/** ++ * This function performs a read of "good" values from the bmi323 discarding what ++ * in the datasheet is described as "dummy data": additional useles bytes. ++ * ++ * PRE: bmi323 has been partially initialized: i2c_device and spi_devices MUST be set to either ++ * the correct value or NULL ++ * ++ * NOTE: bmi323->dev can be NULL (not yet initialized) when this function is called ++ * therefore it is not needed and is not used inside the function ++ * ++ * POST: on success out_value is written with data from the sensor, as it came out, so the ++ * content is little-endian even on big endian architectures ++ * ++ * WARNING: this function does not lock any mutex and synchronization MUST be performed by the caller ++ */ ++int bmi323_read_u16(struct bmi323_private_data *bmi323, u8 in_reg, u16* out_value); ++ ++int bmi323_chip_check(struct bmi323_private_data *bmi323); ++ ++/** ++ * Reset the chip in a known state that is ready to accept commands, but is not configured therefore after calling this function ++ * it is required to load a new configuration to start data acquisition. ++ * ++ * PRE: bmi323 has been fully identified and partially initialized ++ * ++ * NOTE: after issuing a reset the the chip will be in what it is called "suspended mode" and the feature angine is ++ * ready to be set. This mode has everything disabled and consumes aroud 15uA. ++ * ++ * When removing the driver or suspend has been requested it's best to reset the chip so that power consumption ++ * will be the lowest possible. ++ */ ++int bmi323_chip_rst(struct bmi323_private_data *bmi323); ++ ++/** ++ * This function MUST be called in probe and is responsible for registering the userspace sysfs. ++ * ++ * The indio_dev MUST have been allocated but not registered. This function will perform userspace registration. ++ * ++ * @param indio_dev the industrual io device already allocated but not yet registered ++ */ ++int bmi323_iio_init(struct iio_dev *indio_dev); ++ ++void bmi323_iio_deinit(struct iio_dev *indio_dev); + + int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, + enum bmc150_type type, const char *name, +diff '--color=auto' -uraN a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c +--- a/drivers/input/joystick/xpad.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/input/joystick/xpad.c 2023-11-04 16:36:53.575650105 +0300 +@@ -265,6 +265,7 @@ + { 0x0f0d, 0x0067, "HORIPAD ONE", 0, XTYPE_XBOXONE }, + { 0x0f0d, 0x0078, "Hori Real Arcade Pro V Kai Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x0f0d, 0x00c5, "Hori Fighting Commander ONE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, ++ { 0x0f0d, 0x00dc, "HORIPAD FPS for Nintendo Switch", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0f30, 0x010b, "Philips Recoil", 0, XTYPE_XBOX }, + { 0x0f30, 0x0202, "Joytech Advanced Controller", 0, XTYPE_XBOX }, + { 0x0f30, 0x8888, "BigBen XBMiniPad Controller", 0, XTYPE_XBOX }, +@@ -367,6 +368,7 @@ + { 0x31e3, 0x1300, "Wooting 60HE (AVR)", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1310, "Wooting 60HE (ARM)", 0, XTYPE_XBOX360 }, + { 0x3285, 0x0607, "Nacon GC-100", 0, XTYPE_XBOX360 }, ++ { 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 }, + { 0x3767, 0x0101, "Fanatec Speedster 3 Forceshock Wheel", 0, XTYPE_XBOX }, + { 0xffff, 0xffff, "Chinese-made Xbox Controller", 0, XTYPE_XBOX }, + { 0x0000, 0x0000, "Generic X-Box pad", 0, XTYPE_UNKNOWN } +@@ -503,6 +505,8 @@ + XPAD_XBOX360_VENDOR(0x2f24), /* GameSir controllers */ + XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */ + XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */ ++ XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */ ++ XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */ + { } + }; + +@@ -1724,6 +1728,27 @@ + return error; + } + } ++ if (xpad->xtype == XTYPE_XBOX360) { ++ /* ++ * Some third-party controllers Xbox 360-style controllers ++ * require this message to finish initialization. ++ */ ++ u8 dummy[20]; ++ ++ error = usb_control_msg_recv(xpad->udev, 0, ++ /* bRequest */ 0x01, ++ /* bmRequestType */ ++ USB_TYPE_VENDOR | USB_DIR_IN | ++ USB_RECIP_INTERFACE, ++ /* wValue */ 0x100, ++ /* wIndex */ 0x00, ++ dummy, sizeof(dummy), ++ 25, GFP_KERNEL); ++ if (error) ++ dev_warn(&xpad->dev->dev, ++ "unable to receive magic message: %d\n", ++ error); ++ } + + return 0; + } +diff '--color=auto' -uraN a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +--- a/drivers/input/misc/soc_button_array.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/input/misc/soc_button_array.c 2023-11-04 16:36:53.548982960 +0300 +@@ -537,8 +537,8 @@ + * 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 +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -549,31 +549,14 @@ + 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); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; +- +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); +- +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +diff '--color=auto' -uraN a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +--- a/drivers/iommu/intel/iommu.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/iommu/intel/iommu.c 2023-11-04 16:36:53.552316353 +0300 +@@ -37,6 +37,14 @@ + #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_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) ++#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) +@@ -287,12 +295,16 @@ + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_gfx = 1; ++static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 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_IPU 8 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + +@@ -2548,6 +2560,12 @@ + + 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; + } + + return 0; +@@ -2855,6 +2873,12 @@ + 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(); + + ret = si_domain_init(hw_pass_through); +@@ -4755,6 +4779,30 @@ + dmar_map_gfx = 0; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ 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; ++} ++ + /* 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); +@@ -4790,6 +4838,12 @@ + 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 a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +--- a/drivers/iommu/intel/irq_remapping.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/iommu/intel/irq_remapping.c 2023-11-04 16:36:53.545649567 +0300 +@@ -387,6 +387,22 @@ + 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 + * device is the case of a PCIe-to-PCI bridge, where the alias is for +diff '--color=auto' -uraN a/drivers/leds/Kconfig b/drivers/leds/Kconfig +--- a/drivers/leds/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/leds/Kconfig 2023-11-04 16:36:53.572316712 +0300 +@@ -864,6 +864,18 @@ + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +@@ -882,6 +894,13 @@ + 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 '--color=auto' -uraN a/drivers/leds/Makefile b/drivers/leds/Makefile +--- a/drivers/leds/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/leds/Makefile 2023-11-04 16:36:53.572316712 +0300 +@@ -77,12 +77,14 @@ + 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 + 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 ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + 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 a/drivers/leds/leds-steamdeck.c b/drivers/leds/leds-steamdeck.c +--- a/drivers/leds/leds-steamdeck.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/leds/leds-steamdeck.c 2023-11-04 16:36:53.572316712 +0300 +@@ -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"); +diff '--color=auto' -uraN a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +--- a/drivers/leds/leds-tps68470.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/leds/leds-tps68470.c 2023-11-04 16:36:53.558983140 +0300 +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +diff '--color=auto' -uraN a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig +--- a/drivers/leds/trigger/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/leds/trigger/Kconfig 2023-11-04 16:35:57.827983725 +0300 +@@ -155,4 +155,13 @@ + + When build as a module this driver will be called ledtrig-tty. + ++config LEDS_TRIGGER_BLKDEV ++ tristate "LED Trigger for block devices" ++ depends on BLOCK ++ help ++ The blkdev LED trigger allows LEDs to be controlled by block device ++ activity (reads and writes). ++ ++ See Documentation/leds/ledtrig-blkdev.rst. ++ + endif # LEDS_TRIGGERS +diff '--color=auto' -uraN a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile +--- a/drivers/leds/trigger/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/leds/trigger/Makefile 2023-11-04 16:35:57.827983725 +0300 +@@ -16,3 +16,4 @@ + obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o + obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o + obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o ++obj-$(CONFIG_LEDS_TRIGGER_BLKDEV) += ledtrig-blkdev.o +diff '--color=auto' -uraN a/drivers/leds/trigger/ledtrig-blkdev.c b/drivers/leds/trigger/ledtrig-blkdev.c +--- a/drivers/leds/trigger/ledtrig-blkdev.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/leds/trigger/ledtrig-blkdev.c 2023-11-04 16:35:57.827983725 +0300 +@@ -0,0 +1,1218 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++ ++/* ++ * Block device LED trigger ++ * ++ * Copyright 2021-2023 Ian Pilcher ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++/** ++ * DOC: Overview ++ * ++ * The ``blkdev`` LED trigger works by periodically checking the activity ++ * counters of block devices that have been linked to one or more LEDs and ++ * blinking those LED(s) if the correct type of activity has occurred. The ++ * periodic check is scheduled with the Linux kernel's deferred work facility. ++ * ++ * Trigger-specific data about block devices and LEDs is stored in two data ++ * structures --- &struct blkdev_trig_bdev (a "BTB") and &struct blkdev_trig_led ++ * (a "BTL"). Each structure contains a &struct xarray that holds links to any ++ * linked devices of the other type. I.e. &blkdev_trig_bdev.linked_btls ++ * contains links to all BTLs whose LEDs have been linked to the BTB's block ++ * device, and &blkdev_trig_led.linked_btbs contains links to all BTBs whose ++ * block devices have been linked to the BTL's LED. Thus, a block device can ++ * be linked to more than one LED, and an LED can be linked to more than one ++ * block device. ++ */ ++ ++/* Default, minimum & maximum blink duration (milliseconds) */ ++#define BLKDEV_TRIG_BLINK_DEF 75 ++#define BLKDEV_TRIG_BLINK_MIN 10 ++#define BLKDEV_TRIG_BLINK_MAX 86400000 /* 24 hours */ ++ ++/* Default, minimum & maximum activity check interval (milliseconds) */ ++#define BLKDEV_TRIG_CHECK_DEF 100 ++#define BLKDEV_TRIG_CHECK_MIN 25 ++#define BLKDEV_TRIG_CHECK_MAX 86400000 /* 24 hours */ ++ ++/* ++ * If blkdev_trig_check() can't lock the mutex, how long to wait before trying ++ * again (milliseconds) ++ */ ++#define BLKDEV_TRIG_CHECK_RETRY 5 ++ ++/** ++ * struct blkdev_trig_bdev - Trigger-specific data about a block device. ++ * @last_checked: Time (in jiffies) at which the trigger last checked this ++ * block device for activity. ++ * @last_activity: Time (in jiffies) at which the trigger last detected ++ * activity of each type. ++ * @ios: Activity counter values for each type, corresponding to ++ * the timestamps in &last_activity. ++ * @index: &xarray index, so the BTB can be included in one or more ++ * &blkdev_trig_led.linked_btbs. ++ * @bdev: The block device. ++ * @linked_btls: The BTLs that represent the LEDs linked to the BTB's ++ * block device. ++ * ++ * Every block device linked to at least one LED gets a "BTB." A BTB is created ++ * when a block device that is not currently linked to any LEDs is linked to an ++ * LED. ++ * ++ * A BTB is freed when one of the following occurs: ++ * ++ * * The number of LEDs linked to the block device becomes zero, because it has ++ * been unlinked from its last LED using the trigger's &sysfs interface. ++ * ++ * * The number of LEDs linked to the block device becomes zero, because the ++ * last LED to which it was linked has been disassociated from the trigger ++ * (which happens automatically if the LED device is removed from the system). ++ * ++ * * The BTB's block device is removed from the system. To accomodate this ++ * scenario, BTB's are created as device resources, so that the release ++ * function will be called by the driver core when the device is removed. ++ */ ++struct blkdev_trig_bdev { ++ unsigned long last_checked; ++ unsigned long last_activity[NR_STAT_GROUPS]; ++ unsigned long ios[NR_STAT_GROUPS]; ++ unsigned long index; ++ struct block_device *bdev; ++ struct xarray linked_btls; ++}; ++ ++/** ++ * struct blkdev_trig_led - Trigger-specific data about an LED. ++ * @last_checked: Time (in jiffies) at which the trigger last checked the ++ * the block devices linked to this LED for activity. ++ * @index: &xarray index, so the BTL can be included in one or more ++ * &blkdev_trig_bdev.linked_btls. ++ * @mode: Bitmask for types of block device activity that will ++ * cause this LED to blink --- reads, writes, discards, ++ * etc. ++ * @led: The LED device. ++ * @blink_msec: Duration of a blink (milliseconds). ++ * @check_jiffies: Frequency with which block devices linked to this LED ++ * should be checked for activity (jiffies). ++ * @linked_btbs: The BTBs that represent the block devices linked to the ++ * BTL's LED. ++ * @all_btls_node: The BTL's node in the module's list of all BTLs. ++ * ++ * Every LED associated with the block device trigger gets a "BTL." A BTL is ++ * created when the trigger is "activated" on an LED (usually by writing ++ * ``blkdev`` to the LED's &sysfs &trigger attribute). A BTL is freed wnen its ++ * LED is disassociated from the trigger, either through the trigger's &sysfs ++ * interface or because the LED device is removed from the system. ++ */ ++struct blkdev_trig_led { ++ unsigned long last_checked; ++ unsigned long index; ++ unsigned long mode; /* must be ulong for atomic bit ops */ ++ struct led_classdev *led; ++ unsigned int blink_msec; ++ unsigned int check_jiffies; ++ struct xarray linked_btbs; ++ struct hlist_node all_btls_node; ++}; ++ ++/* Protects everything except atomic LED attributes */ ++static DEFINE_MUTEX(blkdev_trig_mutex); ++ ++/* BTB device resource release function */ ++static void blkdev_trig_btb_release(struct device *dev, void *res); ++ ++/* Index for next BTB or BTL */ ++static unsigned long blkdev_trig_next_index; ++ ++/* All LEDs associated with the trigger */ ++static HLIST_HEAD(blkdev_trig_all_btls); ++ ++/* Delayed work to periodically check for activity & blink LEDs */ ++static void blkdev_trig_check(struct work_struct *work); ++static DECLARE_DELAYED_WORK(blkdev_trig_work, blkdev_trig_check); ++ ++/* When is the delayed work scheduled to run next (jiffies) */ ++static unsigned long blkdev_trig_next_check; ++ ++/* Total number of BTB-to-BTL links */ ++static unsigned int blkdev_trig_link_count; ++ ++/* Empty sysfs attribute list for next 2 declarations */ ++static struct attribute *blkdev_trig_attrs_empty[] = { NULL }; ++ ++/* linked_leds sysfs directory for block devs linked to 1 or more LEDs */ ++static const struct attribute_group blkdev_trig_linked_leds = { ++ .name = "linked_leds", ++ .attrs = blkdev_trig_attrs_empty, ++}; ++ ++/* linked_devices sysfs directory for each LED associated with the trigger */ ++static const struct attribute_group blkdev_trig_linked_devs = { ++ .name = "linked_devices", ++ .attrs = blkdev_trig_attrs_empty, ++}; ++ ++ ++/* ++ * ++ * Delayed work to check for activity & blink LEDs ++ * ++ */ ++ ++/** ++ * blkdev_trig_blink() - Blink an LED, if the correct type of activity has ++ * occurred on the block device. ++ * @btl: The BTL that represents the LED ++ * @btb: The BTB that represents the block device ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ * Return: &true if the LED is blinked, &false if not. ++ */ ++static bool blkdev_trig_blink(const struct blkdev_trig_led *btl, ++ const struct blkdev_trig_bdev *btb) ++{ ++ unsigned long mode, mask, delay_on, delay_off; ++ enum stat_group i; ++ ++ mode = READ_ONCE(btl->mode); ++ ++ for (i = STAT_READ, mask = 1; i <= STAT_FLUSH; ++i, mask <<= 1) { ++ ++ if (!(mode & mask)) ++ continue; ++ ++ if (time_before_eq(btb->last_activity[i], btl->last_checked)) ++ continue; ++ ++ delay_on = READ_ONCE(btl->blink_msec); ++ delay_off = 1; /* 0 leaves LED turned on */ ++ ++ led_blink_set_oneshot(btl->led, &delay_on, &delay_off, 0); ++ return true; ++ } ++ ++ return false; ++} ++ ++/** ++ * blkdev_trig_update_btb() - Update a BTB's activity counters and timestamps. ++ * @btb: The BTB ++ * @now: Timestamp (in jiffies) ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_update_btb(struct blkdev_trig_bdev *btb, ++ unsigned long now) ++{ ++ unsigned long new_ios; ++ enum stat_group i; ++ ++ for (i = STAT_READ; i <= STAT_FLUSH; ++i) { ++ ++ new_ios = part_stat_read(btb->bdev, ios[i]); ++ ++ if (new_ios != btb->ios[i]) { ++ btb->ios[i] = new_ios; ++ btb->last_activity[i] = now; ++ } ++ } ++ ++ btb->last_checked = now; ++} ++ ++/** ++ * blkdev_trig_check() - Check linked devices for activity and blink LEDs. ++ * @work: Delayed work (&blkdev_trig_work) ++ * ++ * Context: Process context. Takes and releases &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_check(struct work_struct *work) ++{ ++ struct blkdev_trig_led *btl; ++ struct blkdev_trig_bdev *btb; ++ unsigned long index, delay, now, led_check, led_delay; ++ bool blinked; ++ ++ if (!mutex_trylock(&blkdev_trig_mutex)) { ++ delay = msecs_to_jiffies(BLKDEV_TRIG_CHECK_RETRY); ++ goto exit_reschedule; ++ } ++ ++ now = jiffies; ++ delay = ULONG_MAX; ++ ++ hlist_for_each_entry (btl, &blkdev_trig_all_btls, all_btls_node) { ++ ++ led_check = btl->last_checked + btl->check_jiffies; ++ ++ if (time_before_eq(led_check, now)) { ++ ++ blinked = false; ++ ++ xa_for_each (&btl->linked_btbs, index, btb) { ++ ++ if (btb->last_checked != now) ++ blkdev_trig_update_btb(btb, now); ++ if (!blinked) ++ blinked = blkdev_trig_blink(btl, btb); ++ } ++ ++ btl->last_checked = now; ++ led_delay = btl->check_jiffies; ++ ++ } else { ++ led_delay = led_check - now; ++ } ++ ++ if (led_delay < delay) ++ delay = led_delay; ++ } ++ ++ mutex_unlock(&blkdev_trig_mutex); ++ ++exit_reschedule: ++ WARN_ON_ONCE(delay == ULONG_MAX); ++ WARN_ON_ONCE(!schedule_delayed_work(&blkdev_trig_work, delay)); ++} ++ ++/** ++ * blkdev_trig_sched_led() - Set the schedule of the delayed work when a new ++ * LED is added to the schedule. ++ * @btl: The BTL that represents the LED ++ * ++ * Called when the number of block devices to which an LED is linked becomes ++ * non-zero. ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_sched_led(const struct blkdev_trig_led *btl) ++{ ++ unsigned long delay = READ_ONCE(btl->check_jiffies); ++ unsigned long check_by = jiffies + delay; ++ ++ /* ++ * If no other LED-to-block device links exist, simply schedule the ++ * delayed work according to this LED's check_interval attribute ++ * (check_jiffies). ++ */ ++ if (blkdev_trig_link_count == 0) { ++ WARN_ON(!schedule_delayed_work(&blkdev_trig_work, delay)); ++ blkdev_trig_next_check = check_by; ++ return; ++ } ++ ++ /* ++ * If the next check is already scheduled to occur soon enough to ++ * accomodate this LED's check_interval, the schedule doesn't need ++ * to be changed. ++ */ ++ if (time_after_eq(check_by, blkdev_trig_next_check)) ++ return; ++ ++ /* ++ * Modify the schedule, so that the delayed work runs soon enough for ++ * this LED. ++ */ ++ WARN_ON(!mod_delayed_work(system_wq, &blkdev_trig_work, delay)); ++ blkdev_trig_next_check = check_by; ++} ++ ++ ++/* ++ * ++ * Linking and unlinking LEDs and block devices ++ * ++ */ ++ ++/** ++ * blkdev_trig_link() - Link a block device to an LED. ++ * @btl: The BTL that represents the LED ++ * @btb: The BTB that represents the block device ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ * Return: &0 on success, negative &errno on error. ++ */ ++static int blkdev_trig_link(struct blkdev_trig_led *btl, ++ struct blkdev_trig_bdev *btb) ++{ ++ bool led_first_link; ++ int err; ++ ++ led_first_link = xa_empty(&btl->linked_btbs); ++ ++ err = xa_insert(&btb->linked_btls, btl->index, btl, GFP_KERNEL); ++ if (err) ++ return err; ++ ++ err = xa_insert(&btl->linked_btbs, btb->index, btb, GFP_KERNEL); ++ if (err) ++ goto error_erase_btl; ++ ++ /* Create /sys/class/block//linked_leds/ symlink */ ++ err = sysfs_add_link_to_group(bdev_kobj(btb->bdev), ++ blkdev_trig_linked_leds.name, ++ &btl->led->dev->kobj, btl->led->name); ++ if (err) ++ goto error_erase_btb; ++ ++ /* Create /sys/class/leds//linked_devices/ symlink */ ++ err = sysfs_add_link_to_group(&btl->led->dev->kobj, ++ blkdev_trig_linked_devs.name, ++ bdev_kobj(btb->bdev), ++ dev_name(&btb->bdev->bd_device)); ++ if (err) ++ goto error_remove_symlink; ++ ++ /* ++ * If this is the first block device linked to this LED, the delayed ++ * work schedule may need to be changed. ++ */ ++ if (led_first_link) ++ blkdev_trig_sched_led(btl); ++ ++ ++blkdev_trig_link_count; ++ ++ return 0; ++ ++error_remove_symlink: ++ sysfs_remove_link_from_group(bdev_kobj(btb->bdev), ++ blkdev_trig_linked_leds.name, ++ btl->led->name); ++error_erase_btb: ++ xa_erase(&btl->linked_btbs, btb->index); ++error_erase_btl: ++ xa_erase(&btb->linked_btls, btl->index); ++ return err; ++} ++ ++/** ++ * blkdev_trig_put_btb() - Remove and free a BTB, if it is no longer needed. ++ * @btb: The BTB ++ * ++ * Does nothing if the BTB (block device) is still linked to at least one LED. ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_put_btb(struct blkdev_trig_bdev *btb) ++{ ++ struct block_device *bdev = btb->bdev; ++ int err; ++ ++ if (xa_empty(&btb->linked_btls)) { ++ ++ sysfs_remove_group(bdev_kobj(bdev), &blkdev_trig_linked_leds); ++ err = devres_destroy(&bdev->bd_device, blkdev_trig_btb_release, ++ NULL, NULL); ++ WARN_ON(err); ++ } ++} ++ ++/** ++ * _blkdev_trig_unlink_always() - Perform the unconditionally required steps of ++ * unlinking a block device from an LED. ++ * @btl: The BTL that represents the LED ++ * @btb: The BTB that represents the block device ++ * ++ * When a block device is unlinked from an LED, certain steps must be performed ++ * only if the block device is **not** being released. This function performs ++ * those steps that are **always** required, whether or not the block device is ++ * being released. ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ */ ++static void _blkdev_trig_unlink_always(struct blkdev_trig_led *btl, ++ struct blkdev_trig_bdev *btb) ++{ ++ --blkdev_trig_link_count; ++ ++ if (blkdev_trig_link_count == 0) ++ WARN_ON(!cancel_delayed_work_sync(&blkdev_trig_work)); ++ ++ xa_erase(&btb->linked_btls, btl->index); ++ xa_erase(&btl->linked_btbs, btb->index); ++ ++ /* Remove /sys/class/leds//linked_devices/ symlink */ ++ sysfs_remove_link_from_group(&btl->led->dev->kobj, ++ blkdev_trig_linked_devs.name, ++ dev_name(&btb->bdev->bd_device)); ++} ++ ++/** ++ * blkdev_trig_unlink_norelease() - Unlink an LED from a block device that is ++ * **not** being released. ++ * @btl: The BTL that represents the LED. ++ * @btb: The BTB that represents the block device. ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_unlink_norelease(struct blkdev_trig_led *btl, ++ struct blkdev_trig_bdev *btb) ++{ ++ _blkdev_trig_unlink_always(btl, btb); ++ ++ /* Remove /sys/class/block//linked_leds/ symlink */ ++ sysfs_remove_link_from_group(bdev_kobj(btb->bdev), ++ blkdev_trig_linked_leds.name, ++ btl->led->name); ++ ++ blkdev_trig_put_btb(btb); ++} ++ ++/** ++ * blkdev_trig_unlink_release() - Unlink an LED from a block device that is ++ * being released. ++ * @btl: The BTL that represents the LED ++ * @btb: The BTB that represents the block device ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_unlink_release(struct blkdev_trig_led *btl, ++ struct blkdev_trig_bdev *btb) ++{ ++ _blkdev_trig_unlink_always(btl, btb); ++ ++ /* ++ * If the BTB is being released, the driver core has already removed the ++ * device's attribute groups, and the BTB will be freed automatically, ++ * so there's nothing else to do. ++ */ ++} ++ ++ ++/* ++ * ++ * BTB creation ++ * ++ */ ++ ++/** ++ * blkdev_trig_btb_release() - BTB device resource release function. ++ * @dev: The block device ++ * @res: The BTB ++ * ++ * Called by the driver core when a block device with a BTB is removed. ++ * ++ * Context: Process context. Takes and releases &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_btb_release(struct device *dev, void *res) ++{ ++ struct blkdev_trig_bdev *btb = res; ++ struct blkdev_trig_led *btl; ++ unsigned long index; ++ ++ mutex_lock(&blkdev_trig_mutex); ++ ++ xa_for_each (&btb->linked_btls, index, btl) ++ blkdev_trig_unlink_release(btl, btb); ++ ++ mutex_unlock(&blkdev_trig_mutex); ++} ++ ++/** ++ * blkdev_trig_get_bdev() - Get a block device by path. ++ * @path: The value written to an LED's &link_dev_by_path or ++ * &unlink_dev_by_path attribute, which should be the path to a ++ * special file that represents a block device ++ * @len: The number of characters in &path (not including its ++ * terminating null) ++ * ++ * The caller must call blkdev_put() when finished with the device. ++ * ++ * Context: Process context. ++ * Return: The block device, or an error pointer. ++ */ ++static struct block_device *blkdev_trig_get_bdev(const char *path, size_t len) ++{ ++ struct block_device *bdev; ++ char *buf; ++ ++ buf = kmemdup(path, len + 1, GFP_KERNEL); /* +1 to include null */ ++ if (buf == NULL) ++ return ERR_PTR(-ENOMEM); ++ ++ bdev = blkdev_get_by_path(strim(buf), 0, NULL, NULL); ++ kfree(buf); ++ return bdev; ++} ++ ++/** ++ * blkdev_trig_get_btb() - Find or create the BTB for a block device. ++ * @path: The value written to an LED's &link_dev_by_path attribute, ++ * which should be the path to a special file that represents a ++ * block device ++ * @len: The number of characters in &path ++ * ++ * If a new BTB is created, because the block device was not previously linked ++ * to any LEDs, the block device's &linked_leds &sysfs directory is created. ++ * ++ * Context: Process context. Caller must hold &blkdev_trig_mutex. ++ * Return: Pointer to the BTB, error pointer on error. ++ */ ++static struct blkdev_trig_bdev *blkdev_trig_get_btb(const char *path, ++ size_t len) ++{ ++ struct block_device *bdev; ++ struct blkdev_trig_bdev *btb; ++ int err; ++ ++ bdev = blkdev_trig_get_bdev(path, len); ++ if (IS_ERR(bdev)) ++ return ERR_CAST(bdev); ++ ++ btb = devres_find(&bdev->bd_device, blkdev_trig_btb_release, ++ NULL, NULL); ++ if (btb != NULL) { ++ err = 0; ++ goto exit_put_bdev; ++ } ++ ++ if (blkdev_trig_next_index == ULONG_MAX) { ++ err = -EOVERFLOW; ++ goto exit_put_bdev; ++ } ++ ++ btb = devres_alloc(blkdev_trig_btb_release, sizeof(*btb), GFP_KERNEL); ++ if (btb == NULL) { ++ err = -ENOMEM; ++ goto exit_put_bdev; ++ } ++ ++ err = sysfs_create_group(bdev_kobj(bdev), &blkdev_trig_linked_leds); ++ if (err) ++ goto exit_free_btb; ++ ++ btb->index = blkdev_trig_next_index++; ++ btb->bdev = bdev; ++ xa_init(&btb->linked_btls); ++ ++ /* Populate BTB activity counters */ ++ blkdev_trig_update_btb(btb, jiffies); ++ ++ devres_add(&bdev->bd_device, btb); ++ ++exit_free_btb: ++ if (err) ++ devres_free(btb); ++exit_put_bdev: ++ blkdev_put(bdev, NULL); ++ return err ? ERR_PTR(err) : btb; ++} ++ ++ ++/* ++ * ++ * Activating and deactivating the trigger on an LED ++ * ++ */ ++ ++/** ++ * blkdev_trig_activate() - Called by the LEDs subsystem when an LED is ++ * associated with the trigger. ++ * @led: The LED ++ * ++ * Context: Process context. Takes and releases &blkdev_trig_mutex. ++ * Return: &0 on success, negative &errno on error. ++ */ ++static int blkdev_trig_activate(struct led_classdev *led) ++{ ++ struct blkdev_trig_led *btl; ++ int err; ++ ++ btl = kzalloc(sizeof(*btl), GFP_KERNEL); ++ if (btl == NULL) ++ return -ENOMEM; ++ ++ err = mutex_lock_interruptible(&blkdev_trig_mutex); ++ if (err) ++ goto exit_free; ++ ++ if (blkdev_trig_next_index == ULONG_MAX) { ++ err = -EOVERFLOW; ++ goto exit_unlock; ++ } ++ ++ btl->index = blkdev_trig_next_index++; ++ btl->last_checked = jiffies; ++ btl->mode = -1; /* set all bits */ ++ btl->led = led; ++ btl->blink_msec = BLKDEV_TRIG_BLINK_DEF; ++ btl->check_jiffies = msecs_to_jiffies(BLKDEV_TRIG_CHECK_DEF); ++ xa_init(&btl->linked_btbs); ++ ++ hlist_add_head(&btl->all_btls_node, &blkdev_trig_all_btls); ++ led_set_trigger_data(led, btl); ++ ++exit_unlock: ++ mutex_unlock(&blkdev_trig_mutex); ++exit_free: ++ if (err) ++ kfree(btl); ++ return err; ++} ++ ++/** ++ * blkdev_trig_deactivate() - Called by the the LEDs subsystem when an LED is ++ * disassociated from the trigger. ++ * @led: The LED ++ * ++ * The LEDs subsystem also calls this function when an LED associated with the ++ * trigger is removed or when the trigger is unregistered (if the module is ++ * unloaded). ++ * ++ * Context: Process context. Takes and releases &blkdev_trig_mutex. ++ */ ++static void blkdev_trig_deactivate(struct led_classdev *led) ++{ ++ struct blkdev_trig_led *btl = led_get_trigger_data(led); ++ struct blkdev_trig_bdev *btb; ++ unsigned long index; ++ ++ mutex_lock(&blkdev_trig_mutex); ++ ++ xa_for_each (&btl->linked_btbs, index, btb) ++ blkdev_trig_unlink_norelease(btl, btb); ++ ++ hlist_del(&btl->all_btls_node); ++ kfree(btl); ++ ++ mutex_unlock(&blkdev_trig_mutex); ++} ++ ++ ++/* ++ * ++ * Link-related attribute store functions ++ * ++ */ ++ ++/** ++ * link_dev_by_path_store() - &link_dev_by_path device attribute store function. ++ * @dev: The LED device ++ * @attr: The &link_dev_by_path attribute (&dev_attr_link_dev_by_path) ++ * @buf: The value written to the attribute, which should be the path to ++ * a special file that represents a block device to be linked to ++ * the LED (e.g. ``/dev/sda``) ++ * @count: The number of characters in &buf ++ * ++ * Context: Process context. Takes and releases &blkdev_trig_mutex. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t link_dev_by_path_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct blkdev_trig_led *btl = led_trigger_get_drvdata(dev); ++ struct blkdev_trig_bdev *btb; ++ int err; ++ ++ err = mutex_lock_interruptible(&blkdev_trig_mutex); ++ if (err) ++ return err; ++ ++ btb = blkdev_trig_get_btb(buf, count); ++ if (IS_ERR(btb)) { ++ err = PTR_ERR(btb); ++ goto exit_unlock; ++ } ++ ++ if (xa_load(&btb->linked_btls, btl->index) != NULL) { ++ err = -EEXIST; ++ goto exit_put_btb; ++ } ++ ++ err = blkdev_trig_link(btl, btb); ++ ++exit_put_btb: ++ if (err) ++ blkdev_trig_put_btb(btb); ++exit_unlock: ++ mutex_unlock(&blkdev_trig_mutex); ++ return err ? : count; ++} ++ ++/** ++ * unlink_dev_by_path_store() - &unlink_dev_by_path device attribute store ++ * function. ++ * @dev: The LED device ++ * @attr: The &unlink_dev_by_path attribute (&dev_attr_unlink_dev_by_path) ++ * @buf: The value written to the attribute, which should be the path to ++ * a special file that represents a block device to be unlinked ++ * from the LED (e.g. ``/dev/sda``) ++ * @count: The number of characters in &buf ++ * ++ * Context: Process context. Takes and releases &blkdev_trig_mutex. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t unlink_dev_by_path_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct blkdev_trig_led *btl = led_trigger_get_drvdata(dev); ++ struct block_device *bdev; ++ struct blkdev_trig_bdev *btb; ++ int err; ++ ++ bdev = blkdev_trig_get_bdev(buf, count); ++ if (IS_ERR(bdev)) ++ return PTR_ERR(bdev); ++ ++ err = mutex_lock_interruptible(&blkdev_trig_mutex); ++ if (err) ++ goto exit_put_bdev; ++ ++ btb = devres_find(&bdev->bd_device, blkdev_trig_btb_release, ++ NULL, NULL); ++ if (btb == NULL) { ++ err = -EUNATCH; /* bdev isn't linked to any LED */ ++ goto exit_unlock; ++ } ++ ++ if (xa_load(&btb->linked_btls, btl->index) == NULL) { ++ err = -EUNATCH; /* bdev isn't linked to this LED */ ++ goto exit_unlock; ++ } ++ ++ blkdev_trig_unlink_norelease(btl, btb); ++ ++exit_unlock: ++ mutex_unlock(&blkdev_trig_mutex); ++exit_put_bdev: ++ blkdev_put(bdev, NULL); ++ return err ? : count; ++} ++ ++/** ++ * unlink_dev_by_name_store() - &unlink_dev_by_name device attribute store ++ * function. ++ * @dev: The LED device ++ * @attr: The &unlink_dev_by_name attribute (&dev_attr_unlink_dev_by_name) ++ * @buf: The value written to the attribute, which should be the kernel ++ * name of a block device to be unlinked from the LED (e.g. ++ * ``sda``) ++ * @count: The number of characters in &buf ++ * ++ * Context: Process context. Takes and releases &blkdev_trig_mutex. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t unlink_dev_by_name_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct blkdev_trig_led *btl = led_trigger_get_drvdata(dev); ++ struct blkdev_trig_bdev *btb; ++ unsigned long index; ++ int err; ++ ++ err = mutex_lock_interruptible(&blkdev_trig_mutex); ++ if (err) ++ return err; ++ ++ err = -EUNATCH; ++ ++ xa_for_each (&btl->linked_btbs, index, btb) { ++ ++ if (sysfs_streq(dev_name(&btb->bdev->bd_device), buf)) { ++ blkdev_trig_unlink_norelease(btl, btb); ++ err = 0; ++ break; ++ } ++ } ++ ++ mutex_unlock(&blkdev_trig_mutex); ++ return err ? : count; ++} ++ ++ ++/* ++ * ++ * Atomic attribute show & store functions ++ * ++ */ ++ ++/** ++ * blink_time_show() - &blink_time device attribute show function. ++ * @dev: The LED device ++ * @attr: The &blink_time attribute (&dev_attr_blink_time) ++ * @buf: Output buffer ++ * ++ * Writes the value of &blkdev_trig_led.blink_msec to &buf. ++ * ++ * Context: Process context. ++ * Return: The number of characters written to &buf. ++ */ ++static ssize_t blink_time_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ const struct blkdev_trig_led *btl = led_trigger_get_drvdata(dev); ++ ++ return sysfs_emit(buf, "%u\n", READ_ONCE(btl->blink_msec)); ++} ++ ++/** ++ * blink_time_store() - &blink_time device attribute store function. ++ * @dev: The LED device ++ * @attr: The &blink_time attribute (&dev_attr_blink_time) ++ * @buf: The new value (as written to the &sysfs attribute) ++ * @count: The number of characters in &buf ++ * ++ * Sets &blkdev_trig_led.blink_msec to the value in &buf. ++ * ++ * Context: Process context. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t blink_time_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct blkdev_trig_led *btl = led_trigger_get_drvdata(dev); ++ unsigned int value; ++ int err; ++ ++ err = kstrtouint(buf, 0, &value); ++ if (err) ++ return err; ++ ++ if (value < BLKDEV_TRIG_BLINK_MIN || value > BLKDEV_TRIG_BLINK_MAX) ++ return -ERANGE; ++ ++ WRITE_ONCE(btl->blink_msec, value); ++ return count; ++} ++ ++/** ++ * check_interval_show() - &check_interval device attribute show function. ++ * @dev: The LED device ++ * @attr: The &check_interval attribute (&dev_attr_check_interval) ++ * @buf: Output buffer ++ * ++ * Writes the value of &blkdev_trig_led.check_jiffies (converted to ++ * milliseconds) to &buf. ++ * ++ * Context: Process context. ++ * Return: The number of characters written to &buf. ++ */ ++static ssize_t check_interval_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct blkdev_trig_led *btl = led_trigger_get_drvdata(dev); ++ ++ return sysfs_emit(buf, "%u\n", ++ jiffies_to_msecs(READ_ONCE(btl->check_jiffies))); ++} ++ ++/** ++ * check_interval_store() - &check_interval device attribute store function ++ * @dev: The LED device ++ * @attr: The &check_interval attribute (&dev_attr_check_interval) ++ * @buf: The new value (as written to the &sysfs attribute) ++ * @count: The number of characters in &buf ++ * ++ * Sets &blkdev_trig_led.check_jiffies to the value in &buf (after converting ++ * from milliseconds). ++ * ++ * Context: Process context. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t check_interval_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct blkdev_trig_led *led = led_trigger_get_drvdata(dev); ++ unsigned int value; ++ int err; ++ ++ err = kstrtouint(buf, 0, &value); ++ if (err) ++ return err; ++ ++ if (value < BLKDEV_TRIG_CHECK_MIN || value > BLKDEV_TRIG_CHECK_MAX) ++ return -ERANGE; ++ ++ WRITE_ONCE(led->check_jiffies, msecs_to_jiffies(value)); ++ ++ return count; ++} ++ ++/** ++ * blkdev_trig_mode_show() - Helper for boolean attribute show functions. ++ * @led: The LED ++ * @buf: Output buffer ++ * @bit: Which bit to show ++ * ++ * Context: Process context. ++ * Return: The number of characters written to &buf. ++ */ ++static int blkdev_trig_mode_show(const struct blkdev_trig_led *led, char *buf, ++ enum stat_group bit) ++{ ++ return sysfs_emit(buf, ++ READ_ONCE(led->mode) & (1 << bit) ? "Y\n" : "N\n"); ++} ++ ++/** ++ * blkdev_trig_mode_store() - Helper for boolean attribute store functions. ++ * @led: The LED ++ * @buf: The new value (as written to the &sysfs attribute) ++ * @count: The number of characters in &buf ++ * @bit: Which bit to set ++ * ++ * Context: Process context. ++ * Return: &count on success, negative &errno on error. ++ */ ++static int blkdev_trig_mode_store(struct blkdev_trig_led *led, ++ const char *buf, size_t count, ++ enum stat_group bit) ++{ ++ bool set; ++ int err; ++ ++ err = kstrtobool(buf, &set); ++ if (err) ++ return err; ++ ++ if (set) ++ set_bit(bit, &led->mode); ++ else ++ clear_bit(bit, &led->mode); ++ ++ return count; ++} ++ ++/** ++ * blink_on_read_show() - &blink_on_read device attribute show function. ++ * @dev: The LED device ++ * @attr: The &blink_on_read attribute (&dev_attr_blink_on_read) ++ * @buf: Output buffer ++ * ++ * Writes ``Y`` or ``N`` to &buf, depending on whether the &STAT_READ bit in ++ * &blkdev_trig_led.mode is set or cleared. ++ * ++ * Context: Process context. ++ * Return: The number of characters written to &buf. ++ */ ++static ssize_t blink_on_read_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return blkdev_trig_mode_show(led_trigger_get_drvdata(dev), ++ buf, STAT_READ); ++} ++ ++/** ++ * blink_on_read_store() - &blink_on_read device attribute store function. ++ * @dev: The LED device ++ * @attr: The &blink_on_read attribute (&dev_attr_blink_on_read) ++ * @buf: The new value (as written to the &sysfs attribute) ++ * @count: The number of characters in &buf ++ * ++ * Sets the &STAT_READ bit in &blkdev_trig_led.mode to the value in &buf ++ * (interpretted as a boolean). ++ * ++ * Context: Process context. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t blink_on_read_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return blkdev_trig_mode_store(led_trigger_get_drvdata(dev), ++ buf, count, STAT_READ); ++} ++ ++/** ++ * blink_on_write_show() - &blink_on_write device attribute show function. ++ * @dev: The LED device ++ * @attr: The &blink_on_write attribute (&dev_attr_blink_on_write) ++ * @buf: Output buffer ++ * ++ * Writes ``Y`` or ``N`` to &buf, depending on whether the &STAT_WRITE bit in ++ * in &blkdev_trig_led.mode is set or cleared. ++ * ++ * Context: Process context. ++ * Return: The number of characters written to &buf. ++ */ ++static ssize_t blink_on_write_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return blkdev_trig_mode_show(led_trigger_get_drvdata(dev), ++ buf, STAT_WRITE); ++} ++ ++/** ++ * blink_on_write_store() - &blink_on_write device attribute store function. ++ * @dev: The LED device ++ * @attr: The &blink_on_write attribute (&dev_attr_blink_on_write) ++ * @buf: The new value (as written to the &sysfs attribute) ++ * @count: The number of characters in &buf ++ * ++ * Sets the &STAT_WRITE bit in &blkdev_trig_led.mode to the value in &buf ++ * (interpretted as a boolean). ++ * ++ * Context: Process context. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t blink_on_write_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return blkdev_trig_mode_store(led_trigger_get_drvdata(dev), ++ buf, count, STAT_WRITE); ++} ++ ++/** ++ * blink_on_flush_show() - &blink_on_flush device attribute show function. ++ * @dev: The LED device ++ * @attr: The &blink_on_flush attribute (&dev_attr_blink_on_flush) ++ * @buf: Output buffer ++ * ++ * Writes ``Y`` or ``N`` to &buf, depending whether the &STAT_FLUSH bit in ++ * &blkdev_trig_led.mode is set or cleared. ++ * ++ * Context: Process context. ++ * Return: The number of characters written to &buf. ++ */ ++static ssize_t blink_on_flush_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return blkdev_trig_mode_show(led_trigger_get_drvdata(dev), ++ buf, STAT_FLUSH); ++} ++ ++/** ++ * blink_on_flush_store() - &blink_on_flush device attribute store function. ++ * @dev: The LED device ++ * @attr: The &blink_on_flush attribute (&dev_attr_blink_on_flush) ++ * @buf: The new value (as written to the &sysfs attribute) ++ * @count: The number of characters in &buf ++ * ++ * Sets the &STAT_FLUSH bit in &blkdev_trig_led.mode to the value in &buf ++ * (interpretted as a boolean). ++ * ++ * Context: Process context. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t blink_on_flush_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return blkdev_trig_mode_store(led_trigger_get_drvdata(dev), ++ buf, count, STAT_FLUSH); ++} ++ ++/** ++ * blink_on_discard_show() - &blink_on_discard device attribute show function. ++ * @dev: The LED device ++ * @attr: The &blink_on_discard attribute (&dev_attr_blink_on_discard) ++ * @buf: Output buffer ++ * ++ * Writes ``Y`` or ``N`` to &buf, depending on whether the &STAT_DISCARD bit in ++ * &blkdev_trig_led.mode is set or cleared. ++ * ++ * Context: Process context. ++ * Return: The number of characters written to &buf. ++ */ ++static ssize_t blink_on_discard_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return blkdev_trig_mode_show(led_trigger_get_drvdata(dev), ++ buf, STAT_DISCARD); ++} ++ ++/** ++ * blink_on_discard_store() - &blink_on_discard device attribute store function. ++ * @dev: The LED device ++ * @attr: The &blink_on_discard attribute (&dev_attr_blink_on_discard) ++ * @buf: The new value (as written to the &sysfs attribute) ++ * @count: The number of characters in &buf ++ * ++ * Sets the &STAT_DISCARD bit in &blkdev_trig_led.mode to the value in &buf ++ * (interpretted as a boolean). ++ * ++ * Context: Process context. ++ * Return: &count on success, negative &errno on error. ++ */ ++static ssize_t blink_on_discard_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return blkdev_trig_mode_store(led_trigger_get_drvdata(dev), ++ buf, count, STAT_DISCARD); ++} ++ ++/* Device attributes */ ++static DEVICE_ATTR_WO(link_dev_by_path); ++static DEVICE_ATTR_WO(unlink_dev_by_path); ++static DEVICE_ATTR_WO(unlink_dev_by_name); ++static DEVICE_ATTR_RW(blink_time); ++static DEVICE_ATTR_RW(check_interval); ++static DEVICE_ATTR_RW(blink_on_read); ++static DEVICE_ATTR_RW(blink_on_write); ++static DEVICE_ATTR_RW(blink_on_flush); ++static DEVICE_ATTR_RW(blink_on_discard); ++ ++/* Device attributes in LED directory (/sys/class/leds//...) */ ++static struct attribute *blkdev_trig_attrs[] = { ++ &dev_attr_link_dev_by_path.attr, ++ &dev_attr_unlink_dev_by_path.attr, ++ &dev_attr_unlink_dev_by_name.attr, ++ &dev_attr_blink_time.attr, ++ &dev_attr_check_interval.attr, ++ &dev_attr_blink_on_read.attr, ++ &dev_attr_blink_on_write.attr, ++ &dev_attr_blink_on_flush.attr, ++ &dev_attr_blink_on_discard.attr, ++ NULL ++}; ++ ++/* Unnamed attribute group == no subdirectory */ ++static const struct attribute_group blkdev_trig_attr_group = { ++ .attrs = blkdev_trig_attrs, ++}; ++ ++/* Attribute groups for the trigger */ ++static const struct attribute_group *blkdev_trig_attr_groups[] = { ++ &blkdev_trig_attr_group, /* /sys/class/leds//... */ ++ &blkdev_trig_linked_devs, /* /sys/class/leds//linked_devices/ */ ++ NULL ++}; ++ ++/* Trigger registration data */ ++static struct led_trigger blkdev_trig_trigger = { ++ .name = "blkdev", ++ .activate = blkdev_trig_activate, ++ .deactivate = blkdev_trig_deactivate, ++ .groups = blkdev_trig_attr_groups, ++}; ++ ++/** ++ * blkdev_trig_init() - Block device LED trigger initialization. ++ * ++ * Registers the ``blkdev`` LED trigger. ++ * ++ * Return: &0 on success, negative &errno on failure. ++ */ ++static int __init blkdev_trig_init(void) ++{ ++ return led_trigger_register(&blkdev_trig_trigger); ++} ++module_init(blkdev_trig_init); ++ ++/** ++ * blkdev_trig_exit() - Block device LED trigger module exit. ++ * ++ * Unregisters the ``blkdev`` LED trigger. ++ */ ++static void __exit blkdev_trig_exit(void) ++{ ++ led_trigger_unregister(&blkdev_trig_trigger); ++} ++module_exit(blkdev_trig_exit); ++ ++MODULE_DESCRIPTION("Block device LED trigger"); ++MODULE_AUTHOR("Ian Pilcher "); ++MODULE_LICENSE("GPL v2"); +diff '--color=auto' -uraN a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c +--- a/drivers/md/dm-crypt.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/md/dm-crypt.c 2023-11-04 16:35:57.814650153 +0300 +@@ -3241,6 +3241,11 @@ + goto bad; + } + ++#ifdef CONFIG_CACHY ++ set_bit(DM_CRYPT_NO_READ_WORKQUEUE, &cc->flags); ++ set_bit(DM_CRYPT_NO_WRITE_WORKQUEUE, &cc->flags); ++#endif ++ + ret = crypt_ctr_cipher(ti, argv[0], argv[1]); + if (ret < 0) + goto bad; +diff '--color=auto' -uraN a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +--- a/drivers/media/i2c/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/media/i2c/Kconfig 2023-11-04 16:36:53.555649747 +0300 +@@ -855,6 +855,17 @@ + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + ++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 a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +--- a/drivers/media/i2c/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/media/i2c/Makefile 2023-11-04 16:36:53.555649747 +0300 +@@ -29,6 +29,7 @@ + obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o + obj-$(CONFIG_VIDEO_CX25840) += cx25840/ + obj-$(CONFIG_VIDEO_DW9714) += dw9714.o ++obj-$(CONFIG_VIDEO_DW9719) += dw9719.o + obj-$(CONFIG_VIDEO_DW9768) += dw9768.o + obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o + obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ +diff '--color=auto' -uraN a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +--- a/drivers/media/i2c/dw9719.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/media/i2c/dw9719.c 2023-11-04 16:36:53.555649747 +0300 +@@ -0,0 +1,425 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2012 Intel Corporation ++ ++/* ++ * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: ++ * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#define DW9719_MAX_FOCUS_POS 1023 ++#define DW9719_CTRL_STEPS 16 ++#define DW9719_CTRL_DELAY_US 1000 ++#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) ++ ++#define DW9719_INFO 0 ++#define DW9719_ID 0xF1 ++#define DW9719_CONTROL 2 ++#define DW9719_VCM_CURRENT 3 ++ ++#define DW9719_MODE 6 ++#define DW9719_VCM_FREQ 7 ++ ++#define DW9719_MODE_SAC3 0x40 ++#define DW9719_DEFAULT_VCM_FREQ 0x60 ++#define DW9719_ENABLE_RINGING 0x02 ++ ++#define NUM_REGULATORS 2 ++ ++#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) ++ ++struct dw9719_device { ++ struct device *dev; ++ struct i2c_client *client; ++ struct regulator_bulk_data regulators[NUM_REGULATORS]; ++ struct v4l2_subdev sd; ++ ++ struct dw9719_v4l2_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *focus; ++ } ctrls; ++}; ++ ++static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) ++{ ++ struct i2c_msg msg[2]; ++ u8 buf[2] = { reg }; ++ int ret; ++ ++ msg[0].addr = client->addr; ++ msg[0].flags = 0; ++ msg[0].len = 1; ++ msg[0].buf = buf; ++ ++ msg[1].addr = client->addr; ++ msg[1].flags = I2C_M_RD; ++ msg[1].len = 1; ++ msg[1].buf = &buf[1]; ++ *val = 0; ++ ++ ret = i2c_transfer(client->adapter, msg, 2); ++ if (ret < 0) ++ return ret; ++ ++ *val = buf[1]; ++ ++ return 0; ++} ++ ++static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) ++{ ++ struct i2c_msg msg; ++ int ret; ++ ++ u8 buf[2] = { reg, val }; ++ ++ msg.addr = client->addr; ++ msg.flags = 0; ++ msg.len = sizeof(buf); ++ msg.buf = buf; ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) ++{ ++ struct i2c_msg msg; ++ u8 buf[3] = { reg }; ++ int ret; ++ ++ put_unaligned_be16(val, buf + 1); ++ ++ msg.addr = client->addr; ++ msg.flags = 0; ++ msg.len = sizeof(buf); ++ msg.buf = buf; ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++static int dw9719_detect(struct dw9719_device *dw9719) ++{ ++ int ret; ++ u8 val; ++ ++ ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); ++ if (ret < 0) ++ return ret; ++ ++ if (val != DW9719_ID) { ++ dev_err(dw9719->dev, "Failed to detect correct id\n"); ++ ret = -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static int dw9719_power_down(struct dw9719_device *dw9719) ++{ ++ return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); ++} ++ ++static int dw9719_power_up(struct dw9719_device *dw9719) ++{ ++ int ret; ++ ++ ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); ++ if (ret) ++ return ret; ++ ++ /* Jiggle SCL pin to wake up device */ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); ++ ++ /* Need 100us to transit from SHUTDOWN to STANDBY*/ ++ usleep_range(100, 1000); ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, ++ DW9719_ENABLE_RINGING); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, ++ DW9719_DEFAULT_VCM_FREQ); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ return 0; ++ ++fail_powerdown: ++ dw9719_power_down(dw9719); ++ return ret; ++} ++ ++static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) ++{ ++ int ret; ++ ++ value = clamp(value, 0, DW9719_MAX_FOCUS_POS); ++ ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct dw9719_device *dw9719 = container_of(ctrl->handler, ++ struct dw9719_device, ++ ctrls.handler); ++ int ret; ++ ++ /* Only apply changes to the controls if the device is powered up */ ++ if (!pm_runtime_get_if_in_use(dw9719->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_FOCUS_ABSOLUTE: ++ ret = dw9719_t_focus_abs(dw9719, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ pm_runtime_put(dw9719->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { ++ .s_ctrl = dw9719_set_ctrl, ++}; ++ ++static int __maybe_unused dw9719_suspend(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct dw9719_device *dw9719 = to_dw9719_device(sd); ++ int ret; ++ int val; ++ ++ for (val = dw9719->ctrls.focus->val; val >= 0; ++ val -= DW9719_CTRL_STEPS) { ++ ret = dw9719_t_focus_abs(dw9719, val); ++ if (ret) ++ return ret; ++ ++ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); ++ } ++ ++ return dw9719_power_down(dw9719); ++} ++ ++static int __maybe_unused dw9719_resume(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct dw9719_device *dw9719 = to_dw9719_device(sd); ++ int current_focus = dw9719->ctrls.focus->val; ++ int ret; ++ int val; ++ ++ ret = dw9719_power_up(dw9719); ++ if (ret) ++ return ret; ++ ++ for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; ++ val += DW9719_CTRL_STEPS) { ++ ret = dw9719_t_focus_abs(dw9719, val); ++ if (ret) ++ goto err_power_down; ++ ++ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); ++ } ++ ++ return 0; ++ ++err_power_down: ++ dw9719_power_down(dw9719); ++ return ret; ++} ++ ++static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ return pm_runtime_resume_and_get(sd->dev); ++} ++ ++static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ pm_runtime_put(sd->dev); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { ++ .open = dw9719_open, ++ .close = dw9719_close, ++}; ++ ++static int dw9719_init_controls(struct dw9719_device *dw9719) ++{ ++ const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; ++ int ret; ++ ++ ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); ++ if (ret) ++ return ret; ++ ++ dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, ++ V4L2_CID_FOCUS_ABSOLUTE, 0, ++ DW9719_MAX_FOCUS_POS, 1, 0); ++ ++ if (dw9719->ctrls.handler.error) { ++ dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); ++ ret = dw9719->ctrls.handler.error; ++ goto err_free_handler; ++ } ++ ++ dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; ++ ++ return ret; ++ ++err_free_handler: ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ return ret; ++} ++ ++static const struct v4l2_subdev_ops dw9719_ops = { }; ++ ++static int dw9719_probe(struct i2c_client *client) ++{ ++ struct dw9719_device *dw9719; ++ int ret; ++ ++ dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); ++ if (!dw9719) ++ return -ENOMEM; ++ ++ dw9719->client = client; ++ dw9719->dev = &client->dev; ++ ++ dw9719->regulators[0].supply = "vdd"; ++ /* ++ * The DW9719 has only the 1 VDD voltage input, but some PMICs such as ++ * the TPS68470 PMIC have I2C passthrough capability, to disconnect the ++ * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) ++ * is off, because some sensors then short these pins to ground; ++ * and the DW9719 might sit behind this passthrough, this it needs to ++ * enable VSIO as that will also enable the I2C passthrough. ++ */ ++ dw9719->regulators[1].supply = "vsio"; ++ ++ ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, ++ dw9719->regulators); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, "getting regulators\n"); ++ ++ v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); ++ dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ dw9719->sd.internal_ops = &dw9719_internal_ops; ++ ++ ret = dw9719_init_controls(dw9719); ++ if (ret) ++ return ret; ++ ++ ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); ++ if (ret < 0) ++ goto err_free_ctrl_handler; ++ ++ dw9719->sd.entity.function = MEDIA_ENT_F_LENS; ++ ++ /* ++ * We need the driver to work in the event that pm runtime is disable in ++ * the kernel, so power up and verify the chip now. In the event that ++ * runtime pm is disabled this will leave the chip on, so that the lens ++ * will work. ++ */ ++ ++ ret = dw9719_power_up(dw9719); ++ if (ret) ++ goto err_cleanup_media; ++ ++ ret = dw9719_detect(dw9719); ++ if (ret) ++ goto err_powerdown; ++ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_get_noresume(&client->dev); ++ pm_runtime_enable(&client->dev); ++ ++ ret = v4l2_async_register_subdev(&dw9719->sd); ++ if (ret < 0) ++ goto err_pm_runtime; ++ ++ pm_runtime_set_autosuspend_delay(&client->dev, 1000); ++ pm_runtime_use_autosuspend(&client->dev); ++ pm_runtime_put_autosuspend(&client->dev); ++ ++ return ret; ++ ++err_pm_runtime: ++ pm_runtime_disable(&client->dev); ++ pm_runtime_put_noidle(&client->dev); ++err_powerdown: ++ dw9719_power_down(dw9719); ++err_cleanup_media: ++ media_entity_cleanup(&dw9719->sd.entity); ++err_free_ctrl_handler: ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ ++ return ret; ++} ++ ++static void dw9719_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, ++ sd); ++ ++ pm_runtime_disable(&client->dev); ++ v4l2_async_unregister_subdev(sd); ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ media_entity_cleanup(&dw9719->sd.entity); ++} ++ ++static const struct i2c_device_id dw9719_id_table[] = { ++ { "dw9719" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, dw9719_id_table); ++ ++static const struct dev_pm_ops dw9719_pm_ops = { ++ SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) ++}; ++ ++static struct i2c_driver dw9719_i2c_driver = { ++ .driver = { ++ .name = "dw9719", ++ .pm = &dw9719_pm_ops, ++ }, ++ .probe_new = dw9719_probe, ++ .remove = dw9719_remove, ++ .id_table = dw9719_id_table, ++}; ++module_i2c_driver(dw9719_i2c_driver); ++ ++MODULE_AUTHOR("Daniel Scally "); ++MODULE_DESCRIPTION("DW9719 VCM Driver"); ++MODULE_LICENSE("GPL"); +diff '--color=auto' -uraN a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +--- a/drivers/media/i2c/ov7251.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/media/i2c/ov7251.c 2023-11-04 16:36:53.555649747 +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 a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c 2023-11-04 16:36:53.552316353 +0300 +@@ -1386,7 +1386,10 @@ + { + struct cio2_device *cio2 = to_cio2_device(notifier); + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); ++ struct device *dev = &cio2->pci_dev->dev; + struct cio2_queue *q; ++ unsigned int pad; ++ int ret; + + if (cio2->queue[s_asd->csi2.port].sensor) + return -EBUSY; +@@ -1397,7 +1400,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 */ +@@ -1415,34 +1435,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_subdev *asd; +- struct cio2_queue *q; +- int ret; +- +- list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { +- 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 a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +--- a/drivers/media/v4l2-core/v4l2-async.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/media/v4l2-core/v4l2-async.c 2023-11-04 16:36:53.558983140 +0300 +@@ -760,6 +760,10 @@ + struct v4l2_async_notifier *notifier; + int ret; + ++ 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 +diff '--color=auto' -uraN a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +--- a/drivers/media/v4l2-core/v4l2-fwnode.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c 2023-11-04 16:36:53.558983140 +0300 +@@ -1314,10 +1314,6 @@ + + v4l2_async_nf_init(notifier); + +- 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 a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +--- a/drivers/mfd/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/mfd/Kconfig 2023-11-04 16:36:53.568983319 +0300 +@@ -2307,5 +2307,16 @@ + 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 '--color=auto' -uraN a/drivers/mfd/Makefile b/drivers/mfd/Makefile +--- a/drivers/mfd/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/mfd/Makefile 2023-11-04 16:36:53.572316712 +0300 +@@ -281,3 +281,5 @@ + 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 '--color=auto' -uraN a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c +--- a/drivers/mfd/steamdeck.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/mfd/steamdeck.c 2023-11-04 16:36:53.572316712 +0300 +@@ -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"); +diff '--color=auto' -uraN a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +--- a/drivers/misc/mei/hw-me-regs.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/misc/mei/hw-me-regs.h 2023-11-04 16:36:53.542316174 +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 a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +--- a/drivers/misc/mei/pci-me.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/misc/mei/pci-me.c 2023-11-04 16:36:53.542316174 +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 a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +--- a/drivers/net/wireless/ath/ath10k/core.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/net/wireless/ath/ath10k/core.c 2023-11-04 16:36:53.542316174 +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 a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c 2023-11-04 16:36:53.538982781 +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; + } + ++ /* 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 a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c 2023-11-04 16:36:53.538982781 +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 a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h 2023-11-04 16:36:53.538982781 +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 a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c +--- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c 2023-11-04 16:35:57.827983725 +0300 +@@ -99,7 +99,8 @@ + wiphy->n_iface_combinations = ARRAY_SIZE(if_comb); + } + wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP | +- WIPHY_FLAG_4ADDR_STATION); ++ WIPHY_FLAG_4ADDR_STATION | ++ WIPHY_FLAG_PS_ON_BY_DEFAULT); + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | +@@ -409,12 +410,6 @@ + dev->pm.idle_timeout = MT7921_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; +diff '--color=auto' -uraN a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c +--- a/drivers/nvme/host/pci.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/nvme/host/pci.c 2023-11-04 16:36:53.512315636 +0300 +@@ -5,6 +5,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -2537,6 +2538,7 @@ + + nvme_map_cmb(dev); + ++ pci_enable_pcie_error_reporting(pdev); + pci_save_state(pdev); + + result = nvme_pci_configure_admin_queue(dev); +@@ -2601,8 +2603,10 @@ + nvme_suspend_io_queues(dev); + nvme_suspend_queue(dev, 0); + pci_free_irq_vectors(pdev); +- if (pci_is_enabled(pdev)) ++ if (pci_is_enabled(pdev)) { ++ pci_disable_pcie_error_reporting(pdev); + pci_disable_device(pdev); ++ } + nvme_reap_pending_cqes(dev); + + nvme_cancel_tagset(&dev->ctrl); +diff '--color=auto' -uraN a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile +--- a/drivers/pci/controller/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/pci/controller/Makefile 2023-11-04 16:35:57.814650153 +0300 +@@ -1,4 +1,10 @@ + # SPDX-License-Identifier: GPL-2.0 ++ifdef CONFIG_X86_64 ++ifdef CONFIG_SATA_AHCI ++obj-y += intel-nvme-remap.o ++endif ++endif ++ + obj-$(CONFIG_PCIE_CADENCE) += cadence/ + obj-$(CONFIG_PCI_FTPCI100) += pci-ftpci100.o + obj-$(CONFIG_PCI_IXP4XX) += pci-ixp4xx.o +diff '--color=auto' -uraN a/drivers/pci/controller/intel-nvme-remap.c b/drivers/pci/controller/intel-nvme-remap.c +--- a/drivers/pci/controller/intel-nvme-remap.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/pci/controller/intel-nvme-remap.c 2023-11-04 16:35:57.814650153 +0300 +@@ -0,0 +1,462 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Intel remapped NVMe device support. ++ * ++ * Copyright (c) 2019 Endless Mobile, Inc. ++ * Author: Daniel Drake ++ * ++ * Some products ship by default with the SATA controller in "RAID" or ++ * "Intel RST Premium With Intel Optane System Acceleration" mode. Under this ++ * mode, which we refer to as "remapped NVMe" mode, any installed NVMe ++ * devices disappear from the PCI bus, and instead their I/O memory becomes ++ * available within the AHCI device BARs. ++ * ++ * This scheme is understood to be a way of avoiding usage of the standard ++ * Windows NVMe driver under that OS, instead mandating usage of Intel's ++ * driver instead, which has better power management, and presumably offers ++ * some RAID/disk-caching solutions too. ++ * ++ * Here in this driver, we support the remapped NVMe mode by claiming the ++ * AHCI device and creating a fake PCIe root port. On the new bus, the ++ * original AHCI device is exposed with only minor tweaks. Then, fake PCI ++ * devices corresponding to the remapped NVMe devices are created. The usual ++ * ahci and nvme drivers are then expected to bind to these devices and ++ * operate as normal. ++ * ++ * The PCI configuration space for the NVMe devices is completely ++ * unavailable, so we fake a minimal one and hope for the best. ++ * ++ * Interrupts are shared between the AHCI and NVMe devices. For simplicity, ++ * we only support the legacy interrupt here, although MSI support ++ * could potentially be added later. ++ */ ++ ++#define MODULE_NAME "intel-nvme-remap" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define AHCI_PCI_BAR_STANDARD 5 ++ ++struct nvme_remap_dev { ++ struct pci_dev *dev; /* AHCI device */ ++ struct pci_bus *bus; /* our fake PCI bus */ ++ struct pci_sysdata sysdata; ++ int irq_base; /* our fake interrupts */ ++ ++ /* ++ * When we detect an all-ones write to a BAR register, this flag ++ * is set, so that we return the BAR size on the next read (a ++ * standard PCI behaviour). ++ * This includes the assumption that an all-ones BAR write is ++ * immediately followed by a read of the same register. ++ */ ++ bool bar_sizing; ++ ++ /* ++ * Resources copied from the AHCI device, to be regarded as ++ * resources on our fake bus. ++ */ ++ struct resource ahci_resources[PCI_NUM_RESOURCES]; ++ ++ /* Resources corresponding to the NVMe devices. */ ++ struct resource remapped_dev_mem[AHCI_MAX_REMAP]; ++ ++ /* Number of remapped NVMe devices found. */ ++ int num_remapped_devices; ++}; ++ ++static inline struct nvme_remap_dev *nrdev_from_bus(struct pci_bus *bus) ++{ ++ return container_of(bus->sysdata, struct nvme_remap_dev, sysdata); ++} ++ ++ ++/******** PCI configuration space **********/ ++ ++/* ++ * Helper macros for tweaking returned contents of PCI configuration space. ++ * ++ * value contains len bytes of data read from reg. ++ * If fixup_reg is included in that range, fix up the contents of that ++ * register to fixed_value. ++ */ ++#define NR_FIX8(fixup_reg, fixed_value) do { \ ++ if (reg <= fixup_reg && fixup_reg < reg + len) \ ++ ((u8 *) value)[fixup_reg - reg] = (u8) (fixed_value); \ ++ } while (0) ++ ++#define NR_FIX16(fixup_reg, fixed_value) do { \ ++ NR_FIX8(fixup_reg, fixed_value); \ ++ NR_FIX8(fixup_reg + 1, fixed_value >> 8); \ ++ } while (0) ++ ++#define NR_FIX24(fixup_reg, fixed_value) do { \ ++ NR_FIX8(fixup_reg, fixed_value); \ ++ NR_FIX8(fixup_reg + 1, fixed_value >> 8); \ ++ NR_FIX8(fixup_reg + 2, fixed_value >> 16); \ ++ } while (0) ++ ++#define NR_FIX32(fixup_reg, fixed_value) do { \ ++ NR_FIX16(fixup_reg, (u16) fixed_value); \ ++ NR_FIX16(fixup_reg + 2, fixed_value >> 16); \ ++ } while (0) ++ ++/* ++ * Read PCI config space of the slot 0 (AHCI) device. ++ * We pass through the read request to the underlying device, but ++ * tweak the results in some cases. ++ */ ++static int nvme_remap_pci_read_slot0(struct pci_bus *bus, int reg, ++ int len, u32 *value) ++{ ++ struct nvme_remap_dev *nrdev = nrdev_from_bus(bus); ++ struct pci_bus *ahci_dev_bus = nrdev->dev->bus; ++ int ret; ++ ++ ret = ahci_dev_bus->ops->read(ahci_dev_bus, nrdev->dev->devfn, ++ reg, len, value); ++ if (ret) ++ return ret; ++ ++ /* ++ * Adjust the device class, to prevent this driver from attempting to ++ * additionally probe the device we're simulating here. ++ */ ++ NR_FIX24(PCI_CLASS_PROG, PCI_CLASS_STORAGE_SATA_AHCI); ++ ++ /* ++ * Unset interrupt pin, otherwise ACPI tries to find routing ++ * info for our virtual IRQ, fails, and complains. ++ */ ++ NR_FIX8(PCI_INTERRUPT_PIN, 0); ++ ++ /* ++ * Truncate the AHCI BAR to not include the region that covers the ++ * hidden devices. This will cause the ahci driver to successfully ++ * probe th new device (instead of handing it over to this driver). ++ */ ++ if (nrdev->bar_sizing) { ++ NR_FIX32(PCI_BASE_ADDRESS_5, ~(SZ_16K - 1)); ++ nrdev->bar_sizing = false; ++ } ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++ ++/* ++ * Read PCI config space of a remapped device. ++ * Since the original PCI config space is inaccessible, we provide a minimal, ++ * fake config space instead. ++ */ ++static int nvme_remap_pci_read_remapped(struct pci_bus *bus, unsigned int port, ++ int reg, int len, u32 *value) ++{ ++ struct nvme_remap_dev *nrdev = nrdev_from_bus(bus); ++ struct resource *remapped_mem; ++ ++ if (port > nrdev->num_remapped_devices) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ *value = 0; ++ remapped_mem = &nrdev->remapped_dev_mem[port - 1]; ++ ++ /* Set a Vendor ID, otherwise Linux assumes no device is present */ ++ NR_FIX16(PCI_VENDOR_ID, PCI_VENDOR_ID_INTEL); ++ ++ /* Always appear on & bus mastering */ ++ NR_FIX16(PCI_COMMAND, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); ++ ++ /* Set class so that nvme driver probes us */ ++ NR_FIX24(PCI_CLASS_PROG, PCI_CLASS_STORAGE_EXPRESS); ++ ++ if (nrdev->bar_sizing) { ++ NR_FIX32(PCI_BASE_ADDRESS_0, ++ ~(resource_size(remapped_mem) - 1)); ++ nrdev->bar_sizing = false; ++ } else { ++ resource_size_t mem_start = remapped_mem->start; ++ ++ mem_start |= PCI_BASE_ADDRESS_MEM_TYPE_64; ++ NR_FIX32(PCI_BASE_ADDRESS_0, mem_start); ++ mem_start >>= 32; ++ NR_FIX32(PCI_BASE_ADDRESS_1, mem_start); ++ } ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++ ++/* Read PCI configuration space. */ ++static int nvme_remap_pci_read(struct pci_bus *bus, unsigned int devfn, ++ int reg, int len, u32 *value) ++{ ++ if (PCI_SLOT(devfn) == 0) ++ return nvme_remap_pci_read_slot0(bus, reg, len, value); ++ else ++ return nvme_remap_pci_read_remapped(bus, PCI_SLOT(devfn), ++ reg, len, value); ++} ++ ++/* ++ * Write PCI config space of the slot 0 (AHCI) device. ++ * Apart from the special case of BAR sizing, we disable all writes. ++ * Otherwise, the ahci driver could make changes (e.g. unset PCI bus master) ++ * that would affect the operation of the NVMe devices. ++ */ ++static int nvme_remap_pci_write_slot0(struct pci_bus *bus, int reg, ++ int len, u32 value) ++{ ++ struct nvme_remap_dev *nrdev = nrdev_from_bus(bus); ++ struct pci_bus *ahci_dev_bus = nrdev->dev->bus; ++ ++ if (reg >= PCI_BASE_ADDRESS_0 && reg <= PCI_BASE_ADDRESS_5) { ++ /* ++ * Writing all-ones to a BAR means that the size of the ++ * memory region is being checked. Flag this so that we can ++ * reply with an appropriate size on the next read. ++ */ ++ if (value == ~0) ++ nrdev->bar_sizing = true; ++ ++ return ahci_dev_bus->ops->write(ahci_dev_bus, ++ nrdev->dev->devfn, ++ reg, len, value); ++ } ++ ++ return PCIBIOS_SET_FAILED; ++} ++ ++/* ++ * Write PCI config space of a remapped device. ++ * Since the original PCI config space is inaccessible, we reject all ++ * writes, except for the special case of BAR probing. ++ */ ++static int nvme_remap_pci_write_remapped(struct pci_bus *bus, ++ unsigned int port, ++ int reg, int len, u32 value) ++{ ++ struct nvme_remap_dev *nrdev = nrdev_from_bus(bus); ++ ++ if (port > nrdev->num_remapped_devices) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ /* ++ * Writing all-ones to a BAR means that the size of the memory ++ * region is being checked. Flag this so that we can reply with ++ * an appropriate size on the next read. ++ */ ++ if (value == ~0 && reg >= PCI_BASE_ADDRESS_0 ++ && reg <= PCI_BASE_ADDRESS_5) { ++ nrdev->bar_sizing = true; ++ return PCIBIOS_SUCCESSFUL; ++ } ++ ++ return PCIBIOS_SET_FAILED; ++} ++ ++/* Write PCI configuration space. */ ++static int nvme_remap_pci_write(struct pci_bus *bus, unsigned int devfn, ++ int reg, int len, u32 value) ++{ ++ if (PCI_SLOT(devfn) == 0) ++ return nvme_remap_pci_write_slot0(bus, reg, len, value); ++ else ++ return nvme_remap_pci_write_remapped(bus, PCI_SLOT(devfn), ++ reg, len, value); ++} ++ ++static struct pci_ops nvme_remap_pci_ops = { ++ .read = nvme_remap_pci_read, ++ .write = nvme_remap_pci_write, ++}; ++ ++ ++/******** Initialization & exit **********/ ++ ++/* ++ * Find a PCI domain ID to use for our fake bus. ++ * Start at 0x10000 to not clash with ACPI _SEG domains (16 bits). ++ */ ++static int find_free_domain(void) ++{ ++ int domain = 0xffff; ++ struct pci_bus *bus = NULL; ++ ++ while ((bus = pci_find_next_bus(bus)) != NULL) ++ domain = max_t(int, domain, pci_domain_nr(bus)); ++ ++ return domain + 1; ++} ++ ++static int find_remapped_devices(struct nvme_remap_dev *nrdev, ++ struct list_head *resources) ++{ ++ void __iomem *mmio; ++ int i, count = 0; ++ u32 cap; ++ ++ mmio = pcim_iomap(nrdev->dev, AHCI_PCI_BAR_STANDARD, ++ pci_resource_len(nrdev->dev, ++ AHCI_PCI_BAR_STANDARD)); ++ if (!mmio) ++ return -ENODEV; ++ ++ /* Check if this device might have remapped nvme devices. */ ++ if (pci_resource_len(nrdev->dev, AHCI_PCI_BAR_STANDARD) < SZ_512K || ++ !(readl(mmio + AHCI_VSCAP) & 1)) ++ return -ENODEV; ++ ++ cap = readq(mmio + AHCI_REMAP_CAP); ++ for (i = AHCI_MAX_REMAP-1; i >= 0; i--) { ++ struct resource *remapped_mem; ++ ++ if ((cap & (1 << i)) == 0) ++ continue; ++ if (readl(mmio + ahci_remap_dcc(i)) ++ != PCI_CLASS_STORAGE_EXPRESS) ++ continue; ++ ++ /* We've found a remapped device */ ++ remapped_mem = &nrdev->remapped_dev_mem[count++]; ++ remapped_mem->start = ++ pci_resource_start(nrdev->dev, AHCI_PCI_BAR_STANDARD) ++ + ahci_remap_base(i); ++ remapped_mem->end = remapped_mem->start ++ + AHCI_REMAP_N_SIZE - 1; ++ remapped_mem->flags = IORESOURCE_MEM | IORESOURCE_PCI_FIXED; ++ pci_add_resource(resources, remapped_mem); ++ } ++ ++ pcim_iounmap(nrdev->dev, mmio); ++ ++ if (count == 0) ++ return -ENODEV; ++ ++ nrdev->num_remapped_devices = count; ++ dev_info(&nrdev->dev->dev, "Found %d remapped NVMe devices\n", ++ nrdev->num_remapped_devices); ++ return 0; ++} ++ ++static void nvme_remap_remove_root_bus(void *data) ++{ ++ struct pci_bus *bus = data; ++ ++ pci_stop_root_bus(bus); ++ pci_remove_root_bus(bus); ++} ++ ++static int nvme_remap_probe(struct pci_dev *dev, ++ const struct pci_device_id *id) ++{ ++ struct nvme_remap_dev *nrdev; ++ LIST_HEAD(resources); ++ int i; ++ int ret; ++ struct pci_dev *child; ++ ++ nrdev = devm_kzalloc(&dev->dev, sizeof(*nrdev), GFP_KERNEL); ++ nrdev->sysdata.domain = find_free_domain(); ++ nrdev->sysdata.nvme_remap_dev = dev; ++ nrdev->dev = dev; ++ pci_set_drvdata(dev, nrdev); ++ ++ ret = pcim_enable_device(dev); ++ if (ret < 0) ++ return ret; ++ ++ pci_set_master(dev); ++ ++ ret = find_remapped_devices(nrdev, &resources); ++ if (ret) ++ return ret; ++ ++ /* Add resources from the original AHCI device */ ++ for (i = 0; i < PCI_NUM_RESOURCES; i++) { ++ struct resource *res = &dev->resource[i]; ++ ++ if (res->start) { ++ struct resource *nr_res = &nrdev->ahci_resources[i]; ++ ++ nr_res->start = res->start; ++ nr_res->end = res->end; ++ nr_res->flags = res->flags; ++ pci_add_resource(&resources, nr_res); ++ } ++ } ++ ++ /* Create virtual interrupts */ ++ nrdev->irq_base = devm_irq_alloc_descs(&dev->dev, -1, 0, ++ nrdev->num_remapped_devices + 1, ++ 0); ++ if (nrdev->irq_base < 0) ++ return nrdev->irq_base; ++ ++ /* Create and populate PCI bus */ ++ nrdev->bus = pci_create_root_bus(&dev->dev, 0, &nvme_remap_pci_ops, ++ &nrdev->sysdata, &resources); ++ if (!nrdev->bus) ++ return -ENODEV; ++ ++ if (devm_add_action_or_reset(&dev->dev, nvme_remap_remove_root_bus, ++ nrdev->bus)) ++ return -ENOMEM; ++ ++ /* We don't support sharing MSI interrupts between these devices */ ++ nrdev->bus->bus_flags |= PCI_BUS_FLAGS_NO_MSI; ++ ++ pci_scan_child_bus(nrdev->bus); ++ ++ list_for_each_entry(child, &nrdev->bus->devices, bus_list) { ++ /* ++ * Prevent PCI core from trying to move memory BARs around. ++ * The hidden NVMe devices are at fixed locations. ++ */ ++ for (i = 0; i < PCI_NUM_RESOURCES; i++) { ++ struct resource *res = &child->resource[i]; ++ ++ if (res->flags & IORESOURCE_MEM) ++ res->flags |= IORESOURCE_PCI_FIXED; ++ } ++ ++ /* Share the legacy IRQ between all devices */ ++ child->irq = dev->irq; ++ } ++ ++ pci_assign_unassigned_bus_resources(nrdev->bus); ++ pci_bus_add_devices(nrdev->bus); ++ ++ return 0; ++} ++ ++static const struct pci_device_id nvme_remap_ids[] = { ++ /* ++ * Match all Intel RAID controllers. ++ * ++ * There's overlap here with the set of devices detected by the ahci ++ * driver, but ahci will only successfully probe when there ++ * *aren't* any remapped NVMe devices, and this driver will only ++ * successfully probe when there *are* remapped NVMe devices that ++ * need handling. ++ */ ++ { ++ PCI_VDEVICE(INTEL, PCI_ANY_ID), ++ .class = PCI_CLASS_STORAGE_RAID << 8, ++ .class_mask = 0xffffff00, ++ }, ++ {0,} ++}; ++MODULE_DEVICE_TABLE(pci, nvme_remap_ids); ++ ++static struct pci_driver nvme_remap_drv = { ++ .name = MODULE_NAME, ++ .id_table = nvme_remap_ids, ++ .probe = nvme_remap_probe, ++}; ++module_pci_driver(nvme_remap_drv); ++ ++MODULE_AUTHOR("Daniel Drake "); ++MODULE_LICENSE("GPL v2"); +diff '--color=auto' -uraN a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +--- a/drivers/pci/pci-driver.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/pci/pci-driver.c 2023-11-04 16:36:53.548982960 +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 a/drivers/pci/pci.c b/drivers/pci/pci.c +--- a/drivers/pci/pci.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/pci/pci.c 2023-11-04 16:36:53.512315636 +0300 +@@ -3743,14 +3743,7 @@ + return 0; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); +- cap &= PCI_REBAR_CAP_SIZES; +- +- /* 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; +- +- return cap >> 4; ++ return (cap & PCI_REBAR_CAP_SIZES) >> 4; + } + EXPORT_SYMBOL(pci_rebar_get_possible_sizes); + +diff '--color=auto' -uraN a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/pci/quirks.c 2023-11-04 16:36:53.548982960 +0300 +@@ -3718,6 +3718,106 @@ + dev->dev_flags |= PCI_DEV_FLAGS_NO_BUS_RESET; + } + ++static bool acs_on_downstream; ++static bool acs_on_multifunction; ++ ++#define NUM_ACS_IDS 16 ++struct acs_on_id { ++ unsigned short vendor; ++ unsigned short device; ++}; ++static struct acs_on_id acs_on_ids[NUM_ACS_IDS]; ++static u8 max_acs_id; ++ ++static __init int pcie_acs_override_setup(char *p) ++{ ++ if (!p) ++ return -EINVAL; ++ ++ while (*p) { ++ if (!strncmp(p, "downstream", 10)) ++ acs_on_downstream = true; ++ if (!strncmp(p, "multifunction", 13)) ++ acs_on_multifunction = true; ++ if (!strncmp(p, "id:", 3)) { ++ char opt[5]; ++ int ret; ++ long val; ++ ++ if (max_acs_id >= NUM_ACS_IDS - 1) { ++ pr_warn("Out of PCIe ACS override slots (%d)\n", ++ NUM_ACS_IDS); ++ goto next; ++ } ++ ++ p += 3; ++ snprintf(opt, 5, "%s", p); ++ ret = kstrtol(opt, 16, &val); ++ if (ret) { ++ pr_warn("PCIe ACS ID parse error %d\n", ret); ++ goto next; ++ } ++ acs_on_ids[max_acs_id].vendor = val; ++ ++ p += strcspn(p, ":"); ++ if (*p != ':') { ++ pr_warn("PCIe ACS invalid ID\n"); ++ goto next; ++ } ++ ++ p++; ++ snprintf(opt, 5, "%s", p); ++ ret = kstrtol(opt, 16, &val); ++ if (ret) { ++ pr_warn("PCIe ACS ID parse error %d\n", ret); ++ goto next; ++ } ++ acs_on_ids[max_acs_id].device = val; ++ max_acs_id++; ++ } ++next: ++ p += strcspn(p, ","); ++ if (*p == ',') ++ p++; ++ } ++ ++ if (acs_on_downstream || acs_on_multifunction || max_acs_id) ++ pr_warn("Warning: PCIe ACS overrides enabled; This may allow non-IOMMU protected peer-to-peer DMA\n"); ++ ++ return 0; ++} ++early_param("pcie_acs_override", pcie_acs_override_setup); ++ ++static int pcie_acs_overrides(struct pci_dev *dev, u16 acs_flags) ++{ ++ int i; ++ ++ /* Never override ACS for legacy devices or devices with ACS caps */ ++ if (!pci_is_pcie(dev) || ++ pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS)) ++ return -ENOTTY; ++ ++ for (i = 0; i < max_acs_id; i++) ++ if (acs_on_ids[i].vendor == dev->vendor && ++ acs_on_ids[i].device == dev->device) ++ return 1; ++ ++ switch (pci_pcie_type(dev)) { ++ case PCI_EXP_TYPE_DOWNSTREAM: ++ case PCI_EXP_TYPE_ROOT_PORT: ++ if (acs_on_downstream) ++ return 1; ++ break; ++ case PCI_EXP_TYPE_ENDPOINT: ++ case PCI_EXP_TYPE_UPSTREAM: ++ case PCI_EXP_TYPE_LEG_END: ++ case PCI_EXP_TYPE_RC_END: ++ if (acs_on_multifunction && dev->multifunction) ++ return 1; ++ } ++ ++ return -ENOTTY; ++} + /* + * Some NVIDIA GPU devices do not work with bus reset, SBR needs to be + * prevented for those affected devices. +@@ -5112,6 +5212,7 @@ + { 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 }, ++ { PCI_ANY_ID, PCI_ANY_ID, pcie_acs_overrides }, + { 0 } + }; + +@@ -6138,3 +6239,39 @@ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a2f, dpc_log_size); + 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. ++ */ ++ { ++ .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 +diff '--color=auto' -uraN a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +--- a/drivers/platform/surface/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/surface/Kconfig 2023-11-04 16:36:53.548982960 +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. + ++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 '--color=auto' -uraN a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +--- a/drivers/platform/surface/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/surface/Makefile 2023-11-04 16:36:53.548982960 +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 a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +--- a/drivers/platform/surface/surface3-wmi.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/surface/surface3-wmi.c 2023-11-04 16:36:53.538982781 +0300 +@@ -37,6 +37,13 @@ + 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 '--color=auto' -uraN a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +--- a/drivers/platform/surface/surface_aggregator_registry.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/surface/surface_aggregator_registry.c 2023-11-04 16:36:53.545649567 +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 a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +--- a/drivers/platform/surface/surface_gpe.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/surface/surface_gpe.c 2023-11-04 16:36:53.552316353 +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 a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +--- a/drivers/platform/surface/surfacebook1_dgpu_switch.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c 2023-11-04 16:36:53.548982960 +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, ++ 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"); +diff '--color=auto' -uraN a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +--- a/drivers/platform/surface/surfacepro3_button.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/surface/surfacepro3_button.c 2023-11-04 16:36:53.548982960 +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) + { + 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)); + } + + +diff '--color=auto' -uraN a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +--- a/drivers/platform/x86/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/x86/Kconfig 2023-11-04 16:35:57.817983546 +0300 +@@ -643,6 +643,16 @@ + 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 +@@ -1094,6 +1104,20 @@ + buttons below the display. This module adds an input device + that delivers key events when these buttons are pressed. + ++config STEAMDECK ++ tristate "Valve Steam Deck platform driver" ++ depends on X86_64 ++ help ++ Driver exposing various bits and pieces of functionality ++ provided by Steam Deck specific VLV0100 device presented by ++ EC firmware. This includes but not limited to: ++ - CPU/device's fan control ++ - Read-only access to DDIC registers ++ - Battery tempreature measurements ++ - Various display related control knobs ++ - USB Type-C connector event notification ++ Say N unless you are running on a Steam Deck. ++ + endif # X86_PLATFORM_DEVICES + + config P2SB +diff '--color=auto' -uraN a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +--- a/drivers/platform/x86/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/x86/Makefile 2023-11-04 16:35:57.817983546 +0300 +@@ -65,6 +65,7 @@ + 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 +@@ -135,3 +136,6 @@ + + # Winmate + obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o ++ ++# Steam Deck ++obj-$(CONFIG_STEAMDECK) += steamdeck.o +diff '--color=auto' -uraN a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/x86/asus-wmi.c 2023-11-04 16:36:53.528982602 +0300 +@@ -25,6 +25,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -72,6 +73,7 @@ + + #define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0) + ++#define ASUS_MID_FAN_DESC "mid_fan" + #define ASUS_GPU_FAN_DESC "gpu_fan" + #define ASUS_FAN_DESC "cpu_fan" + #define ASUS_FAN_MFUN 0x13 +@@ -112,9 +114,23 @@ + #define FAN_CURVE_BUF_LEN 32 + #define FAN_CURVE_DEV_CPU 0x00 + #define FAN_CURVE_DEV_GPU 0x01 ++#define FAN_CURVE_DEV_MID 0x02 + /* Mask to determine if setting temperature or percentage */ + #define FAN_CURVE_PWM_MASK 0x04 + ++/* Limits for tunables available on ASUS ROG laptops */ ++#define PPT_TOTAL_MIN 5 ++#define PPT_TOTAL_MAX 250 ++#define PPT_CPU_MIN 5 ++#define PPT_CPU_MAX 130 ++#define NVIDIA_BOOST_MIN 5 ++#define NVIDIA_BOOST_MAX 25 ++#define NVIDIA_TEMP_MIN 75 ++#define NVIDIA_TEMP_MAX 87 ++ ++#define ASUS_SCREENPAD_BRIGHT_MIN 20 ++#define ASUS_SCREENPAD_BRIGHT_MAX 255 ++ + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; + + static int throttle_thermal_policy_write(struct asus_wmi *); +@@ -200,6 +216,7 @@ + + 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; +@@ -229,18 +246,31 @@ + + enum fan_type fan_type; + enum fan_type gpu_fan_type; ++ enum fan_type mid_fan_type; + int fan_pwm_mode; + int gpu_fan_pwm_mode; ++ int mid_fan_pwm_mode; + int agfn_pwm; + + bool fan_boost_mode_available; + u8 fan_boost_mode_mask; + u8 fan_boost_mode; + ++ bool charge_mode_available; + bool egpu_enable_available; ++ bool egpu_connect_available; + bool dgpu_disable_available; + bool gpu_mux_mode_available; + ++ /* Tunables provided by ASUS for gaming laptops */ ++ bool ppt_pl2_sppt_available; ++ bool ppt_pl1_spl_available; ++ bool ppt_apu_sppt_available; ++ bool ppt_plat_sppt_available; ++ bool ppt_fppt_available; ++ bool nv_dyn_boost_available; ++ bool nv_temp_tgt_available; ++ + bool kbd_rgb_mode_available; + bool kbd_rgb_state_available; + +@@ -249,7 +279,8 @@ + + bool cpu_fan_curve_available; + bool gpu_fan_curve_available; +- struct fan_curve_data custom_fan_curves[2]; ++ bool mid_fan_curve_available; ++ struct fan_curve_data custom_fan_curves[3]; + + struct platform_profile_handler platform_profile_handler; + bool platform_profile_support; +@@ -258,6 +289,7 @@ + bool battery_rsoc_available; + + bool panel_overdrive_available; ++ bool mini_led_mode_available; + + struct hotplug_slot hotplug_slot; + struct mutex hotplug_lock; +@@ -586,6 +618,22 @@ + asus_wmi_tablet_sw_report(asus, result); + } + ++/* Charging mode, 1=Barrel, 2=USB ******************************************/ ++static ssize_t charge_mode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int result, value; ++ ++ result = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CHARGE_MODE, &value); ++ if (result < 0) ++ return result; ++ ++ return sysfs_emit(buf, "%d\n", value & 0xff); ++} ++ ++static DEVICE_ATTR_RO(charge_mode); ++ + /* dGPU ********************************************************************/ + static ssize_t dgpu_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -622,6 +670,18 @@ + if (disable > 1) + return -EINVAL; + ++ if (asus->gpu_mux_mode_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX); ++ if (result < 0) ++ /* An error here may signal greater failure of GPU handling */ ++ return result; ++ if (!result && disable) { ++ err = -ENODEV; ++ pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err); ++ return err; ++ } ++ } ++ + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result); + if (err) { + pr_warn("Failed to set dgpu disable: %d\n", err); +@@ -670,14 +730,34 @@ + if (enable > 1) + return -EINVAL; + ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); ++ if (err < 0) { ++ pr_warn("Failed to get egpu connection status: %d\n", err); ++ return err; ++ } ++ ++ if (asus->gpu_mux_mode_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX); ++ if (result < 0) { ++ /* An error here may signal greater failure of GPU handling */ ++ pr_warn("Failed to get gpu mux status: %d\n", err); ++ return result; ++ } ++ if (!result && enable) { ++ err = -ENODEV; ++ pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err); ++ return err; ++ } ++ } ++ + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result); + if (err) { +- pr_warn("Failed to set egpu disable: %d\n", err); ++ pr_warn("Failed to set egpu state: %d\n", err); + return err; + } + + if (result > 1) { +- pr_warn("Failed to set egpu disable (retval): 0x%x\n", result); ++ pr_warn("Failed to set egpu state (retval): 0x%x\n", result); + return -EIO; + } + +@@ -687,6 +767,22 @@ + } + static DEVICE_ATTR_RW(egpu_enable); + ++/* Is eGPU connected? *********************************************************/ ++static ssize_t egpu_connected_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int result; ++ ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); ++ if (result < 0) ++ return result; ++ ++ return sysfs_emit(buf, "%d\n", result); ++} ++ ++static DEVICE_ATTR_RO(egpu_connected); ++ + /* gpu mux switch *************************************************************/ + static ssize_t gpu_mux_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -716,6 +812,30 @@ + if (optimus > 1) + return -EINVAL; + ++ if (asus->dgpu_disable_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU); ++ if (result < 0) ++ /* An error here may signal greater failure of GPU handling */ ++ return result; ++ if (result && !optimus) { ++ err = -ENODEV; ++ pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %d\n", err); ++ return err; ++ } ++ } ++ ++ if (asus->egpu_enable_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU); ++ if (result < 0) ++ /* An error here may signal greater failure of GPU handling */ ++ return result; ++ if (result && !optimus) { ++ err = -ENODEV; ++ pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", err); ++ return err; ++ } ++ } ++ + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result); + if (err) { + dev_err(dev, "Failed to set GPU MUX mode: %d\n", err); +@@ -859,6 +979,244 @@ + NULL, + }; + ++/* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/ ++static ssize_t ppt_pl2_sppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL2_SPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_pl2_sppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_pl2_sppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_pl2_sppt); ++ ++/* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/ ++static ssize_t ppt_pl1_spl_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL1_SPL, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_pl1_spl: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_pl1_spl (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_pl1_spl); ++ ++/* Tunable: PPT APU FPPT ******************************************************/ ++static ssize_t ppt_fppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_FPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_fppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_fppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_fppt); ++ ++/* Tunable: PPT APU SPPT *****************************************************/ ++static ssize_t ppt_apu_sppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_APU_SPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_apu_sppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_apu_sppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_apu_sppt); ++ ++/* Tunable: PPT platform SPPT ************************************************/ ++static ssize_t ppt_platform_sppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PLAT_SPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_platform_sppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_platform_sppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_platform_sppt); ++ ++/* Tunable: NVIDIA dynamic boost *********************************************/ ++static ssize_t nv_dynamic_boost_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < NVIDIA_BOOST_MIN || value > NVIDIA_BOOST_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_DYN_BOOST, value, &result); ++ if (err) { ++ pr_warn("Failed to set nv_dynamic_boost: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set nv_dynamic_boost (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(nv_dynamic_boost); ++ ++/* Tunable: NVIDIA temperature target ****************************************/ ++static ssize_t nv_temp_target_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < NVIDIA_TEMP_MIN || value > NVIDIA_TEMP_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_THERM_TARGET, value, &result); ++ if (err) { ++ pr_warn("Failed to set nv_temp_target: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set nv_temp_target (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(nv_temp_target); ++ + /* Battery ********************************************************************/ + + /* The battery maximum charging percentage */ +@@ -1734,6 +2092,54 @@ + } + static DEVICE_ATTR_RW(panel_od); + ++/* Mini-LED mode **************************************************************/ ++static ssize_t mini_led_mode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int result; ++ ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE); ++ if (result < 0) ++ return result; ++ ++ return sysfs_emit(buf, "%d\n", result); ++} ++ ++static ssize_t mini_led_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 mode; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &mode); ++ if (result) ++ return result; ++ ++ if (mode > 1) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MINI_LED_MODE, mode, &result); ++ ++ if (err) { ++ pr_warn("Failed to set mini-LED: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set mini-LED mode (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mini_led_mode"); ++ ++ return count; ++} ++static DEVICE_ATTR_RW(mini_led_mode); ++ + /* Quirks *********************************************************************/ + + static void asus_wmi_set_xusb2pr(struct asus_wmi *asus) +@@ -2070,6 +2476,8 @@ + asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false; + if (asus->gpu_fan_curve_available) + asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false; ++ if (asus->mid_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false; + + return count; + } +@@ -2122,6 +2530,31 @@ + return sysfs_emit(buf, "%s\n", ASUS_GPU_FAN_DESC); + } + ++/* Middle/Center fan on modern ROG laptops */ ++static ssize_t fan3_input_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int value; ++ int ret; ++ ++ ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_MID_FAN_CTRL, &value); ++ if (ret < 0) ++ return ret; ++ ++ value &= 0xffff; ++ ++ return sysfs_emit(buf, "%d\n", value * 100); ++} ++ ++static ssize_t fan3_label_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "%s\n", ASUS_MID_FAN_DESC); ++} ++ + static ssize_t pwm2_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +@@ -2168,6 +2601,52 @@ + return count; + } + ++static ssize_t pwm3_enable_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ return sysfs_emit(buf, "%d\n", asus->mid_fan_pwm_mode); ++} ++ ++static ssize_t pwm3_enable_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int state; ++ int value; ++ int ret; ++ u32 retval; ++ ++ ret = kstrtouint(buf, 10, &state); ++ if (ret) ++ return ret; ++ ++ switch (state) { /* standard documented hwmon values */ ++ case ASUS_FAN_CTRL_FULLSPEED: ++ value = 1; ++ break; ++ case ASUS_FAN_CTRL_AUTO: ++ value = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_MID_FAN_CTRL, ++ value, &retval); ++ if (ret) ++ return ret; ++ ++ if (retval != 1) ++ return -EIO; ++ ++ asus->mid_fan_pwm_mode = state; ++ return count; ++} ++ + /* Fan1 */ + static DEVICE_ATTR_RW(pwm1); + static DEVICE_ATTR_RW(pwm1_enable); +@@ -2177,6 +2656,10 @@ + static DEVICE_ATTR_RW(pwm2_enable); + static DEVICE_ATTR_RO(fan2_input); + static DEVICE_ATTR_RO(fan2_label); ++/* Fan3 - Middle/center fan */ ++static DEVICE_ATTR_RW(pwm3_enable); ++static DEVICE_ATTR_RO(fan3_input); ++static DEVICE_ATTR_RO(fan3_label); + + /* Temperature */ + static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL); +@@ -2185,10 +2668,13 @@ + &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm2_enable.attr, ++ &dev_attr_pwm3_enable.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_label.attr, + &dev_attr_fan2_input.attr, + &dev_attr_fan2_label.attr, ++ &dev_attr_fan3_input.attr, ++ &dev_attr_fan3_label.attr, + + &dev_attr_temp1_input.attr, + NULL +@@ -2214,6 +2700,11 @@ + || attr == &dev_attr_pwm2_enable.attr) { + if (asus->gpu_fan_type == FAN_TYPE_NONE) + return 0; ++ } else if (attr == &dev_attr_fan3_input.attr ++ || attr == &dev_attr_fan3_label.attr ++ || attr == &dev_attr_pwm3_enable.attr) { ++ if (asus->mid_fan_type == FAN_TYPE_NONE) ++ return 0; + } else if (attr == &dev_attr_temp1_input.attr) { + int err = asus_wmi_get_devstate(asus, + ASUS_WMI_DEVID_THERMAL_CTRL, +@@ -2257,6 +2748,7 @@ + static int asus_wmi_fan_init(struct asus_wmi *asus) + { + asus->gpu_fan_type = FAN_TYPE_NONE; ++ asus->mid_fan_type = FAN_TYPE_NONE; + asus->fan_type = FAN_TYPE_NONE; + asus->agfn_pwm = -1; + +@@ -2271,6 +2763,10 @@ + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_FAN_CTRL)) + asus->gpu_fan_type = FAN_TYPE_SPEC83; + ++ /* Some models also have a center/middle fan */ ++ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MID_FAN_CTRL)) ++ asus->mid_fan_type = FAN_TYPE_SPEC83; ++ + if (asus->fan_type == FAN_TYPE_NONE) + return -ENODEV; + +@@ -2418,9 +2914,8 @@ + { + struct fan_curve_data *curves; + u8 buf[FAN_CURVE_BUF_LEN]; +- int fan_idx = 0; ++ int err, fan_idx; + u8 mode = 0; +- int err; + + if (asus->throttle_thermal_policy_available) + mode = asus->throttle_thermal_policy_mode; +@@ -2430,10 +2925,6 @@ + else if (mode == 1) + mode = 2; + +- if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE) +- fan_idx = FAN_CURVE_DEV_GPU; +- +- curves = &asus->custom_fan_curves[fan_idx]; + err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf, + FAN_CURVE_BUF_LEN); + if (err) { +@@ -2441,9 +2932,17 @@ + return err; + } + +- fan_curve_copy_from_buf(curves, buf); ++ fan_idx = FAN_CURVE_DEV_CPU; ++ if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE) ++ fan_idx = FAN_CURVE_DEV_GPU; ++ ++ if (fan_dev == ASUS_WMI_DEVID_MID_FAN_CURVE) ++ fan_idx = FAN_CURVE_DEV_MID; ++ ++ curves = &asus->custom_fan_curves[fan_idx]; + curves->device_id = fan_dev; + ++ fan_curve_copy_from_buf(curves, buf); + return 0; + } + +@@ -2473,7 +2972,7 @@ + { + int index = to_sensor_dev_attr(attr)->index; + +- return &asus->custom_fan_curves[index & FAN_CURVE_DEV_GPU]; ++ return &asus->custom_fan_curves[index]; + } + + /* Determine which fan the attribute is for if SENSOR_ATTR_2 */ +@@ -2482,7 +2981,7 @@ + { + int nr = to_sensor_dev_attr_2(attr)->nr; + +- return &asus->custom_fan_curves[nr & FAN_CURVE_DEV_GPU]; ++ return &asus->custom_fan_curves[nr & ~FAN_CURVE_PWM_MASK]; + } + + static ssize_t fan_curve_show(struct device *dev, +@@ -2491,13 +2990,13 @@ + struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); + struct asus_wmi *asus = dev_get_drvdata(dev); + struct fan_curve_data *data; +- int value, index, nr; ++ int value, pwm, index; + + data = fan_curve_attr_2_select(asus, attr); ++ pwm = dev_attr->nr & FAN_CURVE_PWM_MASK; + index = dev_attr->index; +- nr = dev_attr->nr; + +- if (nr & FAN_CURVE_PWM_MASK) ++ if (pwm) + value = data->percents[index]; + else + value = data->temps[index]; +@@ -2540,23 +3039,21 @@ + struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); + struct asus_wmi *asus = dev_get_drvdata(dev); + struct fan_curve_data *data; ++ int err, pwm, index; + u8 value; +- int err; +- +- int pwm = dev_attr->nr & FAN_CURVE_PWM_MASK; +- int index = dev_attr->index; + + data = fan_curve_attr_2_select(asus, attr); ++ pwm = dev_attr->nr & FAN_CURVE_PWM_MASK; ++ index = dev_attr->index; + + err = kstrtou8(buf, 10, &value); + if (err < 0) + return err; + +- if (pwm) { ++ if (pwm) + data->percents[index] = value; +- } else { ++ else + data->temps[index] = value; +- } + + /* + * Mark as disabled so the user has to explicitly enable to apply a +@@ -2669,7 +3166,7 @@ + FAN_CURVE_DEV_CPU, 7); + + static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, fan_curve, +- FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0); ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0); + static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, fan_curve, + FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 1); + static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, fan_curve, +@@ -2721,6 +3218,42 @@ + static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve, + FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7); + ++/* MID */ ++static SENSOR_DEVICE_ATTR_RW(pwm3_enable, fan_curve_enable, FAN_CURVE_DEV_MID); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp, fan_curve, ++ FAN_CURVE_DEV_MID, 7); ++ ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_pwm, fan_curve, ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 7); ++ + static struct attribute *asus_fan_curve_attr[] = { + /* CPU */ + &sensor_dev_attr_pwm1_enable.dev_attr.attr, +@@ -2758,6 +3291,24 @@ + &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, ++ /* MID */ ++ &sensor_dev_attr_pwm3_enable.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_point1_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point5_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point6_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point7_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point8_pwm.dev_attr.attr, + NULL + }; + +@@ -2777,6 +3328,9 @@ + if (asus->gpu_fan_curve_available && attr->name[3] == '2') + return 0644; + ++ if (asus->mid_fan_curve_available && attr->name[3] == '3') ++ return 0644; ++ + return 0; + } + +@@ -2806,7 +3360,14 @@ + if (err) + return err; + +- if (!asus->cpu_fan_curve_available && !asus->gpu_fan_curve_available) ++ err = fan_curve_check_present(asus, &asus->mid_fan_curve_available, ++ ASUS_WMI_DEVID_MID_FAN_CURVE); ++ if (err) ++ return err; ++ ++ if (!asus->cpu_fan_curve_available ++ && !asus->gpu_fan_curve_available ++ && !asus->mid_fan_curve_available) + return 0; + + hwmon = devm_hwmon_device_register_with_groups( +@@ -2875,6 +3436,8 @@ + asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false; + if (asus->gpu_fan_curve_available) + asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false; ++ if (asus->mid_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false; + + return 0; + } +@@ -3218,6 +3781,123 @@ + return 0; + } + ++/* Screenpad backlight *******************************************************/ ++ ++static int read_screenpad_backlight_power(struct asus_wmi *asus) ++{ ++ int ret; ++ ++ ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER); ++ if (ret < 0) ++ return ret; ++ /* 1 == powered */ ++ return ret ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; ++} ++ ++static int read_screenpad_brightness(struct backlight_device *bd) ++{ ++ struct asus_wmi *asus = bl_get_data(bd); ++ u32 retval; ++ int err; ++ ++ err = read_screenpad_backlight_power(asus); ++ if (err < 0) ++ return err; ++ /* The device brightness can only be read if powered, so return stored */ ++ if (err == FB_BLANK_POWERDOWN) ++ return asus->driver->screenpad_brightness; ++ ++ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &retval); ++ if (err < 0) ++ return err; ++ ++ return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) - ASUS_SCREENPAD_BRIGHT_MIN; ++} ++ ++static int update_screenpad_bl_status(struct backlight_device *bd) ++{ ++ struct asus_wmi *asus = bl_get_data(bd); ++ int power, err = 0; ++ u32 ctrl_param; ++ ++ power = read_screenpad_backlight_power(asus); ++ if (power < 0) ++ return power; ++ ++ if (bd->props.power != power) { ++ if (power != FB_BLANK_UNBLANK) { ++ /* Only brightness > 0 can power it back on */ ++ ctrl_param = max(ASUS_SCREENPAD_BRIGHT_MIN, asus->driver->screenpad_brightness); ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ++ ctrl_param, NULL); ++ } else { ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); ++ } ++ } else if (power == FB_BLANK_UNBLANK) { ++ /* Only set brightness if powered on or we get invalid/unsync state */ ++ ctrl_param = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL); ++ } ++ ++ /* Ensure brightness is stored to turn back on with */ ++ asus->driver->screenpad_brightness = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; ++ ++ return err; ++} ++ ++static const struct backlight_ops asus_screenpad_bl_ops = { ++ .get_brightness = read_screenpad_brightness, ++ .update_status = update_screenpad_bl_status, ++ .options = BL_CORE_SUSPENDRESUME, ++}; ++ ++static int asus_screenpad_init(struct asus_wmi *asus) ++{ ++ struct backlight_device *bd; ++ struct backlight_properties props; ++ int err, power; ++ int brightness = 0; ++ ++ power = read_screenpad_backlight_power(asus); ++ if (power < 0) ++ return power; ++ ++ if (power != FB_BLANK_POWERDOWN) { ++ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness); ++ if (err < 0) ++ return err; ++ } ++ /* default to an acceptable min brightness on boot if too low */ ++ if (brightness < ASUS_SCREENPAD_BRIGHT_MIN) ++ brightness = ASUS_SCREENPAD_BRIGHT_MIN; ++ ++ memset(&props, 0, sizeof(struct backlight_properties)); ++ props.type = BACKLIGHT_RAW; /* ensure this bd is last to be picked */ ++ props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX - ASUS_SCREENPAD_BRIGHT_MIN; ++ bd = backlight_device_register("asus_screenpad", ++ &asus->platform_device->dev, asus, ++ &asus_screenpad_bl_ops, &props); ++ if (IS_ERR(bd)) { ++ pr_err("Could not register backlight device\n"); ++ return PTR_ERR(bd); ++ } ++ ++ asus->screenpad_backlight_device = bd; ++ asus->driver->screenpad_brightness = brightness; ++ bd->props.brightness = brightness; ++ bd->props.power = power; ++ backlight_update_status(bd); ++ ++ return 0; ++} ++ ++static void asus_screenpad_exit(struct asus_wmi *asus) ++{ ++ backlight_device_unregister(asus->screenpad_backlight_device); ++ ++ asus->screenpad_backlight_device = NULL; ++} ++ + /* Fn-lock ********************************************************************/ + + static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus) +@@ -3465,14 +4145,24 @@ + &dev_attr_camera.attr, + &dev_attr_cardr.attr, + &dev_attr_touchpad.attr, ++ &dev_attr_charge_mode.attr, + &dev_attr_egpu_enable.attr, ++ &dev_attr_egpu_connected.attr, + &dev_attr_dgpu_disable.attr, + &dev_attr_gpu_mux_mode.attr, + &dev_attr_lid_resume.attr, + &dev_attr_als_enable.attr, + &dev_attr_fan_boost_mode.attr, + &dev_attr_throttle_thermal_policy.attr, ++ &dev_attr_ppt_pl2_sppt.attr, ++ &dev_attr_ppt_pl1_spl.attr, ++ &dev_attr_ppt_fppt.attr, ++ &dev_attr_ppt_apu_sppt.attr, ++ &dev_attr_ppt_platform_sppt.attr, ++ &dev_attr_nv_dynamic_boost.attr, ++ &dev_attr_nv_temp_target.attr, + &dev_attr_panel_od.attr, ++ &dev_attr_mini_led_mode.attr, + NULL + }; + +@@ -3494,8 +4184,12 @@ + devid = ASUS_WMI_DEVID_LID_RESUME; + else if (attr == &dev_attr_als_enable.attr) + devid = ASUS_WMI_DEVID_ALS_ENABLE; ++ else if (attr == &dev_attr_charge_mode.attr) ++ ok = asus->charge_mode_available; + else if (attr == &dev_attr_egpu_enable.attr) + ok = asus->egpu_enable_available; ++ else if (attr == &dev_attr_egpu_connected.attr) ++ ok = asus->egpu_connect_available; + else if (attr == &dev_attr_dgpu_disable.attr) + ok = asus->dgpu_disable_available; + else if (attr == &dev_attr_gpu_mux_mode.attr) +@@ -3504,8 +4198,24 @@ + ok = asus->fan_boost_mode_available; + else if (attr == &dev_attr_throttle_thermal_policy.attr) + ok = asus->throttle_thermal_policy_available; ++ else if (attr == &dev_attr_ppt_pl2_sppt.attr) ++ ok = asus->ppt_pl2_sppt_available; ++ else if (attr == &dev_attr_ppt_pl1_spl.attr) ++ ok = asus->ppt_pl1_spl_available; ++ else if (attr == &dev_attr_ppt_fppt.attr) ++ ok = asus->ppt_fppt_available; ++ else if (attr == &dev_attr_ppt_apu_sppt.attr) ++ ok = asus->ppt_apu_sppt_available; ++ else if (attr == &dev_attr_ppt_platform_sppt.attr) ++ ok = asus->ppt_plat_sppt_available; ++ else if (attr == &dev_attr_nv_dynamic_boost.attr) ++ ok = asus->nv_dyn_boost_available; ++ else if (attr == &dev_attr_nv_temp_target.attr) ++ ok = asus->nv_temp_tgt_available; + else if (attr == &dev_attr_panel_od.attr) + ok = asus->panel_overdrive_available; ++ else if (attr == &dev_attr_mini_led_mode.attr) ++ ok = asus->mini_led_mode_available; + + if (devid != -1) + ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); +@@ -3760,12 +4470,22 @@ + if (err) + goto fail_platform; + ++ asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE); + asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); ++ asus->egpu_connect_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); + asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); + asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX); + asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE); + asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); ++ asus->ppt_pl2_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL2_SPPT); ++ asus->ppt_pl1_spl_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL1_SPL); ++ asus->ppt_fppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_FPPT); ++ asus->ppt_apu_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_APU_SPPT); ++ asus->ppt_plat_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PLAT_SPPT); ++ asus->nv_dyn_boost_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_DYN_BOOST); ++ 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); + + err = fan_boost_mode_check_present(asus); + if (err) +@@ -3826,6 +4546,12 @@ + } 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) ++ goto fail_screenpad; ++ } ++ + if (asus_wmi_has_fnlock_key(asus)) { + asus->fnlock_locked = fnlock_default; + asus_wmi_fnlock_update(asus); +@@ -3849,6 +4575,8 @@ + asus_wmi_backlight_exit(asus); + fail_backlight: + asus_wmi_rfkill_exit(asus); ++fail_screenpad: ++ asus_screenpad_exit(asus); + fail_rfkill: + asus_wmi_led_exit(asus); + fail_leds: +@@ -3875,6 +4603,7 @@ + asus = platform_get_drvdata(device); + wmi_remove_notify_handler(asus->driver->event_guid); + asus_wmi_backlight_exit(asus); ++ asus_screenpad_exit(asus); + asus_wmi_input_exit(asus); + asus_wmi_led_exit(asus); + asus_wmi_rfkill_exit(asus); +diff '--color=auto' -uraN a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h +--- a/drivers/platform/x86/asus-wmi.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/x86/asus-wmi.h 2023-11-04 16:36:53.528982602 +0300 +@@ -57,6 +57,7 @@ + struct asus_wmi_driver { + int brightness; + int panel_power; ++ int screenpad_brightness; + int wlan_ctrl_by_user; + + const char *name; +diff '--color=auto' -uraN a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +--- a/drivers/platform/x86/intel/int3472/discrete.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/x86/intel/int3472/discrete.c 2023-11-04 16:36:53.555649747 +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 a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +--- a/drivers/platform/x86/intel/int3472/tps68470.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/platform/x86/intel/int3472/tps68470.c 2023-11-04 16:36:53.558983140 +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 a/drivers/platform/x86/legion-laptop.c b/drivers/platform/x86/legion-laptop.c +--- a/drivers/platform/x86/legion-laptop.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/platform/x86/legion-laptop.c 2023-11-04 16:35:57.817983546 +0300 +@@ -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); +diff '--color=auto' -uraN a/drivers/platform/x86/steamdeck.c b/drivers/platform/x86/steamdeck.c +--- a/drivers/platform/x86/steamdeck.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/drivers/platform/x86/steamdeck.c 2023-11-04 16:35:57.817983546 +0300 +@@ -0,0 +1,523 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++/* ++ * Steam Deck ACPI platform driver ++ * ++ * Copyright (C) 2021-2022 Valve Corporation ++ * ++ */ ++#include ++#include ++#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 { ++ struct acpi_device *adev; ++ struct device *hwmon; ++ void *regmap; ++ long fan_target; ++ struct delayed_work role_work; ++ struct extcon_dev *edev; ++ struct device *dev; ++}; ++ ++static ssize_t ++steamdeck_simple_store(struct device *dev, const char *buf, size_t count, ++ const char *method, ++ unsigned long upper_limit) ++{ ++ struct steamdeck *fan = dev_get_drvdata(dev); ++ unsigned long value; ++ ++ if (kstrtoul(buf, 10, &value) || value >= upper_limit) ++ return -EINVAL; ++ ++ if (ACPI_FAILURE(acpi_execute_simple_method(fan->adev->handle, ++ (char *)method, value))) ++ return -EIO; ++ ++ return count; ++} ++ ++#define STEAMDECK_ATTR_WO(_name, _method, _upper_limit) \ ++ static ssize_t _name##_store(struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ return steamdeck_simple_store(dev, buf, count, \ ++ _method, \ ++ _upper_limit); \ ++ } \ ++ static DEVICE_ATTR_WO(_name) ++ ++STEAMDECK_ATTR_WO(target_cpu_temp, "STCT", U8_MAX / 2); ++STEAMDECK_ATTR_WO(gain, "SGAN", U16_MAX); ++STEAMDECK_ATTR_WO(ramp_rate, "SFRR", U8_MAX); ++STEAMDECK_ATTR_WO(hysteresis, "SHTS", U16_MAX); ++STEAMDECK_ATTR_WO(maximum_battery_charge_rate, "CHGR", U16_MAX); ++STEAMDECK_ATTR_WO(recalculate, "SCHG", U16_MAX); ++ ++STEAMDECK_ATTR_WO(led_brightness, "CHBV", U8_MAX); ++STEAMDECK_ATTR_WO(content_adaptive_brightness, "CABC", U8_MAX); ++STEAMDECK_ATTR_WO(gamma_set, "GAMA", U8_MAX); ++STEAMDECK_ATTR_WO(display_brightness, "WDBV", U8_MAX); ++STEAMDECK_ATTR_WO(ctrl_display, "WCDV", U8_MAX); ++STEAMDECK_ATTR_WO(cabc_minimum_brightness, "WCMB", U8_MAX); ++STEAMDECK_ATTR_WO(memory_data_access_control, "MDAC", U8_MAX); ++ ++#define STEAMDECK_ATTR_WO_NOARG(_name, _method) \ ++ static ssize_t _name##_store(struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ struct steamdeck *fan = dev_get_drvdata(dev); \ ++ \ ++ if (ACPI_FAILURE(acpi_evaluate_object(fan->adev->handle, \ ++ _method, NULL, NULL))) \ ++ return -EIO; \ ++ \ ++ return count; \ ++ } \ ++ static DEVICE_ATTR_WO(_name) ++ ++STEAMDECK_ATTR_WO_NOARG(power_cycle_display, "DPCY"); ++STEAMDECK_ATTR_WO_NOARG(display_normal_mode_on, "NORO"); ++STEAMDECK_ATTR_WO_NOARG(display_inversion_off, "INOF"); ++STEAMDECK_ATTR_WO_NOARG(display_inversion_on, "INON"); ++STEAMDECK_ATTR_WO_NOARG(idle_mode_on, "WRNE"); ++ ++#define STEAMDECK_ATTR_RO(_name, _method) \ ++ static ssize_t _name##_show(struct device *dev, \ ++ struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ struct steamdeck *jup = dev_get_drvdata(dev); \ ++ unsigned long long val; \ ++ \ ++ if (ACPI_FAILURE(acpi_evaluate_integer( \ ++ jup->adev->handle, \ ++ _method, NULL, &val))) \ ++ return -EIO; \ ++ \ ++ return sprintf(buf, "%llu\n", val); \ ++ } \ ++ static DEVICE_ATTR_RO(_name) ++ ++STEAMDECK_ATTR_RO(firmware_version, "PDFW"); ++STEAMDECK_ATTR_RO(board_id, "BOID"); ++STEAMDECK_ATTR_RO(pdcs, "PDCS"); ++ ++static umode_t ++steamdeck_is_visible(struct kobject *kobj, struct attribute *attr, int index) ++{ ++ return attr->mode; ++} ++ ++static struct attribute *steamdeck_attributes[] = { ++ &dev_attr_target_cpu_temp.attr, ++ &dev_attr_gain.attr, ++ &dev_attr_ramp_rate.attr, ++ &dev_attr_hysteresis.attr, ++ &dev_attr_maximum_battery_charge_rate.attr, ++ &dev_attr_recalculate.attr, ++ &dev_attr_power_cycle_display.attr, ++ ++ &dev_attr_led_brightness.attr, ++ &dev_attr_content_adaptive_brightness.attr, ++ &dev_attr_gamma_set.attr, ++ &dev_attr_display_brightness.attr, ++ &dev_attr_ctrl_display.attr, ++ &dev_attr_cabc_minimum_brightness.attr, ++ &dev_attr_memory_data_access_control.attr, ++ ++ &dev_attr_display_normal_mode_on.attr, ++ &dev_attr_display_inversion_off.attr, ++ &dev_attr_display_inversion_on.attr, ++ &dev_attr_idle_mode_on.attr, ++ ++ &dev_attr_firmware_version.attr, ++ &dev_attr_board_id.attr, ++ &dev_attr_pdcs.attr, ++ ++ NULL ++}; ++ ++static const struct attribute_group steamdeck_group = { ++ .attrs = steamdeck_attributes, ++ .is_visible = steamdeck_is_visible, ++}; ++ ++static const struct attribute_group *steamdeck_groups[] = { ++ &steamdeck_group, ++ NULL ++}; ++ ++static int steamdeck_read_fan_speed(struct steamdeck *jup, long *speed) ++{ ++ unsigned long long val; ++ ++ if (ACPI_FAILURE(acpi_evaluate_integer(jup->adev->handle, ++ "FANR", NULL, &val))) ++ return -EIO; ++ ++ *speed = val; ++ return 0; ++} ++ ++static int ++steamdeck_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *out) ++{ ++ struct steamdeck *sd = dev_get_drvdata(dev); ++ unsigned long long val; ++ ++ switch (type) { ++ case hwmon_temp: ++ if (attr != hwmon_temp_input) ++ return -EOPNOTSUPP; ++ ++ if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle, ++ "BATT", NULL, &val))) ++ return -EIO; ++ /* ++ * Assuming BATT returns deg C we need to mutiply it ++ * by 1000 to convert to mC ++ */ ++ *out = val * 1000; ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: ++ return steamdeck_read_fan_speed(sd, out); ++ case hwmon_fan_target: ++ *out = sd->fan_target; ++ break; ++ case hwmon_fan_fault: ++ if (ACPI_FAILURE(acpi_evaluate_integer( ++ sd->adev->handle, ++ "FANC", NULL, &val))) ++ return -EIO; ++ /* ++ * FANC (Fan check): ++ * 0: Abnormal ++ * 1: Normal ++ */ ++ *out = !val; ++ 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) { ++ 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 *sd = dev_get_drvdata(dev); ++ ++ if (type != hwmon_fan || ++ attr != hwmon_fan_target) ++ return -EOPNOTSUPP; ++ ++ if (val > U16_MAX) ++ return -EINVAL; ++ ++ sd->fan_target = val; ++ ++ 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_info[] = { ++ 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_chip_info = { ++ .ops = &steamdeck_hwmon_ops, ++ .info = steamdeck_info, ++}; ++ ++#define STEAMDECK_STA_OK \ ++ (ACPI_STA_DEVICE_ENABLED | \ ++ ACPI_STA_DEVICE_PRESENT | \ ++ ACPI_STA_DEVICE_FUNCTIONING) ++ ++static int ++steamdeck_ddic_reg_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ union acpi_object obj = { .type = ACPI_TYPE_INTEGER }; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &obj, }; ++ struct steamdeck *sd = context; ++ unsigned long long _val; ++ ++ obj.integer.value = reg; ++ ++ if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle, ++ "RDDI", &arg_list, &_val))) ++ return -EIO; ++ ++ *val = _val; ++ return 0; ++} ++ ++static int steamdeck_read_pdcs(struct steamdeck *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 *sd = ++ container_of(work, struct steamdeck, 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; ++ ++ WARN_ON(extcon_set_state_sync(sd->edev, EXTCON_USB_HOST, ++ usb_host)); ++ dev_dbg(sd->dev, "USB role is %s\n", usb_host ? "host" : "device"); ++} ++ ++static void steamdeck_notify(acpi_handle handle, u32 event, void *context) ++{ ++ struct device *dev = context; ++ struct steamdeck *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_err(dev, "Unsupported event [0x%x]\n", event); ++ } ++} ++ ++static void steamdeck_remove_notify_handler(void *data) ++{ ++ struct steamdeck *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_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct steamdeck *sd; ++ acpi_status status; ++ unsigned long long sta; ++ int ret; ++ ++ static const struct regmap_config regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 255, ++ .cache_type = REGCACHE_NONE, ++ .reg_read = steamdeck_ddic_reg_read, ++ }; ++ ++ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); ++ if (!sd) ++ return -ENOMEM; ++ sd->adev = ACPI_COMPANION(&pdev->dev); ++ sd->dev = dev; ++ platform_set_drvdata(pdev, sd); ++ INIT_DELAYED_WORK(&sd->role_work, steamdeck_usb_role_work); ++ ++ 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; ++ } ++ ++ /* ++ * Our ACPI interface doesn't expose a method to read current ++ * fan target, so we use current fan speed as an ++ * approximation. ++ */ ++ if (steamdeck_read_fan_speed(sd, &sd->fan_target)) ++ dev_warn(dev, "Failed to read fan speed"); ++ ++ sd->hwmon = devm_hwmon_device_register_with_info(dev, ++ "steamdeck", ++ sd, ++ &steamdeck_chip_info, ++ steamdeck_groups); ++ if (IS_ERR(sd->hwmon)) { ++ dev_err(dev, "Failed to register HWMON device"); ++ return PTR_ERR(sd->hwmon); ++ } ++ ++ sd->regmap = devm_regmap_init(dev, NULL, sd, ®map_config); ++ if (IS_ERR(sd->regmap)) ++ dev_err(dev, "Failed to register REGMAP"); ++ ++ sd->edev = devm_extcon_dev_allocate(dev, steamdeck_extcon_cable); ++ if (IS_ERR(sd->edev)) ++ return -ENOMEM; ++ ++ 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 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 ACPI platform driver"); ++MODULE_LICENSE("GPL"); +diff '--color=auto' -uraN a/drivers/usb/core/config.c b/drivers/usb/core/config.c +--- a/drivers/usb/core/config.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/usb/core/config.c 2023-11-04 16:36:22.128419484 +0300 +@@ -19,6 +19,149 @@ + #define USB_MAXCONFIG 8 /* Arbitrary limit */ + + ++/* A struct associated with the interrupt_interval_override module parameter, representing ++ an user's choice to force a specific interrupt interval upon all interrupt endpoints of ++ a certain device. */ ++struct interrupt_interval_override { ++ /* The vendor ID of the device of which the interrupt interval shall be overridden */ ++ u16 vendor; ++ /* The product ID of the device of which the interrupt interval shall be overridden */ ++ u16 product; ++ /* The new interval measured in milliseconds that shall be given to all endpoints of type interrupt on said device */ ++ unsigned int interval; ++}; ++ ++static DEFINE_MUTEX(interrupt_interval_override_mutex); ++static char interrupt_interval_override_param[128]; ++static struct interrupt_interval_override *interrupt_interval_override_list = NULL; ++static size_t interrupt_interval_override_count = 0; ++ ++static int interrupt_interval_override_param_set(const char *value, const struct kernel_param *kp) ++{ ++ const char *p; ++ unsigned short vendor, product; ++ unsigned int interval; ++ struct interrupt_interval_override* list; ++ struct interrupt_interval_override param; ++ size_t count, max_count, i, len; ++ int err, res; ++ ++ mutex_lock(&interrupt_interval_override_mutex); ++ ++ if (!value || !*value) { ++ /* Unset the current variable. */ ++ kfree(interrupt_interval_override_list); ++ interrupt_interval_override_list = NULL; ++ interrupt_interval_override_count = 0; ++ param_set_copystring(value, kp); /* Does not fail: the empty string is short enough to fit. */ ++ mutex_unlock(&interrupt_interval_override_mutex); ++ return 0; ++ } ++ ++ /* Compute an upper bound on the amount of entries we need. */ ++ for (max_count = 1, i = 0; value[i]; i++) { ++ if (value[i] == ',') ++ max_count++; ++ } ++ ++ /* Ensure we can allocate enough memory before overwriting the global variables. */ ++ list = kcalloc(max_count, ++ sizeof(struct interrupt_interval_override), ++ GFP_KERNEL); ++ ++ if (!list) { ++ mutex_unlock(&interrupt_interval_override_mutex); ++ return -ENOMEM; ++ } ++ ++ err = param_set_copystring(value, kp); ++ if (err) { ++ kfree(list); ++ mutex_unlock(&interrupt_interval_override_mutex); ++ return err; ++ } ++ ++ /* Parse the parameter. Example of a valid parameter: 045e:00db:16,1bcf:0005:2 */ ++ for (count = 0, p = (const char*)value; p && *p;) { ++ res = sscanf(p, "%hx:%hx:%d%zn", &vendor, &product, &interval, &len); ++ ++ /* Check whether all variables (vendor, product, interval, len) were assigned. ++ %zn does not increase the assignment count, so we need to check for value 3 instead of 4. ++ %zn does not consume input either, so setting len shouldn't fail if interval has been properly set. */ ++ if (res != 3) { ++ pr_warn("Error while parsing USB interrupt interval override parameter %s.\n", value); ++ break; ++ } ++ ++ param.vendor = (u16)vendor; ++ param.product = (u16)product; ++ param.interval = interval; ++ list[count++] = param; ++ ++ p += len; ++ if (*p == ',' && *(p+1) != '\0') { ++ p++; ++ continue; ++ } else if(*p == '\0' || (*p == '\n' && *(p+1) == '\0')) { ++ break; ++ } else { ++ pr_warn("Error while parsing USB interrupt interval override parameter %s.\n", value); ++ break; ++ } ++ } ++ ++ /* Overwrite the global variables with the local ones. */ ++ kfree(interrupt_interval_override_list); ++ interrupt_interval_override_list = list; ++ interrupt_interval_override_count = count; ++ mutex_unlock(&interrupt_interval_override_mutex); ++ return 0; ++} ++ ++static const struct kernel_param_ops interrupt_interval_override_param_ops = { ++ .set = interrupt_interval_override_param_set, ++ .get = param_get_string, ++}; ++ ++static struct kparam_string interrupt_interval_override_param_string = { ++ .maxlen = sizeof(interrupt_interval_override_param), ++ .string = interrupt_interval_override_param, ++}; ++ ++device_param_cb(interrupt_interval_override, ++ &interrupt_interval_override_param_ops, ++ &interrupt_interval_override_param_string, ++ 0644); ++MODULE_PARM_DESC(interrupt_interval_override, ++ "Override the polling interval of all interrupt-type endpoints of a specific USB" ++ " device by specifying interrupt_interval_override=vendorID:productID:interval."); ++ ++/* Given an USB device, this checks whether the user has specified they want to override the interrupt ++ polling interval on all interrupt-type endpoints of said device. ++ ++ This function returns the user-desired amount of milliseconds between interrupts on said endpoint. ++ If this function returns zero, the device-requested interrupt interval should be used. */ ++static unsigned int usb_check_interrupt_interval_override(struct usb_device* udev) ++{ ++ size_t i; ++ unsigned int res; ++ u16 vendor = le16_to_cpu(udev->descriptor.idVendor); ++ u16 product = le16_to_cpu(udev->descriptor.idProduct); ++ ++ mutex_lock(&interrupt_interval_override_mutex); ++ for (i = 0; i < interrupt_interval_override_count; i++) { ++ if (interrupt_interval_override_list[i].vendor == vendor ++ && interrupt_interval_override_list[i].product == product) { ++ ++ res = interrupt_interval_override_list[i].interval; ++ mutex_unlock(&interrupt_interval_override_mutex); ++ return res; ++ } ++ } ++ mutex_unlock(&interrupt_interval_override_mutex); ++ return 0; ++} ++ + static inline const char *plural(int n) + { + return (n == 1 ? "" : "s"); +@@ -261,7 +404,7 @@ + struct usb_endpoint_descriptor *d; + struct usb_host_endpoint *endpoint; + int n, i, j, retval; +- unsigned int maxp; ++ unsigned int maxp, ival; + const unsigned short *maxpacket_maxes; + + d = (struct usb_endpoint_descriptor *) buffer; +@@ -386,6 +529,23 @@ + endpoint->desc.bInterval = n; + } + ++ /* Override the interrupt polling interval if a module parameter tells us to do so. */ ++ if (usb_endpoint_xfer_int(d)) { ++ ival = usb_check_interrupt_interval_override(udev); ++ if (ival > 0) { ++ switch (udev->speed) { ++ case USB_SPEED_SUPER_PLUS: ++ case USB_SPEED_SUPER: ++ case USB_SPEED_HIGH: ++ endpoint->desc.bInterval = fls(ival) + 3; ++ break; ++ default: /* USB_SPEED_FULL or _LOW */ ++ endpoint->desc.bInterval = ival; ++ break; ++ } ++ } ++ } ++ + /* Some buggy low-speed devices have Bulk endpoints, which is + * explicitly forbidden by the USB spec. In an attempt to make + * them usable, we will try treating them as Interrupt endpoints. +@@ -1092,3 +1252,11 @@ + usb_release_bos_descriptor(dev); + return ret; + } ++ ++void usb_release_interrupt_interval_override_list(void) ++{ ++ mutex_lock(&interrupt_interval_override_mutex); ++ kfree(interrupt_interval_override_list); ++ interrupt_interval_override_list = NULL; ++ mutex_unlock(&interrupt_interval_override_mutex); ++} +diff '--color=auto' -uraN a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +--- a/drivers/usb/core/quirks.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/usb/core/quirks.c 2023-11-04 16:36:53.548982960 +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 a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c +--- a/drivers/usb/core/usb.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/usb/core/usb.c 2023-11-04 16:36:22.128419484 +0300 +@@ -1142,6 +1142,7 @@ + return; + + usb_release_quirk_list(); ++ usb_release_interrupt_interval_override_list(); + usb_deregister_device_driver(&usb_generic_driver); + usb_major_cleanup(); + usb_deregister(&usbfs_driver); +diff '--color=auto' -uraN a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h +--- a/drivers/usb/core/usb.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/usb/core/usb.h 2023-11-04 16:36:22.128419484 +0300 +@@ -38,6 +38,7 @@ + extern void usb_detect_quirks(struct usb_device *udev); + extern void usb_detect_interface_quirks(struct usb_device *udev); + extern void usb_release_quirk_list(void); ++extern void usb_release_interrupt_interval_override_list(void); + extern bool usb_endpoint_is_ignored(struct usb_device *udev, + struct usb_host_interface *intf, + struct usb_endpoint_descriptor *epd); +diff '--color=auto' -uraN a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c +--- a/drivers/usb/gadget/function/f_hid.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/drivers/usb/gadget/function/f_hid.c 2023-11-04 16:36:53.572316712 +0300 +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + + #include "u_f.h" + #include "u_hid.h" +@@ -75,6 +76,18 @@ + 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; ++ 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 +536,114 @@ + 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) ++{ ++ 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_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: ++ return -ENOTTY; ++ } ++} ++ + static __poll_t f_hidg_poll(struct file *file, poll_table *wait) + { + struct f_hidg *hidg = file->private_data; +@@ -530,6 +651,7 @@ + + 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; +@@ -542,12 +664,17 @@ + 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) + { +@@ -591,11 +718,19 @@ + + 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->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); ++ wake_up(&hidg->read_queue); ++ } + break; + default: + ERROR(cdev, "Set report failed %d\n", req->status); +@@ -640,6 +775,10 @@ + 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 +786,8 @@ + 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 +800,29 @@ + 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): +@@ -687,12 +843,27 @@ + 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): +@@ -792,6 +963,14 @@ + 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); +@@ -800,6 +979,14 @@ + + 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 +1095,7 @@ + .write = f_hidg_write, + .read = f_hidg_read, + .poll = f_hidg_poll, ++ .unlocked_ioctl = f_hidg_ioctl, + .llseek = noop_llseek, + }; + +@@ -918,6 +1106,14 @@ + 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,9 +1199,14 @@ + 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); +@@ -1021,6 +1222,8 @@ + 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 '--color=auto' -uraN a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c +--- a/fs/btrfs/extent-tree.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/fs/btrfs/extent-tree.c 2023-11-04 16:35:57.827983725 +0300 +@@ -3481,7 +3481,6 @@ + * Helper function for find_free_extent(). + * + * Return -ENOENT to inform caller that we need fallback to unclustered mode. +- * Return -EAGAIN to inform caller that we need to re-search this block group + * Return >0 to inform caller that we find nothing + * Return 0 means we have found a location and set ffe_ctl->found_offset. + */ +@@ -3562,14 +3561,6 @@ + trace_btrfs_reserve_extent_cluster(bg, ffe_ctl); + return 0; + } +- } else if (!ffe_ctl->cached && ffe_ctl->loop > LOOP_CACHING_NOWAIT && +- !ffe_ctl->retry_clustered) { +- spin_unlock(&last_ptr->refill_lock); +- +- ffe_ctl->retry_clustered = true; +- btrfs_wait_block_group_cache_progress(bg, ffe_ctl->num_bytes + +- ffe_ctl->empty_cluster + ffe_ctl->empty_size); +- return -EAGAIN; + } + /* + * At this point we either didn't find a cluster or we weren't able to +@@ -3584,7 +3575,6 @@ + /* + * Return >0 to inform caller that we find nothing + * Return 0 when we found an free extent and set ffe_ctrl->found_offset +- * Return -EAGAIN to inform caller that we need to re-search this block group + */ + static int find_free_extent_unclustered(struct btrfs_block_group *bg, + struct find_free_extent_ctl *ffe_ctl) +@@ -3622,25 +3612,8 @@ + offset = btrfs_find_space_for_alloc(bg, ffe_ctl->search_start, + ffe_ctl->num_bytes, ffe_ctl->empty_size, + &ffe_ctl->max_extent_size); +- +- /* +- * If we didn't find a chunk, and we haven't failed on this block group +- * before, and this block group is in the middle of caching and we are +- * ok with waiting, then go ahead and wait for progress to be made, and +- * set @retry_unclustered to true. +- * +- * If @retry_unclustered is true then we've already waited on this +- * block group once and should move on to the next block group. +- */ +- if (!offset && !ffe_ctl->retry_unclustered && !ffe_ctl->cached && +- ffe_ctl->loop > LOOP_CACHING_NOWAIT) { +- btrfs_wait_block_group_cache_progress(bg, ffe_ctl->num_bytes + +- ffe_ctl->empty_size); +- ffe_ctl->retry_unclustered = true; +- return -EAGAIN; +- } else if (!offset) { ++ if (!offset) + return 1; +- } + ffe_ctl->found_offset = offset; + return 0; + } +@@ -3654,7 +3627,7 @@ + /* We want to try and use the cluster allocator, so lets look there */ + if (ffe_ctl->last_ptr && ffe_ctl->use_cluster) { + ret = find_free_extent_clustered(block_group, ffe_ctl, bg_ret); +- if (ret >= 0 || ret == -EAGAIN) ++ if (ret >= 0) + return ret; + /* ret == -ENOENT case falls through */ + } +@@ -3873,8 +3846,7 @@ + { + switch (ffe_ctl->policy) { + case BTRFS_EXTENT_ALLOC_CLUSTERED: +- ffe_ctl->retry_clustered = false; +- ffe_ctl->retry_unclustered = false; ++ ffe_ctl->retry_uncached = false; + break; + case BTRFS_EXTENT_ALLOC_ZONED: + /* Nothing to do */ +@@ -4225,9 +4197,7 @@ + ffe_ctl->orig_have_caching_bg = false; + ffe_ctl->index = btrfs_bg_flags_to_raid_index(ffe_ctl->flags); + ffe_ctl->loop = 0; +- /* For clustered allocation */ +- ffe_ctl->retry_clustered = false; +- ffe_ctl->retry_unclustered = false; ++ ffe_ctl->retry_uncached = false; + ffe_ctl->cached = 0; + ffe_ctl->max_extent_size = 0; + ffe_ctl->total_free_space = 0; +@@ -4378,16 +4348,12 @@ + + bg_ret = NULL; + ret = do_allocation(block_group, ffe_ctl, &bg_ret); +- if (ret == 0) { +- if (bg_ret && bg_ret != block_group) { +- btrfs_release_block_group(block_group, +- ffe_ctl->delalloc); +- block_group = bg_ret; +- } +- } else if (ret == -EAGAIN) { +- goto have_block_group; +- } else if (ret > 0) { ++ if (ret > 0) + goto loop; ++ ++ if (bg_ret && bg_ret != block_group) { ++ btrfs_release_block_group(block_group, ffe_ctl->delalloc); ++ block_group = bg_ret; + } + + /* Checks */ +@@ -4428,6 +4394,15 @@ + btrfs_release_block_group(block_group, ffe_ctl->delalloc); + break; + loop: ++ if (!ffe_ctl->cached && ffe_ctl->loop > LOOP_CACHING_NOWAIT && ++ !ffe_ctl->retry_uncached) { ++ ffe_ctl->retry_uncached = true; ++ btrfs_wait_block_group_cache_progress(block_group, ++ ffe_ctl->num_bytes + ++ ffe_ctl->empty_cluster + ++ ffe_ctl->empty_size); ++ goto have_block_group; ++ } + release_block_group(block_group, ffe_ctl, ffe_ctl->delalloc); + cond_resched(); + } +diff '--color=auto' -uraN a/fs/btrfs/extent-tree.h b/fs/btrfs/extent-tree.h +--- a/fs/btrfs/extent-tree.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/fs/btrfs/extent-tree.h 2023-11-04 16:35:57.827983725 +0300 +@@ -48,16 +48,11 @@ + int loop; + + /* +- * Whether we're refilling a cluster, if true we need to re-search +- * current block group but don't try to refill the cluster again. ++ * Set to true if we're retrying the allocation on this block group ++ * after waiting for caching progress, this is so that we retry only ++ * once before moving on to another block group. + */ +- bool retry_clustered; +- +- /* +- * Whether we're updating free space cache, if true we need to re-search +- * current block group but don't try updating free space cache again. +- */ +- bool retry_unclustered; ++ bool retry_uncached; + + /* If current block group is cached */ + int cached; +diff '--color=auto' -uraN a/fs/proc/base.c b/fs/proc/base.c +--- a/fs/proc/base.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/fs/proc/base.c 2023-11-04 16:35:57.837983905 +0300 +@@ -3207,6 +3207,7 @@ + mm = get_task_mm(task); + if (mm) { + seq_printf(m, "ksm_rmap_items %lu\n", mm->ksm_rmap_items); ++ seq_printf(m, "ksm_zero_pages %lu\n", mm->ksm_zero_pages); + seq_printf(m, "ksm_merging_pages %lu\n", mm->ksm_merging_pages); + seq_printf(m, "ksm_process_profit %ld\n", ksm_process_profit(mm)); + mmput(mm); +diff '--color=auto' -uraN a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h +--- a/include/acpi/cppc_acpi.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/acpi/cppc_acpi.h 2023-11-04 16:35:57.801316580 +0300 +@@ -139,6 +139,7 @@ + #ifdef CONFIG_ACPI_CPPC_LIB + extern int cppc_get_desired_perf(int cpunum, u64 *desired_perf); + extern int cppc_get_nominal_perf(int cpunum, u64 *nominal_perf); ++extern int cppc_get_highest_perf(int cpunum, u64 *highest_perf); + extern int cppc_get_perf_ctrs(int cpu, struct cppc_perf_fb_ctrs *perf_fb_ctrs); + extern int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls); + extern int cppc_set_enable(int cpu, bool enable); +@@ -165,6 +166,10 @@ + { + return -ENOTSUPP; + } ++static inline int cppc_get_highest_perf(int cpunum, u64 *highest_perf) ++{ ++ return -ENOTSUPP; ++} + static inline int cppc_get_perf_ctrs(int cpu, struct cppc_perf_fb_ctrs *perf_fb_ctrs) + { + return -ENOTSUPP; +diff '--color=auto' -uraN a/include/drm/drm_mode_object.h b/include/drm/drm_mode_object.h +--- a/include/drm/drm_mode_object.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/drm/drm_mode_object.h 2023-11-04 16:35:57.794649794 +0300 +@@ -60,7 +60,7 @@ + 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 '--color=auto' -uraN a/include/drm/drm_plane.h b/include/drm/drm_plane.h +--- a/include/drm/drm_plane.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/drm/drm_plane.h 2023-11-04 16:35:57.794649794 +0300 +@@ -237,6 +237,13 @@ + + /** @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 '--color=auto' -uraN a/include/drm/drm_property.h b/include/drm/drm_property.h +--- a/include/drm/drm_property.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/drm/drm_property.h 2023-11-04 16:35:57.794649794 +0300 +@@ -279,6 +279,12 @@ + 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 '--color=auto' -uraN a/include/linux/amd-pstate.h b/include/linux/amd-pstate.h +--- a/include/linux/amd-pstate.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/amd-pstate.h 2023-11-04 16:35:57.801316580 +0300 +@@ -39,11 +39,16 @@ + * @cppc_req_cached: cached performance request hints + * @highest_perf: the maximum performance an individual processor may reach, + * assuming ideal conditions ++ * For platforms that do not support the preferred core feature, the ++ * highest_pef may be configured with 166 or 255, to avoid max frequency ++ * calculated wrongly. we take the fixed value as the highest_perf. + * @nominal_perf: the maximum sustained performance level of the processor, + * assuming ideal operating conditions + * @lowest_nonlinear_perf: the lowest performance level at which nonlinear power + * savings are achieved + * @lowest_perf: the absolute lowest performance level of the processor ++ * @prefcore_ranking: the preferred core ranking, the higher value indicates a higher ++ * priority. + * @max_freq: the frequency that mapped to highest_perf + * @min_freq: the frequency that mapped to lowest_perf + * @nominal_freq: the frequency that mapped to nominal_perf +@@ -52,6 +57,9 @@ + * @prev: Last Aperf/Mperf/tsc count value read from register + * @freq: current cpu frequency value + * @boost_supported: check whether the Processor or SBIOS supports boost mode ++ * @hw_prefcore: check whether HW supports preferred core featue. ++ * Only when hw_prefcore and early prefcore param are true, ++ * AMD P-State driver supports preferred core featue. + * @epp_policy: Last saved policy used to set energy-performance preference + * @epp_cached: Cached CPPC energy-performance preference value + * @policy: Cpufreq policy value +@@ -70,6 +78,7 @@ + u32 nominal_perf; + u32 lowest_nonlinear_perf; + u32 lowest_perf; ++ u32 prefcore_ranking; + + u32 max_freq; + u32 min_freq; +@@ -81,6 +90,7 @@ + + u64 freq; + bool boost_supported; ++ bool hw_prefcore; + + /* EPP feature related attributes*/ + s16 epp_policy; +diff '--color=auto' -uraN a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/cpufreq.h 2023-11-04 16:35:57.801316580 +0300 +@@ -231,6 +231,7 @@ + void refresh_frequency_limits(struct cpufreq_policy *policy); + void cpufreq_update_policy(unsigned int cpu); + void cpufreq_update_limits(unsigned int cpu); ++void cpufreq_update_highest_perf(unsigned int cpu); + bool have_governor_per_policy(void); + bool cpufreq_supports_freq_invariance(void); + struct kobject *get_governor_parent_kobj(struct cpufreq_policy *policy); +@@ -259,6 +260,7 @@ + return false; + } + static inline void disable_cpufreq(void) { } ++static inline void cpufreq_update_highest_perf(unsigned int cpu) { } + #endif + + #ifdef CONFIG_CPU_FREQ_STAT +@@ -376,6 +378,9 @@ + /* Called to update policy limits on firmware notifications. */ + void (*update_limits)(unsigned int cpu); + ++ /* Called to update highest performance on firmware notifications. */ ++ void (*update_highest_perf)(unsigned int cpu); ++ + /* optional */ + int (*bios_limit)(int cpu, unsigned int *limit); + +diff '--color=auto' -uraN a/include/linux/ksm.h b/include/linux/ksm.h +--- a/include/linux/ksm.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/ksm.h 2023-11-04 16:35:57.837983905 +0300 +@@ -26,6 +26,22 @@ + + int __ksm_enter(struct mm_struct *mm); + void __ksm_exit(struct mm_struct *mm); ++/* ++ * To identify zeropages that were mapped by KSM, we reuse the dirty bit ++ * in the PTE. If the PTE is dirty, the zeropage was mapped by KSM when ++ * deduplicating memory. ++ */ ++#define is_ksm_zero_pte(pte) (is_zero_pfn(pte_pfn(pte)) && pte_dirty(pte)) ++ ++extern unsigned long ksm_zero_pages; ++ ++static inline void ksm_might_unmap_zero_page(struct mm_struct *mm, pte_t pte) ++{ ++ if (is_ksm_zero_pte(pte)) { ++ ksm_zero_pages--; ++ mm->ksm_zero_pages--; ++ } ++} + + static inline int ksm_fork(struct mm_struct *mm, struct mm_struct *oldmm) + { +@@ -95,6 +111,10 @@ + { + } + ++static inline void ksm_might_unmap_zero_page(struct mm_struct *mm, pte_t pte) ++{ ++} ++ + #ifdef CONFIG_MEMORY_FAILURE + static inline void collect_procs_ksm(struct page *page, + struct list_head *to_kill, int force_early) +diff '--color=auto' -uraN a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +--- a/include/linux/mfd/tps68470.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/mfd/tps68470.h 2023-11-04 16:36:53.558983140 +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 a/include/linux/mm.h b/include/linux/mm.h +--- a/include/linux/mm.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/mm.h 2023-11-04 16:35:57.817983546 +0300 +@@ -191,7 +191,7 @@ + * that. + */ + #define MAPCOUNT_ELF_CORE_MARGIN (5) +-#define DEFAULT_MAX_MAP_COUNT (USHRT_MAX - MAPCOUNT_ELF_CORE_MARGIN) ++#define DEFAULT_MAX_MAP_COUNT (INT_MAX - MAPCOUNT_ELF_CORE_MARGIN) + + extern int sysctl_max_map_count; + +diff '--color=auto' -uraN a/include/linux/mm_types.h b/include/linux/mm_types.h +--- a/include/linux/mm_types.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/mm_types.h 2023-11-04 16:35:57.837983905 +0300 +@@ -812,7 +812,7 @@ + #ifdef CONFIG_KSM + /* + * Represent how many pages of this process are involved in KSM +- * merging. ++ * merging (not including ksm_zero_pages). + */ + unsigned long ksm_merging_pages; + /* +@@ -820,7 +820,12 @@ + * including merged and not merged. + */ + unsigned long ksm_rmap_items; +-#endif ++ /* ++ * Represent how many empty pages are merged with kernel zero ++ * pages when enabling KSM use_zero_pages. ++ */ ++ unsigned long ksm_zero_pages; ++#endif /* CONFIG_KSM */ + #ifdef CONFIG_LRU_GEN + struct { + /* this mm_struct is on lru_gen_mm_list */ +diff '--color=auto' -uraN a/include/linux/pageblock-flags.h b/include/linux/pageblock-flags.h +--- a/include/linux/pageblock-flags.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/pageblock-flags.h 2023-11-04 16:35:57.827983725 +0300 +@@ -48,7 +48,7 @@ + #else /* CONFIG_HUGETLB_PAGE */ + + /* If huge pages are not used, group by MAX_ORDER_NR_PAGES */ +-#define pageblock_order MAX_ORDER ++#define pageblock_order PAGE_ALLOC_COSTLY_ORDER + + #endif /* CONFIG_HUGETLB_PAGE */ + +diff '--color=auto' -uraN a/include/linux/pagemap.h b/include/linux/pagemap.h +--- a/include/linux/pagemap.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/pagemap.h 2023-11-04 16:35:57.817983546 +0300 +@@ -1181,7 +1181,7 @@ + ._index = i, \ + } + +-#define VM_READAHEAD_PAGES (SZ_128K / PAGE_SIZE) ++#define VM_READAHEAD_PAGES (SZ_8M / PAGE_SIZE) + + void page_cache_ra_unbounded(struct readahead_control *, + unsigned long nr_to_read, unsigned long lookahead_count); +diff '--color=auto' -uraN a/include/linux/pci.h b/include/linux/pci.h +--- a/include/linux/pci.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/pci.h 2023-11-04 16:36:53.552316353 +0300 +@@ -464,6 +464,7 @@ + unsigned int no_vf_scan:1; /* Don't scan for VFs after IOV enablement */ + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ ++ 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 a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +--- a/include/linux/platform_data/x86/asus-wmi.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/platform_data/x86/asus-wmi.h 2023-11-04 16:36:53.528982602 +0300 +@@ -58,6 +58,10 @@ + #define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 + #define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* ?? */ + #define ASUS_WMI_DEVID_LIGHTBAR 0x00050025 ++/* This can only be used to disable the screen, not re-enable */ ++#define ASUS_WMI_DEVID_SCREENPAD_POWER 0x00050031 ++/* Writing a brightness re-enables the screen if disabled */ ++#define ASUS_WMI_DEVID_SCREENPAD_LIGHT 0x00050032 + #define ASUS_WMI_DEVID_FAN_BOOST_MODE 0x00110018 + #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY 0x00120075 + +@@ -66,6 +70,7 @@ + #define ASUS_WMI_DEVID_CAMERA 0x00060013 + #define ASUS_WMI_DEVID_LID_FLIP 0x00060062 + #define ASUS_WMI_DEVID_LID_FLIP_ROG 0x00060077 ++#define ASUS_WMI_DEVID_MINI_LED_MODE 0x0005001E + + /* Storage */ + #define ASUS_WMI_DEVID_CARDREADER 0x00080013 +@@ -80,8 +85,19 @@ + #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */ + #define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013 + #define ASUS_WMI_DEVID_GPU_FAN_CTRL 0x00110014 ++#define ASUS_WMI_DEVID_MID_FAN_CTRL 0x00110031 + #define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024 + #define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025 ++#define ASUS_WMI_DEVID_MID_FAN_CURVE 0x00110032 ++ ++/* Tunables for AUS ROG laptops */ ++#define ASUS_WMI_DEVID_PPT_PL2_SPPT 0x001200A0 ++#define ASUS_WMI_DEVID_PPT_PL1_SPL 0x001200A3 ++#define ASUS_WMI_DEVID_PPT_APU_SPPT 0x001200B0 ++#define ASUS_WMI_DEVID_PPT_PLAT_SPPT 0x001200B1 ++#define ASUS_WMI_DEVID_PPT_FPPT 0x001200C1 ++#define ASUS_WMI_DEVID_NV_DYN_BOOST 0x001200C0 ++#define ASUS_WMI_DEVID_NV_THERM_TARGET 0x001200C2 + + /* Power */ + #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 +@@ -95,7 +111,12 @@ + /* Keyboard dock */ + #define ASUS_WMI_DEVID_KBD_DOCK 0x00120063 + +-/* dgpu on/off */ ++/* Charging mode - 1=Barrel, 2=USB */ ++#define ASUS_WMI_DEVID_CHARGE_MODE 0x0012006C ++ ++/* epu is connected? 1 == true */ ++#define ASUS_WMI_DEVID_EGPU_CONNECTED 0x00090018 ++/* egpu on/off */ + #define ASUS_WMI_DEVID_EGPU 0x00090019 + + /* dgpu on/off */ +diff '--color=auto' -uraN a/include/linux/rbtree_augmented.h b/include/linux/rbtree_augmented.h +--- a/include/linux/rbtree_augmented.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/rbtree_augmented.h 2023-11-04 16:36:05.668124315 +0300 +@@ -60,6 +60,32 @@ + rb_insert_augmented(node, &root->rb_root, augment); + } + ++static __always_inline struct rb_node * ++rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, ++ bool (*less)(struct rb_node *, const struct rb_node *), ++ const struct rb_augment_callbacks *augment) ++{ ++ struct rb_node **link = &tree->rb_root.rb_node; ++ struct rb_node *parent = NULL; ++ bool leftmost = true; ++ ++ while (*link) { ++ parent = *link; ++ if (less(node, parent)) { ++ link = &parent->rb_left; ++ } else { ++ link = &parent->rb_right; ++ leftmost = false; ++ } ++ } ++ ++ rb_link_node(node, parent, link); ++ augment->propagate(parent, NULL); /* suboptimal */ ++ rb_insert_augmented_cached(node, tree, leftmost, augment); ++ ++ return leftmost ? node : NULL; ++} ++ + /* + * Template for declaring augmented rbtree callbacks (generic case) + * +diff '--color=auto' -uraN a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/sched.h 2023-11-04 16:36:09.578194431 +0300 +@@ -545,17 +545,46 @@ + #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; + struct rb_node run_node; ++ u64 deadline; ++ u64 min_deadline; ++ + struct list_head group_node; + unsigned int on_rq; + + u64 exec_start; + u64 sum_exec_runtime; +- u64 vruntime; + 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; ++#endif // CONFIG_SCHED_BORE ++ s64 vlag; ++ u64 slice; + + u64 nr_migrations; + +@@ -785,6 +814,7 @@ + int static_prio; + int normal_prio; + unsigned int rt_priority; ++ int latency_prio; + + struct sched_entity se; + struct sched_rt_entity rt; +@@ -984,6 +1014,13 @@ + 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 '--color=auto' -uraN a/include/linux/syscalls.h b/include/linux/syscalls.h +--- a/include/linux/syscalls.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/syscalls.h 2023-11-04 16:35:57.837983905 +0300 +@@ -813,6 +813,9 @@ + asmlinkage long sys_process_madvise(int pidfd, const struct iovec __user *vec, + size_t vlen, int behavior, unsigned int flags); + asmlinkage long sys_process_mrelease(int pidfd, unsigned int flags); ++asmlinkage long sys_process_ksm_enable(int pidfd, unsigned int flags); ++asmlinkage long sys_process_ksm_disable(int pidfd, unsigned int flags); ++asmlinkage long sys_process_ksm_status(int pidfd, unsigned int flags); + asmlinkage long sys_remap_file_pages(unsigned long start, unsigned long size, + unsigned long prot, unsigned long pgoff, + unsigned long flags); +diff '--color=auto' -uraN a/include/linux/tcp.h b/include/linux/tcp.h +--- a/include/linux/tcp.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/tcp.h 2023-11-04 16:35:57.801316580 +0300 +@@ -255,7 +255,9 @@ + u8 compressed_ack; + u8 dup_ack_counter:2, + tlp_retrans:1, /* TLP is a retransmission */ +- unused:5; ++ fast_ack_mode:2, /* which fast ack mode ? */ ++ tlp_orig_data_app_limited:1, /* app-limited before TLP rtx? */ ++ unused:2; + u32 chrono_start; /* Start time in jiffies of a TCP chrono */ + u32 chrono_stat[3]; /* Time in jiffies for chrono_stat stats */ + u8 chrono_type:2, /* current chronograph type */ +diff '--color=auto' -uraN a/include/linux/user_namespace.h b/include/linux/user_namespace.h +--- a/include/linux/user_namespace.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/user_namespace.h 2023-11-04 16:35:57.817983546 +0300 +@@ -148,6 +148,8 @@ + + #ifdef CONFIG_USER_NS + ++extern int unprivileged_userns_clone; ++ + static inline struct user_namespace *get_user_ns(struct user_namespace *ns) + { + if (ns) +@@ -181,6 +183,8 @@ + struct ns_common *ns_get_owner(struct ns_common *ns); + #else + ++#define unprivileged_userns_clone 0 ++ + static inline struct user_namespace *get_user_ns(struct user_namespace *ns) + { + return &init_user_ns; +diff '--color=auto' -uraN a/include/linux/zstd.h b/include/linux/zstd.h +--- a/include/linux/zstd.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/zstd.h 2023-11-04 16:35:57.841317298 +0300 +@@ -1,6 +1,6 @@ + /* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/include/linux/zstd_errors.h b/include/linux/zstd_errors.h +--- a/include/linux/zstd_errors.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/zstd_errors.h 2023-11-04 16:35:57.841317298 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -17,8 +18,17 @@ + + + /* ===== ZSTDERRORLIB_API : control library symbols visibility ===== */ +-#define ZSTDERRORLIB_VISIBILITY +-#define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBILITY ++#define ZSTDERRORLIB_VISIBLE ++ ++#ifndef ZSTDERRORLIB_HIDDEN ++# if (__GNUC__ >= 4) && !defined(__MINGW32__) ++# define ZSTDERRORLIB_HIDDEN __attribute__ ((visibility ("hidden"))) ++# else ++# define ZSTDERRORLIB_HIDDEN ++# endif ++#endif ++ ++#define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBLE + + /*-********************************************* + * Error codes list +@@ -43,14 +53,17 @@ + ZSTD_error_frameParameter_windowTooLarge = 16, + ZSTD_error_corruption_detected = 20, + ZSTD_error_checksum_wrong = 22, ++ ZSTD_error_literals_headerWrong = 24, + ZSTD_error_dictionary_corrupted = 30, + ZSTD_error_dictionary_wrong = 32, + ZSTD_error_dictionaryCreation_failed = 34, + ZSTD_error_parameter_unsupported = 40, ++ ZSTD_error_parameter_combination_unsupported = 41, + ZSTD_error_parameter_outOfBound = 42, + ZSTD_error_tableLog_tooLarge = 44, + ZSTD_error_maxSymbolValue_tooLarge = 46, + ZSTD_error_maxSymbolValue_tooSmall = 48, ++ ZSTD_error_stabilityCondition_notRespected = 50, + ZSTD_error_stage_wrong = 60, + ZSTD_error_init_missing = 62, + ZSTD_error_memory_allocation = 64, +@@ -58,11 +71,15 @@ + ZSTD_error_dstSize_tooSmall = 70, + ZSTD_error_srcSize_wrong = 72, + ZSTD_error_dstBuffer_null = 74, ++ ZSTD_error_noForwardProgress_destFull = 80, ++ ZSTD_error_noForwardProgress_inputEmpty = 82, + /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */ + ZSTD_error_frameIndex_tooLarge = 100, + ZSTD_error_seekableIO = 102, + ZSTD_error_dstBuffer_wrong = 104, + ZSTD_error_srcBuffer_wrong = 105, ++ ZSTD_error_sequenceProducer_failed = 106, ++ ZSTD_error_externalSequences_invalid = 107, + ZSTD_error_maxCode = 120 /* never EVER use this value directly, it can change in future versions! Use ZSTD_isError() instead */ + } ZSTD_ErrorCode; + +diff '--color=auto' -uraN a/include/linux/zstd_lib.h b/include/linux/zstd_lib.h +--- a/include/linux/zstd_lib.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/linux/zstd_lib.h 2023-11-04 16:35:57.841317298 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -11,23 +12,42 @@ + #ifndef ZSTD_H_235446 + #define ZSTD_H_235446 + +-/* ====== Dependency ======*/ ++/* ====== Dependencies ======*/ + #include /* INT_MAX */ + #include /* size_t */ + + + /* ===== ZSTDLIB_API : control library symbols visibility ===== */ +-#ifndef ZSTDLIB_VISIBLE ++#define ZSTDLIB_VISIBLE ++ ++#ifndef ZSTDLIB_HIDDEN + # if (__GNUC__ >= 4) && !defined(__MINGW32__) +-# define ZSTDLIB_VISIBLE __attribute__ ((visibility ("default"))) + # define ZSTDLIB_HIDDEN __attribute__ ((visibility ("hidden"))) + # else +-# define ZSTDLIB_VISIBLE + # define ZSTDLIB_HIDDEN + # endif + #endif ++ + #define ZSTDLIB_API ZSTDLIB_VISIBLE + ++/* Deprecation warnings : ++ * Should these warnings be a problem, it is generally possible to disable them, ++ * typically with -Wno-deprecated-declarations for gcc or _CRT_SECURE_NO_WARNINGS in Visual. ++ * Otherwise, it's also possible to define ZSTD_DISABLE_DEPRECATE_WARNINGS. ++ */ ++#ifdef ZSTD_DISABLE_DEPRECATE_WARNINGS ++# define ZSTD_DEPRECATED(message) /* disable deprecation warnings */ ++#else ++# if (defined(GNUC) && (GNUC > 4 || (GNUC == 4 && GNUC_MINOR >= 5))) || defined(__clang__) ++# define ZSTD_DEPRECATED(message) __attribute__((deprecated(message))) ++# elif (__GNUC__ >= 3) ++# define ZSTD_DEPRECATED(message) __attribute__((deprecated)) ++# else ++# pragma message("WARNING: You need to implement ZSTD_DEPRECATED for this compiler") ++# define ZSTD_DEPRECATED(message) ++# endif ++#endif /* ZSTD_DISABLE_DEPRECATE_WARNINGS */ ++ + + /* ***************************************************************************** + Introduction +@@ -65,7 +85,7 @@ + /*------ Version ------*/ + #define ZSTD_VERSION_MAJOR 1 + #define ZSTD_VERSION_MINOR 5 +-#define ZSTD_VERSION_RELEASE 2 ++#define ZSTD_VERSION_RELEASE 5 + #define ZSTD_VERSION_NUMBER (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE) + + /*! ZSTD_versionNumber() : +@@ -107,7 +127,8 @@ + ***************************************/ + /*! ZSTD_compress() : + * Compresses `src` content as a single zstd compressed frame into already allocated `dst`. +- * Hint : compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`. ++ * NOTE: Providing `dstCapacity >= ZSTD_compressBound(srcSize)` guarantees that zstd will have ++ * enough space to successfully compress the data. + * @return : compressed size written into `dst` (<= `dstCapacity), + * or an error code if it fails (which can be tested using ZSTD_isError()). */ + ZSTDLIB_API size_t ZSTD_compress( void* dst, size_t dstCapacity, +@@ -156,7 +177,9 @@ + * "empty", "unknown" and "error" results to the same return value (0), + * while ZSTD_getFrameContentSize() gives them separate return values. + * @return : decompressed size of `src` frame content _if known and not empty_, 0 otherwise. */ +-ZSTDLIB_API unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize); ++ZSTD_DEPRECATED("Replaced by ZSTD_getFrameContentSize") ++ZSTDLIB_API ++unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize); + + /*! ZSTD_findFrameCompressedSize() : Requires v1.4.0+ + * `src` should point to the start of a ZSTD frame or skippable frame. +@@ -168,8 +191,30 @@ + + + /*====== Helper functions ======*/ +-#define ZSTD_COMPRESSBOUND(srcSize) ((srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) /* margin, from 64 to 0 */ : 0)) /* this formula ensures that bound(A) + bound(B) <= bound(A+B) as long as A and B >= 128 KB */ +-ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize); /*!< maximum compressed size in worst case single-pass scenario */ ++/* ZSTD_compressBound() : ++ * maximum compressed size in worst case single-pass scenario. ++ * When invoking `ZSTD_compress()` or any other one-pass compression function, ++ * it's recommended to provide @dstCapacity >= ZSTD_compressBound(srcSize) ++ * as it eliminates one potential failure scenario, ++ * aka not enough room in dst buffer to write the compressed frame. ++ * Note : ZSTD_compressBound() itself can fail, if @srcSize > ZSTD_MAX_INPUT_SIZE . ++ * In which case, ZSTD_compressBound() will return an error code ++ * which can be tested using ZSTD_isError(). ++ * ++ * ZSTD_COMPRESSBOUND() : ++ * same as ZSTD_compressBound(), but as a macro. ++ * It can be used to produce constants, which can be useful for static allocation, ++ * for example to size a static array on stack. ++ * Will produce constant value 0 if srcSize too large. ++ */ ++#define ZSTD_MAX_INPUT_SIZE ((sizeof(size_t)==8) ? 0xFF00FF00FF00FF00LLU : 0xFF00FF00U) ++#define ZSTD_COMPRESSBOUND(srcSize) (((size_t)(srcSize) >= ZSTD_MAX_INPUT_SIZE) ? 0 : (srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) /* margin, from 64 to 0 */ : 0)) /* this formula ensures that bound(A) + bound(B) <= bound(A+B) as long as A and B >= 128 KB */ ++ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize); /*!< maximum compressed size in worst case single-pass scenario */ ++/* ZSTD_isError() : ++ * Most ZSTD_* functions returning a size_t value can be tested for error, ++ * using ZSTD_isError(). ++ * @return 1 if error, 0 otherwise ++ */ + ZSTDLIB_API unsigned ZSTD_isError(size_t code); /*!< tells if a `size_t` function result is an error code */ + ZSTDLIB_API const char* ZSTD_getErrorName(size_t code); /*!< provides readable string from an error code */ + ZSTDLIB_API int ZSTD_minCLevel(void); /*!< minimum negative compression level allowed, requires v1.4.0+ */ +@@ -412,6 +457,9 @@ + * ZSTD_c_validateSequences + * ZSTD_c_useBlockSplitter + * ZSTD_c_useRowMatchFinder ++ * ZSTD_c_prefetchCDictTables ++ * ZSTD_c_enableSeqProducerFallback ++ * ZSTD_c_maxBlockSize + * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them. + * note : never ever use experimentalParam? names directly; + * also, the enums values themselves are unstable and can still change. +@@ -430,7 +478,11 @@ + ZSTD_c_experimentalParam12=1009, + ZSTD_c_experimentalParam13=1010, + ZSTD_c_experimentalParam14=1011, +- ZSTD_c_experimentalParam15=1012 ++ ZSTD_c_experimentalParam15=1012, ++ ZSTD_c_experimentalParam16=1013, ++ ZSTD_c_experimentalParam17=1014, ++ ZSTD_c_experimentalParam18=1015, ++ ZSTD_c_experimentalParam19=1016 + } ZSTD_cParameter; + + typedef struct { +@@ -493,7 +545,7 @@ + * They will be used to compress next frame. + * Resetting session never fails. + * - The parameters : changes all parameters back to "default". +- * This removes any reference to any dictionary too. ++ * This also removes any reference to any dictionary or external sequence producer. + * Parameters can only be changed between 2 sessions (i.e. no compression is currently ongoing) + * otherwise the reset fails, and function returns an error value (which can be tested using ZSTD_isError()) + * - Both : similar to resetting the session, followed by resetting parameters. +@@ -506,7 +558,8 @@ + * Should cctx hold data from a previously unfinished frame, everything about it is forgotten. + * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*() + * - The function is always blocking, returns when compression is completed. +- * Hint : compression runs faster if `dstCapacity` >= `ZSTD_compressBound(srcSize)`. ++ * NOTE: Providing `dstCapacity >= ZSTD_compressBound(srcSize)` guarantees that zstd will have ++ * enough space to successfully compress the data, though it is possible it fails for other reasons. + * @return : compressed size written into `dst` (<= `dstCapacity), + * or an error code if it fails (which can be tested using ZSTD_isError()). + */ +@@ -543,13 +596,15 @@ + * ZSTD_d_stableOutBuffer + * ZSTD_d_forceIgnoreChecksum + * ZSTD_d_refMultipleDDicts ++ * ZSTD_d_disableHuffmanAssembly + * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them. + * note : never ever use experimentalParam? names directly + */ + ZSTD_d_experimentalParam1=1000, + ZSTD_d_experimentalParam2=1001, + ZSTD_d_experimentalParam3=1002, +- ZSTD_d_experimentalParam4=1003 ++ ZSTD_d_experimentalParam4=1003, ++ ZSTD_d_experimentalParam5=1004 + + } ZSTD_dParameter; + +@@ -728,8 +783,6 @@ + * This following is a legacy streaming API, available since v1.0+ . + * It can be replaced by ZSTD_CCtx_reset() and ZSTD_compressStream2(). + * It is redundant, but remains fully supported. +- * Streaming in combination with advanced parameters and dictionary compression +- * can only be used through the new API. + ******************************************************************************/ + + /*! +@@ -738,6 +791,9 @@ + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); + * ZSTD_CCtx_refCDict(zcs, NULL); // clear the dictionary (if any) + * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel); ++ * ++ * Note that ZSTD_initCStream() clears any previously set dictionary. Use the new API ++ * to compress with a dictionary. + */ + ZSTDLIB_API size_t ZSTD_initCStream(ZSTD_CStream* zcs, int compressionLevel); + /*! +@@ -788,13 +844,31 @@ + + /*===== Streaming decompression functions =====*/ + +-/* This function is redundant with the advanced API and equivalent to: ++/*! ZSTD_initDStream() : ++ * Initialize/reset DStream state for new decompression operation. ++ * Call before new decompression operation using same DStream. + * ++ * Note : This function is redundant with the advanced API and equivalent to: + * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); + * ZSTD_DCtx_refDDict(zds, NULL); + */ + ZSTDLIB_API size_t ZSTD_initDStream(ZSTD_DStream* zds); + ++/*! ZSTD_decompressStream() : ++ * Streaming decompression function. ++ * Call repetitively to consume full input updating it as necessary. ++ * Function will update both input and output `pos` fields exposing current state via these fields: ++ * - `input.pos < input.size`, some input remaining and caller should provide remaining input ++ * on the next call. ++ * - `output.pos < output.size`, decoder finished and flushed all remaining buffers. ++ * - `output.pos == output.size`, potentially uncflushed data present in the internal buffers, ++ * call ZSTD_decompressStream() again to flush remaining data to output. ++ * Note : with no additional input, amount of data flushed <= ZSTD_BLOCKSIZE_MAX. ++ * ++ * @return : 0 when a frame is completely decoded and fully flushed, ++ * or an error code, which can be tested using ZSTD_isError(), ++ * or any other value > 0, which means there is some decoding or flushing to do to complete current frame. ++ */ + ZSTDLIB_API size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input); + + ZSTDLIB_API size_t ZSTD_DStreamInSize(void); /*!< recommended size for input buffer */ +@@ -913,7 +987,7 @@ + * If @return == 0, the dictID could not be decoded. + * This could for one of the following reasons : + * - The frame does not require a dictionary to be decoded (most common case). +- * - The frame was built with dictID intentionally removed. Whatever dictionary is necessary is a hidden information. ++ * - The frame was built with dictID intentionally removed. Whatever dictionary is necessary is a hidden piece of information. + * Note : this use case also happens when using a non-conformant dictionary. + * - `srcSize` is too small, and as a result, the frame header could not be decoded (only possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`). + * - This is not a Zstandard frame. +@@ -925,9 +999,11 @@ + * Advanced dictionary and prefix API (Requires v1.4.0+) + * + * This API allows dictionaries to be used with ZSTD_compress2(), +- * ZSTD_compressStream2(), and ZSTD_decompressDCtx(). Dictionaries are sticky, and +- * only reset with the context is reset with ZSTD_reset_parameters or +- * ZSTD_reset_session_and_parameters. Prefixes are single-use. ++ * ZSTD_compressStream2(), and ZSTD_decompressDCtx(). ++ * Dictionaries are sticky, they remain valid when same context is re-used, ++ * they only reset when the context is reset ++ * with ZSTD_reset_parameters or ZSTD_reset_session_and_parameters. ++ * In contrast, Prefixes are single-use. + ******************************************************************************/ + + +@@ -937,8 +1013,9 @@ + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Special: Loading a NULL (or 0-size) dictionary invalidates previous dictionary, + * meaning "return to no-dictionary mode". +- * Note 1 : Dictionary is sticky, it will be used for all future compressed frames. +- * To return to "no-dictionary" situation, load a NULL dictionary (or reset parameters). ++ * Note 1 : Dictionary is sticky, it will be used for all future compressed frames, ++ * until parameters are reset, a new dictionary is loaded, or the dictionary ++ * is explicitly invalidated by loading a NULL dictionary. + * Note 2 : Loading a dictionary involves building tables. + * It's also a CPU consuming operation, with non-negligible impact on latency. + * Tables are dependent on compression parameters, and for this reason, +@@ -947,11 +1024,15 @@ + * Use experimental ZSTD_CCtx_loadDictionary_byReference() to reference content instead. + * In such a case, dictionary buffer must outlive its users. + * Note 4 : Use ZSTD_CCtx_loadDictionary_advanced() +- * to precisely select how dictionary content must be interpreted. */ ++ * to precisely select how dictionary content must be interpreted. ++ * Note 5 : This method does not benefit from LDM (long distance mode). ++ * If you want to employ LDM on some large dictionary content, ++ * prefer employing ZSTD_CCtx_refPrefix() described below. ++ */ + ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); + + /*! ZSTD_CCtx_refCDict() : Requires v1.4.0+ +- * Reference a prepared dictionary, to be used for all next compressed frames. ++ * Reference a prepared dictionary, to be used for all future compressed frames. + * Note that compression parameters are enforced from within CDict, + * and supersede any compression parameter previously set within CCtx. + * The parameters ignored are labelled as "superseded-by-cdict" in the ZSTD_cParameter enum docs. +@@ -970,6 +1051,7 @@ + * Decompression will need same prefix to properly regenerate data. + * Compressing with a prefix is similar in outcome as performing a diff and compressing it, + * but performs much faster, especially during decompression (compression speed is tunable with compression level). ++ * This method is compatible with LDM (long distance mode). + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Special: Adding any prefix (including NULL) invalidates any previous prefix or dictionary + * Note 1 : Prefix buffer is referenced. It **must** outlive compression. +@@ -986,9 +1068,9 @@ + const void* prefix, size_t prefixSize); + + /*! ZSTD_DCtx_loadDictionary() : Requires v1.4.0+ +- * Create an internal DDict from dict buffer, +- * to be used to decompress next frames. +- * The dictionary remains valid for all future frames, until explicitly invalidated. ++ * Create an internal DDict from dict buffer, to be used to decompress all future frames. ++ * The dictionary remains valid for all future frames, until explicitly invalidated, or ++ * a new dictionary is loaded. + * @result : 0, or an error code (which can be tested with ZSTD_isError()). + * Special : Adding a NULL (or 0-size) dictionary invalidates any previous dictionary, + * meaning "return to no-dictionary mode". +@@ -1012,9 +1094,10 @@ + * The memory for the table is allocated on the first call to refDDict, and can be + * freed with ZSTD_freeDCtx(). + * ++ * If called with ZSTD_d_refMultipleDDicts disabled (the default), only one dictionary ++ * will be managed, and referencing a dictionary effectively "discards" any previous one. ++ * + * @result : 0, or an error code (which can be tested with ZSTD_isError()). +- * Note 1 : Currently, only one dictionary can be managed. +- * Referencing a new dictionary effectively "discards" any previous one. + * Special: referencing a NULL DDict means "return to no-dictionary mode". + * Note 2 : DDict is just referenced, its lifetime must outlive its usage from DCtx. + */ +@@ -1071,24 +1154,6 @@ + #define ZSTDLIB_STATIC_API ZSTDLIB_VISIBLE + #endif + +-/* Deprecation warnings : +- * Should these warnings be a problem, it is generally possible to disable them, +- * typically with -Wno-deprecated-declarations for gcc or _CRT_SECURE_NO_WARNINGS in Visual. +- * Otherwise, it's also possible to define ZSTD_DISABLE_DEPRECATE_WARNINGS. +- */ +-#ifdef ZSTD_DISABLE_DEPRECATE_WARNINGS +-# define ZSTD_DEPRECATED(message) ZSTDLIB_STATIC_API /* disable deprecation warnings */ +-#else +-# if (defined(GNUC) && (GNUC > 4 || (GNUC == 4 && GNUC_MINOR >= 5))) || defined(__clang__) +-# define ZSTD_DEPRECATED(message) ZSTDLIB_STATIC_API __attribute__((deprecated(message))) +-# elif (__GNUC__ >= 3) +-# define ZSTD_DEPRECATED(message) ZSTDLIB_STATIC_API __attribute__((deprecated)) +-# else +-# pragma message("WARNING: You need to implement ZSTD_DEPRECATED for this compiler") +-# define ZSTD_DEPRECATED(message) ZSTDLIB_STATIC_API +-# endif +-#endif /* ZSTD_DISABLE_DEPRECATE_WARNINGS */ +- + /* ************************************************************************************** + * experimental API (static linking only) + **************************************************************************************** +@@ -1123,6 +1188,7 @@ + #define ZSTD_TARGETLENGTH_MIN 0 /* note : comparing this constant to an unsigned results in a tautological test */ + #define ZSTD_STRATEGY_MIN ZSTD_fast + #define ZSTD_STRATEGY_MAX ZSTD_btultra2 ++#define ZSTD_BLOCKSIZE_MAX_MIN (1 << 10) /* The minimum valid max blocksize. Maximum blocksizes smaller than this make compressBound() inaccurate. */ + + + #define ZSTD_OVERLAPLOG_MIN 0 +@@ -1303,7 +1369,7 @@ + } ZSTD_paramSwitch_e; + + /* ************************************* +-* Frame size functions ++* Frame header and size functions + ***************************************/ + + /*! ZSTD_findDecompressedSize() : +@@ -1350,29 +1416,109 @@ + * or an error code (if srcSize is too small) */ + ZSTDLIB_STATIC_API size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize); + ++typedef enum { ZSTD_frame, ZSTD_skippableFrame } ZSTD_frameType_e; ++typedef struct { ++ unsigned long long frameContentSize; /* if == ZSTD_CONTENTSIZE_UNKNOWN, it means this field is not available. 0 means "empty" */ ++ unsigned long long windowSize; /* can be very large, up to <= frameContentSize */ ++ unsigned blockSizeMax; ++ ZSTD_frameType_e frameType; /* if == ZSTD_skippableFrame, frameContentSize is the size of skippable content */ ++ unsigned headerSize; ++ unsigned dictID; ++ unsigned checksumFlag; ++ unsigned _reserved1; ++ unsigned _reserved2; ++} ZSTD_frameHeader; ++ ++/*! ZSTD_getFrameHeader() : ++ * decode Frame Header, or requires larger `srcSize`. ++ * @return : 0, `zfhPtr` is correctly filled, ++ * >0, `srcSize` is too small, value is wanted `srcSize` amount, ++ * or an error code, which can be tested using ZSTD_isError() */ ++ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize); /*< doesn't consume input */ ++/*! ZSTD_getFrameHeader_advanced() : ++ * same as ZSTD_getFrameHeader(), ++ * with added capability to select a format (like ZSTD_f_zstd1_magicless) */ ++ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format); ++ ++/*! ZSTD_decompressionMargin() : ++ * Zstd supports in-place decompression, where the input and output buffers overlap. ++ * In this case, the output buffer must be at least (Margin + Output_Size) bytes large, ++ * and the input buffer must be at the end of the output buffer. ++ * ++ * _______________________ Output Buffer ________________________ ++ * | | ++ * | ____ Input Buffer ____| ++ * | | | ++ * v v v ++ * |---------------------------------------|-----------|----------| ++ * ^ ^ ^ ++ * |___________________ Output_Size ___________________|_ Margin _| ++ * ++ * NOTE: See also ZSTD_DECOMPRESSION_MARGIN(). ++ * NOTE: This applies only to single-pass decompression through ZSTD_decompress() or ++ * ZSTD_decompressDCtx(). ++ * NOTE: This function supports multi-frame input. ++ * ++ * @param src The compressed frame(s) ++ * @param srcSize The size of the compressed frame(s) ++ * @returns The decompression margin or an error that can be checked with ZSTD_isError(). ++ */ ++ZSTDLIB_STATIC_API size_t ZSTD_decompressionMargin(const void* src, size_t srcSize); ++ ++/*! ZSTD_DECOMPRESS_MARGIN() : ++ * Similar to ZSTD_decompressionMargin(), but instead of computing the margin from ++ * the compressed frame, compute it from the original size and the blockSizeLog. ++ * See ZSTD_decompressionMargin() for details. ++ * ++ * WARNING: This macro does not support multi-frame input, the input must be a single ++ * zstd frame. If you need that support use the function, or implement it yourself. ++ * ++ * @param originalSize The original uncompressed size of the data. ++ * @param blockSize The block size == MIN(windowSize, ZSTD_BLOCKSIZE_MAX). ++ * Unless you explicitly set the windowLog smaller than ++ * ZSTD_BLOCKSIZELOG_MAX you can just use ZSTD_BLOCKSIZE_MAX. ++ */ ++#define ZSTD_DECOMPRESSION_MARGIN(originalSize, blockSize) ((size_t)( \ ++ ZSTD_FRAMEHEADERSIZE_MAX /* Frame header */ + \ ++ 4 /* checksum */ + \ ++ ((originalSize) == 0 ? 0 : 3 * (((originalSize) + (blockSize) - 1) / blockSize)) /* 3 bytes per block */ + \ ++ (blockSize) /* One block of margin */ \ ++ )) ++ + typedef enum { + ZSTD_sf_noBlockDelimiters = 0, /* Representation of ZSTD_Sequence has no block delimiters, sequences only */ + ZSTD_sf_explicitBlockDelimiters = 1 /* Representation of ZSTD_Sequence contains explicit block delimiters */ + } ZSTD_sequenceFormat_e; + ++/*! ZSTD_sequenceBound() : ++ * `srcSize` : size of the input buffer ++ * @return : upper-bound for the number of sequences that can be generated ++ * from a buffer of srcSize bytes ++ * ++ * note : returns number of sequences - to get bytes, multiply by sizeof(ZSTD_Sequence). ++ */ ++ZSTDLIB_STATIC_API size_t ZSTD_sequenceBound(size_t srcSize); ++ + /*! ZSTD_generateSequences() : +- * Generate sequences using ZSTD_compress2, given a source buffer. ++ * Generate sequences using ZSTD_compress2(), given a source buffer. + * + * Each block will end with a dummy sequence + * with offset == 0, matchLength == 0, and litLength == length of last literals. + * litLength may be == 0, and if so, then the sequence of (of: 0 ml: 0 ll: 0) + * simply acts as a block delimiter. + * +- * zc can be used to insert custom compression params. +- * This function invokes ZSTD_compress2 ++ * @zc can be used to insert custom compression params. ++ * This function invokes ZSTD_compress2(). + * + * The output of this function can be fed into ZSTD_compressSequences() with CCtx + * setting of ZSTD_c_blockDelimiters as ZSTD_sf_explicitBlockDelimiters + * @return : number of sequences generated + */ + +-ZSTDLIB_STATIC_API size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, +- size_t outSeqsSize, const void* src, size_t srcSize); ++ZSTDLIB_STATIC_API size_t ++ZSTD_generateSequences( ZSTD_CCtx* zc, ++ ZSTD_Sequence* outSeqs, size_t outSeqsSize, ++ const void* src, size_t srcSize); + + /*! ZSTD_mergeBlockDelimiters() : + * Given an array of ZSTD_Sequence, remove all sequences that represent block delimiters/last literals +@@ -1388,7 +1534,9 @@ + ZSTDLIB_STATIC_API size_t ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, size_t seqsSize); + + /*! ZSTD_compressSequences() : +- * Compress an array of ZSTD_Sequence, generated from the original source buffer, into dst. ++ * Compress an array of ZSTD_Sequence, associated with @src buffer, into dst. ++ * @src contains the entire input (not just the literals). ++ * If @srcSize > sum(sequence.length), the remaining bytes are considered all literals + * If a dictionary is included, then the cctx should reference the dict. (see: ZSTD_CCtx_refCDict(), ZSTD_CCtx_loadDictionary(), etc.) + * The entire source is compressed into a single frame. + * +@@ -1413,11 +1561,12 @@ + * Note: Repcodes are, as of now, always re-calculated within this function, so ZSTD_Sequence::rep is unused. + * Note 2: Once we integrate ability to ingest repcodes, the explicit block delims mode must respect those repcodes exactly, + * and cannot emit an RLE block that disagrees with the repcode history +- * @return : final compressed size or a ZSTD error. ++ * @return : final compressed size, or a ZSTD error code. + */ +-ZSTDLIB_STATIC_API size_t ZSTD_compressSequences(ZSTD_CCtx* const cctx, void* dst, size_t dstSize, +- const ZSTD_Sequence* inSeqs, size_t inSeqsSize, +- const void* src, size_t srcSize); ++ZSTDLIB_STATIC_API size_t ++ZSTD_compressSequences( ZSTD_CCtx* cctx, void* dst, size_t dstSize, ++ const ZSTD_Sequence* inSeqs, size_t inSeqsSize, ++ const void* src, size_t srcSize); + + + /*! ZSTD_writeSkippableFrame() : +@@ -1481,8 +1630,11 @@ + * and ZSTD_estimateCCtxSize_usingCCtxParams(), which can be used in tandem with ZSTD_CCtxParams_setParameter(). + * Both can be used to estimate memory using custom compression parameters and arbitrary srcSize limits. + * +- * Note 2 : only single-threaded compression is supported. ++ * Note : only single-threaded compression is supported. + * ZSTD_estimateCCtxSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. ++ * ++ * Note 2 : ZSTD_estimateCCtxSize* functions are not compatible with the Block-Level Sequence Producer API at this time. ++ * Size estimates assume that no external sequence producer is registered. + */ + ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize(int compressionLevel); + ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams); +@@ -1501,7 +1653,12 @@ + * or deducted from a valid frame Header, using ZSTD_estimateDStreamSize_fromFrame(); + * Note : if streaming is init with function ZSTD_init?Stream_usingDict(), + * an internal ?Dict will be created, which additional size is not estimated here. +- * In this case, get total size by adding ZSTD_estimate?DictSize */ ++ * In this case, get total size by adding ZSTD_estimate?DictSize ++ * Note 2 : only single-threaded compression is supported. ++ * ZSTD_estimateCStreamSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1. ++ * Note 3 : ZSTD_estimateCStreamSize* functions are not compatible with the Block-Level Sequence Producer API at this time. ++ * Size estimates assume that no external sequence producer is registered. ++ */ + ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize(int compressionLevel); + ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams); + ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params); +@@ -1649,22 +1806,45 @@ + * This function never fails (wide contract) */ + ZSTDLIB_STATIC_API ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, unsigned long long srcSize, size_t dictSize); + ++/*! ZSTD_CCtx_setCParams() : ++ * Set all parameters provided within @p cparams into the working @p cctx. ++ * Note : if modifying parameters during compression (MT mode only), ++ * note that changes to the .windowLog parameter will be ignored. ++ * @return 0 on success, or an error code (can be checked with ZSTD_isError()). ++ * On failure, no parameters are updated. ++ */ ++ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setCParams(ZSTD_CCtx* cctx, ZSTD_compressionParameters cparams); ++ ++/*! ZSTD_CCtx_setFParams() : ++ * Set all parameters provided within @p fparams into the working @p cctx. ++ * @return 0 on success, or an error code (can be checked with ZSTD_isError()). ++ */ ++ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setFParams(ZSTD_CCtx* cctx, ZSTD_frameParameters fparams); ++ ++/*! ZSTD_CCtx_setParams() : ++ * Set all parameters provided within @p params into the working @p cctx. ++ * @return 0 on success, or an error code (can be checked with ZSTD_isError()). ++ */ ++ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setParams(ZSTD_CCtx* cctx, ZSTD_parameters params); ++ + /*! ZSTD_compress_advanced() : + * Note : this function is now DEPRECATED. + * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_setParameter() and other parameter setters. + * This prototype will generate compilation warnings. */ + ZSTD_DEPRECATED("use ZSTD_compress2") ++ZSTDLIB_STATIC_API + size_t ZSTD_compress_advanced(ZSTD_CCtx* cctx, +- void* dst, size_t dstCapacity, +- const void* src, size_t srcSize, +- const void* dict,size_t dictSize, +- ZSTD_parameters params); ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize, ++ const void* dict,size_t dictSize, ++ ZSTD_parameters params); + + /*! ZSTD_compress_usingCDict_advanced() : + * Note : this function is now DEPRECATED. + * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_loadDictionary() and other parameter setters. + * This prototype will generate compilation warnings. */ + ZSTD_DEPRECATED("use ZSTD_compress2 with ZSTD_CCtx_loadDictionary") ++ZSTDLIB_STATIC_API + size_t ZSTD_compress_usingCDict_advanced(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, +@@ -1808,13 +1988,16 @@ + * Experimental parameter. + * Default is 0 == disabled. Set to 1 to enable. + * +- * Tells the compressor that the ZSTD_inBuffer will ALWAYS be the same +- * between calls, except for the modifications that zstd makes to pos (the +- * caller must not modify pos). This is checked by the compressor, and +- * compression will fail if it ever changes. This means the only flush +- * mode that makes sense is ZSTD_e_end, so zstd will error if ZSTD_e_end +- * is not used. The data in the ZSTD_inBuffer in the range [src, src + pos) +- * MUST not be modified during compression or you will get data corruption. ++ * Tells the compressor that input data presented with ZSTD_inBuffer ++ * will ALWAYS be the same between calls. ++ * Technically, the @src pointer must never be changed, ++ * and the @pos field can only be updated by zstd. ++ * However, it's possible to increase the @size field, ++ * allowing scenarios where more data can be appended after compressions starts. ++ * These conditions are checked by the compressor, ++ * and compression will fail if they are not respected. ++ * Also, data in the ZSTD_inBuffer within the range [src, src + pos) ++ * MUST not be modified during compression or it will result in data corruption. + * + * When this flag is enabled zstd won't allocate an input window buffer, + * because the user guarantees it can reference the ZSTD_inBuffer until +@@ -1822,18 +2005,15 @@ + * large enough to fit a block (see ZSTD_c_stableOutBuffer). This will also + * avoid the memcpy() from the input buffer to the input window buffer. + * +- * NOTE: ZSTD_compressStream2() will error if ZSTD_e_end is not used. +- * That means this flag cannot be used with ZSTD_compressStream(). +- * + * NOTE: So long as the ZSTD_inBuffer always points to valid memory, using + * this flag is ALWAYS memory safe, and will never access out-of-bounds +- * memory. However, compression WILL fail if you violate the preconditions. ++ * memory. However, compression WILL fail if conditions are not respected. + * +- * WARNING: The data in the ZSTD_inBuffer in the range [dst, dst + pos) MUST +- * not be modified during compression or you will get data corruption. This +- * is because zstd needs to reference data in the ZSTD_inBuffer to find ++ * WARNING: The data in the ZSTD_inBuffer in the range [src, src + pos) MUST ++ * not be modified during compression or it will result in data corruption. ++ * This is because zstd needs to reference data in the ZSTD_inBuffer to find + * matches. Normally zstd maintains its own window buffer for this purpose, +- * but passing this flag tells zstd to use the user provided buffer. ++ * but passing this flag tells zstd to rely on user provided buffer instead. + */ + #define ZSTD_c_stableInBuffer ZSTD_c_experimentalParam9 + +@@ -1878,7 +2058,7 @@ + * Without validation, providing a sequence that does not conform to the zstd spec will cause + * undefined behavior, and may produce a corrupted block. + * +- * With validation enabled, a if sequence is invalid (see doc/zstd_compression_format.md for ++ * With validation enabled, if sequence is invalid (see doc/zstd_compression_format.md for + * specifics regarding offset/matchlength requirements) then the function will bail out and + * return an error. + * +@@ -1928,6 +2108,79 @@ + */ + #define ZSTD_c_deterministicRefPrefix ZSTD_c_experimentalParam15 + ++/* ZSTD_c_prefetchCDictTables ++ * Controlled with ZSTD_paramSwitch_e enum. Default is ZSTD_ps_auto. ++ * ++ * In some situations, zstd uses CDict tables in-place rather than copying them ++ * into the working context. (See docs on ZSTD_dictAttachPref_e above for details). ++ * In such situations, compression speed is seriously impacted when CDict tables are ++ * "cold" (outside CPU cache). This parameter instructs zstd to prefetch CDict tables ++ * when they are used in-place. ++ * ++ * For sufficiently small inputs, the cost of the prefetch will outweigh the benefit. ++ * For sufficiently large inputs, zstd will by default memcpy() CDict tables ++ * into the working context, so there is no need to prefetch. This parameter is ++ * targeted at a middle range of input sizes, where a prefetch is cheap enough to be ++ * useful but memcpy() is too expensive. The exact range of input sizes where this ++ * makes sense is best determined by careful experimentation. ++ * ++ * Note: for this parameter, ZSTD_ps_auto is currently equivalent to ZSTD_ps_disable, ++ * but in the future zstd may conditionally enable this feature via an auto-detection ++ * heuristic for cold CDicts. ++ * Use ZSTD_ps_disable to opt out of prefetching under any circumstances. ++ */ ++#define ZSTD_c_prefetchCDictTables ZSTD_c_experimentalParam16 ++ ++/* ZSTD_c_enableSeqProducerFallback ++ * Allowed values are 0 (disable) and 1 (enable). The default setting is 0. ++ * ++ * Controls whether zstd will fall back to an internal sequence producer if an ++ * external sequence producer is registered and returns an error code. This fallback ++ * is block-by-block: the internal sequence producer will only be called for blocks ++ * where the external sequence producer returns an error code. Fallback parsing will ++ * follow any other cParam settings, such as compression level, the same as in a ++ * normal (fully-internal) compression operation. ++ * ++ * The user is strongly encouraged to read the full Block-Level Sequence Producer API ++ * documentation (below) before setting this parameter. */ ++#define ZSTD_c_enableSeqProducerFallback ZSTD_c_experimentalParam17 ++ ++/* ZSTD_c_maxBlockSize ++ * Allowed values are between 1KB and ZSTD_BLOCKSIZE_MAX (128KB). ++ * The default is ZSTD_BLOCKSIZE_MAX, and setting to 0 will set to the default. ++ * ++ * This parameter can be used to set an upper bound on the blocksize ++ * that overrides the default ZSTD_BLOCKSIZE_MAX. It cannot be used to set upper ++ * bounds greater than ZSTD_BLOCKSIZE_MAX or bounds lower than 1KB (will make ++ * compressBound() inaccurate). Only currently meant to be used for testing. ++ * ++ */ ++#define ZSTD_c_maxBlockSize ZSTD_c_experimentalParam18 ++ ++/* ZSTD_c_searchForExternalRepcodes ++ * This parameter affects how zstd parses external sequences, such as sequences ++ * provided through the compressSequences() API or from an external block-level ++ * sequence producer. ++ * ++ * If set to ZSTD_ps_enable, the library will check for repeated offsets in ++ * external sequences, even if those repcodes are not explicitly indicated in ++ * the "rep" field. Note that this is the only way to exploit repcode matches ++ * while using compressSequences() or an external sequence producer, since zstd ++ * currently ignores the "rep" field of external sequences. ++ * ++ * If set to ZSTD_ps_disable, the library will not exploit repeated offsets in ++ * external sequences, regardless of whether the "rep" field has been set. This ++ * reduces sequence compression overhead by about 25% while sacrificing some ++ * compression ratio. ++ * ++ * The default value is ZSTD_ps_auto, for which the library will enable/disable ++ * based on compression level. ++ * ++ * Note: for now, this param only has an effect if ZSTD_c_blockDelimiters is ++ * set to ZSTD_sf_explicitBlockDelimiters. That may change in the future. ++ */ ++#define ZSTD_c_searchForExternalRepcodes ZSTD_c_experimentalParam19 ++ + /*! ZSTD_CCtx_getParameter() : + * Get the requested compression parameter value, selected by enum ZSTD_cParameter, + * and store it into int* value. +@@ -2084,7 +2337,7 @@ + * in the range [dst, dst + pos) MUST not be modified during decompression + * or you will get data corruption. + * +- * When this flags is enabled zstd won't allocate an output buffer, because ++ * When this flag is enabled zstd won't allocate an output buffer, because + * it can write directly to the ZSTD_outBuffer, but it will still allocate + * an input buffer large enough to fit any compressed block. This will also + * avoid the memcpy() from the internal output buffer to the ZSTD_outBuffer. +@@ -2137,6 +2390,17 @@ + */ + #define ZSTD_d_refMultipleDDicts ZSTD_d_experimentalParam4 + ++/* ZSTD_d_disableHuffmanAssembly ++ * Set to 1 to disable the Huffman assembly implementation. ++ * The default value is 0, which allows zstd to use the Huffman assembly ++ * implementation if available. ++ * ++ * This parameter can be used to disable Huffman assembly at runtime. ++ * If you want to disable it at compile time you can define the macro ++ * ZSTD_DISABLE_ASM. ++ */ ++#define ZSTD_d_disableHuffmanAssembly ZSTD_d_experimentalParam5 ++ + + /*! ZSTD_DCtx_setFormat() : + * This function is REDUNDANT. Prefer ZSTD_DCtx_setParameter(). +@@ -2145,6 +2409,7 @@ + * such ZSTD_f_zstd1_magicless for example. + * @return : 0, or an error code (which can be tested using ZSTD_isError()). */ + ZSTD_DEPRECATED("use ZSTD_DCtx_setParameter() instead") ++ZSTDLIB_STATIC_API + size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format); + + /*! ZSTD_decompressStream_simpleArgs() : +@@ -2181,6 +2446,7 @@ + * This prototype will generate compilation warnings. + */ + ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") ++ZSTDLIB_STATIC_API + size_t ZSTD_initCStream_srcSize(ZSTD_CStream* zcs, + int compressionLevel, + unsigned long long pledgedSrcSize); +@@ -2198,17 +2464,15 @@ + * This prototype will generate compilation warnings. + */ + ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") ++ZSTDLIB_STATIC_API + size_t ZSTD_initCStream_usingDict(ZSTD_CStream* zcs, + const void* dict, size_t dictSize, + int compressionLevel); + + /*! ZSTD_initCStream_advanced() : +- * This function is DEPRECATED, and is approximately equivalent to: ++ * This function is DEPRECATED, and is equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); +- * // Pseudocode: Set each zstd parameter and leave the rest as-is. +- * for ((param, value) : params) { +- * ZSTD_CCtx_setParameter(zcs, param, value); +- * } ++ * ZSTD_CCtx_setParams(zcs, params); + * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); + * ZSTD_CCtx_loadDictionary(zcs, dict, dictSize); + * +@@ -2218,6 +2482,7 @@ + * This prototype will generate compilation warnings. + */ + ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") ++ZSTDLIB_STATIC_API + size_t ZSTD_initCStream_advanced(ZSTD_CStream* zcs, + const void* dict, size_t dictSize, + ZSTD_parameters params, +@@ -2232,15 +2497,13 @@ + * This prototype will generate compilation warnings. + */ + ZSTD_DEPRECATED("use ZSTD_CCtx_reset and ZSTD_CCtx_refCDict, see zstd.h for detailed instructions") ++ZSTDLIB_STATIC_API + size_t ZSTD_initCStream_usingCDict(ZSTD_CStream* zcs, const ZSTD_CDict* cdict); + + /*! ZSTD_initCStream_usingCDict_advanced() : +- * This function is DEPRECATED, and is approximately equivalent to: ++ * This function is DEPRECATED, and is equivalent to: + * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only); +- * // Pseudocode: Set each zstd frame parameter and leave the rest as-is. +- * for ((fParam, value) : fParams) { +- * ZSTD_CCtx_setParameter(zcs, fParam, value); +- * } ++ * ZSTD_CCtx_setFParams(zcs, fParams); + * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize); + * ZSTD_CCtx_refCDict(zcs, cdict); + * +@@ -2250,6 +2513,7 @@ + * This prototype will generate compilation warnings. + */ + ZSTD_DEPRECATED("use ZSTD_CCtx_reset and ZSTD_CCtx_refCDict, see zstd.h for detailed instructions") ++ZSTDLIB_STATIC_API + size_t ZSTD_initCStream_usingCDict_advanced(ZSTD_CStream* zcs, + const ZSTD_CDict* cdict, + ZSTD_frameParameters fParams, +@@ -2274,6 +2538,7 @@ + * This prototype will generate compilation warnings. + */ + ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") ++ZSTDLIB_STATIC_API + size_t ZSTD_resetCStream(ZSTD_CStream* zcs, unsigned long long pledgedSrcSize); + + +@@ -2319,8 +2584,8 @@ + * ZSTD_DCtx_loadDictionary(zds, dict, dictSize); + * + * note: no dictionary will be used if dict == NULL or dictSize < 8 +- * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ ++ZSTD_DEPRECATED("use ZSTD_DCtx_reset + ZSTD_DCtx_loadDictionary, see zstd.h for detailed instructions") + ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize); + + /*! +@@ -2330,8 +2595,8 @@ + * ZSTD_DCtx_refDDict(zds, ddict); + * + * note : ddict is referenced, it must outlive decompression session +- * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ ++ZSTD_DEPRECATED("use ZSTD_DCtx_reset + ZSTD_DCtx_refDDict, see zstd.h for detailed instructions") + ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* zds, const ZSTD_DDict* ddict); + + /*! +@@ -2340,17 +2605,185 @@ + * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only); + * + * re-use decompression parameters from previous init; saves dictionary loading +- * Note : this prototype will be marked as deprecated and generate compilation warnings on reaching v1.5.x + */ ++ZSTD_DEPRECATED("use ZSTD_DCtx_reset, see zstd.h for detailed instructions") + ZSTDLIB_STATIC_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); + + ++/* ********************* BLOCK-LEVEL SEQUENCE PRODUCER API ********************* ++ * ++ * *** OVERVIEW *** ++ * The Block-Level Sequence Producer API allows users to provide their own custom ++ * sequence producer which libzstd invokes to process each block. The produced list ++ * of sequences (literals and matches) is then post-processed by libzstd to produce ++ * valid compressed blocks. ++ * ++ * This block-level offload API is a more granular complement of the existing ++ * frame-level offload API compressSequences() (introduced in v1.5.1). It offers ++ * an easier migration story for applications already integrated with libzstd: the ++ * user application continues to invoke the same compression functions ++ * ZSTD_compress2() or ZSTD_compressStream2() as usual, and transparently benefits ++ * from the specific advantages of the external sequence producer. For example, ++ * the sequence producer could be tuned to take advantage of known characteristics ++ * of the input, to offer better speed / ratio, or could leverage hardware ++ * acceleration not available within libzstd itself. ++ * ++ * See contrib/externalSequenceProducer for an example program employing the ++ * Block-Level Sequence Producer API. ++ * ++ * *** USAGE *** ++ * The user is responsible for implementing a function of type ++ * ZSTD_sequenceProducer_F. For each block, zstd will pass the following ++ * arguments to the user-provided function: ++ * ++ * - sequenceProducerState: a pointer to a user-managed state for the sequence ++ * producer. ++ * ++ * - outSeqs, outSeqsCapacity: an output buffer for the sequence producer. ++ * outSeqsCapacity is guaranteed >= ZSTD_sequenceBound(srcSize). The memory ++ * backing outSeqs is managed by the CCtx. ++ * ++ * - src, srcSize: an input buffer for the sequence producer to parse. ++ * srcSize is guaranteed to be <= ZSTD_BLOCKSIZE_MAX. ++ * ++ * - dict, dictSize: a history buffer, which may be empty, which the sequence ++ * producer may reference as it parses the src buffer. Currently, zstd will ++ * always pass dictSize == 0 into external sequence producers, but this will ++ * change in the future. ++ * ++ * - compressionLevel: a signed integer representing the zstd compression level ++ * set by the user for the current operation. The sequence producer may choose ++ * to use this information to change its compression strategy and speed/ratio ++ * tradeoff. Note: the compression level does not reflect zstd parameters set ++ * through the advanced API. ++ * ++ * - windowSize: a size_t representing the maximum allowed offset for external ++ * sequences. Note that sequence offsets are sometimes allowed to exceed the ++ * windowSize if a dictionary is present, see doc/zstd_compression_format.md ++ * for details. ++ * ++ * The user-provided function shall return a size_t representing the number of ++ * sequences written to outSeqs. This return value will be treated as an error ++ * code if it is greater than outSeqsCapacity. The return value must be non-zero ++ * if srcSize is non-zero. The ZSTD_SEQUENCE_PRODUCER_ERROR macro is provided ++ * for convenience, but any value greater than outSeqsCapacity will be treated as ++ * an error code. ++ * ++ * If the user-provided function does not return an error code, the sequences ++ * written to outSeqs must be a valid parse of the src buffer. Data corruption may ++ * occur if the parse is not valid. A parse is defined to be valid if the ++ * following conditions hold: ++ * - The sum of matchLengths and literalLengths must equal srcSize. ++ * - All sequences in the parse, except for the final sequence, must have ++ * matchLength >= ZSTD_MINMATCH_MIN. The final sequence must have ++ * matchLength >= ZSTD_MINMATCH_MIN or matchLength == 0. ++ * - All offsets must respect the windowSize parameter as specified in ++ * doc/zstd_compression_format.md. ++ * - If the final sequence has matchLength == 0, it must also have offset == 0. ++ * ++ * zstd will only validate these conditions (and fail compression if they do not ++ * hold) if the ZSTD_c_validateSequences cParam is enabled. Note that sequence ++ * validation has a performance cost. ++ * ++ * If the user-provided function returns an error, zstd will either fall back ++ * to an internal sequence producer or fail the compression operation. The user can ++ * choose between the two behaviors by setting the ZSTD_c_enableSeqProducerFallback ++ * cParam. Fallback compression will follow any other cParam settings, such as ++ * compression level, the same as in a normal compression operation. ++ * ++ * The user shall instruct zstd to use a particular ZSTD_sequenceProducer_F ++ * function by calling ++ * ZSTD_registerSequenceProducer(cctx, ++ * sequenceProducerState, ++ * sequenceProducer) ++ * This setting will persist until the next parameter reset of the CCtx. ++ * ++ * The sequenceProducerState must be initialized by the user before calling ++ * ZSTD_registerSequenceProducer(). The user is responsible for destroying the ++ * sequenceProducerState. ++ * ++ * *** LIMITATIONS *** ++ * This API is compatible with all zstd compression APIs which respect advanced parameters. ++ * However, there are three limitations: ++ * ++ * First, the ZSTD_c_enableLongDistanceMatching cParam is not currently supported. ++ * COMPRESSION WILL FAIL if it is enabled and the user tries to compress with a block-level ++ * external sequence producer. ++ * - Note that ZSTD_c_enableLongDistanceMatching is auto-enabled by default in some ++ * cases (see its documentation for details). Users must explicitly set ++ * ZSTD_c_enableLongDistanceMatching to ZSTD_ps_disable in such cases if an external ++ * sequence producer is registered. ++ * - As of this writing, ZSTD_c_enableLongDistanceMatching is disabled by default ++ * whenever ZSTD_c_windowLog < 128MB, but that's subject to change. Users should ++ * check the docs on ZSTD_c_enableLongDistanceMatching whenever the Block-Level Sequence ++ * Producer API is used in conjunction with advanced settings (like ZSTD_c_windowLog). ++ * ++ * Second, history buffers are not currently supported. Concretely, zstd will always pass ++ * dictSize == 0 to the external sequence producer (for now). This has two implications: ++ * - Dictionaries are not currently supported. Compression will *not* fail if the user ++ * references a dictionary, but the dictionary won't have any effect. ++ * - Stream history is not currently supported. All advanced compression APIs, including ++ * streaming APIs, work with external sequence producers, but each block is treated as ++ * an independent chunk without history from previous blocks. ++ * ++ * Third, multi-threading within a single compression is not currently supported. In other words, ++ * COMPRESSION WILL FAIL if ZSTD_c_nbWorkers > 0 and an external sequence producer is registered. ++ * Multi-threading across compressions is fine: simply create one CCtx per thread. ++ * ++ * Long-term, we plan to overcome all three limitations. There is no technical blocker to ++ * overcoming them. It is purely a question of engineering effort. ++ */ ++ ++#define ZSTD_SEQUENCE_PRODUCER_ERROR ((size_t)(-1)) ++ ++typedef size_t ZSTD_sequenceProducer_F ( ++ void* sequenceProducerState, ++ ZSTD_Sequence* outSeqs, size_t outSeqsCapacity, ++ const void* src, size_t srcSize, ++ const void* dict, size_t dictSize, ++ int compressionLevel, ++ size_t windowSize ++); ++ ++/*! ZSTD_registerSequenceProducer() : ++ * Instruct zstd to use a block-level external sequence producer function. ++ * ++ * The sequenceProducerState must be initialized by the caller, and the caller is ++ * responsible for managing its lifetime. This parameter is sticky across ++ * compressions. It will remain set until the user explicitly resets compression ++ * parameters. ++ * ++ * Sequence producer registration is considered to be an "advanced parameter", ++ * part of the "advanced API". This means it will only have an effect on compression ++ * APIs which respect advanced parameters, such as compress2() and compressStream2(). ++ * Older compression APIs such as compressCCtx(), which predate the introduction of ++ * "advanced parameters", will ignore any external sequence producer setting. ++ * ++ * The sequence producer can be "cleared" by registering a NULL function pointer. This ++ * removes all limitations described above in the "LIMITATIONS" section of the API docs. ++ * ++ * The user is strongly encouraged to read the full API documentation (above) before ++ * calling this function. */ ++ZSTDLIB_STATIC_API void ++ZSTD_registerSequenceProducer( ++ ZSTD_CCtx* cctx, ++ void* sequenceProducerState, ++ ZSTD_sequenceProducer_F* sequenceProducer ++); ++ ++ + /* ******************************************************************* +-* Buffer-less and synchronous inner streaming functions ++* Buffer-less and synchronous inner streaming functions (DEPRECATED) + * +-* This is an advanced API, giving full control over buffer management, for users which need direct control over memory. +-* But it's also a complex one, with several restrictions, documented below. +-* Prefer normal streaming API for an easier experience. ++* This API is deprecated, and will be removed in a future version. ++* It allows streaming (de)compression with user allocated buffers. ++* However, it is hard to use, and not as well tested as the rest of ++* our API. ++* ++* Please use the normal streaming API instead: ZSTD_compressStream2, ++* and ZSTD_decompressStream. ++* If there is functionality that you need, but it doesn't provide, ++* please open an issue on our GitHub. + ********************************************************************* */ + + /* +@@ -2362,7 +2795,6 @@ + + Start by initializing a context. + Use ZSTD_compressBegin(), or ZSTD_compressBegin_usingDict() for dictionary compression. +- It's also possible to duplicate a reference context which has already been initialized, using ZSTD_copyCCtx() + + Then, consume your input using ZSTD_compressContinue(). + There are some important considerations to keep in mind when using this advanced function : +@@ -2384,18 +2816,28 @@ + */ + + /*===== Buffer-less streaming compression functions =====*/ ++ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel); ++ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel); ++ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); /*< note: fails if cdict==NULL */ +-ZSTDLIB_STATIC_API size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, unsigned long long pledgedSrcSize); /*< note: if pledgedSrcSize is not known, use ZSTD_CONTENTSIZE_UNKNOWN */ + ++ZSTD_DEPRECATED("This function will likely be removed in a future release. It is misleading and has very limited utility.") ++ZSTDLIB_STATIC_API ++size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, unsigned long long pledgedSrcSize); /*< note: if pledgedSrcSize is not known, use ZSTD_CONTENTSIZE_UNKNOWN */ ++ ++ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); ++ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); + + /* The ZSTD_compressBegin_advanced() and ZSTD_compressBegin_usingCDict_advanced() are now DEPRECATED and will generate a compiler warning */ + ZSTD_DEPRECATED("use advanced API to access custom parameters") ++ZSTDLIB_STATIC_API + size_t ZSTD_compressBegin_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_parameters params, unsigned long long pledgedSrcSize); /*< pledgedSrcSize : If srcSize is not known at init time, use ZSTD_CONTENTSIZE_UNKNOWN */ + ZSTD_DEPRECATED("use advanced API to access custom parameters") ++ZSTDLIB_STATIC_API + size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_CDict* const cdict, ZSTD_frameParameters const fParams, unsigned long long const pledgedSrcSize); /* compression parameters are already set within cdict. pledgedSrcSize must be correct. If srcSize is not known, use macro ZSTD_CONTENTSIZE_UNKNOWN */ + /* + Buffer-less streaming decompression (synchronous mode) +@@ -2408,8 +2850,8 @@ + Frame header is extracted from the beginning of compressed frame, so providing only the frame's beginning is enough. + Data fragment must be large enough to ensure successful decoding. + `ZSTD_frameHeaderSize_max` bytes is guaranteed to always be large enough. +- @result : 0 : successful decoding, the `ZSTD_frameHeader` structure is correctly filled. +- >0 : `srcSize` is too small, please provide at least @result bytes on next attempt. ++ result : 0 : successful decoding, the `ZSTD_frameHeader` structure is correctly filled. ++ >0 : `srcSize` is too small, please provide at least result bytes on next attempt. + errorCode, which can be tested using ZSTD_isError(). + + It fills a ZSTD_frameHeader structure with important information to correctly decode the frame, +@@ -2428,7 +2870,7 @@ + + The most memory efficient way is to use a round buffer of sufficient size. + Sufficient size is determined by invoking ZSTD_decodingBufferSize_min(), +- which can @return an error code if required value is too large for current system (in 32-bits mode). ++ which can return an error code if required value is too large for current system (in 32-bits mode). + In a round buffer methodology, ZSTD_decompressContinue() decompresses each block next to previous one, + up to the moment there is not enough room left in the buffer to guarantee decoding another full block, + which maximum size is provided in `ZSTD_frameHeader` structure, field `blockSizeMax`. +@@ -2448,7 +2890,7 @@ + ZSTD_nextSrcSizeToDecompress() tells how many bytes to provide as 'srcSize' to ZSTD_decompressContinue(). + ZSTD_decompressContinue() requires this _exact_ amount of bytes, or it will fail. + +- @result of ZSTD_decompressContinue() is the number of bytes regenerated within 'dst' (necessarily <= dstCapacity). ++ result of ZSTD_decompressContinue() is the number of bytes regenerated within 'dst' (necessarily <= dstCapacity). + It can be zero : it just means ZSTD_decompressContinue() has decoded some metadata item. + It can also be an error code, which can be tested with ZSTD_isError(). + +@@ -2471,27 +2913,7 @@ + */ + + /*===== Buffer-less streaming decompression functions =====*/ +-typedef enum { ZSTD_frame, ZSTD_skippableFrame } ZSTD_frameType_e; +-typedef struct { +- unsigned long long frameContentSize; /* if == ZSTD_CONTENTSIZE_UNKNOWN, it means this field is not available. 0 means "empty" */ +- unsigned long long windowSize; /* can be very large, up to <= frameContentSize */ +- unsigned blockSizeMax; +- ZSTD_frameType_e frameType; /* if == ZSTD_skippableFrame, frameContentSize is the size of skippable content */ +- unsigned headerSize; +- unsigned dictID; +- unsigned checksumFlag; +-} ZSTD_frameHeader; + +-/*! ZSTD_getFrameHeader() : +- * decode Frame Header, or requires larger `srcSize`. +- * @return : 0, `zfhPtr` is correctly filled, +- * >0, `srcSize` is too small, value is wanted `srcSize` amount, +- * or an error code, which can be tested using ZSTD_isError() */ +-ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize); /*< doesn't consume input */ +-/*! ZSTD_getFrameHeader_advanced() : +- * same as ZSTD_getFrameHeader(), +- * with added capability to select a format (like ZSTD_f_zstd1_magicless) */ +-ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format); + ZSTDLIB_STATIC_API size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize); /*< when frame content size is not known, pass in frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN */ + + ZSTDLIB_STATIC_API size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx); +@@ -2502,6 +2924,7 @@ + ZSTDLIB_STATIC_API size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); + + /* misc */ ++ZSTD_DEPRECATED("This function will likely be removed in the next minor release. It is misleading and has very limited utility.") + ZSTDLIB_STATIC_API void ZSTD_copyDCtx(ZSTD_DCtx* dctx, const ZSTD_DCtx* preparedDCtx); + typedef enum { ZSTDnit_frameHeader, ZSTDnit_blockHeader, ZSTDnit_block, ZSTDnit_lastBlock, ZSTDnit_checksum, ZSTDnit_skippableFrame } ZSTD_nextInputType_e; + ZSTDLIB_STATIC_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx); +@@ -2509,11 +2932,23 @@ + + + +-/* ============================ */ +-/* Block level API */ +-/* ============================ */ ++/* ========================================= */ ++/* Block level API (DEPRECATED) */ ++/* ========================================= */ + + /*! ++ ++ This API is deprecated in favor of the regular compression API. ++ You can get the frame header down to 2 bytes by setting: ++ - ZSTD_c_format = ZSTD_f_zstd1_magicless ++ - ZSTD_c_contentSizeFlag = 0 ++ - ZSTD_c_checksumFlag = 0 ++ - ZSTD_c_dictIDFlag = 0 ++ ++ This API is not as well tested as our normal API, so we recommend not using it. ++ We will be removing it in a future version. If the normal API doesn't provide ++ the functionality you need, please open a GitHub issue. ++ + Block functions produce and decode raw zstd blocks, without frame metadata. + Frame metadata cost is typically ~12 bytes, which can be non-negligible for very small blocks (< 100 bytes). + But users will have to take in charge needed metadata to regenerate data, such as compressed and content sizes. +@@ -2524,7 +2959,6 @@ + - It is necessary to init context before starting + + compression : any ZSTD_compressBegin*() variant, including with dictionary + + decompression : any ZSTD_decompressBegin*() variant, including with dictionary +- + copyCCtx() and copyDCtx() can be used too + - Block size is limited, it must be <= ZSTD_getBlockSize() <= ZSTD_BLOCKSIZE_MAX == 128 KB + + If input is larger than a block size, it's necessary to split input data into multiple blocks + + For inputs larger than a single block, consider using regular ZSTD_compress() instead. +@@ -2541,11 +2975,14 @@ + */ + + /*===== Raw zstd block functions =====*/ ++ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_getBlockSize (const ZSTD_CCtx* cctx); ++ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_compressBlock (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); ++ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); ++ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.") + ZSTDLIB_STATIC_API size_t ZSTD_insertBlock (ZSTD_DCtx* dctx, const void* blockStart, size_t blockSize); /*< insert uncompressed block into `dctx` history. Useful for multi-blocks decompression. */ + +- + #endif /* ZSTD_H_ZSTD_STATIC_LINKING_ONLY */ + +diff '--color=auto' -uraN a/include/net/inet_connection_sock.h b/include/net/inet_connection_sock.h +--- a/include/net/inet_connection_sock.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/net/inet_connection_sock.h 2023-11-04 16:35:57.801316580 +0300 +@@ -135,8 +135,8 @@ + u32 icsk_probes_tstamp; + u32 icsk_user_timeout; + +- u64 icsk_ca_priv[104 / sizeof(u64)]; +-#define ICSK_CA_PRIV_SIZE sizeof_field(struct inet_connection_sock, icsk_ca_priv) ++#define ICSK_CA_PRIV_SIZE (144) ++ u64 icsk_ca_priv[ICSK_CA_PRIV_SIZE / sizeof(u64)]; + }; + + #define ICSK_TIME_RETRANS 1 /* Retransmit timer */ +diff '--color=auto' -uraN a/include/net/tcp.h b/include/net/tcp.h +--- a/include/net/tcp.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/net/tcp.h 2023-11-04 16:35:57.801316580 +0300 +@@ -374,6 +374,8 @@ + #define TCP_ECN_QUEUE_CWR 2 + #define TCP_ECN_DEMAND_CWR 4 + #define TCP_ECN_SEEN 8 ++#define TCP_ECN_LOW 16 ++#define TCP_ECN_ECT_PERMANENT 32 + + enum tcp_tw_status { + TCP_TW_SUCCESS = 0, +@@ -727,6 +729,15 @@ + tcp_fast_path_on(tp); + } + ++static inline void tcp_set_ecn_low_from_dst(struct sock *sk, ++ const struct dst_entry *dst) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ ++ if (dst_feature(dst, RTAX_FEATURE_ECN_LOW)) ++ tp->ecn_flags |= TCP_ECN_LOW; ++} ++ + /* Compute the actual rto_min value */ + static inline u32 tcp_rto_min(struct sock *sk) + { +@@ -823,6 +834,11 @@ + return max_t(s64, t1 - t0, 0); + } + ++static inline u32 tcp_stamp32_us_delta(u32 t1, u32 t0) ++{ ++ return max_t(s32, t1 - t0, 0); ++} ++ + static inline u32 tcp_skb_timestamp(const struct sk_buff *skb) + { + return tcp_ns_to_ts(skb->skb_mstamp_ns); +@@ -898,9 +914,14 @@ + /* pkts S/ACKed so far upon tx of skb, incl retrans: */ + __u32 delivered; + /* start of send pipeline phase */ +- u64 first_tx_mstamp; ++ u32 first_tx_mstamp; + /* when we reached the "delivered" count */ +- u64 delivered_mstamp; ++ u32 delivered_mstamp; ++#define TCPCB_IN_FLIGHT_BITS 20 ++#define TCPCB_IN_FLIGHT_MAX ((1U << TCPCB_IN_FLIGHT_BITS) - 1) ++ u32 in_flight:20, /* packets in flight at transmit */ ++ unused2:12; ++ u32 lost; /* packets lost so far upon tx of skb */ + } tx; /* only used for outgoing skbs */ + union { + struct inet_skb_parm h4; +@@ -1004,6 +1025,7 @@ + CA_EVENT_LOSS, /* loss timeout */ + CA_EVENT_ECN_NO_CE, /* ECT set, but not CE marked */ + CA_EVENT_ECN_IS_CE, /* received CE marked IP packet */ ++ CA_EVENT_TLP_RECOVERY, /* a lost segment was repaired by TLP probe */ + }; + + /* Information about inbound ACK, passed to cong_ops->in_ack_event() */ +@@ -1026,7 +1048,11 @@ + #define TCP_CONG_NON_RESTRICTED 0x1 + /* Requires ECN/ECT set on all packets */ + #define TCP_CONG_NEEDS_ECN 0x2 +-#define TCP_CONG_MASK (TCP_CONG_NON_RESTRICTED | TCP_CONG_NEEDS_ECN) ++/* Wants notification of CE events (CA_EVENT_ECN_IS_CE, CA_EVENT_ECN_NO_CE). */ ++#define TCP_CONG_WANTS_CE_EVENTS 0x4 ++#define TCP_CONG_MASK (TCP_CONG_NON_RESTRICTED | \ ++ TCP_CONG_NEEDS_ECN | \ ++ TCP_CONG_WANTS_CE_EVENTS) + + union tcp_cc_info; + +@@ -1046,10 +1072,13 @@ + */ + struct rate_sample { + u64 prior_mstamp; /* starting timestamp for interval */ ++ u32 prior_lost; /* tp->lost at "prior_mstamp" */ + u32 prior_delivered; /* tp->delivered at "prior_mstamp" */ + u32 prior_delivered_ce;/* tp->delivered_ce at "prior_mstamp" */ ++ u32 tx_in_flight; /* packets in flight at starting timestamp */ ++ s32 lost; /* number of packets lost over interval */ + s32 delivered; /* number of packets delivered over interval */ +- s32 delivered_ce; /* number of packets delivered w/ CE marks*/ ++ s32 delivered_ce; /* packets delivered w/ CE mark over interval */ + long interval_us; /* time for tp->delivered to incr "delivered" */ + u32 snd_interval_us; /* snd interval for delivered packets */ + u32 rcv_interval_us; /* rcv interval for delivered packets */ +@@ -1060,7 +1089,9 @@ + u32 last_end_seq; /* end_seq of most recently ACKed packet */ + bool is_app_limited; /* is sample from packet with bubble in pipe? */ + bool is_retrans; /* is sample from retransmission? */ ++ bool is_acking_tlp_retrans_seq; /* ACKed a TLP retransmit sequence? */ + bool is_ack_delayed; /* is this (likely) a delayed ACK? */ ++ bool is_ece; /* did this ACK have ECN marked? */ + }; + + struct tcp_congestion_ops { +@@ -1084,8 +1115,11 @@ + /* hook for packet ack accounting (optional) */ + void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample); + +- /* override sysctl_tcp_min_tso_segs */ +- u32 (*min_tso_segs)(struct sock *sk); ++ /* pick target number of segments per TSO/GSO skb (optional): */ ++ u32 (*tso_segs)(struct sock *sk, unsigned int mss_now); ++ ++ /* react to a specific lost skb (optional) */ ++ void (*skb_marked_lost)(struct sock *sk, const struct sk_buff *skb); + + /* call when packets are delivered to update cwnd and pacing rate, + * after all the ca_state processing. (optional) +@@ -1151,6 +1185,14 @@ + } + #endif + ++static inline bool tcp_ca_wants_ce_events(const struct sock *sk) ++{ ++ const struct inet_connection_sock *icsk = inet_csk(sk); ++ ++ return icsk->icsk_ca_ops->flags & (TCP_CONG_NEEDS_ECN | ++ TCP_CONG_WANTS_CE_EVENTS); ++} ++ + static inline bool tcp_ca_needs_ecn(const struct sock *sk) + { + const struct inet_connection_sock *icsk = inet_csk(sk); +@@ -1170,6 +1212,7 @@ + void tcp_set_ca_state(struct sock *sk, const u8 ca_state); + + /* From tcp_rate.c */ ++void tcp_set_tx_in_flight(struct sock *sk, struct sk_buff *skb); + void tcp_rate_skb_sent(struct sock *sk, struct sk_buff *skb); + void tcp_rate_skb_delivered(struct sock *sk, struct sk_buff *skb, + struct rate_sample *rs); +@@ -1182,6 +1225,21 @@ + return t1 > t2 || (t1 == t2 && after(seq1, seq2)); + } + ++/* If a retransmit failed due to local qdisc congestion or other local issues, ++ * then we may have called tcp_set_skb_tso_segs() to increase the number of ++ * segments in the skb without increasing the tx.in_flight. In all other cases, ++ * the tx.in_flight should be at least as big as the pcount of the sk_buff. We ++ * do not have the state to know whether a retransmit failed due to local qdisc ++ * congestion or other local issues, so to avoid spurious warnings we consider ++ * that any skb marked lost may have suffered that fate. ++ */ ++static inline bool tcp_skb_tx_in_flight_is_suspicious(u32 skb_pcount, ++ u32 skb_sacked_flags, ++ u32 tx_in_flight) ++{ ++ return (skb_pcount > tx_in_flight) && !(skb_sacked_flags & TCPCB_LOST); ++} ++ + /* These functions determine how the current flow behaves in respect of SACK + * handling. SACK is negotiated with the peer, and therefore it can vary + * between different flows. +@@ -2181,7 +2239,7 @@ + u8 consec_cong_rounds:5, /* consecutive congested rounds */ + unused:3; + u32 pause_until; /* jiffies32 when PLB can resume rerouting */ +-}; ++} __attribute__ ((__packed__)); + + static inline void tcp_plb_init(const struct sock *sk, + struct tcp_plb_state *plb) +diff '--color=auto' -uraN a/include/sound/cs35l41.h b/include/sound/cs35l41.h +--- a/include/sound/cs35l41.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/sound/cs35l41.h 2023-11-04 16:36:53.562316533 +0300 +@@ -11,7 +11,6 @@ + #define __CS35L41_H + + #include +-#include + #include + + #define CS35L41_FIRSTREG 0x00000000 +@@ -829,6 +828,7 @@ + CSPL_MBOX_CMD_STOP_PRE_REINIT = 4, + CSPL_MBOX_CMD_HIBERNATE = 5, + CSPL_MBOX_CMD_OUT_OF_HIBERNATE = 6, ++ CSPL_MBOX_CMD_SPK_OUT_ENABLE = 7, + CSPL_MBOX_CMD_UNKNOWN_CMD = -1, + CSPL_MBOX_CMD_INVALID_SEQUENCE = -2, + }; +@@ -901,7 +901,8 @@ + 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_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable, +- struct completion *pll_lock); ++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, bool firmware_running); + + #endif /* __CS35L41_H */ +diff '--color=auto' -uraN a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h +--- a/include/uapi/asm-generic/unistd.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/asm-generic/unistd.h 2023-11-04 16:35:57.837983905 +0300 +@@ -820,8 +820,17 @@ + #define __NR_cachestat 451 + __SYSCALL(__NR_cachestat, sys_cachestat) + ++#define __NR_process_ksm_enable 452 ++__SYSCALL(__NR_process_ksm_enable, sys_process_ksm_enable) ++ ++#define __NR_process_ksm_disable 453 ++__SYSCALL(__NR_process_ksm_disable, sys_process_ksm_disable) ++ ++#define __NR_process_ksm_status 454 ++__SYSCALL(__NR_process_ksm_status, sys_process_ksm_status) ++ + #undef __NR_syscalls +-#define __NR_syscalls 452 ++#define __NR_syscalls 455 + + /* + * 32 bit systems traditionally used different +diff '--color=auto' -uraN a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h +--- a/include/uapi/drm/drm_mode.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/drm/drm_mode.h 2023-11-04 16:35:57.794649794 +0300 +@@ -843,6 +843,14 @@ + __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 +diff '--color=auto' -uraN a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h +--- a/include/uapi/linux/inet_diag.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/linux/inet_diag.h 2023-11-04 16:35:57.801316580 +0300 +@@ -229,6 +229,29 @@ + __u32 bbr_min_rtt; /* min-filtered RTT in uSec */ + __u32 bbr_pacing_gain; /* pacing gain shifted left 8 bits */ + __u32 bbr_cwnd_gain; /* cwnd gain shifted left 8 bits */ ++ __u32 bbr_bw_hi_lsb; /* lower 32 bits of bw_hi */ ++ __u32 bbr_bw_hi_msb; /* upper 32 bits of bw_hi */ ++ __u32 bbr_bw_lo_lsb; /* lower 32 bits of bw_lo */ ++ __u32 bbr_bw_lo_msb; /* upper 32 bits of bw_lo */ ++ __u8 bbr_mode; /* current bbr_mode in state machine */ ++ __u8 bbr_phase; /* current state machine phase */ ++ __u8 unused1; /* alignment padding; not used yet */ ++ __u8 bbr_version; /* BBR algorithm version */ ++ __u32 bbr_inflight_lo; /* lower short-term data volume bound */ ++ __u32 bbr_inflight_hi; /* higher long-term data volume bound */ ++ __u32 bbr_extra_acked; /* max excess packets ACKed in epoch */ ++}; ++ ++/* TCP BBR congestion control bbr_phase as reported in netlink/ss stats. */ ++enum tcp_bbr_phase { ++ BBR_PHASE_INVALID = 0, ++ BBR_PHASE_STARTUP = 1, ++ BBR_PHASE_DRAIN = 2, ++ BBR_PHASE_PROBE_RTT = 3, ++ BBR_PHASE_PROBE_BW_UP = 4, ++ BBR_PHASE_PROBE_BW_DOWN = 5, ++ BBR_PHASE_PROBE_BW_CRUISE = 6, ++ BBR_PHASE_PROBE_BW_REFILL = 7, + }; + + union tcp_cc_info { +diff '--color=auto' -uraN a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h +--- a/include/uapi/linux/rtnetlink.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/linux/rtnetlink.h 2023-11-04 16:35:57.801316580 +0300 +@@ -506,9 +506,11 @@ + #define RTAX_FEATURE_SACK (1 << 1) + #define RTAX_FEATURE_TIMESTAMP (1 << 2) + #define RTAX_FEATURE_ALLFRAG (1 << 3) ++#define RTAX_FEATURE_ECN_LOW (1 << 4) + + #define RTAX_FEATURE_MASK (RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | \ +- RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG) ++ RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG \ ++ | RTAX_FEATURE_ECN_LOW) + + struct rta_session { + __u8 proto; +diff '--color=auto' -uraN a/include/uapi/linux/sched/types.h b/include/uapi/linux/sched/types.h +--- a/include/uapi/linux/sched/types.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/linux/sched/types.h 2023-11-04 16:36:05.668124315 +0300 +@@ -10,6 +10,7 @@ + + #define SCHED_ATTR_SIZE_VER0 48 /* sizeof first published struct */ + #define SCHED_ATTR_SIZE_VER1 56 /* add: util_{min,max} */ ++#define SCHED_ATTR_SIZE_VER2 60 /* add: latency_nice */ + + /* + * Extended scheduling parameters data structure. +@@ -98,6 +99,22 @@ + * scheduled on a CPU with no more capacity than the specified value. + * + * A task utilization boundary can be reset by setting the attribute to -1. ++ * ++ * Latency Tolerance Attributes ++ * =========================== ++ * ++ * A subset of sched_attr attributes allows to specify the relative latency ++ * requirements of a task with respect to the other tasks running/queued in the ++ * system. ++ * ++ * @ sched_latency_nice task's latency_nice value ++ * ++ * The latency_nice of a task can have any value in a range of ++ * [MIN_LATENCY_NICE..MAX_LATENCY_NICE]. ++ * ++ * A task with latency_nice with the value of LATENCY_NICE_MIN can be ++ * taken for a task requiring a lower latency as opposed to the task with ++ * higher latency_nice. + */ + struct sched_attr { + __u32 size; +@@ -120,6 +137,8 @@ + __u32 sched_util_min; + __u32 sched_util_max; + ++ /* latency requirement hints */ ++ __s32 sched_latency_nice; + }; + + #endif /* _UAPI_LINUX_SCHED_TYPES_H */ +diff '--color=auto' -uraN a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h +--- a/include/uapi/linux/sched.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/linux/sched.h 2023-11-04 16:36:05.668124315 +0300 +@@ -132,6 +132,7 @@ + #define SCHED_FLAG_KEEP_PARAMS 0x10 + #define SCHED_FLAG_UTIL_CLAMP_MIN 0x20 + #define SCHED_FLAG_UTIL_CLAMP_MAX 0x40 ++#define SCHED_FLAG_LATENCY_NICE 0x80 + + #define SCHED_FLAG_KEEP_ALL (SCHED_FLAG_KEEP_POLICY | \ + SCHED_FLAG_KEEP_PARAMS) +@@ -143,6 +144,7 @@ + SCHED_FLAG_RECLAIM | \ + SCHED_FLAG_DL_OVERRUN | \ + SCHED_FLAG_KEEP_ALL | \ +- SCHED_FLAG_UTIL_CLAMP) ++ SCHED_FLAG_UTIL_CLAMP | \ ++ SCHED_FLAG_LATENCY_NICE) + + #endif /* _UAPI_LINUX_SCHED_H */ +diff '--color=auto' -uraN a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h +--- a/include/uapi/linux/tcp.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/linux/tcp.h 2023-11-04 16:35:57.801316580 +0300 +@@ -170,6 +170,7 @@ + #define TCPI_OPT_ECN 8 /* ECN was negociated at TCP session init */ + #define TCPI_OPT_ECN_SEEN 16 /* we received at least one packet with ECT */ + #define TCPI_OPT_SYN_DATA 32 /* SYN-ACK acked data in SYN sent or rcvd */ ++#define TCPI_OPT_ECN_LOW 64 /* Low-latency ECN configured at init */ + + /* + * Sender's congestion state indicating normal or abnormal situations +diff '--color=auto' -uraN a/include/uapi/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h +--- a/include/uapi/linux/usb/g_hid.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/include/uapi/linux/usb/g_hid.h 2023-11-04 16:36:53.572316712 +0300 +@@ -0,0 +1,22 @@ ++/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ ++ ++#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[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 */ +diff '--color=auto' -uraN a/include/uapi/linux/usb/gadgetfs.h b/include/uapi/linux/usb/gadgetfs.h +--- a/include/uapi/linux/usb/gadgetfs.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/include/uapi/linux/usb/gadgetfs.h 2023-11-04 16:36:53.572316712 +0300 +@@ -62,7 +62,7 @@ + }; + + +-/* 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). + */ +diff '--color=auto' -uraN a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/init/Kconfig 2023-11-04 16:36:09.578194431 +0300 +@@ -123,6 +123,10 @@ + + menu "General setup" + ++config CACHY ++ bool "Some kernel tweaks by CachyOS" ++ default y ++ + config BROKEN + bool + +@@ -1226,6 +1230,22 @@ + + If unsure, say N. + ++config USER_NS_UNPRIVILEGED ++ bool "Allow unprivileged users to create namespaces" ++ default y ++ depends on USER_NS ++ help ++ When disabled, unprivileged users will not be able to create ++ new namespaces. Allowing users to create their own namespaces ++ has been part of several recent local privilege escalation ++ exploits, so if you need user namespaces but are ++ paranoid^Wsecurity-conscious you want to disable this. ++ ++ This setting can be overridden at runtime via the ++ kernel.unprivileged_userns_clone sysctl. ++ ++ If unsure, say Y. ++ + config PID_NS + bool "PID Namespaces" + default y +@@ -1258,6 +1278,26 @@ + + If unsure, say N here. + ++config SCHED_BORE ++ bool "Burst-Oriented Response Enhancer" ++ default y ++ help ++ In Desktop and Mobile computing, one might prefer interactive ++ tasks to keep responsive no matter what they run in the background. ++ ++ Enabling this kernel feature modifies the scheduler to discriminate ++ tasks by their burst time (runtime since it last went sleeping or ++ yielding state) and prioritize those that run less bursty. ++ Such tasks usually include window compositor, widgets backend, ++ terminal emulator, video playback, games and so on. ++ With a little impact to scheduling fairness, it may improve ++ responsiveness especially under heavy background workload. ++ ++ You can turn it off by setting the sysctl kernel.sched_bore = 0. ++ Enabling this feature implies NO_GENTLE_FAIR_SLEEPERS by default. ++ ++ If unsure say Y here. ++ + config SCHED_AUTOGROUP + bool "Automatic process group scheduling" + select CGROUPS +@@ -1368,6 +1408,12 @@ + with the "-O2" compiler flag for best performance and most + helpful compile-time warnings. + ++config CC_OPTIMIZE_FOR_PERFORMANCE_O3 ++ bool "Optimize more for performance (-O3)" ++ help ++ Choosing this option will pass "-O3" to your compiler to optimize ++ the kernel yet more for performance. ++ + config CC_OPTIMIZE_FOR_SIZE + bool "Optimize for size (-Os)" + help +diff '--color=auto' -uraN a/init/init_task.c b/init/init_task.c +--- a/init/init_task.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/init/init_task.c 2023-11-04 16:36:05.668124315 +0300 +@@ -78,6 +78,7 @@ + .prio = MAX_PRIO - 20, + .static_prio = MAX_PRIO - 20, + .normal_prio = MAX_PRIO - 20, ++ .latency_prio = DEFAULT_PRIO, + .policy = SCHED_NORMAL, + .cpus_ptr = &init_task.cpus_mask, + .user_cpus_ptr = NULL, +@@ -89,7 +90,7 @@ + .fn = do_no_restart_syscall, + }, + .se = { +- .group_node = LIST_HEAD_INIT(init_task.se.group_node), ++ .group_node = LIST_HEAD_INIT(init_task.se.group_node), + }, + .rt = { + .run_list = LIST_HEAD_INIT(init_task.rt.run_list), +diff '--color=auto' -uraN a/kernel/Kconfig.hz b/kernel/Kconfig.hz +--- a/kernel/Kconfig.hz 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/Kconfig.hz 2023-11-04 16:35:57.817983546 +0300 +@@ -40,6 +40,27 @@ + on SMP and NUMA systems and exactly dividing by both PAL and + NTSC frame rates for video and multimedia work. + ++ config HZ_500 ++ bool "500 HZ" ++ help ++ 500 Hz is a balanced timer frequency. Provides fast interactivity ++ on desktops with good smoothness without increasing CPU power ++ consumption and sacrificing the battery life on laptops. ++ ++ config HZ_600 ++ bool "600 HZ" ++ help ++ 600 Hz is a balanced timer frequency. Provides fast interactivity ++ on desktops with good smoothness without increasing CPU power ++ consumption and sacrificing the battery life on laptops. ++ ++ config HZ_750 ++ bool "750 HZ" ++ help ++ 750 Hz is a balanced timer frequency. Provides fast interactivity ++ on desktops with good smoothness without increasing CPU power ++ consumption and sacrificing the battery life on laptops. ++ + config HZ_1000 + bool "1000 HZ" + help +@@ -53,6 +74,9 @@ + default 100 if HZ_100 + default 250 if HZ_250 + default 300 if HZ_300 ++ default 500 if HZ_500 ++ default 600 if HZ_600 ++ default 750 if HZ_750 + default 1000 if HZ_1000 + + config SCHED_HRTICK +diff '--color=auto' -uraN a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/fork.c 2023-11-04 16:35:57.817983546 +0300 +@@ -100,6 +100,10 @@ + #include + #include + ++#ifdef CONFIG_USER_NS ++#include ++#endif ++ + #include + #include + #include +@@ -2271,6 +2275,10 @@ + if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS)) + return ERR_PTR(-EINVAL); + ++ if ((clone_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) ++ if (!capable(CAP_SYS_ADMIN)) ++ return ERR_PTR(-EPERM); ++ + /* + * Thread groups must share signals as well, and detached threads + * can only be started up within the thread group. +@@ -3424,6 +3432,12 @@ + if (unshare_flags & CLONE_NEWNS) + unshare_flags |= CLONE_FS; + ++ if ((unshare_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ err = -EPERM; ++ if (!capable(CAP_SYS_ADMIN)) ++ goto bad_unshare_out; ++ } ++ + err = check_unshare_flags(unshare_flags); + if (err) + goto bad_unshare_out; +diff '--color=auto' -uraN a/kernel/padata.c b/kernel/padata.c +--- a/kernel/padata.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/padata.c 2023-11-04 16:35:57.827983725 +0300 +@@ -45,7 +45,7 @@ + }; + + static void padata_free_pd(struct parallel_data *pd); +-static void __init padata_mt_helper(struct work_struct *work); ++static void padata_mt_helper(struct work_struct *work); + + static int padata_index_to_cpu(struct parallel_data *pd, int cpu_index) + { +@@ -438,7 +438,7 @@ + return err; + } + +-static void __init padata_mt_helper(struct work_struct *w) ++static void padata_mt_helper(struct work_struct *w) + { + struct padata_work *pw = container_of(w, struct padata_work, pw_work); + struct padata_mt_job_state *ps = pw->pw_data; +diff '--color=auto' -uraN a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sched/core.c 2023-11-04 16:36:09.578194431 +0300 +@@ -1305,6 +1305,12 @@ + } + } + ++static inline void set_latency_prio(struct task_struct *p, int prio) ++{ ++ p->latency_prio = prio; ++ set_latency_fair(&p->se, prio - MAX_RT_PRIO); ++} ++ + #ifdef CONFIG_UCLAMP_TASK + /* + * Serializes updates of utilization clamp values +@@ -4485,6 +4491,155 @@ + 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; ++ ++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; ++} ++ ++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; ++} ++ ++static u32 count_child_tasks(struct task_struct *p) { ++ struct task_struct *child; ++ u32 cnt = 0; ++ list_for_each_entry(child, &p->children, sibling) {cnt++;} ++ return cnt; ++} ++ ++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); ++} ++ ++static void __update_child_burst_cache( ++ struct task_struct *p, u32 cnt, u32 sum, u64 now) { ++ u16 avg = 0; ++ if (cnt) avg = DIV_ROUND_CLOSEST(sum, cnt); ++ p->child_burst_cache = max(avg, p->se.burst_penalty); ++ p->child_burst_count_cache = cnt; ++ p->child_burst_last_cached = now; ++} ++ ++static void update_child_burst_cache(struct task_struct *p, u64 now) { ++ struct task_struct *child; ++ u32 cnt = 0; ++ u32 sum = 0; ++ ++ list_for_each_entry(child, &p->children, sibling) { ++ cnt++; ++ sum += child->se.burst_penalty; ++ } ++ ++ __update_child_burst_cache(p, cnt, sum, now); ++} ++ ++static void update_child_burst_cache_atavistic( ++ struct task_struct *p, u64 now, u32 depth, u32 *acnt, u32 *asum) { ++ struct task_struct *child, *dec; ++ u32 cnt = 0, dcnt = 0; ++ u32 sum = 0; ++ ++ list_for_each_entry(child, &p->children, sibling) { ++ dec = child; ++ while ((dcnt = count_child_tasks(dec)) == 1) ++ dec = list_first_entry(&dec->children, struct task_struct, sibling); ++ ++ if (!dcnt || !depth) { ++ 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; ++ } ++ } ++ } ++ ++ __update_child_burst_cache(p, cnt, sum, now); ++ *acnt += cnt; ++ *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 = DIV_ROUND_CLOSEST(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) ++ ++static void fork_burst_penalty(struct task_struct *p) { ++ struct sched_entity *se = &p->se; ++ struct task_struct *anc; ++ u64 now = ktime_get_ns(); ++ u32 cnt = 0, sum = 0; ++ u16 burst_cache; ++ ++ if (likely(sched_bore)) { ++ read_lock(&tasklist_lock); ++ ++ if (forked_task_is_process(p)) { ++ anc = p->real_parent; ++ if (likely(sched_burst_fork_atavistic)) { ++ 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, sched_burst_fork_atavistic - 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; ++ } ++ ++ read_unlock(&tasklist_lock); ++ se->prev_burst_penalty = max(se->prev_burst_penalty, burst_cache); ++ } ++ se->burst_penalty = se->prev_burst_penalty; ++} ++#endif // CONFIG_SCHED_BORE ++ + /* + * Perform scheduler related setup for a newly forked process p. + * p is forked by current. +@@ -4501,8 +4656,14 @@ + p->se.prev_sum_exec_runtime = 0; + p->se.nr_migrations = 0; + p->se.vruntime = 0; ++#ifdef CONFIG_SCHED_BORE ++ sched_fork_bore(p); ++#endif // CONFIG_SCHED_BORE ++ p->se.vlag = 0; + INIT_LIST_HEAD(&p->se.group_node); + ++ set_latency_prio(p, p->latency_prio); ++ + #ifdef CONFIG_FAIR_GROUP_SCHED + p->se.cfs_rq = NULL; + #endif +@@ -4754,6 +4915,7 @@ + + p->prio = p->normal_prio = p->static_prio; + set_load_weight(p, false); ++ set_latency_prio(p, NICE_TO_PRIO(0)); + + /* + * We don't need the reset flag anymore after the fork. It has +@@ -4818,6 +4980,9 @@ + + void sched_post_fork(struct task_struct *p) + { ++#ifdef CONFIG_SCHED_BORE ++ fork_burst_penalty(p); ++#endif // CONFIG_SCHED_BORE + uclamp_post_fork(p); + } + +@@ -7516,7 +7681,7 @@ + #define SETPARAM_POLICY -1 + + static void __setscheduler_params(struct task_struct *p, +- const struct sched_attr *attr) ++ const struct sched_attr *attr) + { + int policy = attr->sched_policy; + +@@ -7540,6 +7705,13 @@ + set_load_weight(p, true); + } + ++static void __setscheduler_latency(struct task_struct *p, ++ const struct sched_attr *attr) ++{ ++ if (attr->sched_flags & SCHED_FLAG_LATENCY_NICE) ++ set_latency_prio(p, NICE_TO_PRIO(attr->sched_latency_nice)); ++} ++ + /* + * Check the target process has a UID that matches the current process's: + */ +@@ -7674,6 +7846,13 @@ + return retval; + } + ++ if (attr->sched_flags & SCHED_FLAG_LATENCY_NICE) { ++ if (attr->sched_latency_nice > MAX_NICE) ++ return -EINVAL; ++ if (attr->sched_latency_nice < MIN_NICE) ++ return -EINVAL; ++ } ++ + /* Update task specific "requested" clamps */ + if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) { + retval = uclamp_validate(p, attr); +@@ -7721,6 +7900,9 @@ + goto change; + if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) + goto change; ++ if (attr->sched_flags & SCHED_FLAG_LATENCY_NICE && ++ attr->sched_latency_nice != PRIO_TO_NICE(p->latency_prio)) ++ goto change; + + p->sched_reset_on_fork = reset_on_fork; + retval = 0; +@@ -7809,6 +7991,7 @@ + __setscheduler_params(p, attr); + __setscheduler_prio(p, newprio); + } ++ __setscheduler_latency(p, attr); + __setscheduler_uclamp(p, attr); + + if (queued) { +@@ -8020,6 +8203,9 @@ + size < SCHED_ATTR_SIZE_VER1) + return -EINVAL; + ++ if ((attr->sched_flags & SCHED_FLAG_LATENCY_NICE) && ++ size < SCHED_ATTR_SIZE_VER2) ++ return -EINVAL; + /* + * XXX: Do we want to be lenient like existing syscalls; or do we want + * to be strict and return an error on out-of-bounds values? +@@ -8257,6 +8443,8 @@ + get_params(p, &kattr); + kattr.sched_flags &= SCHED_FLAG_ALL; + ++ kattr.sched_latency_nice = PRIO_TO_NICE(p->latency_prio); ++ + #ifdef CONFIG_UCLAMP_TASK + /* + * This could race with another potential updater, but this is fine +@@ -9921,6 +10109,11 @@ + 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.1.4 by Masahito Suzuki"); ++#endif // CONFIG_SCHED_BORE ++ + wait_bit_init(); + + #ifdef CONFIG_FAIR_GROUP_SCHED +@@ -11180,6 +11373,25 @@ + { + return sched_group_set_idle(css_tg(css), idle); + } ++ ++static s64 cpu_latency_nice_read_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return PRIO_TO_NICE(css_tg(css)->latency_prio); ++} ++ ++static int cpu_latency_nice_write_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft, s64 nice) ++{ ++ int prio; ++ ++ if (nice < MIN_NICE || nice > MAX_NICE) ++ return -ERANGE; ++ ++ prio = NICE_TO_PRIO(nice); ++ ++ return sched_group_set_latency(css_tg(css), prio); ++} + #endif + + static struct cftype cpu_legacy_files[] = { +@@ -11194,6 +11406,11 @@ + .read_s64 = cpu_idle_read_s64, + .write_s64 = cpu_idle_write_s64, + }, ++ { ++ .name = "latency.nice", ++ .read_s64 = cpu_latency_nice_read_s64, ++ .write_s64 = cpu_latency_nice_write_s64, ++ }, + #endif + #ifdef CONFIG_CFS_BANDWIDTH + { +@@ -11411,6 +11628,12 @@ + .read_s64 = cpu_idle_read_s64, + .write_s64 = cpu_idle_write_s64, + }, ++ { ++ .name = "latency.nice", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_s64 = cpu_latency_nice_read_s64, ++ .write_s64 = cpu_latency_nice_write_s64, ++ }, + #endif + #ifdef CONFIG_CFS_BANDWIDTH + { +diff '--color=auto' -uraN a/kernel/sched/debug.c b/kernel/sched/debug.c +--- a/kernel/sched/debug.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sched/debug.c 2023-11-04 16:36:09.578194431 +0300 +@@ -347,10 +347,7 @@ + debugfs_create_file("preempt", 0644, debugfs_sched, NULL, &sched_dynamic_fops); + #endif + +- debugfs_create_u32("latency_ns", 0644, debugfs_sched, &sysctl_sched_latency); +- debugfs_create_u32("min_granularity_ns", 0644, debugfs_sched, &sysctl_sched_min_granularity); +- debugfs_create_u32("idle_min_granularity_ns", 0644, debugfs_sched, &sysctl_sched_idle_min_granularity); +- debugfs_create_u32("wakeup_granularity_ns", 0644, debugfs_sched, &sysctl_sched_wakeup_granularity); ++ debugfs_create_u32("base_slice_ns", 0644, debugfs_sched, &sysctl_sched_base_slice); + + debugfs_create_u32("latency_warn_ms", 0644, debugfs_sched, &sysctl_resched_latency_warn_ms); + debugfs_create_u32("latency_warn_once", 0644, debugfs_sched, &sysctl_resched_latency_warn_once); +@@ -581,9 +578,13 @@ + else + SEQ_printf(m, " %c", task_state_to_char(p)); + +- SEQ_printf(m, " %15s %5d %9Ld.%06ld %9Ld %5d ", ++ SEQ_printf(m, "%15s %5d %9Ld.%06ld %c %9Ld.%06ld %9Ld.%06ld %9Ld.%06ld %9Ld %5d ", + p->comm, task_pid_nr(p), + SPLIT_NS(p->se.vruntime), ++ entity_eligible(cfs_rq_of(&p->se), &p->se) ? 'E' : 'N', ++ SPLIT_NS(p->se.deadline), ++ SPLIT_NS(p->se.slice), ++ SPLIT_NS(p->se.sum_exec_runtime), + (long long)(p->nvcsw + p->nivcsw), + p->prio); + +@@ -593,6 +594,9 @@ + SPLIT_NS(schedstat_val_or_zero(p->stats.sum_sleep_runtime)), + 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]); ++#endif + #ifdef CONFIG_NUMA_BALANCING + SEQ_printf(m, " %d %d", task_node(p), task_numa_group_id(p)); + #endif +@@ -626,10 +630,9 @@ + + void print_cfs_rq(struct seq_file *m, int cpu, struct cfs_rq *cfs_rq) + { +- s64 MIN_vruntime = -1, min_vruntime, max_vruntime = -1, +- spread, rq0_min_vruntime, spread0; ++ s64 left_vruntime = -1, min_vruntime, right_vruntime = -1, spread; ++ struct sched_entity *last, *first; + struct rq *rq = cpu_rq(cpu); +- struct sched_entity *last; + unsigned long flags; + + #ifdef CONFIG_FAIR_GROUP_SCHED +@@ -643,26 +646,25 @@ + SPLIT_NS(cfs_rq->exec_clock)); + + raw_spin_rq_lock_irqsave(rq, flags); +- if (rb_first_cached(&cfs_rq->tasks_timeline)) +- MIN_vruntime = (__pick_first_entity(cfs_rq))->vruntime; ++ first = __pick_first_entity(cfs_rq); ++ if (first) ++ left_vruntime = first->vruntime; + last = __pick_last_entity(cfs_rq); + if (last) +- max_vruntime = last->vruntime; ++ right_vruntime = last->vruntime; + min_vruntime = cfs_rq->min_vruntime; +- rq0_min_vruntime = cpu_rq(0)->cfs.min_vruntime; + raw_spin_rq_unlock_irqrestore(rq, flags); +- SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "MIN_vruntime", +- SPLIT_NS(MIN_vruntime)); ++ ++ SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "left_vruntime", ++ SPLIT_NS(left_vruntime)); + SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "min_vruntime", + SPLIT_NS(min_vruntime)); +- SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "max_vruntime", +- SPLIT_NS(max_vruntime)); +- spread = max_vruntime - MIN_vruntime; +- SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "spread", +- SPLIT_NS(spread)); +- spread0 = min_vruntime - rq0_min_vruntime; +- SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "spread0", +- SPLIT_NS(spread0)); ++ SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "avg_vruntime", ++ SPLIT_NS(avg_vruntime(cfs_rq))); ++ SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "right_vruntime", ++ SPLIT_NS(right_vruntime)); ++ spread = right_vruntime - left_vruntime; ++ SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "spread", SPLIT_NS(spread)); + SEQ_printf(m, " .%-30s: %d\n", "nr_spread_over", + cfs_rq->nr_spread_over); + SEQ_printf(m, " .%-30s: %d\n", "nr_running", cfs_rq->nr_running); +@@ -863,10 +865,7 @@ + SEQ_printf(m, " .%-40s: %Ld\n", #x, (long long)(x)) + #define PN(x) \ + SEQ_printf(m, " .%-40s: %Ld.%06ld\n", #x, SPLIT_NS(x)) +- PN(sysctl_sched_latency); +- PN(sysctl_sched_min_granularity); +- PN(sysctl_sched_idle_min_granularity); +- PN(sysctl_sched_wakeup_granularity); ++ PN(sysctl_sched_base_slice); + P(sysctl_sched_child_runs_first); + P(sysctl_sched_features); + #undef PN +@@ -1089,6 +1088,7 @@ + #endif + P(policy); + P(prio); ++ P(latency_prio); + if (task_has_dl_policy(p)) { + P(dl.runtime); + P(dl.deadline); +diff '--color=auto' -uraN a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sched/fair.c 2023-11-04 16:36:09.578194431 +0300 +@@ -19,6 +19,9 @@ + * + * Adaptive scheduling granularity, math enhancements by Peter Zijlstra + * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra ++ * ++ * Burst-Oriented Response Enhancer (BORE) CPU Scheduler ++ * Copyright (C) 2021-2023 Masahito Suzuki + */ + #include + #include +@@ -47,6 +50,7 @@ + #include + #include + #include ++#include + + #include + +@@ -57,22 +61,6 @@ + #include "autogroup.h" + + /* +- * Targeted preemption latency for CPU-bound tasks: +- * +- * NOTE: this latency value is not the same as the concept of +- * 'timeslice length' - timeslices in CFS are of variable length +- * and have no persistent notion like in traditional, time-slice +- * based scheduling concepts. +- * +- * (to see the precise effective timeslice length of your workload, +- * run vmstat and monitor the context-switches (cs) field) +- * +- * (default: 6ms * (1 + ilog(ncpus)), units: nanoseconds) +- */ +-unsigned int sysctl_sched_latency = 6000000ULL; +-static unsigned int normalized_sysctl_sched_latency = 6000000ULL; +- +-/* + * The initial- and re-scaling of tunables is configurable + * + * Options are: +@@ -90,21 +78,8 @@ + * + * (default: 0.75 msec * (1 + ilog(ncpus)), units: nanoseconds) + */ +-unsigned int sysctl_sched_min_granularity = 750000ULL; +-static unsigned int normalized_sysctl_sched_min_granularity = 750000ULL; +- +-/* +- * Minimal preemption granularity for CPU-bound SCHED_IDLE tasks. +- * Applies only when SCHED_IDLE tasks compete with normal tasks. +- * +- * (default: 0.75 msec) +- */ +-unsigned int sysctl_sched_idle_min_granularity = 750000ULL; +- +-/* +- * This value is kept at sysctl_sched_latency/sysctl_sched_min_granularity +- */ +-static unsigned int sched_nr_latency = 8; ++unsigned int sysctl_sched_base_slice = 750000ULL; ++static unsigned int normalized_sysctl_sched_base_slice = 750000ULL; + + /* + * After fork, child runs first. If set to 0 (default) then +@@ -112,20 +87,69 @@ + */ + unsigned int sysctl_sched_child_runs_first __read_mostly; + +-/* +- * SCHED_OTHER wake-up granularity. +- * +- * This option delays the preemption effects of decoupled workloads +- * and reduces their over-scheduling. Synchronous workloads will still +- * have immediate wakeup/sleep latencies. +- * +- * (default: 1 msec * (1 + ilog(ncpus)), units: nanoseconds) +- */ +-unsigned int sysctl_sched_wakeup_granularity = 1000000UL; +-static unsigned int normalized_sysctl_sched_wakeup_granularity = 1000000UL; +- + 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_bore_extra_flags = 0; ++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 = 1366; ++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 sixty_four = 64; ++static int maxval_12_bits = 4095; ++ ++#define MAX_BURST_PENALTY ((40U << 8) - 1) ++ ++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; ++} ++ ++static inline u32 calc_burst_penalty(u64 burst_time) { ++ u32 greed, tolerance, penalty, scaled_penalty; ++ ++ 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; ++ ++ return min(MAX_BURST_PENALTY, scaled_penalty); ++} ++ ++static 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, bool half) { ++ u32 score = ((x16*)&se->burst_penalty)->u8[1]; ++ if (half) score >>= 1; ++ return mul_u64_u32_shr(delta, sched_prio_to_wmult[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); ++} ++ ++static void restart_burst(struct sched_entity *se) { ++ se->burst_penalty = se->prev_burst_penalty = ++ binary_smooth(se->curr_burst_penalty, se->prev_burst_penalty); ++ se->curr_burst_penalty = 0; ++ se->burst_time = 0; ++} ++#endif // CONFIG_SCHED_BORE ++ + int sched_thermal_decay_shift; + static int __init setup_sched_thermal_decay_shift(char *str) + { +@@ -185,6 +209,78 @@ + + #ifdef CONFIG_SYSCTL + static struct ctl_table sched_fair_sysctls[] = { ++#ifdef CONFIG_SCHED_BORE ++ { ++ .procname = "sched_bore", ++ .data = &sched_bore, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_bore_extra_flags", ++ .data = &sched_bore_extra_flags, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_burst_cache_lifetime", ++ .data = &sched_burst_cache_lifetime, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec, ++ }, ++ { ++ .procname = "sched_burst_fork_atavistic", ++ .data = &sched_burst_fork_atavistic, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &three, ++ }, ++ { ++ .procname = "sched_burst_penalty_offset", ++ .data = &sched_burst_penalty_offset, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &sixty_four, ++ }, ++ { ++ .procname = "sched_burst_penalty_scale", ++ .data = &sched_burst_penalty_scale, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &maxval_12_bits, ++ }, ++ { ++ .procname = "sched_burst_smoothness_down", ++ .data = &sched_burst_smoothness_down, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &three, ++ }, ++ { ++ .procname = "sched_burst_smoothness_up", ++ .data = &sched_burst_smoothness_up, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = &proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &three, ++ }, ++#endif // CONFIG_SCHED_BORE + { + .procname = "sched_child_runs_first", + .data = &sysctl_sched_child_runs_first, +@@ -277,9 +373,7 @@ + + #define SET_SYSCTL(name) \ + (sysctl_##name = (factor) * normalized_sysctl_##name) +- SET_SYSCTL(sched_min_granularity); +- SET_SYSCTL(sched_latency); +- SET_SYSCTL(sched_wakeup_granularity); ++ SET_SYSCTL(sched_base_slice); + #undef SET_SYSCTL + } + +@@ -347,6 +441,27 @@ + return mul_u64_u32_shr(delta_exec, fact, shift); + } + ++/* ++ * delta /= w ++ */ ++#ifdef CONFIG_SCHED_BORE ++#define bore_start_debit_full_penalty (sched_bore_extra_flags) ++#define calc_delta_fair_debit(delta, se) \ ++ __calc_delta_fair(delta, se, !bore_start_debit_full_penalty) ++#define calc_delta_fair(delta, se) __calc_delta_fair(delta, se, false) ++static inline u64 __calc_delta_fair(u64 delta, struct sched_entity *se, bool half) ++#else // CONFIG_SCHED_BORE ++static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) ++#endif // CONFIG_SCHED_BORE ++{ ++ 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, half); ++#endif // CONFIG_SCHED_BORE ++ return delta; ++} + + const struct sched_class fair_sched_class; + +@@ -601,13 +716,198 @@ + return (s64)(a->vruntime - b->vruntime) < 0; + } + ++static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se) ++{ ++ return (s64)(se->vruntime - cfs_rq->min_vruntime); ++} ++ + #define __node_2_se(node) \ + rb_entry((node), struct sched_entity, run_node) + ++/* ++ * Compute virtual time from the per-task service numbers: ++ * ++ * Fair schedulers conserve lag: ++ * ++ * \Sum lag_i = 0 ++ * ++ * Where lag_i is given by: ++ * ++ * lag_i = S - s_i = w_i * (V - v_i) ++ * ++ * Where S is the ideal service time and V is it's virtual time counterpart. ++ * Therefore: ++ * ++ * \Sum lag_i = 0 ++ * \Sum w_i * (V - v_i) = 0 ++ * \Sum w_i * V - w_i * v_i = 0 ++ * ++ * From which we can solve an expression for V in v_i (which we have in ++ * se->vruntime): ++ * ++ * \Sum v_i * w_i \Sum v_i * w_i ++ * V = -------------- = -------------- ++ * \Sum w_i W ++ * ++ * Specifically, this is the weighted average of all entity virtual runtimes. ++ * ++ * [[ NOTE: this is only equal to the ideal scheduler under the condition ++ * that join/leave operations happen at lag_i = 0, otherwise the ++ * virtual time has non-continguous motion equivalent to: ++ * ++ * V +-= lag_i / W ++ * ++ * Also see the comment in place_entity() that deals with this. ]] ++ * ++ * However, since v_i is u64, and the multiplcation could easily overflow ++ * transform it into a relative form that uses smaller quantities: ++ * ++ * Substitute: v_i == (v_i - v0) + v0 ++ * ++ * \Sum ((v_i - v0) + v0) * w_i \Sum (v_i - v0) * w_i ++ * V = ---------------------------- = --------------------- + v0 ++ * W W ++ * ++ * Which we track using: ++ * ++ * v0 := cfs_rq->min_vruntime ++ * \Sum (v_i - v0) * w_i := cfs_rq->avg_vruntime ++ * \Sum w_i := cfs_rq->avg_load ++ * ++ * Since min_vruntime is a monotonic increasing variable that closely tracks ++ * the per-task service, these deltas: (v_i - v), will be in the order of the ++ * maximal (virtual) lag induced in the system due to quantisation. ++ * ++ * Also, we use scale_load_down() to reduce the size. ++ * ++ * As measured, the max (key * weight) value was ~44 bits for a kernel build. ++ */ ++static void ++avg_vruntime_add(struct cfs_rq *cfs_rq, struct sched_entity *se) ++{ ++ unsigned long weight = scale_load_down(se->load.weight); ++ s64 key = entity_key(cfs_rq, se); ++ ++ cfs_rq->avg_vruntime += key * weight; ++ cfs_rq->avg_load += weight; ++} ++ ++static void ++avg_vruntime_sub(struct cfs_rq *cfs_rq, struct sched_entity *se) ++{ ++ unsigned long weight = scale_load_down(se->load.weight); ++ s64 key = entity_key(cfs_rq, se); ++ ++ cfs_rq->avg_vruntime -= key * weight; ++ cfs_rq->avg_load -= weight; ++} ++ ++static inline ++void avg_vruntime_update(struct cfs_rq *cfs_rq, s64 delta) ++{ ++ /* ++ * v' = v + d ==> avg_vruntime' = avg_runtime - d*avg_load ++ */ ++ cfs_rq->avg_vruntime -= cfs_rq->avg_load * delta; ++} ++ ++u64 avg_vruntime(struct cfs_rq *cfs_rq) ++{ ++ struct sched_entity *curr = cfs_rq->curr; ++ s64 avg = cfs_rq->avg_vruntime; ++ long load = cfs_rq->avg_load; ++ ++ if (curr && curr->on_rq) { ++ unsigned long weight = scale_load_down(curr->load.weight); ++ ++ avg += entity_key(cfs_rq, curr) * weight; ++ load += weight; ++ } ++ ++ if (load) ++ avg = div_s64(avg, load); ++ ++ return cfs_rq->min_vruntime + avg; ++} ++ ++/* ++ * lag_i = S - s_i = w_i * (V - v_i) ++ * ++ * However, since V is approximated by the weighted average of all entities it ++ * is possible -- by addition/removal/reweight to the tree -- to move V around ++ * and end up with a larger lag than we started with. ++ * ++ * Limit this to either double the slice length with a minimum of TICK_NSEC ++ * since that is the timing granularity. ++ * ++ * EEVDF gives the following limit for a steady state system: ++ * ++ * -r_max < lag < max(r_max, q) ++ * ++ * XXX could add max_slice to the augmented data to track this. ++ */ ++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); ++} ++ ++/* ++ * Entity is eligible once it received less service than it ought to have, ++ * eg. lag >= 0. ++ * ++ * lag_i = S - s_i = w_i*(V - v_i) ++ * ++ * lag_i >= 0 -> V >= v_i ++ * ++ * \Sum (v_i - v)*w_i ++ * V = ------------------ + v ++ * \Sum w_i ++ * ++ * lag_i >= 0 -> \Sum (v_i - v)*w_i >= (v_i - v)*(\Sum w_i) ++ * ++ * Note: using 'avg_vruntime() > se->vruntime' is inacurate due ++ * to the loss in precision caused by the division. ++ */ ++int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se) ++{ ++ struct sched_entity *curr = cfs_rq->curr; ++ s64 avg = cfs_rq->avg_vruntime; ++ long load = cfs_rq->avg_load; ++ ++ if (curr && curr->on_rq) { ++ unsigned long weight = scale_load_down(curr->load.weight); ++ ++ avg += entity_key(cfs_rq, curr) * weight; ++ load += weight; ++ } ++ ++ return avg >= entity_key(cfs_rq, se) * load; ++} ++ ++static u64 __update_min_vruntime(struct cfs_rq *cfs_rq, u64 vruntime) ++{ ++ u64 min_vruntime = cfs_rq->min_vruntime; ++ /* ++ * open coded max_vruntime() to allow updating avg_vruntime ++ */ ++ s64 delta = (s64)(vruntime - min_vruntime); ++ if (delta > 0) { ++ avg_vruntime_update(cfs_rq, delta); ++ min_vruntime = vruntime; ++ } ++ return min_vruntime; ++} ++ + static void update_min_vruntime(struct cfs_rq *cfs_rq) + { ++ struct sched_entity *se = __pick_first_entity(cfs_rq); + struct sched_entity *curr = cfs_rq->curr; +- struct rb_node *leftmost = rb_first_cached(&cfs_rq->tasks_timeline); + + u64 vruntime = cfs_rq->min_vruntime; + +@@ -618,9 +918,7 @@ + curr = NULL; + } + +- if (leftmost) { /* non-empty tree */ +- struct sched_entity *se = __node_2_se(leftmost); +- ++ if (se) { + if (!curr) + vruntime = se->vruntime; + else +@@ -629,7 +927,7 @@ + + /* ensure we never gain time by being placed backwards. */ + u64_u32_store(cfs_rq->min_vruntime, +- max_vruntime(cfs_rq->min_vruntime, vruntime)); ++ __update_min_vruntime(cfs_rq, vruntime)); + } + + static inline bool __entity_less(struct rb_node *a, const struct rb_node *b) +@@ -637,17 +935,51 @@ + return entity_before(__node_2_se(a), __node_2_se(b)); + } + ++#define deadline_gt(field, lse, rse) ({ (s64)((lse)->field - (rse)->field) > 0; }) ++ ++static inline void __update_min_deadline(struct sched_entity *se, struct rb_node *node) ++{ ++ if (node) { ++ struct sched_entity *rse = __node_2_se(node); ++ if (deadline_gt(min_deadline, se, rse)) ++ se->min_deadline = rse->min_deadline; ++ } ++} ++ ++/* ++ * se->min_deadline = min(se->deadline, left->min_deadline, right->min_deadline) ++ */ ++static inline bool min_deadline_update(struct sched_entity *se, bool exit) ++{ ++ u64 old_min_deadline = se->min_deadline; ++ struct rb_node *node = &se->run_node; ++ ++ se->min_deadline = se->deadline; ++ __update_min_deadline(se, node->rb_right); ++ __update_min_deadline(se, node->rb_left); ++ ++ return se->min_deadline == old_min_deadline; ++} ++ ++RB_DECLARE_CALLBACKS(static, min_deadline_cb, struct sched_entity, ++ run_node, min_deadline, min_deadline_update); ++ + /* + * Enqueue an entity into the rb-tree: + */ + static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +- rb_add_cached(&se->run_node, &cfs_rq->tasks_timeline, __entity_less); ++ avg_vruntime_add(cfs_rq, se); ++ se->min_deadline = se->deadline; ++ rb_add_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, ++ __entity_less, &min_deadline_cb); + } + + static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +- rb_erase_cached(&se->run_node, &cfs_rq->tasks_timeline); ++ rb_erase_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, ++ &min_deadline_cb); ++ avg_vruntime_sub(cfs_rq, se); + } + + struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq) +@@ -660,14 +992,88 @@ + return __node_2_se(left); + } + +-static struct sched_entity *__pick_next_entity(struct sched_entity *se) ++/* ++ * Earliest Eligible Virtual Deadline First ++ * ++ * In order to provide latency guarantees for different request sizes ++ * EEVDF selects the best runnable task from two criteria: ++ * ++ * 1) the task must be eligible (must be owed service) ++ * ++ * 2) from those tasks that meet 1), we select the one ++ * with the earliest virtual deadline. ++ * ++ * We can do this in O(log n) time due to an augmented RB-tree. The ++ * tree keeps the entries sorted on service, but also functions as a ++ * heap based on the deadline by keeping: ++ * ++ * se->min_deadline = min(se->deadline, se->{left,right}->min_deadline) ++ * ++ * Which allows an EDF like search on (sub)trees. ++ */ ++static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq) + { +- struct rb_node *next = rb_next(&se->run_node); ++ struct rb_node *node = cfs_rq->tasks_timeline.rb_root.rb_node; ++ struct sched_entity *curr = cfs_rq->curr; ++ struct sched_entity *best = NULL; + +- if (!next) +- return NULL; ++ if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr))) ++ curr = NULL; ++ ++ /* ++ * Once selected, run a task until it either becomes non-eligible or ++ * until it gets a new slice. See the HACK in set_next_entity(). ++ */ ++ if (sched_feat(RUN_TO_PARITY) && curr && curr->vlag == curr->deadline) ++ return curr; ++ ++ while (node) { ++ struct sched_entity *se = __node_2_se(node); ++ ++ /* ++ * If this entity is not eligible, try the left subtree. ++ */ ++ if (!entity_eligible(cfs_rq, se)) { ++ node = node->rb_left; ++ continue; ++ } ++ ++ /* ++ * If this entity has an earlier deadline than the previous ++ * best, take this one. If it also has the earliest deadline ++ * of its subtree, we're done. ++ */ ++ if (!best || deadline_gt(deadline, best, se)) { ++ best = se; ++ if (best->deadline == best->min_deadline) ++ break; ++ } ++ ++ /* ++ * If the earlest deadline in this subtree is in the fully ++ * eligible left half of our space, go there. ++ */ ++ if (node->rb_left && ++ __node_2_se(node->rb_left)->min_deadline == se->min_deadline) { ++ node = node->rb_left; ++ continue; ++ } ++ ++ node = node->rb_right; ++ } + +- return __node_2_se(next); ++ if (!best || (curr && deadline_gt(deadline, best, curr))) ++ best = curr; ++ ++ if (unlikely(!best)) { ++ struct sched_entity *left = __pick_first_entity(cfs_rq); ++ if (left) { ++ pr_err("EEVDF scheduling fail, picking leftmost\n"); ++ return left; ++ } ++ } ++ ++ return best; + } + + #ifdef CONFIG_SCHED_DEBUG +@@ -689,104 +1095,53 @@ + { + unsigned int factor = get_update_sysctl_factor(); + +- sched_nr_latency = DIV_ROUND_UP(sysctl_sched_latency, +- sysctl_sched_min_granularity); +- + #define WRT_SYSCTL(name) \ + (normalized_sysctl_##name = sysctl_##name / (factor)) +- WRT_SYSCTL(sched_min_granularity); +- WRT_SYSCTL(sched_latency); +- WRT_SYSCTL(sched_wakeup_granularity); ++ WRT_SYSCTL(sched_base_slice); + #undef WRT_SYSCTL + + return 0; + } + #endif + +-/* +- * delta /= w +- */ +-static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) ++void set_latency_fair(struct sched_entity *se, int prio) + { +- if (unlikely(se->load.weight != NICE_0_LOAD)) +- delta = __calc_delta(delta, NICE_0_LOAD, &se->load); +- +- return delta; +-} ++ u32 weight = sched_prio_to_weight[prio]; ++ u64 base = sysctl_sched_base_slice; + +-/* +- * The idea is to set a period in which each task runs once. +- * +- * When there are too many tasks (sched_nr_latency) we have to stretch +- * this period because otherwise the slices get too small. +- * +- * p = (nr <= nl) ? l : l*nr/nl +- */ +-static u64 __sched_period(unsigned long nr_running) +-{ +- if (unlikely(nr_running > sched_nr_latency)) +- return nr_running * sysctl_sched_min_granularity; +- else +- return sysctl_sched_latency; ++ /* ++ * For EEVDF the virtual time slope is determined by w_i (iow. ++ * nice) while the request time r_i is determined by ++ * latency-nice. ++ * ++ * Smaller request gets better latency. ++ */ ++ se->slice = div_u64(base << SCHED_FIXEDPOINT_SHIFT, weight); + } + +-static bool sched_idle_cfs_rq(struct cfs_rq *cfs_rq); ++static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se); + + /* +- * We calculate the wall-time slice from the period by taking a part +- * proportional to the weight. +- * +- * s = p*P[w/rw] ++ * XXX: strictly: vd_i += N*r_i/w_i such that: vd_i > ve_i ++ * this is probably good enough. + */ +-static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se) ++static void update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +- unsigned int nr_running = cfs_rq->nr_running; +- struct sched_entity *init_se = se; +- unsigned int min_gran; +- u64 slice; +- +- if (sched_feat(ALT_PERIOD)) +- nr_running = rq_of(cfs_rq)->cfs.h_nr_running; +- +- slice = __sched_period(nr_running + !se->on_rq); +- +- for_each_sched_entity(se) { +- struct load_weight *load; +- struct load_weight lw; +- struct cfs_rq *qcfs_rq; +- +- qcfs_rq = cfs_rq_of(se); +- load = &qcfs_rq->load; +- +- if (unlikely(!se->on_rq)) { +- lw = qcfs_rq->load; +- +- update_load_add(&lw, se->load.weight); +- load = &lw; +- } +- slice = __calc_delta(slice, se->load.weight, load); +- } ++ if ((s64)(se->vruntime - se->deadline) < 0) ++ return; + +- if (sched_feat(BASE_SLICE)) { +- if (se_is_idle(init_se) && !sched_idle_cfs_rq(cfs_rq)) +- min_gran = sysctl_sched_idle_min_granularity; +- else +- min_gran = sysctl_sched_min_granularity; ++ /* ++ * EEVDF: vd_i = ve_i + r_i / w_i ++ */ ++ se->deadline = se->vruntime + calc_delta_fair(se->slice, se); + +- slice = max_t(u64, slice, min_gran); ++ /* ++ * The task has consumed its request, reschedule. ++ */ ++ if (cfs_rq->nr_running > 1) { ++ resched_curr(rq_of(cfs_rq)); ++ clear_buddies(cfs_rq, se); + } +- +- return slice; +-} +- +-/* +- * We calculate the vruntime slice of a to-be-inserted task. +- * +- * vs = s/w +- */ +-static u64 sched_vslice(struct cfs_rq *cfs_rq, struct sched_entity *se) +-{ +- return calc_delta_fair(sched_slice(cfs_rq, se), se); + } + + #include "pelt.h" +@@ -920,7 +1275,12 @@ + curr->sum_exec_runtime += delta_exec; + schedstat_add(cfs_rq->exec_clock, delta_exec); + +- curr->vruntime += calc_delta_fair(delta_exec, curr); ++#ifdef CONFIG_SCHED_BORE ++ curr->burst_time += delta_exec; ++ update_burst_penalty(curr); ++#endif // CONFIG_SCHED_BORE ++ curr->vruntime += max(1ULL, calc_delta_fair(delta_exec, curr)); ++ update_deadline(cfs_rq, curr); + update_min_vruntime(cfs_rq); + + if (entity_is_task(curr)) { +@@ -3375,16 +3735,36 @@ + static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, + unsigned long weight) + { ++ unsigned long old_weight = se->load.weight; ++ + if (se->on_rq) { + /* commit outstanding execution time */ + if (cfs_rq->curr == se) + update_curr(cfs_rq); ++ else ++ avg_vruntime_sub(cfs_rq, se); + update_load_sub(&cfs_rq->load, se->load.weight); + } + dequeue_load_avg(cfs_rq, se); + + update_load_set(&se->load, weight); + ++ if (!se->on_rq) { ++ /* ++ * Because we keep se->vlag = V - v_i, while: lag_i = w_i*(V - v_i), ++ * we need to scale se->vlag when w_i changes. ++ */ ++ se->vlag = div_s64(se->vlag * old_weight, weight); ++ } else { ++ s64 deadline = se->deadline - se->vruntime; ++ /* ++ * When the weight changes, the virtual time slope changes and ++ * we should adjust the relative virtual deadline accordingly. ++ */ ++ deadline = div_s64(deadline * old_weight, weight); ++ se->deadline = se->vruntime + deadline; ++ } ++ + #ifdef CONFIG_SMP + do { + u32 divider = get_pelt_divider(&se->avg); +@@ -3394,9 +3774,11 @@ + #endif + + enqueue_load_avg(cfs_rq, se); +- if (se->on_rq) ++ if (se->on_rq) { + update_load_add(&cfs_rq->load, se->load.weight); +- ++ if (cfs_rq->curr != se) ++ avg_vruntime_add(cfs_rq, se); ++ } + } + + void reweight_task(struct task_struct *p, int prio) +@@ -4692,159 +5074,128 @@ + + #endif /* CONFIG_SMP */ + +-static void check_spread(struct cfs_rq *cfs_rq, struct sched_entity *se) +-{ +-#ifdef CONFIG_SCHED_DEBUG +- s64 d = se->vruntime - cfs_rq->min_vruntime; +- +- if (d < 0) +- d = -d; +- +- if (d > 3*sysctl_sched_latency) +- schedstat_inc(cfs_rq->nr_spread_over); +-#endif +-} +- +-static inline bool entity_is_long_sleeper(struct sched_entity *se) +-{ +- struct cfs_rq *cfs_rq; +- u64 sleep_time; +- +- if (se->exec_start == 0) +- return false; +- +- cfs_rq = cfs_rq_of(se); +- +- sleep_time = rq_clock_task(rq_of(cfs_rq)); +- +- /* Happen while migrating because of clock task divergence */ +- if (sleep_time <= se->exec_start) +- return false; +- +- sleep_time -= se->exec_start; +- if (sleep_time > ((1ULL << 63) / scale_load_down(NICE_0_LOAD))) +- return true; +- +- return false; +-} +- + static void +-place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial) ++place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + { +- u64 vruntime = cfs_rq->min_vruntime; ++#ifdef CONFIG_SCHED_BORE ++ u64 vslice = calc_delta_fair_debit(se->slice, se); ++#else // CONFIG_SCHED_BORE ++ u64 vslice = calc_delta_fair(se->slice, se); ++#endif // CONFIG_SCHED_BORE ++ u64 vruntime = avg_vruntime(cfs_rq); ++ s64 lag = 0; + + /* +- * The 'current' period is already promised to the current tasks, +- * however the extra weight of the new task will slow them down a +- * little, place the new task so that it fits in the slot that +- * stays open at the end. +- */ +- if (initial && sched_feat(START_DEBIT)) +- vruntime += sched_vslice(cfs_rq, se); +- +- /* sleeps up to a single latency don't count. */ +- if (!initial) { +- unsigned long thresh; ++ * Due to how V is constructed as the weighted average of entities, ++ * adding tasks with positive lag, or removing tasks with negative lag ++ * will move 'time' backwards, this can screw around with the lag of ++ * other tasks. ++ * ++ * EEVDF: placement strategy #1 / #2 ++ */ ++ if (sched_feat(PLACE_LAG) && cfs_rq->nr_running) { ++ struct sched_entity *curr = cfs_rq->curr; ++ unsigned long load; + +- if (se_is_idle(se)) +- thresh = sysctl_sched_min_granularity; +- else +- thresh = sysctl_sched_latency; ++ lag = se->vlag; + + /* +- * Halve their sleep time's effect, to allow +- * for a gentler effect of sleepers: ++ * If we want to place a task and preserve lag, we have to ++ * consider the effect of the new entity on the weighted ++ * average and compensate for this, otherwise lag can quickly ++ * evaporate. ++ * ++ * Lag is defined as: ++ * ++ * lag_i = S - s_i = w_i * (V - v_i) ++ * ++ * To avoid the 'w_i' term all over the place, we only track ++ * the virtual lag: ++ * ++ * vl_i = V - v_i <=> v_i = V - vl_i ++ * ++ * And we take V to be the weighted average of all v: ++ * ++ * V = (\Sum w_j*v_j) / W ++ * ++ * Where W is: \Sum w_j ++ * ++ * Then, the weighted average after adding an entity with lag ++ * vl_i is given by: ++ * ++ * V' = (\Sum w_j*v_j + w_i*v_i) / (W + w_i) ++ * = (W*V + w_i*(V - vl_i)) / (W + w_i) ++ * = (W*V + w_i*V - w_i*vl_i) / (W + w_i) ++ * = (V*(W + w_i) - w_i*l) / (W + w_i) ++ * = V - w_i*vl_i / (W + w_i) ++ * ++ * And the actual lag after adding an entity with vl_i is: ++ * ++ * vl'_i = V' - v_i ++ * = V - w_i*vl_i / (W + w_i) - (V - vl_i) ++ * = vl_i - w_i*vl_i / (W + w_i) ++ * ++ * Which is strictly less than vl_i. So in order to preserve lag ++ * we should inflate the lag before placement such that the ++ * effective lag after placement comes out right. ++ * ++ * As such, invert the above relation for vl'_i to get the vl_i ++ * we need to use such that the lag after placement is the lag ++ * we computed before dequeue. ++ * ++ * vl'_i = vl_i - w_i*vl_i / (W + w_i) ++ * = ((W + w_i)*vl_i - w_i*vl_i) / (W + w_i) ++ * ++ * (W + w_i)*vl'_i = (W + w_i)*vl_i - w_i*vl_i ++ * = W*vl_i ++ * ++ * vl_i = (W + w_i)*vl'_i / W + */ +- if (sched_feat(GENTLE_FAIR_SLEEPERS)) +- thresh >>= 1; ++ load = cfs_rq->avg_load; ++ if (curr && curr->on_rq) ++ load += scale_load_down(curr->load.weight); + +- vruntime -= thresh; ++ lag *= load + scale_load_down(se->load.weight); ++ if (WARN_ON_ONCE(!load)) ++ load = 1; ++ lag = div_s64(lag, load); + } + ++ se->vruntime = vruntime - lag; ++ + /* +- * Pull vruntime of the entity being placed to the base level of +- * cfs_rq, to prevent boosting it if placed backwards. +- * However, min_vruntime can advance much faster than real time, with +- * the extreme being when an entity with the minimal weight always runs +- * on the cfs_rq. If the waking entity slept for a long time, its +- * vruntime difference from min_vruntime may overflow s64 and their +- * comparison may get inversed, so ignore the entity's original +- * vruntime in that case. +- * The maximal vruntime speedup is given by the ratio of normal to +- * minimal weight: scale_load_down(NICE_0_LOAD) / MIN_SHARES. +- * When placing a migrated waking entity, its exec_start has been set +- * from a different rq. In order to take into account a possible +- * divergence between new and prev rq's clocks task because of irq and +- * stolen time, we take an additional margin. +- * So, cutting off on the sleep time of +- * 2^63 / scale_load_down(NICE_0_LOAD) ~ 104 days +- * should be safe. ++ * When joining the competition; the exisiting tasks will be, ++ * on average, halfway through their slice, as such start tasks ++ * off with half a slice to ease into the competition. + */ +- if (entity_is_long_sleeper(se)) +- se->vruntime = vruntime; +- else +- se->vruntime = max_vruntime(se->vruntime, vruntime); ++ if (sched_feat(PLACE_DEADLINE_INITIAL) && (flags & ENQUEUE_INITIAL)) ++ vslice /= 2; ++ ++ /* ++ * EEVDF: vd_i = ve_i + r_i/w_i ++ */ ++ se->deadline = se->vruntime + vslice; + } + + static void check_enqueue_throttle(struct cfs_rq *cfs_rq); + + static inline bool cfs_bandwidth_used(void); + +-/* +- * MIGRATION +- * +- * dequeue +- * update_curr() +- * update_min_vruntime() +- * vruntime -= min_vruntime +- * +- * enqueue +- * update_curr() +- * update_min_vruntime() +- * vruntime += min_vruntime +- * +- * this way the vruntime transition between RQs is done when both +- * min_vruntime are up-to-date. +- * +- * WAKEUP (remote) +- * +- * ->migrate_task_rq_fair() (p->state == TASK_WAKING) +- * vruntime -= min_vruntime +- * +- * enqueue +- * update_curr() +- * update_min_vruntime() +- * vruntime += min_vruntime +- * +- * this way we don't have the most up-to-date min_vruntime on the originating +- * CPU and an up-to-date min_vruntime on the destination CPU. +- */ +- + static void + enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + { +- bool renorm = !(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_MIGRATED); + bool curr = cfs_rq->curr == se; + + /* + * If we're the current task, we must renormalise before calling + * update_curr(). + */ +- if (renorm && curr) +- se->vruntime += cfs_rq->min_vruntime; ++ if (curr) ++ place_entity(cfs_rq, se, flags); + + update_curr(cfs_rq); + + /* +- * Otherwise, renormalise after, such that we're placed at the current +- * moment in time, instead of some random moment in the past. Being +- * placed in the past could significantly boost this task to the +- * fairness detriment of existing tasks. +- */ +- if (renorm && !curr) +- se->vruntime += cfs_rq->min_vruntime; +- +- /* + * When enqueuing a sched_entity, we must: + * - Update loads to have both entity and cfs_rq synced with now. + * - For group_entity, update its runnable_weight to reflect the new +@@ -4855,18 +5206,28 @@ + */ + update_load_avg(cfs_rq, se, UPDATE_TG | DO_ATTACH); + se_update_runnable(se); ++ /* ++ * XXX update_load_avg() above will have attached us to the pelt sum; ++ * but update_cfs_group() here will re-adjust the weight and have to ++ * undo/redo all that. Seems wasteful. ++ */ + update_cfs_group(se); ++ ++ /* ++ * XXX now that the entity has been re-weighted, and it's lag adjusted, ++ * we can place the entity. ++ */ ++ if (!curr) ++ place_entity(cfs_rq, se, flags); ++ + account_entity_enqueue(cfs_rq, se); + +- if (flags & ENQUEUE_WAKEUP) +- place_entity(cfs_rq, se, 0); + /* Entity has migrated, no longer consider this task hot */ + if (flags & ENQUEUE_MIGRATED) + se->exec_start = 0; + + check_schedstat_required(); + update_stats_enqueue_fair(cfs_rq, se, flags); +- check_spread(cfs_rq, se); + if (!curr) + __enqueue_entity(cfs_rq, se); + se->on_rq = 1; +@@ -4878,17 +5239,6 @@ + } + } + +-static void __clear_buddies_last(struct sched_entity *se) +-{ +- for_each_sched_entity(se) { +- struct cfs_rq *cfs_rq = cfs_rq_of(se); +- if (cfs_rq->last != se) +- break; +- +- cfs_rq->last = NULL; +- } +-} +- + static void __clear_buddies_next(struct sched_entity *se) + { + for_each_sched_entity(se) { +@@ -4900,27 +5250,10 @@ + } + } + +-static void __clear_buddies_skip(struct sched_entity *se) +-{ +- for_each_sched_entity(se) { +- struct cfs_rq *cfs_rq = cfs_rq_of(se); +- if (cfs_rq->skip != se) +- break; +- +- cfs_rq->skip = NULL; +- } +-} +- + static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +- if (cfs_rq->last == se) +- __clear_buddies_last(se); +- + if (cfs_rq->next == se) + __clear_buddies_next(se); +- +- if (cfs_rq->skip == se) +- __clear_buddies_skip(se); + } + + static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq); +@@ -4954,20 +5287,12 @@ + + clear_buddies(cfs_rq, se); + ++ update_entity_lag(cfs_rq, se); + if (se != cfs_rq->curr) + __dequeue_entity(cfs_rq, se); + se->on_rq = 0; + account_entity_dequeue(cfs_rq, se); + +- /* +- * Normalize after update_curr(); which will also have moved +- * min_vruntime if @se is the one holding it back. But before doing +- * update_min_vruntime() again, which will discount @se's position and +- * can move min_vruntime forward still more. +- */ +- if (!(flags & DEQUEUE_SLEEP)) +- se->vruntime -= cfs_rq->min_vruntime; +- + /* return excess runtime on last dequeue */ + return_cfs_rq_runtime(cfs_rq); + +@@ -4986,52 +5311,6 @@ + update_idle_cfs_rq_clock_pelt(cfs_rq); + } + +-/* +- * Preempt the current task with a newly woken task if needed: +- */ +-static void +-check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr) +-{ +- unsigned long ideal_runtime, delta_exec; +- struct sched_entity *se; +- s64 delta; +- +- /* +- * When many tasks blow up the sched_period; it is possible that +- * sched_slice() reports unusually large results (when many tasks are +- * very light for example). Therefore impose a maximum. +- */ +- ideal_runtime = min_t(u64, sched_slice(cfs_rq, curr), sysctl_sched_latency); +- +- delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime; +- if (delta_exec > ideal_runtime) { +- resched_curr(rq_of(cfs_rq)); +- /* +- * The current task ran long enough, ensure it doesn't get +- * re-elected due to buddy favours. +- */ +- clear_buddies(cfs_rq, curr); +- return; +- } +- +- /* +- * Ensure that a task that missed wakeup preemption by a +- * narrow margin doesn't have to wait for a full slice. +- * This also mitigates buddy induced latencies under load. +- */ +- if (delta_exec < sysctl_sched_min_granularity) +- return; +- +- se = __pick_first_entity(cfs_rq); +- delta = curr->vruntime - se->vruntime; +- +- if (delta < 0) +- return; +- +- if (delta > ideal_runtime) +- resched_curr(rq_of(cfs_rq)); +-} +- + static void + set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +@@ -5047,6 +5326,11 @@ + update_stats_wait_end_fair(cfs_rq, se); + __dequeue_entity(cfs_rq, se); + update_load_avg(cfs_rq, se, UPDATE_TG); ++ /* ++ * HACK, stash a copy of deadline at the point of pick in vlag, ++ * which isn't used until dequeue. ++ */ ++ se->vlag = se->deadline; + } + + update_stats_curr_start(cfs_rq, se); +@@ -5070,9 +5354,6 @@ + se->prev_sum_exec_runtime = se->sum_exec_runtime; + } + +-static int +-wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se); +- + /* + * Pick the next process, keeping these things in mind, in this order: + * 1) keep things fair between processes/task groups +@@ -5083,50 +5364,14 @@ + static struct sched_entity * + pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr) + { +- struct sched_entity *left = __pick_first_entity(cfs_rq); +- struct sched_entity *se; +- +- /* +- * If curr is set we have to see if its left of the leftmost entity +- * still in the tree, provided there was anything in the tree at all. +- */ +- if (!left || (curr && entity_before(curr, left))) +- left = curr; +- +- se = left; /* ideally we run the leftmost entity */ +- + /* +- * Avoid running the skip buddy, if running something else can +- * be done without getting too unfair. ++ * Enabling NEXT_BUDDY will affect latency but not fairness. + */ +- if (cfs_rq->skip && cfs_rq->skip == se) { +- struct sched_entity *second; +- +- if (se == curr) { +- second = __pick_first_entity(cfs_rq); +- } else { +- second = __pick_next_entity(se); +- if (!second || (curr && entity_before(curr, second))) +- second = curr; +- } +- +- if (second && wakeup_preempt_entity(second, left) < 1) +- se = second; +- } +- +- if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1) { +- /* +- * Someone really wants this to run. If it's not unfair, run it. +- */ +- se = cfs_rq->next; +- } else if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1) { +- /* +- * Prefer last buddy, try to return the CPU to a preempted task. +- */ +- se = cfs_rq->last; +- } ++ if (sched_feat(NEXT_BUDDY) && ++ cfs_rq->next && entity_eligible(cfs_rq, cfs_rq->next)) ++ return cfs_rq->next; + +- return se; ++ return pick_eevdf(cfs_rq); + } + + static bool check_cfs_rq_runtime(struct cfs_rq *cfs_rq); +@@ -5143,8 +5388,6 @@ + /* throttle cfs_rqs exceeding runtime */ + check_cfs_rq_runtime(cfs_rq); + +- check_spread(cfs_rq, prev); +- + if (prev->on_rq) { + update_stats_wait_start_fair(cfs_rq, prev); + /* Put 'current' back into the tree. */ +@@ -5185,9 +5428,6 @@ + hrtimer_active(&rq_of(cfs_rq)->hrtick_timer)) + return; + #endif +- +- if (cfs_rq->nr_running > 1) +- check_preempt_tick(cfs_rq, curr); + } + + +@@ -6210,13 +6450,12 @@ + static void hrtick_start_fair(struct rq *rq, struct task_struct *p) + { + struct sched_entity *se = &p->se; +- struct cfs_rq *cfs_rq = cfs_rq_of(se); + + SCHED_WARN_ON(task_rq(p) != rq); + + if (rq->cfs.h_nr_running > 1) { +- u64 slice = sched_slice(cfs_rq, se); + u64 ran = se->sum_exec_runtime - se->prev_sum_exec_runtime; ++ u64 slice = se->slice; + s64 delta = slice - ran; + + if (delta < 0) { +@@ -6240,8 +6479,7 @@ + if (!hrtick_enabled_fair(rq) || curr->sched_class != &fair_sched_class) + return; + +- if (cfs_rq_of(&curr->se)->nr_running < sched_nr_latency) +- hrtick_start_fair(rq, curr); ++ hrtick_start_fair(rq, curr); + } + #else /* !CONFIG_SCHED_HRTICK */ + static inline void +@@ -6282,17 +6520,6 @@ + rq->nr_running); + } + +-/* +- * Returns true if cfs_rq only has SCHED_IDLE entities enqueued. Note the use +- * of idle_nr_running, which does not consider idle descendants of normal +- * entities. +- */ +-static bool sched_idle_cfs_rq(struct cfs_rq *cfs_rq) +-{ +- return cfs_rq->nr_running && +- cfs_rq->nr_running == cfs_rq->idle_nr_running; +-} +- + #ifdef CONFIG_SMP + static int sched_idle_cpu(int cpu) + { +@@ -6410,6 +6637,9 @@ + util_est_dequeue(&rq->cfs, p); + + 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); + +@@ -7795,18 +8025,6 @@ + { + struct sched_entity *se = &p->se; + +- /* +- * As blocked tasks retain absolute vruntime the migration needs to +- * deal with this by subtracting the old and adding the new +- * min_vruntime -- the latter is done by enqueue_entity() when placing +- * the task on the new runqueue. +- */ +- if (READ_ONCE(p->__state) == TASK_WAKING) { +- struct cfs_rq *cfs_rq = cfs_rq_of(se); +- +- se->vruntime -= u64_u32_load(cfs_rq->min_vruntime); +- } +- + if (!task_on_rq_migrating(p)) { + remove_entity_load_avg(se); + +@@ -7844,66 +8062,6 @@ + } + #endif /* CONFIG_SMP */ + +-static unsigned long wakeup_gran(struct sched_entity *se) +-{ +- unsigned long gran = sysctl_sched_wakeup_granularity; +- +- /* +- * Since its curr running now, convert the gran from real-time +- * to virtual-time in his units. +- * +- * By using 'se' instead of 'curr' we penalize light tasks, so +- * they get preempted easier. That is, if 'se' < 'curr' then +- * the resulting gran will be larger, therefore penalizing the +- * lighter, if otoh 'se' > 'curr' then the resulting gran will +- * be smaller, again penalizing the lighter task. +- * +- * This is especially important for buddies when the leftmost +- * task is higher priority than the buddy. +- */ +- return calc_delta_fair(gran, se); +-} +- +-/* +- * Should 'se' preempt 'curr'. +- * +- * |s1 +- * |s2 +- * |s3 +- * g +- * |<--->|c +- * +- * w(c, s1) = -1 +- * w(c, s2) = 0 +- * w(c, s3) = 1 +- * +- */ +-static int +-wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se) +-{ +- s64 gran, vdiff = curr->vruntime - se->vruntime; +- +- if (vdiff <= 0) +- return -1; +- +- gran = wakeup_gran(se); +- if (vdiff > gran) +- return 1; +- +- return 0; +-} +- +-static void set_last_buddy(struct sched_entity *se) +-{ +- for_each_sched_entity(se) { +- if (SCHED_WARN_ON(!se->on_rq)) +- return; +- if (se_is_idle(se)) +- return; +- cfs_rq_of(se)->last = se; +- } +-} +- + static void set_next_buddy(struct sched_entity *se) + { + for_each_sched_entity(se) { +@@ -7915,12 +8073,6 @@ + } + } + +-static void set_skip_buddy(struct sched_entity *se) +-{ +- for_each_sched_entity(se) +- cfs_rq_of(se)->skip = se; +-} +- + /* + * Preempt the current task with a newly woken task if needed: + */ +@@ -7929,7 +8081,6 @@ + struct task_struct *curr = rq->curr; + struct sched_entity *se = &curr->se, *pse = &p->se; + struct cfs_rq *cfs_rq = task_cfs_rq(curr); +- int scale = cfs_rq->nr_running >= sched_nr_latency; + int next_buddy_marked = 0; + int cse_is_idle, pse_is_idle; + +@@ -7945,7 +8096,7 @@ + if (unlikely(throttled_hierarchy(cfs_rq_of(pse)))) + return; + +- if (sched_feat(NEXT_BUDDY) && scale && !(wake_flags & WF_FORK)) { ++ if (sched_feat(NEXT_BUDDY) && !(wake_flags & WF_FORK)) { + set_next_buddy(pse); + next_buddy_marked = 1; + } +@@ -7990,35 +8141,19 @@ + if (cse_is_idle != pse_is_idle) + return; + +- update_curr(cfs_rq_of(se)); +- if (wakeup_preempt_entity(se, pse) == 1) { +- /* +- * Bias pick_next to pick the sched entity that is +- * triggering this preemption. +- */ +- if (!next_buddy_marked) +- set_next_buddy(pse); ++ cfs_rq = cfs_rq_of(se); ++ update_curr(cfs_rq); ++ ++ /* ++ * XXX pick_eevdf(cfs_rq) != se ? ++ */ ++ if (pick_eevdf(cfs_rq) == pse) + goto preempt; +- } + + return; + + preempt: + resched_curr(rq); +- /* +- * Only set the backward buddy when the current task is still +- * on the rq. This can happen when a wakeup gets interleaved +- * with schedule on the ->pre_schedule() or idle_balance() +- * point, either of which can * drop the rq lock. +- * +- * Also, during early boot the idle thread is in the fair class, +- * for obvious reasons its a bad idea to schedule back to it. +- */ +- if (unlikely(!se->on_rq || curr == rq->idle)) +- return; +- +- if (sched_feat(LAST_BUDDY) && scale && entity_is_task(se)) +- set_last_buddy(se); + } + + #ifdef CONFIG_SMP +@@ -8219,8 +8354,6 @@ + + /* + * sched_yield() is very simple +- * +- * The magic of dealing with the ->skip buddy is in pick_next_entity. + */ + static void yield_task_fair(struct rq *rq) + { +@@ -8231,26 +8364,31 @@ + /* + * Are we the only task in the tree? + */ +- if (unlikely(rq->nr_running == 1)) ++ if (unlikely(rq->nr_running == 1)) { ++#ifdef CONFIG_SCHED_BORE ++ restart_burst(se); ++#endif // CONFIG_SCHED_BORE + return; ++ } + + clear_buddies(cfs_rq, se); + +- if (curr->policy != SCHED_BATCH) { +- update_rq_clock(rq); +- /* +- * Update run-time statistics of the 'current'. +- */ +- update_curr(cfs_rq); +- /* +- * Tell update_rq_clock() that we've just updated, +- * so we don't do microscopic update in schedule() +- * and double the fastpath cost. +- */ +- rq_clock_skip_update(rq); +- } ++ update_rq_clock(rq); ++ /* ++ * Update run-time statistics of the 'current'. ++ */ ++ update_curr(cfs_rq); ++#ifdef CONFIG_SCHED_BORE ++ restart_burst(se); ++#endif // CONFIG_SCHED_BORE ++ /* ++ * Tell update_rq_clock() that we've just updated, ++ * so we don't do microscopic update in schedule() ++ * and double the fastpath cost. ++ */ ++ rq_clock_skip_update(rq); + +- set_skip_buddy(se); ++ se->deadline += calc_delta_fair(se->slice, se); + } + + static bool yield_to_task_fair(struct rq *rq, struct task_struct *p) +@@ -8493,8 +8631,7 @@ + * Buddy candidates are cache hot: + */ + if (sched_feat(CACHE_HOT_BUDDY) && env->dst_rq->nr_running && +- (&p->se == cfs_rq_of(&p->se)->next || +- &p->se == cfs_rq_of(&p->se)->last)) ++ (&p->se == cfs_rq_of(&p->se)->next)) + return 1; + + if (sysctl_sched_migration_cost == -1) +@@ -12004,8 +12141,8 @@ + static inline bool + __entity_slice_used(struct sched_entity *se, int min_nr_tasks) + { +- u64 slice = sched_slice(cfs_rq_of(se), se); + u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime; ++ u64 slice = se->slice; + + return (rtime * min_nr_tasks > slice); + } +@@ -12161,8 +12298,8 @@ + */ + static void task_fork_fair(struct task_struct *p) + { +- struct cfs_rq *cfs_rq; + struct sched_entity *se = &p->se, *curr; ++ struct cfs_rq *cfs_rq; + struct rq *rq = this_rq(); + struct rq_flags rf; + +@@ -12171,22 +12308,9 @@ + + cfs_rq = task_cfs_rq(current); + curr = cfs_rq->curr; +- if (curr) { ++ if (curr) + update_curr(cfs_rq); +- se->vruntime = curr->vruntime; +- } +- place_entity(cfs_rq, se, 1); +- +- if (sysctl_sched_child_runs_first && curr && entity_before(curr, se)) { +- /* +- * Upon rescheduling, sched_class::put_prev_task() will place +- * 'current' within the tree based on its new key value. +- */ +- swap(curr->vruntime, se->vruntime); +- resched_curr(rq); +- } +- +- se->vruntime -= cfs_rq->min_vruntime; ++ place_entity(cfs_rq, se, ENQUEUE_INITIAL); + rq_unlock(rq, &rf); + } + +@@ -12215,34 +12339,6 @@ + check_preempt_curr(rq, p, 0); + } + +-static inline bool vruntime_normalized(struct task_struct *p) +-{ +- struct sched_entity *se = &p->se; +- +- /* +- * In both the TASK_ON_RQ_QUEUED and TASK_ON_RQ_MIGRATING cases, +- * the dequeue_entity(.flags=0) will already have normalized the +- * vruntime. +- */ +- if (p->on_rq) +- return true; +- +- /* +- * When !on_rq, vruntime of the task has usually NOT been normalized. +- * But there are some cases where it has already been normalized: +- * +- * - A forked child which is waiting for being woken up by +- * wake_up_new_task(). +- * - A task which has been woken up by try_to_wake_up() and +- * waiting for actually being woken up by sched_ttwu_pending(). +- */ +- if (!se->sum_exec_runtime || +- (READ_ONCE(p->__state) == TASK_WAKING && p->sched_remote_wakeup)) +- return true; +- +- return false; +-} +- + #ifdef CONFIG_FAIR_GROUP_SCHED + /* + * Propagate the changes of the sched_entity across the tg tree to make it +@@ -12313,16 +12409,6 @@ + static void detach_task_cfs_rq(struct task_struct *p) + { + struct sched_entity *se = &p->se; +- struct cfs_rq *cfs_rq = cfs_rq_of(se); +- +- if (!vruntime_normalized(p)) { +- /* +- * Fix up our vruntime so that the current sleep doesn't +- * cause 'unlimited' sleep bonus. +- */ +- place_entity(cfs_rq, se, 0); +- se->vruntime -= cfs_rq->min_vruntime; +- } + + detach_entity_cfs_rq(se); + } +@@ -12330,12 +12416,8 @@ + static void attach_task_cfs_rq(struct task_struct *p) + { + struct sched_entity *se = &p->se; +- struct cfs_rq *cfs_rq = cfs_rq_of(se); + + attach_entity_cfs_rq(se); +- +- if (!vruntime_normalized(p)) +- se->vruntime += cfs_rq->min_vruntime; + } + + static void switched_from_fair(struct rq *rq, struct task_struct *p) +@@ -12446,6 +12528,7 @@ + goto err; + + tg->shares = NICE_0_LOAD; ++ tg->latency_prio = DEFAULT_PRIO; + + init_cfs_bandwidth(tg_cfs_bandwidth(tg)); + +@@ -12544,6 +12627,9 @@ + } + + se->my_q = cfs_rq; ++ ++ set_latency_fair(se, tg->latency_prio - MAX_RT_PRIO); ++ + /* guarantee group entities always have weight */ + update_load_set(&se->load, NICE_0_LOAD); + se->parent = parent; +@@ -12674,6 +12760,29 @@ + return 0; + } + ++int sched_group_set_latency(struct task_group *tg, int prio) ++{ ++ int i; ++ ++ if (tg == &root_task_group) ++ return -EINVAL; ++ ++ mutex_lock(&shares_mutex); ++ ++ if (tg->latency_prio == prio) { ++ mutex_unlock(&shares_mutex); ++ return 0; ++ } ++ ++ tg->latency_prio = prio; ++ ++ for_each_possible_cpu(i) ++ set_latency_fair(tg->se[i], prio - MAX_RT_PRIO); ++ ++ mutex_unlock(&shares_mutex); ++ return 0; ++} ++ + #else /* CONFIG_FAIR_GROUP_SCHED */ + + void free_fair_sched_group(struct task_group *tg) { } +@@ -12700,7 +12809,7 @@ + * idle runqueue: + */ + if (rq->cfs.load.weight) +- rr_interval = NS_TO_JIFFIES(sched_slice(cfs_rq_of(se), se)); ++ rr_interval = NS_TO_JIFFIES(se->slice); + + return rr_interval; + } +diff '--color=auto' -uraN a/kernel/sched/features.h b/kernel/sched/features.h +--- a/kernel/sched/features.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sched/features.h 2023-11-04 16:36:05.671457708 +0300 +@@ -1,16 +1,12 @@ + /* SPDX-License-Identifier: GPL-2.0 */ +-/* +- * Only give sleepers 50% of their service deficit. This allows +- * them to run sooner, but does not allow tons of sleepers to +- * rip the spread apart. +- */ +-SCHED_FEAT(GENTLE_FAIR_SLEEPERS, true) + + /* +- * Place new tasks ahead so that they do not starve already running +- * tasks ++ * Using the avg_vruntime, do the right thing and preserve lag across ++ * sleep+wake cycles. EEVDF placement strategy #1, #2 if disabled. + */ +-SCHED_FEAT(START_DEBIT, true) ++SCHED_FEAT(PLACE_LAG, true) ++SCHED_FEAT(PLACE_DEADLINE_INITIAL, true) ++SCHED_FEAT(RUN_TO_PARITY, true) + + /* + * Prefer to schedule the task we woke last (assuming it failed +@@ -20,13 +16,6 @@ + SCHED_FEAT(NEXT_BUDDY, false) + + /* +- * Prefer to schedule the task that ran last (when we did +- * wake-preempt) as that likely will touch the same data, increases +- * cache locality. +- */ +-SCHED_FEAT(LAST_BUDDY, true) +- +-/* + * Consider buddies to be cache hot, decreases the likeliness of a + * cache buddy being migrated away, increases cache locality. + */ +@@ -98,6 +87,3 @@ + SCHED_FEAT(UTIL_EST_FASTUP, true) + + SCHED_FEAT(LATENCY_WARN, false) +- +-SCHED_FEAT(ALT_PERIOD, true) +-SCHED_FEAT(BASE_SLICE, true) +diff '--color=auto' -uraN a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sched/sched.h 2023-11-04 16:36:05.671457708 +0300 +@@ -372,6 +372,8 @@ + + /* A positive value indicates that this is a SCHED_IDLE group. */ + int idle; ++ /* latency priority of the group. */ ++ int latency_prio; + + #ifdef CONFIG_SMP + /* +@@ -482,6 +484,8 @@ + + extern int sched_group_set_idle(struct task_group *tg, long idle); + ++extern int sched_group_set_latency(struct task_group *tg, int prio); ++ + #ifdef CONFIG_SMP + extern void set_task_rq_fair(struct sched_entity *se, + struct cfs_rq *prev, struct cfs_rq *next); +@@ -548,6 +552,9 @@ + unsigned int idle_nr_running; /* SCHED_IDLE */ + unsigned int idle_h_nr_running; /* SCHED_IDLE */ + ++ s64 avg_vruntime; ++ u64 avg_load; ++ + u64 exec_clock; + u64 min_vruntime; + #ifdef CONFIG_SCHED_CORE +@@ -567,8 +574,6 @@ + */ + struct sched_entity *curr; + struct sched_entity *next; +- struct sched_entity *last; +- struct sched_entity *skip; + + #ifdef CONFIG_SCHED_DEBUG + unsigned int nr_spread_over; +@@ -2195,6 +2200,7 @@ + #else + #define ENQUEUE_MIGRATED 0x00 + #endif ++#define ENQUEUE_INITIAL 0x80 + + #define RETRY_TASK ((void *)-1UL) + +@@ -2499,11 +2505,9 @@ + extern const_debug unsigned int sysctl_sched_nr_migrate; + extern const_debug unsigned int sysctl_sched_migration_cost; + ++extern unsigned int sysctl_sched_base_slice; ++ + #ifdef CONFIG_SCHED_DEBUG +-extern unsigned int sysctl_sched_latency; +-extern unsigned int sysctl_sched_min_granularity; +-extern unsigned int sysctl_sched_idle_min_granularity; +-extern unsigned int sysctl_sched_wakeup_granularity; + extern int sysctl_resched_latency_warn_ms; + extern int sysctl_resched_latency_warn_once; + +@@ -2516,6 +2520,8 @@ + extern unsigned int sysctl_numa_balancing_hot_threshold; + #endif + ++extern void set_latency_fair(struct sched_entity *se, int prio); ++ + #ifdef CONFIG_SCHED_HRTICK + + /* +@@ -3480,4 +3486,7 @@ + static inline void init_sched_mm_cid(struct task_struct *t) { } + #endif + ++extern u64 avg_vruntime(struct cfs_rq *cfs_rq); ++extern int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se); ++ + #endif /* _KERNEL_SCHED_SCHED_H */ +diff '--color=auto' -uraN a/kernel/sys.c b/kernel/sys.c +--- a/kernel/sys.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sys.c 2023-11-04 16:35:57.841317298 +0300 +@@ -2727,6 +2727,153 @@ + return error; + } + ++#ifdef CONFIG_KSM ++enum pkc_action { ++ PKSM_ENABLE = 0, ++ PKSM_DISABLE, ++ PKSM_STATUS, ++}; ++ ++static long do_process_ksm_control(int pidfd, enum pkc_action action) ++{ ++ long ret; ++ struct pid *pid; ++ struct task_struct *task; ++ struct mm_struct *mm; ++ unsigned int f_flags; ++ ++ pid = pidfd_get_pid(pidfd, &f_flags); ++ if (IS_ERR(pid)) { ++ ret = PTR_ERR(pid); ++ goto out; ++ } ++ ++ task = get_pid_task(pid, PIDTYPE_PID); ++ if (!task) { ++ ret = -ESRCH; ++ goto put_pid; ++ } ++ ++ /* Require PTRACE_MODE_READ to avoid leaking ASLR metadata. */ ++ mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); ++ if (IS_ERR_OR_NULL(mm)) { ++ ret = IS_ERR(mm) ? PTR_ERR(mm) : -ESRCH; ++ goto release_task; ++ } ++ ++ /* Require CAP_SYS_NICE for influencing process performance. */ ++ if (!capable(CAP_SYS_NICE)) { ++ ret = -EPERM; ++ goto release_mm; ++ } ++ ++ if (mmap_write_lock_killable(mm)) { ++ ret = -EINTR; ++ goto release_mm; ++ } ++ ++ switch (action) { ++ case PKSM_ENABLE: ++ ret = ksm_enable_merge_any(mm); ++ break; ++ case PKSM_DISABLE: ++ ret = ksm_disable_merge_any(mm); ++ break; ++ case PKSM_STATUS: ++ ret = !!test_bit(MMF_VM_MERGE_ANY, &mm->flags); ++ break; ++ } ++ ++ mmap_write_unlock(mm); ++ ++release_mm: ++ mmput(mm); ++release_task: ++ put_task_struct(task); ++put_pid: ++ put_pid(pid); ++out: ++ return ret; ++} ++#endif /* CONFIG_KSM */ ++ ++SYSCALL_DEFINE2(process_ksm_enable, int, pidfd, unsigned int, flags) ++{ ++#ifdef CONFIG_KSM ++ if (flags != 0) ++ return -EINVAL; ++ ++ return do_process_ksm_control(pidfd, PKSM_ENABLE); ++#else /* CONFIG_KSM */ ++ return -ENOSYS; ++#endif /* CONFIG_KSM */ ++} ++ ++SYSCALL_DEFINE2(process_ksm_disable, int, pidfd, unsigned int, flags) ++{ ++#ifdef CONFIG_KSM ++ if (flags != 0) ++ return -EINVAL; ++ ++ return do_process_ksm_control(pidfd, PKSM_DISABLE); ++#else /* CONFIG_KSM */ ++ return -ENOSYS; ++#endif /* CONFIG_KSM */ ++} ++ ++SYSCALL_DEFINE2(process_ksm_status, int, pidfd, unsigned int, flags) ++{ ++#ifdef CONFIG_KSM ++ if (flags != 0) ++ return -EINVAL; ++ ++ return do_process_ksm_control(pidfd, PKSM_STATUS); ++#else /* CONFIG_KSM */ ++ return -ENOSYS; ++#endif /* CONFIG_KSM */ ++} ++ ++#ifdef CONFIG_KSM ++static ssize_t process_ksm_enable_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "%u\n", __NR_process_ksm_enable); ++} ++static struct kobj_attribute process_ksm_enable_attr = __ATTR_RO(process_ksm_enable); ++ ++static ssize_t process_ksm_disable_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "%u\n", __NR_process_ksm_disable); ++} ++static struct kobj_attribute process_ksm_disable_attr = __ATTR_RO(process_ksm_disable); ++ ++static ssize_t process_ksm_status_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "%u\n", __NR_process_ksm_status); ++} ++static struct kobj_attribute process_ksm_status_attr = __ATTR_RO(process_ksm_status); ++ ++static struct attribute *process_ksm_sysfs_attrs[] = { ++ &process_ksm_enable_attr.attr, ++ &process_ksm_disable_attr.attr, ++ &process_ksm_status_attr.attr, ++ NULL, ++}; ++ ++static const struct attribute_group process_ksm_sysfs_attr_group = { ++ .attrs = process_ksm_sysfs_attrs, ++ .name = "process_ksm", ++}; ++ ++static int __init process_ksm_sysfs_init(void) ++{ ++ return sysfs_create_group(kernel_kobj, &process_ksm_sysfs_attr_group); ++} ++subsys_initcall(process_ksm_sysfs_init); ++#endif /* CONFIG_KSM */ ++ + SYSCALL_DEFINE3(getcpu, unsigned __user *, cpup, unsigned __user *, nodep, + struct getcpu_cache __user *, unused) + { +diff '--color=auto' -uraN a/kernel/sys_ni.c b/kernel/sys_ni.c +--- a/kernel/sys_ni.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sys_ni.c 2023-11-04 16:35:57.841317298 +0300 +@@ -184,6 +184,9 @@ + COND_SYSCALL(madvise); + COND_SYSCALL(process_madvise); + COND_SYSCALL(process_mrelease); ++COND_SYSCALL(process_ksm_enable); ++COND_SYSCALL(process_ksm_disable); ++COND_SYSCALL(process_ksm_status); + COND_SYSCALL(remap_file_pages); + COND_SYSCALL(mbind); + COND_SYSCALL(get_mempolicy); +diff '--color=auto' -uraN a/kernel/sysctl.c b/kernel/sysctl.c +--- a/kernel/sysctl.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/sysctl.c 2023-11-04 16:35:57.821316939 +0300 +@@ -95,6 +95,9 @@ + #ifdef CONFIG_PERF_EVENTS + static const int six_hundred_forty_kb = 640 * 1024; + #endif ++#ifdef CONFIG_USER_NS ++#include ++#endif + + + static const int ngroups_max = NGROUPS_MAX; +@@ -1623,6 +1626,15 @@ + .mode = 0644, + .proc_handler = proc_dointvec, + }, ++#ifdef CONFIG_USER_NS ++ { ++ .procname = "unprivileged_userns_clone", ++ .data = &unprivileged_userns_clone, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec, ++ }, ++#endif + #ifdef CONFIG_PROC_SYSCTL + { + .procname = "tainted", +diff '--color=auto' -uraN a/kernel/user_namespace.c b/kernel/user_namespace.c +--- a/kernel/user_namespace.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/kernel/user_namespace.c 2023-11-04 16:35:57.821316939 +0300 +@@ -22,6 +22,13 @@ + #include + #include + ++/* sysctl */ ++#ifdef CONFIG_USER_NS_UNPRIVILEGED ++int unprivileged_userns_clone = 1; ++#else ++int unprivileged_userns_clone; ++#endif ++ + static struct kmem_cache *user_ns_cachep __read_mostly; + static DEFINE_MUTEX(userns_state_mutex); + +diff '--color=auto' -uraN a/lib/scatterlist.c b/lib/scatterlist.c +--- a/lib/scatterlist.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/scatterlist.c 2023-11-04 16:35:57.821316939 +0300 +@@ -150,31 +150,12 @@ + */ + static struct scatterlist *sg_kmalloc(unsigned int nents, gfp_t gfp_mask) + { +- if (nents == SG_MAX_SINGLE_ALLOC) { +- /* +- * Kmemleak doesn't track page allocations as they are not +- * commonly used (in a raw form) for kernel data structures. +- * As we chain together a list of pages and then a normal +- * kmalloc (tracked by kmemleak), in order to for that last +- * allocation not to become decoupled (and thus a +- * false-positive) we need to inform kmemleak of all the +- * intermediate allocations. +- */ +- void *ptr = (void *) __get_free_page(gfp_mask); +- kmemleak_alloc(ptr, PAGE_SIZE, 1, gfp_mask); +- return ptr; +- } else +- return kmalloc_array(nents, sizeof(struct scatterlist), +- gfp_mask); ++ return kmalloc_array(nents, sizeof(struct scatterlist), gfp_mask); + } + + static void sg_kfree(struct scatterlist *sg, unsigned int nents) + { +- if (nents == SG_MAX_SINGLE_ALLOC) { +- kmemleak_free(sg); +- free_page((unsigned long) sg); +- } else +- kfree(sg); ++ kfree(sg); + } + + /** +diff '--color=auto' -uraN a/lib/zstd/Makefile b/lib/zstd/Makefile +--- a/lib/zstd/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/Makefile 2023-11-04 16:35:57.841317298 +0300 +@@ -1,6 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + # ################################################################ +-# Copyright (c) Facebook, Inc. ++# Copyright (c) Meta Platforms, Inc. and affiliates. + # All rights reserved. + # + # This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/common/allocations.h b/lib/zstd/common/allocations.h +--- a/lib/zstd/common/allocations.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/lib/zstd/common/allocations.h 2023-11-04 16:35:57.841317298 +0300 +@@ -0,0 +1,56 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ ++/* ++ * Copyright (c) Meta Platforms, Inc. and affiliates. ++ * All rights reserved. ++ * ++ * This source code is licensed under both the BSD-style license (found in the ++ * LICENSE file in the root directory of this source tree) and the GPLv2 (found ++ * in the COPYING file in the root directory of this source tree). ++ * You may select, at your option, one of the above-listed licenses. ++ */ ++ ++/* This file provides custom allocation primitives ++ */ ++ ++#define ZSTD_DEPS_NEED_MALLOC ++#include "zstd_deps.h" /* ZSTD_malloc, ZSTD_calloc, ZSTD_free, ZSTD_memset */ ++ ++#include "mem.h" /* MEM_STATIC */ ++#define ZSTD_STATIC_LINKING_ONLY ++#include /* ZSTD_customMem */ ++ ++#ifndef ZSTD_ALLOCATIONS_H ++#define ZSTD_ALLOCATIONS_H ++ ++/* custom memory allocation functions */ ++ ++MEM_STATIC void* ZSTD_customMalloc(size_t size, ZSTD_customMem customMem) ++{ ++ if (customMem.customAlloc) ++ return customMem.customAlloc(customMem.opaque, size); ++ return ZSTD_malloc(size); ++} ++ ++MEM_STATIC void* ZSTD_customCalloc(size_t size, ZSTD_customMem customMem) ++{ ++ if (customMem.customAlloc) { ++ /* calloc implemented as malloc+memset; ++ * not as efficient as calloc, but next best guess for custom malloc */ ++ void* const ptr = customMem.customAlloc(customMem.opaque, size); ++ ZSTD_memset(ptr, 0, size); ++ return ptr; ++ } ++ return ZSTD_calloc(1, size); ++} ++ ++MEM_STATIC void ZSTD_customFree(void* ptr, ZSTD_customMem customMem) ++{ ++ if (ptr!=NULL) { ++ if (customMem.customFree) ++ customMem.customFree(customMem.opaque, ptr); ++ else ++ ZSTD_free(ptr); ++ } ++} ++ ++#endif /* ZSTD_ALLOCATIONS_H */ +diff '--color=auto' -uraN a/lib/zstd/common/bits.h b/lib/zstd/common/bits.h +--- a/lib/zstd/common/bits.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/lib/zstd/common/bits.h 2023-11-04 16:35:57.841317298 +0300 +@@ -0,0 +1,149 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ ++/* ++ * Copyright (c) Meta Platforms, Inc. and affiliates. ++ * All rights reserved. ++ * ++ * This source code is licensed under both the BSD-style license (found in the ++ * LICENSE file in the root directory of this source tree) and the GPLv2 (found ++ * in the COPYING file in the root directory of this source tree). ++ * You may select, at your option, one of the above-listed licenses. ++ */ ++ ++#ifndef ZSTD_BITS_H ++#define ZSTD_BITS_H ++ ++#include "mem.h" ++ ++MEM_STATIC unsigned ZSTD_countTrailingZeros32_fallback(U32 val) ++{ ++ assert(val != 0); ++ { ++ static const U32 DeBruijnBytePos[32] = {0, 1, 28, 2, 29, 14, 24, 3, ++ 30, 22, 20, 15, 25, 17, 4, 8, ++ 31, 27, 13, 23, 21, 19, 16, 7, ++ 26, 12, 18, 6, 11, 5, 10, 9}; ++ return DeBruijnBytePos[((U32) ((val & -(S32) val) * 0x077CB531U)) >> 27]; ++ } ++} ++ ++MEM_STATIC unsigned ZSTD_countTrailingZeros32(U32 val) ++{ ++ assert(val != 0); ++# if (__GNUC__ >= 4) ++ return (unsigned)__builtin_ctz(val); ++# else ++ return ZSTD_countTrailingZeros32_fallback(val); ++# endif ++} ++ ++MEM_STATIC unsigned ZSTD_countLeadingZeros32_fallback(U32 val) { ++ assert(val != 0); ++ { ++ static const U32 DeBruijnClz[32] = {0, 9, 1, 10, 13, 21, 2, 29, ++ 11, 14, 16, 18, 22, 25, 3, 30, ++ 8, 12, 20, 28, 15, 17, 24, 7, ++ 19, 27, 23, 6, 26, 5, 4, 31}; ++ val |= val >> 1; ++ val |= val >> 2; ++ val |= val >> 4; ++ val |= val >> 8; ++ val |= val >> 16; ++ return 31 - DeBruijnClz[(val * 0x07C4ACDDU) >> 27]; ++ } ++} ++ ++MEM_STATIC unsigned ZSTD_countLeadingZeros32(U32 val) ++{ ++ assert(val != 0); ++# if (__GNUC__ >= 4) ++ return (unsigned)__builtin_clz(val); ++# else ++ return ZSTD_countLeadingZeros32_fallback(val); ++# endif ++} ++ ++MEM_STATIC unsigned ZSTD_countTrailingZeros64(U64 val) ++{ ++ assert(val != 0); ++# if (__GNUC__ >= 4) && defined(__LP64__) ++ return (unsigned)__builtin_ctzll(val); ++# else ++ { ++ U32 mostSignificantWord = (U32)(val >> 32); ++ U32 leastSignificantWord = (U32)val; ++ if (leastSignificantWord == 0) { ++ return 32 + ZSTD_countTrailingZeros32(mostSignificantWord); ++ } else { ++ return ZSTD_countTrailingZeros32(leastSignificantWord); ++ } ++ } ++# endif ++} ++ ++MEM_STATIC unsigned ZSTD_countLeadingZeros64(U64 val) ++{ ++ assert(val != 0); ++# if (__GNUC__ >= 4) ++ return (unsigned)(__builtin_clzll(val)); ++# else ++ { ++ U32 mostSignificantWord = (U32)(val >> 32); ++ U32 leastSignificantWord = (U32)val; ++ if (mostSignificantWord == 0) { ++ return 32 + ZSTD_countLeadingZeros32(leastSignificantWord); ++ } else { ++ return ZSTD_countLeadingZeros32(mostSignificantWord); ++ } ++ } ++# endif ++} ++ ++MEM_STATIC unsigned ZSTD_NbCommonBytes(size_t val) ++{ ++ if (MEM_isLittleEndian()) { ++ if (MEM_64bits()) { ++ return ZSTD_countTrailingZeros64((U64)val) >> 3; ++ } else { ++ return ZSTD_countTrailingZeros32((U32)val) >> 3; ++ } ++ } else { /* Big Endian CPU */ ++ if (MEM_64bits()) { ++ return ZSTD_countLeadingZeros64((U64)val) >> 3; ++ } else { ++ return ZSTD_countLeadingZeros32((U32)val) >> 3; ++ } ++ } ++} ++ ++MEM_STATIC unsigned ZSTD_highbit32(U32 val) /* compress, dictBuilder, decodeCorpus */ ++{ ++ assert(val != 0); ++ return 31 - ZSTD_countLeadingZeros32(val); ++} ++ ++/* ZSTD_rotateRight_*(): ++ * Rotates a bitfield to the right by "count" bits. ++ * https://en.wikipedia.org/w/index.php?title=Circular_shift&oldid=991635599#Implementing_circular_shifts ++ */ ++MEM_STATIC ++U64 ZSTD_rotateRight_U64(U64 const value, U32 count) { ++ assert(count < 64); ++ count &= 0x3F; /* for fickle pattern recognition */ ++ return (value >> count) | (U64)(value << ((0U - count) & 0x3F)); ++} ++ ++MEM_STATIC ++U32 ZSTD_rotateRight_U32(U32 const value, U32 count) { ++ assert(count < 32); ++ count &= 0x1F; /* for fickle pattern recognition */ ++ return (value >> count) | (U32)(value << ((0U - count) & 0x1F)); ++} ++ ++MEM_STATIC ++U16 ZSTD_rotateRight_U16(U16 const value, U32 count) { ++ assert(count < 16); ++ count &= 0x0F; /* for fickle pattern recognition */ ++ return (value >> count) | (U16)(value << ((0U - count) & 0x0F)); ++} ++ ++#endif /* ZSTD_BITS_H */ +diff '--color=auto' -uraN a/lib/zstd/common/bitstream.h b/lib/zstd/common/bitstream.h +--- a/lib/zstd/common/bitstream.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/bitstream.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,7 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* ****************************************************************** + * bitstream + * Part of FSE library +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -27,6 +28,7 @@ + #include "compiler.h" /* UNLIKELY() */ + #include "debug.h" /* assert(), DEBUGLOG(), RAWLOG() */ + #include "error_private.h" /* error codes and messages */ ++#include "bits.h" /* ZSTD_highbit32 */ + + + /*========================================= +@@ -122,33 +124,6 @@ + MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits); + /* faster, but works only if nbBits >= 1 */ + +- +- +-/*-************************************************************** +-* Internal functions +-****************************************************************/ +-MEM_STATIC unsigned BIT_highbit32 (U32 val) +-{ +- assert(val != 0); +- { +-# if (__GNUC__ >= 3) /* Use GCC Intrinsic */ +- return __builtin_clz (val) ^ 31; +-# else /* Software version */ +- static const unsigned DeBruijnClz[32] = { 0, 9, 1, 10, 13, 21, 2, 29, +- 11, 14, 16, 18, 22, 25, 3, 30, +- 8, 12, 20, 28, 15, 17, 24, 7, +- 19, 27, 23, 6, 26, 5, 4, 31 }; +- U32 v = val; +- v |= v >> 1; +- v |= v >> 2; +- v |= v >> 4; +- v |= v >> 8; +- v |= v >> 16; +- return DeBruijnClz[ (U32) (v * 0x07C4ACDDU) >> 27]; +-# endif +- } +-} +- + /*===== Local Constants =====*/ + static const unsigned BIT_mask[] = { + 0, 1, 3, 7, 0xF, 0x1F, +@@ -178,6 +153,12 @@ + return 0; + } + ++MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) ++{ ++ assert(nbBits < BIT_MASK_SIZE); ++ return bitContainer & BIT_mask[nbBits]; ++} ++ + /*! BIT_addBits() : + * can add up to 31 bits into `bitC`. + * Note : does not check for register overflow ! */ +@@ -187,7 +168,7 @@ + DEBUG_STATIC_ASSERT(BIT_MASK_SIZE == 32); + assert(nbBits < BIT_MASK_SIZE); + assert(nbBits + bitC->bitPos < sizeof(bitC->bitContainer) * 8); +- bitC->bitContainer |= (value & BIT_mask[nbBits]) << bitC->bitPos; ++ bitC->bitContainer |= BIT_getLowerBits(value, nbBits) << bitC->bitPos; + bitC->bitPos += nbBits; + } + +@@ -266,7 +247,7 @@ + bitD->ptr = (const char*)srcBuffer + srcSize - sizeof(bitD->bitContainer); + bitD->bitContainer = MEM_readLEST(bitD->ptr); + { BYTE const lastByte = ((const BYTE*)srcBuffer)[srcSize-1]; +- bitD->bitsConsumed = lastByte ? 8 - BIT_highbit32(lastByte) : 0; /* ensures bitsConsumed is always set */ ++ bitD->bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0; /* ensures bitsConsumed is always set */ + if (lastByte == 0) return ERROR(GENERIC); /* endMark not present */ } + } else { + bitD->ptr = bitD->start; +@@ -294,7 +275,7 @@ + default: break; + } + { BYTE const lastByte = ((const BYTE*)srcBuffer)[srcSize-1]; +- bitD->bitsConsumed = lastByte ? 8 - BIT_highbit32(lastByte) : 0; ++ bitD->bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0; + if (lastByte == 0) return ERROR(corruption_detected); /* endMark not present */ + } + bitD->bitsConsumed += (U32)(sizeof(bitD->bitContainer) - srcSize)*8; +@@ -325,12 +306,6 @@ + #endif + } + +-MEM_STATIC FORCE_INLINE_ATTR size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) +-{ +- assert(nbBits < BIT_MASK_SIZE); +- return bitContainer & BIT_mask[nbBits]; +-} +- + /*! BIT_lookBits() : + * Provides next n bits from local register. + * local register is not modified. +@@ -377,7 +352,7 @@ + } + + /*! BIT_readBitsFast() : +- * unsafe version; only works only if nbBits >= 1 */ ++ * unsafe version; only works if nbBits >= 1 */ + MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits) + { + size_t const value = BIT_lookBitsFast(bitD, nbBits); +@@ -408,7 +383,7 @@ + * This function is safe, it guarantees it will not read beyond src buffer. + * @return : status of `BIT_DStream_t` internal register. + * when status == BIT_DStream_unfinished, internal register is filled with at least 25 or 57 bits */ +-MEM_STATIC BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD) ++MEM_STATIC FORCE_INLINE_ATTR BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD) + { + if (bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8)) /* overflow detected, like end of stream */ + return BIT_DStream_overflow; +diff '--color=auto' -uraN a/lib/zstd/common/compiler.h b/lib/zstd/common/compiler.h +--- a/lib/zstd/common/compiler.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/compiler.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -179,6 +180,17 @@ + * Sanitizer + *****************************************************************/ + ++/* Issue #3240 reports an ASAN failure on an llvm-mingw build. Out of an ++ * abundance of caution, disable our custom poisoning on mingw. */ ++#ifdef __MINGW32__ ++#ifndef ZSTD_ASAN_DONT_POISON_WORKSPACE ++#define ZSTD_ASAN_DONT_POISON_WORKSPACE 1 ++#endif ++#ifndef ZSTD_MSAN_DONT_POISON_WORKSPACE ++#define ZSTD_MSAN_DONT_POISON_WORKSPACE 1 ++#endif ++#endif ++ + + + #endif /* ZSTD_COMPILER_H */ +diff '--color=auto' -uraN a/lib/zstd/common/cpu.h b/lib/zstd/common/cpu.h +--- a/lib/zstd/common/cpu.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/cpu.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/common/debug.c b/lib/zstd/common/debug.c +--- a/lib/zstd/common/debug.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/debug.c 2023-11-04 16:35:57.844650691 +0300 +@@ -1,7 +1,8 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* ****************************************************************** + * debug + * Part of FSE library +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy +diff '--color=auto' -uraN a/lib/zstd/common/debug.h b/lib/zstd/common/debug.h +--- a/lib/zstd/common/debug.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/debug.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,7 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* ****************************************************************** + * debug + * Part of FSE library +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy +diff '--color=auto' -uraN a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c +--- a/lib/zstd/common/entropy_common.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/entropy_common.c 2023-11-04 16:35:57.844650691 +0300 +@@ -1,6 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* ****************************************************************** + * Common functions of New Generation Entropy library +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - FSE+HUF source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -19,8 +20,8 @@ + #include "error_private.h" /* ERR_*, ERROR */ + #define FSE_STATIC_LINKING_ONLY /* FSE_MIN_TABLELOG */ + #include "fse.h" +-#define HUF_STATIC_LINKING_ONLY /* HUF_TABLELOG_ABSOLUTEMAX */ + #include "huf.h" ++#include "bits.h" /* ZSDT_highbit32, ZSTD_countTrailingZeros32 */ + + + /*=== Version ===*/ +@@ -38,23 +39,6 @@ + /*-************************************************************** + * FSE NCount encoding-decoding + ****************************************************************/ +-static U32 FSE_ctz(U32 val) +-{ +- assert(val != 0); +- { +-# if (__GNUC__ >= 3) /* GCC Intrinsic */ +- return __builtin_ctz(val); +-# else /* Software version */ +- U32 count = 0; +- while ((val & 1) == 0) { +- val >>= 1; +- ++count; +- } +- return count; +-# endif +- } +-} +- + FORCE_INLINE_TEMPLATE + size_t FSE_readNCount_body(short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr, + const void* headerBuffer, size_t hbSize) +@@ -102,7 +86,7 @@ + * repeat. + * Avoid UB by setting the high bit to 1. + */ +- int repeats = FSE_ctz(~bitStream | 0x80000000) >> 1; ++ int repeats = ZSTD_countTrailingZeros32(~bitStream | 0x80000000) >> 1; + while (repeats >= 12) { + charnum += 3 * 12; + if (LIKELY(ip <= iend-7)) { +@@ -113,7 +97,7 @@ + ip = iend - 4; + } + bitStream = MEM_readLE32(ip) >> bitCount; +- repeats = FSE_ctz(~bitStream | 0x80000000) >> 1; ++ repeats = ZSTD_countTrailingZeros32(~bitStream | 0x80000000) >> 1; + } + charnum += 3 * repeats; + bitStream >>= 2 * repeats; +@@ -178,7 +162,7 @@ + * know that threshold > 1. + */ + if (remaining <= 1) break; +- nbBits = BIT_highbit32(remaining) + 1; ++ nbBits = ZSTD_highbit32(remaining) + 1; + threshold = 1 << (nbBits - 1); + } + if (charnum >= maxSV1) break; +@@ -253,7 +237,7 @@ + const void* src, size_t srcSize) + { + U32 wksp[HUF_READ_STATS_WORKSPACE_SIZE_U32]; +- return HUF_readStats_wksp(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, wksp, sizeof(wksp), /* bmi2 */ 0); ++ return HUF_readStats_wksp(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, wksp, sizeof(wksp), /* flags */ 0); + } + + FORCE_INLINE_TEMPLATE size_t +@@ -301,14 +285,14 @@ + if (weightTotal == 0) return ERROR(corruption_detected); + + /* get last non-null symbol weight (implied, total must be 2^n) */ +- { U32 const tableLog = BIT_highbit32(weightTotal) + 1; ++ { U32 const tableLog = ZSTD_highbit32(weightTotal) + 1; + if (tableLog > HUF_TABLELOG_MAX) return ERROR(corruption_detected); + *tableLogPtr = tableLog; + /* determine last weight */ + { U32 const total = 1 << tableLog; + U32 const rest = total - weightTotal; +- U32 const verif = 1 << BIT_highbit32(rest); +- U32 const lastWeight = BIT_highbit32(rest) + 1; ++ U32 const verif = 1 << ZSTD_highbit32(rest); ++ U32 const lastWeight = ZSTD_highbit32(rest) + 1; + if (verif != rest) return ERROR(corruption_detected); /* last value must be a clean power of 2 */ + huffWeight[oSize] = (BYTE)lastWeight; + rankStats[lastWeight]++; +@@ -345,13 +329,13 @@ + U32* nbSymbolsPtr, U32* tableLogPtr, + const void* src, size_t srcSize, + void* workSpace, size_t wkspSize, +- int bmi2) ++ int flags) + { + #if DYNAMIC_BMI2 +- if (bmi2) { ++ if (flags & HUF_flags_bmi2) { + return HUF_readStats_body_bmi2(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } + #endif +- (void)bmi2; ++ (void)flags; + return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +diff '--color=auto' -uraN a/lib/zstd/common/error_private.c b/lib/zstd/common/error_private.c +--- a/lib/zstd/common/error_private.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/error_private.c 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -27,9 +28,11 @@ + case PREFIX(version_unsupported): return "Version not supported"; + case PREFIX(frameParameter_unsupported): return "Unsupported frame parameter"; + case PREFIX(frameParameter_windowTooLarge): return "Frame requires too much memory for decoding"; +- case PREFIX(corruption_detected): return "Corrupted block detected"; ++ case PREFIX(corruption_detected): return "Data corruption detected"; + case PREFIX(checksum_wrong): return "Restored data doesn't match checksum"; ++ case PREFIX(literals_headerWrong): return "Header of Literals' block doesn't respect format specification"; + case PREFIX(parameter_unsupported): return "Unsupported parameter"; ++ case PREFIX(parameter_combination_unsupported): return "Unsupported combination of parameters"; + case PREFIX(parameter_outOfBound): return "Parameter is out of bound"; + case PREFIX(init_missing): return "Context should be init first"; + case PREFIX(memory_allocation): return "Allocation error : not enough memory"; +@@ -38,17 +41,22 @@ + case PREFIX(tableLog_tooLarge): return "tableLog requires too much memory : unsupported"; + case PREFIX(maxSymbolValue_tooLarge): return "Unsupported max Symbol Value : too large"; + case PREFIX(maxSymbolValue_tooSmall): return "Specified maxSymbolValue is too small"; ++ case PREFIX(stabilityCondition_notRespected): return "pledged buffer stability condition is not respected"; + case PREFIX(dictionary_corrupted): return "Dictionary is corrupted"; + case PREFIX(dictionary_wrong): return "Dictionary mismatch"; + case PREFIX(dictionaryCreation_failed): return "Cannot create Dictionary from provided samples"; + case PREFIX(dstSize_tooSmall): return "Destination buffer is too small"; + case PREFIX(srcSize_wrong): return "Src size is incorrect"; + case PREFIX(dstBuffer_null): return "Operation on NULL destination buffer"; ++ case PREFIX(noForwardProgress_destFull): return "Operation made no progress over multiple calls, due to output buffer being full"; ++ case PREFIX(noForwardProgress_inputEmpty): return "Operation made no progress over multiple calls, due to input being empty"; + /* following error codes are not stable and may be removed or changed in a future version */ + case PREFIX(frameIndex_tooLarge): return "Frame index is too large"; + case PREFIX(seekableIO): return "An I/O error occurred when reading/seeking"; + case PREFIX(dstBuffer_wrong): return "Destination buffer is wrong"; + case PREFIX(srcBuffer_wrong): return "Source buffer is wrong"; ++ case PREFIX(sequenceProducer_failed): return "Block-level external sequence producer returned an error code"; ++ case PREFIX(externalSequences_invalid): return "External sequences are not valid"; + case PREFIX(maxCode): + default: return notErrorCode; + } +diff '--color=auto' -uraN a/lib/zstd/common/error_private.h b/lib/zstd/common/error_private.h +--- a/lib/zstd/common/error_private.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/error_private.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/common/fse.h b/lib/zstd/common/fse.h +--- a/lib/zstd/common/fse.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/fse.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,7 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* ****************************************************************** + * FSE : Finite State Entropy codec + * Public Prototypes declaration +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -50,34 +51,6 @@ + FSE_PUBLIC_API unsigned FSE_versionNumber(void); /*< library version number; to be used when checking dll version */ + + +-/*-**************************************** +-* FSE simple functions +-******************************************/ +-/*! FSE_compress() : +- Compress content of buffer 'src', of size 'srcSize', into destination buffer 'dst'. +- 'dst' buffer must be already allocated. Compression runs faster is dstCapacity >= FSE_compressBound(srcSize). +- @return : size of compressed data (<= dstCapacity). +- Special values : if return == 0, srcData is not compressible => Nothing is stored within dst !!! +- if return == 1, srcData is a single byte symbol * srcSize times. Use RLE compression instead. +- if FSE_isError(return), compression failed (more details using FSE_getErrorName()) +-*/ +-FSE_PUBLIC_API size_t FSE_compress(void* dst, size_t dstCapacity, +- const void* src, size_t srcSize); +- +-/*! FSE_decompress(): +- Decompress FSE data from buffer 'cSrc', of size 'cSrcSize', +- into already allocated destination buffer 'dst', of size 'dstCapacity'. +- @return : size of regenerated data (<= maxDstSize), +- or an error code, which can be tested using FSE_isError() . +- +- ** Important ** : FSE_decompress() does not decompress non-compressible nor RLE data !!! +- Why ? : making this distinction requires a header. +- Header management is intentionally delegated to the user layer, which can better manage special cases. +-*/ +-FSE_PUBLIC_API size_t FSE_decompress(void* dst, size_t dstCapacity, +- const void* cSrc, size_t cSrcSize); +- +- + /*-***************************************** + * Tool functions + ******************************************/ +@@ -89,20 +62,6 @@ + + + /*-***************************************** +-* FSE advanced functions +-******************************************/ +-/*! FSE_compress2() : +- Same as FSE_compress(), but allows the selection of 'maxSymbolValue' and 'tableLog' +- Both parameters can be defined as '0' to mean : use default value +- @return : size of compressed data +- Special values : if return == 0, srcData is not compressible => Nothing is stored within cSrc !!! +- if return == 1, srcData is a single byte symbol * srcSize times. Use RLE compression. +- if FSE_isError(return), it's an error code. +-*/ +-FSE_PUBLIC_API size_t FSE_compress2 (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog); +- +- +-/*-***************************************** + * FSE detailed API + ******************************************/ + /*! +@@ -161,8 +120,6 @@ + /*! Constructor and Destructor of FSE_CTable. + Note that FSE_CTable size depends on 'tableLog' and 'maxSymbolValue' */ + typedef unsigned FSE_CTable; /* don't allocate that. It's only meant to be more restrictive than void* */ +-FSE_PUBLIC_API FSE_CTable* FSE_createCTable (unsigned maxSymbolValue, unsigned tableLog); +-FSE_PUBLIC_API void FSE_freeCTable (FSE_CTable* ct); + + /*! FSE_buildCTable(): + Builds `ct`, which must be already allocated, using FSE_createCTable(). +@@ -238,23 +195,7 @@ + unsigned* maxSymbolValuePtr, unsigned* tableLogPtr, + const void* rBuffer, size_t rBuffSize, int bmi2); + +-/*! Constructor and Destructor of FSE_DTable. +- Note that its size depends on 'tableLog' */ + typedef unsigned FSE_DTable; /* don't allocate that. It's just a way to be more restrictive than void* */ +-FSE_PUBLIC_API FSE_DTable* FSE_createDTable(unsigned tableLog); +-FSE_PUBLIC_API void FSE_freeDTable(FSE_DTable* dt); +- +-/*! FSE_buildDTable(): +- Builds 'dt', which must be already allocated, using FSE_createDTable(). +- return : 0, or an errorCode, which can be tested using FSE_isError() */ +-FSE_PUBLIC_API size_t FSE_buildDTable (FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog); +- +-/*! FSE_decompress_usingDTable(): +- Decompress compressed source `cSrc` of size `cSrcSize` using `dt` +- into `dst` which must be already allocated. +- @return : size of regenerated data (necessarily <= `dstCapacity`), +- or an errorCode, which can be tested using FSE_isError() */ +-FSE_PUBLIC_API size_t FSE_decompress_usingDTable(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, const FSE_DTable* dt); + + /*! + Tutorial : +@@ -317,16 +258,6 @@ + unsigned FSE_optimalTableLog_internal(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, unsigned minus); + /*< same as FSE_optimalTableLog(), which used `minus==2` */ + +-/* FSE_compress_wksp() : +- * Same as FSE_compress2(), but using an externally allocated scratch buffer (`workSpace`). +- * FSE_COMPRESS_WKSP_SIZE_U32() provides the minimum size required for `workSpace` as a table of FSE_CTable. +- */ +-#define FSE_COMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) ( FSE_CTABLE_SIZE_U32(maxTableLog, maxSymbolValue) + ((maxTableLog > 12) ? (1 << (maxTableLog - 2)) : 1024) ) +-size_t FSE_compress_wksp (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize); +- +-size_t FSE_buildCTable_raw (FSE_CTable* ct, unsigned nbBits); +-/*< build a fake FSE_CTable, designed for a flat distribution, where each symbol uses nbBits */ +- + size_t FSE_buildCTable_rle (FSE_CTable* ct, unsigned char symbolValue); + /*< build a fake FSE_CTable, designed to compress always the same symbolValue */ + +@@ -344,19 +275,11 @@ + FSE_PUBLIC_API size_t FSE_buildDTable_wksp(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize); + /*< Same as FSE_buildDTable(), using an externally allocated `workspace` produced with `FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxSymbolValue)` */ + +-size_t FSE_buildDTable_raw (FSE_DTable* dt, unsigned nbBits); +-/*< build a fake FSE_DTable, designed to read a flat distribution where each symbol uses nbBits */ +- +-size_t FSE_buildDTable_rle (FSE_DTable* dt, unsigned char symbolValue); +-/*< build a fake FSE_DTable, designed to always generate the same symbolValue */ +- +-#define FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) (FSE_DTABLE_SIZE_U32(maxTableLog) + FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) + (FSE_MAX_SYMBOL_VALUE + 1) / 2 + 1) ++#define FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) (FSE_DTABLE_SIZE_U32(maxTableLog) + 1 + FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) + (FSE_MAX_SYMBOL_VALUE + 1) / 2 + 1) + #define FSE_DECOMPRESS_WKSP_SIZE(maxTableLog, maxSymbolValue) (FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) * sizeof(unsigned)) +-size_t FSE_decompress_wksp(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize); +-/*< same as FSE_decompress(), using an externally allocated `workSpace` produced with `FSE_DECOMPRESS_WKSP_SIZE_U32(maxLog, maxSymbolValue)` */ +- + size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2); +-/*< Same as FSE_decompress_wksp() but with dynamic BMI2 support. Pass 1 if your CPU supports BMI2 or 0 if it doesn't. */ ++/*< same as FSE_decompress(), using an externally allocated `workSpace` produced with `FSE_DECOMPRESS_WKSP_SIZE_U32(maxLog, maxSymbolValue)`. ++ * Set bmi2 to 1 if your CPU supports BMI2 or 0 if it doesn't */ + + typedef enum { + FSE_repeat_none, /*< Cannot use the previous table */ +@@ -552,7 +475,7 @@ + + /* FSE_getMaxNbBits() : + * Approximate maximum cost of a symbol, in bits. +- * Fractional get rounded up (i.e : a symbol with a normalized frequency of 3 gives the same result as a frequency of 2) ++ * Fractional get rounded up (i.e. a symbol with a normalized frequency of 3 gives the same result as a frequency of 2) + * note 1 : assume symbolValue is valid (<= maxSymbolValue) + * note 2 : if freq[symbolValue]==0, @return a fake cost of tableLog+1 bits */ + MEM_STATIC U32 FSE_getMaxNbBits(const void* symbolTTPtr, U32 symbolValue) +diff '--color=auto' -uraN a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c +--- a/lib/zstd/common/fse_decompress.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/fse_decompress.c 2023-11-04 16:35:57.844650691 +0300 +@@ -1,6 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* ****************************************************************** + * FSE : Finite State Entropy decoder +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -24,6 +25,7 @@ + #include "error_private.h" + #define ZSTD_DEPS_NEED_MALLOC + #include "zstd_deps.h" ++#include "bits.h" /* ZSTD_highbit32 */ + + + /* ************************************************************** +@@ -55,19 +57,6 @@ + #define FSE_FUNCTION_NAME(X,Y) FSE_CAT(X,Y) + #define FSE_TYPE_NAME(X,Y) FSE_CAT(X,Y) + +- +-/* Function templates */ +-FSE_DTable* FSE_createDTable (unsigned tableLog) +-{ +- if (tableLog > FSE_TABLELOG_ABSOLUTE_MAX) tableLog = FSE_TABLELOG_ABSOLUTE_MAX; +- return (FSE_DTable*)ZSTD_malloc( FSE_DTABLE_SIZE_U32(tableLog) * sizeof (U32) ); +-} +- +-void FSE_freeDTable (FSE_DTable* dt) +-{ +- ZSTD_free(dt); +-} +- + static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize) + { + void* const tdPtr = dt+1; /* because *dt is unsigned, 32-bits aligned on 32-bits */ +@@ -127,10 +116,10 @@ + } + } + /* Now we spread those positions across the table. +- * The benefit of doing it in two stages is that we avoid the the ++ * The benefit of doing it in two stages is that we avoid the + * variable size inner loop, which caused lots of branch misses. + * Now we can run through all the positions without any branch misses. +- * We unroll the loop twice, since that is what emperically worked best. ++ * We unroll the loop twice, since that is what empirically worked best. + */ + { + size_t position = 0; +@@ -166,7 +155,7 @@ + for (u=0; utableLog = 0; +- DTableH->fastMode = 0; +- +- cell->newState = 0; +- cell->symbol = symbolValue; +- cell->nbBits = 0; +- +- return 0; +-} +- +- +-size_t FSE_buildDTable_raw (FSE_DTable* dt, unsigned nbBits) +-{ +- void* ptr = dt; +- FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr; +- void* dPtr = dt + 1; +- FSE_decode_t* const dinfo = (FSE_decode_t*)dPtr; +- const unsigned tableSize = 1 << nbBits; +- const unsigned tableMask = tableSize - 1; +- const unsigned maxSV1 = tableMask+1; +- unsigned s; +- +- /* Sanity checks */ +- if (nbBits < 1) return ERROR(GENERIC); /* min size */ +- +- /* Build Decoding Table */ +- DTableH->tableLog = (U16)nbBits; +- DTableH->fastMode = 1; +- for (s=0; sfastMode; +- +- /* select fast mode (static) */ +- if (fastMode) return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 1); +- return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 0); +-} +- +- +-size_t FSE_decompress_wksp(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize) +-{ +- return FSE_decompress_wksp_bmi2(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, /* bmi2 */ 0); +-} +- + typedef struct { + short ncount[FSE_MAX_SYMBOL_VALUE + 1]; + FSE_DTable dtable[1]; /* Dynamically sized */ +@@ -342,7 +268,8 @@ + } + + if (FSE_DECOMPRESS_WKSP_SIZE(tableLog, maxSymbolValue) > wkspSize) return ERROR(tableLog_tooLarge); +- workSpace = wksp->dtable + FSE_DTABLE_SIZE_U32(tableLog); ++ assert(sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog) <= wkspSize); ++ workSpace = (BYTE*)workSpace + sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog); + wkspSize -= sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog); + + CHECK_F( FSE_buildDTable_internal(wksp->dtable, wksp->ncount, maxSymbolValue, tableLog, workSpace, wkspSize) ); +@@ -382,9 +309,4 @@ + return FSE_decompress_wksp_body_default(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } + +- +-typedef FSE_DTable DTable_max_t[FSE_DTABLE_SIZE_U32(FSE_MAX_TABLELOG)]; +- +- +- + #endif /* FSE_COMMONDEFS_ONLY */ +diff '--color=auto' -uraN a/lib/zstd/common/huf.h b/lib/zstd/common/huf.h +--- a/lib/zstd/common/huf.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/huf.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,7 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* ****************************************************************** + * huff0 huffman codec, + * part of Finite State Entropy library +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -18,99 +19,22 @@ + + /* *** Dependencies *** */ + #include "zstd_deps.h" /* size_t */ +- +- +-/* *** library symbols visibility *** */ +-/* Note : when linking with -fvisibility=hidden on gcc, or by default on Visual, +- * HUF symbols remain "private" (internal symbols for library only). +- * Set macro FSE_DLL_EXPORT to 1 if you want HUF symbols visible on DLL interface */ +-#if defined(FSE_DLL_EXPORT) && (FSE_DLL_EXPORT==1) && defined(__GNUC__) && (__GNUC__ >= 4) +-# define HUF_PUBLIC_API __attribute__ ((visibility ("default"))) +-#elif defined(FSE_DLL_EXPORT) && (FSE_DLL_EXPORT==1) /* Visual expected */ +-# define HUF_PUBLIC_API __declspec(dllexport) +-#elif defined(FSE_DLL_IMPORT) && (FSE_DLL_IMPORT==1) +-# define HUF_PUBLIC_API __declspec(dllimport) /* not required, just to generate faster code (saves a function pointer load from IAT and an indirect jump) */ +-#else +-# define HUF_PUBLIC_API +-#endif +- +- +-/* ========================== */ +-/* *** simple functions *** */ +-/* ========================== */ +- +-/* HUF_compress() : +- * Compress content from buffer 'src', of size 'srcSize', into buffer 'dst'. +- * 'dst' buffer must be already allocated. +- * Compression runs faster if `dstCapacity` >= HUF_compressBound(srcSize). +- * `srcSize` must be <= `HUF_BLOCKSIZE_MAX` == 128 KB. +- * @return : size of compressed data (<= `dstCapacity`). +- * Special values : if return == 0, srcData is not compressible => Nothing is stored within dst !!! +- * if HUF_isError(return), compression failed (more details using HUF_getErrorName()) +- */ +-HUF_PUBLIC_API size_t HUF_compress(void* dst, size_t dstCapacity, +- const void* src, size_t srcSize); +- +-/* HUF_decompress() : +- * Decompress HUF data from buffer 'cSrc', of size 'cSrcSize', +- * into already allocated buffer 'dst', of minimum size 'dstSize'. +- * `originalSize` : **must** be the ***exact*** size of original (uncompressed) data. +- * Note : in contrast with FSE, HUF_decompress can regenerate +- * RLE (cSrcSize==1) and uncompressed (cSrcSize==dstSize) data, +- * because it knows size to regenerate (originalSize). +- * @return : size of regenerated data (== originalSize), +- * or an error code, which can be tested using HUF_isError() +- */ +-HUF_PUBLIC_API size_t HUF_decompress(void* dst, size_t originalSize, +- const void* cSrc, size_t cSrcSize); ++#include "mem.h" /* U32 */ ++#define FSE_STATIC_LINKING_ONLY ++#include "fse.h" + + + /* *** Tool functions *** */ +-#define HUF_BLOCKSIZE_MAX (128 * 1024) /*< maximum input size for a single block compressed with HUF_compress */ +-HUF_PUBLIC_API size_t HUF_compressBound(size_t size); /*< maximum compressed size (worst case) */ ++#define HUF_BLOCKSIZE_MAX (128 * 1024) /*< maximum input size for a single block compressed with HUF_compress */ ++size_t HUF_compressBound(size_t size); /*< maximum compressed size (worst case) */ + + /* Error Management */ +-HUF_PUBLIC_API unsigned HUF_isError(size_t code); /*< tells if a return value is an error code */ +-HUF_PUBLIC_API const char* HUF_getErrorName(size_t code); /*< provides error code string (useful for debugging) */ ++unsigned HUF_isError(size_t code); /*< tells if a return value is an error code */ ++const char* HUF_getErrorName(size_t code); /*< provides error code string (useful for debugging) */ + + +-/* *** Advanced function *** */ +- +-/* HUF_compress2() : +- * Same as HUF_compress(), but offers control over `maxSymbolValue` and `tableLog`. +- * `maxSymbolValue` must be <= HUF_SYMBOLVALUE_MAX . +- * `tableLog` must be `<= HUF_TABLELOG_MAX` . */ +-HUF_PUBLIC_API size_t HUF_compress2 (void* dst, size_t dstCapacity, +- const void* src, size_t srcSize, +- unsigned maxSymbolValue, unsigned tableLog); +- +-/* HUF_compress4X_wksp() : +- * Same as HUF_compress2(), but uses externally allocated `workSpace`. +- * `workspace` must be at least as large as HUF_WORKSPACE_SIZE */ + #define HUF_WORKSPACE_SIZE ((8 << 10) + 512 /* sorting scratch space */) + #define HUF_WORKSPACE_SIZE_U64 (HUF_WORKSPACE_SIZE / sizeof(U64)) +-HUF_PUBLIC_API size_t HUF_compress4X_wksp (void* dst, size_t dstCapacity, +- const void* src, size_t srcSize, +- unsigned maxSymbolValue, unsigned tableLog, +- void* workSpace, size_t wkspSize); +- +-#endif /* HUF_H_298734234 */ +- +-/* ****************************************************************** +- * WARNING !! +- * The following section contains advanced and experimental definitions +- * which shall never be used in the context of a dynamic library, +- * because they are not guaranteed to remain stable in the future. +- * Only consider them in association with static linking. +- * *****************************************************************/ +-#if !defined(HUF_H_HUF_STATIC_LINKING_ONLY) +-#define HUF_H_HUF_STATIC_LINKING_ONLY +- +-/* *** Dependencies *** */ +-#include "mem.h" /* U32 */ +-#define FSE_STATIC_LINKING_ONLY +-#include "fse.h" +- + + /* *** Constants *** */ + #define HUF_TABLELOG_MAX 12 /* max runtime value of tableLog (due to static allocation); can be modified up to HUF_TABLELOG_ABSOLUTEMAX */ +@@ -151,25 +75,49 @@ + /* **************************************** + * Advanced decompression functions + ******************************************/ +-size_t HUF_decompress4X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< single-symbol decoder */ +-#ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_decompress4X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< double-symbols decoder */ +-#endif + +-size_t HUF_decompress4X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< decodes RLE and uncompressed */ +-size_t HUF_decompress4X_hufOnly(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< considers RLE and uncompressed as errors */ +-size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /*< considers RLE and uncompressed as errors */ +-size_t HUF_decompress4X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< single-symbol decoder */ +-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); /*< single-symbol decoder */ +-#ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_decompress4X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< double-symbols decoder */ +-size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /*< double-symbols decoder */ +-#endif ++/* ++ * Huffman flags bitset. ++ * For all flags, 0 is the default value. ++ */ ++typedef enum { ++ /* ++ * If compiled with DYNAMIC_BMI2: Set flag only if the CPU supports BMI2 at runtime. ++ * Otherwise: Ignored. ++ */ ++ HUF_flags_bmi2 = (1 << 0), ++ /* ++ * If set: Test possible table depths to find the one that produces the smallest header + encoded size. ++ * If unset: Use heuristic to find the table depth. ++ */ ++ HUF_flags_optimalDepth = (1 << 1), ++ /* ++ * If set: If the previous table can encode the input, always reuse the previous table. ++ * If unset: If the previous table can encode the input, reuse the previous table if it results in a smaller output. ++ */ ++ HUF_flags_preferRepeat = (1 << 2), ++ /* ++ * If set: Sample the input and check if the sample is uncompressible, if it is then don't attempt to compress. ++ * If unset: Always histogram the entire input. ++ */ ++ HUF_flags_suspectUncompressible = (1 << 3), ++ /* ++ * If set: Don't use assembly implementations ++ * If unset: Allow using assembly implementations ++ */ ++ HUF_flags_disableAsm = (1 << 4), ++ /* ++ * If set: Don't use the fast decoding loop, always use the fallback decoding loop. ++ * If unset: Use the fast decoding loop when possible. ++ */ ++ HUF_flags_disableFast = (1 << 5) ++} HUF_flags_e; + + + /* **************************************** + * HUF detailed API + * ****************************************/ ++#define HUF_OPTIMAL_DEPTH_THRESHOLD ZSTD_btultra + + /*! HUF_compress() does the following: + * 1. count symbol occurrence from source[] into table count[] using FSE_count() (exposed within "fse.h") +@@ -182,12 +130,12 @@ + * For example, it's possible to compress several blocks using the same 'CTable', + * or to save and regenerate 'CTable' using external methods. + */ +-unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue); +-size_t HUF_buildCTable (HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue, unsigned maxNbBits); /* @return : maxNbBits; CTable and count can overlap. In which case, CTable will overwrite count content */ +-size_t HUF_writeCTable (void* dst, size_t maxDstSize, const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog); ++unsigned HUF_minTableLog(unsigned symbolCardinality); ++unsigned HUF_cardinality(const unsigned* count, unsigned maxSymbolValue); ++unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, void* workSpace, ++ size_t wkspSize, HUF_CElt* table, const unsigned* count, int flags); /* table is used as scratch space for building and testing tables, not a return value */ + size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog, void* workspace, size_t workspaceSize); +-size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable); +-size_t HUF_compress4X_usingCTable_bmi2(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int bmi2); ++size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags); + size_t HUF_estimateCompressedSize(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue); + int HUF_validateCTable(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue); + +@@ -196,6 +144,7 @@ + HUF_repeat_check, /*< Can use the previous table but it must be checked. Note : The previous table must have been constructed by HUF_compress{1, 4}X_repeat */ + HUF_repeat_valid /*< Can use the previous table and it is assumed to be valid */ + } HUF_repeat; ++ + /* HUF_compress4X_repeat() : + * Same as HUF_compress4X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none. + * If it uses hufTable it does not modify hufTable or repeat. +@@ -206,13 +155,13 @@ + const void* src, size_t srcSize, + unsigned maxSymbolValue, unsigned tableLog, + void* workSpace, size_t wkspSize, /*< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */ +- HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2, unsigned suspectUncompressible); ++ HUF_CElt* hufTable, HUF_repeat* repeat, int flags); + + /* HUF_buildCTable_wksp() : + * Same as HUF_buildCTable(), but using externally allocated scratch buffer. + * `workSpace` must be aligned on 4-bytes boundaries, and its size must be >= HUF_CTABLE_WORKSPACE_SIZE. + */ +-#define HUF_CTABLE_WORKSPACE_SIZE_U32 (2*HUF_SYMBOLVALUE_MAX +1 +1) ++#define HUF_CTABLE_WORKSPACE_SIZE_U32 ((4 * (HUF_SYMBOLVALUE_MAX + 1)) + 192) + #define HUF_CTABLE_WORKSPACE_SIZE (HUF_CTABLE_WORKSPACE_SIZE_U32 * sizeof(unsigned)) + size_t HUF_buildCTable_wksp (HUF_CElt* tree, + const unsigned* count, U32 maxSymbolValue, U32 maxNbBits, +@@ -238,7 +187,7 @@ + U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr, + const void* src, size_t srcSize, + void* workspace, size_t wkspSize, +- int bmi2); ++ int flags); + + /* HUF_readCTable() : + * Loading a CTable saved with HUF_writeCTable() */ +@@ -276,32 +225,12 @@ + #define HUF_DECOMPRESS_WORKSPACE_SIZE ((2 << 10) + (1 << 9)) + #define HUF_DECOMPRESS_WORKSPACE_SIZE_U32 (HUF_DECOMPRESS_WORKSPACE_SIZE / sizeof(U32)) + +-#ifndef HUF_FORCE_DECOMPRESS_X2 +-size_t HUF_readDTableX1 (HUF_DTable* DTable, const void* src, size_t srcSize); +-size_t HUF_readDTableX1_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); +-#endif +-#ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_readDTableX2 (HUF_DTable* DTable, const void* src, size_t srcSize); +-size_t HUF_readDTableX2_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); +-#endif +- +-size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); +-#ifndef HUF_FORCE_DECOMPRESS_X2 +-size_t HUF_decompress4X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); +-#endif +-#ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_decompress4X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); +-#endif +- + + /* ====================== */ + /* single stream variants */ + /* ====================== */ + +-size_t HUF_compress1X (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog); +-size_t HUF_compress1X_wksp (void* dst, size_t dstSize, const void* src, size_t srcSize, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize); /*< `workSpace` must be a table of at least HUF_WORKSPACE_SIZE_U64 U64 */ +-size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable); +-size_t HUF_compress1X_usingCTable_bmi2(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int bmi2); ++size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags); + /* HUF_compress1X_repeat() : + * Same as HUF_compress1X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none. + * If it uses hufTable it does not modify hufTable or repeat. +@@ -312,47 +241,28 @@ + const void* src, size_t srcSize, + unsigned maxSymbolValue, unsigned tableLog, + void* workSpace, size_t wkspSize, /*< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */ +- HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2, unsigned suspectUncompressible); +- +-size_t HUF_decompress1X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* single-symbol decoder */ +-#ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_decompress1X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* double-symbol decoder */ +-#endif +- +-size_t HUF_decompress1X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); +-size_t HUF_decompress1X_DCtx_wksp (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); +-#ifndef HUF_FORCE_DECOMPRESS_X2 +-size_t HUF_decompress1X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< single-symbol decoder */ +-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); /*< single-symbol decoder */ +-#endif +-#ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_decompress1X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /*< double-symbols decoder */ +-size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /*< double-symbols decoder */ +-#endif ++ HUF_CElt* hufTable, HUF_repeat* repeat, int flags); + +-size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); /*< automatic selection of sing or double symbol decoder, based on DTable */ +-#ifndef HUF_FORCE_DECOMPRESS_X2 +-size_t HUF_decompress1X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); +-#endif ++size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); + #ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_decompress1X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); ++size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); /*< double-symbols decoder */ + #endif + + /* BMI2 variants. + * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0. + */ +-size_t HUF_decompress1X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); ++size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags); + #ifndef HUF_FORCE_DECOMPRESS_X2 +-size_t HUF_decompress1X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); ++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, int flags); + #endif +-size_t HUF_decompress4X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); +-size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); ++size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags); ++size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); + #ifndef HUF_FORCE_DECOMPRESS_X2 +-size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int bmi2); ++size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags); + #endif + #ifndef HUF_FORCE_DECOMPRESS_X1 +-size_t HUF_readDTableX2_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int bmi2); ++size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags); + #endif + +-#endif /* HUF_STATIC_LINKING_ONLY */ ++#endif /* HUF_H_298734234 */ + +diff '--color=auto' -uraN a/lib/zstd/common/mem.h b/lib/zstd/common/mem.h +--- a/lib/zstd/common/mem.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/mem.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,6 +1,6 @@ + /* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/common/portability_macros.h b/lib/zstd/common/portability_macros.h +--- a/lib/zstd/common/portability_macros.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/portability_macros.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -12,7 +13,7 @@ + #define ZSTD_PORTABILITY_MACROS_H + + /* +- * This header file contains macro defintions to support portability. ++ * This header file contains macro definitions to support portability. + * This header is shared between C and ASM code, so it MUST only + * contain macro definitions. It MUST not contain any C code. + * +@@ -65,7 +66,7 @@ + #endif + + /* +- * Only enable assembly for GNUC comptabile compilers, ++ * Only enable assembly for GNUC compatible compilers, + * because other platforms may not support GAS assembly syntax. + * + * Only enable assembly for Linux / MacOS, other platforms may +@@ -90,4 +91,23 @@ + */ + #define ZSTD_ENABLE_ASM_X86_64_BMI2 0 + ++/* ++ * For x86 ELF targets, add .note.gnu.property section for Intel CET in ++ * assembly sources when CET is enabled. ++ * ++ * Additionally, any function that may be called indirectly must begin ++ * with ZSTD_CET_ENDBRANCH. ++ */ ++#if defined(__ELF__) && (defined(__x86_64__) || defined(__i386__)) \ ++ && defined(__has_include) ++# if __has_include() ++# include ++# define ZSTD_CET_ENDBRANCH _CET_ENDBR ++# endif ++#endif ++ ++#ifndef ZSTD_CET_ENDBRANCH ++# define ZSTD_CET_ENDBRANCH ++#endif ++ + #endif /* ZSTD_PORTABILITY_MACROS_H */ +diff '--color=auto' -uraN a/lib/zstd/common/zstd_common.c b/lib/zstd/common/zstd_common.c +--- a/lib/zstd/common/zstd_common.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/zstd_common.c 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -14,7 +15,6 @@ + * Dependencies + ***************************************/ + #define ZSTD_DEPS_NEED_MALLOC +-#include "zstd_deps.h" /* ZSTD_malloc, ZSTD_calloc, ZSTD_free, ZSTD_memset */ + #include "error_private.h" + #include "zstd_internal.h" + +@@ -47,37 +47,3 @@ + /*! ZSTD_getErrorString() : + * provides error code string from enum */ + const char* ZSTD_getErrorString(ZSTD_ErrorCode code) { return ERR_getErrorString(code); } +- +- +- +-/*=************************************************************** +-* Custom allocator +-****************************************************************/ +-void* ZSTD_customMalloc(size_t size, ZSTD_customMem customMem) +-{ +- if (customMem.customAlloc) +- return customMem.customAlloc(customMem.opaque, size); +- return ZSTD_malloc(size); +-} +- +-void* ZSTD_customCalloc(size_t size, ZSTD_customMem customMem) +-{ +- if (customMem.customAlloc) { +- /* calloc implemented as malloc+memset; +- * not as efficient as calloc, but next best guess for custom malloc */ +- void* const ptr = customMem.customAlloc(customMem.opaque, size); +- ZSTD_memset(ptr, 0, size); +- return ptr; +- } +- return ZSTD_calloc(1, size); +-} +- +-void ZSTD_customFree(void* ptr, ZSTD_customMem customMem) +-{ +- if (ptr!=NULL) { +- if (customMem.customFree) +- customMem.customFree(customMem.opaque, ptr); +- else +- ZSTD_free(ptr); +- } +-} +diff '--color=auto' -uraN a/lib/zstd/common/zstd_deps.h b/lib/zstd/common/zstd_deps.h +--- a/lib/zstd/common/zstd_deps.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/zstd_deps.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,6 +1,6 @@ + /* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -105,3 +105,21 @@ + + #endif /* ZSTD_DEPS_IO */ + #endif /* ZSTD_DEPS_NEED_IO */ ++ ++/* ++ * Only requested when MSAN is enabled. ++ * Need: ++ * intptr_t ++ */ ++#ifdef ZSTD_DEPS_NEED_STDINT ++#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; ++ ++#endif /* ZSTD_DEPS_STDINT */ ++#endif /* ZSTD_DEPS_NEED_STDINT */ +diff '--color=auto' -uraN a/lib/zstd/common/zstd_internal.h b/lib/zstd/common/zstd_internal.h +--- a/lib/zstd/common/zstd_internal.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/common/zstd_internal.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -28,7 +29,6 @@ + #include + #define FSE_STATIC_LINKING_ONLY + #include "fse.h" +-#define HUF_STATIC_LINKING_ONLY + #include "huf.h" + #include /* XXH_reset, update, digest */ + #define ZSTD_TRACE 0 +@@ -83,9 +83,9 @@ + #define ZSTD_FRAMECHECKSUMSIZE 4 + + #define MIN_SEQUENCES_SIZE 1 /* nbSeq==0 */ +-#define MIN_CBLOCK_SIZE (1 /*litCSize*/ + 1 /* RLE or RAW */ + MIN_SEQUENCES_SIZE /* nbSeq==0 */) /* for a non-null block */ ++#define MIN_CBLOCK_SIZE (1 /*litCSize*/ + 1 /* RLE or RAW */) /* for a non-null block */ ++#define MIN_LITERALS_FOR_4_STREAMS 6 + +-#define HufLog 12 + typedef enum { set_basic, set_rle, set_compressed, set_repeat } symbolEncodingType_e; + + #define LONGNBSEQ 0x7F00 +@@ -93,6 +93,7 @@ + #define MINMATCH 3 + + #define Litbits 8 ++#define LitHufLog 11 + #define MaxLit ((1<= length) return; + op += 16; +@@ -240,7 +237,6 @@ + COPY16(op, ip); + } + while (op < oend); +-#endif + } + } + +@@ -289,11 +285,11 @@ + typedef struct { + seqDef* sequencesStart; + seqDef* sequences; /* ptr to end of sequences */ +- BYTE* litStart; +- BYTE* lit; /* ptr to end of literals */ +- BYTE* llCode; +- BYTE* mlCode; +- BYTE* ofCode; ++ BYTE* litStart; ++ BYTE* lit; /* ptr to end of literals */ ++ BYTE* llCode; ++ BYTE* mlCode; ++ BYTE* ofCode; + size_t maxNbSeq; + size_t maxNbLit; + +@@ -301,8 +297,8 @@ + * in the seqStore that has a value larger than U16 (if it exists). To do so, we increment + * the existing value of the litLength or matchLength by 0x10000. + */ +- ZSTD_longLengthType_e longLengthType; +- U32 longLengthPos; /* Index of the sequence to apply long length modification to */ ++ ZSTD_longLengthType_e longLengthType; ++ U32 longLengthPos; /* Index of the sequence to apply long length modification to */ + } seqStore_t; + + typedef struct { +@@ -321,10 +317,10 @@ + seqLen.matchLength = seq->mlBase + MINMATCH; + if (seqStore->longLengthPos == (U32)(seq - seqStore->sequencesStart)) { + if (seqStore->longLengthType == ZSTD_llt_literalLength) { +- seqLen.litLength += 0xFFFF; ++ seqLen.litLength += 0x10000; + } + if (seqStore->longLengthType == ZSTD_llt_matchLength) { +- seqLen.matchLength += 0xFFFF; ++ seqLen.matchLength += 0x10000; + } + } + return seqLen; +@@ -337,72 +333,13 @@ + * `decompressedBound != ZSTD_CONTENTSIZE_ERROR` + */ + typedef struct { ++ size_t nbBlocks; + size_t compressedSize; + unsigned long long decompressedBound; + } ZSTD_frameSizeInfo; /* decompress & legacy */ + + const seqStore_t* ZSTD_getSeqStore(const ZSTD_CCtx* ctx); /* compress & dictBuilder */ +-void ZSTD_seqToCodes(const seqStore_t* seqStorePtr); /* compress, dictBuilder, decodeCorpus (shouldn't get its definition from here) */ +- +-/* custom memory allocation functions */ +-void* ZSTD_customMalloc(size_t size, ZSTD_customMem customMem); +-void* ZSTD_customCalloc(size_t size, ZSTD_customMem customMem); +-void ZSTD_customFree(void* ptr, ZSTD_customMem customMem); +- +- +-MEM_STATIC U32 ZSTD_highbit32(U32 val) /* compress, dictBuilder, decodeCorpus */ +-{ +- assert(val != 0); +- { +-# if (__GNUC__ >= 3) /* GCC Intrinsic */ +- return __builtin_clz (val) ^ 31; +-# else /* Software version */ +- static const U32 DeBruijnClz[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; +- U32 v = val; +- v |= v >> 1; +- v |= v >> 2; +- v |= v >> 4; +- v |= v >> 8; +- v |= v >> 16; +- return DeBruijnClz[(v * 0x07C4ACDDU) >> 27]; +-# endif +- } +-} +- +-/* +- * Counts the number of trailing zeros of a `size_t`. +- * Most compilers should support CTZ as a builtin. A backup +- * implementation is provided if the builtin isn't supported, but +- * it may not be terribly efficient. +- */ +-MEM_STATIC unsigned ZSTD_countTrailingZeros(size_t val) +-{ +- if (MEM_64bits()) { +-# if (__GNUC__ >= 4) +- return __builtin_ctzll((U64)val); +-# else +- static const int DeBruijnBytePos[64] = { 0, 1, 2, 7, 3, 13, 8, 19, +- 4, 25, 14, 28, 9, 34, 20, 56, +- 5, 17, 26, 54, 15, 41, 29, 43, +- 10, 31, 38, 35, 21, 45, 49, 57, +- 63, 6, 12, 18, 24, 27, 33, 55, +- 16, 53, 40, 42, 30, 37, 44, 48, +- 62, 11, 23, 32, 52, 39, 36, 47, +- 61, 22, 51, 46, 60, 50, 59, 58 }; +- return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +-# endif +- } else { /* 32 bits */ +-# if (__GNUC__ >= 3) +- return __builtin_ctz((U32)val); +-# else +- static const int DeBruijnBytePos[32] = { 0, 1, 28, 2, 29, 14, 24, 3, +- 30, 22, 20, 15, 25, 17, 4, 8, +- 31, 27, 13, 23, 21, 19, 16, 7, +- 26, 12, 18, 6, 11, 5, 10, 9 }; +- return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +-# endif +- } +-} ++int ZSTD_seqToCodes(const seqStore_t* seqStorePtr); /* compress, dictBuilder, decodeCorpus (shouldn't get its definition from here) */ + + + /* ZSTD_invalidateRepCodes() : +diff '--color=auto' -uraN a/lib/zstd/compress/clevels.h b/lib/zstd/compress/clevels.h +--- a/lib/zstd/compress/clevels.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/clevels.h 2023-11-04 16:35:57.844650691 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/compress/fse_compress.c b/lib/zstd/compress/fse_compress.c +--- a/lib/zstd/compress/fse_compress.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/fse_compress.c 2023-11-04 16:35:57.844650691 +0300 +@@ -1,6 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* ****************************************************************** + * FSE : Finite State Entropy encoder +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -26,6 +27,7 @@ + #define ZSTD_DEPS_NEED_MALLOC + #define ZSTD_DEPS_NEED_MATH64 + #include "../common/zstd_deps.h" /* ZSTD_malloc, ZSTD_free, ZSTD_memcpy, ZSTD_memset */ ++#include "../common/bits.h" /* ZSTD_highbit32 */ + + + /* ************************************************************** +@@ -90,7 +92,7 @@ + assert(tableLog < 16); /* required for threshold strategy to work */ + + /* For explanations on how to distribute symbol values over the table : +- * http://fastcompression.blogspot.fr/2014/02/fse-distributing-symbol-values.html */ ++ * https://fastcompression.blogspot.fr/2014/02/fse-distributing-symbol-values.html */ + + #ifdef __clang_analyzer__ + ZSTD_memset(tableSymbol, 0, sizeof(*tableSymbol) * tableSize); /* useless initialization, just to keep scan-build happy */ +@@ -191,7 +193,7 @@ + break; + default : + assert(normalizedCounter[s] > 1); +- { U32 const maxBitsOut = tableLog - BIT_highbit32 ((U32)normalizedCounter[s]-1); ++ { U32 const maxBitsOut = tableLog - ZSTD_highbit32 ((U32)normalizedCounter[s]-1); + U32 const minStatePlus = (U32)normalizedCounter[s] << maxBitsOut; + symbolTT[s].deltaNbBits = (maxBitsOut << 16) - minStatePlus; + symbolTT[s].deltaFindState = (int)(total - (unsigned)normalizedCounter[s]); +@@ -342,21 +344,11 @@ + * FSE Compression Code + ****************************************************************/ + +-FSE_CTable* FSE_createCTable (unsigned maxSymbolValue, unsigned tableLog) +-{ +- size_t size; +- if (tableLog > FSE_TABLELOG_ABSOLUTE_MAX) tableLog = FSE_TABLELOG_ABSOLUTE_MAX; +- size = FSE_CTABLE_SIZE_U32 (tableLog, maxSymbolValue) * sizeof(U32); +- return (FSE_CTable*)ZSTD_malloc(size); +-} +- +-void FSE_freeCTable (FSE_CTable* ct) { ZSTD_free(ct); } +- + /* provides the minimum logSize to safely represent a distribution */ + static unsigned FSE_minTableLog(size_t srcSize, unsigned maxSymbolValue) + { +- U32 minBitsSrc = BIT_highbit32((U32)(srcSize)) + 1; +- U32 minBitsSymbols = BIT_highbit32(maxSymbolValue) + 2; ++ U32 minBitsSrc = ZSTD_highbit32((U32)(srcSize)) + 1; ++ U32 minBitsSymbols = ZSTD_highbit32(maxSymbolValue) + 2; + U32 minBits = minBitsSrc < minBitsSymbols ? minBitsSrc : minBitsSymbols; + assert(srcSize > 1); /* Not supported, RLE should be used instead */ + return minBits; +@@ -364,7 +356,7 @@ + + unsigned FSE_optimalTableLog_internal(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, unsigned minus) + { +- U32 maxBitsSrc = BIT_highbit32((U32)(srcSize - 1)) - minus; ++ U32 maxBitsSrc = ZSTD_highbit32((U32)(srcSize - 1)) - minus; + U32 tableLog = maxTableLog; + U32 minBits = FSE_minTableLog(srcSize, maxSymbolValue); + assert(srcSize > 1); /* Not supported, RLE should be used instead */ +@@ -532,40 +524,6 @@ + return tableLog; + } + +- +-/* fake FSE_CTable, for raw (uncompressed) input */ +-size_t FSE_buildCTable_raw (FSE_CTable* ct, unsigned nbBits) +-{ +- const unsigned tableSize = 1 << nbBits; +- const unsigned tableMask = tableSize - 1; +- const unsigned maxSymbolValue = tableMask; +- void* const ptr = ct; +- U16* const tableU16 = ( (U16*) ptr) + 2; +- void* const FSCT = ((U32*)ptr) + 1 /* header */ + (tableSize>>1); /* assumption : tableLog >= 1 */ +- FSE_symbolCompressionTransform* const symbolTT = (FSE_symbolCompressionTransform*) (FSCT); +- unsigned s; +- +- /* Sanity checks */ +- if (nbBits < 1) return ERROR(GENERIC); /* min size */ +- +- /* header */ +- tableU16[-2] = (U16) nbBits; +- tableU16[-1] = (U16) maxSymbolValue; +- +- /* Build table */ +- for (s=0; s= 2 ++ ++static size_t showU32(const U32* arr, size_t size) ++{ ++ size_t u; ++ for (u=0; u= sizeof(HUF_WriteCTableWksp)); ++ + /* check conditions */ + if (workspaceSize < sizeof(HUF_WriteCTableWksp)) return ERROR(GENERIC); + if (maxSymbolValue > HUF_SYMBOLVALUE_MAX) return ERROR(maxSymbolValue_tooLarge); +@@ -204,16 +264,6 @@ + return ((maxSymbolValue+1)/2) + 1; + } + +-/*! HUF_writeCTable() : +- `CTable` : Huffman tree to save, using huf representation. +- @return : size of saved CTable */ +-size_t HUF_writeCTable (void* dst, size_t maxDstSize, +- const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog) +-{ +- HUF_WriteCTableWksp wksp; +- return HUF_writeCTable_wksp(dst, maxDstSize, CTable, maxSymbolValue, huffLog, &wksp, sizeof(wksp)); +-} +- + + size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void* src, size_t srcSize, unsigned* hasZeroWeights) + { +@@ -269,68 +319,64 @@ + + U32 HUF_getNbBitsFromCTable(HUF_CElt const* CTable, U32 symbolValue) + { +- const HUF_CElt* ct = CTable + 1; ++ const HUF_CElt* const ct = CTable + 1; + assert(symbolValue <= HUF_SYMBOLVALUE_MAX); + return (U32)HUF_getNbBits(ct[symbolValue]); + } + + +-typedef struct nodeElt_s { +- U32 count; +- U16 parent; +- BYTE byte; +- BYTE nbBits; +-} nodeElt; +- + /* + * HUF_setMaxHeight(): +- * Enforces maxNbBits on the Huffman tree described in huffNode. ++ * Try to enforce @targetNbBits on the Huffman tree described in @huffNode. + * +- * It sets all nodes with nbBits > maxNbBits to be maxNbBits. Then it adjusts +- * the tree to so that it is a valid canonical Huffman tree. ++ * It attempts to convert all nodes with nbBits > @targetNbBits ++ * to employ @targetNbBits instead. Then it adjusts the tree ++ * so that it remains a valid canonical Huffman tree. + * + * @pre The sum of the ranks of each symbol == 2^largestBits, + * where largestBits == huffNode[lastNonNull].nbBits. + * @post The sum of the ranks of each symbol == 2^largestBits, +- * where largestBits is the return value <= maxNbBits. ++ * where largestBits is the return value (expected <= targetNbBits). + * +- * @param huffNode The Huffman tree modified in place to enforce maxNbBits. ++ * @param huffNode The Huffman tree modified in place to enforce targetNbBits. ++ * It's presumed sorted, from most frequent to rarest symbol. + * @param lastNonNull The symbol with the lowest count in the Huffman tree. +- * @param maxNbBits The maximum allowed number of bits, which the Huffman tree ++ * @param targetNbBits The allowed number of bits, which the Huffman tree + * may not respect. After this function the Huffman tree will +- * respect maxNbBits. +- * @return The maximum number of bits of the Huffman tree after adjustment, +- * necessarily no more than maxNbBits. ++ * respect targetNbBits. ++ * @return The maximum number of bits of the Huffman tree after adjustment. + */ +-static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 maxNbBits) ++static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 targetNbBits) + { + const U32 largestBits = huffNode[lastNonNull].nbBits; +- /* early exit : no elt > maxNbBits, so the tree is already valid. */ +- if (largestBits <= maxNbBits) return largestBits; ++ /* early exit : no elt > targetNbBits, so the tree is already valid. */ ++ if (largestBits <= targetNbBits) return largestBits; ++ ++ DEBUGLOG(5, "HUF_setMaxHeight (targetNbBits = %u)", targetNbBits); + + /* there are several too large elements (at least >= 2) */ + { int totalCost = 0; +- const U32 baseCost = 1 << (largestBits - maxNbBits); ++ const U32 baseCost = 1 << (largestBits - targetNbBits); + int n = (int)lastNonNull; + +- /* Adjust any ranks > maxNbBits to maxNbBits. ++ /* Adjust any ranks > targetNbBits to targetNbBits. + * Compute totalCost, which is how far the sum of the ranks is + * we are over 2^largestBits after adjust the offending ranks. + */ +- while (huffNode[n].nbBits > maxNbBits) { ++ while (huffNode[n].nbBits > targetNbBits) { + totalCost += baseCost - (1 << (largestBits - huffNode[n].nbBits)); +- huffNode[n].nbBits = (BYTE)maxNbBits; ++ huffNode[n].nbBits = (BYTE)targetNbBits; + n--; + } +- /* n stops at huffNode[n].nbBits <= maxNbBits */ +- assert(huffNode[n].nbBits <= maxNbBits); +- /* n end at index of smallest symbol using < maxNbBits */ +- while (huffNode[n].nbBits == maxNbBits) --n; ++ /* n stops at huffNode[n].nbBits <= targetNbBits */ ++ assert(huffNode[n].nbBits <= targetNbBits); ++ /* n end at index of smallest symbol using < targetNbBits */ ++ while (huffNode[n].nbBits == targetNbBits) --n; + +- /* renorm totalCost from 2^largestBits to 2^maxNbBits ++ /* renorm totalCost from 2^largestBits to 2^targetNbBits + * note : totalCost is necessarily a multiple of baseCost */ +- assert((totalCost & (baseCost - 1)) == 0); +- totalCost >>= (largestBits - maxNbBits); ++ assert(((U32)totalCost & (baseCost - 1)) == 0); ++ totalCost >>= (largestBits - targetNbBits); + assert(totalCost > 0); + + /* repay normalized cost */ +@@ -339,19 +385,19 @@ + + /* Get pos of last (smallest = lowest cum. count) symbol per rank */ + ZSTD_memset(rankLast, 0xF0, sizeof(rankLast)); +- { U32 currentNbBits = maxNbBits; ++ { U32 currentNbBits = targetNbBits; + int pos; + for (pos=n ; pos >= 0; pos--) { + if (huffNode[pos].nbBits >= currentNbBits) continue; +- currentNbBits = huffNode[pos].nbBits; /* < maxNbBits */ +- rankLast[maxNbBits-currentNbBits] = (U32)pos; ++ currentNbBits = huffNode[pos].nbBits; /* < targetNbBits */ ++ rankLast[targetNbBits-currentNbBits] = (U32)pos; + } } + + while (totalCost > 0) { + /* Try to reduce the next power of 2 above totalCost because we + * gain back half the rank. + */ +- U32 nBitsToDecrease = BIT_highbit32((U32)totalCost) + 1; ++ U32 nBitsToDecrease = ZSTD_highbit32((U32)totalCost) + 1; + for ( ; nBitsToDecrease > 1; nBitsToDecrease--) { + U32 const highPos = rankLast[nBitsToDecrease]; + U32 const lowPos = rankLast[nBitsToDecrease-1]; +@@ -391,7 +437,7 @@ + rankLast[nBitsToDecrease] = noSymbol; + else { + rankLast[nBitsToDecrease]--; +- if (huffNode[rankLast[nBitsToDecrease]].nbBits != maxNbBits-nBitsToDecrease) ++ if (huffNode[rankLast[nBitsToDecrease]].nbBits != targetNbBits-nBitsToDecrease) + rankLast[nBitsToDecrease] = noSymbol; /* this rank is now empty */ + } + } /* while (totalCost > 0) */ +@@ -403,11 +449,11 @@ + * TODO. + */ + while (totalCost < 0) { /* Sometimes, cost correction overshoot */ +- /* special case : no rank 1 symbol (using maxNbBits-1); +- * let's create one from largest rank 0 (using maxNbBits). ++ /* special case : no rank 1 symbol (using targetNbBits-1); ++ * let's create one from largest rank 0 (using targetNbBits). + */ + if (rankLast[1] == noSymbol) { +- while (huffNode[n].nbBits == maxNbBits) n--; ++ while (huffNode[n].nbBits == targetNbBits) n--; + huffNode[n+1].nbBits--; + assert(n >= 0); + rankLast[1] = (U32)(n+1); +@@ -421,7 +467,7 @@ + } /* repay normalized cost */ + } /* there are several too large elements (at least >= 2) */ + +- return maxNbBits; ++ return targetNbBits; + } + + typedef struct { +@@ -429,7 +475,7 @@ + U16 curr; + } rankPos; + +-typedef nodeElt huffNodeTable[HUF_CTABLE_WORKSPACE_SIZE_U32]; ++typedef nodeElt huffNodeTable[2 * (HUF_SYMBOLVALUE_MAX + 1)]; + + /* Number of buckets available for HUF_sort() */ + #define RANK_POSITION_TABLE_SIZE 192 +@@ -448,8 +494,8 @@ + * Let buckets 166 to 192 represent all remaining counts up to RANK_POSITION_MAX_COUNT_LOG using log2 bucketing. + */ + #define RANK_POSITION_MAX_COUNT_LOG 32 +-#define RANK_POSITION_LOG_BUCKETS_BEGIN (RANK_POSITION_TABLE_SIZE - 1) - RANK_POSITION_MAX_COUNT_LOG - 1 /* == 158 */ +-#define RANK_POSITION_DISTINCT_COUNT_CUTOFF RANK_POSITION_LOG_BUCKETS_BEGIN + BIT_highbit32(RANK_POSITION_LOG_BUCKETS_BEGIN) /* == 166 */ ++#define RANK_POSITION_LOG_BUCKETS_BEGIN ((RANK_POSITION_TABLE_SIZE - 1) - RANK_POSITION_MAX_COUNT_LOG - 1 /* == 158 */) ++#define RANK_POSITION_DISTINCT_COUNT_CUTOFF (RANK_POSITION_LOG_BUCKETS_BEGIN + ZSTD_highbit32(RANK_POSITION_LOG_BUCKETS_BEGIN) /* == 166 */) + + /* Return the appropriate bucket index for a given count. See definition of + * RANK_POSITION_DISTINCT_COUNT_CUTOFF for explanation of bucketing strategy. +@@ -457,7 +503,7 @@ + static U32 HUF_getIndex(U32 const count) { + return (count < RANK_POSITION_DISTINCT_COUNT_CUTOFF) + ? count +- : BIT_highbit32(count) + RANK_POSITION_LOG_BUCKETS_BEGIN; ++ : ZSTD_highbit32(count) + RANK_POSITION_LOG_BUCKETS_BEGIN; + } + + /* Helper swap function for HUF_quickSortPartition() */ +@@ -580,7 +626,7 @@ + + /* Sort each bucket. */ + for (n = RANK_POSITION_DISTINCT_COUNT_CUTOFF; n < RANK_POSITION_TABLE_SIZE - 1; ++n) { +- U32 const bucketSize = rankPosition[n].curr-rankPosition[n].base; ++ int const bucketSize = rankPosition[n].curr - rankPosition[n].base; + U32 const bucketStartIdx = rankPosition[n].base; + if (bucketSize > 1) { + assert(bucketStartIdx < maxSymbolValue1); +@@ -591,6 +637,7 @@ + assert(HUF_isSorted(huffNode, maxSymbolValue1)); + } + ++ + /* HUF_buildCTable_wksp() : + * Same as HUF_buildCTable(), but using externally allocated scratch buffer. + * `workSpace` must be aligned on 4-bytes boundaries, and be at least as large as sizeof(HUF_buildCTable_wksp_tables). +@@ -611,6 +658,7 @@ + int lowS, lowN; + int nodeNb = STARTNODE; + int n, nodeRoot; ++ DEBUGLOG(5, "HUF_buildTree (alphabet size = %u)", maxSymbolValue + 1); + /* init for parents */ + nonNullRank = (int)maxSymbolValue; + while(huffNode[nonNullRank].count == 0) nonNullRank--; +@@ -637,6 +685,8 @@ + for (n=0; n<=nonNullRank; n++) + huffNode[n].nbBits = huffNode[ huffNode[n].parent ].nbBits + 1; + ++ DEBUGLOG(6, "Initial distribution of bits completed (%zu sorted symbols)", showHNodeBits(huffNode, maxSymbolValue+1)); ++ + return nonNullRank; + } + +@@ -674,28 +724,36 @@ + CTable[0] = maxNbBits; + } + +-size_t HUF_buildCTable_wksp (HUF_CElt* CTable, const unsigned* count, U32 maxSymbolValue, U32 maxNbBits, void* workSpace, size_t wkspSize) ++size_t ++HUF_buildCTable_wksp(HUF_CElt* CTable, const unsigned* count, U32 maxSymbolValue, U32 maxNbBits, ++ void* workSpace, size_t wkspSize) + { +- HUF_buildCTable_wksp_tables* const wksp_tables = (HUF_buildCTable_wksp_tables*)HUF_alignUpWorkspace(workSpace, &wkspSize, ZSTD_ALIGNOF(U32)); ++ HUF_buildCTable_wksp_tables* const wksp_tables = ++ (HUF_buildCTable_wksp_tables*)HUF_alignUpWorkspace(workSpace, &wkspSize, ZSTD_ALIGNOF(U32)); + nodeElt* const huffNode0 = wksp_tables->huffNodeTbl; + nodeElt* const huffNode = huffNode0+1; + int nonNullRank; + ++ HUF_STATIC_ASSERT(HUF_CTABLE_WORKSPACE_SIZE == sizeof(HUF_buildCTable_wksp_tables)); ++ ++ DEBUGLOG(5, "HUF_buildCTable_wksp (alphabet size = %u)", maxSymbolValue+1); ++ + /* safety checks */ + if (wkspSize < sizeof(HUF_buildCTable_wksp_tables)) +- return ERROR(workSpace_tooSmall); ++ return ERROR(workSpace_tooSmall); + if (maxNbBits == 0) maxNbBits = HUF_TABLELOG_DEFAULT; + if (maxSymbolValue > HUF_SYMBOLVALUE_MAX) +- return ERROR(maxSymbolValue_tooLarge); ++ return ERROR(maxSymbolValue_tooLarge); + ZSTD_memset(huffNode0, 0, sizeof(huffNodeTable)); + + /* sort, decreasing order */ + HUF_sort(huffNode, count, maxSymbolValue, wksp_tables->rankPosition); ++ DEBUGLOG(6, "sorted symbols completed (%zu symbols)", showHNodeSymbols(huffNode, maxSymbolValue+1)); + + /* build tree */ + nonNullRank = HUF_buildTree(huffNode, maxSymbolValue); + +- /* enforce maxTableLog */ ++ /* determine and enforce maxTableLog */ + maxNbBits = HUF_setMaxHeight(huffNode, (U32)nonNullRank, maxNbBits); + if (maxNbBits > HUF_TABLELOG_MAX) return ERROR(GENERIC); /* check fit into table */ + +@@ -804,7 +862,7 @@ + #if DEBUGLEVEL >= 1 + { + size_t const nbBits = HUF_getNbBits(elt); +- size_t const dirtyBits = nbBits == 0 ? 0 : BIT_highbit32((U32)nbBits) + 1; ++ size_t const dirtyBits = nbBits == 0 ? 0 : ZSTD_highbit32((U32)nbBits) + 1; + (void)dirtyBits; + /* Middle bits are 0. */ + assert(((elt >> dirtyBits) << (dirtyBits + nbBits)) == 0); +@@ -884,7 +942,7 @@ + { + size_t const nbBits = bitC->bitPos[0] & 0xFF; + if (bitC->ptr >= bitC->endPtr) return 0; /* overflow detected */ +- return (bitC->ptr - bitC->startPtr) + (nbBits > 0); ++ return (size_t)(bitC->ptr - bitC->startPtr) + (nbBits > 0); + } + } + +@@ -1045,9 +1103,9 @@ + static size_t + HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, + const void* src, size_t srcSize, +- const HUF_CElt* CTable, const int bmi2) ++ const HUF_CElt* CTable, const int flags) + { +- if (bmi2) { ++ if (flags & HUF_flags_bmi2) { + return HUF_compress1X_usingCTable_internal_bmi2(dst, dstSize, src, srcSize, CTable); + } + return HUF_compress1X_usingCTable_internal_default(dst, dstSize, src, srcSize, CTable); +@@ -1058,28 +1116,23 @@ + static size_t + HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, + const void* src, size_t srcSize, +- const HUF_CElt* CTable, const int bmi2) ++ const HUF_CElt* CTable, const int flags) + { +- (void)bmi2; ++ (void)flags; + return HUF_compress1X_usingCTable_internal_body(dst, dstSize, src, srcSize, CTable); + } + + #endif + +-size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable) +-{ +- return HUF_compress1X_usingCTable_bmi2(dst, dstSize, src, srcSize, CTable, /* bmi2 */ 0); +-} +- +-size_t HUF_compress1X_usingCTable_bmi2(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int bmi2) ++size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags) + { +- return HUF_compress1X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, bmi2); ++ return HUF_compress1X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, flags); + } + + static size_t + HUF_compress4X_usingCTable_internal(void* dst, size_t dstSize, + const void* src, size_t srcSize, +- const HUF_CElt* CTable, int bmi2) ++ const HUF_CElt* CTable, int flags) + { + size_t const segmentSize = (srcSize+3)/4; /* first 3 segments */ + const BYTE* ip = (const BYTE*) src; +@@ -1093,7 +1146,7 @@ + op += 6; /* jumpTable */ + + assert(op <= oend); +- { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, bmi2) ); ++ { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; + MEM_writeLE16(ostart, (U16)cSize); + op += cSize; +@@ -1101,7 +1154,7 @@ + + ip += segmentSize; + assert(op <= oend); +- { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, bmi2) ); ++ { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; + MEM_writeLE16(ostart+2, (U16)cSize); + op += cSize; +@@ -1109,7 +1162,7 @@ + + ip += segmentSize; + assert(op <= oend); +- { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, bmi2) ); ++ { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, segmentSize, CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; + MEM_writeLE16(ostart+4, (U16)cSize); + op += cSize; +@@ -1118,7 +1171,7 @@ + ip += segmentSize; + assert(op <= oend); + assert(ip <= iend); +- { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, (size_t)(iend-ip), CTable, bmi2) ); ++ { CHECK_V_F(cSize, HUF_compress1X_usingCTable_internal(op, (size_t)(oend-op), ip, (size_t)(iend-ip), CTable, flags) ); + if (cSize == 0 || cSize > 65535) return 0; + op += cSize; + } +@@ -1126,14 +1179,9 @@ + return (size_t)(op-ostart); + } + +-size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable) ++size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags) + { +- return HUF_compress4X_usingCTable_bmi2(dst, dstSize, src, srcSize, CTable, /* bmi2 */ 0); +-} +- +-size_t HUF_compress4X_usingCTable_bmi2(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int bmi2) +-{ +- return HUF_compress4X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, bmi2); ++ return HUF_compress4X_usingCTable_internal(dst, dstSize, src, srcSize, CTable, flags); + } + + typedef enum { HUF_singleStream, HUF_fourStreams } HUF_nbStreams_e; +@@ -1141,11 +1189,11 @@ + static size_t HUF_compressCTable_internal( + BYTE* const ostart, BYTE* op, BYTE* const oend, + const void* src, size_t srcSize, +- HUF_nbStreams_e nbStreams, const HUF_CElt* CTable, const int bmi2) ++ HUF_nbStreams_e nbStreams, const HUF_CElt* CTable, const int flags) + { + size_t const cSize = (nbStreams==HUF_singleStream) ? +- HUF_compress1X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, bmi2) : +- HUF_compress4X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, bmi2); ++ HUF_compress1X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, flags) : ++ HUF_compress4X_usingCTable_internal(op, (size_t)(oend - op), src, srcSize, CTable, flags); + if (HUF_isError(cSize)) { return cSize; } + if (cSize==0) { return 0; } /* uncompressible */ + op += cSize; +@@ -1168,6 +1216,79 @@ + #define SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE 4096 + #define SUSPECT_INCOMPRESSIBLE_SAMPLE_RATIO 10 /* Must be >= 2 */ + ++unsigned HUF_cardinality(const unsigned* count, unsigned maxSymbolValue) ++{ ++ unsigned cardinality = 0; ++ unsigned i; ++ ++ for (i = 0; i < maxSymbolValue + 1; i++) { ++ if (count[i] != 0) cardinality += 1; ++ } ++ ++ return cardinality; ++} ++ ++unsigned HUF_minTableLog(unsigned symbolCardinality) ++{ ++ U32 minBitsSymbols = ZSTD_highbit32(symbolCardinality) + 1; ++ return minBitsSymbols; ++} ++ ++unsigned HUF_optimalTableLog( ++ unsigned maxTableLog, ++ size_t srcSize, ++ unsigned maxSymbolValue, ++ void* workSpace, size_t wkspSize, ++ HUF_CElt* table, ++ const unsigned* count, ++ int flags) ++{ ++ assert(srcSize > 1); /* Not supported, RLE should be used instead */ ++ assert(wkspSize >= sizeof(HUF_buildCTable_wksp_tables)); ++ ++ if (!(flags & HUF_flags_optimalDepth)) { ++ /* cheap evaluation, based on FSE */ ++ return FSE_optimalTableLog_internal(maxTableLog, srcSize, maxSymbolValue, 1); ++ } ++ ++ { BYTE* dst = (BYTE*)workSpace + sizeof(HUF_WriteCTableWksp); ++ size_t dstSize = wkspSize - sizeof(HUF_WriteCTableWksp); ++ size_t maxBits, hSize, newSize; ++ const unsigned symbolCardinality = HUF_cardinality(count, maxSymbolValue); ++ const unsigned minTableLog = HUF_minTableLog(symbolCardinality); ++ size_t optSize = ((size_t) ~0) - 1; ++ unsigned optLog = maxTableLog, optLogGuess; ++ ++ DEBUGLOG(6, "HUF_optimalTableLog: probing huf depth (srcSize=%zu)", srcSize); ++ ++ /* Search until size increases */ ++ for (optLogGuess = minTableLog; optLogGuess <= maxTableLog; optLogGuess++) { ++ DEBUGLOG(7, "checking for huffLog=%u", optLogGuess); ++ maxBits = HUF_buildCTable_wksp(table, count, maxSymbolValue, optLogGuess, workSpace, wkspSize); ++ if (ERR_isError(maxBits)) continue; ++ ++ if (maxBits < optLogGuess && optLogGuess > minTableLog) break; ++ ++ hSize = HUF_writeCTable_wksp(dst, dstSize, table, maxSymbolValue, (U32)maxBits, workSpace, wkspSize); ++ ++ if (ERR_isError(hSize)) continue; ++ ++ newSize = HUF_estimateCompressedSize(table, count, maxSymbolValue) + hSize; ++ ++ if (newSize > optSize + 1) { ++ break; ++ } ++ ++ if (newSize < optSize) { ++ optSize = newSize; ++ optLog = optLogGuess; ++ } ++ } ++ assert(optLog <= HUF_TABLELOG_MAX); ++ return optLog; ++ } ++} ++ + /* HUF_compress_internal() : + * `workSpace_align4` must be aligned on 4-bytes boundaries, + * and occupies the same space as a table of HUF_WORKSPACE_SIZE_U64 unsigned */ +@@ -1177,14 +1298,14 @@ + unsigned maxSymbolValue, unsigned huffLog, + HUF_nbStreams_e nbStreams, + void* workSpace, size_t wkspSize, +- HUF_CElt* oldHufTable, HUF_repeat* repeat, int preferRepeat, +- const int bmi2, unsigned suspectUncompressible) ++ HUF_CElt* oldHufTable, HUF_repeat* repeat, int flags) + { + HUF_compress_tables_t* const table = (HUF_compress_tables_t*)HUF_alignUpWorkspace(workSpace, &wkspSize, ZSTD_ALIGNOF(size_t)); + BYTE* const ostart = (BYTE*)dst; + BYTE* const oend = ostart + dstSize; + BYTE* op = ostart; + ++ DEBUGLOG(5, "HUF_compress_internal (srcSize=%zu)", srcSize); + HUF_STATIC_ASSERT(sizeof(*table) + HUF_WORKSPACE_MAX_ALIGNMENT <= HUF_WORKSPACE_SIZE); + + /* checks & inits */ +@@ -1198,16 +1319,17 @@ + if (!huffLog) huffLog = HUF_TABLELOG_DEFAULT; + + /* Heuristic : If old table is valid, use it for small inputs */ +- if (preferRepeat && repeat && *repeat == HUF_repeat_valid) { ++ if ((flags & HUF_flags_preferRepeat) && repeat && *repeat == HUF_repeat_valid) { + return HUF_compressCTable_internal(ostart, op, oend, + src, srcSize, +- nbStreams, oldHufTable, bmi2); ++ nbStreams, oldHufTable, flags); + } + + /* If uncompressible data is suspected, do a smaller sampling first */ + DEBUG_STATIC_ASSERT(SUSPECT_INCOMPRESSIBLE_SAMPLE_RATIO >= 2); +- if (suspectUncompressible && srcSize >= (SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE * SUSPECT_INCOMPRESSIBLE_SAMPLE_RATIO)) { ++ if ((flags & HUF_flags_suspectUncompressible) && srcSize >= (SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE * SUSPECT_INCOMPRESSIBLE_SAMPLE_RATIO)) { + size_t largestTotal = 0; ++ DEBUGLOG(5, "input suspected incompressible : sampling to check"); + { unsigned maxSymbolValueBegin = maxSymbolValue; + CHECK_V_F(largestBegin, HIST_count_simple (table->count, &maxSymbolValueBegin, (const BYTE*)src, SUSPECT_INCOMPRESSIBLE_SAMPLE_SIZE) ); + largestTotal += largestBegin; +@@ -1224,6 +1346,7 @@ + if (largest == srcSize) { *ostart = ((const BYTE*)src)[0]; return 1; } /* single symbol, rle */ + if (largest <= (srcSize >> 7)+4) return 0; /* heuristic : probably not compressible enough */ + } ++ DEBUGLOG(6, "histogram detail completed (%zu symbols)", showU32(table->count, maxSymbolValue+1)); + + /* Check validity of previous table */ + if ( repeat +@@ -1232,19 +1355,20 @@ + *repeat = HUF_repeat_none; + } + /* Heuristic : use existing table for small inputs */ +- if (preferRepeat && repeat && *repeat != HUF_repeat_none) { ++ if ((flags & HUF_flags_preferRepeat) && repeat && *repeat != HUF_repeat_none) { + return HUF_compressCTable_internal(ostart, op, oend, + src, srcSize, +- nbStreams, oldHufTable, bmi2); ++ nbStreams, oldHufTable, flags); + } + + /* Build Huffman Tree */ +- huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue); ++ huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue, &table->wksps, sizeof(table->wksps), table->CTable, table->count, flags); + { size_t const maxBits = HUF_buildCTable_wksp(table->CTable, table->count, + maxSymbolValue, huffLog, + &table->wksps.buildCTable_wksp, sizeof(table->wksps.buildCTable_wksp)); + CHECK_F(maxBits); + huffLog = (U32)maxBits; ++ DEBUGLOG(6, "bit distribution completed (%zu symbols)", showCTableBits(table->CTable + 1, maxSymbolValue+1)); + } + /* Zero unused symbols in CTable, so we can check it for validity */ + { +@@ -1263,7 +1387,7 @@ + if (oldSize <= hSize + newSize || hSize + 12 >= srcSize) { + return HUF_compressCTable_internal(ostart, op, oend, + src, srcSize, +- nbStreams, oldHufTable, bmi2); ++ nbStreams, oldHufTable, flags); + } } + + /* Use the new huffman table */ +@@ -1275,46 +1399,20 @@ + } + return HUF_compressCTable_internal(ostart, op, oend, + src, srcSize, +- nbStreams, table->CTable, bmi2); +-} +- +- +-size_t HUF_compress1X_wksp (void* dst, size_t dstSize, +- const void* src, size_t srcSize, +- unsigned maxSymbolValue, unsigned huffLog, +- void* workSpace, size_t wkspSize) +-{ +- return HUF_compress_internal(dst, dstSize, src, srcSize, +- maxSymbolValue, huffLog, HUF_singleStream, +- workSpace, wkspSize, +- NULL, NULL, 0, 0 /*bmi2*/, 0); ++ nbStreams, table->CTable, flags); + } + + size_t HUF_compress1X_repeat (void* dst, size_t dstSize, + const void* src, size_t srcSize, + unsigned maxSymbolValue, unsigned huffLog, + void* workSpace, size_t wkspSize, +- HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, +- int bmi2, unsigned suspectUncompressible) ++ HUF_CElt* hufTable, HUF_repeat* repeat, int flags) + { ++ DEBUGLOG(5, "HUF_compress1X_repeat (srcSize = %zu)", srcSize); + return HUF_compress_internal(dst, dstSize, src, srcSize, + maxSymbolValue, huffLog, HUF_singleStream, + workSpace, wkspSize, hufTable, +- repeat, preferRepeat, bmi2, suspectUncompressible); +-} +- +-/* HUF_compress4X_repeat(): +- * compress input using 4 streams. +- * provide workspace to generate compression tables */ +-size_t HUF_compress4X_wksp (void* dst, size_t dstSize, +- const void* src, size_t srcSize, +- unsigned maxSymbolValue, unsigned huffLog, +- void* workSpace, size_t wkspSize) +-{ +- return HUF_compress_internal(dst, dstSize, src, srcSize, +- maxSymbolValue, huffLog, HUF_fourStreams, +- workSpace, wkspSize, +- NULL, NULL, 0, 0 /*bmi2*/, 0); ++ repeat, flags); + } + + /* HUF_compress4X_repeat(): +@@ -1325,11 +1423,11 @@ + const void* src, size_t srcSize, + unsigned maxSymbolValue, unsigned huffLog, + void* workSpace, size_t wkspSize, +- HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2, unsigned suspectUncompressible) ++ HUF_CElt* hufTable, HUF_repeat* repeat, int flags) + { ++ DEBUGLOG(5, "HUF_compress4X_repeat (srcSize = %zu)", srcSize); + return HUF_compress_internal(dst, dstSize, src, srcSize, + maxSymbolValue, huffLog, HUF_fourStreams, + workSpace, wkspSize, +- hufTable, repeat, preferRepeat, bmi2, suspectUncompressible); ++ hufTable, repeat, flags); + } +- +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress.c b/lib/zstd/compress/zstd_compress.c +--- a/lib/zstd/compress/zstd_compress.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -11,12 +12,12 @@ + /*-************************************* + * Dependencies + ***************************************/ ++#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ + #include "../common/zstd_deps.h" /* INT_MAX, ZSTD_memset, ZSTD_memcpy */ + #include "../common/mem.h" + #include "hist.h" /* HIST_countFast_wksp */ + #define FSE_STATIC_LINKING_ONLY /* FSE_encodeSymbol */ + #include "../common/fse.h" +-#define HUF_STATIC_LINKING_ONLY + #include "../common/huf.h" + #include "zstd_compress_internal.h" + #include "zstd_compress_sequences.h" +@@ -27,6 +28,7 @@ + #include "zstd_opt.h" + #include "zstd_ldm.h" + #include "zstd_compress_superblock.h" ++#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_rotateRight_U64 */ + + /* *************************************************************** + * Tuning parameters +@@ -55,14 +57,17 @@ + * Helper functions + ***************************************/ + /* ZSTD_compressBound() +- * Note that the result from this function is only compatible with the "normal" +- * full-block strategy. +- * When there are a lot of small blocks due to frequent flush in streaming mode +- * the overhead of headers can make the compressed data to be larger than the +- * return value of ZSTD_compressBound(). ++ * Note that the result from this function is only valid for ++ * the one-pass compression functions. ++ * When employing the streaming mode, ++ * if flushes are frequently altering the size of blocks, ++ * the overhead from block headers can make the compressed data larger ++ * than the return value of ZSTD_compressBound(). + */ + size_t ZSTD_compressBound(size_t srcSize) { +- return ZSTD_COMPRESSBOUND(srcSize); ++ size_t const r = ZSTD_COMPRESSBOUND(srcSize); ++ if (r==0) return ERROR(srcSize_wrong); ++ return r; + } + + +@@ -171,12 +176,9 @@ + if (cctx==NULL) return 0; /* support free on NULL */ + RETURN_ERROR_IF(cctx->staticSize, memory_allocation, + "not compatible with static CCtx"); +- { +- int cctxInWorkspace = ZSTD_cwksp_owns_buffer(&cctx->workspace, cctx); ++ { int cctxInWorkspace = ZSTD_cwksp_owns_buffer(&cctx->workspace, cctx); + ZSTD_freeCCtxContent(cctx); +- if (!cctxInWorkspace) { +- ZSTD_customFree(cctx, cctx->customMem); +- } ++ if (!cctxInWorkspace) ZSTD_customFree(cctx, cctx->customMem); + } + return 0; + } +@@ -257,9 +259,9 @@ + return forDDSDict || ((strategy != ZSTD_fast) && !ZSTD_rowMatchFinderUsed(strategy, useRowMatchFinder)); + } + +-/* Returns 1 if compression parameters are such that we should ++/* Returns ZSTD_ps_enable if compression parameters are such that we should + * enable long distance matching (wlog >= 27, strategy >= btopt). +- * Returns 0 otherwise. ++ * Returns ZSTD_ps_disable otherwise. + */ + static ZSTD_paramSwitch_e ZSTD_resolveEnableLdm(ZSTD_paramSwitch_e mode, + const ZSTD_compressionParameters* const cParams) { +@@ -267,6 +269,34 @@ + return (cParams->strategy >= ZSTD_btopt && cParams->windowLog >= 27) ? ZSTD_ps_enable : ZSTD_ps_disable; + } + ++static int ZSTD_resolveExternalSequenceValidation(int mode) { ++ return mode; ++} ++ ++/* Resolves maxBlockSize to the default if no value is present. */ ++static size_t ZSTD_resolveMaxBlockSize(size_t maxBlockSize) { ++ if (maxBlockSize == 0) { ++ return ZSTD_BLOCKSIZE_MAX; ++ } else { ++ return maxBlockSize; ++ } ++} ++ ++static ZSTD_paramSwitch_e ZSTD_resolveExternalRepcodeSearch(ZSTD_paramSwitch_e value, int cLevel) { ++ if (value != ZSTD_ps_auto) return value; ++ if (cLevel < 10) { ++ return ZSTD_ps_disable; ++ } else { ++ return ZSTD_ps_enable; ++ } ++} ++ ++/* Returns 1 if compression parameters are such that CDict hashtable and chaintable indices are tagged. ++ * If so, the tags need to be removed in ZSTD_resetCCtx_byCopyingCDict. */ ++static int ZSTD_CDictIndicesAreTagged(const ZSTD_compressionParameters* const cParams) { ++ return cParams->strategy == ZSTD_fast || cParams->strategy == ZSTD_dfast; ++} ++ + static ZSTD_CCtx_params ZSTD_makeCCtxParamsFromCParams( + ZSTD_compressionParameters cParams) + { +@@ -284,6 +314,10 @@ + } + cctxParams.useBlockSplitter = ZSTD_resolveBlockSplitterMode(cctxParams.useBlockSplitter, &cParams); + cctxParams.useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(cctxParams.useRowMatchFinder, &cParams); ++ cctxParams.validateSequences = ZSTD_resolveExternalSequenceValidation(cctxParams.validateSequences); ++ cctxParams.maxBlockSize = ZSTD_resolveMaxBlockSize(cctxParams.maxBlockSize); ++ cctxParams.searchForExternalRepcodes = ZSTD_resolveExternalRepcodeSearch(cctxParams.searchForExternalRepcodes, ++ cctxParams.compressionLevel); + assert(!ZSTD_checkCParams(cParams)); + return cctxParams; + } +@@ -329,10 +363,13 @@ + #define ZSTD_NO_CLEVEL 0 + + /* +- * Initializes the cctxParams from params and compressionLevel. ++ * Initializes `cctxParams` from `params` and `compressionLevel`. + * @param compressionLevel If params are derived from a compression level then that compression level, otherwise ZSTD_NO_CLEVEL. + */ +-static void ZSTD_CCtxParams_init_internal(ZSTD_CCtx_params* cctxParams, ZSTD_parameters const* params, int compressionLevel) ++static void ++ZSTD_CCtxParams_init_internal(ZSTD_CCtx_params* cctxParams, ++ const ZSTD_parameters* params, ++ int compressionLevel) + { + assert(!ZSTD_checkCParams(params->cParams)); + ZSTD_memset(cctxParams, 0, sizeof(*cctxParams)); +@@ -345,6 +382,9 @@ + cctxParams->useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(cctxParams->useRowMatchFinder, ¶ms->cParams); + cctxParams->useBlockSplitter = ZSTD_resolveBlockSplitterMode(cctxParams->useBlockSplitter, ¶ms->cParams); + cctxParams->ldmParams.enableLdm = ZSTD_resolveEnableLdm(cctxParams->ldmParams.enableLdm, ¶ms->cParams); ++ cctxParams->validateSequences = ZSTD_resolveExternalSequenceValidation(cctxParams->validateSequences); ++ cctxParams->maxBlockSize = ZSTD_resolveMaxBlockSize(cctxParams->maxBlockSize); ++ cctxParams->searchForExternalRepcodes = ZSTD_resolveExternalRepcodeSearch(cctxParams->searchForExternalRepcodes, compressionLevel); + DEBUGLOG(4, "ZSTD_CCtxParams_init_internal: useRowMatchFinder=%d, useBlockSplitter=%d ldm=%d", + cctxParams->useRowMatchFinder, cctxParams->useBlockSplitter, cctxParams->ldmParams.enableLdm); + } +@@ -359,7 +399,7 @@ + + /* + * Sets cctxParams' cParams and fParams from params, but otherwise leaves them alone. +- * @param param Validated zstd parameters. ++ * @param params Validated zstd parameters. + */ + static void ZSTD_CCtxParams_setZstdParams( + ZSTD_CCtx_params* cctxParams, const ZSTD_parameters* params) +@@ -455,8 +495,8 @@ + return bounds; + + case ZSTD_c_enableLongDistanceMatching: +- bounds.lowerBound = 0; +- bounds.upperBound = 1; ++ bounds.lowerBound = (int)ZSTD_ps_auto; ++ bounds.upperBound = (int)ZSTD_ps_disable; + return bounds; + + case ZSTD_c_ldmHashLog: +@@ -549,6 +589,26 @@ + bounds.upperBound = 1; + return bounds; + ++ case ZSTD_c_prefetchCDictTables: ++ bounds.lowerBound = (int)ZSTD_ps_auto; ++ bounds.upperBound = (int)ZSTD_ps_disable; ++ return bounds; ++ ++ case ZSTD_c_enableSeqProducerFallback: ++ bounds.lowerBound = 0; ++ bounds.upperBound = 1; ++ return bounds; ++ ++ case ZSTD_c_maxBlockSize: ++ bounds.lowerBound = ZSTD_BLOCKSIZE_MAX_MIN; ++ bounds.upperBound = ZSTD_BLOCKSIZE_MAX; ++ return bounds; ++ ++ case ZSTD_c_searchForExternalRepcodes: ++ bounds.lowerBound = (int)ZSTD_ps_auto; ++ bounds.upperBound = (int)ZSTD_ps_disable; ++ return bounds; ++ + default: + bounds.error = ERROR(parameter_unsupported); + return bounds; +@@ -613,6 +673,10 @@ + case ZSTD_c_useBlockSplitter: + case ZSTD_c_useRowMatchFinder: + case ZSTD_c_deterministicRefPrefix: ++ case ZSTD_c_prefetchCDictTables: ++ case ZSTD_c_enableSeqProducerFallback: ++ case ZSTD_c_maxBlockSize: ++ case ZSTD_c_searchForExternalRepcodes: + default: + return 0; + } +@@ -625,7 +689,7 @@ + if (ZSTD_isUpdateAuthorized(param)) { + cctx->cParamsChanged = 1; + } else { +- RETURN_ERROR(stage_wrong, "can only set params in ctx init stage"); ++ RETURN_ERROR(stage_wrong, "can only set params in cctx init stage"); + } } + + switch(param) +@@ -668,6 +732,10 @@ + case ZSTD_c_useBlockSplitter: + case ZSTD_c_useRowMatchFinder: + case ZSTD_c_deterministicRefPrefix: ++ case ZSTD_c_prefetchCDictTables: ++ case ZSTD_c_enableSeqProducerFallback: ++ case ZSTD_c_maxBlockSize: ++ case ZSTD_c_searchForExternalRepcodes: + break; + + default: RETURN_ERROR(parameter_unsupported, "unknown parameter"); +@@ -723,12 +791,12 @@ + case ZSTD_c_minMatch : + if (value!=0) /* 0 => use default */ + BOUNDCHECK(ZSTD_c_minMatch, value); +- CCtxParams->cParams.minMatch = value; ++ CCtxParams->cParams.minMatch = (U32)value; + return CCtxParams->cParams.minMatch; + + case ZSTD_c_targetLength : + BOUNDCHECK(ZSTD_c_targetLength, value); +- CCtxParams->cParams.targetLength = value; ++ CCtxParams->cParams.targetLength = (U32)value; + return CCtxParams->cParams.targetLength; + + case ZSTD_c_strategy : +@@ -741,12 +809,12 @@ + /* Content size written in frame header _when known_ (default:1) */ + DEBUGLOG(4, "set content size flag = %u", (value!=0)); + CCtxParams->fParams.contentSizeFlag = value != 0; +- return CCtxParams->fParams.contentSizeFlag; ++ return (size_t)CCtxParams->fParams.contentSizeFlag; + + case ZSTD_c_checksumFlag : + /* A 32-bits content checksum will be calculated and written at end of frame (default:0) */ + CCtxParams->fParams.checksumFlag = value != 0; +- return CCtxParams->fParams.checksumFlag; ++ return (size_t)CCtxParams->fParams.checksumFlag; + + case ZSTD_c_dictIDFlag : /* When applicable, dictionary's dictID is provided in frame header (default:1) */ + DEBUGLOG(4, "set dictIDFlag = %u", (value!=0)); +@@ -755,18 +823,18 @@ + + case ZSTD_c_forceMaxWindow : + CCtxParams->forceWindow = (value != 0); +- return CCtxParams->forceWindow; ++ return (size_t)CCtxParams->forceWindow; + + case ZSTD_c_forceAttachDict : { + const ZSTD_dictAttachPref_e pref = (ZSTD_dictAttachPref_e)value; +- BOUNDCHECK(ZSTD_c_forceAttachDict, pref); ++ BOUNDCHECK(ZSTD_c_forceAttachDict, (int)pref); + CCtxParams->attachDictPref = pref; + return CCtxParams->attachDictPref; + } + + case ZSTD_c_literalCompressionMode : { + const ZSTD_paramSwitch_e lcm = (ZSTD_paramSwitch_e)value; +- BOUNDCHECK(ZSTD_c_literalCompressionMode, lcm); ++ BOUNDCHECK(ZSTD_c_literalCompressionMode, (int)lcm); + CCtxParams->literalCompressionMode = lcm; + return CCtxParams->literalCompressionMode; + } +@@ -789,47 +857,48 @@ + + case ZSTD_c_enableDedicatedDictSearch : + CCtxParams->enableDedicatedDictSearch = (value!=0); +- return CCtxParams->enableDedicatedDictSearch; ++ return (size_t)CCtxParams->enableDedicatedDictSearch; + + case ZSTD_c_enableLongDistanceMatching : ++ BOUNDCHECK(ZSTD_c_enableLongDistanceMatching, value); + CCtxParams->ldmParams.enableLdm = (ZSTD_paramSwitch_e)value; + return CCtxParams->ldmParams.enableLdm; + + case ZSTD_c_ldmHashLog : + if (value!=0) /* 0 ==> auto */ + BOUNDCHECK(ZSTD_c_ldmHashLog, value); +- CCtxParams->ldmParams.hashLog = value; ++ CCtxParams->ldmParams.hashLog = (U32)value; + return CCtxParams->ldmParams.hashLog; + + case ZSTD_c_ldmMinMatch : + if (value!=0) /* 0 ==> default */ + BOUNDCHECK(ZSTD_c_ldmMinMatch, value); +- CCtxParams->ldmParams.minMatchLength = value; ++ CCtxParams->ldmParams.minMatchLength = (U32)value; + return CCtxParams->ldmParams.minMatchLength; + + case ZSTD_c_ldmBucketSizeLog : + if (value!=0) /* 0 ==> default */ + BOUNDCHECK(ZSTD_c_ldmBucketSizeLog, value); +- CCtxParams->ldmParams.bucketSizeLog = value; ++ CCtxParams->ldmParams.bucketSizeLog = (U32)value; + return CCtxParams->ldmParams.bucketSizeLog; + + case ZSTD_c_ldmHashRateLog : + if (value!=0) /* 0 ==> default */ + BOUNDCHECK(ZSTD_c_ldmHashRateLog, value); +- CCtxParams->ldmParams.hashRateLog = value; ++ CCtxParams->ldmParams.hashRateLog = (U32)value; + return CCtxParams->ldmParams.hashRateLog; + + case ZSTD_c_targetCBlockSize : + if (value!=0) /* 0 ==> default */ + BOUNDCHECK(ZSTD_c_targetCBlockSize, value); +- CCtxParams->targetCBlockSize = value; ++ CCtxParams->targetCBlockSize = (U32)value; + return CCtxParams->targetCBlockSize; + + case ZSTD_c_srcSizeHint : + if (value!=0) /* 0 ==> default */ + BOUNDCHECK(ZSTD_c_srcSizeHint, value); + CCtxParams->srcSizeHint = value; +- return CCtxParams->srcSizeHint; ++ return (size_t)CCtxParams->srcSizeHint; + + case ZSTD_c_stableInBuffer: + BOUNDCHECK(ZSTD_c_stableInBuffer, value); +@@ -866,6 +935,27 @@ + CCtxParams->deterministicRefPrefix = !!value; + return CCtxParams->deterministicRefPrefix; + ++ case ZSTD_c_prefetchCDictTables: ++ BOUNDCHECK(ZSTD_c_prefetchCDictTables, value); ++ CCtxParams->prefetchCDictTables = (ZSTD_paramSwitch_e)value; ++ return CCtxParams->prefetchCDictTables; ++ ++ case ZSTD_c_enableSeqProducerFallback: ++ BOUNDCHECK(ZSTD_c_enableSeqProducerFallback, value); ++ CCtxParams->enableMatchFinderFallback = value; ++ return CCtxParams->enableMatchFinderFallback; ++ ++ case ZSTD_c_maxBlockSize: ++ if (value!=0) /* 0 ==> default */ ++ BOUNDCHECK(ZSTD_c_maxBlockSize, value); ++ CCtxParams->maxBlockSize = value; ++ return CCtxParams->maxBlockSize; ++ ++ case ZSTD_c_searchForExternalRepcodes: ++ BOUNDCHECK(ZSTD_c_searchForExternalRepcodes, value); ++ CCtxParams->searchForExternalRepcodes = (ZSTD_paramSwitch_e)value; ++ return CCtxParams->searchForExternalRepcodes; ++ + default: RETURN_ERROR(parameter_unsupported, "unknown parameter"); + } + } +@@ -980,6 +1070,18 @@ + case ZSTD_c_deterministicRefPrefix: + *value = (int)CCtxParams->deterministicRefPrefix; + break; ++ case ZSTD_c_prefetchCDictTables: ++ *value = (int)CCtxParams->prefetchCDictTables; ++ break; ++ case ZSTD_c_enableSeqProducerFallback: ++ *value = CCtxParams->enableMatchFinderFallback; ++ break; ++ case ZSTD_c_maxBlockSize: ++ *value = (int)CCtxParams->maxBlockSize; ++ break; ++ case ZSTD_c_searchForExternalRepcodes: ++ *value = (int)CCtxParams->searchForExternalRepcodes; ++ break; + default: RETURN_ERROR(parameter_unsupported, "unknown parameter"); + } + return 0; +@@ -1006,9 +1108,47 @@ + return 0; + } + ++size_t ZSTD_CCtx_setCParams(ZSTD_CCtx* cctx, ZSTD_compressionParameters cparams) ++{ ++ ZSTD_STATIC_ASSERT(sizeof(cparams) == 7 * 4 /* all params are listed below */); ++ DEBUGLOG(4, "ZSTD_CCtx_setCParams"); ++ /* only update if all parameters are valid */ ++ FORWARD_IF_ERROR(ZSTD_checkCParams(cparams), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, cparams.windowLog), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_chainLog, cparams.chainLog), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_hashLog, cparams.hashLog), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_searchLog, cparams.searchLog), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_minMatch, cparams.minMatch), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_targetLength, cparams.targetLength), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_strategy, cparams.strategy), ""); ++ return 0; ++} ++ ++size_t ZSTD_CCtx_setFParams(ZSTD_CCtx* cctx, ZSTD_frameParameters fparams) ++{ ++ ZSTD_STATIC_ASSERT(sizeof(fparams) == 3 * 4 /* all params are listed below */); ++ DEBUGLOG(4, "ZSTD_CCtx_setFParams"); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_contentSizeFlag, fparams.contentSizeFlag != 0), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, fparams.checksumFlag != 0), ""); ++ FORWARD_IF_ERROR(ZSTD_CCtx_setParameter(cctx, ZSTD_c_dictIDFlag, fparams.noDictIDFlag == 0), ""); ++ return 0; ++} ++ ++size_t ZSTD_CCtx_setParams(ZSTD_CCtx* cctx, ZSTD_parameters params) ++{ ++ DEBUGLOG(4, "ZSTD_CCtx_setParams"); ++ /* First check cParams, because we want to update all or none. */ ++ FORWARD_IF_ERROR(ZSTD_checkCParams(params.cParams), ""); ++ /* Next set fParams, because this could fail if the cctx isn't in init stage. */ ++ FORWARD_IF_ERROR(ZSTD_CCtx_setFParams(cctx, params.fParams), ""); ++ /* Finally set cParams, which should succeed. */ ++ FORWARD_IF_ERROR(ZSTD_CCtx_setCParams(cctx, params.cParams), ""); ++ return 0; ++} ++ + size_t ZSTD_CCtx_setPledgedSrcSize(ZSTD_CCtx* cctx, unsigned long long pledgedSrcSize) + { +- DEBUGLOG(4, "ZSTD_CCtx_setPledgedSrcSize to %u bytes", (U32)pledgedSrcSize); ++ DEBUGLOG(4, "ZSTD_CCtx_setPledgedSrcSize to %llu bytes", pledgedSrcSize); + RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, + "Can't set pledgedSrcSize when not in init stage."); + cctx->pledgedSrcSizePlusOne = pledgedSrcSize+1; +@@ -1024,9 +1164,9 @@ + ZSTD_compressionParameters* cParams); + + /* +- * Initializes the local dict using the requested parameters. +- * NOTE: This does not use the pledged src size, because it may be used for more +- * than one compression. ++ * Initializes the local dictionary using requested parameters. ++ * NOTE: Initialization does not employ the pledged src size, ++ * because the dictionary may be used for multiple compressions. + */ + static size_t ZSTD_initLocalDict(ZSTD_CCtx* cctx) + { +@@ -1039,8 +1179,8 @@ + return 0; + } + if (dl->cdict != NULL) { +- assert(cctx->cdict == dl->cdict); + /* Local dictionary already initialized. */ ++ assert(cctx->cdict == dl->cdict); + return 0; + } + assert(dl->dictSize > 0); +@@ -1060,26 +1200,30 @@ + } + + size_t ZSTD_CCtx_loadDictionary_advanced( +- ZSTD_CCtx* cctx, const void* dict, size_t dictSize, +- ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType) ++ ZSTD_CCtx* cctx, ++ const void* dict, size_t dictSize, ++ ZSTD_dictLoadMethod_e dictLoadMethod, ++ ZSTD_dictContentType_e dictContentType) + { +- RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, +- "Can't load a dictionary when ctx is not in init stage."); + DEBUGLOG(4, "ZSTD_CCtx_loadDictionary_advanced (size: %u)", (U32)dictSize); +- ZSTD_clearAllDicts(cctx); /* in case one already exists */ +- if (dict == NULL || dictSize == 0) /* no dictionary mode */ ++ RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, ++ "Can't load a dictionary when cctx is not in init stage."); ++ ZSTD_clearAllDicts(cctx); /* erase any previously set dictionary */ ++ if (dict == NULL || dictSize == 0) /* no dictionary */ + return 0; + if (dictLoadMethod == ZSTD_dlm_byRef) { + cctx->localDict.dict = dict; + } else { ++ /* copy dictionary content inside CCtx to own its lifetime */ + void* dictBuffer; + RETURN_ERROR_IF(cctx->staticSize, memory_allocation, +- "no malloc for static CCtx"); ++ "static CCtx can't allocate for an internal copy of dictionary"); + dictBuffer = ZSTD_customMalloc(dictSize, cctx->customMem); +- RETURN_ERROR_IF(!dictBuffer, memory_allocation, "NULL pointer!"); ++ RETURN_ERROR_IF(dictBuffer==NULL, memory_allocation, ++ "allocation failed for dictionary content"); + ZSTD_memcpy(dictBuffer, dict, dictSize); +- cctx->localDict.dictBuffer = dictBuffer; +- cctx->localDict.dict = dictBuffer; ++ cctx->localDict.dictBuffer = dictBuffer; /* owned ptr to free */ ++ cctx->localDict.dict = dictBuffer; /* read-only reference */ + } + cctx->localDict.dictSize = dictSize; + cctx->localDict.dictContentType = dictContentType; +@@ -1149,8 +1293,9 @@ + if ( (reset == ZSTD_reset_parameters) + || (reset == ZSTD_reset_session_and_parameters) ) { + RETURN_ERROR_IF(cctx->streamStage != zcss_init, stage_wrong, +- "Can't reset parameters only when not in init stage."); ++ "Reset parameters is only possible during init stage."); + ZSTD_clearAllDicts(cctx); ++ ZSTD_memset(&cctx->externalMatchCtx, 0, sizeof(cctx->externalMatchCtx)); + return ZSTD_CCtxParams_reset(&cctx->requestedParams); + } + return 0; +@@ -1247,7 +1392,8 @@ + ZSTD_adjustCParams_internal(ZSTD_compressionParameters cPar, + unsigned long long srcSize, + size_t dictSize, +- ZSTD_cParamMode_e mode) ++ ZSTD_cParamMode_e mode, ++ ZSTD_paramSwitch_e useRowMatchFinder) + { + const U64 minSrcSize = 513; /* (1<<9) + 1 */ + const U64 maxWindowResize = 1ULL << (ZSTD_WINDOWLOG_MAX-1); +@@ -1281,8 +1427,8 @@ + } + + /* resize windowLog if input is small enough, to use less memory */ +- if ( (srcSize < maxWindowResize) +- && (dictSize < maxWindowResize) ) { ++ if ( (srcSize <= maxWindowResize) ++ && (dictSize <= maxWindowResize) ) { + U32 const tSize = (U32)(srcSize + dictSize); + static U32 const hashSizeMin = 1 << ZSTD_HASHLOG_MIN; + U32 const srcLog = (tSize < hashSizeMin) ? ZSTD_HASHLOG_MIN : +@@ -1300,6 +1446,42 @@ + if (cPar.windowLog < ZSTD_WINDOWLOG_ABSOLUTEMIN) + cPar.windowLog = ZSTD_WINDOWLOG_ABSOLUTEMIN; /* minimum wlog required for valid frame header */ + ++ /* We can't use more than 32 bits of hash in total, so that means that we require: ++ * (hashLog + 8) <= 32 && (chainLog + 8) <= 32 ++ */ ++ if (mode == ZSTD_cpm_createCDict && ZSTD_CDictIndicesAreTagged(&cPar)) { ++ U32 const maxShortCacheHashLog = 32 - ZSTD_SHORT_CACHE_TAG_BITS; ++ if (cPar.hashLog > maxShortCacheHashLog) { ++ cPar.hashLog = maxShortCacheHashLog; ++ } ++ if (cPar.chainLog > maxShortCacheHashLog) { ++ cPar.chainLog = maxShortCacheHashLog; ++ } ++ } ++ ++ ++ /* At this point, we aren't 100% sure if we are using the row match finder. ++ * Unless it is explicitly disabled, conservatively assume that it is enabled. ++ * In this case it will only be disabled for small sources, so shrinking the ++ * hash log a little bit shouldn't result in any ratio loss. ++ */ ++ if (useRowMatchFinder == ZSTD_ps_auto) ++ useRowMatchFinder = ZSTD_ps_enable; ++ ++ /* We can't hash more than 32-bits in total. So that means that we require: ++ * (hashLog - rowLog + 8) <= 32 ++ */ ++ if (ZSTD_rowMatchFinderUsed(cPar.strategy, useRowMatchFinder)) { ++ /* Switch to 32-entry rows if searchLog is 5 (or more) */ ++ U32 const rowLog = BOUNDED(4, cPar.searchLog, 6); ++ U32 const maxRowHashLog = 32 - ZSTD_ROW_HASH_TAG_BITS; ++ U32 const maxHashLog = maxRowHashLog + rowLog; ++ assert(cPar.hashLog >= rowLog); ++ if (cPar.hashLog > maxHashLog) { ++ cPar.hashLog = maxHashLog; ++ } ++ } ++ + return cPar; + } + +@@ -1310,7 +1492,7 @@ + { + cPar = ZSTD_clampCParams(cPar); /* resulting cPar is necessarily valid (all parameters within range) */ + if (srcSize == 0) srcSize = ZSTD_CONTENTSIZE_UNKNOWN; +- return ZSTD_adjustCParams_internal(cPar, srcSize, dictSize, ZSTD_cpm_unknown); ++ return ZSTD_adjustCParams_internal(cPar, srcSize, dictSize, ZSTD_cpm_unknown, ZSTD_ps_auto); + } + + static ZSTD_compressionParameters ZSTD_getCParams_internal(int compressionLevel, unsigned long long srcSizeHint, size_t dictSize, ZSTD_cParamMode_e mode); +@@ -1341,7 +1523,7 @@ + ZSTD_overrideCParams(&cParams, &CCtxParams->cParams); + assert(!ZSTD_checkCParams(cParams)); + /* srcSizeHint == 0 means 0 */ +- return ZSTD_adjustCParams_internal(cParams, srcSizeHint, dictSize, mode); ++ return ZSTD_adjustCParams_internal(cParams, srcSizeHint, dictSize, mode, CCtxParams->useRowMatchFinder); + } + + static size_t +@@ -1370,7 +1552,7 @@ + + ZSTD_cwksp_aligned_alloc_size((ZSTD_OPT_NUM+1) * sizeof(ZSTD_match_t)) + + ZSTD_cwksp_aligned_alloc_size((ZSTD_OPT_NUM+1) * sizeof(ZSTD_optimal_t)); + size_t const lazyAdditionalSpace = ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder) +- ? ZSTD_cwksp_aligned_alloc_size(hSize*sizeof(U16)) ++ ? ZSTD_cwksp_aligned_alloc_size(hSize) + : 0; + size_t const optSpace = (forCCtx && (cParams->strategy >= ZSTD_btopt)) + ? optPotentialSpace +@@ -1386,6 +1568,13 @@ + return tableSpace + optSpace + slackSpace + lazyAdditionalSpace; + } + ++/* Helper function for calculating memory requirements. ++ * Gives a tighter bound than ZSTD_sequenceBound() by taking minMatch into account. */ ++static size_t ZSTD_maxNbSeq(size_t blockSize, unsigned minMatch, int useSequenceProducer) { ++ U32 const divider = (minMatch==3 || useSequenceProducer) ? 3 : 4; ++ return blockSize / divider; ++} ++ + static size_t ZSTD_estimateCCtxSize_usingCCtxParams_internal( + const ZSTD_compressionParameters* cParams, + const ldmParams_t* ldmParams, +@@ -1393,12 +1582,13 @@ + const ZSTD_paramSwitch_e useRowMatchFinder, + const size_t buffInSize, + const size_t buffOutSize, +- const U64 pledgedSrcSize) ++ const U64 pledgedSrcSize, ++ int useSequenceProducer, ++ size_t maxBlockSize) + { + size_t const windowSize = (size_t) BOUNDED(1ULL, 1ULL << cParams->windowLog, pledgedSrcSize); +- size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, windowSize); +- U32 const divider = (cParams->minMatch==3) ? 3 : 4; +- size_t const maxNbSeq = blockSize / divider; ++ size_t const blockSize = MIN(ZSTD_resolveMaxBlockSize(maxBlockSize), windowSize); ++ size_t const maxNbSeq = ZSTD_maxNbSeq(blockSize, cParams->minMatch, useSequenceProducer); + size_t const tokenSpace = ZSTD_cwksp_alloc_size(WILDCOPY_OVERLENGTH + blockSize) + + ZSTD_cwksp_aligned_alloc_size(maxNbSeq * sizeof(seqDef)) + + 3 * ZSTD_cwksp_alloc_size(maxNbSeq * sizeof(BYTE)); +@@ -1417,6 +1607,11 @@ + + size_t const cctxSpace = isStatic ? ZSTD_cwksp_alloc_size(sizeof(ZSTD_CCtx)) : 0; + ++ size_t const maxNbExternalSeq = ZSTD_sequenceBound(blockSize); ++ size_t const externalSeqSpace = useSequenceProducer ++ ? ZSTD_cwksp_aligned_alloc_size(maxNbExternalSeq * sizeof(ZSTD_Sequence)) ++ : 0; ++ + size_t const neededSpace = + cctxSpace + + entropySpace + +@@ -1425,7 +1620,8 @@ + ldmSeqSpace + + matchStateSize + + tokenSpace + +- bufferSpace; ++ bufferSpace + ++ externalSeqSpace; + + DEBUGLOG(5, "estimate workspace : %u", (U32)neededSpace); + return neededSpace; +@@ -1443,7 +1639,7 @@ + * be needed. However, we still allocate two 0-sized buffers, which can + * take space under ASAN. */ + return ZSTD_estimateCCtxSize_usingCCtxParams_internal( +- &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, 0, 0, ZSTD_CONTENTSIZE_UNKNOWN); ++ &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, 0, 0, ZSTD_CONTENTSIZE_UNKNOWN, params->useSequenceProducer, params->maxBlockSize); + } + + size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams) +@@ -1493,7 +1689,7 @@ + RETURN_ERROR_IF(params->nbWorkers > 0, GENERIC, "Estimate CCtx size is supported for single-threaded compression only."); + { ZSTD_compressionParameters const cParams = + ZSTD_getCParamsFromCCtxParams(params, ZSTD_CONTENTSIZE_UNKNOWN, 0, ZSTD_cpm_noAttachDict); +- size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, (size_t)1 << cParams.windowLog); ++ size_t const blockSize = MIN(ZSTD_resolveMaxBlockSize(params->maxBlockSize), (size_t)1 << cParams.windowLog); + size_t const inBuffSize = (params->inBufferMode == ZSTD_bm_buffered) + ? ((size_t)1 << cParams.windowLog) + blockSize + : 0; +@@ -1504,7 +1700,7 @@ + + return ZSTD_estimateCCtxSize_usingCCtxParams_internal( + &cParams, ¶ms->ldmParams, 1, useRowMatchFinder, inBuffSize, outBuffSize, +- ZSTD_CONTENTSIZE_UNKNOWN); ++ ZSTD_CONTENTSIZE_UNKNOWN, params->useSequenceProducer, params->maxBlockSize); + } + } + +@@ -1637,6 +1833,19 @@ + ZSTD_resetTarget_CCtx + } ZSTD_resetTarget_e; + ++/* Mixes bits in a 64 bits in a value, based on XXH3_rrmxmx */ ++static U64 ZSTD_bitmix(U64 val, U64 len) { ++ val ^= ZSTD_rotateRight_U64(val, 49) ^ ZSTD_rotateRight_U64(val, 24); ++ val *= 0x9FB21C651E98DF25ULL; ++ val ^= (val >> 35) + len ; ++ val *= 0x9FB21C651E98DF25ULL; ++ return val ^ (val >> 28); ++} ++ ++/* Mixes in the hashSalt and hashSaltEntropy to create a new hashSalt */ ++static void ZSTD_advanceHashSalt(ZSTD_matchState_t* ms) { ++ ms->hashSalt = ZSTD_bitmix(ms->hashSalt, 8) ^ ZSTD_bitmix((U64) ms->hashSaltEntropy, 4); ++} + + static size_t + ZSTD_reset_matchState(ZSTD_matchState_t* ms, +@@ -1664,6 +1873,7 @@ + } + + ms->hashLog3 = hashLog3; ++ ms->lazySkipping = 0; + + ZSTD_invalidateMatchState(ms); + +@@ -1685,6 +1895,27 @@ + ZSTD_cwksp_clean_tables(ws); + } + ++ if (ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)) { ++ /* Row match finder needs an additional table of hashes ("tags") */ ++ size_t const tagTableSize = hSize; ++ /* We want to generate a new salt in case we reset a Cctx, but we always want to use ++ * 0 when we reset a Cdict */ ++ if(forWho == ZSTD_resetTarget_CCtx) { ++ ms->tagTable = (BYTE*) ZSTD_cwksp_reserve_aligned_init_once(ws, tagTableSize); ++ ZSTD_advanceHashSalt(ms); ++ } else { ++ /* When we are not salting we want to always memset the memory */ ++ ms->tagTable = (BYTE*) ZSTD_cwksp_reserve_aligned(ws, tagTableSize); ++ ZSTD_memset(ms->tagTable, 0, tagTableSize); ++ ms->hashSalt = 0; ++ } ++ { /* Switch to 32-entry rows if searchLog is 5 (or more) */ ++ U32 const rowLog = BOUNDED(4, cParams->searchLog, 6); ++ assert(cParams->hashLog >= rowLog); ++ ms->rowHashLog = cParams->hashLog - rowLog; ++ } ++ } ++ + /* opt parser space */ + if ((forWho == ZSTD_resetTarget_CCtx) && (cParams->strategy >= ZSTD_btopt)) { + DEBUGLOG(4, "reserving optimal parser space"); +@@ -1696,19 +1927,6 @@ + ms->opt.priceTable = (ZSTD_optimal_t*)ZSTD_cwksp_reserve_aligned(ws, (ZSTD_OPT_NUM+1) * sizeof(ZSTD_optimal_t)); + } + +- if (ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)) { +- { /* Row match finder needs an additional table of hashes ("tags") */ +- size_t const tagTableSize = hSize*sizeof(U16); +- ms->tagTable = (U16*)ZSTD_cwksp_reserve_aligned(ws, tagTableSize); +- if (ms->tagTable) ZSTD_memset(ms->tagTable, 0, tagTableSize); +- } +- { /* Switch to 32-entry rows if searchLog is 5 (or more) */ +- U32 const rowLog = BOUNDED(4, cParams->searchLog, 6); +- assert(cParams->hashLog >= rowLog); +- ms->rowHashLog = cParams->hashLog - rowLog; +- } +- } +- + ms->cParams = *cParams; + + RETURN_ERROR_IF(ZSTD_cwksp_reserve_failed(ws), memory_allocation, +@@ -1768,6 +1986,7 @@ + assert(params->useRowMatchFinder != ZSTD_ps_auto); + assert(params->useBlockSplitter != ZSTD_ps_auto); + assert(params->ldmParams.enableLdm != ZSTD_ps_auto); ++ assert(params->maxBlockSize != 0); + if (params->ldmParams.enableLdm == ZSTD_ps_enable) { + /* Adjust long distance matching parameters */ + ZSTD_ldm_adjustParameters(&zc->appliedParams.ldmParams, ¶ms->cParams); +@@ -1776,9 +1995,8 @@ + } + + { size_t const windowSize = MAX(1, (size_t)MIN(((U64)1 << params->cParams.windowLog), pledgedSrcSize)); +- size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, windowSize); +- U32 const divider = (params->cParams.minMatch==3) ? 3 : 4; +- size_t const maxNbSeq = blockSize / divider; ++ size_t const blockSize = MIN(params->maxBlockSize, windowSize); ++ size_t const maxNbSeq = ZSTD_maxNbSeq(blockSize, params->cParams.minMatch, params->useSequenceProducer); + size_t const buffOutSize = (zbuff == ZSTDb_buffered && params->outBufferMode == ZSTD_bm_buffered) + ? ZSTD_compressBound(blockSize) + 1 + : 0; +@@ -1795,7 +2013,7 @@ + size_t const neededSpace = + ZSTD_estimateCCtxSize_usingCCtxParams_internal( + ¶ms->cParams, ¶ms->ldmParams, zc->staticSize != 0, params->useRowMatchFinder, +- buffInSize, buffOutSize, pledgedSrcSize); ++ buffInSize, buffOutSize, pledgedSrcSize, params->useSequenceProducer, params->maxBlockSize); + int resizeWorkspace; + + FORWARD_IF_ERROR(neededSpace, "cctx size estimate failed!"); +@@ -1838,6 +2056,7 @@ + + /* init params */ + zc->blockState.matchState.cParams = params->cParams; ++ zc->blockState.matchState.prefetchCDictTables = params->prefetchCDictTables == ZSTD_ps_enable; + zc->pledgedSrcSizePlusOne = pledgedSrcSize+1; + zc->consumedSrcSize = 0; + zc->producedCSize = 0; +@@ -1854,13 +2073,46 @@ + + ZSTD_reset_compressedBlockState(zc->blockState.prevCBlock); + ++ FORWARD_IF_ERROR(ZSTD_reset_matchState( ++ &zc->blockState.matchState, ++ ws, ++ ¶ms->cParams, ++ params->useRowMatchFinder, ++ crp, ++ needsIndexReset, ++ ZSTD_resetTarget_CCtx), ""); ++ ++ zc->seqStore.sequencesStart = (seqDef*)ZSTD_cwksp_reserve_aligned(ws, maxNbSeq * sizeof(seqDef)); ++ ++ /* ldm hash table */ ++ if (params->ldmParams.enableLdm == ZSTD_ps_enable) { ++ /* TODO: avoid memset? */ ++ size_t const ldmHSize = ((size_t)1) << params->ldmParams.hashLog; ++ zc->ldmState.hashTable = (ldmEntry_t*)ZSTD_cwksp_reserve_aligned(ws, ldmHSize * sizeof(ldmEntry_t)); ++ ZSTD_memset(zc->ldmState.hashTable, 0, ldmHSize * sizeof(ldmEntry_t)); ++ zc->ldmSequences = (rawSeq*)ZSTD_cwksp_reserve_aligned(ws, maxNbLdmSeq * sizeof(rawSeq)); ++ zc->maxNbLdmSequences = maxNbLdmSeq; ++ ++ ZSTD_window_init(&zc->ldmState.window); ++ zc->ldmState.loadedDictEnd = 0; ++ } ++ ++ /* reserve space for block-level external sequences */ ++ if (params->useSequenceProducer) { ++ size_t const maxNbExternalSeq = ZSTD_sequenceBound(blockSize); ++ zc->externalMatchCtx.seqBufferCapacity = maxNbExternalSeq; ++ zc->externalMatchCtx.seqBuffer = ++ (ZSTD_Sequence*)ZSTD_cwksp_reserve_aligned(ws, maxNbExternalSeq * sizeof(ZSTD_Sequence)); ++ } ++ ++ /* buffers */ ++ + /* ZSTD_wildcopy() is used to copy into the literals buffer, + * so we have to oversize the buffer by WILDCOPY_OVERLENGTH bytes. + */ + zc->seqStore.litStart = ZSTD_cwksp_reserve_buffer(ws, blockSize + WILDCOPY_OVERLENGTH); + zc->seqStore.maxNbLit = blockSize; + +- /* buffers */ + zc->bufferedPolicy = zbuff; + zc->inBuffSize = buffInSize; + zc->inBuff = (char*)ZSTD_cwksp_reserve_buffer(ws, buffInSize); +@@ -1883,32 +2135,9 @@ + zc->seqStore.llCode = ZSTD_cwksp_reserve_buffer(ws, maxNbSeq * sizeof(BYTE)); + zc->seqStore.mlCode = ZSTD_cwksp_reserve_buffer(ws, maxNbSeq * sizeof(BYTE)); + zc->seqStore.ofCode = ZSTD_cwksp_reserve_buffer(ws, maxNbSeq * sizeof(BYTE)); +- zc->seqStore.sequencesStart = (seqDef*)ZSTD_cwksp_reserve_aligned(ws, maxNbSeq * sizeof(seqDef)); +- +- FORWARD_IF_ERROR(ZSTD_reset_matchState( +- &zc->blockState.matchState, +- ws, +- ¶ms->cParams, +- params->useRowMatchFinder, +- crp, +- needsIndexReset, +- ZSTD_resetTarget_CCtx), ""); +- +- /* ldm hash table */ +- if (params->ldmParams.enableLdm == ZSTD_ps_enable) { +- /* TODO: avoid memset? */ +- size_t const ldmHSize = ((size_t)1) << params->ldmParams.hashLog; +- zc->ldmState.hashTable = (ldmEntry_t*)ZSTD_cwksp_reserve_aligned(ws, ldmHSize * sizeof(ldmEntry_t)); +- ZSTD_memset(zc->ldmState.hashTable, 0, ldmHSize * sizeof(ldmEntry_t)); +- zc->ldmSequences = (rawSeq*)ZSTD_cwksp_reserve_aligned(ws, maxNbLdmSeq * sizeof(rawSeq)); +- zc->maxNbLdmSequences = maxNbLdmSeq; +- +- ZSTD_window_init(&zc->ldmState.window); +- zc->ldmState.loadedDictEnd = 0; +- } + + DEBUGLOG(3, "wksp: finished allocating, %zd bytes remain available", ZSTD_cwksp_available_space(ws)); +- assert(ZSTD_cwksp_estimated_space_within_bounds(ws, neededSpace, resizeWorkspace)); ++ assert(ZSTD_cwksp_estimated_space_within_bounds(ws, neededSpace)); + + zc->initialized = 1; + +@@ -1980,7 +2209,8 @@ + } + + params.cParams = ZSTD_adjustCParams_internal(adjusted_cdict_cParams, pledgedSrcSize, +- cdict->dictContentSize, ZSTD_cpm_attachDict); ++ cdict->dictContentSize, ZSTD_cpm_attachDict, ++ params.useRowMatchFinder); + params.cParams.windowLog = windowLog; + params.useRowMatchFinder = cdict->useRowMatchFinder; /* cdict overrides */ + FORWARD_IF_ERROR(ZSTD_resetCCtx_internal(cctx, ¶ms, pledgedSrcSize, +@@ -2019,6 +2249,22 @@ + return 0; + } + ++static void ZSTD_copyCDictTableIntoCCtx(U32* dst, U32 const* src, size_t tableSize, ++ ZSTD_compressionParameters const* cParams) { ++ if (ZSTD_CDictIndicesAreTagged(cParams)){ ++ /* Remove tags from the CDict table if they are present. ++ * See docs on "short cache" in zstd_compress_internal.h for context. */ ++ size_t i; ++ for (i = 0; i < tableSize; i++) { ++ U32 const taggedIndex = src[i]; ++ U32 const index = taggedIndex >> ZSTD_SHORT_CACHE_TAG_BITS; ++ dst[i] = index; ++ } ++ } else { ++ ZSTD_memcpy(dst, src, tableSize * sizeof(U32)); ++ } ++} ++ + static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx, + const ZSTD_CDict* cdict, + ZSTD_CCtx_params params, +@@ -2054,21 +2300,23 @@ + : 0; + size_t const hSize = (size_t)1 << cdict_cParams->hashLog; + +- ZSTD_memcpy(cctx->blockState.matchState.hashTable, +- cdict->matchState.hashTable, +- hSize * sizeof(U32)); ++ ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.hashTable, ++ cdict->matchState.hashTable, ++ hSize, cdict_cParams); ++ + /* Do not copy cdict's chainTable if cctx has parameters such that it would not use chainTable */ + if (ZSTD_allocateChainTable(cctx->appliedParams.cParams.strategy, cctx->appliedParams.useRowMatchFinder, 0 /* forDDSDict */)) { +- ZSTD_memcpy(cctx->blockState.matchState.chainTable, +- cdict->matchState.chainTable, +- chainSize * sizeof(U32)); ++ ZSTD_copyCDictTableIntoCCtx(cctx->blockState.matchState.chainTable, ++ cdict->matchState.chainTable, ++ chainSize, cdict_cParams); + } + /* copy tag table */ + if (ZSTD_rowMatchFinderUsed(cdict_cParams->strategy, cdict->useRowMatchFinder)) { +- size_t const tagTableSize = hSize*sizeof(U16); ++ size_t const tagTableSize = hSize; + ZSTD_memcpy(cctx->blockState.matchState.tagTable, +- cdict->matchState.tagTable, +- tagTableSize); ++ cdict->matchState.tagTable, ++ tagTableSize); ++ cctx->blockState.matchState.hashSalt = cdict->matchState.hashSalt; + } + } + +@@ -2147,6 +2395,7 @@ + params.useBlockSplitter = srcCCtx->appliedParams.useBlockSplitter; + params.ldmParams = srcCCtx->appliedParams.ldmParams; + params.fParams = fParams; ++ params.maxBlockSize = srcCCtx->appliedParams.maxBlockSize; + ZSTD_resetCCtx_internal(dstCCtx, ¶ms, pledgedSrcSize, + /* loadedDictSize */ 0, + ZSTDcrp_leaveDirty, zbuff); +@@ -2294,7 +2543,7 @@ + + /* See doc/zstd_compression_format.md for detailed format description */ + +-void ZSTD_seqToCodes(const seqStore_t* seqStorePtr) ++int ZSTD_seqToCodes(const seqStore_t* seqStorePtr) + { + const seqDef* const sequences = seqStorePtr->sequencesStart; + BYTE* const llCodeTable = seqStorePtr->llCode; +@@ -2302,18 +2551,24 @@ + BYTE* const mlCodeTable = seqStorePtr->mlCode; + U32 const nbSeq = (U32)(seqStorePtr->sequences - seqStorePtr->sequencesStart); + U32 u; ++ int longOffsets = 0; + assert(nbSeq <= seqStorePtr->maxNbSeq); + for (u=0; u= STREAM_ACCUMULATOR_MIN)); ++ if (MEM_32bits() && ofCode >= STREAM_ACCUMULATOR_MIN) ++ longOffsets = 1; + } + if (seqStorePtr->longLengthType==ZSTD_llt_literalLength) + llCodeTable[seqStorePtr->longLengthPos] = MaxLL; + if (seqStorePtr->longLengthType==ZSTD_llt_matchLength) + mlCodeTable[seqStorePtr->longLengthPos] = MaxML; ++ return longOffsets; + } + + /* ZSTD_useTargetCBlockSize(): +@@ -2347,6 +2602,7 @@ + U32 MLtype; + size_t size; + size_t lastCountSize; /* Accounts for bug in 1.3.4. More detail in ZSTD_entropyCompressSeqStore_internal() */ ++ int longOffsets; + } ZSTD_symbolEncodingTypeStats_t; + + /* ZSTD_buildSequencesStatistics(): +@@ -2357,11 +2613,13 @@ + * entropyWkspSize must be of size at least ENTROPY_WORKSPACE_SIZE - (MaxSeq + 1)*sizeof(U32) + */ + static ZSTD_symbolEncodingTypeStats_t +-ZSTD_buildSequencesStatistics(seqStore_t* seqStorePtr, size_t nbSeq, +- const ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy, +- BYTE* dst, const BYTE* const dstEnd, +- ZSTD_strategy strategy, unsigned* countWorkspace, +- void* entropyWorkspace, size_t entropyWkspSize) { ++ZSTD_buildSequencesStatistics( ++ const seqStore_t* seqStorePtr, size_t nbSeq, ++ const ZSTD_fseCTables_t* prevEntropy, ZSTD_fseCTables_t* nextEntropy, ++ BYTE* dst, const BYTE* const dstEnd, ++ ZSTD_strategy strategy, unsigned* countWorkspace, ++ void* entropyWorkspace, size_t entropyWkspSize) ++{ + BYTE* const ostart = dst; + const BYTE* const oend = dstEnd; + BYTE* op = ostart; +@@ -2375,7 +2633,7 @@ + + stats.lastCountSize = 0; + /* convert length/distances into codes */ +- ZSTD_seqToCodes(seqStorePtr); ++ stats.longOffsets = ZSTD_seqToCodes(seqStorePtr); + assert(op <= oend); + assert(nbSeq != 0); /* ZSTD_selectEncodingType() divides by nbSeq */ + /* build CTable for Literal Lengths */ +@@ -2480,22 +2738,22 @@ + */ + #define SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO 20 + MEM_STATIC size_t +-ZSTD_entropyCompressSeqStore_internal(seqStore_t* seqStorePtr, +- const ZSTD_entropyCTables_t* prevEntropy, +- ZSTD_entropyCTables_t* nextEntropy, +- const ZSTD_CCtx_params* cctxParams, +- void* dst, size_t dstCapacity, +- void* entropyWorkspace, size_t entropyWkspSize, +- const int bmi2) ++ZSTD_entropyCompressSeqStore_internal( ++ const seqStore_t* seqStorePtr, ++ const ZSTD_entropyCTables_t* prevEntropy, ++ ZSTD_entropyCTables_t* nextEntropy, ++ const ZSTD_CCtx_params* cctxParams, ++ void* dst, size_t dstCapacity, ++ void* entropyWorkspace, size_t entropyWkspSize, ++ const int bmi2) + { +- const int longOffsets = cctxParams->cParams.windowLog > STREAM_ACCUMULATOR_MIN; + ZSTD_strategy const strategy = cctxParams->cParams.strategy; + unsigned* count = (unsigned*)entropyWorkspace; + FSE_CTable* CTable_LitLength = nextEntropy->fse.litlengthCTable; + FSE_CTable* CTable_OffsetBits = nextEntropy->fse.offcodeCTable; + FSE_CTable* CTable_MatchLength = nextEntropy->fse.matchlengthCTable; + const seqDef* const sequences = seqStorePtr->sequencesStart; +- const size_t nbSeq = seqStorePtr->sequences - seqStorePtr->sequencesStart; ++ const size_t nbSeq = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart); + const BYTE* const ofCodeTable = seqStorePtr->ofCode; + const BYTE* const llCodeTable = seqStorePtr->llCode; + const BYTE* const mlCodeTable = seqStorePtr->mlCode; +@@ -2503,29 +2761,31 @@ + BYTE* const oend = ostart + dstCapacity; + BYTE* op = ostart; + size_t lastCountSize; ++ int longOffsets = 0; + + entropyWorkspace = count + (MaxSeq + 1); + entropyWkspSize -= (MaxSeq + 1) * sizeof(*count); + +- DEBUGLOG(4, "ZSTD_entropyCompressSeqStore_internal (nbSeq=%zu)", nbSeq); ++ DEBUGLOG(5, "ZSTD_entropyCompressSeqStore_internal (nbSeq=%zu, dstCapacity=%zu)", nbSeq, dstCapacity); + ZSTD_STATIC_ASSERT(HUF_WORKSPACE_SIZE >= (1<= HUF_WORKSPACE_SIZE); + + /* Compress literals */ + { const BYTE* const literals = seqStorePtr->litStart; +- size_t const numSequences = seqStorePtr->sequences - seqStorePtr->sequencesStart; +- size_t const numLiterals = seqStorePtr->lit - seqStorePtr->litStart; ++ size_t const numSequences = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart); ++ size_t const numLiterals = (size_t)(seqStorePtr->lit - seqStorePtr->litStart); + /* Base suspicion of uncompressibility on ratio of literals to sequences */ + unsigned const suspectUncompressible = (numSequences == 0) || (numLiterals / numSequences >= SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO); + size_t const litSize = (size_t)(seqStorePtr->lit - literals); ++ + size_t const cSize = ZSTD_compressLiterals( +- &prevEntropy->huf, &nextEntropy->huf, +- cctxParams->cParams.strategy, +- ZSTD_literalsCompressionIsDisabled(cctxParams), + op, dstCapacity, + literals, litSize, + entropyWorkspace, entropyWkspSize, +- bmi2, suspectUncompressible); ++ &prevEntropy->huf, &nextEntropy->huf, ++ cctxParams->cParams.strategy, ++ ZSTD_literalsCompressionIsDisabled(cctxParams), ++ suspectUncompressible, bmi2); + FORWARD_IF_ERROR(cSize, "ZSTD_compressLiterals failed"); + assert(cSize <= dstCapacity); + op += cSize; +@@ -2551,11 +2811,10 @@ + ZSTD_memcpy(&nextEntropy->fse, &prevEntropy->fse, sizeof(prevEntropy->fse)); + return (size_t)(op - ostart); + } +- { +- ZSTD_symbolEncodingTypeStats_t stats; +- BYTE* seqHead = op++; ++ { BYTE* const seqHead = op++; + /* build stats for sequences */ +- stats = ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq, ++ const ZSTD_symbolEncodingTypeStats_t stats = ++ ZSTD_buildSequencesStatistics(seqStorePtr, nbSeq, + &prevEntropy->fse, &nextEntropy->fse, + op, oend, + strategy, count, +@@ -2564,6 +2823,7 @@ + *seqHead = (BYTE)((stats.LLtype<<6) + (stats.Offtype<<4) + (stats.MLtype<<2)); + lastCountSize = stats.lastCountSize; + op += stats.size; ++ longOffsets = stats.longOffsets; + } + + { size_t const bitstreamSize = ZSTD_encodeSequences( +@@ -2598,14 +2858,15 @@ + } + + MEM_STATIC size_t +-ZSTD_entropyCompressSeqStore(seqStore_t* seqStorePtr, +- const ZSTD_entropyCTables_t* prevEntropy, +- ZSTD_entropyCTables_t* nextEntropy, +- const ZSTD_CCtx_params* cctxParams, +- void* dst, size_t dstCapacity, +- size_t srcSize, +- void* entropyWorkspace, size_t entropyWkspSize, +- int bmi2) ++ZSTD_entropyCompressSeqStore( ++ const seqStore_t* seqStorePtr, ++ const ZSTD_entropyCTables_t* prevEntropy, ++ ZSTD_entropyCTables_t* nextEntropy, ++ const ZSTD_CCtx_params* cctxParams, ++ void* dst, size_t dstCapacity, ++ size_t srcSize, ++ void* entropyWorkspace, size_t entropyWkspSize, ++ int bmi2) + { + size_t const cSize = ZSTD_entropyCompressSeqStore_internal( + seqStorePtr, prevEntropy, nextEntropy, cctxParams, +@@ -2615,15 +2876,21 @@ + /* When srcSize <= dstCapacity, there is enough space to write a raw uncompressed block. + * Since we ran out of space, block must be not compressible, so fall back to raw uncompressed block. + */ +- if ((cSize == ERROR(dstSize_tooSmall)) & (srcSize <= dstCapacity)) ++ if ((cSize == ERROR(dstSize_tooSmall)) & (srcSize <= dstCapacity)) { ++ DEBUGLOG(4, "not enough dstCapacity (%zu) for ZSTD_entropyCompressSeqStore_internal()=> do not compress block", dstCapacity); + return 0; /* block not compressed */ ++ } + FORWARD_IF_ERROR(cSize, "ZSTD_entropyCompressSeqStore_internal failed"); + + /* Check compressibility */ + { size_t const maxCSize = srcSize - ZSTD_minGain(srcSize, cctxParams->cParams.strategy); + if (cSize >= maxCSize) return 0; /* block not compressed */ + } +- DEBUGLOG(4, "ZSTD_entropyCompressSeqStore() cSize: %zu", cSize); ++ DEBUGLOG(5, "ZSTD_entropyCompressSeqStore() cSize: %zu", cSize); ++ /* libzstd decoder before > v1.5.4 is not compatible with compressed blocks of size ZSTD_BLOCKSIZE_MAX exactly. ++ * This restriction is indirectly already fulfilled by respecting ZSTD_minGain() condition above. ++ */ ++ assert(cSize < ZSTD_BLOCKSIZE_MAX); + return cSize; + } + +@@ -2718,6 +2985,72 @@ + ssPtr->longLengthType = ZSTD_llt_none; + } + ++/* ZSTD_postProcessSequenceProducerResult() : ++ * Validates and post-processes sequences obtained through the external matchfinder API: ++ * - Checks whether nbExternalSeqs represents an error condition. ++ * - Appends a block delimiter to outSeqs if one is not already present. ++ * See zstd.h for context regarding block delimiters. ++ * Returns the number of sequences after post-processing, or an error code. */ ++static size_t ZSTD_postProcessSequenceProducerResult( ++ ZSTD_Sequence* outSeqs, size_t nbExternalSeqs, size_t outSeqsCapacity, size_t srcSize ++) { ++ RETURN_ERROR_IF( ++ nbExternalSeqs > outSeqsCapacity, ++ sequenceProducer_failed, ++ "External sequence producer returned error code %lu", ++ (unsigned long)nbExternalSeqs ++ ); ++ ++ RETURN_ERROR_IF( ++ nbExternalSeqs == 0 && srcSize > 0, ++ sequenceProducer_failed, ++ "Got zero sequences from external sequence producer for a non-empty src buffer!" ++ ); ++ ++ if (srcSize == 0) { ++ ZSTD_memset(&outSeqs[0], 0, sizeof(ZSTD_Sequence)); ++ return 1; ++ } ++ ++ { ++ ZSTD_Sequence const lastSeq = outSeqs[nbExternalSeqs - 1]; ++ ++ /* We can return early if lastSeq is already a block delimiter. */ ++ if (lastSeq.offset == 0 && lastSeq.matchLength == 0) { ++ return nbExternalSeqs; ++ } ++ ++ /* This error condition is only possible if the external matchfinder ++ * produced an invalid parse, by definition of ZSTD_sequenceBound(). */ ++ RETURN_ERROR_IF( ++ nbExternalSeqs == outSeqsCapacity, ++ sequenceProducer_failed, ++ "nbExternalSeqs == outSeqsCapacity but lastSeq is not a block delimiter!" ++ ); ++ ++ /* lastSeq is not a block delimiter, so we need to append one. */ ++ ZSTD_memset(&outSeqs[nbExternalSeqs], 0, sizeof(ZSTD_Sequence)); ++ return nbExternalSeqs + 1; ++ } ++} ++ ++/* ZSTD_fastSequenceLengthSum() : ++ * Returns sum(litLen) + sum(matchLen) + lastLits for *seqBuf*. ++ * Similar to another function in zstd_compress.c (determine_blockSize), ++ * except it doesn't check for a block delimiter to end summation. ++ * Removing the early exit allows the compiler to auto-vectorize (https://godbolt.org/z/cY1cajz9P). ++ * This function can be deleted and replaced by determine_blockSize after we resolve issue #3456. */ ++static size_t ZSTD_fastSequenceLengthSum(ZSTD_Sequence const* seqBuf, size_t seqBufSize) { ++ size_t matchLenSum, litLenSum, i; ++ matchLenSum = 0; ++ litLenSum = 0; ++ for (i = 0; i < seqBufSize; i++) { ++ litLenSum += seqBuf[i].litLength; ++ matchLenSum += seqBuf[i].matchLength; ++ } ++ return litLenSum + matchLenSum; ++} ++ + typedef enum { ZSTDbss_compress, ZSTDbss_noCompress } ZSTD_buildSeqStore_e; + + static size_t ZSTD_buildSeqStore(ZSTD_CCtx* zc, const void* src, size_t srcSize) +@@ -2727,7 +3060,9 @@ + assert(srcSize <= ZSTD_BLOCKSIZE_MAX); + /* Assert that we have correctly flushed the ctx params into the ms's copy */ + ZSTD_assertEqualCParams(zc->appliedParams.cParams, ms->cParams); +- if (srcSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1) { ++ /* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding ++ * additional 1. We need to revisit and change this logic to be more consistent */ ++ if (srcSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1+1) { + if (zc->appliedParams.cParams.strategy >= ZSTD_btopt) { + ZSTD_ldm_skipRawSeqStoreBytes(&zc->externSeqStore, srcSize); + } else { +@@ -2763,6 +3098,15 @@ + } + if (zc->externSeqStore.pos < zc->externSeqStore.size) { + assert(zc->appliedParams.ldmParams.enableLdm == ZSTD_ps_disable); ++ ++ /* External matchfinder + LDM is technically possible, just not implemented yet. ++ * We need to revisit soon and implement it. */ ++ RETURN_ERROR_IF( ++ zc->appliedParams.useSequenceProducer, ++ parameter_combination_unsupported, ++ "Long-distance matching with external sequence producer enabled is not currently supported." ++ ); ++ + /* Updates ldmSeqStore.pos */ + lastLLSize = + ZSTD_ldm_blockCompress(&zc->externSeqStore, +@@ -2774,6 +3118,14 @@ + } else if (zc->appliedParams.ldmParams.enableLdm == ZSTD_ps_enable) { + rawSeqStore_t ldmSeqStore = kNullRawSeqStore; + ++ /* External matchfinder + LDM is technically possible, just not implemented yet. ++ * We need to revisit soon and implement it. */ ++ RETURN_ERROR_IF( ++ zc->appliedParams.useSequenceProducer, ++ parameter_combination_unsupported, ++ "Long-distance matching with external sequence producer enabled is not currently supported." ++ ); ++ + ldmSeqStore.seq = zc->ldmSequences; + ldmSeqStore.capacity = zc->maxNbLdmSequences; + /* Updates ldmSeqStore.size */ +@@ -2788,7 +3140,68 @@ + zc->appliedParams.useRowMatchFinder, + src, srcSize); + assert(ldmSeqStore.pos == ldmSeqStore.size); +- } else { /* not long range mode */ ++ } else if (zc->appliedParams.useSequenceProducer) { ++ assert( ++ zc->externalMatchCtx.seqBufferCapacity >= ZSTD_sequenceBound(srcSize) ++ ); ++ assert(zc->externalMatchCtx.mFinder != NULL); ++ ++ { U32 const windowSize = (U32)1 << zc->appliedParams.cParams.windowLog; ++ ++ size_t const nbExternalSeqs = (zc->externalMatchCtx.mFinder)( ++ zc->externalMatchCtx.mState, ++ zc->externalMatchCtx.seqBuffer, ++ zc->externalMatchCtx.seqBufferCapacity, ++ src, srcSize, ++ NULL, 0, /* dict and dictSize, currently not supported */ ++ zc->appliedParams.compressionLevel, ++ windowSize ++ ); ++ ++ size_t const nbPostProcessedSeqs = ZSTD_postProcessSequenceProducerResult( ++ zc->externalMatchCtx.seqBuffer, ++ nbExternalSeqs, ++ zc->externalMatchCtx.seqBufferCapacity, ++ srcSize ++ ); ++ ++ /* Return early if there is no error, since we don't need to worry about last literals */ ++ if (!ZSTD_isError(nbPostProcessedSeqs)) { ++ ZSTD_sequencePosition seqPos = {0,0,0}; ++ size_t const seqLenSum = ZSTD_fastSequenceLengthSum(zc->externalMatchCtx.seqBuffer, nbPostProcessedSeqs); ++ RETURN_ERROR_IF(seqLenSum > srcSize, externalSequences_invalid, "External sequences imply too large a block!"); ++ FORWARD_IF_ERROR( ++ ZSTD_copySequencesToSeqStoreExplicitBlockDelim( ++ zc, &seqPos, ++ zc->externalMatchCtx.seqBuffer, nbPostProcessedSeqs, ++ src, srcSize, ++ zc->appliedParams.searchForExternalRepcodes ++ ), ++ "Failed to copy external sequences to seqStore!" ++ ); ++ ms->ldmSeqStore = NULL; ++ DEBUGLOG(5, "Copied %lu sequences from external sequence producer to internal seqStore.", (unsigned long)nbExternalSeqs); ++ return ZSTDbss_compress; ++ } ++ ++ /* Propagate the error if fallback is disabled */ ++ if (!zc->appliedParams.enableMatchFinderFallback) { ++ return nbPostProcessedSeqs; ++ } ++ ++ /* Fallback to software matchfinder */ ++ { ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, ++ zc->appliedParams.useRowMatchFinder, ++ dictMode); ++ ms->ldmSeqStore = NULL; ++ DEBUGLOG( ++ 5, ++ "External sequence producer returned error code %lu. Falling back to internal parser.", ++ (unsigned long)nbExternalSeqs ++ ); ++ lastLLSize = blockCompressor(ms, &zc->seqStore, zc->blockState.nextCBlock->rep, src, srcSize); ++ } } ++ } else { /* not long range mode and no external matchfinder */ + ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, + zc->appliedParams.useRowMatchFinder, + dictMode); +@@ -2849,7 +3262,7 @@ + /* seqStoreSeqs[i].offset == offCode+1, and ZSTD_updateRep() expects offCode + so we provide seqStoreSeqs[i].offset - 1 */ + ZSTD_updateRep(updatedRepcodes.rep, +- seqStoreSeqs[i].offBase - 1, ++ seqStoreSeqs[i].offBase, + seqStoreSeqs[i].litLength == 0); + literalsRead += outSeqs[i].litLength; + } +@@ -2865,6 +3278,10 @@ + zc->seqCollector.seqIndex += seqStoreSeqSize; + } + ++size_t ZSTD_sequenceBound(size_t srcSize) { ++ return (srcSize / ZSTD_MINMATCH_MIN) + 1; ++} ++ + size_t ZSTD_generateSequences(ZSTD_CCtx* zc, ZSTD_Sequence* outSeqs, + size_t outSeqsSize, const void* src, size_t srcSize) + { +@@ -2910,19 +3327,17 @@ + const size_t unrollMask = unrollSize - 1; + const size_t prefixLength = length & unrollMask; + size_t i; +- size_t u; + if (length == 1) return 1; + /* Check if prefix is RLE first before using unrolled loop */ + if (prefixLength && ZSTD_count(ip+1, ip, ip+prefixLength) != prefixLength-1) { + return 0; + } + for (i = prefixLength; i != length; i += unrollSize) { ++ size_t u; + for (u = 0; u < unrollSize; u += sizeof(size_t)) { + if (MEM_readST(ip + i + u) != valueST) { + return 0; +- } +- } +- } ++ } } } + return 1; + } + +@@ -2938,7 +3353,8 @@ + return nbSeqs < 4 && nbLits < 10; + } + +-static void ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs) ++static void ++ZSTD_blockState_confirmRepcodesAndEntropyTables(ZSTD_blockState_t* const bs) + { + ZSTD_compressedBlockState_t* const tmp = bs->prevCBlock; + bs->prevCBlock = bs->nextCBlock; +@@ -2946,7 +3362,9 @@ + } + + /* Writes the block header */ +-static void writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastBlock) { ++static void ++writeBlockHeader(void* op, size_t cSize, size_t blockSize, U32 lastBlock) ++{ + U32 const cBlockHeader = cSize == 1 ? + lastBlock + (((U32)bt_rle)<<1) + (U32)(blockSize << 3) : + lastBlock + (((U32)bt_compressed)<<1) + (U32)(cSize << 3); +@@ -2959,13 +3377,16 @@ + * Stores literals block type (raw, rle, compressed, repeat) and + * huffman description table to hufMetadata. + * Requires ENTROPY_WORKSPACE_SIZE workspace +- * @return : size of huffman description table or error code */ +-static size_t ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSize, +- const ZSTD_hufCTables_t* prevHuf, +- ZSTD_hufCTables_t* nextHuf, +- ZSTD_hufCTablesMetadata_t* hufMetadata, +- const int literalsCompressionIsDisabled, +- void* workspace, size_t wkspSize) ++ * @return : size of huffman description table, or an error code ++ */ ++static size_t ++ZSTD_buildBlockEntropyStats_literals(void* const src, size_t srcSize, ++ const ZSTD_hufCTables_t* prevHuf, ++ ZSTD_hufCTables_t* nextHuf, ++ ZSTD_hufCTablesMetadata_t* hufMetadata, ++ const int literalsCompressionIsDisabled, ++ void* workspace, size_t wkspSize, ++ int hufFlags) + { + BYTE* const wkspStart = (BYTE*)workspace; + BYTE* const wkspEnd = wkspStart + wkspSize; +@@ -2973,9 +3394,9 @@ + unsigned* const countWksp = (unsigned*)workspace; + const size_t countWkspSize = (HUF_SYMBOLVALUE_MAX + 1) * sizeof(unsigned); + BYTE* const nodeWksp = countWkspStart + countWkspSize; +- const size_t nodeWkspSize = wkspEnd-nodeWksp; ++ const size_t nodeWkspSize = (size_t)(wkspEnd - nodeWksp); + unsigned maxSymbolValue = HUF_SYMBOLVALUE_MAX; +- unsigned huffLog = HUF_TABLELOG_DEFAULT; ++ unsigned huffLog = LitHufLog; + HUF_repeat repeat = prevHuf->repeatMode; + DEBUGLOG(5, "ZSTD_buildBlockEntropyStats_literals (srcSize=%zu)", srcSize); + +@@ -2990,73 +3411,77 @@ + + /* small ? don't even attempt compression (speed opt) */ + #ifndef COMPRESS_LITERALS_SIZE_MIN +-#define COMPRESS_LITERALS_SIZE_MIN 63 ++# define COMPRESS_LITERALS_SIZE_MIN 63 /* heuristic */ + #endif + { size_t const minLitSize = (prevHuf->repeatMode == HUF_repeat_valid) ? 6 : COMPRESS_LITERALS_SIZE_MIN; + if (srcSize <= minLitSize) { + DEBUGLOG(5, "set_basic - too small"); + hufMetadata->hType = set_basic; + return 0; +- } +- } ++ } } + + /* Scan input and build symbol stats */ +- { size_t const largest = HIST_count_wksp (countWksp, &maxSymbolValue, (const BYTE*)src, srcSize, workspace, wkspSize); ++ { size_t const largest = ++ HIST_count_wksp (countWksp, &maxSymbolValue, ++ (const BYTE*)src, srcSize, ++ workspace, wkspSize); + FORWARD_IF_ERROR(largest, "HIST_count_wksp failed"); + if (largest == srcSize) { ++ /* only one literal symbol */ + DEBUGLOG(5, "set_rle"); + hufMetadata->hType = set_rle; + return 0; + } + if (largest <= (srcSize >> 7)+4) { ++ /* heuristic: likely not compressible */ + DEBUGLOG(5, "set_basic - no gain"); + hufMetadata->hType = set_basic; + return 0; +- } +- } ++ } } + + /* Validate the previous Huffman table */ +- if (repeat == HUF_repeat_check && !HUF_validateCTable((HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue)) { ++ if (repeat == HUF_repeat_check ++ && !HUF_validateCTable((HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue)) { + repeat = HUF_repeat_none; + } + + /* Build Huffman Tree */ + ZSTD_memset(nextHuf->CTable, 0, sizeof(nextHuf->CTable)); +- huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue); ++ huffLog = HUF_optimalTableLog(huffLog, srcSize, maxSymbolValue, nodeWksp, nodeWkspSize, nextHuf->CTable, countWksp, hufFlags); ++ assert(huffLog <= LitHufLog); + { size_t const maxBits = HUF_buildCTable_wksp((HUF_CElt*)nextHuf->CTable, countWksp, + maxSymbolValue, huffLog, + nodeWksp, nodeWkspSize); + FORWARD_IF_ERROR(maxBits, "HUF_buildCTable_wksp"); + huffLog = (U32)maxBits; +- { /* Build and write the CTable */ +- size_t const newCSize = HUF_estimateCompressedSize( +- (HUF_CElt*)nextHuf->CTable, countWksp, maxSymbolValue); +- size_t const hSize = HUF_writeCTable_wksp( +- hufMetadata->hufDesBuffer, sizeof(hufMetadata->hufDesBuffer), +- (HUF_CElt*)nextHuf->CTable, maxSymbolValue, huffLog, +- nodeWksp, nodeWkspSize); +- /* Check against repeating the previous CTable */ +- if (repeat != HUF_repeat_none) { +- size_t const oldCSize = HUF_estimateCompressedSize( +- (HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue); +- if (oldCSize < srcSize && (oldCSize <= hSize + newCSize || hSize + 12 >= srcSize)) { +- DEBUGLOG(5, "set_repeat - smaller"); +- ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); +- hufMetadata->hType = set_repeat; +- return 0; +- } +- } +- if (newCSize + hSize >= srcSize) { +- DEBUGLOG(5, "set_basic - no gains"); ++ } ++ { /* Build and write the CTable */ ++ size_t const newCSize = HUF_estimateCompressedSize( ++ (HUF_CElt*)nextHuf->CTable, countWksp, maxSymbolValue); ++ size_t const hSize = HUF_writeCTable_wksp( ++ hufMetadata->hufDesBuffer, sizeof(hufMetadata->hufDesBuffer), ++ (HUF_CElt*)nextHuf->CTable, maxSymbolValue, huffLog, ++ nodeWksp, nodeWkspSize); ++ /* Check against repeating the previous CTable */ ++ if (repeat != HUF_repeat_none) { ++ size_t const oldCSize = HUF_estimateCompressedSize( ++ (HUF_CElt const*)prevHuf->CTable, countWksp, maxSymbolValue); ++ if (oldCSize < srcSize && (oldCSize <= hSize + newCSize || hSize + 12 >= srcSize)) { ++ DEBUGLOG(5, "set_repeat - smaller"); + ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); +- hufMetadata->hType = set_basic; ++ hufMetadata->hType = set_repeat; + return 0; +- } +- DEBUGLOG(5, "set_compressed (hSize=%u)", (U32)hSize); +- hufMetadata->hType = set_compressed; +- nextHuf->repeatMode = HUF_repeat_check; +- return hSize; ++ } } ++ if (newCSize + hSize >= srcSize) { ++ DEBUGLOG(5, "set_basic - no gains"); ++ ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); ++ hufMetadata->hType = set_basic; ++ return 0; + } ++ DEBUGLOG(5, "set_compressed (hSize=%u)", (U32)hSize); ++ hufMetadata->hType = set_compressed; ++ nextHuf->repeatMode = HUF_repeat_check; ++ return hSize; + } + } + +@@ -3066,8 +3491,9 @@ + * and updates nextEntropy to the appropriate repeatMode. + */ + static ZSTD_symbolEncodingTypeStats_t +-ZSTD_buildDummySequencesStatistics(ZSTD_fseCTables_t* nextEntropy) { +- ZSTD_symbolEncodingTypeStats_t stats = {set_basic, set_basic, set_basic, 0, 0}; ++ZSTD_buildDummySequencesStatistics(ZSTD_fseCTables_t* nextEntropy) ++{ ++ ZSTD_symbolEncodingTypeStats_t stats = {set_basic, set_basic, set_basic, 0, 0, 0}; + nextEntropy->litlength_repeatMode = FSE_repeat_none; + nextEntropy->offcode_repeatMode = FSE_repeat_none; + nextEntropy->matchlength_repeatMode = FSE_repeat_none; +@@ -3078,16 +3504,18 @@ + * Builds entropy for the sequences. + * Stores symbol compression modes and fse table to fseMetadata. + * Requires ENTROPY_WORKSPACE_SIZE wksp. +- * @return : size of fse tables or error code */ +-static size_t ZSTD_buildBlockEntropyStats_sequences(seqStore_t* seqStorePtr, +- const ZSTD_fseCTables_t* prevEntropy, +- ZSTD_fseCTables_t* nextEntropy, +- const ZSTD_CCtx_params* cctxParams, +- ZSTD_fseCTablesMetadata_t* fseMetadata, +- void* workspace, size_t wkspSize) ++ * @return : size of fse tables or error code */ ++static size_t ++ZSTD_buildBlockEntropyStats_sequences( ++ const seqStore_t* seqStorePtr, ++ const ZSTD_fseCTables_t* prevEntropy, ++ ZSTD_fseCTables_t* nextEntropy, ++ const ZSTD_CCtx_params* cctxParams, ++ ZSTD_fseCTablesMetadata_t* fseMetadata, ++ void* workspace, size_t wkspSize) + { + ZSTD_strategy const strategy = cctxParams->cParams.strategy; +- size_t const nbSeq = seqStorePtr->sequences - seqStorePtr->sequencesStart; ++ size_t const nbSeq = (size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart); + BYTE* const ostart = fseMetadata->fseTablesBuffer; + BYTE* const oend = ostart + sizeof(fseMetadata->fseTablesBuffer); + BYTE* op = ostart; +@@ -3114,23 +3542,28 @@ + /* ZSTD_buildBlockEntropyStats() : + * Builds entropy for the block. + * Requires workspace size ENTROPY_WORKSPACE_SIZE +- * +- * @return : 0 on success or error code ++ * @return : 0 on success, or an error code ++ * Note : also employed in superblock + */ +-size_t ZSTD_buildBlockEntropyStats(seqStore_t* seqStorePtr, +- const ZSTD_entropyCTables_t* prevEntropy, +- ZSTD_entropyCTables_t* nextEntropy, +- const ZSTD_CCtx_params* cctxParams, +- ZSTD_entropyCTablesMetadata_t* entropyMetadata, +- void* workspace, size_t wkspSize) +-{ +- size_t const litSize = seqStorePtr->lit - seqStorePtr->litStart; ++size_t ZSTD_buildBlockEntropyStats( ++ const seqStore_t* seqStorePtr, ++ const ZSTD_entropyCTables_t* prevEntropy, ++ ZSTD_entropyCTables_t* nextEntropy, ++ const ZSTD_CCtx_params* cctxParams, ++ ZSTD_entropyCTablesMetadata_t* entropyMetadata, ++ void* workspace, size_t wkspSize) ++{ ++ size_t const litSize = (size_t)(seqStorePtr->lit - seqStorePtr->litStart); ++ int const huf_useOptDepth = (cctxParams->cParams.strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD); ++ int const hufFlags = huf_useOptDepth ? HUF_flags_optimalDepth : 0; ++ + entropyMetadata->hufMetadata.hufDesSize = + ZSTD_buildBlockEntropyStats_literals(seqStorePtr->litStart, litSize, + &prevEntropy->huf, &nextEntropy->huf, + &entropyMetadata->hufMetadata, + ZSTD_literalsCompressionIsDisabled(cctxParams), +- workspace, wkspSize); ++ workspace, wkspSize, hufFlags); ++ + FORWARD_IF_ERROR(entropyMetadata->hufMetadata.hufDesSize, "ZSTD_buildBlockEntropyStats_literals failed"); + entropyMetadata->fseMetadata.fseTablesSize = + ZSTD_buildBlockEntropyStats_sequences(seqStorePtr, +@@ -3143,11 +3576,12 @@ + } + + /* Returns the size estimate for the literals section (header + content) of a block */ +-static size_t ZSTD_estimateBlockSize_literal(const BYTE* literals, size_t litSize, +- const ZSTD_hufCTables_t* huf, +- const ZSTD_hufCTablesMetadata_t* hufMetadata, +- void* workspace, size_t wkspSize, +- int writeEntropy) ++static size_t ++ZSTD_estimateBlockSize_literal(const BYTE* literals, size_t litSize, ++ const ZSTD_hufCTables_t* huf, ++ const ZSTD_hufCTablesMetadata_t* hufMetadata, ++ void* workspace, size_t wkspSize, ++ int writeEntropy) + { + unsigned* const countWksp = (unsigned*)workspace; + unsigned maxSymbolValue = HUF_SYMBOLVALUE_MAX; +@@ -3169,12 +3603,13 @@ + } + + /* Returns the size estimate for the FSE-compressed symbols (of, ml, ll) of a block */ +-static size_t ZSTD_estimateBlockSize_symbolType(symbolEncodingType_e type, +- const BYTE* codeTable, size_t nbSeq, unsigned maxCode, +- const FSE_CTable* fseCTable, +- const U8* additionalBits, +- short const* defaultNorm, U32 defaultNormLog, U32 defaultMax, +- void* workspace, size_t wkspSize) ++static size_t ++ZSTD_estimateBlockSize_symbolType(symbolEncodingType_e type, ++ const BYTE* codeTable, size_t nbSeq, unsigned maxCode, ++ const FSE_CTable* fseCTable, ++ const U8* additionalBits, ++ short const* defaultNorm, U32 defaultNormLog, U32 defaultMax, ++ void* workspace, size_t wkspSize) + { + unsigned* const countWksp = (unsigned*)workspace; + const BYTE* ctp = codeTable; +@@ -3206,99 +3641,107 @@ + } + + /* Returns the size estimate for the sequences section (header + content) of a block */ +-static size_t ZSTD_estimateBlockSize_sequences(const BYTE* ofCodeTable, +- const BYTE* llCodeTable, +- const BYTE* mlCodeTable, +- size_t nbSeq, +- const ZSTD_fseCTables_t* fseTables, +- const ZSTD_fseCTablesMetadata_t* fseMetadata, +- void* workspace, size_t wkspSize, +- int writeEntropy) ++static size_t ++ZSTD_estimateBlockSize_sequences(const BYTE* ofCodeTable, ++ const BYTE* llCodeTable, ++ const BYTE* mlCodeTable, ++ size_t nbSeq, ++ const ZSTD_fseCTables_t* fseTables, ++ const ZSTD_fseCTablesMetadata_t* fseMetadata, ++ void* workspace, size_t wkspSize, ++ int writeEntropy) + { + size_t sequencesSectionHeaderSize = 1 /* seqHead */ + 1 /* min seqSize size */ + (nbSeq >= 128) + (nbSeq >= LONGNBSEQ); + size_t cSeqSizeEstimate = 0; + cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->ofType, ofCodeTable, nbSeq, MaxOff, +- fseTables->offcodeCTable, NULL, +- OF_defaultNorm, OF_defaultNormLog, DefaultMaxOff, +- workspace, wkspSize); ++ fseTables->offcodeCTable, NULL, ++ OF_defaultNorm, OF_defaultNormLog, DefaultMaxOff, ++ workspace, wkspSize); + cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->llType, llCodeTable, nbSeq, MaxLL, +- fseTables->litlengthCTable, LL_bits, +- LL_defaultNorm, LL_defaultNormLog, MaxLL, +- workspace, wkspSize); ++ fseTables->litlengthCTable, LL_bits, ++ LL_defaultNorm, LL_defaultNormLog, MaxLL, ++ workspace, wkspSize); + cSeqSizeEstimate += ZSTD_estimateBlockSize_symbolType(fseMetadata->mlType, mlCodeTable, nbSeq, MaxML, +- fseTables->matchlengthCTable, ML_bits, +- ML_defaultNorm, ML_defaultNormLog, MaxML, +- workspace, wkspSize); ++ fseTables->matchlengthCTable, ML_bits, ++ ML_defaultNorm, ML_defaultNormLog, MaxML, ++ workspace, wkspSize); + if (writeEntropy) cSeqSizeEstimate += fseMetadata->fseTablesSize; + return cSeqSizeEstimate + sequencesSectionHeaderSize; + } + + /* Returns the size estimate for a given stream of literals, of, ll, ml */ +-static size_t ZSTD_estimateBlockSize(const BYTE* literals, size_t litSize, +- const BYTE* ofCodeTable, +- const BYTE* llCodeTable, +- const BYTE* mlCodeTable, +- size_t nbSeq, +- const ZSTD_entropyCTables_t* entropy, +- const ZSTD_entropyCTablesMetadata_t* entropyMetadata, +- void* workspace, size_t wkspSize, +- int writeLitEntropy, int writeSeqEntropy) { ++static size_t ++ZSTD_estimateBlockSize(const BYTE* literals, size_t litSize, ++ const BYTE* ofCodeTable, ++ const BYTE* llCodeTable, ++ const BYTE* mlCodeTable, ++ size_t nbSeq, ++ const ZSTD_entropyCTables_t* entropy, ++ const ZSTD_entropyCTablesMetadata_t* entropyMetadata, ++ void* workspace, size_t wkspSize, ++ int writeLitEntropy, int writeSeqEntropy) ++{ + size_t const literalsSize = ZSTD_estimateBlockSize_literal(literals, litSize, +- &entropy->huf, &entropyMetadata->hufMetadata, +- workspace, wkspSize, writeLitEntropy); ++ &entropy->huf, &entropyMetadata->hufMetadata, ++ workspace, wkspSize, writeLitEntropy); + size_t const seqSize = ZSTD_estimateBlockSize_sequences(ofCodeTable, llCodeTable, mlCodeTable, +- nbSeq, &entropy->fse, &entropyMetadata->fseMetadata, +- workspace, wkspSize, writeSeqEntropy); ++ nbSeq, &entropy->fse, &entropyMetadata->fseMetadata, ++ workspace, wkspSize, writeSeqEntropy); + return seqSize + literalsSize + ZSTD_blockHeaderSize; + } + + /* Builds entropy statistics and uses them for blocksize estimation. + * +- * Returns the estimated compressed size of the seqStore, or a zstd error. ++ * @return: estimated compressed size of the seqStore, or a zstd error. + */ +-static size_t ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(seqStore_t* seqStore, ZSTD_CCtx* zc) { +- ZSTD_entropyCTablesMetadata_t* entropyMetadata = &zc->blockSplitCtx.entropyMetadata; ++static size_t ++ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(seqStore_t* seqStore, ZSTD_CCtx* zc) ++{ ++ ZSTD_entropyCTablesMetadata_t* const entropyMetadata = &zc->blockSplitCtx.entropyMetadata; + DEBUGLOG(6, "ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize()"); + FORWARD_IF_ERROR(ZSTD_buildBlockEntropyStats(seqStore, + &zc->blockState.prevCBlock->entropy, + &zc->blockState.nextCBlock->entropy, + &zc->appliedParams, + entropyMetadata, +- zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE /* statically allocated in resetCCtx */), ""); +- return ZSTD_estimateBlockSize(seqStore->litStart, (size_t)(seqStore->lit - seqStore->litStart), ++ zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE), ""); ++ return ZSTD_estimateBlockSize( ++ seqStore->litStart, (size_t)(seqStore->lit - seqStore->litStart), + seqStore->ofCode, seqStore->llCode, seqStore->mlCode, + (size_t)(seqStore->sequences - seqStore->sequencesStart), +- &zc->blockState.nextCBlock->entropy, entropyMetadata, zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE, ++ &zc->blockState.nextCBlock->entropy, ++ entropyMetadata, ++ zc->entropyWorkspace, ENTROPY_WORKSPACE_SIZE, + (int)(entropyMetadata->hufMetadata.hType == set_compressed), 1); + } + + /* Returns literals bytes represented in a seqStore */ +-static size_t ZSTD_countSeqStoreLiteralsBytes(const seqStore_t* const seqStore) { ++static size_t ZSTD_countSeqStoreLiteralsBytes(const seqStore_t* const seqStore) ++{ + size_t literalsBytes = 0; +- size_t const nbSeqs = seqStore->sequences - seqStore->sequencesStart; ++ size_t const nbSeqs = (size_t)(seqStore->sequences - seqStore->sequencesStart); + size_t i; + for (i = 0; i < nbSeqs; ++i) { +- seqDef seq = seqStore->sequencesStart[i]; ++ seqDef const seq = seqStore->sequencesStart[i]; + literalsBytes += seq.litLength; + if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_llt_literalLength) { + literalsBytes += 0x10000; +- } +- } ++ } } + return literalsBytes; + } + + /* Returns match bytes represented in a seqStore */ +-static size_t ZSTD_countSeqStoreMatchBytes(const seqStore_t* const seqStore) { ++static size_t ZSTD_countSeqStoreMatchBytes(const seqStore_t* const seqStore) ++{ + size_t matchBytes = 0; +- size_t const nbSeqs = seqStore->sequences - seqStore->sequencesStart; ++ size_t const nbSeqs = (size_t)(seqStore->sequences - seqStore->sequencesStart); + size_t i; + for (i = 0; i < nbSeqs; ++i) { + seqDef seq = seqStore->sequencesStart[i]; + matchBytes += seq.mlBase + MINMATCH; + if (i == seqStore->longLengthPos && seqStore->longLengthType == ZSTD_llt_matchLength) { + matchBytes += 0x10000; +- } +- } ++ } } + return matchBytes; + } + +@@ -3307,15 +3750,12 @@ + */ + static void ZSTD_deriveSeqStoreChunk(seqStore_t* resultSeqStore, + const seqStore_t* originalSeqStore, +- size_t startIdx, size_t endIdx) { +- BYTE* const litEnd = originalSeqStore->lit; +- size_t literalsBytes; +- size_t literalsBytesPreceding = 0; +- ++ size_t startIdx, size_t endIdx) ++{ + *resultSeqStore = *originalSeqStore; + if (startIdx > 0) { + resultSeqStore->sequences = originalSeqStore->sequencesStart + startIdx; +- literalsBytesPreceding = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); ++ resultSeqStore->litStart += ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); + } + + /* Move longLengthPos into the correct position if necessary */ +@@ -3328,13 +3768,12 @@ + } + resultSeqStore->sequencesStart = originalSeqStore->sequencesStart + startIdx; + resultSeqStore->sequences = originalSeqStore->sequencesStart + endIdx; +- literalsBytes = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); +- resultSeqStore->litStart += literalsBytesPreceding; + if (endIdx == (size_t)(originalSeqStore->sequences - originalSeqStore->sequencesStart)) { + /* This accounts for possible last literals if the derived chunk reaches the end of the block */ +- resultSeqStore->lit = litEnd; ++ assert(resultSeqStore->lit == originalSeqStore->lit); + } else { +- resultSeqStore->lit = resultSeqStore->litStart+literalsBytes; ++ size_t const literalsBytes = ZSTD_countSeqStoreLiteralsBytes(resultSeqStore); ++ resultSeqStore->lit = resultSeqStore->litStart + literalsBytes; + } + resultSeqStore->llCode += startIdx; + resultSeqStore->mlCode += startIdx; +@@ -3342,20 +3781,26 @@ + } + + /* +- * Returns the raw offset represented by the combination of offCode, ll0, and repcode history. +- * offCode must represent a repcode in the numeric representation of ZSTD_storeSeq(). ++ * Returns the raw offset represented by the combination of offBase, ll0, and repcode history. ++ * offBase must represent a repcode in the numeric representation of ZSTD_storeSeq(). + */ + static U32 +-ZSTD_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], const U32 offCode, const U32 ll0) ++ZSTD_resolveRepcodeToRawOffset(const U32 rep[ZSTD_REP_NUM], const U32 offBase, const U32 ll0) + { +- U32 const adjustedOffCode = STORED_REPCODE(offCode) - 1 + ll0; /* [ 0 - 3 ] */ +- assert(STORED_IS_REPCODE(offCode)); +- if (adjustedOffCode == ZSTD_REP_NUM) { +- /* litlength == 0 and offCode == 2 implies selection of first repcode - 1 */ +- assert(rep[0] > 0); ++ U32 const adjustedRepCode = OFFBASE_TO_REPCODE(offBase) - 1 + ll0; /* [ 0 - 3 ] */ ++ assert(OFFBASE_IS_REPCODE(offBase)); ++ if (adjustedRepCode == ZSTD_REP_NUM) { ++ assert(ll0); ++ /* litlength == 0 and offCode == 2 implies selection of first repcode - 1 ++ * This is only valid if it results in a valid offset value, aka > 0. ++ * Note : it may happen that `rep[0]==1` in exceptional circumstances. ++ * In which case this function will return 0, which is an invalid offset. ++ * It's not an issue though, since this value will be ++ * compared and discarded within ZSTD_seqStore_resolveOffCodes(). ++ */ + return rep[0] - 1; + } +- return rep[adjustedOffCode]; ++ return rep[adjustedRepCode]; + } + + /* +@@ -3371,30 +3816,33 @@ + * 1-3 : repcode 1-3 + * 4+ : real_offset+3 + */ +-static void ZSTD_seqStore_resolveOffCodes(repcodes_t* const dRepcodes, repcodes_t* const cRepcodes, +- seqStore_t* const seqStore, U32 const nbSeq) { ++static void ++ZSTD_seqStore_resolveOffCodes(repcodes_t* const dRepcodes, repcodes_t* const cRepcodes, ++ const seqStore_t* const seqStore, U32 const nbSeq) ++{ + U32 idx = 0; ++ U32 const longLitLenIdx = seqStore->longLengthType == ZSTD_llt_literalLength ? seqStore->longLengthPos : nbSeq; + for (; idx < nbSeq; ++idx) { + seqDef* const seq = seqStore->sequencesStart + idx; +- U32 const ll0 = (seq->litLength == 0); +- U32 const offCode = OFFBASE_TO_STORED(seq->offBase); +- assert(seq->offBase > 0); +- if (STORED_IS_REPCODE(offCode)) { +- U32 const dRawOffset = ZSTD_resolveRepcodeToRawOffset(dRepcodes->rep, offCode, ll0); +- U32 const cRawOffset = ZSTD_resolveRepcodeToRawOffset(cRepcodes->rep, offCode, ll0); ++ U32 const ll0 = (seq->litLength == 0) && (idx != longLitLenIdx); ++ U32 const offBase = seq->offBase; ++ assert(offBase > 0); ++ if (OFFBASE_IS_REPCODE(offBase)) { ++ U32 const dRawOffset = ZSTD_resolveRepcodeToRawOffset(dRepcodes->rep, offBase, ll0); ++ U32 const cRawOffset = ZSTD_resolveRepcodeToRawOffset(cRepcodes->rep, offBase, ll0); + /* Adjust simulated decompression repcode history if we come across a mismatch. Replace + * the repcode with the offset it actually references, determined by the compression + * repcode history. + */ + if (dRawOffset != cRawOffset) { +- seq->offBase = cRawOffset + ZSTD_REP_NUM; ++ seq->offBase = OFFSET_TO_OFFBASE(cRawOffset); + } + } + /* Compression repcode history is always updated with values directly from the unmodified seqStore. + * Decompression repcode history may use modified seq->offset value taken from compression repcode history. + */ +- ZSTD_updateRep(dRepcodes->rep, OFFBASE_TO_STORED(seq->offBase), ll0); +- ZSTD_updateRep(cRepcodes->rep, offCode, ll0); ++ ZSTD_updateRep(dRepcodes->rep, seq->offBase, ll0); ++ ZSTD_updateRep(cRepcodes->rep, offBase, ll0); + } + } + +@@ -3404,10 +3852,11 @@ + * Returns the total size of that block (including header) or a ZSTD error code. + */ + static size_t +-ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, seqStore_t* const seqStore, ++ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, ++ const seqStore_t* const seqStore, + repcodes_t* const dRep, repcodes_t* const cRep, + void* dst, size_t dstCapacity, +- const void* src, size_t srcSize, ++ const void* src, size_t srcSize, + U32 lastBlock, U32 isPartition) + { + const U32 rleMaxLength = 25; +@@ -3481,45 +3930,49 @@ + + /* Helper function to perform the recursive search for block splits. + * Estimates the cost of seqStore prior to split, and estimates the cost of splitting the sequences in half. +- * If advantageous to split, then we recurse down the two sub-blocks. If not, or if an error occurred in estimation, then +- * we do not recurse. ++ * If advantageous to split, then we recurse down the two sub-blocks. ++ * If not, or if an error occurred in estimation, then we do not recurse. + * +- * Note: The recursion depth is capped by a heuristic minimum number of sequences, defined by MIN_SEQUENCES_BLOCK_SPLITTING. ++ * Note: The recursion depth is capped by a heuristic minimum number of sequences, ++ * defined by MIN_SEQUENCES_BLOCK_SPLITTING. + * In theory, this means the absolute largest recursion depth is 10 == log2(maxNbSeqInBlock/MIN_SEQUENCES_BLOCK_SPLITTING). + * In practice, recursion depth usually doesn't go beyond 4. + * +- * Furthermore, the number of splits is capped by ZSTD_MAX_NB_BLOCK_SPLITS. At ZSTD_MAX_NB_BLOCK_SPLITS == 196 with the current existing blockSize ++ * Furthermore, the number of splits is capped by ZSTD_MAX_NB_BLOCK_SPLITS. ++ * At ZSTD_MAX_NB_BLOCK_SPLITS == 196 with the current existing blockSize + * maximum of 128 KB, this value is actually impossible to reach. + */ + static void + ZSTD_deriveBlockSplitsHelper(seqStoreSplits* splits, size_t startIdx, size_t endIdx, + ZSTD_CCtx* zc, const seqStore_t* origSeqStore) + { +- seqStore_t* fullSeqStoreChunk = &zc->blockSplitCtx.fullSeqStoreChunk; +- seqStore_t* firstHalfSeqStore = &zc->blockSplitCtx.firstHalfSeqStore; +- seqStore_t* secondHalfSeqStore = &zc->blockSplitCtx.secondHalfSeqStore; ++ seqStore_t* const fullSeqStoreChunk = &zc->blockSplitCtx.fullSeqStoreChunk; ++ seqStore_t* const firstHalfSeqStore = &zc->blockSplitCtx.firstHalfSeqStore; ++ seqStore_t* const secondHalfSeqStore = &zc->blockSplitCtx.secondHalfSeqStore; + size_t estimatedOriginalSize; + size_t estimatedFirstHalfSize; + size_t estimatedSecondHalfSize; + size_t midIdx = (startIdx + endIdx)/2; + ++ DEBUGLOG(5, "ZSTD_deriveBlockSplitsHelper: startIdx=%zu endIdx=%zu", startIdx, endIdx); ++ assert(endIdx >= startIdx); + if (endIdx - startIdx < MIN_SEQUENCES_BLOCK_SPLITTING || splits->idx >= ZSTD_MAX_NB_BLOCK_SPLITS) { +- DEBUGLOG(6, "ZSTD_deriveBlockSplitsHelper: Too few sequences"); ++ DEBUGLOG(6, "ZSTD_deriveBlockSplitsHelper: Too few sequences (%zu)", endIdx - startIdx); + return; + } +- DEBUGLOG(4, "ZSTD_deriveBlockSplitsHelper: startIdx=%zu endIdx=%zu", startIdx, endIdx); + ZSTD_deriveSeqStoreChunk(fullSeqStoreChunk, origSeqStore, startIdx, endIdx); + ZSTD_deriveSeqStoreChunk(firstHalfSeqStore, origSeqStore, startIdx, midIdx); + ZSTD_deriveSeqStoreChunk(secondHalfSeqStore, origSeqStore, midIdx, endIdx); + estimatedOriginalSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(fullSeqStoreChunk, zc); + estimatedFirstHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(firstHalfSeqStore, zc); + estimatedSecondHalfSize = ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(secondHalfSeqStore, zc); +- DEBUGLOG(4, "Estimated original block size: %zu -- First half split: %zu -- Second half split: %zu", ++ DEBUGLOG(5, "Estimated original block size: %zu -- First half split: %zu -- Second half split: %zu", + estimatedOriginalSize, estimatedFirstHalfSize, estimatedSecondHalfSize); + if (ZSTD_isError(estimatedOriginalSize) || ZSTD_isError(estimatedFirstHalfSize) || ZSTD_isError(estimatedSecondHalfSize)) { + return; + } + if (estimatedFirstHalfSize + estimatedSecondHalfSize < estimatedOriginalSize) { ++ DEBUGLOG(5, "split decided at seqNb:%zu", midIdx); + ZSTD_deriveBlockSplitsHelper(splits, startIdx, midIdx, zc, origSeqStore); + splits->splitLocations[splits->idx] = (U32)midIdx; + splits->idx++; +@@ -3527,14 +3980,18 @@ + } + } + +-/* Base recursive function. Populates a table with intra-block partition indices that can improve compression ratio. ++/* Base recursive function. ++ * Populates a table with intra-block partition indices that can improve compression ratio. + * +- * Returns the number of splits made (which equals the size of the partition table - 1). ++ * @return: number of splits made (which equals the size of the partition table - 1). + */ +-static size_t ZSTD_deriveBlockSplits(ZSTD_CCtx* zc, U32 partitions[], U32 nbSeq) { +- seqStoreSplits splits = {partitions, 0}; ++static size_t ZSTD_deriveBlockSplits(ZSTD_CCtx* zc, U32 partitions[], U32 nbSeq) ++{ ++ seqStoreSplits splits; ++ splits.splitLocations = partitions; ++ splits.idx = 0; + if (nbSeq <= 4) { +- DEBUGLOG(4, "ZSTD_deriveBlockSplits: Too few sequences to split"); ++ DEBUGLOG(5, "ZSTD_deriveBlockSplits: Too few sequences to split (%u <= 4)", nbSeq); + /* Refuse to try and split anything with less than 4 sequences */ + return 0; + } +@@ -3550,18 +4007,20 @@ + * Returns combined size of all blocks (which includes headers), or a ZSTD error code. + */ + static size_t +-ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx* zc, void* dst, size_t dstCapacity, +- const void* src, size_t blockSize, U32 lastBlock, U32 nbSeq) ++ZSTD_compressBlock_splitBlock_internal(ZSTD_CCtx* zc, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t blockSize, ++ U32 lastBlock, U32 nbSeq) + { + size_t cSize = 0; + const BYTE* ip = (const BYTE*)src; + BYTE* op = (BYTE*)dst; + size_t i = 0; + size_t srcBytesTotal = 0; +- U32* partitions = zc->blockSplitCtx.partitions; /* size == ZSTD_MAX_NB_BLOCK_SPLITS */ +- seqStore_t* nextSeqStore = &zc->blockSplitCtx.nextSeqStore; +- seqStore_t* currSeqStore = &zc->blockSplitCtx.currSeqStore; +- size_t numSplits = ZSTD_deriveBlockSplits(zc, partitions, nbSeq); ++ U32* const partitions = zc->blockSplitCtx.partitions; /* size == ZSTD_MAX_NB_BLOCK_SPLITS */ ++ seqStore_t* const nextSeqStore = &zc->blockSplitCtx.nextSeqStore; ++ seqStore_t* const currSeqStore = &zc->blockSplitCtx.currSeqStore; ++ size_t const numSplits = ZSTD_deriveBlockSplits(zc, partitions, nbSeq); + + /* If a block is split and some partitions are emitted as RLE/uncompressed, then repcode history + * may become invalid. In order to reconcile potentially invalid repcodes, we keep track of two +@@ -3583,30 +4042,31 @@ + ZSTD_memcpy(cRep.rep, zc->blockState.prevCBlock->rep, sizeof(repcodes_t)); + ZSTD_memset(nextSeqStore, 0, sizeof(seqStore_t)); + +- DEBUGLOG(4, "ZSTD_compressBlock_splitBlock_internal (dstCapacity=%u, dictLimit=%u, nextToUpdate=%u)", ++ DEBUGLOG(5, "ZSTD_compressBlock_splitBlock_internal (dstCapacity=%u, dictLimit=%u, nextToUpdate=%u)", + (unsigned)dstCapacity, (unsigned)zc->blockState.matchState.window.dictLimit, + (unsigned)zc->blockState.matchState.nextToUpdate); + + if (numSplits == 0) { +- size_t cSizeSingleBlock = ZSTD_compressSeqStore_singleBlock(zc, &zc->seqStore, +- &dRep, &cRep, +- op, dstCapacity, +- ip, blockSize, +- lastBlock, 0 /* isPartition */); ++ size_t cSizeSingleBlock = ++ ZSTD_compressSeqStore_singleBlock(zc, &zc->seqStore, ++ &dRep, &cRep, ++ op, dstCapacity, ++ ip, blockSize, ++ lastBlock, 0 /* isPartition */); + FORWARD_IF_ERROR(cSizeSingleBlock, "Compressing single block from splitBlock_internal() failed!"); + DEBUGLOG(5, "ZSTD_compressBlock_splitBlock_internal: No splits"); +- assert(cSizeSingleBlock <= ZSTD_BLOCKSIZE_MAX + ZSTD_blockHeaderSize); ++ assert(zc->blockSize <= ZSTD_BLOCKSIZE_MAX); ++ assert(cSizeSingleBlock <= zc->blockSize + ZSTD_blockHeaderSize); + return cSizeSingleBlock; + } + + ZSTD_deriveSeqStoreChunk(currSeqStore, &zc->seqStore, 0, partitions[0]); + for (i = 0; i <= numSplits; ++i) { +- size_t srcBytes; + size_t cSizeChunk; + U32 const lastPartition = (i == numSplits); + U32 lastBlockEntireSrc = 0; + +- srcBytes = ZSTD_countSeqStoreLiteralsBytes(currSeqStore) + ZSTD_countSeqStoreMatchBytes(currSeqStore); ++ size_t srcBytes = ZSTD_countSeqStoreLiteralsBytes(currSeqStore) + ZSTD_countSeqStoreMatchBytes(currSeqStore); + srcBytesTotal += srcBytes; + if (lastPartition) { + /* This is the final partition, need to account for possible last literals */ +@@ -3621,7 +4081,8 @@ + op, dstCapacity, + ip, srcBytes, + lastBlockEntireSrc, 1 /* isPartition */); +- DEBUGLOG(5, "Estimated size: %zu actual size: %zu", ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(currSeqStore, zc), cSizeChunk); ++ DEBUGLOG(5, "Estimated size: %zu vs %zu : actual size", ++ ZSTD_buildEntropyStatisticsAndEstimateSubBlockSize(currSeqStore, zc), cSizeChunk); + FORWARD_IF_ERROR(cSizeChunk, "Compressing chunk failed!"); + + ip += srcBytes; +@@ -3629,10 +4090,10 @@ + dstCapacity -= cSizeChunk; + cSize += cSizeChunk; + *currSeqStore = *nextSeqStore; +- assert(cSizeChunk <= ZSTD_BLOCKSIZE_MAX + ZSTD_blockHeaderSize); ++ assert(cSizeChunk <= zc->blockSize + ZSTD_blockHeaderSize); + } +- /* cRep and dRep may have diverged during the compression. If so, we use the dRep repcodes +- * for the next block. ++ /* cRep and dRep may have diverged during the compression. ++ * If so, we use the dRep repcodes for the next block. + */ + ZSTD_memcpy(zc->blockState.prevCBlock->rep, dRep.rep, sizeof(repcodes_t)); + return cSize; +@@ -3643,8 +4104,6 @@ + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, U32 lastBlock) + { +- const BYTE* ip = (const BYTE*)src; +- BYTE* op = (BYTE*)dst; + U32 nbSeq; + size_t cSize; + DEBUGLOG(4, "ZSTD_compressBlock_splitBlock"); +@@ -3655,7 +4114,7 @@ + if (bss == ZSTDbss_noCompress) { + if (zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode == FSE_repeat_valid) + zc->blockState.prevCBlock->entropy.fse.offcode_repeatMode = FSE_repeat_check; +- cSize = ZSTD_noCompressBlock(op, dstCapacity, ip, srcSize, lastBlock); ++ cSize = ZSTD_noCompressBlock(dst, dstCapacity, src, srcSize, lastBlock); + FORWARD_IF_ERROR(cSize, "ZSTD_noCompressBlock failed"); + DEBUGLOG(4, "ZSTD_compressBlock_splitBlock: Nocompress block"); + return cSize; +@@ -3673,9 +4132,9 @@ + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, U32 frame) + { +- /* This the upper bound for the length of an rle block. +- * This isn't the actual upper bound. Finding the real threshold +- * needs further investigation. ++ /* This is an estimated upper bound for the length of an rle block. ++ * This isn't the actual upper bound. ++ * Finding the real threshold needs further investigation. + */ + const U32 rleMaxLength = 25; + size_t cSize; +@@ -3767,10 +4226,11 @@ + * * cSize >= blockBound(srcSize): We have expanded the block too much so + * emit an uncompressed block. + */ +- { +- size_t const cSize = ZSTD_compressSuperBlock(zc, dst, dstCapacity, src, srcSize, lastBlock); ++ { size_t const cSize = ++ ZSTD_compressSuperBlock(zc, dst, dstCapacity, src, srcSize, lastBlock); + if (cSize != ERROR(dstSize_tooSmall)) { +- size_t const maxCSize = srcSize - ZSTD_minGain(srcSize, zc->appliedParams.cParams.strategy); ++ size_t const maxCSize = ++ srcSize - ZSTD_minGain(srcSize, zc->appliedParams.cParams.strategy); + FORWARD_IF_ERROR(cSize, "ZSTD_compressSuperBlock failed"); + if (cSize != 0 && cSize < maxCSize + ZSTD_blockHeaderSize) { + ZSTD_blockState_confirmRepcodesAndEntropyTables(&zc->blockState); +@@ -3778,7 +4238,7 @@ + } + } + } +- } ++ } /* if (bss == ZSTDbss_compress)*/ + + DEBUGLOG(6, "Resorting to ZSTD_noCompressBlock()"); + /* Superblock compression failed, attempt to emit a single no compress block. +@@ -3836,7 +4296,7 @@ + * All blocks will be terminated, all input will be consumed. + * Function will issue an error if there is not enough `dstCapacity` to hold the compressed content. + * Frame is supposed already started (header already produced) +-* @return : compressed size, or an error code ++* @return : compressed size, or an error code + */ + static size_t ZSTD_compress_frameChunk(ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, +@@ -3860,7 +4320,9 @@ + ZSTD_matchState_t* const ms = &cctx->blockState.matchState; + U32 const lastBlock = lastFrameChunk & (blockSize >= remaining); + +- RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize + MIN_CBLOCK_SIZE, ++ /* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding ++ * additional 1. We need to revisit and change this logic to be more consistent */ ++ RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize + MIN_CBLOCK_SIZE + 1, + dstSize_tooSmall, + "not enough space to store compressed block"); + if (remaining < blockSize) blockSize = remaining; +@@ -3899,7 +4361,7 @@ + MEM_writeLE24(op, cBlockHeader); + cSize += ZSTD_blockHeaderSize; + } +- } ++ } /* if (ZSTD_useTargetCBlockSize(&cctx->appliedParams))*/ + + + ip += blockSize; +@@ -4078,31 +4540,51 @@ + } + } + +-size_t ZSTD_compressContinue (ZSTD_CCtx* cctx, +- void* dst, size_t dstCapacity, +- const void* src, size_t srcSize) ++size_t ZSTD_compressContinue_public(ZSTD_CCtx* cctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize) + { + DEBUGLOG(5, "ZSTD_compressContinue (srcSize=%u)", (unsigned)srcSize); + return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 1 /* frame mode */, 0 /* last chunk */); + } + ++/* NOTE: Must just wrap ZSTD_compressContinue_public() */ ++size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize) ++{ ++ return ZSTD_compressContinue_public(cctx, dst, dstCapacity, src, srcSize); ++} + +-size_t ZSTD_getBlockSize(const ZSTD_CCtx* cctx) ++static size_t ZSTD_getBlockSize_deprecated(const ZSTD_CCtx* cctx) + { + ZSTD_compressionParameters const cParams = cctx->appliedParams.cParams; + assert(!ZSTD_checkCParams(cParams)); +- return MIN (ZSTD_BLOCKSIZE_MAX, (U32)1 << cParams.windowLog); ++ return MIN(cctx->appliedParams.maxBlockSize, (size_t)1 << cParams.windowLog); + } + +-size_t ZSTD_compressBlock(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) ++/* NOTE: Must just wrap ZSTD_getBlockSize_deprecated() */ ++size_t ZSTD_getBlockSize(const ZSTD_CCtx* cctx) ++{ ++ return ZSTD_getBlockSize_deprecated(cctx); ++} ++ ++/* NOTE: Must just wrap ZSTD_compressBlock_deprecated() */ ++size_t ZSTD_compressBlock_deprecated(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) + { + DEBUGLOG(5, "ZSTD_compressBlock: srcSize = %u", (unsigned)srcSize); +- { size_t const blockSizeMax = ZSTD_getBlockSize(cctx); ++ { size_t const blockSizeMax = ZSTD_getBlockSize_deprecated(cctx); + RETURN_ERROR_IF(srcSize > blockSizeMax, srcSize_wrong, "input is larger than a block"); } + + return ZSTD_compressContinue_internal(cctx, dst, dstCapacity, src, srcSize, 0 /* frame mode */, 0 /* last chunk */); + } + ++/* NOTE: Must just wrap ZSTD_compressBlock_deprecated() */ ++size_t ZSTD_compressBlock(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) ++{ ++ return ZSTD_compressBlock_deprecated(cctx, dst, dstCapacity, src, srcSize); ++} ++ + /*! ZSTD_loadDictionaryContent() : + * @return : 0, or an error code + */ +@@ -4111,25 +4593,36 @@ + ZSTD_cwksp* ws, + ZSTD_CCtx_params const* params, + const void* src, size_t srcSize, +- ZSTD_dictTableLoadMethod_e dtlm) ++ ZSTD_dictTableLoadMethod_e dtlm, ++ ZSTD_tableFillPurpose_e tfp) + { + const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + int const loadLdmDict = params->ldmParams.enableLdm == ZSTD_ps_enable && ls != NULL; + +- /* Assert that we the ms params match the params we're being given */ ++ /* Assert that the ms params match the params we're being given */ + ZSTD_assertEqualCParams(params->cParams, ms->cParams); + +- if (srcSize > ZSTD_CHUNKSIZE_MAX) { ++ { /* Ensure large dictionaries can't cause index overflow */ ++ + /* Allow the dictionary to set indices up to exactly ZSTD_CURRENT_MAX. + * Dictionaries right at the edge will immediately trigger overflow + * correction, but I don't want to insert extra constraints here. + */ +- U32 const maxDictSize = ZSTD_CURRENT_MAX - 1; +- /* We must have cleared our windows when our source is this large. */ +- assert(ZSTD_window_isEmpty(ms->window)); +- if (loadLdmDict) +- assert(ZSTD_window_isEmpty(ls->window)); ++ U32 maxDictSize = ZSTD_CURRENT_MAX - ZSTD_WINDOW_START_INDEX; ++ ++ int const CDictTaggedIndices = ZSTD_CDictIndicesAreTagged(¶ms->cParams); ++ if (CDictTaggedIndices && tfp == ZSTD_tfp_forCDict) { ++ /* Some dictionary matchfinders in zstd use "short cache", ++ * which treats the lower ZSTD_SHORT_CACHE_TAG_BITS of each ++ * CDict hashtable entry as a tag rather than as part of an index. ++ * When short cache is used, we need to truncate the dictionary ++ * so that its indices don't overlap with the tag. */ ++ U32 const shortCacheMaxDictSize = (1u << (32 - ZSTD_SHORT_CACHE_TAG_BITS)) - ZSTD_WINDOW_START_INDEX; ++ maxDictSize = MIN(maxDictSize, shortCacheMaxDictSize); ++ assert(!loadLdmDict); ++ } ++ + /* If the dictionary is too large, only load the suffix of the dictionary. */ + if (srcSize > maxDictSize) { + ip = iend - maxDictSize; +@@ -4138,30 +4631,46 @@ + } + } + +- DEBUGLOG(4, "ZSTD_loadDictionaryContent(): useRowMatchFinder=%d", (int)params->useRowMatchFinder); ++ if (srcSize > ZSTD_CHUNKSIZE_MAX) { ++ /* We must have cleared our windows when our source is this large. */ ++ assert(ZSTD_window_isEmpty(ms->window)); ++ if (loadLdmDict) assert(ZSTD_window_isEmpty(ls->window)); ++ } + ZSTD_window_update(&ms->window, src, srcSize, /* forceNonContiguous */ 0); +- ms->loadedDictEnd = params->forceWindow ? 0 : (U32)(iend - ms->window.base); +- ms->forceNonContiguous = params->deterministicRefPrefix; + +- if (loadLdmDict) { ++ DEBUGLOG(4, "ZSTD_loadDictionaryContent(): useRowMatchFinder=%d", (int)params->useRowMatchFinder); ++ ++ if (loadLdmDict) { /* Load the entire dict into LDM matchfinders. */ + ZSTD_window_update(&ls->window, src, srcSize, /* forceNonContiguous */ 0); + ls->loadedDictEnd = params->forceWindow ? 0 : (U32)(iend - ls->window.base); ++ ZSTD_ldm_fillHashTable(ls, ip, iend, ¶ms->ldmParams); ++ } ++ ++ /* If the dict is larger than we can reasonably index in our tables, only load the suffix. */ ++ if (params->cParams.strategy < ZSTD_btultra) { ++ U32 maxDictSize = 8U << MIN(MAX(params->cParams.hashLog, params->cParams.chainLog), 28); ++ if (srcSize > maxDictSize) { ++ ip = iend - maxDictSize; ++ src = ip; ++ srcSize = maxDictSize; ++ } + } + ++ ms->nextToUpdate = (U32)(ip - ms->window.base); ++ ms->loadedDictEnd = params->forceWindow ? 0 : (U32)(iend - ms->window.base); ++ ms->forceNonContiguous = params->deterministicRefPrefix; ++ + if (srcSize <= HASH_READ_SIZE) return 0; + + ZSTD_overflowCorrectIfNeeded(ms, ws, params, ip, iend); + +- if (loadLdmDict) +- ZSTD_ldm_fillHashTable(ls, ip, iend, ¶ms->ldmParams); +- + switch(params->cParams.strategy) + { + case ZSTD_fast: +- ZSTD_fillHashTable(ms, iend, dtlm); ++ ZSTD_fillHashTable(ms, iend, dtlm, tfp); + break; + case ZSTD_dfast: +- ZSTD_fillDoubleHashTable(ms, iend, dtlm); ++ ZSTD_fillDoubleHashTable(ms, iend, dtlm, tfp); + break; + + case ZSTD_greedy: +@@ -4174,7 +4683,7 @@ + } else { + assert(params->useRowMatchFinder != ZSTD_ps_auto); + if (params->useRowMatchFinder == ZSTD_ps_enable) { +- size_t const tagTableSize = ((size_t)1 << params->cParams.hashLog) * sizeof(U16); ++ size_t const tagTableSize = ((size_t)1 << params->cParams.hashLog); + ZSTD_memset(ms->tagTable, 0, tagTableSize); + ZSTD_row_update(ms, iend-HASH_READ_SIZE); + DEBUGLOG(4, "Using row-based hash table for lazy dict"); +@@ -4327,6 +4836,7 @@ + ZSTD_CCtx_params const* params, + const void* dict, size_t dictSize, + ZSTD_dictTableLoadMethod_e dtlm, ++ ZSTD_tableFillPurpose_e tfp, + void* workspace) + { + const BYTE* dictPtr = (const BYTE*)dict; +@@ -4345,7 +4855,7 @@ + { + size_t const dictContentSize = (size_t)(dictEnd - dictPtr); + FORWARD_IF_ERROR(ZSTD_loadDictionaryContent( +- ms, NULL, ws, params, dictPtr, dictContentSize, dtlm), ""); ++ ms, NULL, ws, params, dictPtr, dictContentSize, dtlm, tfp), ""); + } + return dictID; + } +@@ -4361,6 +4871,7 @@ + const void* dict, size_t dictSize, + ZSTD_dictContentType_e dictContentType, + ZSTD_dictTableLoadMethod_e dtlm, ++ ZSTD_tableFillPurpose_e tfp, + void* workspace) + { + DEBUGLOG(4, "ZSTD_compress_insertDictionary (dictSize=%u)", (U32)dictSize); +@@ -4373,13 +4884,13 @@ + + /* dict restricted modes */ + if (dictContentType == ZSTD_dct_rawContent) +- return ZSTD_loadDictionaryContent(ms, ls, ws, params, dict, dictSize, dtlm); ++ return ZSTD_loadDictionaryContent(ms, ls, ws, params, dict, dictSize, dtlm, tfp); + + if (MEM_readLE32(dict) != ZSTD_MAGIC_DICTIONARY) { + if (dictContentType == ZSTD_dct_auto) { + DEBUGLOG(4, "raw content dictionary detected"); + return ZSTD_loadDictionaryContent( +- ms, ls, ws, params, dict, dictSize, dtlm); ++ ms, ls, ws, params, dict, dictSize, dtlm, tfp); + } + RETURN_ERROR_IF(dictContentType == ZSTD_dct_fullDict, dictionary_wrong, ""); + assert(0); /* impossible */ +@@ -4387,13 +4898,14 @@ + + /* dict as full zstd dictionary */ + return ZSTD_loadZstdDictionary( +- bs, ms, ws, params, dict, dictSize, dtlm, workspace); ++ bs, ms, ws, params, dict, dictSize, dtlm, tfp, workspace); + } + + #define ZSTD_USE_CDICT_PARAMS_SRCSIZE_CUTOFF (128 KB) + #define ZSTD_USE_CDICT_PARAMS_DICTSIZE_MULTIPLIER (6ULL) + + /*! ZSTD_compressBegin_internal() : ++ * Assumption : either @dict OR @cdict (or none) is non-NULL, never both + * @return : 0, or an error code */ + static size_t ZSTD_compressBegin_internal(ZSTD_CCtx* cctx, + const void* dict, size_t dictSize, +@@ -4426,11 +4938,11 @@ + cctx->blockState.prevCBlock, &cctx->blockState.matchState, + &cctx->ldmState, &cctx->workspace, &cctx->appliedParams, cdict->dictContent, + cdict->dictContentSize, cdict->dictContentType, dtlm, +- cctx->entropyWorkspace) ++ ZSTD_tfp_forCCtx, cctx->entropyWorkspace) + : ZSTD_compress_insertDictionary( + cctx->blockState.prevCBlock, &cctx->blockState.matchState, + &cctx->ldmState, &cctx->workspace, &cctx->appliedParams, dict, dictSize, +- dictContentType, dtlm, cctx->entropyWorkspace); ++ dictContentType, dtlm, ZSTD_tfp_forCCtx, cctx->entropyWorkspace); + FORWARD_IF_ERROR(dictID, "ZSTD_compress_insertDictionary failed"); + assert(dictID <= UINT_MAX); + cctx->dictID = (U32)dictID; +@@ -4471,11 +4983,11 @@ + &cctxParams, pledgedSrcSize); + } + +-size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel) ++static size_t ++ZSTD_compressBegin_usingDict_deprecated(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel) + { + ZSTD_CCtx_params cctxParams; +- { +- ZSTD_parameters const params = ZSTD_getParams_internal(compressionLevel, ZSTD_CONTENTSIZE_UNKNOWN, dictSize, ZSTD_cpm_noAttachDict); ++ { ZSTD_parameters const params = ZSTD_getParams_internal(compressionLevel, ZSTD_CONTENTSIZE_UNKNOWN, dictSize, ZSTD_cpm_noAttachDict); + ZSTD_CCtxParams_init_internal(&cctxParams, ¶ms, (compressionLevel == 0) ? ZSTD_CLEVEL_DEFAULT : compressionLevel); + } + DEBUGLOG(4, "ZSTD_compressBegin_usingDict (dictSize=%u)", (unsigned)dictSize); +@@ -4483,9 +4995,15 @@ + &cctxParams, ZSTD_CONTENTSIZE_UNKNOWN, ZSTDb_not_buffered); + } + ++size_t ++ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel) ++{ ++ return ZSTD_compressBegin_usingDict_deprecated(cctx, dict, dictSize, compressionLevel); ++} ++ + size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel) + { +- return ZSTD_compressBegin_usingDict(cctx, NULL, 0, compressionLevel); ++ return ZSTD_compressBegin_usingDict_deprecated(cctx, NULL, 0, compressionLevel); + } + + +@@ -4537,9 +5055,9 @@ + (void)extraCSize; + } + +-size_t ZSTD_compressEnd (ZSTD_CCtx* cctx, +- void* dst, size_t dstCapacity, +- const void* src, size_t srcSize) ++size_t ZSTD_compressEnd_public(ZSTD_CCtx* cctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize) + { + size_t endResult; + size_t const cSize = ZSTD_compressContinue_internal(cctx, +@@ -4563,6 +5081,14 @@ + return cSize + endResult; + } + ++/* NOTE: Must just wrap ZSTD_compressEnd_public() */ ++size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize) ++{ ++ return ZSTD_compressEnd_public(cctx, dst, dstCapacity, src, srcSize); ++} ++ + size_t ZSTD_compress_advanced (ZSTD_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, +@@ -4591,7 +5117,7 @@ + FORWARD_IF_ERROR( ZSTD_compressBegin_internal(cctx, + dict, dictSize, ZSTD_dct_auto, ZSTD_dtlm_fast, NULL, + params, srcSize, ZSTDb_not_buffered) , ""); +- return ZSTD_compressEnd(cctx, dst, dstCapacity, src, srcSize); ++ return ZSTD_compressEnd_public(cctx, dst, dstCapacity, src, srcSize); + } + + size_t ZSTD_compress_usingDict(ZSTD_CCtx* cctx, +@@ -4709,7 +5235,7 @@ + { size_t const dictID = ZSTD_compress_insertDictionary( + &cdict->cBlockState, &cdict->matchState, NULL, &cdict->workspace, + ¶ms, cdict->dictContent, cdict->dictContentSize, +- dictContentType, ZSTD_dtlm_full, cdict->entropyWorkspace); ++ dictContentType, ZSTD_dtlm_full, ZSTD_tfp_forCDict, cdict->entropyWorkspace); + FORWARD_IF_ERROR(dictID, "ZSTD_compress_insertDictionary failed"); + assert(dictID <= (size_t)(U32)-1); + cdict->dictID = (U32)dictID; +@@ -4906,6 +5432,7 @@ + params.cParams = cParams; + params.useRowMatchFinder = useRowMatchFinder; + cdict->useRowMatchFinder = useRowMatchFinder; ++ cdict->compressionLevel = ZSTD_NO_CLEVEL; + + if (ZSTD_isError( ZSTD_initCDict_internal(cdict, + dict, dictSize, +@@ -4985,12 +5512,17 @@ + + /* ZSTD_compressBegin_usingCDict() : + * cdict must be != NULL */ +-size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict) ++size_t ZSTD_compressBegin_usingCDict_deprecated(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict) + { + ZSTD_frameParameters const fParams = { 0 /*content*/, 0 /*checksum*/, 0 /*noDictID*/ }; + return ZSTD_compressBegin_usingCDict_internal(cctx, cdict, fParams, ZSTD_CONTENTSIZE_UNKNOWN); + } + ++size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict) ++{ ++ return ZSTD_compressBegin_usingCDict_deprecated(cctx, cdict); ++} ++ + /*! ZSTD_compress_usingCDict_internal(): + * Implementation of various ZSTD_compress_usingCDict* functions. + */ +@@ -5000,7 +5532,7 @@ + const ZSTD_CDict* cdict, ZSTD_frameParameters fParams) + { + FORWARD_IF_ERROR(ZSTD_compressBegin_usingCDict_internal(cctx, cdict, fParams, srcSize), ""); /* will check if cdict != NULL */ +- return ZSTD_compressEnd(cctx, dst, dstCapacity, src, srcSize); ++ return ZSTD_compressEnd_public(cctx, dst, dstCapacity, src, srcSize); + } + + /*! ZSTD_compress_usingCDict_advanced(): +@@ -5197,30 +5729,41 @@ + + static size_t ZSTD_nextInputSizeHint(const ZSTD_CCtx* cctx) + { +- size_t hintInSize = cctx->inBuffTarget - cctx->inBuffPos; +- if (hintInSize==0) hintInSize = cctx->blockSize; +- return hintInSize; ++ if (cctx->appliedParams.inBufferMode == ZSTD_bm_stable) { ++ return cctx->blockSize - cctx->stableIn_notConsumed; ++ } ++ assert(cctx->appliedParams.inBufferMode == ZSTD_bm_buffered); ++ { size_t hintInSize = cctx->inBuffTarget - cctx->inBuffPos; ++ if (hintInSize==0) hintInSize = cctx->blockSize; ++ return hintInSize; ++ } + } + + /* ZSTD_compressStream_generic(): + * internal function for all *compressStream*() variants +- * non-static, because can be called from zstdmt_compress.c +- * @return : hint size for next input */ ++ * @return : hint size for next input to complete ongoing block */ + static size_t ZSTD_compressStream_generic(ZSTD_CStream* zcs, + ZSTD_outBuffer* output, + ZSTD_inBuffer* input, + ZSTD_EndDirective const flushMode) + { +- const char* const istart = (const char*)input->src; +- const char* const iend = input->size != 0 ? istart + input->size : istart; +- const char* ip = input->pos != 0 ? istart + input->pos : istart; +- char* const ostart = (char*)output->dst; +- char* const oend = output->size != 0 ? ostart + output->size : ostart; +- char* op = output->pos != 0 ? ostart + output->pos : ostart; ++ const char* const istart = (assert(input != NULL), (const char*)input->src); ++ const char* const iend = (istart != NULL) ? istart + input->size : istart; ++ const char* ip = (istart != NULL) ? istart + input->pos : istart; ++ char* const ostart = (assert(output != NULL), (char*)output->dst); ++ char* const oend = (ostart != NULL) ? ostart + output->size : ostart; ++ char* op = (ostart != NULL) ? ostart + output->pos : ostart; + U32 someMoreWork = 1; + + /* check expectations */ +- DEBUGLOG(5, "ZSTD_compressStream_generic, flush=%u", (unsigned)flushMode); ++ DEBUGLOG(5, "ZSTD_compressStream_generic, flush=%i, srcSize = %zu", (int)flushMode, input->size - input->pos); ++ assert(zcs != NULL); ++ if (zcs->appliedParams.inBufferMode == ZSTD_bm_stable) { ++ assert(input->pos >= zcs->stableIn_notConsumed); ++ input->pos -= zcs->stableIn_notConsumed; ++ ip -= zcs->stableIn_notConsumed; ++ zcs->stableIn_notConsumed = 0; ++ } + if (zcs->appliedParams.inBufferMode == ZSTD_bm_buffered) { + assert(zcs->inBuff != NULL); + assert(zcs->inBuffSize > 0); +@@ -5229,8 +5772,10 @@ + assert(zcs->outBuff != NULL); + assert(zcs->outBuffSize > 0); + } +- assert(output->pos <= output->size); ++ if (input->src == NULL) assert(input->size == 0); + assert(input->pos <= input->size); ++ if (output->dst == NULL) assert(output->size == 0); ++ assert(output->pos <= output->size); + assert((U32)flushMode <= (U32)ZSTD_e_end); + + while (someMoreWork) { +@@ -5245,7 +5790,7 @@ + || zcs->appliedParams.outBufferMode == ZSTD_bm_stable) /* OR we are allowed to return dstSizeTooSmall */ + && (zcs->inBuffPos == 0) ) { + /* shortcut to compression pass directly into output buffer */ +- size_t const cSize = ZSTD_compressEnd(zcs, ++ size_t const cSize = ZSTD_compressEnd_public(zcs, + op, oend-op, ip, iend-ip); + DEBUGLOG(4, "ZSTD_compressEnd : cSize=%u", (unsigned)cSize); + FORWARD_IF_ERROR(cSize, "ZSTD_compressEnd failed"); +@@ -5262,8 +5807,7 @@ + zcs->inBuff + zcs->inBuffPos, toLoad, + ip, iend-ip); + zcs->inBuffPos += loaded; +- if (loaded != 0) +- ip += loaded; ++ if (ip) ip += loaded; + if ( (flushMode == ZSTD_e_continue) + && (zcs->inBuffPos < zcs->inBuffTarget) ) { + /* not enough input to fill full block : stop here */ +@@ -5274,6 +5818,20 @@ + /* empty */ + someMoreWork = 0; break; + } ++ } else { ++ assert(zcs->appliedParams.inBufferMode == ZSTD_bm_stable); ++ if ( (flushMode == ZSTD_e_continue) ++ && ( (size_t)(iend - ip) < zcs->blockSize) ) { ++ /* can't compress a full block : stop here */ ++ zcs->stableIn_notConsumed = (size_t)(iend - ip); ++ ip = iend; /* pretend to have consumed input */ ++ someMoreWork = 0; break; ++ } ++ if ( (flushMode == ZSTD_e_flush) ++ && (ip == iend) ) { ++ /* empty */ ++ someMoreWork = 0; break; ++ } + } + /* compress current block (note : this stage cannot be stopped in the middle) */ + DEBUGLOG(5, "stream compression stage (flushMode==%u)", flushMode); +@@ -5281,9 +5839,8 @@ + void* cDst; + size_t cSize; + size_t oSize = oend-op; +- size_t const iSize = inputBuffered +- ? zcs->inBuffPos - zcs->inToCompress +- : MIN((size_t)(iend - ip), zcs->blockSize); ++ size_t const iSize = inputBuffered ? zcs->inBuffPos - zcs->inToCompress ++ : MIN((size_t)(iend - ip), zcs->blockSize); + if (oSize >= ZSTD_compressBound(iSize) || zcs->appliedParams.outBufferMode == ZSTD_bm_stable) + cDst = op; /* compress into output buffer, to skip flush stage */ + else +@@ -5291,9 +5848,9 @@ + if (inputBuffered) { + unsigned const lastBlock = (flushMode == ZSTD_e_end) && (ip==iend); + cSize = lastBlock ? +- ZSTD_compressEnd(zcs, cDst, oSize, ++ ZSTD_compressEnd_public(zcs, cDst, oSize, + zcs->inBuff + zcs->inToCompress, iSize) : +- ZSTD_compressContinue(zcs, cDst, oSize, ++ ZSTD_compressContinue_public(zcs, cDst, oSize, + zcs->inBuff + zcs->inToCompress, iSize); + FORWARD_IF_ERROR(cSize, "%s", lastBlock ? "ZSTD_compressEnd failed" : "ZSTD_compressContinue failed"); + zcs->frameEnded = lastBlock; +@@ -5306,19 +5863,16 @@ + if (!lastBlock) + assert(zcs->inBuffTarget <= zcs->inBuffSize); + zcs->inToCompress = zcs->inBuffPos; +- } else { +- unsigned const lastBlock = (ip + iSize == iend); +- assert(flushMode == ZSTD_e_end /* Already validated */); ++ } else { /* !inputBuffered, hence ZSTD_bm_stable */ ++ unsigned const lastBlock = (flushMode == ZSTD_e_end) && (ip + iSize == iend); + cSize = lastBlock ? +- ZSTD_compressEnd(zcs, cDst, oSize, ip, iSize) : +- ZSTD_compressContinue(zcs, cDst, oSize, ip, iSize); ++ ZSTD_compressEnd_public(zcs, cDst, oSize, ip, iSize) : ++ ZSTD_compressContinue_public(zcs, cDst, oSize, ip, iSize); + /* Consume the input prior to error checking to mirror buffered mode. */ +- if (iSize > 0) +- ip += iSize; ++ if (ip) ip += iSize; + FORWARD_IF_ERROR(cSize, "%s", lastBlock ? "ZSTD_compressEnd failed" : "ZSTD_compressContinue failed"); + zcs->frameEnded = lastBlock; +- if (lastBlock) +- assert(ip == iend); ++ if (lastBlock) assert(ip == iend); + } + if (cDst == op) { /* no need to flush */ + op += cSize; +@@ -5388,8 +5942,10 @@ + /* After a compression call set the expected input/output buffer. + * This is validated at the start of the next compression call. + */ +-static void ZSTD_setBufferExpectations(ZSTD_CCtx* cctx, ZSTD_outBuffer const* output, ZSTD_inBuffer const* input) ++static void ++ZSTD_setBufferExpectations(ZSTD_CCtx* cctx, const ZSTD_outBuffer* output, const ZSTD_inBuffer* input) + { ++ DEBUGLOG(5, "ZSTD_setBufferExpectations (for advanced stable in/out modes)"); + if (cctx->appliedParams.inBufferMode == ZSTD_bm_stable) { + cctx->expectedInBuffer = *input; + } +@@ -5408,22 +5964,22 @@ + { + if (cctx->appliedParams.inBufferMode == ZSTD_bm_stable) { + ZSTD_inBuffer const expect = cctx->expectedInBuffer; +- if (expect.src != input->src || expect.pos != input->pos || expect.size != input->size) +- RETURN_ERROR(srcBuffer_wrong, "ZSTD_c_stableInBuffer enabled but input differs!"); +- if (endOp != ZSTD_e_end) +- RETURN_ERROR(srcBuffer_wrong, "ZSTD_c_stableInBuffer can only be used with ZSTD_e_end!"); ++ if (expect.src != input->src || expect.pos != input->pos) ++ RETURN_ERROR(stabilityCondition_notRespected, "ZSTD_c_stableInBuffer enabled but input differs!"); + } ++ (void)endOp; + if (cctx->appliedParams.outBufferMode == ZSTD_bm_stable) { + size_t const outBufferSize = output->size - output->pos; + if (cctx->expectedOutBufferSize != outBufferSize) +- RETURN_ERROR(dstBuffer_wrong, "ZSTD_c_stableOutBuffer enabled but output size differs!"); ++ RETURN_ERROR(stabilityCondition_notRespected, "ZSTD_c_stableOutBuffer enabled but output size differs!"); + } + return 0; + } + + static size_t ZSTD_CCtx_init_compressStream2(ZSTD_CCtx* cctx, + ZSTD_EndDirective endOp, +- size_t inSize) { ++ size_t inSize) ++{ + ZSTD_CCtx_params params = cctx->requestedParams; + ZSTD_prefixDict const prefixDict = cctx->prefixDict; + FORWARD_IF_ERROR( ZSTD_initLocalDict(cctx) , ""); /* Init the local dict if present. */ +@@ -5437,9 +5993,9 @@ + params.compressionLevel = cctx->cdict->compressionLevel; + } + DEBUGLOG(4, "ZSTD_compressStream2 : transparent init stage"); +- if (endOp == ZSTD_e_end) cctx->pledgedSrcSizePlusOne = inSize + 1; /* auto-fix pledgedSrcSize */ +- { +- size_t const dictSize = prefixDict.dict ++ if (endOp == ZSTD_e_end) cctx->pledgedSrcSizePlusOne = inSize + 1; /* auto-determine pledgedSrcSize */ ++ ++ { size_t const dictSize = prefixDict.dict + ? prefixDict.dictSize + : (cctx->cdict ? cctx->cdict->dictContentSize : 0); + ZSTD_cParamMode_e const mode = ZSTD_getCParamMode(cctx->cdict, ¶ms, cctx->pledgedSrcSizePlusOne - 1); +@@ -5451,6 +6007,9 @@ + params.useBlockSplitter = ZSTD_resolveBlockSplitterMode(params.useBlockSplitter, ¶ms.cParams); + params.ldmParams.enableLdm = ZSTD_resolveEnableLdm(params.ldmParams.enableLdm, ¶ms.cParams); + params.useRowMatchFinder = ZSTD_resolveRowMatchFinderMode(params.useRowMatchFinder, ¶ms.cParams); ++ params.validateSequences = ZSTD_resolveExternalSequenceValidation(params.validateSequences); ++ params.maxBlockSize = ZSTD_resolveMaxBlockSize(params.maxBlockSize); ++ params.searchForExternalRepcodes = ZSTD_resolveExternalRepcodeSearch(params.searchForExternalRepcodes, params.compressionLevel); + + { U64 const pledgedSrcSize = cctx->pledgedSrcSizePlusOne - 1; + assert(!ZSTD_isError(ZSTD_checkCParams(params.cParams))); +@@ -5477,6 +6036,8 @@ + return 0; + } + ++/* @return provides a minimum amount of data remaining to be flushed from internal buffers ++ */ + size_t ZSTD_compressStream2( ZSTD_CCtx* cctx, + ZSTD_outBuffer* output, + ZSTD_inBuffer* input, +@@ -5491,8 +6052,27 @@ + + /* transparent initialization stage */ + if (cctx->streamStage == zcss_init) { +- FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, endOp, input->size), "CompressStream2 initialization failed"); +- ZSTD_setBufferExpectations(cctx, output, input); /* Set initial buffer expectations now that we've initialized */ ++ size_t const inputSize = input->size - input->pos; /* no obligation to start from pos==0 */ ++ size_t const totalInputSize = inputSize + cctx->stableIn_notConsumed; ++ if ( (cctx->requestedParams.inBufferMode == ZSTD_bm_stable) /* input is presumed stable, across invocations */ ++ && (endOp == ZSTD_e_continue) /* no flush requested, more input to come */ ++ && (totalInputSize < ZSTD_BLOCKSIZE_MAX) ) { /* not even reached one block yet */ ++ if (cctx->stableIn_notConsumed) { /* not the first time */ ++ /* check stable source guarantees */ ++ RETURN_ERROR_IF(input->src != cctx->expectedInBuffer.src, stabilityCondition_notRespected, "stableInBuffer condition not respected: wrong src pointer"); ++ RETURN_ERROR_IF(input->pos != cctx->expectedInBuffer.size, stabilityCondition_notRespected, "stableInBuffer condition not respected: externally modified pos"); ++ } ++ /* pretend input was consumed, to give a sense forward progress */ ++ input->pos = input->size; ++ /* save stable inBuffer, for later control, and flush/end */ ++ cctx->expectedInBuffer = *input; ++ /* but actually input wasn't consumed, so keep track of position from where compression shall resume */ ++ cctx->stableIn_notConsumed += inputSize; ++ /* don't initialize yet, wait for the first block of flush() order, for better parameters adaptation */ ++ return ZSTD_FRAMEHEADERSIZE_MIN(cctx->requestedParams.format); /* at least some header to produce */ ++ } ++ FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, endOp, totalInputSize), "compressStream2 initialization failed"); ++ ZSTD_setBufferExpectations(cctx, output, input); /* Set initial buffer expectations now that we've initialized */ + } + /* end of transparent initialization stage */ + +@@ -5510,13 +6090,20 @@ + const void* src, size_t srcSize, size_t* srcPos, + ZSTD_EndDirective endOp) + { +- ZSTD_outBuffer output = { dst, dstCapacity, *dstPos }; +- ZSTD_inBuffer input = { src, srcSize, *srcPos }; ++ ZSTD_outBuffer output; ++ ZSTD_inBuffer input; ++ output.dst = dst; ++ output.size = dstCapacity; ++ output.pos = *dstPos; ++ input.src = src; ++ input.size = srcSize; ++ input.pos = *srcPos; + /* ZSTD_compressStream2() will check validity of dstPos and srcPos */ +- size_t const cErr = ZSTD_compressStream2(cctx, &output, &input, endOp); +- *dstPos = output.pos; +- *srcPos = input.pos; +- return cErr; ++ { size_t const cErr = ZSTD_compressStream2(cctx, &output, &input, endOp); ++ *dstPos = output.pos; ++ *srcPos = input.pos; ++ return cErr; ++ } + } + + size_t ZSTD_compress2(ZSTD_CCtx* cctx, +@@ -5539,6 +6126,7 @@ + /* Reset to the original values. */ + cctx->requestedParams.inBufferMode = originalInBufferMode; + cctx->requestedParams.outBufferMode = originalOutBufferMode; ++ + FORWARD_IF_ERROR(result, "ZSTD_compressStream2_simpleArgs failed"); + if (result != 0) { /* compression not completed, due to lack of output space */ + assert(oPos == dstCapacity); +@@ -5549,64 +6137,61 @@ + } + } + +-typedef struct { +- U32 idx; /* Index in array of ZSTD_Sequence */ +- U32 posInSequence; /* Position within sequence at idx */ +- size_t posInSrc; /* Number of bytes given by sequences provided so far */ +-} ZSTD_sequencePosition; +- + /* ZSTD_validateSequence() : + * @offCode : is presumed to follow format required by ZSTD_storeSeq() + * @returns a ZSTD error code if sequence is not valid + */ + static size_t +-ZSTD_validateSequence(U32 offCode, U32 matchLength, +- size_t posInSrc, U32 windowLog, size_t dictSize) ++ZSTD_validateSequence(U32 offCode, U32 matchLength, U32 minMatch, ++ size_t posInSrc, U32 windowLog, size_t dictSize, int useSequenceProducer) + { +- U32 const windowSize = 1 << windowLog; ++ U32 const windowSize = 1u << windowLog; + /* posInSrc represents the amount of data the decoder would decode up to this point. + * As long as the amount of data decoded is less than or equal to window size, offsets may be + * larger than the total length of output decoded in order to reference the dict, even larger than + * window size. After output surpasses windowSize, we're limited to windowSize offsets again. + */ + size_t const offsetBound = posInSrc > windowSize ? (size_t)windowSize : posInSrc + (size_t)dictSize; +- RETURN_ERROR_IF(offCode > STORE_OFFSET(offsetBound), corruption_detected, "Offset too large!"); +- RETURN_ERROR_IF(matchLength < MINMATCH, corruption_detected, "Matchlength too small"); ++ size_t const matchLenLowerBound = (minMatch == 3 || useSequenceProducer) ? 3 : 4; ++ RETURN_ERROR_IF(offCode > OFFSET_TO_OFFBASE(offsetBound), externalSequences_invalid, "Offset too large!"); ++ /* Validate maxNbSeq is large enough for the given matchLength and minMatch */ ++ RETURN_ERROR_IF(matchLength < matchLenLowerBound, externalSequences_invalid, "Matchlength too small for the minMatch"); + return 0; + } + + /* Returns an offset code, given a sequence's raw offset, the ongoing repcode array, and whether litLength == 0 */ +-static U32 ZSTD_finalizeOffCode(U32 rawOffset, const U32 rep[ZSTD_REP_NUM], U32 ll0) ++static U32 ZSTD_finalizeOffBase(U32 rawOffset, const U32 rep[ZSTD_REP_NUM], U32 ll0) + { +- U32 offCode = STORE_OFFSET(rawOffset); ++ U32 offBase = OFFSET_TO_OFFBASE(rawOffset); + + if (!ll0 && rawOffset == rep[0]) { +- offCode = STORE_REPCODE_1; ++ offBase = REPCODE1_TO_OFFBASE; + } else if (rawOffset == rep[1]) { +- offCode = STORE_REPCODE(2 - ll0); ++ offBase = REPCODE_TO_OFFBASE(2 - ll0); + } else if (rawOffset == rep[2]) { +- offCode = STORE_REPCODE(3 - ll0); ++ offBase = REPCODE_TO_OFFBASE(3 - ll0); + } else if (ll0 && rawOffset == rep[0] - 1) { +- offCode = STORE_REPCODE_3; ++ offBase = REPCODE3_TO_OFFBASE; + } +- return offCode; ++ return offBase; + } + +-/* Returns 0 on success, and a ZSTD_error otherwise. This function scans through an array of +- * ZSTD_Sequence, storing the sequences it finds, until it reaches a block delimiter. +- */ +-static size_t ++size_t + ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, + ZSTD_sequencePosition* seqPos, + const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, +- const void* src, size_t blockSize) ++ const void* src, size_t blockSize, ++ ZSTD_paramSwitch_e externalRepSearch) + { + U32 idx = seqPos->idx; ++ U32 const startIdx = idx; + BYTE const* ip = (BYTE const*)(src); + const BYTE* const iend = ip + blockSize; + repcodes_t updatedRepcodes; + U32 dictSize; + ++ DEBUGLOG(5, "ZSTD_copySequencesToSeqStoreExplicitBlockDelim (blockSize = %zu)", blockSize); ++ + if (cctx->cdict) { + dictSize = (U32)cctx->cdict->dictContentSize; + } else if (cctx->prefixDict.dict) { +@@ -5615,25 +6200,55 @@ + dictSize = 0; + } + ZSTD_memcpy(updatedRepcodes.rep, cctx->blockState.prevCBlock->rep, sizeof(repcodes_t)); +- for (; (inSeqs[idx].matchLength != 0 || inSeqs[idx].offset != 0) && idx < inSeqsSize; ++idx) { ++ for (; idx < inSeqsSize && (inSeqs[idx].matchLength != 0 || inSeqs[idx].offset != 0); ++idx) { + U32 const litLength = inSeqs[idx].litLength; +- U32 const ll0 = (litLength == 0); + U32 const matchLength = inSeqs[idx].matchLength; +- U32 const offCode = ZSTD_finalizeOffCode(inSeqs[idx].offset, updatedRepcodes.rep, ll0); +- ZSTD_updateRep(updatedRepcodes.rep, offCode, ll0); ++ U32 offBase; ++ ++ if (externalRepSearch == ZSTD_ps_disable) { ++ offBase = OFFSET_TO_OFFBASE(inSeqs[idx].offset); ++ } else { ++ U32 const ll0 = (litLength == 0); ++ offBase = ZSTD_finalizeOffBase(inSeqs[idx].offset, updatedRepcodes.rep, ll0); ++ ZSTD_updateRep(updatedRepcodes.rep, offBase, ll0); ++ } + +- DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offCode, matchLength, litLength); ++ DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offBase, matchLength, litLength); + if (cctx->appliedParams.validateSequences) { + seqPos->posInSrc += litLength + matchLength; +- FORWARD_IF_ERROR(ZSTD_validateSequence(offCode, matchLength, seqPos->posInSrc, +- cctx->appliedParams.cParams.windowLog, dictSize), ++ FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, ++ cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useSequenceProducer), + "Sequence validation failed"); + } +- RETURN_ERROR_IF(idx - seqPos->idx > cctx->seqStore.maxNbSeq, memory_allocation, ++ RETURN_ERROR_IF(idx - seqPos->idx >= cctx->seqStore.maxNbSeq, externalSequences_invalid, + "Not enough memory allocated. Try adjusting ZSTD_c_minMatch."); +- ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offCode, matchLength); ++ ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offBase, matchLength); + ip += matchLength + litLength; + } ++ ++ /* If we skipped repcode search while parsing, we need to update repcodes now */ ++ assert(externalRepSearch != ZSTD_ps_auto); ++ assert(idx >= startIdx); ++ if (externalRepSearch == ZSTD_ps_disable && idx != startIdx) { ++ U32* const rep = updatedRepcodes.rep; ++ U32 lastSeqIdx = idx - 1; /* index of last non-block-delimiter sequence */ ++ ++ if (lastSeqIdx >= startIdx + 2) { ++ rep[2] = inSeqs[lastSeqIdx - 2].offset; ++ rep[1] = inSeqs[lastSeqIdx - 1].offset; ++ rep[0] = inSeqs[lastSeqIdx].offset; ++ } else if (lastSeqIdx == startIdx + 1) { ++ rep[2] = rep[0]; ++ rep[1] = inSeqs[lastSeqIdx - 1].offset; ++ rep[0] = inSeqs[lastSeqIdx].offset; ++ } else { ++ assert(lastSeqIdx == startIdx); ++ rep[2] = rep[1]; ++ rep[1] = rep[0]; ++ rep[0] = inSeqs[lastSeqIdx].offset; ++ } ++ } ++ + ZSTD_memcpy(cctx->blockState.nextCBlock->rep, updatedRepcodes.rep, sizeof(repcodes_t)); + + if (inSeqs[idx].litLength) { +@@ -5642,26 +6257,15 @@ + ip += inSeqs[idx].litLength; + seqPos->posInSrc += inSeqs[idx].litLength; + } +- RETURN_ERROR_IF(ip != iend, corruption_detected, "Blocksize doesn't agree with block delimiter!"); ++ RETURN_ERROR_IF(ip != iend, externalSequences_invalid, "Blocksize doesn't agree with block delimiter!"); + seqPos->idx = idx+1; + return 0; + } + +-/* Returns the number of bytes to move the current read position back by. Only non-zero +- * if we ended up splitting a sequence. Otherwise, it may return a ZSTD error if something +- * went wrong. +- * +- * This function will attempt to scan through blockSize bytes represented by the sequences +- * in inSeqs, storing any (partial) sequences. +- * +- * Occasionally, we may want to change the actual number of bytes we consumed from inSeqs to +- * avoid splitting a match, or to avoid splitting a match such that it would produce a match +- * smaller than MINMATCH. In this case, we return the number of bytes that we didn't read from this block. +- */ +-static size_t ++size_t + ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, + const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, +- const void* src, size_t blockSize) ++ const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch) + { + U32 idx = seqPos->idx; + U32 startPosInSequence = seqPos->posInSequence; +@@ -5673,6 +6277,9 @@ + U32 bytesAdjustment = 0; + U32 finalMatchSplit = 0; + ++ /* TODO(embg) support fast parsing mode in noBlockDelim mode */ ++ (void)externalRepSearch; ++ + if (cctx->cdict) { + dictSize = cctx->cdict->dictContentSize; + } else if (cctx->prefixDict.dict) { +@@ -5680,7 +6287,7 @@ + } else { + dictSize = 0; + } +- DEBUGLOG(5, "ZSTD_copySequencesToSeqStore: idx: %u PIS: %u blockSize: %zu", idx, startPosInSequence, blockSize); ++ DEBUGLOG(5, "ZSTD_copySequencesToSeqStoreNoBlockDelim: idx: %u PIS: %u blockSize: %zu", idx, startPosInSequence, blockSize); + DEBUGLOG(5, "Start seq: idx: %u (of: %u ml: %u ll: %u)", idx, inSeqs[idx].offset, inSeqs[idx].matchLength, inSeqs[idx].litLength); + ZSTD_memcpy(updatedRepcodes.rep, cctx->blockState.prevCBlock->rep, sizeof(repcodes_t)); + while (endPosInSequence && idx < inSeqsSize && !finalMatchSplit) { +@@ -5688,7 +6295,7 @@ + U32 litLength = currSeq.litLength; + U32 matchLength = currSeq.matchLength; + U32 const rawOffset = currSeq.offset; +- U32 offCode; ++ U32 offBase; + + /* Modify the sequence depending on where endPosInSequence lies */ + if (endPosInSequence >= currSeq.litLength + currSeq.matchLength) { +@@ -5702,7 +6309,6 @@ + /* Move to the next sequence */ + endPosInSequence -= currSeq.litLength + currSeq.matchLength; + startPosInSequence = 0; +- idx++; + } else { + /* This is the final (partial) sequence we're adding from inSeqs, and endPosInSequence + does not reach the end of the match. So, we have to split the sequence */ +@@ -5742,21 +6348,23 @@ + } + /* Check if this offset can be represented with a repcode */ + { U32 const ll0 = (litLength == 0); +- offCode = ZSTD_finalizeOffCode(rawOffset, updatedRepcodes.rep, ll0); +- ZSTD_updateRep(updatedRepcodes.rep, offCode, ll0); ++ offBase = ZSTD_finalizeOffBase(rawOffset, updatedRepcodes.rep, ll0); ++ ZSTD_updateRep(updatedRepcodes.rep, offBase, ll0); + } + + if (cctx->appliedParams.validateSequences) { + seqPos->posInSrc += litLength + matchLength; +- FORWARD_IF_ERROR(ZSTD_validateSequence(offCode, matchLength, seqPos->posInSrc, +- cctx->appliedParams.cParams.windowLog, dictSize), ++ FORWARD_IF_ERROR(ZSTD_validateSequence(offBase, matchLength, cctx->appliedParams.cParams.minMatch, seqPos->posInSrc, ++ cctx->appliedParams.cParams.windowLog, dictSize, cctx->appliedParams.useSequenceProducer), + "Sequence validation failed"); + } +- DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offCode, matchLength, litLength); +- RETURN_ERROR_IF(idx - seqPos->idx > cctx->seqStore.maxNbSeq, memory_allocation, ++ DEBUGLOG(6, "Storing sequence: (of: %u, ml: %u, ll: %u)", offBase, matchLength, litLength); ++ RETURN_ERROR_IF(idx - seqPos->idx >= cctx->seqStore.maxNbSeq, externalSequences_invalid, + "Not enough memory allocated. Try adjusting ZSTD_c_minMatch."); +- ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offCode, matchLength); ++ ZSTD_storeSeq(&cctx->seqStore, litLength, ip, iend, offBase, matchLength); + ip += matchLength + litLength; ++ if (!finalMatchSplit) ++ idx++; /* Next Sequence */ + } + DEBUGLOG(5, "Ending seq: idx: %u (of: %u ml: %u ll: %u)", idx, inSeqs[idx].offset, inSeqs[idx].matchLength, inSeqs[idx].litLength); + assert(idx == inSeqsSize || endPosInSequence <= inSeqs[idx].litLength + inSeqs[idx].matchLength); +@@ -5779,7 +6387,7 @@ + + typedef size_t (*ZSTD_sequenceCopier) (ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, + const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, +- const void* src, size_t blockSize); ++ const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch); + static ZSTD_sequenceCopier ZSTD_selectSequenceCopier(ZSTD_sequenceFormat_e mode) + { + ZSTD_sequenceCopier sequenceCopier = NULL; +@@ -5793,6 +6401,57 @@ + return sequenceCopier; + } + ++/* Discover the size of next block by searching for the delimiter. ++ * Note that a block delimiter **must** exist in this mode, ++ * otherwise it's an input error. ++ * The block size retrieved will be later compared to ensure it remains within bounds */ ++static size_t ++blockSize_explicitDelimiter(const ZSTD_Sequence* inSeqs, size_t inSeqsSize, ZSTD_sequencePosition seqPos) ++{ ++ int end = 0; ++ size_t blockSize = 0; ++ size_t spos = seqPos.idx; ++ DEBUGLOG(6, "blockSize_explicitDelimiter : seq %zu / %zu", spos, inSeqsSize); ++ assert(spos <= inSeqsSize); ++ while (spos < inSeqsSize) { ++ end = (inSeqs[spos].offset == 0); ++ blockSize += inSeqs[spos].litLength + inSeqs[spos].matchLength; ++ if (end) { ++ if (inSeqs[spos].matchLength != 0) ++ RETURN_ERROR(externalSequences_invalid, "delimiter format error : both matchlength and offset must be == 0"); ++ break; ++ } ++ spos++; ++ } ++ if (!end) ++ RETURN_ERROR(externalSequences_invalid, "Reached end of sequences without finding a block delimiter"); ++ return blockSize; ++} ++ ++/* More a "target" block size */ ++static size_t blockSize_noDelimiter(size_t blockSize, size_t remaining) ++{ ++ int const lastBlock = (remaining <= blockSize); ++ return lastBlock ? remaining : blockSize; ++} ++ ++static size_t determine_blockSize(ZSTD_sequenceFormat_e mode, ++ size_t blockSize, size_t remaining, ++ const ZSTD_Sequence* inSeqs, size_t inSeqsSize, ZSTD_sequencePosition seqPos) ++{ ++ DEBUGLOG(6, "determine_blockSize : remainingSize = %zu", remaining); ++ if (mode == ZSTD_sf_noBlockDelimiters) ++ return blockSize_noDelimiter(blockSize, remaining); ++ { size_t const explicitBlockSize = blockSize_explicitDelimiter(inSeqs, inSeqsSize, seqPos); ++ FORWARD_IF_ERROR(explicitBlockSize, "Error while determining block size with explicit delimiters"); ++ if (explicitBlockSize > blockSize) ++ RETURN_ERROR(externalSequences_invalid, "sequences incorrectly define a too large block"); ++ if (explicitBlockSize > remaining) ++ RETURN_ERROR(externalSequences_invalid, "sequences define a frame longer than source"); ++ return explicitBlockSize; ++ } ++} ++ + /* Compress, block-by-block, all of the sequences given. + * + * Returns the cumulative size of all compressed blocks (including their headers), +@@ -5805,9 +6464,6 @@ + const void* src, size_t srcSize) + { + size_t cSize = 0; +- U32 lastBlock; +- size_t blockSize; +- size_t compressedSeqsSize; + size_t remaining = srcSize; + ZSTD_sequencePosition seqPos = {0, 0, 0}; + +@@ -5827,22 +6483,29 @@ + } + + while (remaining) { ++ size_t compressedSeqsSize; + size_t cBlockSize; + size_t additionalByteAdjustment; +- lastBlock = remaining <= cctx->blockSize; +- blockSize = lastBlock ? (U32)remaining : (U32)cctx->blockSize; ++ size_t blockSize = determine_blockSize(cctx->appliedParams.blockDelimiters, ++ cctx->blockSize, remaining, ++ inSeqs, inSeqsSize, seqPos); ++ U32 const lastBlock = (blockSize == remaining); ++ FORWARD_IF_ERROR(blockSize, "Error while trying to determine block size"); ++ assert(blockSize <= remaining); + ZSTD_resetSeqStore(&cctx->seqStore); +- DEBUGLOG(4, "Working on new block. Blocksize: %zu", blockSize); ++ DEBUGLOG(5, "Working on new block. Blocksize: %zu (total:%zu)", blockSize, (ip - (const BYTE*)src) + blockSize); + +- additionalByteAdjustment = sequenceCopier(cctx, &seqPos, inSeqs, inSeqsSize, ip, blockSize); ++ additionalByteAdjustment = sequenceCopier(cctx, &seqPos, inSeqs, inSeqsSize, ip, blockSize, cctx->appliedParams.searchForExternalRepcodes); + FORWARD_IF_ERROR(additionalByteAdjustment, "Bad sequence copy"); + blockSize -= additionalByteAdjustment; + + /* If blocks are too small, emit as a nocompress block */ +- if (blockSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1) { ++ /* TODO: See 3090. We reduced MIN_CBLOCK_SIZE from 3 to 2 so to compensate we are adding ++ * additional 1. We need to revisit and change this logic to be more consistent */ ++ if (blockSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1+1) { + cBlockSize = ZSTD_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); + FORWARD_IF_ERROR(cBlockSize, "Nocompress block failed"); +- DEBUGLOG(4, "Block too small, writing out nocompress block: cSize: %zu", cBlockSize); ++ DEBUGLOG(5, "Block too small, writing out nocompress block: cSize: %zu", cBlockSize); + cSize += cBlockSize; + ip += blockSize; + op += cBlockSize; +@@ -5851,6 +6514,7 @@ + continue; + } + ++ RETURN_ERROR_IF(dstCapacity < ZSTD_blockHeaderSize, dstSize_tooSmall, "not enough dstCapacity to write a new compressed block"); + compressedSeqsSize = ZSTD_entropyCompressSeqStore(&cctx->seqStore, + &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, + &cctx->appliedParams, +@@ -5859,11 +6523,11 @@ + cctx->entropyWorkspace, ENTROPY_WORKSPACE_SIZE /* statically allocated in resetCCtx */, + cctx->bmi2); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); +- DEBUGLOG(4, "Compressed sequences size: %zu", compressedSeqsSize); ++ DEBUGLOG(5, "Compressed sequences size: %zu", compressedSeqsSize); + + if (!cctx->isFirstBlock && + ZSTD_maybeRLE(&cctx->seqStore) && +- ZSTD_isRLE((BYTE const*)src, srcSize)) { ++ ZSTD_isRLE(ip, blockSize)) { + /* We don't want to emit our first block as a RLE even if it qualifies because + * doing so will cause the decoder (cli only) to throw a "should consume all input error." + * This is only an issue for zstd <= v1.4.3 +@@ -5874,12 +6538,12 @@ + if (compressedSeqsSize == 0) { + /* ZSTD_noCompressBlock writes the block header as well */ + cBlockSize = ZSTD_noCompressBlock(op, dstCapacity, ip, blockSize, lastBlock); +- FORWARD_IF_ERROR(cBlockSize, "Nocompress block failed"); +- DEBUGLOG(4, "Writing out nocompress block, size: %zu", cBlockSize); ++ FORWARD_IF_ERROR(cBlockSize, "ZSTD_noCompressBlock failed"); ++ DEBUGLOG(5, "Writing out nocompress block, size: %zu", cBlockSize); + } else if (compressedSeqsSize == 1) { + cBlockSize = ZSTD_rleCompressBlock(op, dstCapacity, *ip, blockSize, lastBlock); +- FORWARD_IF_ERROR(cBlockSize, "RLE compress block failed"); +- DEBUGLOG(4, "Writing out RLE block, size: %zu", cBlockSize); ++ FORWARD_IF_ERROR(cBlockSize, "ZSTD_rleCompressBlock failed"); ++ DEBUGLOG(5, "Writing out RLE block, size: %zu", cBlockSize); + } else { + U32 cBlockHeader; + /* Error checking and repcodes update */ +@@ -5891,11 +6555,10 @@ + cBlockHeader = lastBlock + (((U32)bt_compressed)<<1) + (U32)(compressedSeqsSize << 3); + MEM_writeLE24(op, cBlockHeader); + cBlockSize = ZSTD_blockHeaderSize + compressedSeqsSize; +- DEBUGLOG(4, "Writing out compressed block, size: %zu", cBlockSize); ++ DEBUGLOG(5, "Writing out compressed block, size: %zu", cBlockSize); + } + + cSize += cBlockSize; +- DEBUGLOG(4, "cSize running total: %zu", cSize); + + if (lastBlock) { + break; +@@ -5906,12 +6569,15 @@ + dstCapacity -= cBlockSize; + cctx->isFirstBlock = 0; + } ++ DEBUGLOG(5, "cSize running total: %zu (remaining dstCapacity=%zu)", cSize, dstCapacity); + } + ++ DEBUGLOG(4, "cSize final total: %zu", cSize); + return cSize; + } + +-size_t ZSTD_compressSequences(ZSTD_CCtx* const cctx, void* dst, size_t dstCapacity, ++size_t ZSTD_compressSequences(ZSTD_CCtx* cctx, ++ void* dst, size_t dstCapacity, + const ZSTD_Sequence* inSeqs, size_t inSeqsSize, + const void* src, size_t srcSize) + { +@@ -5921,7 +6587,7 @@ + size_t frameHeaderSize = 0; + + /* Transparent initialization stage, same as compressStream2() */ +- DEBUGLOG(3, "ZSTD_compressSequences()"); ++ DEBUGLOG(4, "ZSTD_compressSequences (dstCapacity=%zu)", dstCapacity); + assert(cctx != NULL); + FORWARD_IF_ERROR(ZSTD_CCtx_init_compressStream2(cctx, ZSTD_e_end, srcSize), "CCtx initialization failed"); + /* Begin writing output, starting with frame header */ +@@ -5949,26 +6615,34 @@ + cSize += 4; + } + +- DEBUGLOG(3, "Final compressed size: %zu", cSize); ++ DEBUGLOG(4, "Final compressed size: %zu", cSize); + return cSize; + } + + /*====== Finalize ======*/ + ++static ZSTD_inBuffer inBuffer_forEndFlush(const ZSTD_CStream* zcs) ++{ ++ const ZSTD_inBuffer nullInput = { NULL, 0, 0 }; ++ const int stableInput = (zcs->appliedParams.inBufferMode == ZSTD_bm_stable); ++ return stableInput ? zcs->expectedInBuffer : nullInput; ++} ++ + /*! ZSTD_flushStream() : + * @return : amount of data remaining to flush */ + size_t ZSTD_flushStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output) + { +- ZSTD_inBuffer input = { NULL, 0, 0 }; ++ ZSTD_inBuffer input = inBuffer_forEndFlush(zcs); ++ input.size = input.pos; /* do not ingest more input during flush */ + return ZSTD_compressStream2(zcs, output, &input, ZSTD_e_flush); + } + + + size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output) + { +- ZSTD_inBuffer input = { NULL, 0, 0 }; ++ ZSTD_inBuffer input = inBuffer_forEndFlush(zcs); + size_t const remainingToFlush = ZSTD_compressStream2(zcs, output, &input, ZSTD_e_end); +- FORWARD_IF_ERROR( remainingToFlush , "ZSTD_compressStream2 failed"); ++ FORWARD_IF_ERROR(remainingToFlush , "ZSTD_compressStream2(,,ZSTD_e_end) failed"); + if (zcs->appliedParams.nbWorkers > 0) return remainingToFlush; /* minimal estimation */ + /* single thread mode : attempt to calculate remaining to flush more precisely */ + { size_t const lastBlockSize = zcs->frameEnded ? 0 : ZSTD_BLOCKHEADERSIZE; +@@ -6090,7 +6764,7 @@ + cp.targetLength = (unsigned)(-clampedCompressionLevel); + } + /* refine parameters based on srcSize & dictSize */ +- return ZSTD_adjustCParams_internal(cp, srcSizeHint, dictSize, mode); ++ return ZSTD_adjustCParams_internal(cp, srcSizeHint, dictSize, mode, ZSTD_ps_auto); + } + } + +@@ -6125,3 +6799,21 @@ + if (srcSizeHint == 0) srcSizeHint = ZSTD_CONTENTSIZE_UNKNOWN; + return ZSTD_getParams_internal(compressionLevel, srcSizeHint, dictSize, ZSTD_cpm_unknown); + } ++ ++void ZSTD_registerSequenceProducer( ++ ZSTD_CCtx* zc, void* mState, ++ ZSTD_sequenceProducer_F* mFinder ++) { ++ if (mFinder != NULL) { ++ ZSTD_externalMatchCtx emctx; ++ emctx.mState = mState; ++ emctx.mFinder = mFinder; ++ emctx.seqBuffer = NULL; ++ emctx.seqBufferCapacity = 0; ++ zc->externalMatchCtx = emctx; ++ zc->requestedParams.useSequenceProducer = 1; ++ } else { ++ ZSTD_memset(&zc->externalMatchCtx, 0, sizeof(zc->externalMatchCtx)); ++ zc->requestedParams.useSequenceProducer = 0; ++ } ++} +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress_internal.h b/lib/zstd/compress/zstd_compress_internal.h +--- a/lib/zstd/compress/zstd_compress_internal.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress_internal.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -20,6 +21,7 @@ + ***************************************/ + #include "../common/zstd_internal.h" + #include "zstd_cwksp.h" ++#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_NbCommonBytes */ + + + /*-************************************* +@@ -111,12 +113,13 @@ + /* ZSTD_buildBlockEntropyStats() : + * Builds entropy for the block. + * @return : 0 on success or error code */ +-size_t ZSTD_buildBlockEntropyStats(seqStore_t* seqStorePtr, +- const ZSTD_entropyCTables_t* prevEntropy, +- ZSTD_entropyCTables_t* nextEntropy, +- const ZSTD_CCtx_params* cctxParams, +- ZSTD_entropyCTablesMetadata_t* entropyMetadata, +- void* workspace, size_t wkspSize); ++size_t ZSTD_buildBlockEntropyStats( ++ const seqStore_t* seqStorePtr, ++ const ZSTD_entropyCTables_t* prevEntropy, ++ ZSTD_entropyCTables_t* nextEntropy, ++ const ZSTD_CCtx_params* cctxParams, ++ ZSTD_entropyCTablesMetadata_t* entropyMetadata, ++ void* workspace, size_t wkspSize); + + /* ******************************* + * Compression internals structs * +@@ -142,6 +145,12 @@ + size_t capacity; /* The capacity starting from `seq` pointer */ + } rawSeqStore_t; + ++typedef struct { ++ U32 idx; /* Index in array of ZSTD_Sequence */ ++ U32 posInSequence; /* Position within sequence at idx */ ++ size_t posInSrc; /* Number of bytes given by sequences provided so far */ ++} ZSTD_sequencePosition; ++ + UNUSED_ATTR static const rawSeqStore_t kNullRawSeqStore = {NULL, 0, 0, 0, 0}; + + typedef struct { +@@ -212,8 +221,10 @@ + U32 hashLog3; /* dispatch table for matches of len==3 : larger == faster, more memory */ + + U32 rowHashLog; /* For row-based matchfinder: Hashlog based on nb of rows in the hashTable.*/ +- U16* tagTable; /* For row-based matchFinder: A row-based table containing the hashes and head index. */ ++ BYTE* tagTable; /* For row-based matchFinder: A row-based table containing the hashes and head index. */ + U32 hashCache[ZSTD_ROW_HASH_CACHE_SIZE]; /* For row-based matchFinder: a cache of hashes to improve speed */ ++ U64 hashSalt; /* For row-based matchFinder: salts the hash for re-use of tag table */ ++ U32 hashSaltEntropy; /* For row-based matchFinder: collects entropy for salt generation */ + + U32* hashTable; + U32* hashTable3; +@@ -228,6 +239,18 @@ + const ZSTD_matchState_t* dictMatchState; + ZSTD_compressionParameters cParams; + const rawSeqStore_t* ldmSeqStore; ++ ++ /* Controls prefetching in some dictMatchState matchfinders. ++ * This behavior is controlled from the cctx ms. ++ * This parameter has no effect in the cdict ms. */ ++ int prefetchCDictTables; ++ ++ /* When == 0, lazy match finders insert every position. ++ * When != 0, lazy match finders only insert positions they search. ++ * This allows them to skip much faster over incompressible data, ++ * at a small cost to compression ratio. ++ */ ++ int lazySkipping; + }; + + typedef struct { +@@ -324,6 +347,24 @@ + + /* Internal use, for createCCtxParams() and freeCCtxParams() only */ + ZSTD_customMem customMem; ++ ++ /* Controls prefetching in some dictMatchState matchfinders */ ++ ZSTD_paramSwitch_e prefetchCDictTables; ++ ++ /* Controls whether zstd will fall back to an internal matchfinder ++ * if the external matchfinder returns an error code. */ ++ int enableMatchFinderFallback; ++ ++ /* Indicates whether an external matchfinder has been referenced. ++ * Users can't set this externally. ++ * It is set internally in ZSTD_registerSequenceProducer(). */ ++ int useSequenceProducer; ++ ++ /* Adjust the max block size*/ ++ size_t maxBlockSize; ++ ++ /* Controls repcode search in external sequence parsing */ ++ ZSTD_paramSwitch_e searchForExternalRepcodes; + }; /* typedef'd to ZSTD_CCtx_params within "zstd.h" */ + + #define COMPRESS_SEQUENCES_WORKSPACE_SIZE (sizeof(unsigned) * (MaxSeq + 2)) +@@ -355,6 +396,14 @@ + ZSTD_entropyCTablesMetadata_t entropyMetadata; + } ZSTD_blockSplitCtx; + ++/* Context for block-level external matchfinder API */ ++typedef struct { ++ void* mState; ++ ZSTD_sequenceProducer_F* mFinder; ++ ZSTD_Sequence* seqBuffer; ++ size_t seqBufferCapacity; ++} ZSTD_externalMatchCtx; ++ + struct ZSTD_CCtx_s { + ZSTD_compressionStage_e stage; + int cParamsChanged; /* == 1 if cParams(except wlog) or compression level are changed in requestedParams. Triggers transmission of new params to ZSTDMT (if available) then reset to 0. */ +@@ -404,6 +453,7 @@ + + /* Stable in/out buffer verification */ + ZSTD_inBuffer expectedInBuffer; ++ size_t stableIn_notConsumed; /* nb bytes within stable input buffer that are said to be consumed but are not */ + size_t expectedOutBufferSize; + + /* Dictionary */ +@@ -417,9 +467,13 @@ + + /* Workspace for block splitter */ + ZSTD_blockSplitCtx blockSplitCtx; ++ ++ /* Workspace for external matchfinder */ ++ ZSTD_externalMatchCtx externalMatchCtx; + }; + + typedef enum { ZSTD_dtlm_fast, ZSTD_dtlm_full } ZSTD_dictTableLoadMethod_e; ++typedef enum { ZSTD_tfp_forCCtx, ZSTD_tfp_forCDict } ZSTD_tableFillPurpose_e; + + typedef enum { + ZSTD_noDict = 0, +@@ -441,7 +495,7 @@ + * In this mode we take both the source size and the dictionary size + * into account when selecting and adjusting the parameters. + */ +- ZSTD_cpm_unknown = 3, /* ZSTD_getCParams, ZSTD_getParams, ZSTD_adjustParams. ++ ZSTD_cpm_unknown = 3 /* ZSTD_getCParams, ZSTD_getParams, ZSTD_adjustParams. + * We don't know what these parameters are for. We default to the legacy + * behavior of taking both the source size and the dict size into account + * when selecting and adjusting parameters. +@@ -500,9 +554,11 @@ + /* ZSTD_noCompressBlock() : + * Writes uncompressed block to dst buffer from given src. + * Returns the size of the block */ +-MEM_STATIC size_t ZSTD_noCompressBlock (void* dst, size_t dstCapacity, const void* src, size_t srcSize, U32 lastBlock) ++MEM_STATIC size_t ++ZSTD_noCompressBlock(void* dst, size_t dstCapacity, const void* src, size_t srcSize, U32 lastBlock) + { + U32 const cBlockHeader24 = lastBlock + (((U32)bt_raw)<<1) + (U32)(srcSize << 3); ++ DEBUGLOG(5, "ZSTD_noCompressBlock (srcSize=%zu, dstCapacity=%zu)", srcSize, dstCapacity); + RETURN_ERROR_IF(srcSize + ZSTD_blockHeaderSize > dstCapacity, + dstSize_tooSmall, "dst buf too small for uncompressed block"); + MEM_writeLE24(dst, cBlockHeader24); +@@ -510,7 +566,8 @@ + return ZSTD_blockHeaderSize + srcSize; + } + +-MEM_STATIC size_t ZSTD_rleCompressBlock (void* dst, size_t dstCapacity, BYTE src, size_t srcSize, U32 lastBlock) ++MEM_STATIC size_t ++ZSTD_rleCompressBlock(void* dst, size_t dstCapacity, BYTE src, size_t srcSize, U32 lastBlock) + { + BYTE* const op = (BYTE*)dst; + U32 const cBlockHeader = lastBlock + (((U32)bt_rle)<<1) + (U32)(srcSize << 3); +@@ -529,7 +586,7 @@ + { + U32 const minlog = (strat>=ZSTD_btultra) ? (U32)(strat) - 1 : 6; + ZSTD_STATIC_ASSERT(ZSTD_btultra == 8); +- assert(ZSTD_cParam_withinBounds(ZSTD_c_strategy, strat)); ++ assert(ZSTD_cParam_withinBounds(ZSTD_c_strategy, (int)strat)); + return (srcSize >> minlog) + 2; + } + +@@ -565,29 +622,27 @@ + while (ip < iend) *op++ = *ip++; + } + +-#define ZSTD_REP_MOVE (ZSTD_REP_NUM-1) +-#define STORE_REPCODE_1 STORE_REPCODE(1) +-#define STORE_REPCODE_2 STORE_REPCODE(2) +-#define STORE_REPCODE_3 STORE_REPCODE(3) +-#define STORE_REPCODE(r) (assert((r)>=1), assert((r)<=3), (r)-1) +-#define STORE_OFFSET(o) (assert((o)>0), o + ZSTD_REP_MOVE) +-#define STORED_IS_OFFSET(o) ((o) > ZSTD_REP_MOVE) +-#define STORED_IS_REPCODE(o) ((o) <= ZSTD_REP_MOVE) +-#define STORED_OFFSET(o) (assert(STORED_IS_OFFSET(o)), (o)-ZSTD_REP_MOVE) +-#define STORED_REPCODE(o) (assert(STORED_IS_REPCODE(o)), (o)+1) /* returns ID 1,2,3 */ +-#define STORED_TO_OFFBASE(o) ((o)+1) +-#define OFFBASE_TO_STORED(o) ((o)-1) ++ ++#define REPCODE1_TO_OFFBASE REPCODE_TO_OFFBASE(1) ++#define REPCODE2_TO_OFFBASE REPCODE_TO_OFFBASE(2) ++#define REPCODE3_TO_OFFBASE REPCODE_TO_OFFBASE(3) ++#define REPCODE_TO_OFFBASE(r) (assert((r)>=1), assert((r)<=ZSTD_REP_NUM), (r)) /* accepts IDs 1,2,3 */ ++#define OFFSET_TO_OFFBASE(o) (assert((o)>0), o + ZSTD_REP_NUM) ++#define OFFBASE_IS_OFFSET(o) ((o) > ZSTD_REP_NUM) ++#define OFFBASE_IS_REPCODE(o) ( 1 <= (o) && (o) <= ZSTD_REP_NUM) ++#define OFFBASE_TO_OFFSET(o) (assert(OFFBASE_IS_OFFSET(o)), (o) - ZSTD_REP_NUM) ++#define OFFBASE_TO_REPCODE(o) (assert(OFFBASE_IS_REPCODE(o)), (o)) /* returns ID 1,2,3 */ + + /*! ZSTD_storeSeq() : +- * Store a sequence (litlen, litPtr, offCode and matchLength) into seqStore_t. +- * @offBase_minus1 : Users should use employ macros STORE_REPCODE_X and STORE_OFFSET(). ++ * Store a sequence (litlen, litPtr, offBase and matchLength) into seqStore_t. ++ * @offBase : Users should employ macros REPCODE_TO_OFFBASE() and OFFSET_TO_OFFBASE(). + * @matchLength : must be >= MINMATCH +- * Allowed to overread literals up to litLimit. ++ * Allowed to over-read literals up to litLimit. + */ + HINT_INLINE UNUSED_ATTR void + ZSTD_storeSeq(seqStore_t* seqStorePtr, + size_t litLength, const BYTE* literals, const BYTE* litLimit, +- U32 offBase_minus1, ++ U32 offBase, + size_t matchLength) + { + BYTE const* const litLimit_w = litLimit - WILDCOPY_OVERLENGTH; +@@ -596,8 +651,8 @@ + static const BYTE* g_start = NULL; + if (g_start==NULL) g_start = (const BYTE*)literals; /* note : index only works for compression within a single segment */ + { U32 const pos = (U32)((const BYTE*)literals - g_start); +- DEBUGLOG(6, "Cpos%7u :%3u literals, match%4u bytes at offCode%7u", +- pos, (U32)litLength, (U32)matchLength, (U32)offBase_minus1); ++ DEBUGLOG(6, "Cpos%7u :%3u literals, match%4u bytes at offBase%7u", ++ pos, (U32)litLength, (U32)matchLength, (U32)offBase); + } + #endif + assert((size_t)(seqStorePtr->sequences - seqStorePtr->sequencesStart) < seqStorePtr->maxNbSeq); +@@ -607,9 +662,9 @@ + assert(literals + litLength <= litLimit); + if (litEnd <= litLimit_w) { + /* Common case we can use wildcopy. +- * First copy 16 bytes, because literals are likely short. +- */ +- assert(WILDCOPY_OVERLENGTH >= 16); ++ * First copy 16 bytes, because literals are likely short. ++ */ ++ ZSTD_STATIC_ASSERT(WILDCOPY_OVERLENGTH >= 16); + ZSTD_copy16(seqStorePtr->lit, literals); + if (litLength > 16) { + ZSTD_wildcopy(seqStorePtr->lit+16, literals+16, (ptrdiff_t)litLength-16, ZSTD_no_overlap); +@@ -628,7 +683,7 @@ + seqStorePtr->sequences[0].litLength = (U16)litLength; + + /* match offset */ +- seqStorePtr->sequences[0].offBase = STORED_TO_OFFBASE(offBase_minus1); ++ seqStorePtr->sequences[0].offBase = offBase; + + /* match Length */ + assert(matchLength >= MINMATCH); +@@ -646,17 +701,17 @@ + + /* ZSTD_updateRep() : + * updates in-place @rep (array of repeat offsets) +- * @offBase_minus1 : sum-type, with same numeric representation as ZSTD_storeSeq() ++ * @offBase : sum-type, using numeric representation of ZSTD_storeSeq() + */ + MEM_STATIC void +-ZSTD_updateRep(U32 rep[ZSTD_REP_NUM], U32 const offBase_minus1, U32 const ll0) ++ZSTD_updateRep(U32 rep[ZSTD_REP_NUM], U32 const offBase, U32 const ll0) + { +- if (STORED_IS_OFFSET(offBase_minus1)) { /* full offset */ ++ if (OFFBASE_IS_OFFSET(offBase)) { /* full offset */ + rep[2] = rep[1]; + rep[1] = rep[0]; +- rep[0] = STORED_OFFSET(offBase_minus1); ++ rep[0] = OFFBASE_TO_OFFSET(offBase); + } else { /* repcode */ +- U32 const repCode = STORED_REPCODE(offBase_minus1) - 1 + ll0; ++ U32 const repCode = OFFBASE_TO_REPCODE(offBase) - 1 + ll0; + if (repCode > 0) { /* note : if repCode==0, no change */ + U32 const currentOffset = (repCode==ZSTD_REP_NUM) ? (rep[0] - 1) : rep[repCode]; + rep[2] = (repCode >= 2) ? rep[1] : rep[2]; +@@ -673,11 +728,11 @@ + } repcodes_t; + + MEM_STATIC repcodes_t +-ZSTD_newRep(U32 const rep[ZSTD_REP_NUM], U32 const offBase_minus1, U32 const ll0) ++ZSTD_newRep(U32 const rep[ZSTD_REP_NUM], U32 const offBase, U32 const ll0) + { + repcodes_t newReps; + ZSTD_memcpy(&newReps, rep, sizeof(newReps)); +- ZSTD_updateRep(newReps.rep, offBase_minus1, ll0); ++ ZSTD_updateRep(newReps.rep, offBase, ll0); + return newReps; + } + +@@ -685,59 +740,6 @@ + /*-************************************* + * Match length counter + ***************************************/ +-static unsigned ZSTD_NbCommonBytes (size_t val) +-{ +- if (MEM_isLittleEndian()) { +- if (MEM_64bits()) { +-# if (__GNUC__ >= 4) +- return (__builtin_ctzll((U64)val) >> 3); +-# else +- static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, +- 0, 3, 1, 3, 1, 4, 2, 7, +- 0, 2, 3, 6, 1, 5, 3, 5, +- 1, 3, 4, 4, 2, 5, 6, 7, +- 7, 0, 1, 2, 3, 3, 4, 6, +- 2, 6, 5, 5, 3, 4, 5, 6, +- 7, 1, 2, 4, 6, 4, 4, 5, +- 7, 2, 6, 5, 7, 6, 7, 7 }; +- return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +-# endif +- } else { /* 32 bits */ +-# if (__GNUC__ >= 3) +- return (__builtin_ctz((U32)val) >> 3); +-# else +- static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, +- 3, 2, 2, 1, 3, 2, 0, 1, +- 3, 3, 1, 2, 2, 2, 2, 0, +- 3, 1, 2, 0, 1, 0, 1, 1 }; +- return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +-# endif +- } +- } else { /* Big Endian CPU */ +- if (MEM_64bits()) { +-# if (__GNUC__ >= 4) +- return (__builtin_clzll(val) >> 3); +-# else +- unsigned r; +- const unsigned n32 = sizeof(size_t)*4; /* calculate this way due to compiler complaining in 32-bits mode */ +- if (!(val>>n32)) { r=4; } else { r=0; val>>=n32; } +- if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } +- r += (!val); +- return r; +-# endif +- } else { /* 32 bits */ +-# if (__GNUC__ >= 3) +- return (__builtin_clz((U32)val) >> 3); +-# else +- unsigned r; +- if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } +- r += (!val); +- return r; +-# endif +- } } +-} +- +- + MEM_STATIC size_t ZSTD_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* const pInLimit) + { + const BYTE* const pStart = pIn; +@@ -783,32 +785,43 @@ + * Hashes + ***************************************/ + static const U32 prime3bytes = 506832829U; +-static U32 ZSTD_hash3(U32 u, U32 h) { return ((u << (32-24)) * prime3bytes) >> (32-h) ; } +-MEM_STATIC size_t ZSTD_hash3Ptr(const void* ptr, U32 h) { return ZSTD_hash3(MEM_readLE32(ptr), h); } /* only in zstd_opt.h */ ++static U32 ZSTD_hash3(U32 u, U32 h, U32 s) { assert(h <= 32); return (((u << (32-24)) * prime3bytes) ^ s) >> (32-h) ; } ++MEM_STATIC size_t ZSTD_hash3Ptr(const void* ptr, U32 h) { return ZSTD_hash3(MEM_readLE32(ptr), h, 0); } /* only in zstd_opt.h */ ++MEM_STATIC size_t ZSTD_hash3PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash3(MEM_readLE32(ptr), h, s); } + + static const U32 prime4bytes = 2654435761U; +-static U32 ZSTD_hash4(U32 u, U32 h) { return (u * prime4bytes) >> (32-h) ; } +-static size_t ZSTD_hash4Ptr(const void* ptr, U32 h) { return ZSTD_hash4(MEM_read32(ptr), h); } ++static U32 ZSTD_hash4(U32 u, U32 h, U32 s) { assert(h <= 32); return ((u * prime4bytes) ^ s) >> (32-h) ; } ++static size_t ZSTD_hash4Ptr(const void* ptr, U32 h) { return ZSTD_hash4(MEM_readLE32(ptr), h, 0); } ++static size_t ZSTD_hash4PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash4(MEM_readLE32(ptr), h, s); } + + static const U64 prime5bytes = 889523592379ULL; +-static size_t ZSTD_hash5(U64 u, U32 h) { return (size_t)(((u << (64-40)) * prime5bytes) >> (64-h)) ; } +-static size_t ZSTD_hash5Ptr(const void* p, U32 h) { return ZSTD_hash5(MEM_readLE64(p), h); } ++static size_t ZSTD_hash5(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-40)) * prime5bytes) ^ s) >> (64-h)) ; } ++static size_t ZSTD_hash5Ptr(const void* p, U32 h) { return ZSTD_hash5(MEM_readLE64(p), h, 0); } ++static size_t ZSTD_hash5PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash5(MEM_readLE64(p), h, s); } + + static const U64 prime6bytes = 227718039650203ULL; +-static size_t ZSTD_hash6(U64 u, U32 h) { return (size_t)(((u << (64-48)) * prime6bytes) >> (64-h)) ; } +-static size_t ZSTD_hash6Ptr(const void* p, U32 h) { return ZSTD_hash6(MEM_readLE64(p), h); } ++static size_t ZSTD_hash6(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-48)) * prime6bytes) ^ s) >> (64-h)) ; } ++static size_t ZSTD_hash6Ptr(const void* p, U32 h) { return ZSTD_hash6(MEM_readLE64(p), h, 0); } ++static size_t ZSTD_hash6PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash6(MEM_readLE64(p), h, s); } + + static const U64 prime7bytes = 58295818150454627ULL; +-static size_t ZSTD_hash7(U64 u, U32 h) { return (size_t)(((u << (64-56)) * prime7bytes) >> (64-h)) ; } +-static size_t ZSTD_hash7Ptr(const void* p, U32 h) { return ZSTD_hash7(MEM_readLE64(p), h); } ++static size_t ZSTD_hash7(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-56)) * prime7bytes) ^ s) >> (64-h)) ; } ++static size_t ZSTD_hash7Ptr(const void* p, U32 h) { return ZSTD_hash7(MEM_readLE64(p), h, 0); } ++static size_t ZSTD_hash7PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash7(MEM_readLE64(p), h, s); } + + static const U64 prime8bytes = 0xCF1BBCDCB7A56463ULL; +-static size_t ZSTD_hash8(U64 u, U32 h) { return (size_t)(((u) * prime8bytes) >> (64-h)) ; } +-static size_t ZSTD_hash8Ptr(const void* p, U32 h) { return ZSTD_hash8(MEM_readLE64(p), h); } ++static size_t ZSTD_hash8(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u) * prime8bytes) ^ s) >> (64-h)) ; } ++static size_t ZSTD_hash8Ptr(const void* p, U32 h) { return ZSTD_hash8(MEM_readLE64(p), h, 0); } ++static size_t ZSTD_hash8PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash8(MEM_readLE64(p), h, s); } ++ + + MEM_STATIC FORCE_INLINE_ATTR + size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls) + { ++ /* Although some of these hashes do support hBits up to 64, some do not. ++ * To be on the safe side, always avoid hBits > 32. */ ++ assert(hBits <= 32); ++ + switch(mls) + { + default: +@@ -820,6 +833,24 @@ + } + } + ++MEM_STATIC FORCE_INLINE_ATTR ++size_t ZSTD_hashPtrSalted(const void* p, U32 hBits, U32 mls, const U64 hashSalt) { ++ /* Although some of these hashes do support hBits up to 64, some do not. ++ * To be on the safe side, always avoid hBits > 32. */ ++ assert(hBits <= 32); ++ ++ switch(mls) ++ { ++ default: ++ case 4: return ZSTD_hash4PtrS(p, hBits, (U32)hashSalt); ++ case 5: return ZSTD_hash5PtrS(p, hBits, hashSalt); ++ case 6: return ZSTD_hash6PtrS(p, hBits, hashSalt); ++ case 7: return ZSTD_hash7PtrS(p, hBits, hashSalt); ++ case 8: return ZSTD_hash8PtrS(p, hBits, hashSalt); ++ } ++} ++ ++ + /* ZSTD_ipow() : + * Return base^exponent. + */ +@@ -1167,10 +1198,15 @@ + (unsigned)blockEndIdx, (unsigned)maxDist, (unsigned)loadedDictEnd); + assert(blockEndIdx >= loadedDictEnd); + +- if (blockEndIdx > loadedDictEnd + maxDist) { ++ if (blockEndIdx > loadedDictEnd + maxDist || loadedDictEnd != window->dictLimit) { + /* On reaching window size, dictionaries are invalidated. + * For simplification, if window size is reached anywhere within next block, + * the dictionary is invalidated for the full block. ++ * ++ * We also have to invalidate the dictionary if ZSTD_window_update() has detected ++ * non-contiguous segments, which means that loadedDictEnd != window->dictLimit. ++ * loadedDictEnd may be 0, if forceWindow is true, but in that case we never use ++ * dictMatchState, so setting it to NULL is not a problem. + */ + DEBUGLOG(6, "invalidating dictionary for current block (distance > windowSize)"); + *loadedDictEndPtr = 0; +@@ -1302,6 +1338,42 @@ + + #endif + ++/* Short Cache */ ++ ++/* Normally, zstd matchfinders follow this flow: ++ * 1. Compute hash at ip ++ * 2. Load index from hashTable[hash] ++ * 3. Check if *ip == *(base + index) ++ * In dictionary compression, loading *(base + index) is often an L2 or even L3 miss. ++ * ++ * Short cache is an optimization which allows us to avoid step 3 most of the time ++ * when the data doesn't actually match. With short cache, the flow becomes: ++ * 1. Compute (hash, currentTag) at ip. currentTag is an 8-bit independent hash at ip. ++ * 2. Load (index, matchTag) from hashTable[hash]. See ZSTD_writeTaggedIndex to understand how this works. ++ * 3. Only if currentTag == matchTag, check *ip == *(base + index). Otherwise, continue. ++ * ++ * Currently, short cache is only implemented in CDict hashtables. Thus, its use is limited to ++ * dictMatchState matchfinders. ++ */ ++#define ZSTD_SHORT_CACHE_TAG_BITS 8 ++#define ZSTD_SHORT_CACHE_TAG_MASK ((1u << ZSTD_SHORT_CACHE_TAG_BITS) - 1) ++ ++/* Helper function for ZSTD_fillHashTable and ZSTD_fillDoubleHashTable. ++ * Unpacks hashAndTag into (hash, tag), then packs (index, tag) into hashTable[hash]. */ ++MEM_STATIC void ZSTD_writeTaggedIndex(U32* const hashTable, size_t hashAndTag, U32 index) { ++ size_t const hash = hashAndTag >> ZSTD_SHORT_CACHE_TAG_BITS; ++ U32 const tag = (U32)(hashAndTag & ZSTD_SHORT_CACHE_TAG_MASK); ++ assert(index >> (32 - ZSTD_SHORT_CACHE_TAG_BITS) == 0); ++ hashTable[hash] = (index << ZSTD_SHORT_CACHE_TAG_BITS) | tag; ++} ++ ++/* Helper function for short cache matchfinders. ++ * Unpacks tag1 and tag2 from lower bits of packedTag1 and packedTag2, then checks if the tags match. */ ++MEM_STATIC int ZSTD_comparePackedTags(size_t packedTag1, size_t packedTag2) { ++ U32 const tag1 = packedTag1 & ZSTD_SHORT_CACHE_TAG_MASK; ++ U32 const tag2 = packedTag2 & ZSTD_SHORT_CACHE_TAG_MASK; ++ return tag1 == tag2; ++} + + + /* =============================================================== +@@ -1396,4 +1468,51 @@ + */ + void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize); + ++/* Returns 0 on success, and a ZSTD_error otherwise. This function scans through an array of ++ * ZSTD_Sequence, storing the sequences it finds, until it reaches a block delimiter. ++ * Note that the block delimiter must include the last literals of the block. ++ */ ++size_t ++ZSTD_copySequencesToSeqStoreExplicitBlockDelim(ZSTD_CCtx* cctx, ++ ZSTD_sequencePosition* seqPos, ++ const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, ++ const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch); ++ ++/* Returns the number of bytes to move the current read position back by. ++ * Only non-zero if we ended up splitting a sequence. ++ * Otherwise, it may return a ZSTD error if something went wrong. ++ * ++ * This function will attempt to scan through blockSize bytes ++ * represented by the sequences in @inSeqs, ++ * storing any (partial) sequences. ++ * ++ * Occasionally, we may want to change the actual number of bytes we consumed from inSeqs to ++ * avoid splitting a match, or to avoid splitting a match such that it would produce a match ++ * smaller than MINMATCH. In this case, we return the number of bytes that we didn't read from this block. ++ */ ++size_t ++ZSTD_copySequencesToSeqStoreNoBlockDelim(ZSTD_CCtx* cctx, ZSTD_sequencePosition* seqPos, ++ const ZSTD_Sequence* const inSeqs, size_t inSeqsSize, ++ const void* src, size_t blockSize, ZSTD_paramSwitch_e externalRepSearch); ++ ++ ++/* =============================================================== ++ * Deprecated definitions that are still used internally to avoid ++ * deprecation warnings. These functions are exactly equivalent to ++ * their public variants, but avoid the deprecation warnings. ++ * =============================================================== */ ++ ++size_t ZSTD_compressBegin_usingCDict_deprecated(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); ++ ++size_t ZSTD_compressContinue_public(ZSTD_CCtx* cctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize); ++ ++size_t ZSTD_compressEnd_public(ZSTD_CCtx* cctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize); ++ ++size_t ZSTD_compressBlock_deprecated(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); ++ ++ + #endif /* ZSTD_COMPRESS_H */ +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress_literals.c b/lib/zstd/compress/zstd_compress_literals.c +--- a/lib/zstd/compress/zstd_compress_literals.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress_literals.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -13,11 +14,36 @@ + ***************************************/ + #include "zstd_compress_literals.h" + ++ ++/* ************************************************************** ++* Debug Traces ++****************************************************************/ ++#if DEBUGLEVEL >= 2 ++ ++static size_t showHexa(const void* src, size_t srcSize) ++{ ++ const BYTE* const ip = (const BYTE*)src; ++ size_t u; ++ for (u=0; u31) + (srcSize>4095); + ++ DEBUGLOG(5, "ZSTD_noCompressLiterals: srcSize=%zu, dstCapacity=%zu", srcSize, dstCapacity); ++ + RETURN_ERROR_IF(srcSize + flSize > dstCapacity, dstSize_tooSmall, ""); + + switch(flSize) +@@ -36,16 +62,30 @@ + } + + ZSTD_memcpy(ostart + flSize, src, srcSize); +- DEBUGLOG(5, "Raw literals: %u -> %u", (U32)srcSize, (U32)(srcSize + flSize)); ++ DEBUGLOG(5, "Raw (uncompressed) literals: %u -> %u", (U32)srcSize, (U32)(srcSize + flSize)); + return srcSize + flSize; + } + ++static int allBytesIdentical(const void* src, size_t srcSize) ++{ ++ assert(srcSize >= 1); ++ assert(src != NULL); ++ { const BYTE b = ((const BYTE*)src)[0]; ++ size_t p; ++ for (p=1; p31) + (srcSize>4095); + +- (void)dstCapacity; /* dstCapacity already guaranteed to be >=4, hence large enough */ ++ assert(dstCapacity >= 4); (void)dstCapacity; ++ assert(allBytesIdentical(src, srcSize)); + + switch(flSize) + { +@@ -63,28 +103,51 @@ + } + + ostart[flSize] = *(const BYTE*)src; +- DEBUGLOG(5, "RLE literals: %u -> %u", (U32)srcSize, (U32)flSize + 1); ++ DEBUGLOG(5, "RLE : Repeated Literal (%02X: %u times) -> %u bytes encoded", ((const BYTE*)src)[0], (U32)srcSize, (U32)flSize + 1); + return flSize+1; + } + +-size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf, +- ZSTD_hufCTables_t* nextHuf, +- ZSTD_strategy strategy, int disableLiteralCompression, +- void* dst, size_t dstCapacity, +- const void* src, size_t srcSize, +- void* entropyWorkspace, size_t entropyWorkspaceSize, +- const int bmi2, +- unsigned suspectUncompressible) ++/* ZSTD_minLiteralsToCompress() : ++ * returns minimal amount of literals ++ * for literal compression to even be attempted. ++ * Minimum is made tighter as compression strategy increases. ++ */ ++static size_t ++ZSTD_minLiteralsToCompress(ZSTD_strategy strategy, HUF_repeat huf_repeat) ++{ ++ assert((int)strategy >= 0); ++ assert((int)strategy <= 9); ++ /* btultra2 : min 8 bytes; ++ * then 2x larger for each successive compression strategy ++ * max threshold 64 bytes */ ++ { int const shift = MIN(9-(int)strategy, 3); ++ size_t const mintc = (huf_repeat == HUF_repeat_valid) ? 6 : (size_t)8 << shift; ++ DEBUGLOG(7, "minLiteralsToCompress = %zu", mintc); ++ return mintc; ++ } ++} ++ ++size_t ZSTD_compressLiterals ( ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize, ++ void* entropyWorkspace, size_t entropyWorkspaceSize, ++ const ZSTD_hufCTables_t* prevHuf, ++ ZSTD_hufCTables_t* nextHuf, ++ ZSTD_strategy strategy, ++ int disableLiteralCompression, ++ int suspectUncompressible, ++ int bmi2) + { +- size_t const minGain = ZSTD_minGain(srcSize, strategy); + size_t const lhSize = 3 + (srcSize >= 1 KB) + (srcSize >= 16 KB); + BYTE* const ostart = (BYTE*)dst; + U32 singleStream = srcSize < 256; + symbolEncodingType_e hType = set_compressed; + size_t cLitSize; + +- DEBUGLOG(5,"ZSTD_compressLiterals (disableLiteralCompression=%i srcSize=%u)", +- disableLiteralCompression, (U32)srcSize); ++ DEBUGLOG(5,"ZSTD_compressLiterals (disableLiteralCompression=%i, srcSize=%u, dstCapacity=%zu)", ++ disableLiteralCompression, (U32)srcSize, dstCapacity); ++ ++ DEBUGLOG(6, "Completed literals listing (%zu bytes)", showHexa(src, srcSize)); + + /* Prepare nextEntropy assuming reusing the existing table */ + ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); +@@ -92,40 +155,51 @@ + if (disableLiteralCompression) + return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); + +- /* small ? don't even attempt compression (speed opt) */ +-# define COMPRESS_LITERALS_SIZE_MIN 63 +- { size_t const minLitSize = (prevHuf->repeatMode == HUF_repeat_valid) ? 6 : COMPRESS_LITERALS_SIZE_MIN; +- if (srcSize <= minLitSize) return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); +- } ++ /* if too small, don't even attempt compression (speed opt) */ ++ if (srcSize < ZSTD_minLiteralsToCompress(strategy, prevHuf->repeatMode)) ++ return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); + + RETURN_ERROR_IF(dstCapacity < lhSize+1, dstSize_tooSmall, "not enough space for compression"); + { HUF_repeat repeat = prevHuf->repeatMode; +- int const preferRepeat = strategy < ZSTD_lazy ? srcSize <= 1024 : 0; ++ int const flags = 0 ++ | (bmi2 ? HUF_flags_bmi2 : 0) ++ | (strategy < ZSTD_lazy && srcSize <= 1024 ? HUF_flags_preferRepeat : 0) ++ | (strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD ? HUF_flags_optimalDepth : 0) ++ | (suspectUncompressible ? HUF_flags_suspectUncompressible : 0); ++ ++ typedef size_t (*huf_compress_f)(void*, size_t, const void*, size_t, unsigned, unsigned, void*, size_t, HUF_CElt*, HUF_repeat*, int); ++ huf_compress_f huf_compress; + if (repeat == HUF_repeat_valid && lhSize == 3) singleStream = 1; +- cLitSize = singleStream ? +- HUF_compress1X_repeat( +- ostart+lhSize, dstCapacity-lhSize, src, srcSize, +- HUF_SYMBOLVALUE_MAX, HUF_TABLELOG_DEFAULT, entropyWorkspace, entropyWorkspaceSize, +- (HUF_CElt*)nextHuf->CTable, &repeat, preferRepeat, bmi2, suspectUncompressible) : +- HUF_compress4X_repeat( +- ostart+lhSize, dstCapacity-lhSize, src, srcSize, +- HUF_SYMBOLVALUE_MAX, HUF_TABLELOG_DEFAULT, entropyWorkspace, entropyWorkspaceSize, +- (HUF_CElt*)nextHuf->CTable, &repeat, preferRepeat, bmi2, suspectUncompressible); ++ huf_compress = singleStream ? HUF_compress1X_repeat : HUF_compress4X_repeat; ++ cLitSize = huf_compress(ostart+lhSize, dstCapacity-lhSize, ++ src, srcSize, ++ HUF_SYMBOLVALUE_MAX, LitHufLog, ++ entropyWorkspace, entropyWorkspaceSize, ++ (HUF_CElt*)nextHuf->CTable, ++ &repeat, flags); ++ DEBUGLOG(5, "%zu literals compressed into %zu bytes (before header)", srcSize, cLitSize); + if (repeat != HUF_repeat_none) { + /* reused the existing table */ +- DEBUGLOG(5, "Reusing previous huffman table"); ++ DEBUGLOG(5, "reusing statistics from previous huffman block"); + hType = set_repeat; + } + } + +- if ((cLitSize==0) || (cLitSize >= srcSize - minGain) || ERR_isError(cLitSize)) { +- ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); +- return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); +- } ++ { size_t const minGain = ZSTD_minGain(srcSize, strategy); ++ if ((cLitSize==0) || (cLitSize >= srcSize - minGain) || ERR_isError(cLitSize)) { ++ ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); ++ return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize); ++ } } + if (cLitSize==1) { +- ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); +- return ZSTD_compressRleLiteralsBlock(dst, dstCapacity, src, srcSize); +- } ++ /* A return value of 1 signals that the alphabet consists of a single symbol. ++ * However, in some rare circumstances, it could be the compressed size (a single byte). ++ * For that outcome to have a chance to happen, it's necessary that `srcSize < 8`. ++ * (it's also necessary to not generate statistics). ++ * Therefore, in such a case, actively check that all bytes are identical. */ ++ if ((srcSize >= 8) || allBytesIdentical(src, srcSize)) { ++ ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf)); ++ return ZSTD_compressRleLiteralsBlock(dst, dstCapacity, src, srcSize); ++ } } + + if (hType == set_compressed) { + /* using a newly constructed table */ +@@ -136,16 +210,19 @@ + switch(lhSize) + { + case 3: /* 2 - 2 - 10 - 10 */ +- { U32 const lhc = hType + ((!singleStream) << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<14); ++ if (!singleStream) assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); ++ { U32 const lhc = hType + ((U32)(!singleStream) << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<14); + MEM_writeLE24(ostart, lhc); + break; + } + case 4: /* 2 - 2 - 14 - 14 */ ++ assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); + { U32 const lhc = hType + (2 << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<18); + MEM_writeLE32(ostart, lhc); + break; + } + case 5: /* 2 - 2 - 18 - 18 */ ++ assert(srcSize >= MIN_LITERALS_FOR_4_STREAMS); + { U32 const lhc = hType + (3 << 2) + ((U32)srcSize<<4) + ((U32)cLitSize<<22); + MEM_writeLE32(ostart, lhc); + ostart[4] = (BYTE)(cLitSize >> 10); +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress_literals.h b/lib/zstd/compress/zstd_compress_literals.h +--- a/lib/zstd/compress/zstd_compress_literals.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress_literals.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -16,16 +17,24 @@ + + size_t ZSTD_noCompressLiterals (void* dst, size_t dstCapacity, const void* src, size_t srcSize); + ++/* ZSTD_compressRleLiteralsBlock() : ++ * Conditions : ++ * - All bytes in @src are identical ++ * - dstCapacity >= 4 */ + size_t ZSTD_compressRleLiteralsBlock (void* dst, size_t dstCapacity, const void* src, size_t srcSize); + +-/* If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */ +-size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf, +- ZSTD_hufCTables_t* nextHuf, +- ZSTD_strategy strategy, int disableLiteralCompression, +- void* dst, size_t dstCapacity, ++/* ZSTD_compressLiterals(): ++ * @entropyWorkspace: must be aligned on 4-bytes boundaries ++ * @entropyWorkspaceSize : must be >= HUF_WORKSPACE_SIZE ++ * @suspectUncompressible: sampling checks, to potentially skip huffman coding ++ */ ++size_t ZSTD_compressLiterals (void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + void* entropyWorkspace, size_t entropyWorkspaceSize, +- const int bmi2, +- unsigned suspectUncompressible); ++ const ZSTD_hufCTables_t* prevHuf, ++ ZSTD_hufCTables_t* nextHuf, ++ ZSTD_strategy strategy, int disableLiteralCompression, ++ int suspectUncompressible, ++ int bmi2); + + #endif /* ZSTD_COMPRESS_LITERALS_H */ +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c +--- a/lib/zstd/compress/zstd_compress_sequences.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress_sequences.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -58,7 +59,7 @@ + { + /* Heuristic: This should cover most blocks <= 16K and + * start to fade out after 16K to about 32K depending on +- * comprssibility. ++ * compressibility. + */ + return nbSeq >= 2048; + } +@@ -166,7 +167,7 @@ + if (mostFrequent == nbSeq) { + *repeatMode = FSE_repeat_none; + if (isDefaultAllowed && nbSeq <= 2) { +- /* Prefer set_basic over set_rle when there are 2 or less symbols, ++ /* Prefer set_basic over set_rle when there are 2 or fewer symbols, + * since RLE uses 1 byte, but set_basic uses 5-6 bits per symbol. + * If basic encoding isn't possible, always choose RLE. + */ +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress_sequences.h b/lib/zstd/compress/zstd_compress_sequences.h +--- a/lib/zstd/compress/zstd_compress_sequences.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress_sequences.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress_superblock.c b/lib/zstd/compress/zstd_compress_superblock.c +--- a/lib/zstd/compress/zstd_compress_superblock.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress_superblock.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -36,13 +37,14 @@ + * If it is set_compressed, first sub-block's literals section will be Treeless_Literals_Block + * and the following sub-blocks' literals sections will be Treeless_Literals_Block. + * @return : compressed size of literals section of a sub-block +- * Or 0 if it unable to compress. ++ * Or 0 if unable to compress. + * Or error code */ +-static size_t ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, +- const ZSTD_hufCTablesMetadata_t* hufMetadata, +- const BYTE* literals, size_t litSize, +- void* dst, size_t dstSize, +- const int bmi2, int writeEntropy, int* entropyWritten) ++static size_t ++ZSTD_compressSubBlock_literal(const HUF_CElt* hufTable, ++ const ZSTD_hufCTablesMetadata_t* hufMetadata, ++ const BYTE* literals, size_t litSize, ++ void* dst, size_t dstSize, ++ const int bmi2, int writeEntropy, int* entropyWritten) + { + size_t const header = writeEntropy ? 200 : 0; + size_t const lhSize = 3 + (litSize >= (1 KB - header)) + (litSize >= (16 KB - header)); +@@ -53,8 +55,6 @@ + symbolEncodingType_e hType = writeEntropy ? hufMetadata->hType : set_repeat; + size_t cLitSize = 0; + +- (void)bmi2; /* TODO bmi2... */ +- + DEBUGLOG(5, "ZSTD_compressSubBlock_literal (litSize=%zu, lhSize=%zu, writeEntropy=%d)", litSize, lhSize, writeEntropy); + + *entropyWritten = 0; +@@ -76,9 +76,9 @@ + DEBUGLOG(5, "ZSTD_compressSubBlock_literal (hSize=%zu)", hufMetadata->hufDesSize); + } + +- /* TODO bmi2 */ +- { const size_t cSize = singleStream ? HUF_compress1X_usingCTable(op, oend-op, literals, litSize, hufTable) +- : HUF_compress4X_usingCTable(op, oend-op, literals, litSize, hufTable); ++ { int const flags = bmi2 ? HUF_flags_bmi2 : 0; ++ const size_t cSize = singleStream ? HUF_compress1X_usingCTable(op, oend-op, literals, litSize, hufTable, flags) ++ : HUF_compress4X_usingCTable(op, oend-op, literals, litSize, hufTable, flags); + op += cSize; + cLitSize += cSize; + if (cSize == 0 || ERR_isError(cSize)) { +@@ -126,7 +126,11 @@ + return op-ostart; + } + +-static size_t ZSTD_seqDecompressedSize(seqStore_t const* seqStore, const seqDef* sequences, size_t nbSeq, size_t litSize, int lastSequence) { ++static size_t ++ZSTD_seqDecompressedSize(seqStore_t const* seqStore, ++ const seqDef* sequences, size_t nbSeq, ++ size_t litSize, int lastSequence) ++{ + const seqDef* const sstart = sequences; + const seqDef* const send = sequences + nbSeq; + const seqDef* sp = sstart; +@@ -156,13 +160,14 @@ + * @return : compressed size of sequences section of a sub-block + * Or 0 if it is unable to compress + * Or error code. */ +-static size_t ZSTD_compressSubBlock_sequences(const ZSTD_fseCTables_t* fseTables, +- const ZSTD_fseCTablesMetadata_t* fseMetadata, +- const seqDef* sequences, size_t nbSeq, +- const BYTE* llCode, const BYTE* mlCode, const BYTE* ofCode, +- const ZSTD_CCtx_params* cctxParams, +- void* dst, size_t dstCapacity, +- const int bmi2, int writeEntropy, int* entropyWritten) ++static size_t ++ZSTD_compressSubBlock_sequences(const ZSTD_fseCTables_t* fseTables, ++ const ZSTD_fseCTablesMetadata_t* fseMetadata, ++ const seqDef* sequences, size_t nbSeq, ++ const BYTE* llCode, const BYTE* mlCode, const BYTE* ofCode, ++ const ZSTD_CCtx_params* cctxParams, ++ void* dst, size_t dstCapacity, ++ const int bmi2, int writeEntropy, int* entropyWritten) + { + const int longOffsets = cctxParams->cParams.windowLog > STREAM_ACCUMULATOR_MIN; + BYTE* const ostart = (BYTE*)dst; +@@ -539,7 +544,7 @@ + repcodes_t rep; + ZSTD_memcpy(&rep, prevCBlock->rep, sizeof(rep)); + for (seq = sstart; seq < sp; ++seq) { +- ZSTD_updateRep(rep.rep, seq->offBase - 1, ZSTD_getSequenceLength(seqStorePtr, seq).litLength == 0); ++ ZSTD_updateRep(rep.rep, seq->offBase, ZSTD_getSequenceLength(seqStorePtr, seq).litLength == 0); + } + ZSTD_memcpy(nextCBlock->rep, &rep, sizeof(rep)); + } +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_compress_superblock.h b/lib/zstd/compress/zstd_compress_superblock.h +--- a/lib/zstd/compress/zstd_compress_superblock.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_compress_superblock.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_cwksp.h b/lib/zstd/compress/zstd_cwksp.h +--- a/lib/zstd/compress/zstd_cwksp.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_cwksp.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -14,7 +15,9 @@ + /*-************************************* + * Dependencies + ***************************************/ ++#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customFree */ + #include "../common/zstd_internal.h" ++#include "../common/portability_macros.h" + + + /*-************************************* +@@ -41,8 +44,9 @@ + ***************************************/ + typedef enum { + ZSTD_cwksp_alloc_objects, +- ZSTD_cwksp_alloc_buffers, +- ZSTD_cwksp_alloc_aligned ++ ZSTD_cwksp_alloc_aligned_init_once, ++ ZSTD_cwksp_alloc_aligned, ++ ZSTD_cwksp_alloc_buffers + } ZSTD_cwksp_alloc_phase_e; + + /* +@@ -95,8 +99,8 @@ + * + * Workspace Layout: + * +- * [ ... workspace ... ] +- * [objects][tables ... ->] free space [<- ... aligned][<- ... buffers] ++ * [ ... workspace ... ] ++ * [objects][tables ->] free space [<- buffers][<- aligned][<- init once] + * + * The various objects that live in the workspace are divided into the + * following categories, and are allocated separately: +@@ -120,9 +124,18 @@ + * uint32_t arrays, all of whose values are between 0 and (nextSrc - base). + * Their sizes depend on the cparams. These tables are 64-byte aligned. + * +- * - Aligned: these buffers are used for various purposes that require 4 byte +- * alignment, but don't require any initialization before they're used. These +- * buffers are each aligned to 64 bytes. ++ * - Init once: these buffers require to be initialized at least once before ++ * use. They should be used when we want to skip memory initialization ++ * while not triggering memory checkers (like Valgrind) when reading from ++ * from this memory without writing to it first. ++ * These buffers should be used carefully as they might contain data ++ * from previous compressions. ++ * Buffers are aligned to 64 bytes. ++ * ++ * - Aligned: these buffers don't require any initialization before they're ++ * used. The user of the buffer should make sure they write into a buffer ++ * location before reading from it. ++ * Buffers are aligned to 64 bytes. + * + * - Buffers: these buffers are used for various purposes that don't require + * any alignment or initialization before they're used. This means they can +@@ -134,8 +147,9 @@ + * correctly packed into the workspace buffer. That order is: + * + * 1. Objects +- * 2. Buffers +- * 3. Aligned/Tables ++ * 2. Init once / Tables ++ * 3. Aligned / Tables ++ * 4. Buffers / Tables + * + * Attempts to reserve objects of different types out of order will fail. + */ +@@ -147,6 +161,7 @@ + void* tableEnd; + void* tableValidEnd; + void* allocStart; ++ void* initOnceStart; + + BYTE allocFailed; + int workspaceOversizedDuration; +@@ -159,6 +174,7 @@ + ***************************************/ + + MEM_STATIC size_t ZSTD_cwksp_available_space(ZSTD_cwksp* ws); ++MEM_STATIC void* ZSTD_cwksp_initialAllocStart(ZSTD_cwksp* ws); + + MEM_STATIC void ZSTD_cwksp_assert_internal_consistency(ZSTD_cwksp* ws) { + (void)ws; +@@ -168,6 +184,8 @@ + assert(ws->tableEnd <= ws->allocStart); + assert(ws->tableValidEnd <= ws->allocStart); + assert(ws->allocStart <= ws->workspaceEnd); ++ assert(ws->initOnceStart <= ZSTD_cwksp_initialAllocStart(ws)); ++ assert(ws->workspace <= ws->initOnceStart); + } + + /* +@@ -210,14 +228,10 @@ + * for internal purposes (currently only alignment). + */ + MEM_STATIC size_t ZSTD_cwksp_slack_space_required(void) { +- /* For alignment, the wksp will always allocate an additional n_1=[1, 64] bytes +- * to align the beginning of tables section, as well as another n_2=[0, 63] bytes +- * to align the beginning of the aligned section. +- * +- * n_1 + n_2 == 64 bytes if the cwksp is freshly allocated, due to tables and +- * aligneds being sized in multiples of 64 bytes. ++ /* For alignment, the wksp will always allocate an additional 2*ZSTD_CWKSP_ALIGNMENT_BYTES ++ * bytes to align the beginning of tables section and end of buffers; + */ +- size_t const slackSpace = ZSTD_CWKSP_ALIGNMENT_BYTES; ++ size_t const slackSpace = ZSTD_CWKSP_ALIGNMENT_BYTES * 2; + return slackSpace; + } + +@@ -230,11 +244,19 @@ + size_t const alignBytesMask = alignBytes - 1; + size_t const bytes = (alignBytes - ((size_t)ptr & (alignBytesMask))) & alignBytesMask; + assert((alignBytes & alignBytesMask) == 0); +- assert(bytes != ZSTD_CWKSP_ALIGNMENT_BYTES); ++ assert(bytes < alignBytes); + return bytes; + } + + /* ++ * Returns the initial value for allocStart which is used to determine the position from ++ * which we can allocate from the end of the workspace. ++ */ ++MEM_STATIC void* ZSTD_cwksp_initialAllocStart(ZSTD_cwksp* ws) { ++ return (void*)((size_t)ws->workspaceEnd & ~(ZSTD_CWKSP_ALIGNMENT_BYTES-1)); ++} ++ ++/* + * Internal function. Do not use directly. + * Reserves the given number of bytes within the aligned/buffer segment of the wksp, + * which counts from the end of the wksp (as opposed to the object/table segment). +@@ -274,27 +296,16 @@ + { + assert(phase >= ws->phase); + if (phase > ws->phase) { +- /* Going from allocating objects to allocating buffers */ +- if (ws->phase < ZSTD_cwksp_alloc_buffers && +- phase >= ZSTD_cwksp_alloc_buffers) { ++ /* Going from allocating objects to allocating initOnce / tables */ ++ if (ws->phase < ZSTD_cwksp_alloc_aligned_init_once && ++ phase >= ZSTD_cwksp_alloc_aligned_init_once) { + ws->tableValidEnd = ws->objectEnd; +- } ++ ws->initOnceStart = ZSTD_cwksp_initialAllocStart(ws); + +- /* Going from allocating buffers to allocating aligneds/tables */ +- if (ws->phase < ZSTD_cwksp_alloc_aligned && +- phase >= ZSTD_cwksp_alloc_aligned) { +- { /* Align the start of the "aligned" to 64 bytes. Use [1, 64] bytes. */ +- size_t const bytesToAlign = +- ZSTD_CWKSP_ALIGNMENT_BYTES - ZSTD_cwksp_bytes_to_align_ptr(ws->allocStart, ZSTD_CWKSP_ALIGNMENT_BYTES); +- DEBUGLOG(5, "reserving aligned alignment addtl space: %zu", bytesToAlign); +- ZSTD_STATIC_ASSERT((ZSTD_CWKSP_ALIGNMENT_BYTES & (ZSTD_CWKSP_ALIGNMENT_BYTES - 1)) == 0); /* power of 2 */ +- RETURN_ERROR_IF(!ZSTD_cwksp_reserve_internal_buffer_space(ws, bytesToAlign), +- memory_allocation, "aligned phase - alignment initial allocation failed!"); +- } + { /* Align the start of the tables to 64 bytes. Use [0, 63] bytes */ +- void* const alloc = ws->objectEnd; ++ void *const alloc = ws->objectEnd; + size_t const bytesToAlign = ZSTD_cwksp_bytes_to_align_ptr(alloc, ZSTD_CWKSP_ALIGNMENT_BYTES); +- void* const objectEnd = (BYTE*)alloc + bytesToAlign; ++ void *const objectEnd = (BYTE *) alloc + bytesToAlign; + DEBUGLOG(5, "reserving table alignment addtl space: %zu", bytesToAlign); + RETURN_ERROR_IF(objectEnd > ws->workspaceEnd, memory_allocation, + "table phase - alignment initial allocation failed!"); +@@ -302,7 +313,9 @@ + ws->tableEnd = objectEnd; /* table area starts being empty */ + if (ws->tableValidEnd < ws->tableEnd) { + ws->tableValidEnd = ws->tableEnd; +- } } } ++ } ++ } ++ } + ws->phase = phase; + ZSTD_cwksp_assert_internal_consistency(ws); + } +@@ -314,7 +327,7 @@ + */ + MEM_STATIC int ZSTD_cwksp_owns_buffer(const ZSTD_cwksp* ws, const void* ptr) + { +- return (ptr != NULL) && (ws->workspace <= ptr) && (ptr <= ws->workspaceEnd); ++ return (ptr != NULL) && (ws->workspace <= ptr) && (ptr < ws->workspaceEnd); + } + + /* +@@ -345,6 +358,33 @@ + + /* + * Reserves and returns memory sized on and aligned on ZSTD_CWKSP_ALIGNMENT_BYTES (64 bytes). ++ * This memory has been initialized at least once in the past. ++ * This doesn't mean it has been initialized this time, and it might contain data from previous ++ * operations. ++ * The main usage is for algorithms that might need read access into uninitialized memory. ++ * The algorithm must maintain safety under these conditions and must make sure it doesn't ++ * leak any of the past data (directly or in side channels). ++ */ ++MEM_STATIC void* ZSTD_cwksp_reserve_aligned_init_once(ZSTD_cwksp* ws, size_t bytes) ++{ ++ size_t const alignedBytes = ZSTD_cwksp_align(bytes, ZSTD_CWKSP_ALIGNMENT_BYTES); ++ void* ptr = ZSTD_cwksp_reserve_internal(ws, alignedBytes, ZSTD_cwksp_alloc_aligned_init_once); ++ assert(((size_t)ptr & (ZSTD_CWKSP_ALIGNMENT_BYTES-1))== 0); ++ if(ptr && ptr < ws->initOnceStart) { ++ /* We assume the memory following the current allocation is either: ++ * 1. Not usable as initOnce memory (end of workspace) ++ * 2. Another initOnce buffer that has been allocated before (and so was previously memset) ++ * 3. An ASAN redzone, in which case we don't want to write on it ++ * For these reasons it should be fine to not explicitly zero every byte up to ws->initOnceStart. ++ * Note that we assume here that MSAN and ASAN cannot run in the same time. */ ++ ZSTD_memset(ptr, 0, MIN((size_t)((U8*)ws->initOnceStart - (U8*)ptr), alignedBytes)); ++ ws->initOnceStart = ptr; ++ } ++ return ptr; ++} ++ ++/* ++ * Reserves and returns memory sized on and aligned on ZSTD_CWKSP_ALIGNMENT_BYTES (64 bytes). + */ + MEM_STATIC void* ZSTD_cwksp_reserve_aligned(ZSTD_cwksp* ws, size_t bytes) + { +@@ -361,13 +401,17 @@ + */ + MEM_STATIC void* ZSTD_cwksp_reserve_table(ZSTD_cwksp* ws, size_t bytes) + { +- const ZSTD_cwksp_alloc_phase_e phase = ZSTD_cwksp_alloc_aligned; ++ const ZSTD_cwksp_alloc_phase_e phase = ZSTD_cwksp_alloc_aligned_init_once; + void* alloc; + void* end; + void* top; + +- if (ZSTD_isError(ZSTD_cwksp_internal_advance_phase(ws, phase))) { +- return NULL; ++ /* We can only start allocating tables after we are done reserving space for objects at the ++ * start of the workspace */ ++ if(ws->phase < phase) { ++ if (ZSTD_isError(ZSTD_cwksp_internal_advance_phase(ws, phase))) { ++ return NULL; ++ } + } + alloc = ws->tableEnd; + end = (BYTE *)alloc + bytes; +@@ -451,7 +495,7 @@ + assert(ws->tableValidEnd >= ws->objectEnd); + assert(ws->tableValidEnd <= ws->allocStart); + if (ws->tableValidEnd < ws->tableEnd) { +- ZSTD_memset(ws->tableValidEnd, 0, (BYTE*)ws->tableEnd - (BYTE*)ws->tableValidEnd); ++ ZSTD_memset(ws->tableValidEnd, 0, (size_t)((BYTE*)ws->tableEnd - (BYTE*)ws->tableValidEnd)); + } + ZSTD_cwksp_mark_tables_clean(ws); + } +@@ -478,10 +522,10 @@ + + + ws->tableEnd = ws->objectEnd; +- ws->allocStart = ws->workspaceEnd; ++ ws->allocStart = ZSTD_cwksp_initialAllocStart(ws); + ws->allocFailed = 0; +- if (ws->phase > ZSTD_cwksp_alloc_buffers) { +- ws->phase = ZSTD_cwksp_alloc_buffers; ++ if (ws->phase > ZSTD_cwksp_alloc_aligned_init_once) { ++ ws->phase = ZSTD_cwksp_alloc_aligned_init_once; + } + ZSTD_cwksp_assert_internal_consistency(ws); + } +@@ -498,6 +542,7 @@ + ws->workspaceEnd = (BYTE*)start + size; + ws->objectEnd = ws->workspace; + ws->tableValidEnd = ws->objectEnd; ++ ws->initOnceStart = ZSTD_cwksp_initialAllocStart(ws); + ws->phase = ZSTD_cwksp_alloc_objects; + ws->isStatic = isStatic; + ZSTD_cwksp_clear(ws); +@@ -550,17 +595,11 @@ + * Returns if the estimated space needed for a wksp is within an acceptable limit of the + * actual amount of space used. + */ +-MEM_STATIC int ZSTD_cwksp_estimated_space_within_bounds(const ZSTD_cwksp* const ws, +- size_t const estimatedSpace, int resizedWorkspace) { +- if (resizedWorkspace) { +- /* Resized/newly allocated wksp should have exact bounds */ +- return ZSTD_cwksp_used(ws) == estimatedSpace; +- } else { +- /* Due to alignment, when reusing a workspace, we can actually consume 63 fewer or more bytes +- * than estimatedSpace. See the comments in zstd_cwksp.h for details. +- */ +- return (ZSTD_cwksp_used(ws) >= estimatedSpace - 63) && (ZSTD_cwksp_used(ws) <= estimatedSpace + 63); +- } ++MEM_STATIC int ZSTD_cwksp_estimated_space_within_bounds(const ZSTD_cwksp *const ws, size_t const estimatedSpace) { ++ /* We have an alignment space between objects and tables between tables and buffers, so we can have up to twice ++ * the alignment bytes difference between estimation and actual usage */ ++ return (estimatedSpace - ZSTD_cwksp_slack_space_required()) <= ZSTD_cwksp_used(ws) && ++ ZSTD_cwksp_used(ws) <= estimatedSpace; + } + + +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_double_fast.c b/lib/zstd/compress/zstd_double_fast.c +--- a/lib/zstd/compress/zstd_double_fast.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_double_fast.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -11,8 +12,43 @@ + #include "zstd_compress_internal.h" + #include "zstd_double_fast.h" + ++static void ZSTD_fillDoubleHashTableForCDict(ZSTD_matchState_t* ms, ++ void const* end, ZSTD_dictTableLoadMethod_e dtlm) ++{ ++ const ZSTD_compressionParameters* const cParams = &ms->cParams; ++ U32* const hashLarge = ms->hashTable; ++ U32 const hBitsL = cParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; ++ U32 const mls = cParams->minMatch; ++ U32* const hashSmall = ms->chainTable; ++ U32 const hBitsS = cParams->chainLog + ZSTD_SHORT_CACHE_TAG_BITS; ++ const BYTE* const base = ms->window.base; ++ const BYTE* ip = base + ms->nextToUpdate; ++ const BYTE* const iend = ((const BYTE*)end) - HASH_READ_SIZE; ++ const U32 fastHashFillStep = 3; + +-void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, ++ /* Always insert every fastHashFillStep position into the hash tables. ++ * Insert the other positions into the large hash table if their entry ++ * is empty. ++ */ ++ for (; ip + fastHashFillStep - 1 <= iend; ip += fastHashFillStep) { ++ U32 const curr = (U32)(ip - base); ++ U32 i; ++ for (i = 0; i < fastHashFillStep; ++i) { ++ size_t const smHashAndTag = ZSTD_hashPtr(ip + i, hBitsS, mls); ++ size_t const lgHashAndTag = ZSTD_hashPtr(ip + i, hBitsL, 8); ++ if (i == 0) { ++ ZSTD_writeTaggedIndex(hashSmall, smHashAndTag, curr + i); ++ } ++ if (i == 0 || hashLarge[lgHashAndTag >> ZSTD_SHORT_CACHE_TAG_BITS] == 0) { ++ ZSTD_writeTaggedIndex(hashLarge, lgHashAndTag, curr + i); ++ } ++ /* Only load extra positions for ZSTD_dtlm_full */ ++ if (dtlm == ZSTD_dtlm_fast) ++ break; ++ } } ++} ++ ++static void ZSTD_fillDoubleHashTableForCCtx(ZSTD_matchState_t* ms, + void const* end, ZSTD_dictTableLoadMethod_e dtlm) + { + const ZSTD_compressionParameters* const cParams = &ms->cParams; +@@ -43,7 +79,19 @@ + /* Only load extra positions for ZSTD_dtlm_full */ + if (dtlm == ZSTD_dtlm_fast) + break; +- } } ++ } } ++} ++ ++void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, ++ const void* const end, ++ ZSTD_dictTableLoadMethod_e dtlm, ++ ZSTD_tableFillPurpose_e tfp) ++{ ++ if (tfp == ZSTD_tfp_forCDict) { ++ ZSTD_fillDoubleHashTableForCDict(ms, end, dtlm); ++ } else { ++ ZSTD_fillDoubleHashTableForCCtx(ms, end, dtlm); ++ } + } + + +@@ -67,7 +115,7 @@ + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - HASH_READ_SIZE; + U32 offset_1=rep[0], offset_2=rep[1]; +- U32 offsetSaved = 0; ++ U32 offsetSaved1 = 0, offsetSaved2 = 0; + + size_t mLength; + U32 offset; +@@ -100,8 +148,8 @@ + U32 const current = (U32)(ip - base); + U32 const windowLow = ZSTD_getLowestPrefixIndex(ms, current, cParams->windowLog); + U32 const maxRep = current - windowLow; +- if (offset_2 > maxRep) offsetSaved = offset_2, offset_2 = 0; +- if (offset_1 > maxRep) offsetSaved = offset_1, offset_1 = 0; ++ if (offset_2 > maxRep) offsetSaved2 = offset_2, offset_2 = 0; ++ if (offset_1 > maxRep) offsetSaved1 = offset_1, offset_1 = 0; + } + + /* Outer Loop: one iteration per match found and stored */ +@@ -131,7 +179,7 @@ + if ((offset_1 > 0) & (MEM_read32(ip+1-offset_1) == MEM_read32(ip+1))) { + mLength = ZSTD_count(ip+1+4, ip+1+4-offset_1, iend) + 4; + ip++; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_REPCODE_1, mLength); ++ ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); + goto _match_stored; + } + +@@ -175,9 +223,13 @@ + } while (ip1 <= ilimit); + + _cleanup: ++ /* If offset_1 started invalid (offsetSaved1 != 0) and became valid (offset_1 != 0), ++ * rotate saved offsets. See comment in ZSTD_compressBlock_fast_noDict for more context. */ ++ offsetSaved2 = ((offsetSaved1 != 0) && (offset_1 != 0)) ? offsetSaved1 : offsetSaved2; ++ + /* save reps for next block */ +- rep[0] = offset_1 ? offset_1 : offsetSaved; +- rep[1] = offset_2 ? offset_2 : offsetSaved; ++ rep[0] = offset_1 ? offset_1 : offsetSaved1; ++ rep[1] = offset_2 ? offset_2 : offsetSaved2; + + /* Return the last literals size */ + return (size_t)(iend - anchor); +@@ -217,7 +269,7 @@ + hashLong[hl1] = (U32)(ip1 - base); + } + +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_OFFSET(offset), mLength); ++ ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); + + _match_stored: + /* match found */ +@@ -243,7 +295,7 @@ + U32 const tmpOff = offset_2; offset_2 = offset_1; offset_1 = tmpOff; /* swap offset_2 <=> offset_1 */ + hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = (U32)(ip-base); + hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = (U32)(ip-base); +- ZSTD_storeSeq(seqStore, 0, anchor, iend, STORE_REPCODE_1, rLength); ++ ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, rLength); + ip += rLength; + anchor = ip; + continue; /* faster when present ... (?) */ +@@ -275,7 +327,6 @@ + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - HASH_READ_SIZE; + U32 offset_1=rep[0], offset_2=rep[1]; +- U32 offsetSaved = 0; + + const ZSTD_matchState_t* const dms = ms->dictMatchState; + const ZSTD_compressionParameters* const dictCParams = &dms->cParams; +@@ -286,8 +337,8 @@ + const BYTE* const dictStart = dictBase + dictStartIndex; + const BYTE* const dictEnd = dms->window.nextSrc; + const U32 dictIndexDelta = prefixLowestIndex - (U32)(dictEnd - dictBase); +- const U32 dictHBitsL = dictCParams->hashLog; +- const U32 dictHBitsS = dictCParams->chainLog; ++ const U32 dictHBitsL = dictCParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; ++ const U32 dictHBitsS = dictCParams->chainLog + ZSTD_SHORT_CACHE_TAG_BITS; + const U32 dictAndPrefixLength = (U32)((ip - prefixLowest) + (dictEnd - dictStart)); + + DEBUGLOG(5, "ZSTD_compressBlock_doubleFast_dictMatchState_generic"); +@@ -295,6 +346,13 @@ + /* if a dictionary is attached, it must be within window range */ + assert(ms->window.dictLimit + (1U << cParams->windowLog) >= endIndex); + ++ if (ms->prefetchCDictTables) { ++ size_t const hashTableBytes = (((size_t)1) << dictCParams->hashLog) * sizeof(U32); ++ size_t const chainTableBytes = (((size_t)1) << dictCParams->chainLog) * sizeof(U32); ++ PREFETCH_AREA(dictHashLong, hashTableBytes) ++ PREFETCH_AREA(dictHashSmall, chainTableBytes) ++ } ++ + /* init */ + ip += (dictAndPrefixLength == 0); + +@@ -309,8 +367,12 @@ + U32 offset; + size_t const h2 = ZSTD_hashPtr(ip, hBitsL, 8); + size_t const h = ZSTD_hashPtr(ip, hBitsS, mls); +- size_t const dictHL = ZSTD_hashPtr(ip, dictHBitsL, 8); +- size_t const dictHS = ZSTD_hashPtr(ip, dictHBitsS, mls); ++ size_t const dictHashAndTagL = ZSTD_hashPtr(ip, dictHBitsL, 8); ++ size_t const dictHashAndTagS = ZSTD_hashPtr(ip, dictHBitsS, mls); ++ U32 const dictMatchIndexAndTagL = dictHashLong[dictHashAndTagL >> ZSTD_SHORT_CACHE_TAG_BITS]; ++ U32 const dictMatchIndexAndTagS = dictHashSmall[dictHashAndTagS >> ZSTD_SHORT_CACHE_TAG_BITS]; ++ int const dictTagsMatchL = ZSTD_comparePackedTags(dictMatchIndexAndTagL, dictHashAndTagL); ++ int const dictTagsMatchS = ZSTD_comparePackedTags(dictMatchIndexAndTagS, dictHashAndTagS); + U32 const curr = (U32)(ip-base); + U32 const matchIndexL = hashLong[h2]; + U32 matchIndexS = hashSmall[h]; +@@ -328,7 +390,7 @@ + const BYTE* repMatchEnd = repIndex < prefixLowestIndex ? dictEnd : iend; + mLength = ZSTD_count_2segments(ip+1+4, repMatch+4, iend, repMatchEnd, prefixLowest) + 4; + ip++; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_REPCODE_1, mLength); ++ ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); + goto _match_stored; + } + +@@ -340,9 +402,9 @@ + while (((ip>anchor) & (matchLong>prefixLowest)) && (ip[-1] == matchLong[-1])) { ip--; matchLong--; mLength++; } /* catch up */ + goto _match_found; + } +- } else { ++ } else if (dictTagsMatchL) { + /* check dictMatchState long match */ +- U32 const dictMatchIndexL = dictHashLong[dictHL]; ++ U32 const dictMatchIndexL = dictMatchIndexAndTagL >> ZSTD_SHORT_CACHE_TAG_BITS; + const BYTE* dictMatchL = dictBase + dictMatchIndexL; + assert(dictMatchL < dictEnd); + +@@ -358,9 +420,9 @@ + if (MEM_read32(match) == MEM_read32(ip)) { + goto _search_next_long; + } +- } else { ++ } else if (dictTagsMatchS) { + /* check dictMatchState short match */ +- U32 const dictMatchIndexS = dictHashSmall[dictHS]; ++ U32 const dictMatchIndexS = dictMatchIndexAndTagS >> ZSTD_SHORT_CACHE_TAG_BITS; + match = dictBase + dictMatchIndexS; + matchIndexS = dictMatchIndexS + dictIndexDelta; + +@@ -375,10 +437,11 @@ + continue; + + _search_next_long: +- + { size_t const hl3 = ZSTD_hashPtr(ip+1, hBitsL, 8); +- size_t const dictHLNext = ZSTD_hashPtr(ip+1, dictHBitsL, 8); ++ size_t const dictHashAndTagL3 = ZSTD_hashPtr(ip+1, dictHBitsL, 8); + U32 const matchIndexL3 = hashLong[hl3]; ++ U32 const dictMatchIndexAndTagL3 = dictHashLong[dictHashAndTagL3 >> ZSTD_SHORT_CACHE_TAG_BITS]; ++ int const dictTagsMatchL3 = ZSTD_comparePackedTags(dictMatchIndexAndTagL3, dictHashAndTagL3); + const BYTE* matchL3 = base + matchIndexL3; + hashLong[hl3] = curr + 1; + +@@ -391,9 +454,9 @@ + while (((ip>anchor) & (matchL3>prefixLowest)) && (ip[-1] == matchL3[-1])) { ip--; matchL3--; mLength++; } /* catch up */ + goto _match_found; + } +- } else { ++ } else if (dictTagsMatchL3) { + /* check dict long +1 match */ +- U32 const dictMatchIndexL3 = dictHashLong[dictHLNext]; ++ U32 const dictMatchIndexL3 = dictMatchIndexAndTagL3 >> ZSTD_SHORT_CACHE_TAG_BITS; + const BYTE* dictMatchL3 = dictBase + dictMatchIndexL3; + assert(dictMatchL3 < dictEnd); + if (dictMatchL3 > dictStart && MEM_read64(dictMatchL3) == MEM_read64(ip+1)) { +@@ -419,7 +482,7 @@ + offset_2 = offset_1; + offset_1 = offset; + +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_OFFSET(offset), mLength); ++ ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); + + _match_stored: + /* match found */ +@@ -448,7 +511,7 @@ + const BYTE* const repEnd2 = repIndex2 < prefixLowestIndex ? dictEnd : iend; + size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixLowest) + 4; + U32 tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; /* swap offset_2 <=> offset_1 */ +- ZSTD_storeSeq(seqStore, 0, anchor, iend, STORE_REPCODE_1, repLength2); ++ ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); + hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = current2; + hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = current2; + ip += repLength2; +@@ -461,8 +524,8 @@ + } /* while (ip < ilimit) */ + + /* save reps for next block */ +- rep[0] = offset_1 ? offset_1 : offsetSaved; +- rep[1] = offset_2 ? offset_2 : offsetSaved; ++ rep[0] = offset_1; ++ rep[1] = offset_2; + + /* Return the last literals size */ + return (size_t)(iend - anchor); +@@ -585,7 +648,7 @@ + const BYTE* repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; + mLength = ZSTD_count_2segments(ip+1+4, repMatch+4, iend, repMatchEnd, prefixStart) + 4; + ip++; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_REPCODE_1, mLength); ++ ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); + } else { + if ((matchLongIndex > dictStartIndex) && (MEM_read64(matchLong) == MEM_read64(ip))) { + const BYTE* const matchEnd = matchLongIndex < prefixStartIndex ? dictEnd : iend; +@@ -596,7 +659,7 @@ + while (((ip>anchor) & (matchLong>lowMatchPtr)) && (ip[-1] == matchLong[-1])) { ip--; matchLong--; mLength++; } /* catch up */ + offset_2 = offset_1; + offset_1 = offset; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_OFFSET(offset), mLength); ++ ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); + + } else if ((matchIndex > dictStartIndex) && (MEM_read32(match) == MEM_read32(ip))) { + size_t const h3 = ZSTD_hashPtr(ip+1, hBitsL, 8); +@@ -621,7 +684,7 @@ + } + offset_2 = offset_1; + offset_1 = offset; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_OFFSET(offset), mLength); ++ ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); + + } else { + ip += ((ip-anchor) >> kSearchStrength) + 1; +@@ -653,7 +716,7 @@ + const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; + size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; + U32 const tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; /* swap offset_2 <=> offset_1 */ +- ZSTD_storeSeq(seqStore, 0, anchor, iend, STORE_REPCODE_1, repLength2); ++ ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); + hashSmall[ZSTD_hashPtr(ip, hBitsS, mls)] = current2; + hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = current2; + ip += repLength2; +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_double_fast.h b/lib/zstd/compress/zstd_double_fast.h +--- a/lib/zstd/compress/zstd_double_fast.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_double_fast.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -16,7 +17,8 @@ + #include "zstd_compress_internal.h" /* ZSTD_CCtx, size_t */ + + void ZSTD_fillDoubleHashTable(ZSTD_matchState_t* ms, +- void const* end, ZSTD_dictTableLoadMethod_e dtlm); ++ void const* end, ZSTD_dictTableLoadMethod_e dtlm, ++ ZSTD_tableFillPurpose_e tfp); + size_t ZSTD_compressBlock_doubleFast( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_fast.c b/lib/zstd/compress/zstd_fast.c +--- a/lib/zstd/compress/zstd_fast.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_fast.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -11,8 +12,42 @@ + #include "zstd_compress_internal.h" /* ZSTD_hashPtr, ZSTD_count, ZSTD_storeSeq */ + #include "zstd_fast.h" + ++static void ZSTD_fillHashTableForCDict(ZSTD_matchState_t* ms, ++ const void* const end, ++ ZSTD_dictTableLoadMethod_e dtlm) ++{ ++ const ZSTD_compressionParameters* const cParams = &ms->cParams; ++ U32* const hashTable = ms->hashTable; ++ U32 const hBits = cParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; ++ U32 const mls = cParams->minMatch; ++ const BYTE* const base = ms->window.base; ++ const BYTE* ip = base + ms->nextToUpdate; ++ const BYTE* const iend = ((const BYTE*)end) - HASH_READ_SIZE; ++ const U32 fastHashFillStep = 3; + +-void ZSTD_fillHashTable(ZSTD_matchState_t* ms, ++ /* Currently, we always use ZSTD_dtlm_full for filling CDict tables. ++ * Feel free to remove this assert if there's a good reason! */ ++ assert(dtlm == ZSTD_dtlm_full); ++ ++ /* Always insert every fastHashFillStep position into the hash table. ++ * Insert the other positions if their hash entry is empty. ++ */ ++ for ( ; ip + fastHashFillStep < iend + 2; ip += fastHashFillStep) { ++ U32 const curr = (U32)(ip - base); ++ { size_t const hashAndTag = ZSTD_hashPtr(ip, hBits, mls); ++ ZSTD_writeTaggedIndex(hashTable, hashAndTag, curr); } ++ ++ if (dtlm == ZSTD_dtlm_fast) continue; ++ /* Only load extra positions for ZSTD_dtlm_full */ ++ { U32 p; ++ for (p = 1; p < fastHashFillStep; ++p) { ++ size_t const hashAndTag = ZSTD_hashPtr(ip + p, hBits, mls); ++ if (hashTable[hashAndTag >> ZSTD_SHORT_CACHE_TAG_BITS] == 0) { /* not yet filled */ ++ ZSTD_writeTaggedIndex(hashTable, hashAndTag, curr + p); ++ } } } } ++} ++ ++static void ZSTD_fillHashTableForCCtx(ZSTD_matchState_t* ms, + const void* const end, + ZSTD_dictTableLoadMethod_e dtlm) + { +@@ -25,6 +60,10 @@ + const BYTE* const iend = ((const BYTE*)end) - HASH_READ_SIZE; + const U32 fastHashFillStep = 3; + ++ /* Currently, we always use ZSTD_dtlm_fast for filling CCtx tables. ++ * Feel free to remove this assert if there's a good reason! */ ++ assert(dtlm == ZSTD_dtlm_fast); ++ + /* Always insert every fastHashFillStep position into the hash table. + * Insert the other positions if their hash entry is empty. + */ +@@ -42,6 +81,18 @@ + } } } } + } + ++void ZSTD_fillHashTable(ZSTD_matchState_t* ms, ++ const void* const end, ++ ZSTD_dictTableLoadMethod_e dtlm, ++ ZSTD_tableFillPurpose_e tfp) ++{ ++ if (tfp == ZSTD_tfp_forCDict) { ++ ZSTD_fillHashTableForCDict(ms, end, dtlm); ++ } else { ++ ZSTD_fillHashTableForCCtx(ms, end, dtlm); ++ } ++} ++ + + /* + * If you squint hard enough (and ignore repcodes), the search operation at any +@@ -117,7 +168,7 @@ + + U32 rep_offset1 = rep[0]; + U32 rep_offset2 = rep[1]; +- U32 offsetSaved = 0; ++ U32 offsetSaved1 = 0, offsetSaved2 = 0; + + size_t hash0; /* hash for ip0 */ + size_t hash1; /* hash for ip1 */ +@@ -141,8 +192,8 @@ + { U32 const curr = (U32)(ip0 - base); + U32 const windowLow = ZSTD_getLowestPrefixIndex(ms, curr, cParams->windowLog); + U32 const maxRep = curr - windowLow; +- if (rep_offset2 > maxRep) offsetSaved = rep_offset2, rep_offset2 = 0; +- if (rep_offset1 > maxRep) offsetSaved = rep_offset1, rep_offset1 = 0; ++ if (rep_offset2 > maxRep) offsetSaved2 = rep_offset2, rep_offset2 = 0; ++ if (rep_offset1 > maxRep) offsetSaved1 = rep_offset1, rep_offset1 = 0; + } + + /* start each op */ +@@ -180,8 +231,14 @@ + mLength = ip0[-1] == match0[-1]; + ip0 -= mLength; + match0 -= mLength; +- offcode = STORE_REPCODE_1; ++ offcode = REPCODE1_TO_OFFBASE; + mLength += 4; ++ ++ /* First write next hash table entry; we've already calculated it. ++ * This write is known to be safe because the ip1 is before the ++ * repcode (ip2). */ ++ hashTable[hash1] = (U32)(ip1 - base); ++ + goto _match; + } + +@@ -195,6 +252,12 @@ + /* check match at ip[0] */ + if (MEM_read32(ip0) == mval) { + /* found a match! */ ++ ++ /* First write next hash table entry; we've already calculated it. ++ * This write is known to be safe because the ip1 == ip0 + 1, so ++ * we know we will resume searching after ip1 */ ++ hashTable[hash1] = (U32)(ip1 - base); ++ + goto _offset; + } + +@@ -224,6 +287,21 @@ + /* check match at ip[0] */ + if (MEM_read32(ip0) == mval) { + /* found a match! */ ++ ++ /* first write next hash table entry; we've already calculated it */ ++ if (step <= 4) { ++ /* We need to avoid writing an index into the hash table >= the ++ * position at which we will pick up our searching after we've ++ * taken this match. ++ * ++ * The minimum possible match has length 4, so the earliest ip0 ++ * can be after we take this match will be the current ip0 + 4. ++ * ip1 is ip0 + step - 1. If ip1 is >= ip0 + 4, we can't safely ++ * write this position. ++ */ ++ hashTable[hash1] = (U32)(ip1 - base); ++ } ++ + goto _offset; + } + +@@ -254,9 +332,24 @@ + * However, it seems to be a meaningful performance hit to try to search + * them. So let's not. */ + ++ /* When the repcodes are outside of the prefix, we set them to zero before the loop. ++ * When the offsets are still zero, we need to restore them after the block to have a correct ++ * repcode history. If only one offset was invalid, it is easy. The tricky case is when both ++ * offsets were invalid. We need to figure out which offset to refill with. ++ * - If both offsets are zero they are in the same order. ++ * - If both offsets are non-zero, we won't restore the offsets from `offsetSaved[12]`. ++ * - If only one is zero, we need to decide which offset to restore. ++ * - If rep_offset1 is non-zero, then rep_offset2 must be offsetSaved1. ++ * - It is impossible for rep_offset2 to be non-zero. ++ * ++ * So if rep_offset1 started invalid (offsetSaved1 != 0) and became valid (rep_offset1 != 0), then ++ * set rep[0] = rep_offset1 and rep[1] = offsetSaved1. ++ */ ++ offsetSaved2 = ((offsetSaved1 != 0) && (rep_offset1 != 0)) ? offsetSaved1 : offsetSaved2; ++ + /* save reps for next block */ +- rep[0] = rep_offset1 ? rep_offset1 : offsetSaved; +- rep[1] = rep_offset2 ? rep_offset2 : offsetSaved; ++ rep[0] = rep_offset1 ? rep_offset1 : offsetSaved1; ++ rep[1] = rep_offset2 ? rep_offset2 : offsetSaved2; + + /* Return the last literals size */ + return (size_t)(iend - anchor); +@@ -267,7 +360,7 @@ + match0 = base + idx; + rep_offset2 = rep_offset1; + rep_offset1 = (U32)(ip0-match0); +- offcode = STORE_OFFSET(rep_offset1); ++ offcode = OFFSET_TO_OFFBASE(rep_offset1); + mLength = 4; + + /* Count the backwards match length. */ +@@ -287,11 +380,6 @@ + ip0 += mLength; + anchor = ip0; + +- /* write next hash table entry */ +- if (ip1 < ip0) { +- hashTable[hash1] = (U32)(ip1 - base); +- } +- + /* Fill table and check for immediate repcode. */ + if (ip0 <= ilimit) { + /* Fill Table */ +@@ -306,7 +394,7 @@ + { U32 const tmpOff = rep_offset2; rep_offset2 = rep_offset1; rep_offset1 = tmpOff; } /* swap rep_offset2 <=> rep_offset1 */ + hashTable[ZSTD_hashPtr(ip0, hlog, mls)] = (U32)(ip0-base); + ip0 += rLength; +- ZSTD_storeSeq(seqStore, 0 /*litLen*/, anchor, iend, STORE_REPCODE_1, rLength); ++ ZSTD_storeSeq(seqStore, 0 /*litLen*/, anchor, iend, REPCODE1_TO_OFFBASE, rLength); + anchor = ip0; + continue; /* faster when present (confirmed on gcc-8) ... (?) */ + } } } +@@ -380,14 +468,14 @@ + U32 const stepSize = cParams->targetLength + !(cParams->targetLength); + const BYTE* const base = ms->window.base; + const BYTE* const istart = (const BYTE*)src; +- const BYTE* ip = istart; ++ const BYTE* ip0 = istart; ++ const BYTE* ip1 = ip0 + stepSize; /* we assert below that stepSize >= 1 */ + const BYTE* anchor = istart; + const U32 prefixStartIndex = ms->window.dictLimit; + const BYTE* const prefixStart = base + prefixStartIndex; + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - HASH_READ_SIZE; + U32 offset_1=rep[0], offset_2=rep[1]; +- U32 offsetSaved = 0; + + const ZSTD_matchState_t* const dms = ms->dictMatchState; + const ZSTD_compressionParameters* const dictCParams = &dms->cParams ; +@@ -397,13 +485,13 @@ + const BYTE* const dictStart = dictBase + dictStartIndex; + const BYTE* const dictEnd = dms->window.nextSrc; + const U32 dictIndexDelta = prefixStartIndex - (U32)(dictEnd - dictBase); +- const U32 dictAndPrefixLength = (U32)(ip - prefixStart + dictEnd - dictStart); +- const U32 dictHLog = dictCParams->hashLog; ++ const U32 dictAndPrefixLength = (U32)(istart - prefixStart + dictEnd - dictStart); ++ const U32 dictHBits = dictCParams->hashLog + ZSTD_SHORT_CACHE_TAG_BITS; + + /* if a dictionary is still attached, it necessarily means that + * it is within window size. So we just check it. */ + const U32 maxDistance = 1U << cParams->windowLog; +- const U32 endIndex = (U32)((size_t)(ip - base) + srcSize); ++ const U32 endIndex = (U32)((size_t)(istart - base) + srcSize); + assert(endIndex - prefixStartIndex <= maxDistance); + (void)maxDistance; (void)endIndex; /* these variables are not used when assert() is disabled */ + +@@ -413,106 +501,155 @@ + * when translating a dict index into a local index */ + assert(prefixStartIndex >= (U32)(dictEnd - dictBase)); + ++ if (ms->prefetchCDictTables) { ++ size_t const hashTableBytes = (((size_t)1) << dictCParams->hashLog) * sizeof(U32); ++ PREFETCH_AREA(dictHashTable, hashTableBytes) ++ } ++ + /* init */ + DEBUGLOG(5, "ZSTD_compressBlock_fast_dictMatchState_generic"); +- ip += (dictAndPrefixLength == 0); ++ ip0 += (dictAndPrefixLength == 0); + /* dictMatchState repCode checks don't currently handle repCode == 0 + * disabling. */ + assert(offset_1 <= dictAndPrefixLength); + assert(offset_2 <= dictAndPrefixLength); + +- /* Main Search Loop */ +- while (ip < ilimit) { /* < instead of <=, because repcode check at (ip+1) */ ++ /* Outer search loop */ ++ assert(stepSize >= 1); ++ while (ip1 <= ilimit) { /* repcode check at (ip0 + 1) is safe because ip0 < ip1 */ + size_t mLength; +- size_t const h = ZSTD_hashPtr(ip, hlog, mls); +- U32 const curr = (U32)(ip-base); +- U32 const matchIndex = hashTable[h]; +- const BYTE* match = base + matchIndex; +- const U32 repIndex = curr + 1 - offset_1; +- const BYTE* repMatch = (repIndex < prefixStartIndex) ? +- dictBase + (repIndex - dictIndexDelta) : +- base + repIndex; +- hashTable[h] = curr; /* update hash table */ +- +- if ( ((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow : ensure repIndex isn't overlapping dict + prefix */ +- && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) { +- const BYTE* const repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; +- mLength = ZSTD_count_2segments(ip+1+4, repMatch+4, iend, repMatchEnd, prefixStart) + 4; +- ip++; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_REPCODE_1, mLength); +- } else if ( (matchIndex <= prefixStartIndex) ) { +- size_t const dictHash = ZSTD_hashPtr(ip, dictHLog, mls); +- U32 const dictMatchIndex = dictHashTable[dictHash]; +- const BYTE* dictMatch = dictBase + dictMatchIndex; +- if (dictMatchIndex <= dictStartIndex || +- MEM_read32(dictMatch) != MEM_read32(ip)) { +- assert(stepSize >= 1); +- ip += ((ip-anchor) >> kSearchStrength) + stepSize; +- continue; +- } else { +- /* found a dict match */ +- U32 const offset = (U32)(curr-dictMatchIndex-dictIndexDelta); +- mLength = ZSTD_count_2segments(ip+4, dictMatch+4, iend, dictEnd, prefixStart) + 4; +- while (((ip>anchor) & (dictMatch>dictStart)) +- && (ip[-1] == dictMatch[-1])) { +- ip--; dictMatch--; mLength++; ++ size_t hash0 = ZSTD_hashPtr(ip0, hlog, mls); ++ ++ size_t const dictHashAndTag0 = ZSTD_hashPtr(ip0, dictHBits, mls); ++ U32 dictMatchIndexAndTag = dictHashTable[dictHashAndTag0 >> ZSTD_SHORT_CACHE_TAG_BITS]; ++ int dictTagsMatch = ZSTD_comparePackedTags(dictMatchIndexAndTag, dictHashAndTag0); ++ ++ U32 matchIndex = hashTable[hash0]; ++ U32 curr = (U32)(ip0 - base); ++ size_t step = stepSize; ++ const size_t kStepIncr = 1 << kSearchStrength; ++ const BYTE* nextStep = ip0 + kStepIncr; ++ ++ /* Inner search loop */ ++ while (1) { ++ const BYTE* match = base + matchIndex; ++ const U32 repIndex = curr + 1 - offset_1; ++ const BYTE* repMatch = (repIndex < prefixStartIndex) ? ++ dictBase + (repIndex - dictIndexDelta) : ++ base + repIndex; ++ const size_t hash1 = ZSTD_hashPtr(ip1, hlog, mls); ++ size_t const dictHashAndTag1 = ZSTD_hashPtr(ip1, dictHBits, mls); ++ hashTable[hash0] = curr; /* update hash table */ ++ ++ if (((U32) ((prefixStartIndex - 1) - repIndex) >= ++ 3) /* intentional underflow : ensure repIndex isn't overlapping dict + prefix */ ++ && (MEM_read32(repMatch) == MEM_read32(ip0 + 1))) { ++ const BYTE* const repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; ++ mLength = ZSTD_count_2segments(ip0 + 1 + 4, repMatch + 4, iend, repMatchEnd, prefixStart) + 4; ++ ip0++; ++ ZSTD_storeSeq(seqStore, (size_t) (ip0 - anchor), anchor, iend, REPCODE1_TO_OFFBASE, mLength); ++ break; ++ } ++ ++ if (dictTagsMatch) { ++ /* Found a possible dict match */ ++ const U32 dictMatchIndex = dictMatchIndexAndTag >> ZSTD_SHORT_CACHE_TAG_BITS; ++ const BYTE* dictMatch = dictBase + dictMatchIndex; ++ if (dictMatchIndex > dictStartIndex && ++ MEM_read32(dictMatch) == MEM_read32(ip0)) { ++ /* To replicate extDict parse behavior, we only use dict matches when the normal matchIndex is invalid */ ++ if (matchIndex <= prefixStartIndex) { ++ U32 const offset = (U32) (curr - dictMatchIndex - dictIndexDelta); ++ mLength = ZSTD_count_2segments(ip0 + 4, dictMatch + 4, iend, dictEnd, prefixStart) + 4; ++ while (((ip0 > anchor) & (dictMatch > dictStart)) ++ && (ip0[-1] == dictMatch[-1])) { ++ ip0--; ++ dictMatch--; ++ mLength++; ++ } /* catch up */ ++ offset_2 = offset_1; ++ offset_1 = offset; ++ ZSTD_storeSeq(seqStore, (size_t) (ip0 - anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); ++ break; ++ } ++ } ++ } ++ ++ if (matchIndex > prefixStartIndex && MEM_read32(match) == MEM_read32(ip0)) { ++ /* found a regular match */ ++ U32 const offset = (U32) (ip0 - match); ++ mLength = ZSTD_count(ip0 + 4, match + 4, iend) + 4; ++ while (((ip0 > anchor) & (match > prefixStart)) ++ && (ip0[-1] == match[-1])) { ++ ip0--; ++ match--; ++ mLength++; + } /* catch up */ + offset_2 = offset_1; + offset_1 = offset; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_OFFSET(offset), mLength); ++ ZSTD_storeSeq(seqStore, (size_t) (ip0 - anchor), anchor, iend, OFFSET_TO_OFFBASE(offset), mLength); ++ break; + } +- } else if (MEM_read32(match) != MEM_read32(ip)) { +- /* it's not a match, and we're not going to check the dictionary */ +- assert(stepSize >= 1); +- ip += ((ip-anchor) >> kSearchStrength) + stepSize; +- continue; +- } else { +- /* found a regular match */ +- U32 const offset = (U32)(ip-match); +- mLength = ZSTD_count(ip+4, match+4, iend) + 4; +- while (((ip>anchor) & (match>prefixStart)) +- && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ +- offset_2 = offset_1; +- offset_1 = offset; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_OFFSET(offset), mLength); +- } ++ ++ /* Prepare for next iteration */ ++ dictMatchIndexAndTag = dictHashTable[dictHashAndTag1 >> ZSTD_SHORT_CACHE_TAG_BITS]; ++ dictTagsMatch = ZSTD_comparePackedTags(dictMatchIndexAndTag, dictHashAndTag1); ++ matchIndex = hashTable[hash1]; ++ ++ if (ip1 >= nextStep) { ++ step++; ++ nextStep += kStepIncr; ++ } ++ ip0 = ip1; ++ ip1 = ip1 + step; ++ if (ip1 > ilimit) goto _cleanup; ++ ++ curr = (U32)(ip0 - base); ++ hash0 = hash1; ++ } /* end inner search loop */ + + /* match found */ +- ip += mLength; +- anchor = ip; ++ assert(mLength); ++ ip0 += mLength; ++ anchor = ip0; + +- if (ip <= ilimit) { ++ if (ip0 <= ilimit) { + /* Fill Table */ + assert(base+curr+2 > istart); /* check base overflow */ + hashTable[ZSTD_hashPtr(base+curr+2, hlog, mls)] = curr+2; /* here because curr+2 could be > iend-8 */ +- hashTable[ZSTD_hashPtr(ip-2, hlog, mls)] = (U32)(ip-2-base); ++ hashTable[ZSTD_hashPtr(ip0-2, hlog, mls)] = (U32)(ip0-2-base); + + /* check immediate repcode */ +- while (ip <= ilimit) { +- U32 const current2 = (U32)(ip-base); ++ while (ip0 <= ilimit) { ++ U32 const current2 = (U32)(ip0-base); + U32 const repIndex2 = current2 - offset_2; + const BYTE* repMatch2 = repIndex2 < prefixStartIndex ? + dictBase - dictIndexDelta + repIndex2 : + base + repIndex2; + if ( ((U32)((prefixStartIndex-1) - (U32)repIndex2) >= 3 /* intentional overflow */) +- && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { ++ && (MEM_read32(repMatch2) == MEM_read32(ip0))) { + const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; +- size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; ++ size_t const repLength2 = ZSTD_count_2segments(ip0+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; + U32 tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; /* swap offset_2 <=> offset_1 */ +- ZSTD_storeSeq(seqStore, 0, anchor, iend, STORE_REPCODE_1, repLength2); +- hashTable[ZSTD_hashPtr(ip, hlog, mls)] = current2; +- ip += repLength2; +- anchor = ip; ++ ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); ++ hashTable[ZSTD_hashPtr(ip0, hlog, mls)] = current2; ++ ip0 += repLength2; ++ anchor = ip0; + continue; + } + break; + } + } ++ ++ /* Prepare for next iteration */ ++ assert(ip0 == anchor); ++ ip1 = ip0 + stepSize; + } + ++_cleanup: + /* save reps for next block */ +- rep[0] = offset_1 ? offset_1 : offsetSaved; +- rep[1] = offset_2 ? offset_2 : offsetSaved; ++ rep[0] = offset_1; ++ rep[1] = offset_2; + + /* Return the last literals size */ + return (size_t)(iend - anchor); +@@ -553,11 +690,10 @@ + U32* const hashTable = ms->hashTable; + U32 const hlog = cParams->hashLog; + /* support stepSize of 0 */ +- U32 const stepSize = cParams->targetLength + !(cParams->targetLength); ++ size_t const stepSize = cParams->targetLength + !(cParams->targetLength) + 1; + const BYTE* const base = ms->window.base; + const BYTE* const dictBase = ms->window.dictBase; + const BYTE* const istart = (const BYTE*)src; +- const BYTE* ip = istart; + const BYTE* anchor = istart; + const U32 endIndex = (U32)((size_t)(istart - base) + srcSize); + const U32 lowLimit = ZSTD_getLowestMatchIndex(ms, endIndex, cParams->windowLog); +@@ -570,6 +706,28 @@ + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - 8; + U32 offset_1=rep[0], offset_2=rep[1]; ++ U32 offsetSaved1 = 0, offsetSaved2 = 0; ++ ++ const BYTE* ip0 = istart; ++ const BYTE* ip1; ++ const BYTE* ip2; ++ const BYTE* ip3; ++ U32 current0; ++ ++ ++ size_t hash0; /* hash for ip0 */ ++ size_t hash1; /* hash for ip1 */ ++ U32 idx; /* match idx for ip0 */ ++ const BYTE* idxBase; /* base pointer for idx */ ++ ++ U32 offcode; ++ const BYTE* match0; ++ size_t mLength; ++ const BYTE* matchEnd = 0; /* initialize to avoid warning, assert != 0 later */ ++ ++ size_t step; ++ const BYTE* nextStep; ++ const size_t kStepIncr = (1 << (kSearchStrength - 1)); + + (void)hasStep; /* not currently specialized on whether it's accelerated */ + +@@ -579,75 +737,202 @@ + if (prefixStartIndex == dictStartIndex) + return ZSTD_compressBlock_fast(ms, seqStore, rep, src, srcSize); + +- /* Search Loop */ +- while (ip < ilimit) { /* < instead of <=, because (ip+1) */ +- const size_t h = ZSTD_hashPtr(ip, hlog, mls); +- const U32 matchIndex = hashTable[h]; +- const BYTE* const matchBase = matchIndex < prefixStartIndex ? dictBase : base; +- const BYTE* match = matchBase + matchIndex; +- const U32 curr = (U32)(ip-base); +- const U32 repIndex = curr + 1 - offset_1; +- const BYTE* const repBase = repIndex < prefixStartIndex ? dictBase : base; +- const BYTE* const repMatch = repBase + repIndex; +- hashTable[h] = curr; /* update hash table */ +- DEBUGLOG(7, "offset_1 = %u , curr = %u", offset_1, curr); +- +- if ( ( ((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow */ +- & (offset_1 <= curr+1 - dictStartIndex) ) /* note: we are searching at curr+1 */ +- && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) { +- const BYTE* const repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend; +- size_t const rLength = ZSTD_count_2segments(ip+1 +4, repMatch +4, iend, repMatchEnd, prefixStart) + 4; +- ip++; +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_REPCODE_1, rLength); +- ip += rLength; +- anchor = ip; +- } else { +- if ( (matchIndex < dictStartIndex) || +- (MEM_read32(match) != MEM_read32(ip)) ) { +- assert(stepSize >= 1); +- ip += ((ip-anchor) >> kSearchStrength) + stepSize; +- continue; ++ { U32 const curr = (U32)(ip0 - base); ++ U32 const maxRep = curr - dictStartIndex; ++ if (offset_2 >= maxRep) offsetSaved2 = offset_2, offset_2 = 0; ++ if (offset_1 >= maxRep) offsetSaved1 = offset_1, offset_1 = 0; ++ } ++ ++ /* start each op */ ++_start: /* Requires: ip0 */ ++ ++ step = stepSize; ++ nextStep = ip0 + kStepIncr; ++ ++ /* calculate positions, ip0 - anchor == 0, so we skip step calc */ ++ ip1 = ip0 + 1; ++ ip2 = ip0 + step; ++ ip3 = ip2 + 1; ++ ++ if (ip3 >= ilimit) { ++ goto _cleanup; ++ } ++ ++ hash0 = ZSTD_hashPtr(ip0, hlog, mls); ++ hash1 = ZSTD_hashPtr(ip1, hlog, mls); ++ ++ idx = hashTable[hash0]; ++ idxBase = idx < prefixStartIndex ? dictBase : base; ++ ++ do { ++ { /* load repcode match for ip[2] */ ++ U32 const current2 = (U32)(ip2 - base); ++ U32 const repIndex = current2 - offset_1; ++ const BYTE* const repBase = repIndex < prefixStartIndex ? dictBase : base; ++ U32 rval; ++ if ( ((U32)(prefixStartIndex - repIndex) >= 4) /* intentional underflow */ ++ & (offset_1 > 0) ) { ++ rval = MEM_read32(repBase + repIndex); ++ } else { ++ rval = MEM_read32(ip2) ^ 1; /* guaranteed to not match. */ + } +- { const BYTE* const matchEnd = matchIndex < prefixStartIndex ? dictEnd : iend; +- const BYTE* const lowMatchPtr = matchIndex < prefixStartIndex ? dictStart : prefixStart; +- U32 const offset = curr - matchIndex; +- size_t mLength = ZSTD_count_2segments(ip+4, match+4, iend, matchEnd, prefixStart) + 4; +- while (((ip>anchor) & (match>lowMatchPtr)) && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ +- offset_2 = offset_1; offset_1 = offset; /* update offset history */ +- ZSTD_storeSeq(seqStore, (size_t)(ip-anchor), anchor, iend, STORE_OFFSET(offset), mLength); +- ip += mLength; +- anchor = ip; ++ ++ /* write back hash table entry */ ++ current0 = (U32)(ip0 - base); ++ hashTable[hash0] = current0; ++ ++ /* check repcode at ip[2] */ ++ if (MEM_read32(ip2) == rval) { ++ ip0 = ip2; ++ match0 = repBase + repIndex; ++ matchEnd = repIndex < prefixStartIndex ? dictEnd : iend; ++ assert((match0 != prefixStart) & (match0 != dictStart)); ++ mLength = ip0[-1] == match0[-1]; ++ ip0 -= mLength; ++ match0 -= mLength; ++ offcode = REPCODE1_TO_OFFBASE; ++ mLength += 4; ++ goto _match; + } } + +- if (ip <= ilimit) { +- /* Fill Table */ +- hashTable[ZSTD_hashPtr(base+curr+2, hlog, mls)] = curr+2; +- hashTable[ZSTD_hashPtr(ip-2, hlog, mls)] = (U32)(ip-2-base); +- /* check immediate repcode */ +- while (ip <= ilimit) { +- U32 const current2 = (U32)(ip-base); +- U32 const repIndex2 = current2 - offset_2; +- const BYTE* const repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : base + repIndex2; +- if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3) & (offset_2 <= curr - dictStartIndex)) /* intentional overflow */ +- && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { +- const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; +- size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; +- { U32 const tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; } /* swap offset_2 <=> offset_1 */ +- ZSTD_storeSeq(seqStore, 0 /*litlen*/, anchor, iend, STORE_REPCODE_1, repLength2); +- hashTable[ZSTD_hashPtr(ip, hlog, mls)] = current2; +- ip += repLength2; +- anchor = ip; +- continue; +- } +- break; +- } } } ++ { /* load match for ip[0] */ ++ U32 const mval = idx >= dictStartIndex ? ++ MEM_read32(idxBase + idx) : ++ MEM_read32(ip0) ^ 1; /* guaranteed not to match */ ++ ++ /* check match at ip[0] */ ++ if (MEM_read32(ip0) == mval) { ++ /* found a match! */ ++ goto _offset; ++ } } ++ ++ /* lookup ip[1] */ ++ idx = hashTable[hash1]; ++ idxBase = idx < prefixStartIndex ? dictBase : base; ++ ++ /* hash ip[2] */ ++ hash0 = hash1; ++ hash1 = ZSTD_hashPtr(ip2, hlog, mls); ++ ++ /* advance to next positions */ ++ ip0 = ip1; ++ ip1 = ip2; ++ ip2 = ip3; ++ ++ /* write back hash table entry */ ++ current0 = (U32)(ip0 - base); ++ hashTable[hash0] = current0; ++ ++ { /* load match for ip[0] */ ++ U32 const mval = idx >= dictStartIndex ? ++ MEM_read32(idxBase + idx) : ++ MEM_read32(ip0) ^ 1; /* guaranteed not to match */ ++ ++ /* check match at ip[0] */ ++ if (MEM_read32(ip0) == mval) { ++ /* found a match! */ ++ goto _offset; ++ } } ++ ++ /* lookup ip[1] */ ++ idx = hashTable[hash1]; ++ idxBase = idx < prefixStartIndex ? dictBase : base; ++ ++ /* hash ip[2] */ ++ hash0 = hash1; ++ hash1 = ZSTD_hashPtr(ip2, hlog, mls); ++ ++ /* advance to next positions */ ++ ip0 = ip1; ++ ip1 = ip2; ++ ip2 = ip0 + step; ++ ip3 = ip1 + step; ++ ++ /* calculate step */ ++ if (ip2 >= nextStep) { ++ step++; ++ PREFETCH_L1(ip1 + 64); ++ PREFETCH_L1(ip1 + 128); ++ nextStep += kStepIncr; ++ } ++ } while (ip3 < ilimit); ++ ++_cleanup: ++ /* Note that there are probably still a couple positions we could search. ++ * However, it seems to be a meaningful performance hit to try to search ++ * them. So let's not. */ ++ ++ /* If offset_1 started invalid (offsetSaved1 != 0) and became valid (offset_1 != 0), ++ * rotate saved offsets. See comment in ZSTD_compressBlock_fast_noDict for more context. */ ++ offsetSaved2 = ((offsetSaved1 != 0) && (offset_1 != 0)) ? offsetSaved1 : offsetSaved2; + + /* save reps for next block */ +- rep[0] = offset_1; +- rep[1] = offset_2; ++ rep[0] = offset_1 ? offset_1 : offsetSaved1; ++ rep[1] = offset_2 ? offset_2 : offsetSaved2; + + /* Return the last literals size */ + return (size_t)(iend - anchor); ++ ++_offset: /* Requires: ip0, idx, idxBase */ ++ ++ /* Compute the offset code. */ ++ { U32 const offset = current0 - idx; ++ const BYTE* const lowMatchPtr = idx < prefixStartIndex ? dictStart : prefixStart; ++ matchEnd = idx < prefixStartIndex ? dictEnd : iend; ++ match0 = idxBase + idx; ++ offset_2 = offset_1; ++ offset_1 = offset; ++ offcode = OFFSET_TO_OFFBASE(offset); ++ mLength = 4; ++ ++ /* Count the backwards match length. */ ++ while (((ip0>anchor) & (match0>lowMatchPtr)) && (ip0[-1] == match0[-1])) { ++ ip0--; ++ match0--; ++ mLength++; ++ } } ++ ++_match: /* Requires: ip0, match0, offcode, matchEnd */ ++ ++ /* Count the forward length. */ ++ assert(matchEnd != 0); ++ mLength += ZSTD_count_2segments(ip0 + mLength, match0 + mLength, iend, matchEnd, prefixStart); ++ ++ ZSTD_storeSeq(seqStore, (size_t)(ip0 - anchor), anchor, iend, offcode, mLength); ++ ++ ip0 += mLength; ++ anchor = ip0; ++ ++ /* write next hash table entry */ ++ if (ip1 < ip0) { ++ hashTable[hash1] = (U32)(ip1 - base); ++ } ++ ++ /* Fill table and check for immediate repcode. */ ++ if (ip0 <= ilimit) { ++ /* Fill Table */ ++ assert(base+current0+2 > istart); /* check base overflow */ ++ hashTable[ZSTD_hashPtr(base+current0+2, hlog, mls)] = current0+2; /* here because current+2 could be > iend-8 */ ++ hashTable[ZSTD_hashPtr(ip0-2, hlog, mls)] = (U32)(ip0-2-base); ++ ++ while (ip0 <= ilimit) { ++ U32 const repIndex2 = (U32)(ip0-base) - offset_2; ++ const BYTE* const repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : base + repIndex2; ++ if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3) & (offset_2 > 0)) /* intentional underflow */ ++ && (MEM_read32(repMatch2) == MEM_read32(ip0)) ) { ++ const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend; ++ size_t const repLength2 = ZSTD_count_2segments(ip0+4, repMatch2+4, iend, repEnd2, prefixStart) + 4; ++ { U32 const tmpOffset = offset_2; offset_2 = offset_1; offset_1 = tmpOffset; } /* swap offset_2 <=> offset_1 */ ++ ZSTD_storeSeq(seqStore, 0 /*litlen*/, anchor, iend, REPCODE1_TO_OFFBASE, repLength2); ++ hashTable[ZSTD_hashPtr(ip0, hlog, mls)] = (U32)(ip0-base); ++ ip0 += repLength2; ++ anchor = ip0; ++ continue; ++ } ++ break; ++ } } ++ ++ goto _start; + } + + ZSTD_GEN_FAST_FN(extDict, 4, 0) +@@ -660,6 +945,7 @@ + void const* src, size_t srcSize) + { + U32 const mls = ms->cParams.minMatch; ++ assert(ms->dictMatchState == NULL); + switch(mls) + { + default: /* includes case 3 */ +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_fast.h b/lib/zstd/compress/zstd_fast.h +--- a/lib/zstd/compress/zstd_fast.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_fast.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -16,7 +17,8 @@ + #include "zstd_compress_internal.h" + + void ZSTD_fillHashTable(ZSTD_matchState_t* ms, +- void const* end, ZSTD_dictTableLoadMethod_e dtlm); ++ void const* end, ZSTD_dictTableLoadMethod_e dtlm, ++ ZSTD_tableFillPurpose_e tfp); + size_t ZSTD_compressBlock_fast( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_lazy.c b/lib/zstd/compress/zstd_lazy.c +--- a/lib/zstd/compress/zstd_lazy.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_lazy.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -10,6 +11,9 @@ + + #include "zstd_compress_internal.h" + #include "zstd_lazy.h" ++#include "../common/bits.h" /* ZSTD_countTrailingZeros64 */ ++ ++#define kLazySkippingStep 8 + + + /*-************************************* +@@ -197,8 +201,8 @@ + U32 matchIndex = dictMatchIndex + dictIndexDelta; + if ( (4*(int)(matchLength-bestLength)) > (int)(ZSTD_highbit32(curr-matchIndex+1) - ZSTD_highbit32((U32)offsetPtr[0]+1)) ) { + DEBUGLOG(9, "ZSTD_DUBT_findBetterDictMatch(%u) : found better match length %u -> %u and offsetCode %u -> %u (dictMatchIndex %u, matchIndex %u)", +- curr, (U32)bestLength, (U32)matchLength, (U32)*offsetPtr, STORE_OFFSET(curr - matchIndex), dictMatchIndex, matchIndex); +- bestLength = matchLength, *offsetPtr = STORE_OFFSET(curr - matchIndex); ++ curr, (U32)bestLength, (U32)matchLength, (U32)*offsetPtr, OFFSET_TO_OFFBASE(curr - matchIndex), dictMatchIndex, matchIndex); ++ bestLength = matchLength, *offsetPtr = OFFSET_TO_OFFBASE(curr - matchIndex); + } + if (ip+matchLength == iend) { /* reached end of input : ip[matchLength] is not valid, no way to know if it's larger or smaller than match */ + break; /* drop, to guarantee consistency (miss a little bit of compression) */ +@@ -218,7 +222,7 @@ + } + + if (bestLength >= MINMATCH) { +- U32 const mIndex = curr - (U32)STORED_OFFSET(*offsetPtr); (void)mIndex; ++ U32 const mIndex = curr - (U32)OFFBASE_TO_OFFSET(*offsetPtr); (void)mIndex; + DEBUGLOG(8, "ZSTD_DUBT_findBetterDictMatch(%u) : found match of length %u and offsetCode %u (pos %u)", + curr, (U32)bestLength, (U32)*offsetPtr, mIndex); + } +@@ -230,7 +234,7 @@ + static size_t + ZSTD_DUBT_findBestMatch(ZSTD_matchState_t* ms, + const BYTE* const ip, const BYTE* const iend, +- size_t* offsetPtr, ++ size_t* offBasePtr, + U32 const mls, + const ZSTD_dictMode_e dictMode) + { +@@ -327,8 +331,8 @@ + if (matchLength > bestLength) { + if (matchLength > matchEndIdx - matchIndex) + matchEndIdx = matchIndex + (U32)matchLength; +- if ( (4*(int)(matchLength-bestLength)) > (int)(ZSTD_highbit32(curr-matchIndex+1) - ZSTD_highbit32((U32)offsetPtr[0]+1)) ) +- bestLength = matchLength, *offsetPtr = STORE_OFFSET(curr - matchIndex); ++ if ( (4*(int)(matchLength-bestLength)) > (int)(ZSTD_highbit32(curr - matchIndex + 1) - ZSTD_highbit32((U32)*offBasePtr)) ) ++ bestLength = matchLength, *offBasePtr = OFFSET_TO_OFFBASE(curr - matchIndex); + if (ip+matchLength == iend) { /* equal : no way to know if inf or sup */ + if (dictMode == ZSTD_dictMatchState) { + nbCompares = 0; /* in addition to avoiding checking any +@@ -361,16 +365,16 @@ + if (dictMode == ZSTD_dictMatchState && nbCompares) { + bestLength = ZSTD_DUBT_findBetterDictMatch( + ms, ip, iend, +- offsetPtr, bestLength, nbCompares, ++ offBasePtr, bestLength, nbCompares, + mls, dictMode); + } + + assert(matchEndIdx > curr+8); /* ensure nextToUpdate is increased */ + ms->nextToUpdate = matchEndIdx - 8; /* skip repetitive patterns */ + if (bestLength >= MINMATCH) { +- U32 const mIndex = curr - (U32)STORED_OFFSET(*offsetPtr); (void)mIndex; ++ U32 const mIndex = curr - (U32)OFFBASE_TO_OFFSET(*offBasePtr); (void)mIndex; + DEBUGLOG(8, "ZSTD_DUBT_findBestMatch(%u) : found match of length %u and offsetCode %u (pos %u)", +- curr, (U32)bestLength, (U32)*offsetPtr, mIndex); ++ curr, (U32)bestLength, (U32)*offBasePtr, mIndex); + } + return bestLength; + } +@@ -381,14 +385,14 @@ + FORCE_INLINE_TEMPLATE size_t + ZSTD_BtFindBestMatch( ZSTD_matchState_t* ms, + const BYTE* const ip, const BYTE* const iLimit, +- size_t* offsetPtr, ++ size_t* offBasePtr, + const U32 mls /* template */, + const ZSTD_dictMode_e dictMode) + { + DEBUGLOG(7, "ZSTD_BtFindBestMatch"); + if (ip < ms->window.base + ms->nextToUpdate) return 0; /* skipped area */ + ZSTD_updateDUBT(ms, ip, iLimit, mls); +- return ZSTD_DUBT_findBestMatch(ms, ip, iLimit, offsetPtr, mls, dictMode); ++ return ZSTD_DUBT_findBestMatch(ms, ip, iLimit, offBasePtr, mls, dictMode); + } + + /* ********************************* +@@ -561,7 +565,7 @@ + /* save best solution */ + if (currentMl > ml) { + ml = currentMl; +- *offsetPtr = STORE_OFFSET(curr - (matchIndex + ddsIndexDelta)); ++ *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + ddsIndexDelta)); + if (ip+currentMl == iLimit) { + /* best possible, avoids read overflow on next attempt */ + return ml; +@@ -598,7 +602,7 @@ + /* save best solution */ + if (currentMl > ml) { + ml = currentMl; +- *offsetPtr = STORE_OFFSET(curr - (matchIndex + ddsIndexDelta)); ++ *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + ddsIndexDelta)); + if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ + } + } +@@ -617,7 +621,7 @@ + FORCE_INLINE_TEMPLATE U32 ZSTD_insertAndFindFirstIndex_internal( + ZSTD_matchState_t* ms, + const ZSTD_compressionParameters* const cParams, +- const BYTE* ip, U32 const mls) ++ const BYTE* ip, U32 const mls, U32 const lazySkipping) + { + U32* const hashTable = ms->hashTable; + const U32 hashLog = cParams->hashLog; +@@ -632,6 +636,9 @@ + NEXT_IN_CHAIN(idx, chainMask) = hashTable[h]; + hashTable[h] = idx; + idx++; ++ /* Stop inserting every position when in the lazy skipping mode. */ ++ if (lazySkipping) ++ break; + } + + ms->nextToUpdate = target; +@@ -640,7 +647,7 @@ + + U32 ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, const BYTE* ip) { + const ZSTD_compressionParameters* const cParams = &ms->cParams; +- return ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, ms->cParams.minMatch); ++ return ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, ms->cParams.minMatch, /* lazySkipping*/ 0); + } + + /* inlining is important to hardwire a hot branch (template emulation) */ +@@ -684,14 +691,15 @@ + } + + /* HC4 match finder */ +- matchIndex = ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, mls); ++ matchIndex = ZSTD_insertAndFindFirstIndex_internal(ms, cParams, ip, mls, ms->lazySkipping); + + for ( ; (matchIndex>=lowLimit) & (nbAttempts>0) ; nbAttempts--) { + size_t currentMl=0; + if ((dictMode != ZSTD_extDict) || matchIndex >= dictLimit) { + const BYTE* const match = base + matchIndex; + assert(matchIndex >= dictLimit); /* ensures this is true if dictMode != ZSTD_extDict */ +- if (match[ml] == ip[ml]) /* potentially better */ ++ /* read 4B starting from (match + ml + 1 - sizeof(U32)) */ ++ if (MEM_read32(match + ml - 3) == MEM_read32(ip + ml - 3)) /* potentially better */ + currentMl = ZSTD_count(ip, match, iLimit); + } else { + const BYTE* const match = dictBase + matchIndex; +@@ -703,7 +711,7 @@ + /* save best solution */ + if (currentMl > ml) { + ml = currentMl; +- *offsetPtr = STORE_OFFSET(curr - matchIndex); ++ *offsetPtr = OFFSET_TO_OFFBASE(curr - matchIndex); + if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ + } + +@@ -739,7 +747,7 @@ + if (currentMl > ml) { + ml = currentMl; + assert(curr > matchIndex + dmsIndexDelta); +- *offsetPtr = STORE_OFFSET(curr - (matchIndex + dmsIndexDelta)); ++ *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + dmsIndexDelta)); + if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ + } + +@@ -756,8 +764,6 @@ + * (SIMD) Row-based matchfinder + ***********************************/ + /* Constants for row-based hash */ +-#define ZSTD_ROW_HASH_TAG_OFFSET 16 /* byte offset of hashes in the match state's tagTable from the beginning of a row */ +-#define ZSTD_ROW_HASH_TAG_BITS 8 /* nb bits to use for the tag */ + #define ZSTD_ROW_HASH_TAG_MASK ((1u << ZSTD_ROW_HASH_TAG_BITS) - 1) + #define ZSTD_ROW_HASH_MAX_ENTRIES 64 /* absolute maximum number of entries per row, for all configurations */ + +@@ -769,64 +775,19 @@ + * Starting from the LSB, returns the idx of the next non-zero bit. + * Basically counting the nb of trailing zeroes. + */ +-static U32 ZSTD_VecMask_next(ZSTD_VecMask val) { +- assert(val != 0); +-# if (defined(__GNUC__) && ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4)))) +- if (sizeof(size_t) == 4) { +- U32 mostSignificantWord = (U32)(val >> 32); +- U32 leastSignificantWord = (U32)val; +- if (leastSignificantWord == 0) { +- return 32 + (U32)__builtin_ctz(mostSignificantWord); +- } else { +- return (U32)__builtin_ctz(leastSignificantWord); +- } +- } else { +- return (U32)__builtin_ctzll(val); +- } +-# else +- /* Software ctz version: http://aggregate.org/MAGIC/#Trailing%20Zero%20Count +- * and: https://stackoverflow.com/questions/2709430/count-number-of-bits-in-a-64-bit-long-big-integer +- */ +- val = ~val & (val - 1ULL); /* Lowest set bit mask */ +- val = val - ((val >> 1) & 0x5555555555555555); +- val = (val & 0x3333333333333333ULL) + ((val >> 2) & 0x3333333333333333ULL); +- return (U32)((((val + (val >> 4)) & 0xF0F0F0F0F0F0F0FULL) * 0x101010101010101ULL) >> 56); +-# endif +-} +- +-/* ZSTD_rotateRight_*(): +- * Rotates a bitfield to the right by "count" bits. +- * https://en.wikipedia.org/w/index.php?title=Circular_shift&oldid=991635599#Implementing_circular_shifts +- */ +-FORCE_INLINE_TEMPLATE +-U64 ZSTD_rotateRight_U64(U64 const value, U32 count) { +- assert(count < 64); +- count &= 0x3F; /* for fickle pattern recognition */ +- return (value >> count) | (U64)(value << ((0U - count) & 0x3F)); +-} +- +-FORCE_INLINE_TEMPLATE +-U32 ZSTD_rotateRight_U32(U32 const value, U32 count) { +- assert(count < 32); +- count &= 0x1F; /* for fickle pattern recognition */ +- return (value >> count) | (U32)(value << ((0U - count) & 0x1F)); +-} +- +-FORCE_INLINE_TEMPLATE +-U16 ZSTD_rotateRight_U16(U16 const value, U32 count) { +- assert(count < 16); +- count &= 0x0F; /* for fickle pattern recognition */ +- return (value >> count) | (U16)(value << ((0U - count) & 0x0F)); ++MEM_STATIC U32 ZSTD_VecMask_next(ZSTD_VecMask val) { ++ return ZSTD_countTrailingZeros64(val); + } + + /* ZSTD_row_nextIndex(): + * Returns the next index to insert at within a tagTable row, and updates the "head" +- * value to reflect the update. Essentially cycles backwards from [0, {entries per row}) ++ * value to reflect the update. Essentially cycles backwards from [1, {entries per row}) + */ + FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextIndex(BYTE* const tagRow, U32 const rowMask) { +- U32 const next = (*tagRow - 1) & rowMask; +- *tagRow = (BYTE)next; +- return next; ++ U32 next = (*tagRow-1) & rowMask; ++ next += (next == 0) ? rowMask : 0; /* skip first position */ ++ *tagRow = (BYTE)next; ++ return next; + } + + /* ZSTD_isAligned(): +@@ -840,7 +801,7 @@ + /* ZSTD_row_prefetch(): + * Performs prefetching for the hashTable and tagTable at a given row. + */ +-FORCE_INLINE_TEMPLATE void ZSTD_row_prefetch(U32 const* hashTable, U16 const* tagTable, U32 const relRow, U32 const rowLog) { ++FORCE_INLINE_TEMPLATE void ZSTD_row_prefetch(U32 const* hashTable, BYTE const* tagTable, U32 const relRow, U32 const rowLog) { + PREFETCH_L1(hashTable + relRow); + if (rowLog >= 5) { + PREFETCH_L1(hashTable + relRow + 16); +@@ -864,13 +825,13 @@ + U32 idx, const BYTE* const iLimit) + { + U32 const* const hashTable = ms->hashTable; +- U16 const* const tagTable = ms->tagTable; ++ BYTE const* const tagTable = ms->tagTable; + U32 const hashLog = ms->rowHashLog; + U32 const maxElemsToPrefetch = (base + idx) > iLimit ? 0 : (U32)(iLimit - (base + idx) + 1); + U32 const lim = idx + MIN(ZSTD_ROW_HASH_CACHE_SIZE, maxElemsToPrefetch); + + for (; idx < lim; ++idx) { +- U32 const hash = (U32)ZSTD_hashPtr(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls); ++ U32 const hash = (U32)ZSTD_hashPtrSalted(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt); + U32 const row = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; + ZSTD_row_prefetch(hashTable, tagTable, row, rowLog); + ms->hashCache[idx & ZSTD_ROW_HASH_CACHE_MASK] = hash; +@@ -886,11 +847,12 @@ + * base + idx + ZSTD_ROW_HASH_CACHE_SIZE. Also prefetches the appropriate rows from hashTable and tagTable. + */ + FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTable, +- U16 const* tagTable, BYTE const* base, ++ BYTE const* tagTable, BYTE const* base, + U32 idx, U32 const hashLog, +- U32 const rowLog, U32 const mls) ++ U32 const rowLog, U32 const mls, ++ U64 const hashSalt) + { +- U32 const newHash = (U32)ZSTD_hashPtr(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls); ++ U32 const newHash = (U32)ZSTD_hashPtrSalted(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, hashSalt); + U32 const row = (newHash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; + ZSTD_row_prefetch(hashTable, tagTable, row, rowLog); + { U32 const hash = cache[idx & ZSTD_ROW_HASH_CACHE_MASK]; +@@ -908,22 +870,21 @@ + U32 const rowMask, U32 const useCache) + { + U32* const hashTable = ms->hashTable; +- U16* const tagTable = ms->tagTable; ++ BYTE* const tagTable = ms->tagTable; + U32 const hashLog = ms->rowHashLog; + const BYTE* const base = ms->window.base; + + DEBUGLOG(6, "ZSTD_row_update_internalImpl(): updateStartIdx=%u, updateEndIdx=%u", updateStartIdx, updateEndIdx); + for (; updateStartIdx < updateEndIdx; ++updateStartIdx) { +- U32 const hash = useCache ? ZSTD_row_nextCachedHash(ms->hashCache, hashTable, tagTable, base, updateStartIdx, hashLog, rowLog, mls) +- : (U32)ZSTD_hashPtr(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls); ++ U32 const hash = useCache ? ZSTD_row_nextCachedHash(ms->hashCache, hashTable, tagTable, base, updateStartIdx, hashLog, rowLog, mls, ms->hashSalt) ++ : (U32)ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt); + U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; + U32* const row = hashTable + relRow; +- BYTE* tagRow = (BYTE*)(tagTable + relRow); /* Though tagTable is laid out as a table of U16, each tag is only 1 byte. +- Explicit cast allows us to get exact desired position within each row */ ++ BYTE* tagRow = tagTable + relRow; + U32 const pos = ZSTD_row_nextIndex(tagRow, rowMask); + +- assert(hash == ZSTD_hashPtr(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls)); +- ((BYTE*)tagRow)[pos + ZSTD_ROW_HASH_TAG_OFFSET] = hash & ZSTD_ROW_HASH_TAG_MASK; ++ assert(hash == ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt)); ++ tagRow[pos] = hash & ZSTD_ROW_HASH_TAG_MASK; + row[pos] = updateStartIdx; + } + } +@@ -971,7 +932,35 @@ + const U32 mls = MIN(ms->cParams.minMatch, 6 /* mls caps out at 6 */); + + DEBUGLOG(5, "ZSTD_row_update(), rowLog=%u", rowLog); +- ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 0 /* dont use cache */); ++ ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 0 /* don't use cache */); ++} ++ ++/* Returns the mask width of bits group of which will be set to 1. Given not all ++ * architectures have easy movemask instruction, this helps to iterate over ++ * groups of bits easier and faster. ++ */ ++FORCE_INLINE_TEMPLATE U32 ++ZSTD_row_matchMaskGroupWidth(const U32 rowEntries) ++{ ++ assert((rowEntries == 16) || (rowEntries == 32) || rowEntries == 64); ++ assert(rowEntries <= ZSTD_ROW_HASH_MAX_ENTRIES); ++ (void)rowEntries; ++#if defined(ZSTD_ARCH_ARM_NEON) ++ /* NEON path only works for little endian */ ++ if (!MEM_isLittleEndian()) { ++ return 1; ++ } ++ if (rowEntries == 16) { ++ return 4; ++ } ++ if (rowEntries == 32) { ++ return 2; ++ } ++ if (rowEntries == 64) { ++ return 1; ++ } ++#endif ++ return 1; + } + + #if defined(ZSTD_ARCH_X86_SSE2) +@@ -994,71 +983,82 @@ + } + #endif + +-/* Returns a ZSTD_VecMask (U32) that has the nth bit set to 1 if the newly-computed "tag" matches +- * the hash at the nth position in a row of the tagTable. +- * Each row is a circular buffer beginning at the value of "head". So we must rotate the "matches" bitfield +- * to match up with the actual layout of the entries within the hashTable */ ++#if defined(ZSTD_ARCH_ARM_NEON) ++FORCE_INLINE_TEMPLATE ZSTD_VecMask ++ZSTD_row_getNEONMask(const U32 rowEntries, const BYTE* const src, const BYTE tag, const U32 headGrouped) ++{ ++ assert((rowEntries == 16) || (rowEntries == 32) || rowEntries == 64); ++ if (rowEntries == 16) { ++ /* vshrn_n_u16 shifts by 4 every u16 and narrows to 8 lower bits. ++ * After that groups of 4 bits represent the equalMask. We lower ++ * all bits except the highest in these groups by doing AND with ++ * 0x88 = 0b10001000. ++ */ ++ const uint8x16_t chunk = vld1q_u8(src); ++ const uint16x8_t equalMask = vreinterpretq_u16_u8(vceqq_u8(chunk, vdupq_n_u8(tag))); ++ const uint8x8_t res = vshrn_n_u16(equalMask, 4); ++ const U64 matches = vget_lane_u64(vreinterpret_u64_u8(res), 0); ++ return ZSTD_rotateRight_U64(matches, headGrouped) & 0x8888888888888888ull; ++ } else if (rowEntries == 32) { ++ /* Same idea as with rowEntries == 16 but doing AND with ++ * 0x55 = 0b01010101. ++ */ ++ const uint16x8x2_t chunk = vld2q_u16((const uint16_t*)(const void*)src); ++ const uint8x16_t chunk0 = vreinterpretq_u8_u16(chunk.val[0]); ++ const uint8x16_t chunk1 = vreinterpretq_u8_u16(chunk.val[1]); ++ const uint8x16_t dup = vdupq_n_u8(tag); ++ const uint8x8_t t0 = vshrn_n_u16(vreinterpretq_u16_u8(vceqq_u8(chunk0, dup)), 6); ++ const uint8x8_t t1 = vshrn_n_u16(vreinterpretq_u16_u8(vceqq_u8(chunk1, dup)), 6); ++ const uint8x8_t res = vsli_n_u8(t0, t1, 4); ++ const U64 matches = vget_lane_u64(vreinterpret_u64_u8(res), 0) ; ++ return ZSTD_rotateRight_U64(matches, headGrouped) & 0x5555555555555555ull; ++ } else { /* rowEntries == 64 */ ++ const uint8x16x4_t chunk = vld4q_u8(src); ++ const uint8x16_t dup = vdupq_n_u8(tag); ++ const uint8x16_t cmp0 = vceqq_u8(chunk.val[0], dup); ++ const uint8x16_t cmp1 = vceqq_u8(chunk.val[1], dup); ++ const uint8x16_t cmp2 = vceqq_u8(chunk.val[2], dup); ++ const uint8x16_t cmp3 = vceqq_u8(chunk.val[3], dup); ++ ++ const uint8x16_t t0 = vsriq_n_u8(cmp1, cmp0, 1); ++ const uint8x16_t t1 = vsriq_n_u8(cmp3, cmp2, 1); ++ const uint8x16_t t2 = vsriq_n_u8(t1, t0, 2); ++ const uint8x16_t t3 = vsriq_n_u8(t2, t2, 4); ++ const uint8x8_t t4 = vshrn_n_u16(vreinterpretq_u16_u8(t3), 4); ++ const U64 matches = vget_lane_u64(vreinterpret_u64_u8(t4), 0); ++ return ZSTD_rotateRight_U64(matches, headGrouped); ++ } ++} ++#endif ++ ++/* Returns a ZSTD_VecMask (U64) that has the nth group (determined by ++ * ZSTD_row_matchMaskGroupWidth) of bits set to 1 if the newly-computed "tag" ++ * matches the hash at the nth position in a row of the tagTable. ++ * Each row is a circular buffer beginning at the value of "headGrouped". So we ++ * must rotate the "matches" bitfield to match up with the actual layout of the ++ * entries within the hashTable */ + FORCE_INLINE_TEMPLATE ZSTD_VecMask +-ZSTD_row_getMatchMask(const BYTE* const tagRow, const BYTE tag, const U32 head, const U32 rowEntries) ++ZSTD_row_getMatchMask(const BYTE* const tagRow, const BYTE tag, const U32 headGrouped, const U32 rowEntries) + { +- const BYTE* const src = tagRow + ZSTD_ROW_HASH_TAG_OFFSET; ++ const BYTE* const src = tagRow; + assert((rowEntries == 16) || (rowEntries == 32) || rowEntries == 64); + assert(rowEntries <= ZSTD_ROW_HASH_MAX_ENTRIES); ++ assert(ZSTD_row_matchMaskGroupWidth(rowEntries) * rowEntries <= sizeof(ZSTD_VecMask) * 8); + + #if defined(ZSTD_ARCH_X86_SSE2) + +- return ZSTD_row_getSSEMask(rowEntries / 16, src, tag, head); ++ return ZSTD_row_getSSEMask(rowEntries / 16, src, tag, headGrouped); + + #else /* SW or NEON-LE */ + + # if defined(ZSTD_ARCH_ARM_NEON) + /* This NEON path only works for little endian - otherwise use SWAR below */ + if (MEM_isLittleEndian()) { +- if (rowEntries == 16) { +- const uint8x16_t chunk = vld1q_u8(src); +- const uint16x8_t equalMask = vreinterpretq_u16_u8(vceqq_u8(chunk, vdupq_n_u8(tag))); +- const uint16x8_t t0 = vshlq_n_u16(equalMask, 7); +- const uint32x4_t t1 = vreinterpretq_u32_u16(vsriq_n_u16(t0, t0, 14)); +- const uint64x2_t t2 = vreinterpretq_u64_u32(vshrq_n_u32(t1, 14)); +- const uint8x16_t t3 = vreinterpretq_u8_u64(vsraq_n_u64(t2, t2, 28)); +- const U16 hi = (U16)vgetq_lane_u8(t3, 8); +- const U16 lo = (U16)vgetq_lane_u8(t3, 0); +- return ZSTD_rotateRight_U16((hi << 8) | lo, head); +- } else if (rowEntries == 32) { +- const uint16x8x2_t chunk = vld2q_u16((const U16*)(const void*)src); +- const uint8x16_t chunk0 = vreinterpretq_u8_u16(chunk.val[0]); +- const uint8x16_t chunk1 = vreinterpretq_u8_u16(chunk.val[1]); +- const uint8x16_t equalMask0 = vceqq_u8(chunk0, vdupq_n_u8(tag)); +- const uint8x16_t equalMask1 = vceqq_u8(chunk1, vdupq_n_u8(tag)); +- const int8x8_t pack0 = vqmovn_s16(vreinterpretq_s16_u8(equalMask0)); +- const int8x8_t pack1 = vqmovn_s16(vreinterpretq_s16_u8(equalMask1)); +- const uint8x8_t t0 = vreinterpret_u8_s8(pack0); +- const uint8x8_t t1 = vreinterpret_u8_s8(pack1); +- const uint8x8_t t2 = vsri_n_u8(t1, t0, 2); +- const uint8x8x2_t t3 = vuzp_u8(t2, t0); +- const uint8x8_t t4 = vsri_n_u8(t3.val[1], t3.val[0], 4); +- const U32 matches = vget_lane_u32(vreinterpret_u32_u8(t4), 0); +- return ZSTD_rotateRight_U32(matches, head); +- } else { /* rowEntries == 64 */ +- const uint8x16x4_t chunk = vld4q_u8(src); +- const uint8x16_t dup = vdupq_n_u8(tag); +- const uint8x16_t cmp0 = vceqq_u8(chunk.val[0], dup); +- const uint8x16_t cmp1 = vceqq_u8(chunk.val[1], dup); +- const uint8x16_t cmp2 = vceqq_u8(chunk.val[2], dup); +- const uint8x16_t cmp3 = vceqq_u8(chunk.val[3], dup); +- +- const uint8x16_t t0 = vsriq_n_u8(cmp1, cmp0, 1); +- const uint8x16_t t1 = vsriq_n_u8(cmp3, cmp2, 1); +- const uint8x16_t t2 = vsriq_n_u8(t1, t0, 2); +- const uint8x16_t t3 = vsriq_n_u8(t2, t2, 4); +- const uint8x8_t t4 = vshrn_n_u16(vreinterpretq_u16_u8(t3), 4); +- const U64 matches = vget_lane_u64(vreinterpret_u64_u8(t4), 0); +- return ZSTD_rotateRight_U64(matches, head); +- } ++ return ZSTD_row_getNEONMask(rowEntries, src, tag, headGrouped); + } + # endif /* ZSTD_ARCH_ARM_NEON */ + /* SWAR */ +- { const size_t chunkSize = sizeof(size_t); ++ { const int chunkSize = sizeof(size_t); + const size_t shiftAmount = ((chunkSize * 8) - chunkSize); + const size_t xFF = ~((size_t)0); + const size_t x01 = xFF / 0xFF; +@@ -1091,11 +1091,11 @@ + } + matches = ~matches; + if (rowEntries == 16) { +- return ZSTD_rotateRight_U16((U16)matches, head); ++ return ZSTD_rotateRight_U16((U16)matches, headGrouped); + } else if (rowEntries == 32) { +- return ZSTD_rotateRight_U32((U32)matches, head); ++ return ZSTD_rotateRight_U32((U32)matches, headGrouped); + } else { +- return ZSTD_rotateRight_U64((U64)matches, head); ++ return ZSTD_rotateRight_U64((U64)matches, headGrouped); + } + } + #endif +@@ -1125,7 +1125,7 @@ + const U32 rowLog) + { + U32* const hashTable = ms->hashTable; +- U16* const tagTable = ms->tagTable; ++ BYTE* const tagTable = ms->tagTable; + U32* const hashCache = ms->hashCache; + const U32 hashLog = ms->rowHashLog; + const ZSTD_compressionParameters* const cParams = &ms->cParams; +@@ -1143,8 +1143,11 @@ + const U32 rowEntries = (1U << rowLog); + const U32 rowMask = rowEntries - 1; + const U32 cappedSearchLog = MIN(cParams->searchLog, rowLog); /* nb of searches is capped at nb entries per row */ ++ const U32 groupWidth = ZSTD_row_matchMaskGroupWidth(rowEntries); ++ const U64 hashSalt = ms->hashSalt; + U32 nbAttempts = 1U << cappedSearchLog; + size_t ml=4-1; ++ U32 hash; + + /* DMS/DDS variables that may be referenced laster */ + const ZSTD_matchState_t* const dms = ms->dictMatchState; +@@ -1168,7 +1171,7 @@ + if (dictMode == ZSTD_dictMatchState) { + /* Prefetch DMS rows */ + U32* const dmsHashTable = dms->hashTable; +- U16* const dmsTagTable = dms->tagTable; ++ BYTE* const dmsTagTable = dms->tagTable; + U32 const dmsHash = (U32)ZSTD_hashPtr(ip, dms->rowHashLog + ZSTD_ROW_HASH_TAG_BITS, mls); + U32 const dmsRelRow = (dmsHash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; + dmsTag = dmsHash & ZSTD_ROW_HASH_TAG_MASK; +@@ -1178,23 +1181,34 @@ + } + + /* Update the hashTable and tagTable up to (but not including) ip */ +- ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 1 /* useCache */); ++ if (!ms->lazySkipping) { ++ ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 1 /* useCache */); ++ hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls, hashSalt); ++ } else { ++ /* Stop inserting every position when in the lazy skipping mode. ++ * The hash cache is also not kept up to date in this mode. ++ */ ++ hash = (U32)ZSTD_hashPtrSalted(ip, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, hashSalt); ++ ms->nextToUpdate = curr; ++ } ++ ms->hashSaltEntropy += hash; /* collect salt entropy */ ++ + { /* Get the hash for ip, compute the appropriate row */ +- U32 const hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls); + U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog; + U32 const tag = hash & ZSTD_ROW_HASH_TAG_MASK; + U32* const row = hashTable + relRow; + BYTE* tagRow = (BYTE*)(tagTable + relRow); +- U32 const head = *tagRow & rowMask; ++ U32 const headGrouped = (*tagRow & rowMask) * groupWidth; + U32 matchBuffer[ZSTD_ROW_HASH_MAX_ENTRIES]; + size_t numMatches = 0; + size_t currMatch = 0; +- ZSTD_VecMask matches = ZSTD_row_getMatchMask(tagRow, (BYTE)tag, head, rowEntries); ++ ZSTD_VecMask matches = ZSTD_row_getMatchMask(tagRow, (BYTE)tag, headGrouped, rowEntries); + + /* Cycle through the matches and prefetch */ +- for (; (matches > 0) && (nbAttempts > 0); --nbAttempts, matches &= (matches - 1)) { +- U32 const matchPos = (head + ZSTD_VecMask_next(matches)) & rowMask; ++ for (; (matches > 0) && (nbAttempts > 0); matches &= (matches - 1)) { ++ U32 const matchPos = ((headGrouped + ZSTD_VecMask_next(matches)) / groupWidth) & rowMask; + U32 const matchIndex = row[matchPos]; ++ if(matchPos == 0) continue; + assert(numMatches < rowEntries); + if (matchIndex < lowLimit) + break; +@@ -1204,13 +1218,14 @@ + PREFETCH_L1(dictBase + matchIndex); + } + matchBuffer[numMatches++] = matchIndex; ++ --nbAttempts; + } + + /* Speed opt: insert current byte into hashtable too. This allows us to avoid one iteration of the loop + in ZSTD_row_update_internal() at the next search. */ + { + U32 const pos = ZSTD_row_nextIndex(tagRow, rowMask); +- tagRow[pos + ZSTD_ROW_HASH_TAG_OFFSET] = (BYTE)tag; ++ tagRow[pos] = (BYTE)tag; + row[pos] = ms->nextToUpdate++; + } + +@@ -1224,7 +1239,8 @@ + if ((dictMode != ZSTD_extDict) || matchIndex >= dictLimit) { + const BYTE* const match = base + matchIndex; + assert(matchIndex >= dictLimit); /* ensures this is true if dictMode != ZSTD_extDict */ +- if (match[ml] == ip[ml]) /* potentially better */ ++ /* read 4B starting from (match + ml + 1 - sizeof(U32)) */ ++ if (MEM_read32(match + ml - 3) == MEM_read32(ip + ml - 3)) /* potentially better */ + currentMl = ZSTD_count(ip, match, iLimit); + } else { + const BYTE* const match = dictBase + matchIndex; +@@ -1236,7 +1252,7 @@ + /* Save best solution */ + if (currentMl > ml) { + ml = currentMl; +- *offsetPtr = STORE_OFFSET(curr - matchIndex); ++ *offsetPtr = OFFSET_TO_OFFBASE(curr - matchIndex); + if (ip+currentMl == iLimit) break; /* best possible, avoids read overflow on next attempt */ + } + } +@@ -1254,19 +1270,21 @@ + const U32 dmsSize = (U32)(dmsEnd - dmsBase); + const U32 dmsIndexDelta = dictLimit - dmsSize; + +- { U32 const head = *dmsTagRow & rowMask; ++ { U32 const headGrouped = (*dmsTagRow & rowMask) * groupWidth; + U32 matchBuffer[ZSTD_ROW_HASH_MAX_ENTRIES]; + size_t numMatches = 0; + size_t currMatch = 0; +- ZSTD_VecMask matches = ZSTD_row_getMatchMask(dmsTagRow, (BYTE)dmsTag, head, rowEntries); ++ ZSTD_VecMask matches = ZSTD_row_getMatchMask(dmsTagRow, (BYTE)dmsTag, headGrouped, rowEntries); + +- for (; (matches > 0) && (nbAttempts > 0); --nbAttempts, matches &= (matches - 1)) { +- U32 const matchPos = (head + ZSTD_VecMask_next(matches)) & rowMask; ++ for (; (matches > 0) && (nbAttempts > 0); matches &= (matches - 1)) { ++ U32 const matchPos = ((headGrouped + ZSTD_VecMask_next(matches)) / groupWidth) & rowMask; + U32 const matchIndex = dmsRow[matchPos]; ++ if(matchPos == 0) continue; + if (matchIndex < dmsLowestIndex) + break; + PREFETCH_L1(dmsBase + matchIndex); + matchBuffer[numMatches++] = matchIndex; ++ --nbAttempts; + } + + /* Return the longest match */ +@@ -1285,7 +1303,7 @@ + if (currentMl > ml) { + ml = currentMl; + assert(curr > matchIndex + dmsIndexDelta); +- *offsetPtr = STORE_OFFSET(curr - (matchIndex + dmsIndexDelta)); ++ *offsetPtr = OFFSET_TO_OFFBASE(curr - (matchIndex + dmsIndexDelta)); + if (ip+currentMl == iLimit) break; + } + } +@@ -1491,7 +1509,8 @@ + const U32 mls = BOUNDED(4, ms->cParams.minMatch, 6); + const U32 rowLog = BOUNDED(4, ms->cParams.searchLog, 6); + +- U32 offset_1 = rep[0], offset_2 = rep[1], savedOffset=0; ++ U32 offset_1 = rep[0], offset_2 = rep[1]; ++ U32 offsetSaved1 = 0, offsetSaved2 = 0; + + const int isDMS = dictMode == ZSTD_dictMatchState; + const int isDDS = dictMode == ZSTD_dedicatedDictSearch; +@@ -1512,8 +1531,8 @@ + U32 const curr = (U32)(ip - base); + U32 const windowLow = ZSTD_getLowestPrefixIndex(ms, curr, ms->cParams.windowLog); + U32 const maxRep = curr - windowLow; +- if (offset_2 > maxRep) savedOffset = offset_2, offset_2 = 0; +- if (offset_1 > maxRep) savedOffset = offset_1, offset_1 = 0; ++ if (offset_2 > maxRep) offsetSaved2 = offset_2, offset_2 = 0; ++ if (offset_1 > maxRep) offsetSaved1 = offset_1, offset_1 = 0; + } + if (isDxS) { + /* dictMatchState repCode checks don't currently handle repCode == 0 +@@ -1522,10 +1541,11 @@ + assert(offset_2 <= dictAndPrefixLength); + } + ++ /* Reset the lazy skipping state */ ++ ms->lazySkipping = 0; ++ + if (searchMethod == search_rowHash) { +- ZSTD_row_fillHashCache(ms, base, rowLog, +- MIN(ms->cParams.minMatch, 6 /* mls caps out at 6 */), +- ms->nextToUpdate, ilimit); ++ ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); + } + + /* Match Loop */ +@@ -1537,7 +1557,7 @@ + #endif + while (ip < ilimit) { + size_t matchLength=0; +- size_t offcode=STORE_REPCODE_1; ++ size_t offBase = REPCODE1_TO_OFFBASE; + const BYTE* start=ip+1; + DEBUGLOG(7, "search baseline (depth 0)"); + +@@ -1562,14 +1582,23 @@ + } + + /* first search (depth 0) */ +- { size_t offsetFound = 999999999; +- size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offsetFound, mls, rowLog, searchMethod, dictMode); ++ { size_t offbaseFound = 999999999; ++ size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offbaseFound, mls, rowLog, searchMethod, dictMode); + if (ml2 > matchLength) +- matchLength = ml2, start = ip, offcode=offsetFound; ++ matchLength = ml2, start = ip, offBase = offbaseFound; + } + + if (matchLength < 4) { +- ip += ((ip-anchor) >> kSearchStrength) + 1; /* jump faster over incompressible sections */ ++ size_t const step = ((size_t)(ip-anchor) >> kSearchStrength) + 1; /* jump faster over incompressible sections */; ++ ip += step; ++ /* Enter the lazy skipping mode once we are skipping more than 8 bytes at a time. ++ * In this mode we stop inserting every position into our tables, and only insert ++ * positions that we search, which is one in step positions. ++ * The exact cutoff is flexible, I've just chosen a number that is reasonably high, ++ * so we minimize the compression ratio loss in "normal" scenarios. This mode gets ++ * triggered once we've gone 2KB without finding any matches. ++ */ ++ ms->lazySkipping = step > kLazySkippingStep; + continue; + } + +@@ -1579,12 +1608,12 @@ + DEBUGLOG(7, "search depth 1"); + ip ++; + if ( (dictMode == ZSTD_noDict) +- && (offcode) && ((offset_1>0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { ++ && (offBase) && ((offset_1>0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { + size_t const mlRep = ZSTD_count(ip+4, ip+4-offset_1, iend) + 4; + int const gain2 = (int)(mlRep * 3); +- int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 1); ++ int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offBase) + 1); + if ((mlRep >= 4) && (gain2 > gain1)) +- matchLength = mlRep, offcode = STORE_REPCODE_1, start = ip; ++ matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; + } + if (isDxS) { + const U32 repIndex = (U32)(ip - base) - offset_1; +@@ -1596,17 +1625,17 @@ + const BYTE* repMatchEnd = repIndex < prefixLowestIndex ? dictEnd : iend; + size_t const mlRep = ZSTD_count_2segments(ip+4, repMatch+4, iend, repMatchEnd, prefixLowest) + 4; + int const gain2 = (int)(mlRep * 3); +- int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 1); ++ int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offBase) + 1); + if ((mlRep >= 4) && (gain2 > gain1)) +- matchLength = mlRep, offcode = STORE_REPCODE_1, start = ip; ++ matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; + } + } +- { size_t offset2=999999999; +- size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offset2, mls, rowLog, searchMethod, dictMode); +- int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offset2))); /* raw approx */ +- int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 4); ++ { size_t ofbCandidate=999999999; ++ size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, dictMode); ++ int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ ++ int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 4); + if ((ml2 >= 4) && (gain2 > gain1)) { +- matchLength = ml2, offcode = offset2, start = ip; ++ matchLength = ml2, offBase = ofbCandidate, start = ip; + continue; /* search a better one */ + } } + +@@ -1615,12 +1644,12 @@ + DEBUGLOG(7, "search depth 2"); + ip ++; + if ( (dictMode == ZSTD_noDict) +- && (offcode) && ((offset_1>0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { ++ && (offBase) && ((offset_1>0) & (MEM_read32(ip) == MEM_read32(ip - offset_1)))) { + size_t const mlRep = ZSTD_count(ip+4, ip+4-offset_1, iend) + 4; + int const gain2 = (int)(mlRep * 4); +- int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 1); ++ int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 1); + if ((mlRep >= 4) && (gain2 > gain1)) +- matchLength = mlRep, offcode = STORE_REPCODE_1, start = ip; ++ matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; + } + if (isDxS) { + const U32 repIndex = (U32)(ip - base) - offset_1; +@@ -1632,17 +1661,17 @@ + const BYTE* repMatchEnd = repIndex < prefixLowestIndex ? dictEnd : iend; + size_t const mlRep = ZSTD_count_2segments(ip+4, repMatch+4, iend, repMatchEnd, prefixLowest) + 4; + int const gain2 = (int)(mlRep * 4); +- int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 1); ++ int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 1); + if ((mlRep >= 4) && (gain2 > gain1)) +- matchLength = mlRep, offcode = STORE_REPCODE_1, start = ip; ++ matchLength = mlRep, offBase = REPCODE1_TO_OFFBASE, start = ip; + } + } +- { size_t offset2=999999999; +- size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offset2, mls, rowLog, searchMethod, dictMode); +- int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offset2))); /* raw approx */ +- int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 7); ++ { size_t ofbCandidate=999999999; ++ size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, dictMode); ++ int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ ++ int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 7); + if ((ml2 >= 4) && (gain2 > gain1)) { +- matchLength = ml2, offcode = offset2, start = ip; ++ matchLength = ml2, offBase = ofbCandidate, start = ip; + continue; + } } } + break; /* nothing found : store previous solution */ +@@ -1653,26 +1682,33 @@ + * notably if `value` is unsigned, resulting in a large positive `-value`. + */ + /* catch up */ +- if (STORED_IS_OFFSET(offcode)) { ++ if (OFFBASE_IS_OFFSET(offBase)) { + if (dictMode == ZSTD_noDict) { +- while ( ((start > anchor) & (start - STORED_OFFSET(offcode) > prefixLowest)) +- && (start[-1] == (start-STORED_OFFSET(offcode))[-1]) ) /* only search for offset within prefix */ ++ while ( ((start > anchor) & (start - OFFBASE_TO_OFFSET(offBase) > prefixLowest)) ++ && (start[-1] == (start-OFFBASE_TO_OFFSET(offBase))[-1]) ) /* only search for offset within prefix */ + { start--; matchLength++; } + } + if (isDxS) { +- U32 const matchIndex = (U32)((size_t)(start-base) - STORED_OFFSET(offcode)); ++ U32 const matchIndex = (U32)((size_t)(start-base) - OFFBASE_TO_OFFSET(offBase)); + const BYTE* match = (matchIndex < prefixLowestIndex) ? dictBase + matchIndex - dictIndexDelta : base + matchIndex; + const BYTE* const mStart = (matchIndex < prefixLowestIndex) ? dictLowest : prefixLowest; + while ((start>anchor) && (match>mStart) && (start[-1] == match[-1])) { start--; match--; matchLength++; } /* catch up */ + } +- offset_2 = offset_1; offset_1 = (U32)STORED_OFFSET(offcode); ++ offset_2 = offset_1; offset_1 = (U32)OFFBASE_TO_OFFSET(offBase); + } + /* store sequence */ + _storeSequence: + { size_t const litLength = (size_t)(start - anchor); +- ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offcode, matchLength); ++ ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offBase, matchLength); + anchor = ip = start + matchLength; + } ++ if (ms->lazySkipping) { ++ /* We've found a match, disable lazy skipping mode, and refill the hash cache. */ ++ if (searchMethod == search_rowHash) { ++ ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); ++ } ++ ms->lazySkipping = 0; ++ } + + /* check immediate repcode */ + if (isDxS) { +@@ -1686,8 +1722,8 @@ + && (MEM_read32(repMatch) == MEM_read32(ip)) ) { + const BYTE* const repEnd2 = repIndex < prefixLowestIndex ? dictEnd : iend; + matchLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd2, prefixLowest) + 4; +- offcode = offset_2; offset_2 = offset_1; offset_1 = (U32)offcode; /* swap offset_2 <=> offset_1 */ +- ZSTD_storeSeq(seqStore, 0, anchor, iend, STORE_REPCODE_1, matchLength); ++ offBase = offset_2; offset_2 = offset_1; offset_1 = (U32)offBase; /* swap offset_2 <=> offset_1 */ ++ ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, matchLength); + ip += matchLength; + anchor = ip; + continue; +@@ -1701,16 +1737,20 @@ + && (MEM_read32(ip) == MEM_read32(ip - offset_2)) ) { + /* store sequence */ + matchLength = ZSTD_count(ip+4, ip+4-offset_2, iend) + 4; +- offcode = offset_2; offset_2 = offset_1; offset_1 = (U32)offcode; /* swap repcodes */ +- ZSTD_storeSeq(seqStore, 0, anchor, iend, STORE_REPCODE_1, matchLength); ++ offBase = offset_2; offset_2 = offset_1; offset_1 = (U32)offBase; /* swap repcodes */ ++ ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, matchLength); + ip += matchLength; + anchor = ip; + continue; /* faster when present ... (?) */ + } } } + +- /* Save reps for next block */ +- rep[0] = offset_1 ? offset_1 : savedOffset; +- rep[1] = offset_2 ? offset_2 : savedOffset; ++ /* If offset_1 started invalid (offsetSaved1 != 0) and became valid (offset_1 != 0), ++ * rotate saved offsets. See comment in ZSTD_compressBlock_fast_noDict for more context. */ ++ offsetSaved2 = ((offsetSaved1 != 0) && (offset_1 != 0)) ? offsetSaved1 : offsetSaved2; ++ ++ /* save reps for next block */ ++ rep[0] = offset_1 ? offset_1 : offsetSaved1; ++ rep[1] = offset_2 ? offset_2 : offsetSaved2; + + /* Return the last literals size */ + return (size_t)(iend - anchor); +@@ -1886,12 +1926,13 @@ + + DEBUGLOG(5, "ZSTD_compressBlock_lazy_extDict_generic (searchFunc=%u)", (U32)searchMethod); + ++ /* Reset the lazy skipping state */ ++ ms->lazySkipping = 0; ++ + /* init */ + ip += (ip == prefixStart); + if (searchMethod == search_rowHash) { +- ZSTD_row_fillHashCache(ms, base, rowLog, +- MIN(ms->cParams.minMatch, 6 /* mls caps out at 6 */), +- ms->nextToUpdate, ilimit); ++ ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); + } + + /* Match Loop */ +@@ -1903,7 +1944,7 @@ + #endif + while (ip < ilimit) { + size_t matchLength=0; +- size_t offcode=STORE_REPCODE_1; ++ size_t offBase = REPCODE1_TO_OFFBASE; + const BYTE* start=ip+1; + U32 curr = (U32)(ip-base); + +@@ -1922,14 +1963,23 @@ + } } + + /* first search (depth 0) */ +- { size_t offsetFound = 999999999; +- size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offsetFound, mls, rowLog, searchMethod, ZSTD_extDict); ++ { size_t ofbCandidate = 999999999; ++ size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, ZSTD_extDict); + if (ml2 > matchLength) +- matchLength = ml2, start = ip, offcode=offsetFound; ++ matchLength = ml2, start = ip, offBase = ofbCandidate; + } + + if (matchLength < 4) { +- ip += ((ip-anchor) >> kSearchStrength) + 1; /* jump faster over incompressible sections */ ++ size_t const step = ((size_t)(ip-anchor) >> kSearchStrength); ++ ip += step + 1; /* jump faster over incompressible sections */ ++ /* Enter the lazy skipping mode once we are skipping more than 8 bytes at a time. ++ * In this mode we stop inserting every position into our tables, and only insert ++ * positions that we search, which is one in step positions. ++ * The exact cutoff is flexible, I've just chosen a number that is reasonably high, ++ * so we minimize the compression ratio loss in "normal" scenarios. This mode gets ++ * triggered once we've gone 2KB without finding any matches. ++ */ ++ ms->lazySkipping = step > kLazySkippingStep; + continue; + } + +@@ -1939,7 +1989,7 @@ + ip ++; + curr++; + /* check repCode */ +- if (offcode) { ++ if (offBase) { + const U32 windowLow = ZSTD_getLowestMatchIndex(ms, curr, windowLog); + const U32 repIndex = (U32)(curr - offset_1); + const BYTE* const repBase = repIndex < dictLimit ? dictBase : base; +@@ -1951,18 +2001,18 @@ + const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend; + size_t const repLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd, prefixStart) + 4; + int const gain2 = (int)(repLength * 3); +- int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 1); ++ int const gain1 = (int)(matchLength*3 - ZSTD_highbit32((U32)offBase) + 1); + if ((repLength >= 4) && (gain2 > gain1)) +- matchLength = repLength, offcode = STORE_REPCODE_1, start = ip; ++ matchLength = repLength, offBase = REPCODE1_TO_OFFBASE, start = ip; + } } + + /* search match, depth 1 */ +- { size_t offset2=999999999; +- size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offset2, mls, rowLog, searchMethod, ZSTD_extDict); +- int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offset2))); /* raw approx */ +- int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 4); ++ { size_t ofbCandidate = 999999999; ++ size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, ZSTD_extDict); ++ int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ ++ int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 4); + if ((ml2 >= 4) && (gain2 > gain1)) { +- matchLength = ml2, offcode = offset2, start = ip; ++ matchLength = ml2, offBase = ofbCandidate, start = ip; + continue; /* search a better one */ + } } + +@@ -1971,7 +2021,7 @@ + ip ++; + curr++; + /* check repCode */ +- if (offcode) { ++ if (offBase) { + const U32 windowLow = ZSTD_getLowestMatchIndex(ms, curr, windowLog); + const U32 repIndex = (U32)(curr - offset_1); + const BYTE* const repBase = repIndex < dictLimit ? dictBase : base; +@@ -1983,38 +2033,45 @@ + const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend; + size_t const repLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd, prefixStart) + 4; + int const gain2 = (int)(repLength * 4); +- int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 1); ++ int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 1); + if ((repLength >= 4) && (gain2 > gain1)) +- matchLength = repLength, offcode = STORE_REPCODE_1, start = ip; ++ matchLength = repLength, offBase = REPCODE1_TO_OFFBASE, start = ip; + } } + + /* search match, depth 2 */ +- { size_t offset2=999999999; +- size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &offset2, mls, rowLog, searchMethod, ZSTD_extDict); +- int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offset2))); /* raw approx */ +- int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)STORED_TO_OFFBASE(offcode)) + 7); ++ { size_t ofbCandidate = 999999999; ++ size_t const ml2 = ZSTD_searchMax(ms, ip, iend, &ofbCandidate, mls, rowLog, searchMethod, ZSTD_extDict); ++ int const gain2 = (int)(ml2*4 - ZSTD_highbit32((U32)ofbCandidate)); /* raw approx */ ++ int const gain1 = (int)(matchLength*4 - ZSTD_highbit32((U32)offBase) + 7); + if ((ml2 >= 4) && (gain2 > gain1)) { +- matchLength = ml2, offcode = offset2, start = ip; ++ matchLength = ml2, offBase = ofbCandidate, start = ip; + continue; + } } } + break; /* nothing found : store previous solution */ + } + + /* catch up */ +- if (STORED_IS_OFFSET(offcode)) { +- U32 const matchIndex = (U32)((size_t)(start-base) - STORED_OFFSET(offcode)); ++ if (OFFBASE_IS_OFFSET(offBase)) { ++ U32 const matchIndex = (U32)((size_t)(start-base) - OFFBASE_TO_OFFSET(offBase)); + const BYTE* match = (matchIndex < dictLimit) ? dictBase + matchIndex : base + matchIndex; + const BYTE* const mStart = (matchIndex < dictLimit) ? dictStart : prefixStart; + while ((start>anchor) && (match>mStart) && (start[-1] == match[-1])) { start--; match--; matchLength++; } /* catch up */ +- offset_2 = offset_1; offset_1 = (U32)STORED_OFFSET(offcode); ++ offset_2 = offset_1; offset_1 = (U32)OFFBASE_TO_OFFSET(offBase); + } + + /* store sequence */ + _storeSequence: + { size_t const litLength = (size_t)(start - anchor); +- ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offcode, matchLength); ++ ZSTD_storeSeq(seqStore, litLength, anchor, iend, (U32)offBase, matchLength); + anchor = ip = start + matchLength; + } ++ if (ms->lazySkipping) { ++ /* We've found a match, disable lazy skipping mode, and refill the hash cache. */ ++ if (searchMethod == search_rowHash) { ++ ZSTD_row_fillHashCache(ms, base, rowLog, mls, ms->nextToUpdate, ilimit); ++ } ++ ms->lazySkipping = 0; ++ } + + /* check immediate repcode */ + while (ip <= ilimit) { +@@ -2029,8 +2086,8 @@ + /* repcode detected we should take it */ + const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend; + matchLength = ZSTD_count_2segments(ip+4, repMatch+4, iend, repEnd, prefixStart) + 4; +- offcode = offset_2; offset_2 = offset_1; offset_1 = (U32)offcode; /* swap offset history */ +- ZSTD_storeSeq(seqStore, 0, anchor, iend, STORE_REPCODE_1, matchLength); ++ offBase = offset_2; offset_2 = offset_1; offset_1 = (U32)offBase; /* swap offset history */ ++ ZSTD_storeSeq(seqStore, 0, anchor, iend, REPCODE1_TO_OFFBASE, matchLength); + ip += matchLength; + anchor = ip; + continue; /* faster when present ... (?) */ +@@ -2096,7 +2153,6 @@ + size_t ZSTD_compressBlock_lazy2_extDict_row( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize) +- + { + return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2); + } +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_lazy.h b/lib/zstd/compress/zstd_lazy.h +--- a/lib/zstd/compress/zstd_lazy.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_lazy.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -22,6 +23,8 @@ + */ + #define ZSTD_LAZY_DDSS_BUCKET_LOG 2 + ++#define ZSTD_ROW_HASH_TAG_BITS 8 /* nb bits to use for the tag */ ++ + U32 ZSTD_insertAndFindFirstIndex(ZSTD_matchState_t* ms, const BYTE* ip); + void ZSTD_row_update(ZSTD_matchState_t* const ms, const BYTE* ip); + +@@ -113,7 +116,7 @@ + size_t ZSTD_compressBlock_btlazy2_extDict( + ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], + void const* src, size_t srcSize); +- ++ + + + #endif /* ZSTD_LAZY_H */ +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_ldm.c b/lib/zstd/compress/zstd_ldm.c +--- a/lib/zstd/compress/zstd_ldm.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_ldm.c 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -242,11 +243,11 @@ + switch(ms->cParams.strategy) + { + case ZSTD_fast: +- ZSTD_fillHashTable(ms, iend, ZSTD_dtlm_fast); ++ ZSTD_fillHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); + break; + + case ZSTD_dfast: +- ZSTD_fillDoubleHashTable(ms, iend, ZSTD_dtlm_fast); ++ ZSTD_fillDoubleHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); + break; + + case ZSTD_greedy: +@@ -549,7 +550,7 @@ + * the window through early invalidation. + * TODO: * Test the chunk size. + * * Try invalidation after the sequence generation and test the +- * the offset against maxDist directly. ++ * offset against maxDist directly. + * + * NOTE: Because of dictionaries + sequence splitting we MUST make sure + * that any offset used is valid at the END of the sequence, since it may +@@ -711,7 +712,7 @@ + rep[0] = sequence.offset; + /* Store the sequence */ + ZSTD_storeSeq(seqStore, newLitLength, ip - newLitLength, iend, +- STORE_OFFSET(sequence.offset), ++ OFFSET_TO_OFFBASE(sequence.offset), + sequence.matchLength); + ip += sequence.matchLength; + } +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_ldm.h b/lib/zstd/compress/zstd_ldm.h +--- a/lib/zstd/compress/zstd_ldm.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_ldm.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_ldm_geartab.h b/lib/zstd/compress/zstd_ldm_geartab.h +--- a/lib/zstd/compress/zstd_ldm_geartab.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_ldm_geartab.h 2023-11-04 16:35:57.847984084 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_opt.c b/lib/zstd/compress/zstd_opt.c +--- a/lib/zstd/compress/zstd_opt.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_opt.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Przemyslaw Skibinski, Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -16,7 +17,7 @@ + #define ZSTD_LITFREQ_ADD 2 /* scaling factor for litFreq, so that frequencies adapt faster to new stats */ + #define ZSTD_MAX_PRICE (1<<30) + +-#define ZSTD_PREDEF_THRESHOLD 1024 /* if srcSize < ZSTD_PREDEF_THRESHOLD, symbols' cost is assumed static, directly determined by pre-defined distributions */ ++#define ZSTD_PREDEF_THRESHOLD 8 /* if srcSize < ZSTD_PREDEF_THRESHOLD, symbols' cost is assumed static, directly determined by pre-defined distributions */ + + + /*-************************************* +@@ -26,27 +27,35 @@ + #if 0 /* approximation at bit level (for tests) */ + # define BITCOST_ACCURACY 0 + # define BITCOST_MULTIPLIER (1 << BITCOST_ACCURACY) +-# define WEIGHT(stat, opt) ((void)opt, ZSTD_bitWeight(stat)) ++# define WEIGHT(stat, opt) ((void)(opt), ZSTD_bitWeight(stat)) + #elif 0 /* fractional bit accuracy (for tests) */ + # define BITCOST_ACCURACY 8 + # define BITCOST_MULTIPLIER (1 << BITCOST_ACCURACY) +-# define WEIGHT(stat,opt) ((void)opt, ZSTD_fracWeight(stat)) ++# define WEIGHT(stat,opt) ((void)(opt), ZSTD_fracWeight(stat)) + #else /* opt==approx, ultra==accurate */ + # define BITCOST_ACCURACY 8 + # define BITCOST_MULTIPLIER (1 << BITCOST_ACCURACY) +-# define WEIGHT(stat,opt) (opt ? ZSTD_fracWeight(stat) : ZSTD_bitWeight(stat)) ++# define WEIGHT(stat,opt) ((opt) ? ZSTD_fracWeight(stat) : ZSTD_bitWeight(stat)) + #endif + ++/* ZSTD_bitWeight() : ++ * provide estimated "cost" of a stat in full bits only */ + MEM_STATIC U32 ZSTD_bitWeight(U32 stat) + { + return (ZSTD_highbit32(stat+1) * BITCOST_MULTIPLIER); + } + ++/* ZSTD_fracWeight() : ++ * provide fractional-bit "cost" of a stat, ++ * using linear interpolation approximation */ + MEM_STATIC U32 ZSTD_fracWeight(U32 rawStat) + { + U32 const stat = rawStat + 1; + U32 const hb = ZSTD_highbit32(stat); + U32 const BWeight = hb * BITCOST_MULTIPLIER; ++ /* Fweight was meant for "Fractional weight" ++ * but it's effectively a value between 1 and 2 ++ * using fixed point arithmetic */ + U32 const FWeight = (stat << BITCOST_ACCURACY) >> hb; + U32 const weight = BWeight + FWeight; + assert(hb + BITCOST_ACCURACY < 31); +@@ -57,7 +66,7 @@ + /* debugging function, + * @return price in bytes as fractional value + * for debug messages only */ +-MEM_STATIC double ZSTD_fCost(U32 price) ++MEM_STATIC double ZSTD_fCost(int price) + { + return (double)price / (BITCOST_MULTIPLIER*8); + } +@@ -88,20 +97,26 @@ + return total; + } + +-static U32 ZSTD_downscaleStats(unsigned* table, U32 lastEltIndex, U32 shift) ++typedef enum { base_0possible=0, base_1guaranteed=1 } base_directive_e; ++ ++static U32 ++ZSTD_downscaleStats(unsigned* table, U32 lastEltIndex, U32 shift, base_directive_e base1) + { + U32 s, sum=0; +- DEBUGLOG(5, "ZSTD_downscaleStats (nbElts=%u, shift=%u)", (unsigned)lastEltIndex+1, (unsigned)shift); ++ DEBUGLOG(5, "ZSTD_downscaleStats (nbElts=%u, shift=%u)", ++ (unsigned)lastEltIndex+1, (unsigned)shift ); + assert(shift < 30); + for (s=0; s> shift); +- sum += table[s]; ++ unsigned const base = base1 ? 1 : (table[s]>0); ++ unsigned const newStat = base + (table[s] >> shift); ++ sum += newStat; ++ table[s] = newStat; + } + return sum; + } + + /* ZSTD_scaleStats() : +- * reduce all elements in table is sum too large ++ * reduce all elt frequencies in table if sum too large + * return the resulting sum of elements */ + static U32 ZSTD_scaleStats(unsigned* table, U32 lastEltIndex, U32 logTarget) + { +@@ -110,7 +125,7 @@ + DEBUGLOG(5, "ZSTD_scaleStats (nbElts=%u, target=%u)", (unsigned)lastEltIndex+1, (unsigned)logTarget); + assert(logTarget < 30); + if (factor <= 1) return prevsum; +- return ZSTD_downscaleStats(table, lastEltIndex, ZSTD_highbit32(factor)); ++ return ZSTD_downscaleStats(table, lastEltIndex, ZSTD_highbit32(factor), base_1guaranteed); + } + + /* ZSTD_rescaleFreqs() : +@@ -129,18 +144,22 @@ + DEBUGLOG(5, "ZSTD_rescaleFreqs (srcSize=%u)", (unsigned)srcSize); + optPtr->priceType = zop_dynamic; + +- if (optPtr->litLengthSum == 0) { /* first block : init */ +- if (srcSize <= ZSTD_PREDEF_THRESHOLD) { /* heuristic */ +- DEBUGLOG(5, "(srcSize <= ZSTD_PREDEF_THRESHOLD) => zop_predef"); ++ if (optPtr->litLengthSum == 0) { /* no literals stats collected -> first block assumed -> init */ ++ ++ /* heuristic: use pre-defined stats for too small inputs */ ++ if (srcSize <= ZSTD_PREDEF_THRESHOLD) { ++ DEBUGLOG(5, "srcSize <= %i : use predefined stats", ZSTD_PREDEF_THRESHOLD); + optPtr->priceType = zop_predef; + } + + assert(optPtr->symbolCosts != NULL); + if (optPtr->symbolCosts->huf.repeatMode == HUF_repeat_valid) { +- /* huffman table presumed generated by dictionary */ ++ ++ /* huffman stats covering the full value set : table presumed generated by dictionary */ + optPtr->priceType = zop_dynamic; + + if (compressedLiterals) { ++ /* generate literals statistics from huffman table */ + unsigned lit; + assert(optPtr->litFreq != NULL); + optPtr->litSum = 0; +@@ -188,13 +207,14 @@ + optPtr->offCodeSum += optPtr->offCodeFreq[of]; + } } + +- } else { /* not a dictionary */ ++ } else { /* first block, no dictionary */ + + assert(optPtr->litFreq != NULL); + if (compressedLiterals) { ++ /* base initial cost of literals on direct frequency within src */ + unsigned lit = MaxLit; + HIST_count_simple(optPtr->litFreq, &lit, src, srcSize); /* use raw first block to init statistics */ +- optPtr->litSum = ZSTD_downscaleStats(optPtr->litFreq, MaxLit, 8); ++ optPtr->litSum = ZSTD_downscaleStats(optPtr->litFreq, MaxLit, 8, base_0possible); + } + + { unsigned const baseLLfreqs[MaxLL+1] = { +@@ -224,10 +244,9 @@ + optPtr->offCodeSum = sum_u32(baseOFCfreqs, MaxOff+1); + } + +- + } + +- } else { /* new block : re-use previous statistics, scaled down */ ++ } else { /* new block : scale down accumulated statistics */ + + if (compressedLiterals) + optPtr->litSum = ZSTD_scaleStats(optPtr->litFreq, MaxLit, 12); +@@ -255,11 +274,14 @@ + return (litLength*6) * BITCOST_MULTIPLIER; /* 6 bit per literal - no statistic used */ + + /* dynamic statistics */ +- { U32 price = litLength * optPtr->litSumBasePrice; ++ { U32 price = optPtr->litSumBasePrice * litLength; ++ U32 const litPriceMax = optPtr->litSumBasePrice - BITCOST_MULTIPLIER; + U32 u; ++ assert(optPtr->litSumBasePrice >= BITCOST_MULTIPLIER); + for (u=0; u < litLength; u++) { +- assert(WEIGHT(optPtr->litFreq[literals[u]], optLevel) <= optPtr->litSumBasePrice); /* literal cost should never be negative */ +- price -= WEIGHT(optPtr->litFreq[literals[u]], optLevel); ++ U32 litPrice = WEIGHT(optPtr->litFreq[literals[u]], optLevel); ++ if (UNLIKELY(litPrice > litPriceMax)) litPrice = litPriceMax; ++ price -= litPrice; + } + return price; + } +@@ -272,10 +294,11 @@ + assert(litLength <= ZSTD_BLOCKSIZE_MAX); + if (optPtr->priceType == zop_predef) + return WEIGHT(litLength, optLevel); +- /* We can't compute the litLength price for sizes >= ZSTD_BLOCKSIZE_MAX +- * because it isn't representable in the zstd format. So instead just +- * call it 1 bit more than ZSTD_BLOCKSIZE_MAX - 1. In this case the block +- * would be all literals. ++ ++ /* ZSTD_LLcode() can't compute litLength price for sizes >= ZSTD_BLOCKSIZE_MAX ++ * because it isn't representable in the zstd format. ++ * So instead just pretend it would cost 1 bit more than ZSTD_BLOCKSIZE_MAX - 1. ++ * In such a case, the block would be all literals. + */ + if (litLength == ZSTD_BLOCKSIZE_MAX) + return BITCOST_MULTIPLIER + ZSTD_litLengthPrice(ZSTD_BLOCKSIZE_MAX - 1, optPtr, optLevel); +@@ -289,24 +312,25 @@ + } + + /* ZSTD_getMatchPrice() : +- * Provides the cost of the match part (offset + matchLength) of a sequence ++ * Provides the cost of the match part (offset + matchLength) of a sequence. + * Must be combined with ZSTD_fullLiteralsCost() to get the full cost of a sequence. +- * @offcode : expects a scale where 0,1,2 are repcodes 1-3, and 3+ are real_offsets+2 ++ * @offBase : sumtype, representing an offset or a repcode, and using numeric representation of ZSTD_storeSeq() + * @optLevel: when <2, favors small offset for decompression speed (improved cache efficiency) + */ + FORCE_INLINE_TEMPLATE U32 +-ZSTD_getMatchPrice(U32 const offcode, ++ZSTD_getMatchPrice(U32 const offBase, + U32 const matchLength, + const optState_t* const optPtr, + int const optLevel) + { + U32 price; +- U32 const offCode = ZSTD_highbit32(STORED_TO_OFFBASE(offcode)); ++ U32 const offCode = ZSTD_highbit32(offBase); + U32 const mlBase = matchLength - MINMATCH; + assert(matchLength >= MINMATCH); + +- if (optPtr->priceType == zop_predef) /* fixed scheme, do not use statistics */ +- return WEIGHT(mlBase, optLevel) + ((16 + offCode) * BITCOST_MULTIPLIER); ++ if (optPtr->priceType == zop_predef) /* fixed scheme, does not use statistics */ ++ return WEIGHT(mlBase, optLevel) ++ + ((16 + offCode) * BITCOST_MULTIPLIER); /* emulated offset cost */ + + /* dynamic statistics */ + price = (offCode * BITCOST_MULTIPLIER) + (optPtr->offCodeSumBasePrice - WEIGHT(optPtr->offCodeFreq[offCode], optLevel)); +@@ -325,10 +349,10 @@ + } + + /* ZSTD_updateStats() : +- * assumption : literals + litLengtn <= iend */ ++ * assumption : literals + litLength <= iend */ + static void ZSTD_updateStats(optState_t* const optPtr, + U32 litLength, const BYTE* literals, +- U32 offsetCode, U32 matchLength) ++ U32 offBase, U32 matchLength) + { + /* literals */ + if (ZSTD_compressedLiterals(optPtr)) { +@@ -344,8 +368,8 @@ + optPtr->litLengthSum++; + } + +- /* offset code : expected to follow storeSeq() numeric representation */ +- { U32 const offCode = ZSTD_highbit32(STORED_TO_OFFBASE(offsetCode)); ++ /* offset code : follows storeSeq() numeric representation */ ++ { U32 const offCode = ZSTD_highbit32(offBase); + assert(offCode <= MaxOff); + optPtr->offCodeFreq[offCode]++; + optPtr->offCodeSum++; +@@ -552,16 +576,17 @@ + ZSTD_updateTree_internal(ms, ip, iend, ms->cParams.minMatch, ZSTD_noDict); + } + +-FORCE_INLINE_TEMPLATE +-U32 ZSTD_insertBtAndGetAllMatches ( +- ZSTD_match_t* matches, /* store result (found matches) in this table (presumed large enough) */ +- ZSTD_matchState_t* ms, +- U32* nextToUpdate3, +- const BYTE* const ip, const BYTE* const iLimit, const ZSTD_dictMode_e dictMode, +- const U32 rep[ZSTD_REP_NUM], +- U32 const ll0, /* tells if associated literal length is 0 or not. This value must be 0 or 1 */ +- const U32 lengthToBeat, +- U32 const mls /* template */) ++FORCE_INLINE_TEMPLATE U32 ++ZSTD_insertBtAndGetAllMatches ( ++ ZSTD_match_t* matches, /* store result (found matches) in this table (presumed large enough) */ ++ ZSTD_matchState_t* ms, ++ U32* nextToUpdate3, ++ const BYTE* const ip, const BYTE* const iLimit, ++ const ZSTD_dictMode_e dictMode, ++ const U32 rep[ZSTD_REP_NUM], ++ const U32 ll0, /* tells if associated literal length is 0 or not. This value must be 0 or 1 */ ++ const U32 lengthToBeat, ++ const U32 mls /* template */) + { + const ZSTD_compressionParameters* const cParams = &ms->cParams; + U32 const sufficient_len = MIN(cParams->targetLength, ZSTD_OPT_NUM -1); +@@ -644,7 +669,7 @@ + DEBUGLOG(8, "found repCode %u (ll0:%u, offset:%u) of length %u", + repCode, ll0, repOffset, repLen); + bestLength = repLen; +- matches[mnum].off = STORE_REPCODE(repCode - ll0 + 1); /* expect value between 1 and 3 */ ++ matches[mnum].off = REPCODE_TO_OFFBASE(repCode - ll0 + 1); /* expect value between 1 and 3 */ + matches[mnum].len = (U32)repLen; + mnum++; + if ( (repLen > sufficient_len) +@@ -673,7 +698,7 @@ + bestLength = mlen; + assert(curr > matchIndex3); + assert(mnum==0); /* no prior solution */ +- matches[0].off = STORE_OFFSET(curr - matchIndex3); ++ matches[0].off = OFFSET_TO_OFFBASE(curr - matchIndex3); + matches[0].len = (U32)mlen; + mnum = 1; + if ( (mlen > sufficient_len) | +@@ -706,13 +731,13 @@ + } + + if (matchLength > bestLength) { +- DEBUGLOG(8, "found match of length %u at distance %u (offCode=%u)", +- (U32)matchLength, curr - matchIndex, STORE_OFFSET(curr - matchIndex)); ++ DEBUGLOG(8, "found match of length %u at distance %u (offBase=%u)", ++ (U32)matchLength, curr - matchIndex, OFFSET_TO_OFFBASE(curr - matchIndex)); + assert(matchEndIdx > matchIndex); + if (matchLength > matchEndIdx - matchIndex) + matchEndIdx = matchIndex + (U32)matchLength; + bestLength = matchLength; +- matches[mnum].off = STORE_OFFSET(curr - matchIndex); ++ matches[mnum].off = OFFSET_TO_OFFBASE(curr - matchIndex); + matches[mnum].len = (U32)matchLength; + mnum++; + if ( (matchLength > ZSTD_OPT_NUM) +@@ -754,12 +779,12 @@ + + if (matchLength > bestLength) { + matchIndex = dictMatchIndex + dmsIndexDelta; +- DEBUGLOG(8, "found dms match of length %u at distance %u (offCode=%u)", +- (U32)matchLength, curr - matchIndex, STORE_OFFSET(curr - matchIndex)); ++ DEBUGLOG(8, "found dms match of length %u at distance %u (offBase=%u)", ++ (U32)matchLength, curr - matchIndex, OFFSET_TO_OFFBASE(curr - matchIndex)); + if (matchLength > matchEndIdx - matchIndex) + matchEndIdx = matchIndex + (U32)matchLength; + bestLength = matchLength; +- matches[mnum].off = STORE_OFFSET(curr - matchIndex); ++ matches[mnum].off = OFFSET_TO_OFFBASE(curr - matchIndex); + matches[mnum].len = (U32)matchLength; + mnum++; + if ( (matchLength > ZSTD_OPT_NUM) +@@ -960,7 +985,7 @@ + const ZSTD_optLdm_t* optLdm, U32 currPosInBlock) + { + U32 const posDiff = currPosInBlock - optLdm->startPosInBlock; +- /* Note: ZSTD_match_t actually contains offCode and matchLength (before subtracting MINMATCH) */ ++ /* Note: ZSTD_match_t actually contains offBase and matchLength (before subtracting MINMATCH) */ + U32 const candidateMatchLength = optLdm->endPosInBlock - optLdm->startPosInBlock - posDiff; + + /* Ensure that current block position is not outside of the match */ +@@ -971,11 +996,11 @@ + } + + if (*nbMatches == 0 || ((candidateMatchLength > matches[*nbMatches-1].len) && *nbMatches < ZSTD_OPT_NUM)) { +- U32 const candidateOffCode = STORE_OFFSET(optLdm->offset); +- DEBUGLOG(6, "ZSTD_optLdm_maybeAddMatch(): Adding ldm candidate match (offCode: %u matchLength %u) at block position=%u", +- candidateOffCode, candidateMatchLength, currPosInBlock); ++ U32 const candidateOffBase = OFFSET_TO_OFFBASE(optLdm->offset); ++ DEBUGLOG(6, "ZSTD_optLdm_maybeAddMatch(): Adding ldm candidate match (offBase: %u matchLength %u) at block position=%u", ++ candidateOffBase, candidateMatchLength, currPosInBlock); + matches[*nbMatches].len = candidateMatchLength; +- matches[*nbMatches].off = candidateOffCode; ++ matches[*nbMatches].off = candidateOffBase; + (*nbMatches)++; + } + } +@@ -1062,6 +1087,8 @@ + ZSTD_optimal_t lastSequence; + ZSTD_optLdm_t optLdm; + ++ ZSTD_memset(&lastSequence, 0, sizeof(ZSTD_optimal_t)); ++ + optLdm.seqStore = ms->ldmSeqStore ? *ms->ldmSeqStore : kNullRawSeqStore; + optLdm.endPosInBlock = optLdm.startPosInBlock = optLdm.offset = 0; + ZSTD_opt_getNextMatchAndUpdateSeqStore(&optLdm, (U32)(ip-istart), (U32)(iend-ip)); +@@ -1098,14 +1125,14 @@ + + /* large match -> immediate encoding */ + { U32 const maxML = matches[nbMatches-1].len; +- U32 const maxOffcode = matches[nbMatches-1].off; +- DEBUGLOG(6, "found %u matches of maxLength=%u and maxOffCode=%u at cPos=%u => start new series", +- nbMatches, maxML, maxOffcode, (U32)(ip-prefixStart)); ++ U32 const maxOffBase = matches[nbMatches-1].off; ++ DEBUGLOG(6, "found %u matches of maxLength=%u and maxOffBase=%u at cPos=%u => start new series", ++ nbMatches, maxML, maxOffBase, (U32)(ip-prefixStart)); + + if (maxML > sufficient_len) { + lastSequence.litlen = litlen; + lastSequence.mlen = maxML; +- lastSequence.off = maxOffcode; ++ lastSequence.off = maxOffBase; + DEBUGLOG(6, "large match (%u>%u), immediate encoding", + maxML, sufficient_len); + cur = 0; +@@ -1122,15 +1149,15 @@ + opt[pos].price = ZSTD_MAX_PRICE; /* mlen, litlen and price will be fixed during forward scanning */ + } + for (matchNb = 0; matchNb < nbMatches; matchNb++) { +- U32 const offcode = matches[matchNb].off; ++ U32 const offBase = matches[matchNb].off; + U32 const end = matches[matchNb].len; + for ( ; pos <= end ; pos++ ) { +- U32 const matchPrice = ZSTD_getMatchPrice(offcode, pos, optStatePtr, optLevel); ++ U32 const matchPrice = ZSTD_getMatchPrice(offBase, pos, optStatePtr, optLevel); + U32 const sequencePrice = literalsPrice + matchPrice; + DEBUGLOG(7, "rPos:%u => set initial price : %.2f", +- pos, ZSTD_fCost(sequencePrice)); ++ pos, ZSTD_fCost((int)sequencePrice)); + opt[pos].mlen = pos; +- opt[pos].off = offcode; ++ opt[pos].off = offBase; + opt[pos].litlen = litlen; + opt[pos].price = (int)sequencePrice; + } } +@@ -1230,7 +1257,7 @@ + U32 const startML = (matchNb>0) ? matches[matchNb-1].len+1 : minMatch; + U32 mlen; + +- DEBUGLOG(7, "testing match %u => offCode=%4u, mlen=%2u, llen=%2u", ++ DEBUGLOG(7, "testing match %u => offBase=%4u, mlen=%2u, llen=%2u", + matchNb, matches[matchNb].off, lastML, litlen); + + for (mlen = lastML; mlen >= startML; mlen--) { /* scan downward */ +@@ -1296,7 +1323,7 @@ + for (storePos=storeStart; storePos <= storeEnd; storePos++) { + U32 const llen = opt[storePos].litlen; + U32 const mlen = opt[storePos].mlen; +- U32 const offCode = opt[storePos].off; ++ U32 const offBase = opt[storePos].off; + U32 const advance = llen + mlen; + DEBUGLOG(6, "considering seq starting at %zi, llen=%u, mlen=%u", + anchor - istart, (unsigned)llen, (unsigned)mlen); +@@ -1308,8 +1335,8 @@ + } + + assert(anchor + llen <= iend); +- ZSTD_updateStats(optStatePtr, llen, anchor, offCode, mlen); +- ZSTD_storeSeq(seqStore, llen, anchor, iend, offCode, mlen); ++ ZSTD_updateStats(optStatePtr, llen, anchor, offBase, mlen); ++ ZSTD_storeSeq(seqStore, llen, anchor, iend, offBase, mlen); + anchor += advance; + ip = anchor; + } } +@@ -1349,7 +1376,7 @@ + /* ZSTD_initStats_ultra(): + * make a first compression pass, just to seed stats with more accurate starting values. + * only works on first block, with no dictionary and no ldm. +- * this function cannot error, hence its contract must be respected. ++ * this function cannot error out, its narrow contract must be respected. + */ + static void + ZSTD_initStats_ultra(ZSTD_matchState_t* ms, +@@ -1368,7 +1395,7 @@ + + ZSTD_compressBlock_opt2(ms, seqStore, tmpRep, src, srcSize, ZSTD_noDict); /* generate stats into ms->opt*/ + +- /* invalidate first scan from history */ ++ /* invalidate first scan from history, only keep entropy stats */ + ZSTD_resetSeqStore(seqStore); + ms->window.base -= srcSize; + ms->window.dictLimit += (U32)srcSize; +@@ -1392,20 +1419,20 @@ + U32 const curr = (U32)((const BYTE*)src - ms->window.base); + DEBUGLOG(5, "ZSTD_compressBlock_btultra2 (srcSize=%zu)", srcSize); + +- /* 2-pass strategy: ++ /* 2-passes strategy: + * this strategy makes a first pass over first block to collect statistics +- * and seed next round's statistics with it. +- * After 1st pass, function forgets everything, and starts a new block. ++ * in order to seed next round's statistics with it. ++ * After 1st pass, function forgets history, and starts a new block. + * Consequently, this can only work if no data has been previously loaded in tables, + * aka, no dictionary, no prefix, no ldm preprocessing. + * The compression ratio gain is generally small (~0.5% on first block), +- * the cost is 2x cpu time on first block. */ ++ ** the cost is 2x cpu time on first block. */ + assert(srcSize <= ZSTD_BLOCKSIZE_MAX); + if ( (ms->opt.litLengthSum==0) /* first block */ + && (seqStore->sequences == seqStore->sequencesStart) /* no ldm */ + && (ms->window.dictLimit == ms->window.lowLimit) /* no dictionary */ +- && (curr == ms->window.dictLimit) /* start of frame, nothing already loaded nor skipped */ +- && (srcSize > ZSTD_PREDEF_THRESHOLD) ++ && (curr == ms->window.dictLimit) /* start of frame, nothing already loaded nor skipped */ ++ && (srcSize > ZSTD_PREDEF_THRESHOLD) /* input large enough to not employ default stats */ + ) { + ZSTD_initStats_ultra(ms, seqStore, rep, src, srcSize); + } +diff '--color=auto' -uraN a/lib/zstd/compress/zstd_opt.h b/lib/zstd/compress/zstd_opt.h +--- a/lib/zstd/compress/zstd_opt.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/compress/zstd_opt.h 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c +--- a/lib/zstd/decompress/huf_decompress.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress/huf_decompress.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,7 +1,8 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* ****************************************************************** + * huff0 huffman decoder, + * part of Finite State Entropy library +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - FSE+HUF source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -19,10 +20,10 @@ + #include "../common/compiler.h" + #include "../common/bitstream.h" /* BIT_* */ + #include "../common/fse.h" /* to compress headers */ +-#define HUF_STATIC_LINKING_ONLY + #include "../common/huf.h" + #include "../common/error_private.h" + #include "../common/zstd_internal.h" ++#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_countTrailingZeros64 */ + + /* ************************************************************** + * Constants +@@ -43,27 +44,25 @@ + #error "Cannot force the use of the X1 and X2 decoders at the same time!" + #endif + +-#if ZSTD_ENABLE_ASM_X86_64_BMI2 && DYNAMIC_BMI2 +-# define HUF_ASM_X86_64_BMI2_ATTRS BMI2_TARGET_ATTRIBUTE ++/* When DYNAMIC_BMI2 is enabled, fast decoders are only called when bmi2 is ++ * supported at runtime, so we can add the BMI2 target attribute. ++ * When it is disabled, we will still get BMI2 if it is enabled statically. ++ */ ++#if DYNAMIC_BMI2 ++# define HUF_FAST_BMI2_ATTRS BMI2_TARGET_ATTRIBUTE + #else +-# define HUF_ASM_X86_64_BMI2_ATTRS ++# define HUF_FAST_BMI2_ATTRS + #endif + + #define HUF_EXTERN_C + #define HUF_ASM_DECL HUF_EXTERN_C + +-#if DYNAMIC_BMI2 || (ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__)) ++#if DYNAMIC_BMI2 + # define HUF_NEED_BMI2_FUNCTION 1 + #else + # define HUF_NEED_BMI2_FUNCTION 0 + #endif + +-#if !(ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__)) +-# define HUF_NEED_DEFAULT_FUNCTION 1 +-#else +-# define HUF_NEED_DEFAULT_FUNCTION 0 +-#endif +- + /* ************************************************************** + * Error Management + ****************************************************************/ +@@ -80,6 +79,11 @@ + /* ************************************************************** + * BMI2 Variant Wrappers + ****************************************************************/ ++typedef size_t (*HUF_DecompressUsingDTableFn)(void *dst, size_t dstSize, ++ const void *cSrc, ++ size_t cSrcSize, ++ const HUF_DTable *DTable); ++ + #if DYNAMIC_BMI2 + + #define HUF_DGEN(fn) \ +@@ -101,9 +105,9 @@ + } \ + \ + static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ +- size_t cSrcSize, HUF_DTable const* DTable, int bmi2) \ ++ size_t cSrcSize, HUF_DTable const* DTable, int flags) \ + { \ +- if (bmi2) { \ ++ if (flags & HUF_flags_bmi2) { \ + return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \ + } \ + return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \ +@@ -113,9 +117,9 @@ + + #define HUF_DGEN(fn) \ + static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ +- size_t cSrcSize, HUF_DTable const* DTable, int bmi2) \ ++ size_t cSrcSize, HUF_DTable const* DTable, int flags) \ + { \ +- (void)bmi2; \ ++ (void)flags; \ + return fn##_body(dst, dstSize, cSrc, cSrcSize, DTable); \ + } + +@@ -134,15 +138,28 @@ + return dtd; + } + +-#if ZSTD_ENABLE_ASM_X86_64_BMI2 +- +-static size_t HUF_initDStream(BYTE const* ip) { ++static size_t HUF_initFastDStream(BYTE const* ip) { + BYTE const lastByte = ip[7]; +- size_t const bitsConsumed = lastByte ? 8 - BIT_highbit32(lastByte) : 0; ++ size_t const bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0; + size_t const value = MEM_readLEST(ip) | 1; + assert(bitsConsumed <= 8); ++ assert(sizeof(size_t) == 8); + return value << bitsConsumed; + } ++ ++ ++/* ++ * The input/output arguments to the Huffman fast decoding loop: ++ * ++ * ip [in/out] - The input pointers, must be updated to reflect what is consumed. ++ * op [in/out] - The output pointers, must be updated to reflect what is written. ++ * bits [in/out] - The bitstream containers, must be updated to reflect the current state. ++ * dt [in] - The decoding table. ++ * ilimit [in] - The input limit, stop when any input pointer is below ilimit. ++ * oend [in] - The end of the output stream. op[3] must not cross oend. ++ * iend [in] - The end of each input stream. ip[i] may cross iend[i], ++ * as long as it is above ilimit, but that indicates corruption. ++ */ + typedef struct { + BYTE const* ip[4]; + BYTE* op[4]; +@@ -151,15 +168,17 @@ + BYTE const* ilimit; + BYTE* oend; + BYTE const* iend[4]; +-} HUF_DecompressAsmArgs; ++} HUF_DecompressFastArgs; ++ ++typedef void (*HUF_DecompressFastLoopFn)(HUF_DecompressFastArgs*); + + /* +- * Initializes args for the asm decoding loop. +- * @returns 0 on success +- * 1 if the fallback implementation should be used. ++ * Initializes args for the fast decoding loop. ++ * @returns 1 on success ++ * 0 if the fallback implementation should be used. + * Or an error code on failure. + */ +-static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, size_t dstSize, void const* src, size_t srcSize, const HUF_DTable* DTable) ++static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* dst, size_t dstSize, void const* src, size_t srcSize, const HUF_DTable* DTable) + { + void const* dt = DTable + 1; + U32 const dtLog = HUF_getDTableDesc(DTable).tableLog; +@@ -168,9 +187,11 @@ + + BYTE* const oend = (BYTE*)dst + dstSize; + +- /* The following condition is false on x32 platform, +- * but HUF_asm is not compatible with this ABI */ +- if (!(MEM_isLittleEndian() && !MEM_32bits())) return 1; ++ /* The fast decoding loop assumes 64-bit little-endian. ++ * This condition is false on x32. ++ */ ++ if (!MEM_isLittleEndian() || MEM_32bits()) ++ return 0; + + /* strict minimum : jump table + 1 byte per stream */ + if (srcSize < 10) +@@ -181,7 +202,7 @@ + * 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) +- return 1; ++ return 0; + + /* Read the jump table. */ + { +@@ -195,13 +216,13 @@ + args->iend[2] = args->iend[1] + length2; + args->iend[3] = args->iend[2] + length3; + +- /* HUF_initDStream() requires this, and this small of an input ++ /* HUF_initFastDStream() requires this, and this small of an input + * won't benefit from the ASM loop anyways. + * length1 must be >= 16 so that ip[0] >= ilimit before the loop + * starts. + */ + if (length1 < 16 || length2 < 8 || length3 < 8 || length4 < 8) +- return 1; ++ return 0; + if (length4 > srcSize) return ERROR(corruption_detected); /* overflow */ + } + /* ip[] contains the position that is currently loaded into bits[]. */ +@@ -218,7 +239,7 @@ + + /* No point to call the ASM loop for tiny outputs. */ + if (args->op[3] >= oend) +- return 1; ++ return 0; + + /* bits[] is the bit container. + * It is read from the MSB down to the LSB. +@@ -227,10 +248,10 @@ + * set, so that CountTrailingZeros(bits[]) can be used + * to count how many bits we've consumed. + */ +- args->bits[0] = HUF_initDStream(args->ip[0]); +- args->bits[1] = HUF_initDStream(args->ip[1]); +- args->bits[2] = HUF_initDStream(args->ip[2]); +- args->bits[3] = HUF_initDStream(args->ip[3]); ++ args->bits[0] = HUF_initFastDStream(args->ip[0]); ++ args->bits[1] = HUF_initFastDStream(args->ip[1]); ++ args->bits[2] = HUF_initFastDStream(args->ip[2]); ++ args->bits[3] = HUF_initFastDStream(args->ip[3]); + + /* If ip[] >= ilimit, it is guaranteed to be safe to + * reload bits[]. It may be beyond its section, but is +@@ -241,10 +262,10 @@ + args->oend = oend; + args->dt = dt; + +- return 0; ++ return 1; + } + +-static size_t HUF_initRemainingDStream(BIT_DStream_t* bit, HUF_DecompressAsmArgs const* args, int stream, BYTE* segmentEnd) ++static size_t HUF_initRemainingDStream(BIT_DStream_t* bit, HUF_DecompressFastArgs const* args, int stream, BYTE* segmentEnd) + { + /* Validate that we haven't overwritten. */ + if (args->op[stream] > segmentEnd) +@@ -258,15 +279,15 @@ + return ERROR(corruption_detected); + + /* Construct the BIT_DStream_t. */ +- bit->bitContainer = MEM_readLE64(args->ip[stream]); +- bit->bitsConsumed = ZSTD_countTrailingZeros((size_t)args->bits[stream]); ++ assert(sizeof(size_t) == 8); ++ bit->bitContainer = MEM_readLEST(args->ip[stream]); ++ bit->bitsConsumed = ZSTD_countTrailingZeros64(args->bits[stream]); + bit->start = (const char*)args->iend[0]; + bit->limitPtr = bit->start + sizeof(size_t); + bit->ptr = (const char*)args->ip[stream]; + + return 0; + } +-#endif + + + #ifndef HUF_FORCE_DECOMPRESS_X2 +@@ -283,10 +304,11 @@ + static U64 HUF_DEltX1_set4(BYTE symbol, BYTE nbBits) { + U64 D4; + if (MEM_isLittleEndian()) { +- D4 = (symbol << 8) + nbBits; ++ D4 = (U64)((symbol << 8) + nbBits); + } else { +- D4 = symbol + (nbBits << 8); ++ D4 = (U64)(symbol + (nbBits << 8)); + } ++ assert(D4 < (1U << 16)); + D4 *= 0x0001000100010001ULL; + return D4; + } +@@ -329,13 +351,7 @@ + BYTE huffWeight[HUF_SYMBOLVALUE_MAX + 1]; + } HUF_ReadDTableX1_Workspace; + +- +-size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize) +-{ +- return HUF_readDTableX1_wksp_bmi2(DTable, src, srcSize, workSpace, wkspSize, /* bmi2 */ 0); +-} +- +-size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int bmi2) ++size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags) + { + U32 tableLog = 0; + U32 nbSymbols = 0; +@@ -350,7 +366,7 @@ + DEBUG_STATIC_ASSERT(sizeof(DTableDesc) == sizeof(HUF_DTable)); + /* ZSTD_memset(huffWeight, 0, sizeof(huffWeight)); */ /* is not necessary, even though some analyzer complain ... */ + +- iSize = HUF_readStats_wksp(wksp->huffWeight, HUF_SYMBOLVALUE_MAX + 1, wksp->rankVal, &nbSymbols, &tableLog, src, srcSize, wksp->statsWksp, sizeof(wksp->statsWksp), bmi2); ++ iSize = HUF_readStats_wksp(wksp->huffWeight, HUF_SYMBOLVALUE_MAX + 1, wksp->rankVal, &nbSymbols, &tableLog, src, srcSize, wksp->statsWksp, sizeof(wksp->statsWksp), flags); + if (HUF_isError(iSize)) return iSize; + + +@@ -377,9 +393,8 @@ + * rankStart[0] is not filled because there are no entries in the table for + * weight 0. + */ +- { +- int n; +- int nextRankStart = 0; ++ { int n; ++ U32 nextRankStart = 0; + int const unroll = 4; + int const nLimit = (int)nbSymbols - unroll + 1; + for (n=0; n<(int)tableLog+1; n++) { +@@ -406,10 +421,9 @@ + * We can switch based on the length to a different inner loop which is + * optimized for that particular case. + */ +- { +- U32 w; +- int symbol=wksp->rankVal[0]; +- int rankStart=0; ++ { U32 w; ++ int symbol = wksp->rankVal[0]; ++ int rankStart = 0; + for (w=1; wrankVal[w]; + int const length = (1 << w) >> 1; +@@ -519,7 +533,7 @@ + while (p < pEnd) + HUF_DECODE_SYMBOLX1_0(p, bitDPtr); + +- return pEnd-pStart; ++ return (size_t)(pEnd-pStart); + } + + FORCE_INLINE_TEMPLATE size_t +@@ -545,6 +559,10 @@ + return dstSize; + } + ++/* HUF_decompress4X1_usingDTable_internal_body(): ++ * Conditions : ++ * @dstSize >= 6 ++ */ + FORCE_INLINE_TEMPLATE size_t + HUF_decompress4X1_usingDTable_internal_body( + void* dst, size_t dstSize, +@@ -588,6 +606,7 @@ + + if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ + if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ ++ if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ + 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 @@ + } + #endif + +-#if HUF_NEED_DEFAULT_FUNCTION + static + size_t HUF_decompress4X1_usingDTable_internal_default(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 + +-HUF_ASM_DECL void HUF_decompress4X1_usingDTable_internal_bmi2_asm_loop(HUF_DecompressAsmArgs* args) ZSTDLIB_HIDDEN; ++HUF_ASM_DECL void HUF_decompress4X1_usingDTable_internal_fast_asm_loop(HUF_DecompressFastArgs* args) ZSTDLIB_HIDDEN; + +-static HUF_ASM_X86_64_BMI2_ATTRS ++#endif ++ ++static HUF_FAST_BMI2_ATTRS ++void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* args) ++{ ++ U64 bits[4]; ++ BYTE const* ip[4]; ++ BYTE* op[4]; ++ U16 const* const dtable = (U16 const*)args->dt; ++ BYTE* const oend = args->oend; ++ BYTE const* const ilimit = args->ilimit; ++ ++ /* Copy the arguments to local variables */ ++ ZSTD_memcpy(&bits, &args->bits, sizeof(bits)); ++ ZSTD_memcpy((void*)(&ip), &args->ip, sizeof(ip)); ++ ZSTD_memcpy(&op, &args->op, sizeof(op)); ++ ++ assert(MEM_isLittleEndian()); ++ assert(!MEM_32bits()); ++ ++ for (;;) { ++ BYTE* olimit; ++ int stream; ++ int symbol; ++ ++ /* Assert loop preconditions */ ++#ifndef NDEBUG ++ for (stream = 0; stream < 4; ++stream) { ++ assert(op[stream] <= (stream == 3 ? oend : op[stream + 1])); ++ assert(ip[stream] >= ilimit); ++ } ++#endif ++ /* Compute olimit */ ++ { ++ /* Each iteration produces 5 output symbols per stream */ ++ size_t const oiters = (size_t)(oend - op[3]) / 5; ++ /* Each iteration consumes up to 11 bits * 5 = 55 bits < 7 bytes ++ * per stream. ++ */ ++ size_t const iiters = (size_t)(ip[0] - ilimit) / 7; ++ /* We can safely run iters iterations before running bounds checks */ ++ size_t const iters = MIN(oiters, iiters); ++ size_t const symbols = iters * 5; ++ ++ /* We can simply check that op[3] < olimit, instead of checking all ++ * of our bounds, since we can't hit the other bounds until we've run ++ * iters iterations, which only happens when op[3] == olimit. ++ */ ++ olimit = op[3] + symbols; ++ ++ /* Exit fast decoding loop once we get close to the end. */ ++ if (op[3] + 20 > olimit) ++ break; ++ ++ /* Exit the decoding loop if any input pointer has crossed the ++ * previous one. This indicates corruption, and a precondition ++ * to our loop is that ip[i] >= ip[0]. ++ */ ++ for (stream = 1; stream < 4; ++stream) { ++ if (ip[stream] < ip[stream - 1]) ++ goto _out; ++ } ++ } ++ ++#ifndef NDEBUG ++ for (stream = 1; stream < 4; ++stream) { ++ assert(ip[stream] >= ip[stream - 1]); ++ } ++#endif ++ ++ 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; ++ } ++ } while (op[3] < olimit); ++ } ++ ++_out: ++ ++ /* Save the final values of each of the state variables back to args. */ ++ ZSTD_memcpy(&args->bits, &bits, sizeof(bits)); ++ ZSTD_memcpy((void*)(&args->ip), &ip, sizeof(ip)); ++ ZSTD_memcpy(&args->op, &op, sizeof(op)); ++} ++ ++/* ++ * @returns @p dstSize on success (>= 6) ++ * 0 if the fallback implementation should be used ++ * An error if an error occurred ++ */ ++static HUF_FAST_BMI2_ATTRS + size_t +-HUF_decompress4X1_usingDTable_internal_bmi2_asm( ++HUF_decompress4X1_usingDTable_internal_fast( + void* dst, size_t dstSize, + const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) ++ const HUF_DTable* DTable, ++ HUF_DecompressFastLoopFn loopFn) + { + void const* dt = DTable + 1; + const BYTE* const iend = (const BYTE*)cSrc + 6; + BYTE* const oend = (BYTE*)dst + dstSize; +- HUF_DecompressAsmArgs args; +- { +- size_t const ret = HUF_DecompressAsmArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); +- FORWARD_IF_ERROR(ret, "Failed to init asm args"); +- if (ret != 0) +- return HUF_decompress4X1_usingDTable_internal_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); ++ HUF_DecompressFastArgs args; ++ { size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); ++ FORWARD_IF_ERROR(ret, "Failed to init fast loop args"); ++ if (ret == 0) ++ return 0; + } + + assert(args.ip[0] >= args.ilimit); +- HUF_decompress4X1_usingDTable_internal_bmi2_asm_loop(&args); ++ loopFn(&args); + + /* Our loop guarantees that ip[] >= ilimit and that we haven't + * overwritten any op[]. +@@ -694,8 +817,7 @@ + (void)iend; + + /* finish bit streams one by one. */ +- { +- size_t const segmentSize = (dstSize+3) / 4; ++ { size_t const segmentSize = (dstSize+3) / 4; + BYTE* segmentEnd = (BYTE*)dst; + int i; + for (i = 0; i < 4; ++i) { +@@ -712,97 +834,59 @@ + } + + /* decoded size */ ++ assert(dstSize != 0); + return dstSize; + } +-#endif /* ZSTD_ENABLE_ASM_X86_64_BMI2 */ +- +-typedef size_t (*HUF_decompress_usingDTable_t)(void *dst, size_t dstSize, +- const void *cSrc, +- size_t cSrcSize, +- const HUF_DTable *DTable); + + HUF_DGEN(HUF_decompress1X1_usingDTable_internal) + + static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, void const* cSrc, +- size_t cSrcSize, HUF_DTable const* DTable, int bmi2) ++ size_t cSrcSize, HUF_DTable const* DTable, int flags) + { ++ HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X1_usingDTable_internal_default; ++ HUF_DecompressFastLoopFn loopFn = HUF_decompress4X1_usingDTable_internal_fast_c_loop; ++ + #if DYNAMIC_BMI2 +- if (bmi2) { ++ if (flags & HUF_flags_bmi2) { ++ fallbackFn = HUF_decompress4X1_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 +- return HUF_decompress4X1_usingDTable_internal_bmi2_asm(dst, dstSize, cSrc, cSrcSize, DTable); +-# else +- return HUF_decompress4X1_usingDTable_internal_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); ++ if (!(flags & HUF_flags_disableAsm)) { ++ loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; ++ } + # endif ++ } else { ++ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#else +- (void)bmi2; + #endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) +- return HUF_decompress4X1_usingDTable_internal_bmi2_asm(dst, dstSize, cSrc, cSrcSize, DTable); +-#else +- return HUF_decompress4X1_usingDTable_internal_default(dst, dstSize, cSrc, cSrcSize, DTable); ++ if (!(flags & HUF_flags_disableAsm)) { ++ loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; ++ } + #endif +-} +- +- +-size_t HUF_decompress1X1_usingDTable( +- void* dst, size_t dstSize, +- const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) +-{ +- DTableDesc dtd = HUF_getDTableDesc(DTable); +- 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); +- ip += hSize; cSrcSize -= hSize; + +- return HUF_decompress1X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, /* bmi2 */ 0); +-} +- +- +-size_t HUF_decompress4X1_usingDTable( +- void* dst, size_t dstSize, +- const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) +-{ +- 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)) { ++ size_t const ret = HUF_decompress4X1_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); ++ if (ret != 0) ++ return ret; ++ } ++ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } + +-static size_t HUF_decompress4X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, ++static 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, int bmi2) ++ void* workSpace, size_t wkspSize, int flags) + { + const BYTE* ip = (const BYTE*) cSrc; + +- size_t const hSize = HUF_readDTableX1_wksp_bmi2(dctx, cSrc, cSrcSize, workSpace, wkspSize, bmi2); ++ size_t const hSize = HUF_readDTableX1_wksp(dctx, cSrc, cSrcSize, workSpace, wkspSize, flags); + if (HUF_isError(hSize)) return hSize; + if (hSize >= cSrcSize) return ERROR(srcSize_wrong); + 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); +-} +- +- + #endif /* HUF_FORCE_DECOMPRESS_X2 */ + + +@@ -985,7 +1069,7 @@ + + static void HUF_fillDTableX2(HUF_DEltX2* DTable, const U32 targetLog, + const sortedSymbol_t* sortedList, +- const U32* rankStart, rankValCol_t *rankValOrigin, const U32 maxWeight, ++ const U32* rankStart, rankValCol_t* rankValOrigin, const U32 maxWeight, + const U32 nbBitsBaseline) + { + U32* const rankVal = rankValOrigin[0]; +@@ -1040,14 +1124,7 @@ + + size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, + const void* src, size_t srcSize, +- void* workSpace, size_t wkspSize) +-{ +- return HUF_readDTableX2_wksp_bmi2(DTable, src, srcSize, workSpace, wkspSize, /* bmi2 */ 0); +-} +- +-size_t HUF_readDTableX2_wksp_bmi2(HUF_DTable* DTable, +- const void* src, size_t srcSize, +- void* workSpace, size_t wkspSize, int bmi2) ++ void* workSpace, size_t wkspSize, int flags) + { + U32 tableLog, maxW, nbSymbols; + DTableDesc dtd = HUF_getDTableDesc(DTable); +@@ -1069,7 +1146,7 @@ + if (maxTableLog > HUF_TABLELOG_MAX) return ERROR(tableLog_tooLarge); + /* ZSTD_memset(weightList, 0, sizeof(weightList)); */ /* is not necessary, even though some analyzer complain ... */ + +- iSize = HUF_readStats_wksp(wksp->weightList, HUF_SYMBOLVALUE_MAX + 1, wksp->rankStats, &nbSymbols, &tableLog, src, srcSize, wksp->calleeWksp, sizeof(wksp->calleeWksp), bmi2); ++ iSize = HUF_readStats_wksp(wksp->weightList, HUF_SYMBOLVALUE_MAX + 1, wksp->rankStats, &nbSymbols, &tableLog, src, srcSize, wksp->calleeWksp, sizeof(wksp->calleeWksp), flags); + if (HUF_isError(iSize)) return iSize; + + /* check result */ +@@ -1240,6 +1317,11 @@ + /* decoded size */ + return dstSize; + } ++ ++/* HUF_decompress4X2_usingDTable_internal_body(): ++ * Conditions: ++ * @dstSize >= 6 ++ */ + FORCE_INLINE_TEMPLATE size_t + HUF_decompress4X2_usingDTable_internal_body( + void* dst, size_t dstSize, +@@ -1280,8 +1362,9 @@ + DTableDesc const dtd = HUF_getDTableDesc(DTable); + U32 const dtLog = dtd.tableLog; + +- if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ +- if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ ++ if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ ++ if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ ++ if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */ + 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 @@ + } + #endif + +-#if HUF_NEED_DEFAULT_FUNCTION + static + size_t HUF_decompress4X2_usingDTable_internal_default(void* dst, size_t dstSize, void const* cSrc, + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 + +-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 ++void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* args) ++{ ++ U64 bits[4]; ++ BYTE const* ip[4]; ++ BYTE* op[4]; ++ BYTE* oend[4]; ++ HUF_DEltX2 const* const dtable = (HUF_DEltX2 const*)args->dt; ++ BYTE const* const ilimit = args->ilimit; ++ ++ /* Copy the arguments to local registers. */ ++ ZSTD_memcpy(&bits, &args->bits, sizeof(bits)); ++ ZSTD_memcpy((void*)(&ip), &args->ip, sizeof(ip)); ++ ZSTD_memcpy(&op, &args->op, sizeof(op)); ++ ++ oend[0] = op[1]; ++ oend[1] = op[2]; ++ oend[2] = op[3]; ++ oend[3] = args->oend; ++ ++ assert(MEM_isLittleEndian()); ++ assert(!MEM_32bits()); ++ ++ for (;;) { ++ BYTE* olimit; ++ int stream; ++ int symbol; ++ ++ /* Assert loop preconditions */ ++#ifndef NDEBUG ++ for (stream = 0; stream < 4; ++stream) { ++ assert(op[stream] <= oend[stream]); ++ assert(ip[stream] >= ilimit); ++ } ++#endif ++ /* Compute olimit */ ++ { ++ /* Each loop does 5 table lookups for each of the 4 streams. ++ * Each table lookup consumes up to 11 bits of input, and produces ++ * up to 2 bytes of output. ++ */ ++ /* We can consume up to 7 bytes of input per iteration per stream. ++ * We also know that each input pointer is >= ip[0]. So we can run ++ * iters loops before running out of input. ++ */ ++ size_t iters = (size_t)(ip[0] - ilimit) / 7; ++ /* Each iteration can produce up to 10 bytes of output per stream. ++ * Each output stream my advance at different rates. So take the ++ * minimum number of safe iterations among all the output streams. ++ */ ++ for (stream = 0; stream < 4; ++stream) { ++ size_t const oiters = (size_t)(oend[stream] - op[stream]) / 10; ++ iters = MIN(iters, oiters); ++ } ++ ++ /* Each iteration produces at least 5 output symbols. So until ++ * op[3] crosses olimit, we know we haven't executed iters ++ * iterations yet. This saves us maintaining an iters counter, ++ * at the expense of computing the remaining # of iterations ++ * more frequently. ++ */ ++ olimit = op[3] + (iters * 5); ++ ++ /* Exit the fast decoding loop if we are too close to the end. */ ++ if (op[3] + 10 > olimit) ++ break; ++ ++ /* Exit the decoding loop if any input pointer has crossed the ++ * previous one. This indicates corruption, and a precondition ++ * to our loop is that ip[i] >= ip[0]. ++ */ ++ for (stream = 1; stream < 4; ++stream) { ++ if (ip[stream] < ip[stream - 1]) ++ goto _out; ++ } ++ } ++ ++#ifndef NDEBUG ++ for (stream = 1; stream < 4; ++stream) { ++ assert(ip[stream] >= ip[stream - 1]); ++ } ++#endif ++ ++ 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; ++ } ++ } ++ } while (op[3] < olimit); ++ } ++ ++_out: ++ ++ /* Save the final values of each of the state variables back to args. */ ++ ZSTD_memcpy(&args->bits, &bits, sizeof(bits)); ++ ZSTD_memcpy((void*)(&args->ip), &ip, sizeof(ip)); ++ ZSTD_memcpy(&args->op, &op, sizeof(op)); ++} ++ ++ ++static HUF_FAST_BMI2_ATTRS size_t ++HUF_decompress4X2_usingDTable_internal_fast( + void* dst, size_t dstSize, + const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) { ++ const HUF_DTable* DTable, ++ HUF_DecompressFastLoopFn loopFn) { + void const* dt = DTable + 1; + const BYTE* const iend = (const BYTE*)cSrc + 6; + BYTE* const oend = (BYTE*)dst + dstSize; +- HUF_DecompressAsmArgs args; ++ HUF_DecompressFastArgs args; + { +- size_t const ret = HUF_DecompressAsmArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); ++ size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable); + FORWARD_IF_ERROR(ret, "Failed to init asm args"); +- if (ret != 0) +- return HUF_decompress4X2_usingDTable_internal_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); ++ if (ret == 0) ++ return 0; + } + + assert(args.ip[0] >= args.ilimit); +- HUF_decompress4X2_usingDTable_internal_bmi2_asm_loop(&args); ++ loopFn(&args); + + /* note : op4 already verified within main loop */ + assert(args.ip[0] >= iend); +@@ -1426,91 +1650,72 @@ + /* decoded size */ + return dstSize; + } +-#endif /* ZSTD_ENABLE_ASM_X86_64_BMI2 */ + + static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, void const* cSrc, +- size_t cSrcSize, HUF_DTable const* DTable, int bmi2) ++ size_t cSrcSize, HUF_DTable const* DTable, int flags) + { ++ HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X2_usingDTable_internal_default; ++ HUF_DecompressFastLoopFn loopFn = HUF_decompress4X2_usingDTable_internal_fast_c_loop; ++ + #if DYNAMIC_BMI2 +- if (bmi2) { ++ if (flags & HUF_flags_bmi2) { ++ fallbackFn = HUF_decompress4X2_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 +- return HUF_decompress4X2_usingDTable_internal_bmi2_asm(dst, dstSize, cSrc, cSrcSize, DTable); +-# else +- return HUF_decompress4X2_usingDTable_internal_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); ++ if (!(flags & HUF_flags_disableAsm)) { ++ loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; ++ } + # endif ++ } else { ++ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#else +- (void)bmi2; + #endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) +- return HUF_decompress4X2_usingDTable_internal_bmi2_asm(dst, dstSize, cSrc, cSrcSize, DTable); +-#else +- return HUF_decompress4X2_usingDTable_internal_default(dst, dstSize, cSrc, cSrcSize, DTable); ++ if (!(flags & HUF_flags_disableAsm)) { ++ loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; ++ } + #endif ++ ++ if (!(flags & HUF_flags_disableFast)) { ++ size_t const ret = HUF_decompress4X2_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); ++ if (ret != 0) ++ return ret; ++ } ++ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } + + HUF_DGEN(HUF_decompress1X2_usingDTable_internal) + +-size_t HUF_decompress1X2_usingDTable( +- void* dst, size_t dstSize, +- const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) +-{ +- DTableDesc dtd = HUF_getDTableDesc(DTable); +- if (dtd.tableType != 1) return ERROR(GENERIC); +- return HUF_decompress1X2_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); +-} +- + size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* DCtx, void* dst, size_t dstSize, + const void* cSrc, size_t cSrcSize, +- void* workSpace, size_t wkspSize) ++ void* workSpace, size_t wkspSize, int flags) + { + const BYTE* ip = (const BYTE*) cSrc; + + size_t const hSize = HUF_readDTableX2_wksp(DCtx, cSrc, cSrcSize, +- workSpace, wkspSize); ++ workSpace, wkspSize, flags); + if (HUF_isError(hSize)) return hSize; + if (hSize >= cSrcSize) return ERROR(srcSize_wrong); + ip += hSize; cSrcSize -= hSize; + +- return HUF_decompress1X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, /* bmi2 */ 0); +-} +- +- +-size_t HUF_decompress4X2_usingDTable( +- void* dst, size_t dstSize, +- const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) +-{ +- 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, +- void* workSpace, size_t wkspSize, int bmi2) ++ void* workSpace, size_t wkspSize, int flags) + { + const BYTE* ip = (const BYTE*) cSrc; + + size_t hSize = HUF_readDTableX2_wksp(dctx, cSrc, cSrcSize, +- workSpace, wkspSize); ++ workSpace, wkspSize, flags); + if (HUF_isError(hSize)) return hSize; + if (hSize >= cSrcSize) return ERROR(srcSize_wrong); + ip += hSize; cSrcSize -= hSize; + +- return HUF_decompress4X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, bmi2); +-} +- +-size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, +- const void* cSrc, size_t cSrcSize, +- void* workSpace, size_t wkspSize) +-{ +- return HUF_decompress4X2_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, /* bmi2 */ 0); ++ return HUF_decompress4X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags); + } + +- + #endif /* HUF_FORCE_DECOMPRESS_X1 */ + + +@@ -1518,44 +1723,6 @@ + /* Universal decompression selectors */ + /* ***********************************/ + +-size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, +- const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) +-{ +- DTableDesc const dtd = HUF_getDTableDesc(DTable); +-#if defined(HUF_FORCE_DECOMPRESS_X1) +- (void)dtd; +- assert(dtd.tableType == 0); +- return HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); +-#elif defined(HUF_FORCE_DECOMPRESS_X2) +- (void)dtd; +- assert(dtd.tableType == 1); +- return HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); +-#else +- return dtd.tableType ? HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0) : +- HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); +-#endif +-} +- +-size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, +- const void* cSrc, size_t cSrcSize, +- const HUF_DTable* DTable) +-{ +- DTableDesc const dtd = HUF_getDTableDesc(DTable); +-#if defined(HUF_FORCE_DECOMPRESS_X1) +- (void)dtd; +- assert(dtd.tableType == 0); +- return HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); +-#elif defined(HUF_FORCE_DECOMPRESS_X2) +- (void)dtd; +- assert(dtd.tableType == 1); +- return HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); +-#else +- return dtd.tableType ? HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0) : +- HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); +-#endif +-} +- + + #if !defined(HUF_FORCE_DECOMPRESS_X1) && !defined(HUF_FORCE_DECOMPRESS_X2) + typedef struct { U32 tableTime; U32 decode256Time; } algo_time_t; +@@ -1610,36 +1777,9 @@ + #endif + } + +- +-size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, +- size_t dstSize, const void* cSrc, +- size_t cSrcSize, void* workSpace, +- size_t wkspSize) +-{ +- /* validation checks */ +- if (dstSize == 0) return ERROR(dstSize_tooSmall); +- if (cSrcSize == 0) return ERROR(corruption_detected); +- +- { U32 const algoNb = HUF_selectDecoder(dstSize, cSrcSize); +-#if defined(HUF_FORCE_DECOMPRESS_X1) +- (void)algoNb; +- assert(algoNb == 0); +- return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize); +-#elif defined(HUF_FORCE_DECOMPRESS_X2) +- (void)algoNb; +- assert(algoNb == 1); +- return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize); +-#else +- return algoNb ? HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, +- cSrcSize, workSpace, wkspSize): +- HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize); +-#endif +- } +-} +- + size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, + const void* cSrc, size_t cSrcSize, +- void* workSpace, size_t wkspSize) ++ void* workSpace, size_t wkspSize, int flags) + { + /* validation checks */ + if (dstSize == 0) return ERROR(dstSize_tooSmall); +@@ -1652,71 +1792,71 @@ + (void)algoNb; + assert(algoNb == 0); + return HUF_decompress1X1_DCtx_wksp(dctx, dst, dstSize, cSrc, +- cSrcSize, workSpace, wkspSize); ++ cSrcSize, workSpace, wkspSize, flags); + #elif defined(HUF_FORCE_DECOMPRESS_X2) + (void)algoNb; + assert(algoNb == 1); + return HUF_decompress1X2_DCtx_wksp(dctx, dst, dstSize, cSrc, +- cSrcSize, workSpace, wkspSize); ++ cSrcSize, workSpace, wkspSize, flags); + #else + return algoNb ? HUF_decompress1X2_DCtx_wksp(dctx, dst, dstSize, cSrc, +- cSrcSize, workSpace, wkspSize): ++ cSrcSize, workSpace, wkspSize, flags): + HUF_decompress1X1_DCtx_wksp(dctx, dst, dstSize, cSrc, +- cSrcSize, workSpace, wkspSize); ++ cSrcSize, workSpace, wkspSize, flags); + #endif + } + } + + +-size_t HUF_decompress1X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2) ++size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags) + { + DTableDesc const dtd = HUF_getDTableDesc(DTable); + #if defined(HUF_FORCE_DECOMPRESS_X1) + (void)dtd; + assert(dtd.tableType == 0); +- return HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); ++ return HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); + #elif defined(HUF_FORCE_DECOMPRESS_X2) + (void)dtd; + assert(dtd.tableType == 1); +- return HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); ++ return HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); + #else +- return dtd.tableType ? HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2) : +- HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); ++ return dtd.tableType ? HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags) : ++ HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); + #endif + } + + #ifndef HUF_FORCE_DECOMPRESS_X2 +-size_t HUF_decompress1X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2) ++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, int flags) + { + const BYTE* ip = (const BYTE*) cSrc; + +- size_t const hSize = HUF_readDTableX1_wksp_bmi2(dctx, cSrc, cSrcSize, workSpace, wkspSize, bmi2); ++ size_t const hSize = HUF_readDTableX1_wksp(dctx, cSrc, cSrcSize, workSpace, wkspSize, flags); + if (HUF_isError(hSize)) return hSize; + if (hSize >= cSrcSize) return ERROR(srcSize_wrong); + ip += hSize; cSrcSize -= hSize; + +- return HUF_decompress1X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, bmi2); ++ return HUF_decompress1X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags); + } + #endif + +-size_t HUF_decompress4X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2) ++size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags) + { + DTableDesc const dtd = HUF_getDTableDesc(DTable); + #if defined(HUF_FORCE_DECOMPRESS_X1) + (void)dtd; + assert(dtd.tableType == 0); +- return HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); ++ return HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); + #elif defined(HUF_FORCE_DECOMPRESS_X2) + (void)dtd; + assert(dtd.tableType == 1); +- return HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); ++ return HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); + #else +- return dtd.tableType ? HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2) : +- HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, bmi2); ++ return dtd.tableType ? HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags) : ++ HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags); + #endif + } + +-size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2) ++size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags) + { + /* validation checks */ + if (dstSize == 0) return ERROR(dstSize_tooSmall); +@@ -1726,15 +1866,14 @@ + #if defined(HUF_FORCE_DECOMPRESS_X1) + (void)algoNb; + assert(algoNb == 0); +- return HUF_decompress4X1_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2); ++ return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags); + #elif defined(HUF_FORCE_DECOMPRESS_X2) + (void)algoNb; + assert(algoNb == 1); +- return HUF_decompress4X2_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2); ++ return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags); + #else +- return algoNb ? HUF_decompress4X2_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2) : +- HUF_decompress4X1_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, bmi2); ++ return algoNb ? HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags) : ++ HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags); + #endif + } + } +- +diff '--color=auto' -uraN a/lib/zstd/decompress/zstd_ddict.c b/lib/zstd/decompress/zstd_ddict.c +--- a/lib/zstd/decompress/zstd_ddict.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress/zstd_ddict.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -14,12 +15,12 @@ + /*-******************************************************* + * Dependencies + *********************************************************/ ++#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customFree */ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ + #include "../common/cpu.h" /* bmi2 */ + #include "../common/mem.h" /* low level memory routines */ + #define FSE_STATIC_LINKING_ONLY + #include "../common/fse.h" +-#define HUF_STATIC_LINKING_ONLY + #include "../common/huf.h" + #include "zstd_decompress_internal.h" + #include "zstd_ddict.h" +@@ -131,7 +132,7 @@ + ZSTD_memcpy(internalBuffer, dict, dictSize); + } + ddict->dictSize = dictSize; +- ddict->entropy.hufTable[0] = (HUF_DTable)((HufLog)*0x1000001); /* cover both little and big endian */ ++ ddict->entropy.hufTable[0] = (HUF_DTable)((ZSTD_HUFFDTABLE_CAPACITY_LOG)*0x1000001); /* cover both little and big endian */ + + /* parse dictionary content */ + FORWARD_IF_ERROR( ZSTD_loadEntropy_intoDDict(ddict, dictContentType) , ""); +@@ -237,5 +238,5 @@ + unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ddict) + { + if (ddict==NULL) return 0; +- return ZSTD_getDictID_fromDict(ddict->dictContent, ddict->dictSize); ++ return ddict->dictID; + } +diff '--color=auto' -uraN a/lib/zstd/decompress/zstd_ddict.h b/lib/zstd/decompress/zstd_ddict.h +--- a/lib/zstd/decompress/zstd_ddict.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress/zstd_ddict.h 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/decompress/zstd_decompress.c b/lib/zstd/decompress/zstd_decompress.c +--- a/lib/zstd/decompress/zstd_decompress.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress/zstd_decompress.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -52,17 +53,18 @@ + /*-******************************************************* + * Dependencies + *********************************************************/ ++#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ + #include "../common/mem.h" /* low level memory routines */ + #define FSE_STATIC_LINKING_ONLY + #include "../common/fse.h" +-#define HUF_STATIC_LINKING_ONLY + #include "../common/huf.h" + #include /* xxh64_reset, xxh64_update, xxh64_digest, XXH64 */ + #include "../common/zstd_internal.h" /* blockProperties_t */ + #include "zstd_decompress_internal.h" /* ZSTD_DCtx */ + #include "zstd_ddict.h" /* ZSTD_DDictDictContent */ + #include "zstd_decompress_block.h" /* ZSTD_decompressBlock_internal */ ++#include "../common/bits.h" /* ZSTD_highbit32 */ + + + +@@ -72,11 +74,11 @@ + *************************************/ + + #define DDICT_HASHSET_MAX_LOAD_FACTOR_COUNT_MULT 4 +-#define DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT 3 /* These two constants represent SIZE_MULT/COUNT_MULT load factor without using a float. +- * Currently, that means a 0.75 load factor. +- * So, if count * COUNT_MULT / size * SIZE_MULT != 0, then we've exceeded +- * the load factor of the ddict hash set. +- */ ++#define DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT 3 /* These two constants represent SIZE_MULT/COUNT_MULT load factor without using a float. ++ * Currently, that means a 0.75 load factor. ++ * So, if count * COUNT_MULT / size * SIZE_MULT != 0, then we've exceeded ++ * the load factor of the ddict hash set. ++ */ + + #define DDICT_HASHSET_TABLE_BASE_SIZE 64 + #define DDICT_HASHSET_RESIZE_FACTOR 2 +@@ -237,6 +239,7 @@ + dctx->outBufferMode = ZSTD_bm_buffered; + dctx->forceIgnoreChecksum = ZSTD_d_validateChecksum; + dctx->refMultipleDDicts = ZSTD_rmd_refSingleDDict; ++ dctx->disableHufAsm = 0; + } + + static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) +@@ -421,16 +424,40 @@ + * note : only works for formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless + * @return : 0, `zfhPtr` is correctly filled, + * >0, `srcSize` is too small, value is wanted `srcSize` amount, +- * or an error code, which can be tested using ZSTD_isError() */ ++** or an error code, which can be tested using ZSTD_isError() */ + size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format) + { + const BYTE* ip = (const BYTE*)src; + size_t const minInputSize = ZSTD_startingInputLength(format); + +- ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr)); /* not strictly necessary, but static analyzer do not understand that zfhPtr is only going to be read only if return value is zero, since they are 2 different signals */ +- if (srcSize < minInputSize) return minInputSize; +- RETURN_ERROR_IF(src==NULL, GENERIC, "invalid parameter"); ++ DEBUGLOG(5, "ZSTD_getFrameHeader_advanced: minInputSize = %zu, srcSize = %zu", minInputSize, srcSize); ++ ++ if (srcSize > 0) { ++ /* note : technically could be considered an assert(), since it's an invalid entry */ ++ RETURN_ERROR_IF(src==NULL, GENERIC, "invalid parameter : src==NULL, but srcSize>0"); ++ } ++ if (srcSize < minInputSize) { ++ if (srcSize > 0 && format != ZSTD_f_zstd1_magicless) { ++ /* when receiving less than @minInputSize bytes, ++ * control these bytes at least correspond to a supported magic number ++ * in order to error out early if they don't. ++ **/ ++ size_t const toCopy = MIN(4, srcSize); ++ unsigned char hbuf[4]; MEM_writeLE32(hbuf, ZSTD_MAGICNUMBER); ++ assert(src != NULL); ++ ZSTD_memcpy(hbuf, src, toCopy); ++ if ( MEM_readLE32(hbuf) != ZSTD_MAGICNUMBER ) { ++ /* not a zstd frame : let's check if it's a skippable frame */ ++ MEM_writeLE32(hbuf, ZSTD_MAGIC_SKIPPABLE_START); ++ ZSTD_memcpy(hbuf, src, toCopy); ++ if ((MEM_readLE32(hbuf) & ZSTD_MAGIC_SKIPPABLE_MASK) != ZSTD_MAGIC_SKIPPABLE_START) { ++ RETURN_ERROR(prefix_unknown, ++ "first bytes don't correspond to any supported magic number"); ++ } } } ++ return minInputSize; ++ } + ++ ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr)); /* not strictly necessary, but static analyzers may not understand that zfhPtr will be read only if return value is zero, since they are 2 different signals */ + if ( (format != ZSTD_f_zstd1_magicless) + && (MEM_readLE32(src) != ZSTD_MAGICNUMBER) ) { + if ((MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { +@@ -540,49 +567,52 @@ + sizeU32 = MEM_readLE32((BYTE const*)src + ZSTD_FRAMEIDSIZE); + RETURN_ERROR_IF((U32)(sizeU32 + ZSTD_SKIPPABLEHEADERSIZE) < sizeU32, + frameParameter_unsupported, ""); +- { +- size_t const skippableSize = skippableHeaderSize + sizeU32; ++ { size_t const skippableSize = skippableHeaderSize + sizeU32; + RETURN_ERROR_IF(skippableSize > srcSize, srcSize_wrong, ""); + return skippableSize; + } + } + + /*! ZSTD_readSkippableFrame() : +- * Retrieves a zstd skippable frame containing data given by src, and writes it to dst buffer. ++ * Retrieves content of a skippable frame, and writes it to dst buffer. + * + * The parameter magicVariant will receive the magicVariant that was supplied when the frame was written, + * i.e. magicNumber - ZSTD_MAGIC_SKIPPABLE_START. This can be NULL if the caller is not interested + * in the magicVariant. + * +- * Returns an error if destination buffer is not large enough, or if the frame is not skippable. ++ * Returns an error if destination buffer is not large enough, or if this is not a valid skippable frame. + * + * @return : number of bytes written or a ZSTD error. + */ +-ZSTDLIB_API size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity, unsigned* magicVariant, +- const void* src, size_t srcSize) ++size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity, ++ unsigned* magicVariant, /* optional, can be NULL */ ++ const void* src, size_t srcSize) + { +- U32 const magicNumber = MEM_readLE32(src); +- size_t skippableFrameSize = readSkippableFrameSize(src, srcSize); +- size_t skippableContentSize = skippableFrameSize - ZSTD_SKIPPABLEHEADERSIZE; +- +- /* check input validity */ +- RETURN_ERROR_IF(!ZSTD_isSkippableFrame(src, srcSize), frameParameter_unsupported, ""); +- RETURN_ERROR_IF(skippableFrameSize < ZSTD_SKIPPABLEHEADERSIZE || skippableFrameSize > srcSize, srcSize_wrong, ""); +- RETURN_ERROR_IF(skippableContentSize > dstCapacity, dstSize_tooSmall, ""); +- +- /* deliver payload */ +- if (skippableContentSize > 0 && dst != NULL) +- ZSTD_memcpy(dst, (const BYTE *)src + ZSTD_SKIPPABLEHEADERSIZE, skippableContentSize); +- if (magicVariant != NULL) +- *magicVariant = magicNumber - ZSTD_MAGIC_SKIPPABLE_START; +- return skippableContentSize; ++ RETURN_ERROR_IF(srcSize < ZSTD_SKIPPABLEHEADERSIZE, srcSize_wrong, ""); ++ ++ { U32 const magicNumber = MEM_readLE32(src); ++ size_t skippableFrameSize = readSkippableFrameSize(src, srcSize); ++ size_t skippableContentSize = skippableFrameSize - ZSTD_SKIPPABLEHEADERSIZE; ++ ++ /* check input validity */ ++ RETURN_ERROR_IF(!ZSTD_isSkippableFrame(src, srcSize), frameParameter_unsupported, ""); ++ RETURN_ERROR_IF(skippableFrameSize < ZSTD_SKIPPABLEHEADERSIZE || skippableFrameSize > srcSize, srcSize_wrong, ""); ++ RETURN_ERROR_IF(skippableContentSize > dstCapacity, dstSize_tooSmall, ""); ++ ++ /* deliver payload */ ++ if (skippableContentSize > 0 && dst != NULL) ++ ZSTD_memcpy(dst, (const BYTE *)src + ZSTD_SKIPPABLEHEADERSIZE, skippableContentSize); ++ if (magicVariant != NULL) ++ *magicVariant = magicNumber - ZSTD_MAGIC_SKIPPABLE_START; ++ return skippableContentSize; ++ } + } + + /* ZSTD_findDecompressedSize() : +- * compatible with legacy mode + * `srcSize` must be the exact length of some number of ZSTD compressed and/or + * skippable frames +- * @return : decompressed size of the frames contained */ ++ * note: compatible with legacy mode ++ * @return : decompressed size of the frames contained */ + unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize) + { + unsigned long long totalDstSize = 0; +@@ -592,9 +622,7 @@ + + if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { + size_t const skippableSize = readSkippableFrameSize(src, srcSize); +- if (ZSTD_isError(skippableSize)) { +- return ZSTD_CONTENTSIZE_ERROR; +- } ++ if (ZSTD_isError(skippableSize)) return ZSTD_CONTENTSIZE_ERROR; + assert(skippableSize <= srcSize); + + src = (const BYTE *)src + skippableSize; +@@ -602,17 +630,17 @@ + continue; + } + +- { unsigned long long const ret = ZSTD_getFrameContentSize(src, srcSize); +- if (ret >= ZSTD_CONTENTSIZE_ERROR) return ret; ++ { unsigned long long const fcs = ZSTD_getFrameContentSize(src, srcSize); ++ if (fcs >= ZSTD_CONTENTSIZE_ERROR) return fcs; + +- /* check for overflow */ +- if (totalDstSize + ret < totalDstSize) return ZSTD_CONTENTSIZE_ERROR; +- totalDstSize += ret; ++ if (totalDstSize + fcs < totalDstSize) ++ return ZSTD_CONTENTSIZE_ERROR; /* check for overflow */ ++ totalDstSize += fcs; + } ++ /* skip to next frame */ + { size_t const frameSrcSize = ZSTD_findFrameCompressedSize(src, srcSize); +- if (ZSTD_isError(frameSrcSize)) { +- return ZSTD_CONTENTSIZE_ERROR; +- } ++ if (ZSTD_isError(frameSrcSize)) return ZSTD_CONTENTSIZE_ERROR; ++ assert(frameSrcSize <= srcSize); + + src = (const BYTE *)src + frameSrcSize; + srcSize -= frameSrcSize; +@@ -730,10 +758,11 @@ + ip += 4; + } + ++ frameSizeInfo.nbBlocks = nbBlocks; + frameSizeInfo.compressedSize = (size_t)(ip - ipstart); + frameSizeInfo.decompressedBound = (zfh.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN) + ? zfh.frameContentSize +- : nbBlocks * zfh.blockSizeMax; ++ : (unsigned long long)nbBlocks * zfh.blockSizeMax; + return frameSizeInfo; + } + } +@@ -773,6 +802,48 @@ + return bound; + } + ++size_t ZSTD_decompressionMargin(void const* src, size_t srcSize) ++{ ++ size_t margin = 0; ++ unsigned maxBlockSize = 0; ++ ++ /* Iterate over each frame */ ++ while (srcSize > 0) { ++ ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize); ++ size_t const compressedSize = frameSizeInfo.compressedSize; ++ unsigned long long const decompressedBound = frameSizeInfo.decompressedBound; ++ ZSTD_frameHeader zfh; ++ ++ FORWARD_IF_ERROR(ZSTD_getFrameHeader(&zfh, src, srcSize), ""); ++ if (ZSTD_isError(compressedSize) || decompressedBound == ZSTD_CONTENTSIZE_ERROR) ++ return ERROR(corruption_detected); ++ ++ if (zfh.frameType == ZSTD_frame) { ++ /* Add the frame header to our margin */ ++ margin += zfh.headerSize; ++ /* Add the checksum to our margin */ ++ margin += zfh.checksumFlag ? 4 : 0; ++ /* Add 3 bytes per block */ ++ margin += 3 * frameSizeInfo.nbBlocks; ++ ++ /* Compute the max block size */ ++ maxBlockSize = MAX(maxBlockSize, zfh.blockSizeMax); ++ } else { ++ assert(zfh.frameType == ZSTD_skippableFrame); ++ /* Add the entire skippable frame size to our margin. */ ++ margin += compressedSize; ++ } ++ ++ assert(srcSize >= compressedSize); ++ src = (const BYTE*)src + compressedSize; ++ srcSize -= compressedSize; ++ } ++ ++ /* Add the max block size back to the margin. */ ++ margin += maxBlockSize; ++ ++ return margin; ++} + + /*-************************************************************* + * Frame decoding +@@ -930,6 +1001,7 @@ + } + ZSTD_DCtx_trace_end(dctx, (U64)(op-ostart), (U64)(ip-istart), /* streaming */ 0); + /* Allow caller to get size read */ ++ DEBUGLOG(4, "ZSTD_decompressFrame: decompressed frame of size %zi, consuming %zi bytes of input", op-ostart, ip - (const BYTE*)*srcPtr); + *srcPtr = ip; + *srcSizePtr = remainingSrcSize; + return (size_t)(op-ostart); +@@ -955,17 +1027,18 @@ + while (srcSize >= ZSTD_startingInputLength(dctx->format)) { + + +- { U32 const magicNumber = MEM_readLE32(src); +- DEBUGLOG(4, "reading magic number %08X (expecting %08X)", +- (unsigned)magicNumber, ZSTD_MAGICNUMBER); ++ if (srcSize >= 4) { ++ U32 const magicNumber = MEM_readLE32(src); ++ DEBUGLOG(5, "reading magic number %08X", (unsigned)magicNumber); + if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { ++ /* skippable frame detected : skip it */ + size_t const skippableSize = readSkippableFrameSize(src, srcSize); +- FORWARD_IF_ERROR(skippableSize, "readSkippableFrameSize failed"); ++ FORWARD_IF_ERROR(skippableSize, "invalid skippable frame"); + assert(skippableSize <= srcSize); + + src = (const BYTE *)src + skippableSize; + srcSize -= skippableSize; +- continue; ++ continue; /* check next frame */ + } } + + if (ddict) { +@@ -1061,8 +1134,8 @@ + size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx) { return dctx->expected; } + + /* +- * Similar to ZSTD_nextSrcSizeToDecompress(), but when a block input can be streamed, +- * we allow taking a partial block as the input. Currently only raw uncompressed blocks can ++ * Similar to ZSTD_nextSrcSizeToDecompress(), but when a block input can be streamed, we ++ * allow taking a partial block as the input. Currently only raw uncompressed blocks can + * be streamed. + * + * For blocks that can be streamed, this allows us to reduce the latency until we produce +@@ -1262,7 +1335,7 @@ + + default: + assert(0); /* impossible */ +- RETURN_ERROR(GENERIC, "impossible to reach"); /* some compiler require default to do something */ ++ RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */ + } + } + +@@ -1303,11 +1376,11 @@ + /* in minimal huffman, we always use X1 variants */ + size_t const hSize = HUF_readDTableX1_wksp(entropy->hufTable, + dictPtr, dictEnd - dictPtr, +- workspace, workspaceSize); ++ workspace, workspaceSize, /* flags */ 0); + #else + size_t const hSize = HUF_readDTableX2_wksp(entropy->hufTable, + dictPtr, (size_t)(dictEnd - dictPtr), +- workspace, workspaceSize); ++ workspace, workspaceSize, /* flags */ 0); + #endif + RETURN_ERROR_IF(HUF_isError(hSize), dictionary_corrupted, ""); + dictPtr += hSize; +@@ -1403,7 +1476,7 @@ + dctx->prefixStart = NULL; + dctx->virtualStart = NULL; + dctx->dictEnd = NULL; +- dctx->entropy.hufTable[0] = (HUF_DTable)((HufLog)*0x1000001); /* cover both little and big endian */ ++ dctx->entropy.hufTable[0] = (HUF_DTable)((ZSTD_HUFFDTABLE_CAPACITY_LOG)*0x1000001); /* cover both little and big endian */ + dctx->litEntropy = dctx->fseEntropy = 0; + dctx->dictID = 0; + dctx->bType = bt_reserved; +@@ -1465,7 +1538,7 @@ + * This could for one of the following reasons : + * - The frame does not require a dictionary (most common case). + * - The frame was built with dictID intentionally removed. +- * Needed dictionary is a hidden information. ++ * Needed dictionary is a hidden piece of information. + * Note : this use case also happens when using a non-conformant dictionary. + * - `srcSize` is too small, and as a result, frame header could not be decoded. + * Note : possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`. +@@ -1474,7 +1547,7 @@ + * ZSTD_getFrameHeader(), which will provide a more precise error code. */ + unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize) + { +- ZSTD_frameHeader zfp = { 0, 0, 0, ZSTD_frame, 0, 0, 0 }; ++ ZSTD_frameHeader zfp = { 0, 0, 0, ZSTD_frame, 0, 0, 0, 0, 0 }; + size_t const hError = ZSTD_getFrameHeader(&zfp, src, srcSize); + if (ZSTD_isError(hError)) return 0; + return zfp.dictID; +@@ -1581,7 +1654,9 @@ + size_t ZSTD_initDStream(ZSTD_DStream* zds) + { + DEBUGLOG(4, "ZSTD_initDStream"); +- return ZSTD_initDStream_usingDDict(zds, NULL); ++ FORWARD_IF_ERROR(ZSTD_DCtx_reset(zds, ZSTD_reset_session_only), ""); ++ FORWARD_IF_ERROR(ZSTD_DCtx_refDDict(zds, NULL), ""); ++ return ZSTD_startingInputLength(zds->format); + } + + /* ZSTD_initDStream_usingDDict() : +@@ -1589,6 +1664,7 @@ + * this function cannot fail */ + size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* dctx, const ZSTD_DDict* ddict) + { ++ DEBUGLOG(4, "ZSTD_initDStream_usingDDict"); + FORWARD_IF_ERROR( ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only) , ""); + FORWARD_IF_ERROR( ZSTD_DCtx_refDDict(dctx, ddict) , ""); + return ZSTD_startingInputLength(dctx->format); +@@ -1599,6 +1675,7 @@ + * this function cannot fail */ + size_t ZSTD_resetDStream(ZSTD_DStream* dctx) + { ++ DEBUGLOG(4, "ZSTD_resetDStream"); + FORWARD_IF_ERROR(ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only), ""); + return ZSTD_startingInputLength(dctx->format); + } +@@ -1670,6 +1747,11 @@ + bounds.lowerBound = (int)ZSTD_rmd_refSingleDDict; + bounds.upperBound = (int)ZSTD_rmd_refMultipleDDicts; + return bounds; ++ case ZSTD_d_disableHuffmanAssembly: ++ bounds.lowerBound = 0; ++ bounds.upperBound = 1; ++ return bounds; ++ + default:; + } + bounds.error = ERROR(parameter_unsupported); +@@ -1710,6 +1792,9 @@ + case ZSTD_d_refMultipleDDicts: + *value = (int)dctx->refMultipleDDicts; + return 0; ++ case ZSTD_d_disableHuffmanAssembly: ++ *value = (int)dctx->disableHufAsm; ++ return 0; + default:; + } + RETURN_ERROR(parameter_unsupported, ""); +@@ -1743,6 +1828,10 @@ + } + dctx->refMultipleDDicts = (ZSTD_refMultipleDDicts_e)value; + return 0; ++ case ZSTD_d_disableHuffmanAssembly: ++ CHECK_DBOUNDS(ZSTD_d_disableHuffmanAssembly, value); ++ dctx->disableHufAsm = value != 0; ++ return 0; + default:; + } + RETURN_ERROR(parameter_unsupported, ""); +@@ -1918,7 +2007,6 @@ + if (zds->refMultipleDDicts && zds->ddictSet) { + ZSTD_DCtx_selectFrameDDict(zds); + } +- DEBUGLOG(5, "header size : %u", (U32)hSize); + if (ZSTD_isError(hSize)) { + return hSize; /* error */ + } +@@ -1932,6 +2020,11 @@ + zds->lhSize += remainingInput; + } + input->pos = input->size; ++ /* check first few bytes */ ++ FORWARD_IF_ERROR( ++ ZSTD_getFrameHeader_advanced(&zds->fParams, zds->headerBuffer, zds->lhSize, zds->format), ++ "First few bytes detected incorrect" ); ++ /* return hint input size */ + return (MAX((size_t)ZSTD_FRAMEHEADERSIZE_MIN(zds->format), hSize) - zds->lhSize) + ZSTD_blockHeaderSize; /* remaining header bytes + next block header */ + } + assert(ip != NULL); +@@ -1949,8 +2042,9 @@ + size_t const decompressedSize = ZSTD_decompress_usingDDict(zds, op, (size_t)(oend-op), istart, cSize, ZSTD_getDDict(zds)); + if (ZSTD_isError(decompressedSize)) return decompressedSize; + DEBUGLOG(4, "shortcut to single-pass ZSTD_decompress_usingDDict()") ++ assert(istart != NULL); + ip = istart + cSize; +- op += decompressedSize; ++ op = op ? op + decompressedSize : op; /* can occur if frameContentSize = 0 (empty frame) */ + zds->expected = 0; + zds->streamStage = zdss_init; + someMoreWork = 0; +@@ -2034,6 +2128,7 @@ + } + if ((size_t)(iend-ip) >= neededInSize) { /* decode directly from src */ + FORWARD_IF_ERROR(ZSTD_decompressContinueStream(zds, &op, oend, ip, neededInSize), ""); ++ assert(ip != NULL); + ip += neededInSize; + /* Function modifies the stage so we must break */ + break; +@@ -2048,7 +2143,7 @@ + int const isSkipFrame = ZSTD_isSkipFrame(zds); + size_t loadedSize; + /* At this point we shouldn't be decompressing a block that we can stream. */ +- assert(neededInSize == ZSTD_nextSrcSizeToDecompressWithInputSize(zds, iend - ip)); ++ assert(neededInSize == ZSTD_nextSrcSizeToDecompressWithInputSize(zds, (size_t)(iend - ip))); + if (isSkipFrame) { + loadedSize = MIN(toLoad, (size_t)(iend-ip)); + } else { +@@ -2057,8 +2152,11 @@ + "should never happen"); + loadedSize = ZSTD_limitCopy(zds->inBuff + zds->inPos, toLoad, ip, (size_t)(iend-ip)); + } +- ip += loadedSize; +- zds->inPos += loadedSize; ++ if (loadedSize != 0) { ++ /* ip may be NULL */ ++ ip += loadedSize; ++ zds->inPos += loadedSize; ++ } + if (loadedSize < toLoad) { someMoreWork = 0; break; } /* not enough input, wait for more */ + + /* decode loaded input */ +@@ -2068,14 +2166,17 @@ + break; + } + case zdss_flush: +- { size_t const toFlushSize = zds->outEnd - zds->outStart; ++ { ++ size_t const toFlushSize = zds->outEnd - zds->outStart; + size_t const flushedSize = ZSTD_limitCopy(op, (size_t)(oend-op), zds->outBuff + zds->outStart, toFlushSize); +- op += flushedSize; ++ ++ op = op ? op + flushedSize : op; ++ + zds->outStart += flushedSize; + if (flushedSize == toFlushSize) { /* flush completed */ + zds->streamStage = zdss_read; + if ( (zds->outBuffSize < zds->fParams.frameContentSize) +- && (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) { ++ && (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) { + DEBUGLOG(5, "restart filling outBuff from beginning (left:%i, needed:%u)", + (int)(zds->outBuffSize - zds->outStart), + (U32)zds->fParams.blockSizeMax); +@@ -2089,7 +2190,7 @@ + + default: + assert(0); /* impossible */ +- RETURN_ERROR(GENERIC, "impossible to reach"); /* some compiler require default to do something */ ++ RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */ + } } + + /* result */ +@@ -2102,8 +2203,8 @@ + if ((ip==istart) && (op==ostart)) { /* no forward progress */ + zds->noForwardProgress ++; + if (zds->noForwardProgress >= ZSTD_NO_FORWARD_PROGRESS_MAX) { +- RETURN_ERROR_IF(op==oend, dstSize_tooSmall, ""); +- RETURN_ERROR_IF(ip==iend, srcSize_wrong, ""); ++ RETURN_ERROR_IF(op==oend, noForwardProgress_destFull, ""); ++ RETURN_ERROR_IF(ip==iend, noForwardProgress_inputEmpty, ""); + assert(0); + } + } else { +@@ -2140,11 +2241,17 @@ + void* dst, size_t dstCapacity, size_t* dstPos, + const void* src, size_t srcSize, size_t* srcPos) + { +- ZSTD_outBuffer output = { dst, dstCapacity, *dstPos }; +- ZSTD_inBuffer input = { src, srcSize, *srcPos }; +- /* ZSTD_compress_generic() will check validity of dstPos and srcPos */ +- size_t const cErr = ZSTD_decompressStream(dctx, &output, &input); +- *dstPos = output.pos; +- *srcPos = input.pos; +- return cErr; ++ ZSTD_outBuffer output; ++ ZSTD_inBuffer input; ++ output.dst = dst; ++ output.size = dstCapacity; ++ output.pos = *dstPos; ++ input.src = src; ++ input.size = srcSize; ++ input.pos = *srcPos; ++ { size_t const cErr = ZSTD_decompressStream(dctx, &output, &input); ++ *dstPos = output.pos; ++ *srcPos = input.pos; ++ return cErr; ++ } + } +diff '--color=auto' -uraN a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c +--- a/lib/zstd/decompress/zstd_decompress_block.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress/zstd_decompress_block.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -20,12 +21,12 @@ + #include "../common/mem.h" /* low level memory routines */ + #define FSE_STATIC_LINKING_ONLY + #include "../common/fse.h" +-#define HUF_STATIC_LINKING_ONLY + #include "../common/huf.h" + #include "../common/zstd_internal.h" + #include "zstd_decompress_internal.h" /* ZSTD_DCtx */ + #include "zstd_ddict.h" /* ZSTD_DDictDictContent */ + #include "zstd_decompress_block.h" ++#include "../common/bits.h" /* ZSTD_highbit32 */ + + /*_******************************************************* + * Macros +@@ -89,7 +90,7 @@ + dctx->litBufferEnd = dctx->litBuffer + litSize - ZSTD_LITBUFFEREXTRASIZE; + } + else { +- /* initially this will be stored entirely in dst during huffman decoding, it will partially shifted to litExtraBuffer after */ ++ /* initially this will be stored entirely in dst during huffman decoding, it will partially be shifted to litExtraBuffer after */ + dctx->litBuffer = (BYTE*)dst + expectedWriteSize - litSize; + dctx->litBufferEnd = (BYTE*)dst + expectedWriteSize; + } +@@ -134,13 +135,16 @@ + ZSTD_FALLTHROUGH; + + case set_compressed: +- RETURN_ERROR_IF(srcSize < 5, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 3; here we need up to 5 for case 3"); ++ RETURN_ERROR_IF(srcSize < 5, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need up to 5 for case 3"); + { size_t lhSize, litSize, litCSize; + U32 singleStream=0; + U32 const lhlCode = (istart[0] >> 2) & 3; + U32 const lhc = MEM_readLE32(istart); + size_t hufSuccess; + size_t expectedWriteSize = MIN(ZSTD_BLOCKSIZE_MAX, dstCapacity); ++ int const flags = 0 ++ | (ZSTD_DCtx_get_bmi2(dctx) ? HUF_flags_bmi2 : 0) ++ | (dctx->disableHufAsm ? HUF_flags_disableAsm : 0); + switch(lhlCode) + { + case 0: case 1: default: /* note : default is impossible, since lhlCode into [0..3] */ +@@ -165,6 +169,10 @@ + } + RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); + RETURN_ERROR_IF(litSize > ZSTD_BLOCKSIZE_MAX, corruption_detected, ""); ++ if (!singleStream) ++ RETURN_ERROR_IF(litSize < MIN_LITERALS_FOR_4_STREAMS, literals_headerWrong, ++ "Not enough literals (%zu) for the 4-streams mode (min %u)", ++ litSize, MIN_LITERALS_FOR_4_STREAMS); + RETURN_ERROR_IF(litCSize + lhSize > srcSize, corruption_detected, ""); + RETURN_ERROR_IF(expectedWriteSize < litSize , dstSize_tooSmall, ""); + ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 0); +@@ -176,13 +184,14 @@ + + if (litEncType==set_repeat) { + if (singleStream) { +- hufSuccess = HUF_decompress1X_usingDTable_bmi2( ++ hufSuccess = HUF_decompress1X_usingDTable( + dctx->litBuffer, litSize, istart+lhSize, litCSize, +- dctx->HUFptr, ZSTD_DCtx_get_bmi2(dctx)); ++ dctx->HUFptr, flags); + } else { +- hufSuccess = HUF_decompress4X_usingDTable_bmi2( ++ assert(litSize >= MIN_LITERALS_FOR_4_STREAMS); ++ hufSuccess = HUF_decompress4X_usingDTable( + dctx->litBuffer, litSize, istart+lhSize, litCSize, +- dctx->HUFptr, ZSTD_DCtx_get_bmi2(dctx)); ++ dctx->HUFptr, flags); + } + } else { + if (singleStream) { +@@ -190,18 +199,18 @@ + hufSuccess = HUF_decompress1X_DCtx_wksp( + dctx->entropy.hufTable, dctx->litBuffer, litSize, + istart+lhSize, litCSize, dctx->workspace, +- sizeof(dctx->workspace)); ++ sizeof(dctx->workspace), flags); + #else +- hufSuccess = HUF_decompress1X1_DCtx_wksp_bmi2( ++ hufSuccess = HUF_decompress1X1_DCtx_wksp( + dctx->entropy.hufTable, dctx->litBuffer, litSize, + istart+lhSize, litCSize, dctx->workspace, +- sizeof(dctx->workspace), ZSTD_DCtx_get_bmi2(dctx)); ++ sizeof(dctx->workspace), flags); + #endif + } else { +- hufSuccess = HUF_decompress4X_hufOnly_wksp_bmi2( ++ hufSuccess = HUF_decompress4X_hufOnly_wksp( + dctx->entropy.hufTable, dctx->litBuffer, litSize, + istart+lhSize, litCSize, dctx->workspace, +- sizeof(dctx->workspace), ZSTD_DCtx_get_bmi2(dctx)); ++ sizeof(dctx->workspace), flags); + } + } + if (dctx->litBufferLocation == ZSTD_split) +@@ -237,6 +246,7 @@ + break; + case 3: + lhSize = 3; ++ RETURN_ERROR_IF(srcSize<3, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize = 3"); + litSize = MEM_readLE24(istart) >> 4; + break; + } +@@ -279,12 +289,13 @@ + break; + case 1: + lhSize = 2; ++ RETURN_ERROR_IF(srcSize<3, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize+1 = 3"); + litSize = MEM_readLE16(istart) >> 4; + break; + case 3: + lhSize = 3; ++ RETURN_ERROR_IF(srcSize<4, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize+1 = 4"); + litSize = MEM_readLE24(istart) >> 4; +- RETURN_ERROR_IF(srcSize<4, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 3; here we need lhSize+1 = 4"); + break; + } + RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled"); +@@ -506,14 +517,15 @@ + for (i = 8; i < n; i += 8) { + MEM_write64(spread + pos + i, sv); + } +- pos += n; ++ assert(n>=0); ++ pos += (size_t)n; + } + } + /* Now we spread those positions across the table. +- * The benefit of doing it in two stages is that we avoid the the ++ * The benefit of doing it in two stages is that we avoid the + * variable size inner loop, which caused lots of branch misses. + * Now we can run through all the positions without any branch misses. +- * We unroll the loop twice, since that is what emperically worked best. ++ * We unroll the loop twice, since that is what empirically worked best. + */ + { + size_t position = 0; +@@ -540,7 +552,7 @@ + for (i=0; i highThreshold) position = (position + step) & tableMask; /* lowprob area */ ++ while (UNLIKELY(position > highThreshold)) position = (position + step) & tableMask; /* lowprob area */ + } } + assert(position == 0); /* position must reach all cells once, otherwise normalizedCounter is incorrect */ + } +@@ -551,7 +563,7 @@ + for (u=0; ustateLL.table + seqState->stateLL.state, sizeof(ZSTD_seqSymbol)); ++ ZSTD_memcpy(mlDInfo, seqState->stateML.table + seqState->stateML.state, sizeof(ZSTD_seqSymbol)); ++ ZSTD_memcpy(ofDInfo, seqState->stateOffb.table + seqState->stateOffb.state, sizeof(ZSTD_seqSymbol)); ++#else + const ZSTD_seqSymbol* const llDInfo = seqState->stateLL.table + seqState->stateLL.state; + const ZSTD_seqSymbol* const mlDInfo = seqState->stateML.table + seqState->stateML.state; + const ZSTD_seqSymbol* const ofDInfo = seqState->stateOffb.table + seqState->stateOffb.state; ++#endif + seq.matchLength = mlDInfo->baseValue; + seq.litLength = llDInfo->baseValue; + { U32 const ofBase = ofDInfo->baseValue; +@@ -1186,28 +1221,31 @@ + U32 const llnbBits = llDInfo->nbBits; + U32 const mlnbBits = mlDInfo->nbBits; + U32 const ofnbBits = ofDInfo->nbBits; ++ ++ assert(llBits <= MaxLLBits); ++ assert(mlBits <= MaxMLBits); ++ assert(ofBits <= MaxOff); + /* + * As gcc has better branch and block analyzers, sometimes it is only +- * valuable to mark likelyness for clang, it gives around 3-4% of ++ * valuable to mark likeliness for clang, it gives around 3-4% of + * performance. + */ + + /* sequence */ + { size_t offset; +- #if defined(__clang__) +- if (LIKELY(ofBits > 1)) { +- #else + if (ofBits > 1) { +- #endif + ZSTD_STATIC_ASSERT(ZSTD_lo_isLongOffset == 1); + ZSTD_STATIC_ASSERT(LONG_OFFSETS_MAX_EXTRA_BITS_32 == 5); +- assert(ofBits <= MaxOff); ++ ZSTD_STATIC_ASSERT(STREAM_ACCUMULATOR_MIN_32 > LONG_OFFSETS_MAX_EXTRA_BITS_32); ++ ZSTD_STATIC_ASSERT(STREAM_ACCUMULATOR_MIN_32 - LONG_OFFSETS_MAX_EXTRA_BITS_32 >= MaxMLBits); + if (MEM_32bits() && longOffsets && (ofBits >= STREAM_ACCUMULATOR_MIN_32)) { +- U32 const extraBits = ofBits - MIN(ofBits, 32 - seqState->DStream.bitsConsumed); ++ /* Always read extra bits, this keeps the logic simple, ++ * avoids branches, and avoids accidentally reading 0 bits. ++ */ ++ U32 const extraBits = LONG_OFFSETS_MAX_EXTRA_BITS_32; + offset = ofBase + (BIT_readBitsFast(&seqState->DStream, ofBits - extraBits) << extraBits); + BIT_reloadDStream(&seqState->DStream); +- if (extraBits) offset += BIT_readBitsFast(&seqState->DStream, extraBits); +- assert(extraBits <= LONG_OFFSETS_MAX_EXTRA_BITS_32); /* to avoid another reload */ ++ offset += BIT_readBitsFast(&seqState->DStream, extraBits); + } else { + offset = ofBase + BIT_readBitsFast(&seqState->DStream, ofBits/*>0*/); /* <= (ZSTD_WINDOWLOG_MAX-1) bits */ + if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); +@@ -1232,11 +1270,7 @@ + seq.offset = offset; + } + +- #if defined(__clang__) +- if (UNLIKELY(mlBits > 0)) +- #else + if (mlBits > 0) +- #endif + seq.matchLength += BIT_readBitsFast(&seqState->DStream, mlBits/*>0*/); + + if (MEM_32bits() && (mlBits+llBits >= STREAM_ACCUMULATOR_MIN_32-LONG_OFFSETS_MAX_EXTRA_BITS_32)) +@@ -1246,11 +1280,7 @@ + /* Ensure there are enough bits to read the rest of data in 64-bit mode. */ + ZSTD_STATIC_ASSERT(16+LLFSELog+MLFSELog+OffFSELog < STREAM_ACCUMULATOR_MIN_64); + +- #if defined(__clang__) +- if (UNLIKELY(llBits > 0)) +- #else + if (llBits > 0) +- #endif + seq.litLength += BIT_readBitsFast(&seqState->DStream, llBits/*>0*/); + + if (MEM_32bits()) +@@ -1552,7 +1582,7 @@ + const BYTE* const prefixStart = (const BYTE*)(dctx->prefixStart); + const BYTE* const vBase = (const BYTE*)(dctx->virtualStart); + const BYTE* const dictEnd = (const BYTE*)(dctx->dictEnd); +- DEBUGLOG(5, "ZSTD_decompressSequences_body"); ++ DEBUGLOG(5, "ZSTD_decompressSequences_body: nbSeq = %d", nbSeq); + (void)frame; + + /* Regen sequences */ +@@ -1945,34 +1975,79 @@ + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ + + ++/* ++ * @returns The total size of the history referenceable by zstd, including ++ * both the prefix and the extDict. At @p op any offset larger than this ++ * is invalid. ++ */ ++static size_t ZSTD_totalHistorySize(BYTE* op, BYTE const* virtualStart) ++{ ++ return (size_t)(op - virtualStart); ++} ++ ++typedef struct { ++ unsigned longOffsetShare; ++ unsigned maxNbAdditionalBits; ++} ZSTD_OffsetInfo; + +-#if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ +- !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) +-/* ZSTD_getLongOffsetsShare() : ++/* ZSTD_getOffsetInfo() : + * condition : offTable must be valid + * @return : "share" of long offsets (arbitrarily defined as > (1<<23)) +- * compared to maximum possible of (1< 22) total += 1; ++ * compared to maximum possible of (1< 22) info.longOffsetShare += 1; ++ } ++ ++ assert(tableLog <= OffFSELog); ++ info.longOffsetShare <<= (OffFSELog - tableLog); /* scale to OffFSELog */ + } + +- assert(tableLog <= OffFSELog); +- total <<= (OffFSELog - tableLog); /* scale to OffFSELog */ ++ return info; ++} + +- return total; ++/* ++ * @returns The maximum offset we can decode in one read of our bitstream, without ++ * reloading more bits in the middle of the offset bits read. Any offsets larger ++ * than this must use the long offset decoder. ++ */ ++static size_t ZSTD_maxShortOffset(void) ++{ ++ if (MEM_64bits()) { ++ /* We can decode any offset without reloading bits. ++ * This might change if the max window size grows. ++ */ ++ ZSTD_STATIC_ASSERT(ZSTD_WINDOWLOG_MAX <= 31); ++ return (size_t)-1; ++ } else { ++ /* The maximum offBase is (1 << (STREAM_ACCUMULATOR_MIN + 1)) - 1. ++ * This offBase would require STREAM_ACCUMULATOR_MIN extra bits. ++ * Then we have to subtract ZSTD_REP_NUM to get the maximum possible offset. ++ */ ++ size_t const maxOffbase = ((size_t)1 << (STREAM_ACCUMULATOR_MIN + 1)) - 1; ++ size_t const maxOffset = maxOffbase - ZSTD_REP_NUM; ++ assert(ZSTD_highbit32((U32)maxOffbase) == STREAM_ACCUMULATOR_MIN); ++ return maxOffset; ++ } + } +-#endif + + size_t + ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx, +@@ -1980,20 +2055,21 @@ + const void* src, size_t srcSize, const int frame, const streaming_operation streaming) + { /* blockType == blockCompressed */ + const BYTE* ip = (const BYTE*)src; +- /* isLongOffset must be true if there are long offsets. +- * Offsets are long if they are larger than 2^STREAM_ACCUMULATOR_MIN. +- * We don't expect that to be the case in 64-bit mode. +- * In block mode, window size is not known, so we have to be conservative. +- * (note: but it could be evaluated from current-lowLimit) +- */ +- ZSTD_longOffset_e const isLongOffset = (ZSTD_longOffset_e)(MEM_32bits() && (!frame || (dctx->fParams.windowSize > (1ULL << STREAM_ACCUMULATOR_MIN)))); + DEBUGLOG(5, "ZSTD_decompressBlock_internal (size : %u)", (U32)srcSize); + +- RETURN_ERROR_IF(srcSize >= ZSTD_BLOCKSIZE_MAX, srcSize_wrong, ""); ++ /* Note : the wording of the specification ++ * allows compressed block to be sized exactly ZSTD_BLOCKSIZE_MAX. ++ * This generally does not happen, as it makes little sense, ++ * since an uncompressed block would feature same size and have no decompression cost. ++ * Also, note that decoder from reference libzstd before < v1.5.4 ++ * would consider this edge case as an error. ++ * As a consequence, avoid generating compressed blocks of size ZSTD_BLOCKSIZE_MAX ++ * for broader compatibility with the deployed ecosystem of zstd decoders */ ++ RETURN_ERROR_IF(srcSize > ZSTD_BLOCKSIZE_MAX, srcSize_wrong, ""); + + /* Decode literals section */ + { size_t const litCSize = ZSTD_decodeLiteralsBlock(dctx, src, srcSize, dst, dstCapacity, streaming); +- DEBUGLOG(5, "ZSTD_decodeLiteralsBlock : %u", (U32)litCSize); ++ DEBUGLOG(5, "ZSTD_decodeLiteralsBlock : cSize=%u, nbLiterals=%zu", (U32)litCSize, dctx->litSize); + if (ZSTD_isError(litCSize)) return litCSize; + ip += litCSize; + srcSize -= litCSize; +@@ -2001,6 +2077,23 @@ + + /* Build Decoding Tables */ + { ++ /* Compute the maximum block size, which must also work when !frame and fParams are unset. ++ * Additionally, take the min with dstCapacity to ensure that the totalHistorySize fits in a size_t. ++ */ ++ size_t const blockSizeMax = MIN(dstCapacity, (frame ? dctx->fParams.blockSizeMax : ZSTD_BLOCKSIZE_MAX)); ++ size_t const totalHistorySize = ZSTD_totalHistorySize((BYTE*)dst + blockSizeMax, (BYTE const*)dctx->virtualStart); ++ /* isLongOffset must be true if there are long offsets. ++ * Offsets are long if they are larger than ZSTD_maxShortOffset(). ++ * We don't expect that to be the case in 64-bit mode. ++ * ++ * We check here to see if our history is large enough to allow long offsets. ++ * If it isn't, then we can't possible have (valid) long offsets. If the offset ++ * is invalid, then it is okay to read it incorrectly. ++ * ++ * If isLongOffsets is true, then we will later check our decoding table to see ++ * if it is even possible to generate long offsets. ++ */ ++ ZSTD_longOffset_e isLongOffset = (ZSTD_longOffset_e)(MEM_32bits() && (totalHistorySize > ZSTD_maxShortOffset())); + /* These macros control at build-time which decompressor implementation + * we use. If neither is defined, we do some inspection and dispatch at + * runtime. +@@ -2008,6 +2101,11 @@ + #if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ + !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) + int usePrefetchDecoder = dctx->ddictIsCold; ++#else ++ /* Set to 1 to avoid computing offset info if we don't need to. ++ * Otherwise this value is ignored. ++ */ ++ int usePrefetchDecoder = 1; + #endif + int nbSeq; + size_t const seqHSize = ZSTD_decodeSeqHeaders(dctx, &nbSeq, ip, srcSize); +@@ -2015,28 +2113,42 @@ + ip += seqHSize; + srcSize -= seqHSize; + +- RETURN_ERROR_IF(dst == NULL && nbSeq > 0, dstSize_tooSmall, "NULL not handled"); +- +-#if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ +- !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) +- if ( !usePrefetchDecoder +- && (!frame || (dctx->fParams.windowSize > (1<<24))) +- && (nbSeq>ADVANCED_SEQS) ) { /* could probably use a larger nbSeq limit */ +- U32 const shareLongOffsets = ZSTD_getLongOffsetsShare(dctx->OFTptr); +- U32 const minShare = MEM_64bits() ? 7 : 20; /* heuristic values, correspond to 2.73% and 7.81% */ +- usePrefetchDecoder = (shareLongOffsets >= minShare); ++ RETURN_ERROR_IF((dst == NULL || dstCapacity == 0) && nbSeq > 0, dstSize_tooSmall, "NULL not handled"); ++ RETURN_ERROR_IF(MEM_64bits() && sizeof(size_t) == sizeof(void*) && (size_t)(-1) - (size_t)dst < (size_t)(1 << 20), dstSize_tooSmall, ++ "invalid dst"); ++ ++ /* If we could potentially have long offsets, or we might want to use the prefetch decoder, ++ * compute information about the share of long offsets, and the maximum nbAdditionalBits. ++ * NOTE: could probably use a larger nbSeq limit ++ */ ++ if (isLongOffset || (!usePrefetchDecoder && (totalHistorySize > (1u << 24)) && (nbSeq > 8))) { ++ ZSTD_OffsetInfo const info = ZSTD_getOffsetInfo(dctx->OFTptr, nbSeq); ++ if (isLongOffset && info.maxNbAdditionalBits <= STREAM_ACCUMULATOR_MIN) { ++ /* If isLongOffset, but the maximum number of additional bits that we see in our table is small ++ * enough, then we know it is impossible to have too long an offset in this block, so we can ++ * use the regular offset decoder. ++ */ ++ isLongOffset = ZSTD_lo_isRegularOffset; ++ } ++ if (!usePrefetchDecoder) { ++ U32 const minShare = MEM_64bits() ? 7 : 20; /* heuristic values, correspond to 2.73% and 7.81% */ ++ usePrefetchDecoder = (info.longOffsetShare >= minShare); ++ } + } +-#endif + + dctx->ddictIsCold = 0; + + #if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \ + !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG) +- if (usePrefetchDecoder) ++ if (usePrefetchDecoder) { ++#else ++ (void)usePrefetchDecoder; ++ { + #endif + #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT + return ZSTD_decompressSequencesLong(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset, frame); + #endif ++ } + + #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG + /* else */ +@@ -2060,9 +2172,9 @@ + } + + +-size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, +- void* dst, size_t dstCapacity, +- const void* src, size_t srcSize) ++size_t ZSTD_decompressBlock_deprecated(ZSTD_DCtx* dctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize) + { + size_t dSize; + ZSTD_checkContinuity(dctx, dst, dstCapacity); +@@ -2070,3 +2182,12 @@ + dctx->previousDstEnd = (char*)dst + dSize; + return dSize; + } ++ ++ ++/* NOTE: Must just wrap ZSTD_decompressBlock_deprecated() */ ++size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize) ++{ ++ return ZSTD_decompressBlock_deprecated(dctx, dst, dstCapacity, src, srcSize); ++} +diff '--color=auto' -uraN a/lib/zstd/decompress/zstd_decompress_block.h b/lib/zstd/decompress/zstd_decompress_block.h +--- a/lib/zstd/decompress/zstd_decompress_block.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress/zstd_decompress_block.h 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -64,5 +65,10 @@ + unsigned tableLog, void* wksp, size_t wkspSize, + int bmi2); + ++/* Internal definition of ZSTD_decompressBlock() to avoid deprecation warnings. */ ++size_t ZSTD_decompressBlock_deprecated(ZSTD_DCtx* dctx, ++ void* dst, size_t dstCapacity, ++ const void* src, size_t srcSize); ++ + + #endif /* ZSTD_DEC_BLOCK_H */ +diff '--color=auto' -uraN a/lib/zstd/decompress/zstd_decompress_internal.h b/lib/zstd/decompress/zstd_decompress_internal.h +--- a/lib/zstd/decompress/zstd_decompress_internal.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress/zstd_decompress_internal.h 2023-11-04 16:35:57.851317477 +0300 +@@ -1,5 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Yann Collet, Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -75,12 +76,13 @@ + + #define ZSTD_BUILD_FSE_TABLE_WKSP_SIZE (sizeof(S16) * (MaxSeq + 1) + (1u << MaxFSELog) + sizeof(U64)) + #define ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32 ((ZSTD_BUILD_FSE_TABLE_WKSP_SIZE + sizeof(U32) - 1) / sizeof(U32)) ++#define ZSTD_HUFFDTABLE_CAPACITY_LOG 12 + + typedef struct { + ZSTD_seqSymbol LLTable[SEQSYMBOL_TABLE_SIZE(LLFSELog)]; /* Note : Space reserved for FSE Tables */ + ZSTD_seqSymbol OFTable[SEQSYMBOL_TABLE_SIZE(OffFSELog)]; /* is also used as temporary workspace while building hufTable during DDict creation */ + ZSTD_seqSymbol MLTable[SEQSYMBOL_TABLE_SIZE(MLFSELog)]; /* and therefore must be at least HUF_DECOMPRESS_WORKSPACE_SIZE large */ +- HUF_DTable hufTable[HUF_DTABLE_SIZE(HufLog)]; /* can accommodate HUF_decompress4X */ ++ HUF_DTable hufTable[HUF_DTABLE_SIZE(ZSTD_HUFFDTABLE_CAPACITY_LOG)]; /* can accommodate HUF_decompress4X */ + U32 rep[ZSTD_REP_NUM]; + U32 workspace[ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32]; + } ZSTD_entropyDTables_t; +@@ -164,6 +166,7 @@ + ZSTD_dictUses_e dictUses; + ZSTD_DDictHashSet* ddictSet; /* Hash set for multiple ddicts */ + ZSTD_refMultipleDDicts_e refMultipleDDicts; /* User specified: if == 1, will allow references to multiple DDicts. Default == 0 (disabled) */ ++ int disableHufAsm; + + /* streaming */ + ZSTD_dStreamStage streamStage; +diff '--color=auto' -uraN a/lib/zstd/decompress_sources.h b/lib/zstd/decompress_sources.h +--- a/lib/zstd/decompress_sources.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/decompress_sources.h 2023-11-04 16:35:57.851317477 +0300 +@@ -1,6 +1,6 @@ + /* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ + /* +- * Copyright (c) Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/zstd_common_module.c b/lib/zstd/zstd_common_module.c +--- a/lib/zstd/zstd_common_module.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/zstd_common_module.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +@@ -24,9 +24,6 @@ + EXPORT_SYMBOL_GPL(ZSTD_isError); + EXPORT_SYMBOL_GPL(ZSTD_getErrorName); + EXPORT_SYMBOL_GPL(ZSTD_getErrorCode); +-EXPORT_SYMBOL_GPL(ZSTD_customMalloc); +-EXPORT_SYMBOL_GPL(ZSTD_customCalloc); +-EXPORT_SYMBOL_GPL(ZSTD_customFree); + + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("Zstd Common"); +diff '--color=auto' -uraN a/lib/zstd/zstd_compress_module.c b/lib/zstd/zstd_compress_module.c +--- a/lib/zstd/zstd_compress_module.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/zstd_compress_module.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/lib/zstd/zstd_decompress_module.c b/lib/zstd/zstd_decompress_module.c +--- a/lib/zstd/zstd_decompress_module.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/lib/zstd/zstd_decompress_module.c 2023-11-04 16:35:57.851317477 +0300 +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause + /* +- * Copyright (c) Facebook, Inc. ++ * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the +diff '--color=auto' -uraN a/mm/Kconfig b/mm/Kconfig +--- a/mm/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/Kconfig 2023-11-04 16:35:57.821316939 +0300 +@@ -631,7 +631,7 @@ + config COMPACT_UNEVICTABLE_DEFAULT + int + depends on COMPACTION +- default 0 if PREEMPT_RT ++ default 0 if PREEMPT_RT || CACHY + default 1 + + # +diff '--color=auto' -uraN a/mm/internal.h b/mm/internal.h +--- a/mm/internal.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/internal.h 2023-11-04 16:35:57.821316939 +0300 +@@ -421,6 +421,7 @@ + extern void post_alloc_hook(struct page *page, unsigned int order, + gfp_t gfp_flags); + extern int user_min_free_kbytes; ++extern atomic_long_t kswapd_waiters; + + extern void free_unref_page(struct page *page, unsigned int order); + extern void free_unref_page_list(struct list_head *list); +diff '--color=auto' -uraN a/mm/khugepaged.c b/mm/khugepaged.c +--- a/mm/khugepaged.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/khugepaged.c 2023-11-04 16:35:57.841317298 +0300 +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -709,6 +710,7 @@ + spin_lock(ptl); + ptep_clear(vma->vm_mm, address, _pte); + spin_unlock(ptl); ++ ksm_might_unmap_zero_page(vma->vm_mm, pteval); + } + } else { + src_page = pte_page(pteval); +diff '--color=auto' -uraN a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/ksm.c 2023-11-04 16:35:57.841317298 +0300 +@@ -278,6 +278,9 @@ + /* Whether to merge empty (zeroed) pages with actual zero pages */ + static bool ksm_use_zero_pages __read_mostly; + ++/* The number of zero pages which is placed by KSM */ ++unsigned long ksm_zero_pages; ++ + #ifdef CONFIG_NUMA + /* Zeroed when merging across nodes is not allowed */ + static unsigned int ksm_merge_across_nodes = 1; +@@ -448,7 +451,8 @@ + if (is_migration_entry(entry)) + page = pfn_swap_entry_to_page(entry); + } +- ret = page && PageKsm(page); ++ /* return 1 if the page is an normal ksm page or KSM-placed zero page */ ++ ret = (page && PageKsm(page)) || is_ksm_zero_pte(*pte); + pte_unmap_unlock(pte, ptl); + return ret; + } +@@ -1229,8 +1233,14 @@ + page_add_anon_rmap(kpage, vma, addr, RMAP_NONE); + newpte = mk_pte(kpage, vma->vm_page_prot); + } else { +- newpte = pte_mkspecial(pfn_pte(page_to_pfn(kpage), +- vma->vm_page_prot)); ++ /* ++ * Use pte_mkdirty to mark the zero page mapped by KSM, and then ++ * we can easily track all KSM-placed zero pages by checking if ++ * the dirty bit in zero page's PTE is set. ++ */ ++ newpte = pte_mkdirty(pte_mkspecial(pfn_pte(page_to_pfn(kpage), vma->vm_page_prot))); ++ ksm_zero_pages++; ++ mm->ksm_zero_pages++; + /* + * We're replacing an anonymous page with a zero page, which is + * not anonymous. We need to do proper accounting otherwise we +@@ -3091,7 +3101,7 @@ + #ifdef CONFIG_PROC_FS + long ksm_process_profit(struct mm_struct *mm) + { +- return mm->ksm_merging_pages * PAGE_SIZE - ++ return (long)(mm->ksm_merging_pages + mm->ksm_zero_pages) * PAGE_SIZE - + mm->ksm_rmap_items * sizeof(struct ksm_rmap_item); + } + #endif /* CONFIG_PROC_FS */ +@@ -3360,12 +3370,19 @@ + } + KSM_ATTR_RO(pages_volatile); + ++static ssize_t ksm_zero_pages_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return sysfs_emit(buf, "%ld\n", ksm_zero_pages); ++} ++KSM_ATTR_RO(ksm_zero_pages); ++ + static ssize_t general_profit_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) + { + long general_profit; + +- general_profit = ksm_pages_sharing * PAGE_SIZE - ++ general_profit = (ksm_pages_sharing + ksm_zero_pages) * PAGE_SIZE - + ksm_rmap_items * sizeof(struct ksm_rmap_item); + + return sysfs_emit(buf, "%ld\n", general_profit); +@@ -3427,6 +3444,7 @@ + &pages_sharing_attr.attr, + &pages_unshared_attr.attr, + &pages_volatile_attr.attr, ++ &ksm_zero_pages_attr.attr, + &full_scans_attr.attr, + #ifdef CONFIG_NUMA + &merge_across_nodes_attr.attr, +diff '--color=auto' -uraN a/mm/list_lru.c b/mm/list_lru.c +--- a/mm/list_lru.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/list_lru.c 2023-11-04 16:35:57.821316939 +0300 +@@ -178,6 +178,7 @@ + unsigned long list_lru_count_one(struct list_lru *lru, + int nid, struct mem_cgroup *memcg) + { ++#if defined(CONFIG_MEMCG) && !defined(CONFIG_SLOB) + struct list_lru_one *l; + long count; + +@@ -190,6 +191,9 @@ + count = 0; + + return count; ++#else ++ return READ_ONCE(lru->node[nid].lru.nr_items); ++#endif + } + EXPORT_SYMBOL_GPL(list_lru_count_one); + +diff '--color=auto' -uraN a/mm/memory.c b/mm/memory.c +--- a/mm/memory.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/memory.c 2023-11-04 16:35:57.841317298 +0300 +@@ -1433,8 +1433,10 @@ + tlb_remove_tlb_entry(tlb, pte, addr); + zap_install_uffd_wp_if_needed(vma, addr, pte, details, + ptent); +- if (unlikely(!page)) ++ if (unlikely(!page)) { ++ ksm_might_unmap_zero_page(mm, ptent); + continue; ++ } + + delay_rmap = 0; + if (!PageAnon(page)) { +@@ -3128,6 +3130,7 @@ + inc_mm_counter(mm, MM_ANONPAGES); + } + } else { ++ ksm_might_unmap_zero_page(mm, vmf->orig_pte); + inc_mm_counter(mm, MM_ANONPAGES); + } + flush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte)); +diff '--color=auto' -uraN a/mm/page-writeback.c b/mm/page-writeback.c +--- a/mm/page-writeback.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/page-writeback.c 2023-11-04 16:35:57.824650332 +0300 +@@ -71,7 +71,11 @@ + /* + * Start background writeback (via writeback threads) at this percentage + */ ++#ifdef CONFIG_CACHY ++static int dirty_background_ratio = 5; ++#else + static int dirty_background_ratio = 10; ++#endif + + /* + * dirty_background_bytes starts at 0 (disabled) so that it is a function of +@@ -99,7 +103,11 @@ + /* + * The interval between `kupdate'-style writebacks + */ ++#ifdef CONFIG_CACHY ++unsigned int dirty_writeback_interval = 10 * 100; /* centiseconds */ ++#else + unsigned int dirty_writeback_interval = 5 * 100; /* centiseconds */ ++#endif + + EXPORT_SYMBOL_GPL(dirty_writeback_interval); + +diff '--color=auto' -uraN a/mm/page_alloc.c b/mm/page_alloc.c +--- a/mm/page_alloc.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/page_alloc.c 2023-11-04 16:35:57.824650332 +0300 +@@ -204,6 +204,8 @@ + + gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK; + ++atomic_long_t kswapd_waiters = ATOMIC_LONG_INIT(0); ++ + /* + * A cached value of the page's pageblock's migratetype, used when the page is + * put on a pcplist. Used to avoid the pageblock migratetype lookup when +@@ -297,7 +299,7 @@ + + int min_free_kbytes = 1024; + int user_min_free_kbytes = -1; +-static int watermark_boost_factor __read_mostly = 15000; ++static int watermark_boost_factor __read_mostly; + static int watermark_scale_factor = 10; + + /* movable_zone is the "real" zone pages in ZONE_MOVABLE are taken from */ +@@ -2152,16 +2154,17 @@ + } + + /* +- * Obtain a specified number of elements from the buddy allocator, all under +- * a single hold of the lock, for efficiency. Add them to the supplied list. +- * Returns the number of new pages which were placed at *list. ++ * Obtain a specified number of elements from the buddy allocator, and relax the ++ * zone lock when needed. Add them to the supplied list. Returns the number of ++ * new pages which were placed at *list. + */ + static int rmqueue_bulk(struct zone *zone, unsigned int order, + unsigned long count, struct list_head *list, + int migratetype, unsigned int alloc_flags) + { ++ const bool can_resched = !preempt_count() && !irqs_disabled(); + unsigned long flags; +- int i; ++ int i, last_mod = 0; + + spin_lock_irqsave(&zone->lock, flags); + for (i = 0; i < count; ++i) { +@@ -2170,6 +2173,18 @@ + if (unlikely(page == NULL)) + break; + ++ /* Reschedule and ease the contention on the lock if needed */ ++ if (i + 1 < count && ((can_resched && need_resched()) || ++ spin_needbreak(&zone->lock))) { ++ __mod_zone_page_state(zone, NR_FREE_PAGES, ++ -((i + 1 - last_mod) << order)); ++ last_mod = i + 1; ++ spin_unlock_irqrestore(&zone->lock, flags); ++ if (can_resched) ++ cond_resched(); ++ spin_lock_irqsave(&zone->lock, flags); ++ } ++ + /* + * Split buddy pages returned by expand() are received here in + * physical page order. The page is added to the tail of +@@ -2186,7 +2201,7 @@ + -(1 << order)); + } + +- __mod_zone_page_state(zone, NR_FREE_PAGES, -(i << order)); ++ __mod_zone_page_state(zone, NR_FREE_PAGES, -((i - last_mod) << order)); + spin_unlock_irqrestore(&zone->lock, flags); + + return i; +@@ -3962,6 +3977,7 @@ + unsigned int cpuset_mems_cookie; + unsigned int zonelist_iter_cookie; + int reserve_flags; ++ bool woke_kswapd = false; + + restart: + compaction_retries = 0; +@@ -4001,8 +4017,13 @@ + goto nopage; + } + +- if (alloc_flags & ALLOC_KSWAPD) ++ if (alloc_flags & ALLOC_KSWAPD) { ++ if (!woke_kswapd) { ++ atomic_long_inc(&kswapd_waiters); ++ woke_kswapd = true; ++ } + wake_all_kswapds(order, gfp_mask, ac); ++ } + + /* + * The adjusted alloc_flags might result in immediate success, so try +@@ -4217,9 +4238,12 @@ + goto retry; + } + fail: +- warn_alloc(gfp_mask, ac->nodemask, +- "page allocation failure: order:%u", order); + got_pg: ++ if (woke_kswapd) ++ atomic_long_dec(&kswapd_waiters); ++ if (!page) ++ warn_alloc(gfp_mask, ac->nodemask, ++ "page allocation failure: order:%u", order); + return page; + } + +diff '--color=auto' -uraN a/mm/swap.c b/mm/swap.c +--- a/mm/swap.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/swap.c 2023-11-04 16:35:57.824650332 +0300 +@@ -1090,6 +1090,10 @@ + */ + void __init swap_setup(void) + { ++#ifdef CONFIG_CACHY ++ /* Only swap-in pages requested, avoid readahead */ ++ page_cluster = 0; ++#else + unsigned long megs = totalram_pages() >> (20 - PAGE_SHIFT); + + /* Use a smaller cluster for small-memory machines */ +@@ -1101,4 +1105,5 @@ + * Right now other parts of the system means that we + * _really_ don't want to cluster much more + */ ++#endif + } +diff '--color=auto' -uraN a/mm/vmpressure.c b/mm/vmpressure.c +--- a/mm/vmpressure.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/vmpressure.c 2023-11-04 16:35:57.824650332 +0300 +@@ -43,7 +43,11 @@ + * essence, they are percents: the higher the value, the more number + * unsuccessful reclaims there were. + */ ++#ifdef CONFIG_CACHY ++static const unsigned int vmpressure_level_med = 65; ++#else + static const unsigned int vmpressure_level_med = 60; ++#endif + static const unsigned int vmpressure_level_critical = 95; + + /* +diff '--color=auto' -uraN a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/mm/vmscan.c 2023-11-04 16:35:57.824650332 +0300 +@@ -186,7 +186,11 @@ + /* + * From 0 .. 200. Higher means more swappy. + */ ++#ifdef CONFIG_CACHY ++int vm_swappiness = 20; ++#else + int vm_swappiness = 60; ++#endif + + LIST_HEAD(shrinker_list); + DECLARE_RWSEM(shrinker_rwsem); +@@ -4595,7 +4599,11 @@ + } + + /* to protect the working set of the last N jiffies */ ++#ifdef CONFIG_CACHY ++static unsigned long lru_gen_min_ttl __read_mostly = HZ; ++#else + static unsigned long lru_gen_min_ttl __read_mostly; ++#endif + + static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc) + { +@@ -6908,7 +6916,7 @@ + return 0; + } + +-static bool allow_direct_reclaim(pg_data_t *pgdat) ++static bool allow_direct_reclaim(pg_data_t *pgdat, bool using_kswapd) + { + struct zone *zone; + unsigned long pfmemalloc_reserve = 0; +@@ -6937,6 +6945,10 @@ + + wmark_ok = free_pages > pfmemalloc_reserve / 2; + ++ /* The throttled direct reclaimer is now a kswapd waiter */ ++ if (unlikely(!using_kswapd && !wmark_ok)) ++ atomic_long_inc(&kswapd_waiters); ++ + /* kswapd must be awake if processes are being throttled */ + if (!wmark_ok && waitqueue_active(&pgdat->kswapd_wait)) { + if (READ_ONCE(pgdat->kswapd_highest_zoneidx) > ZONE_NORMAL) +@@ -7002,7 +7014,7 @@ + + /* Throttle based on the first usable node */ + pgdat = zone->zone_pgdat; +- if (allow_direct_reclaim(pgdat)) ++ if (allow_direct_reclaim(pgdat, gfp_mask & __GFP_KSWAPD_RECLAIM)) + goto out; + break; + } +@@ -7024,11 +7036,14 @@ + */ + if (!(gfp_mask & __GFP_FS)) + wait_event_interruptible_timeout(pgdat->pfmemalloc_wait, +- allow_direct_reclaim(pgdat), HZ); ++ allow_direct_reclaim(pgdat, true), HZ); + else + /* Throttle until kswapd wakes the process */ + wait_event_killable(zone->zone_pgdat->pfmemalloc_wait, +- allow_direct_reclaim(pgdat)); ++ allow_direct_reclaim(pgdat, true)); ++ ++ if (unlikely(!(gfp_mask & __GFP_KSWAPD_RECLAIM))) ++ atomic_long_dec(&kswapd_waiters); + + if (fatal_signal_pending(current)) + return true; +@@ -7526,14 +7541,15 @@ + * able to safely make forward progress. Wake them + */ + if (waitqueue_active(&pgdat->pfmemalloc_wait) && +- allow_direct_reclaim(pgdat)) ++ allow_direct_reclaim(pgdat, true)) + wake_up_all(&pgdat->pfmemalloc_wait); + + /* Check if kswapd should be suspending */ + __fs_reclaim_release(_THIS_IP_); + ret = try_to_freeze(); + __fs_reclaim_acquire(_THIS_IP_); +- if (ret || kthread_should_stop()) ++ if (ret || kthread_should_stop() || ++ !atomic_long_read(&kswapd_waiters)) + break; + + /* +diff '--color=auto' -uraN a/net/ipv4/Kconfig b/net/ipv4/Kconfig +--- a/net/ipv4/Kconfig 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/Kconfig 2023-11-04 16:35:57.801316580 +0300 +@@ -668,15 +668,18 @@ + default n + help + +- BBR (Bottleneck Bandwidth and RTT) TCP congestion control aims to +- maximize network utilization and minimize queues. It builds an explicit +- model of the bottleneck delivery rate and path round-trip propagation +- delay. It tolerates packet loss and delay unrelated to congestion. It +- can operate over LAN, WAN, cellular, wifi, or cable modem links. It can +- coexist with flows that use loss-based congestion control, and can +- operate with shallow buffers, deep buffers, bufferbloat, policers, or +- AQM schemes that do not provide a delay signal. It requires the fq +- ("Fair Queue") pacing packet scheduler. ++ BBR (Bottleneck Bandwidth and RTT) TCP congestion control is a ++ model-based congestion control algorithm that aims to maximize ++ network utilization, keep queues and retransmit rates low, and to be ++ able to coexist with Reno/CUBIC in common scenarios. It builds an ++ explicit model of the network path. It tolerates a targeted degree ++ of random packet loss and delay. It can operate over LAN, WAN, ++ cellular, wifi, or cable modem links, and can use shallow-threshold ++ ECN signals. It can coexist to some degree with flows that use ++ loss-based congestion control, and can operate with shallow buffers, ++ deep buffers, bufferbloat, policers, or AQM schemes that do not ++ provide a delay signal. It requires pacing, using either TCP internal ++ pacing or the fq ("Fair Queue") pacing packet scheduler. + + choice + prompt "Default TCP congestion control" +diff '--color=auto' -uraN a/net/ipv4/tcp.c b/net/ipv4/tcp.c +--- a/net/ipv4/tcp.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp.c 2023-11-04 16:35:57.804649973 +0300 +@@ -3077,6 +3077,7 @@ + tp->rx_opt.dsack = 0; + tp->rx_opt.num_sacks = 0; + tp->rcv_ooopack = 0; ++ tp->fast_ack_mode = 0; + + + /* Clean up fastopen related fields */ +@@ -3772,6 +3773,8 @@ + info->tcpi_options |= TCPI_OPT_ECN; + if (tp->ecn_flags & TCP_ECN_SEEN) + info->tcpi_options |= TCPI_OPT_ECN_SEEN; ++ if (tp->ecn_flags & TCP_ECN_LOW) ++ info->tcpi_options |= TCPI_OPT_ECN_LOW; + if (tp->syn_data_acked) + info->tcpi_options |= TCPI_OPT_SYN_DATA; + +diff '--color=auto' -uraN a/net/ipv4/tcp_bbr.c b/net/ipv4/tcp_bbr.c +--- a/net/ipv4/tcp_bbr.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp_bbr.c 2023-11-04 16:35:57.804649973 +0300 +@@ -1,18 +1,19 @@ +-/* Bottleneck Bandwidth and RTT (BBR) congestion control ++/* BBR (Bottleneck Bandwidth and RTT) congestion control + * +- * BBR congestion control computes the sending rate based on the delivery +- * rate (throughput) estimated from ACKs. In a nutshell: ++ * BBR is a model-based congestion control algorithm that aims for low queues, ++ * low loss, and (bounded) Reno/CUBIC coexistence. To maintain a model of the ++ * network path, it uses measurements of bandwidth and RTT, as well as (if they ++ * occur) packet loss and/or shallow-threshold ECN signals. Note that although ++ * it can use ECN or loss signals explicitly, it does not require either; it ++ * can bound its in-flight data based on its estimate of the BDP. + * +- * On each ACK, update our model of the network path: +- * bottleneck_bandwidth = windowed_max(delivered / elapsed, 10 round trips) +- * min_rtt = windowed_min(rtt, 10 seconds) +- * pacing_rate = pacing_gain * bottleneck_bandwidth +- * cwnd = max(cwnd_gain * bottleneck_bandwidth * min_rtt, 4) +- * +- * The core algorithm does not react directly to packet losses or delays, +- * although BBR may adjust the size of next send per ACK when loss is +- * observed, or adjust the sending rate if it estimates there is a +- * traffic policer, in order to keep the drop rate reasonable. ++ * The model has both higher and lower bounds for the operating range: ++ * lo: bw_lo, inflight_lo: conservative short-term lower bound ++ * hi: bw_hi, inflight_hi: robust long-term upper bound ++ * The bandwidth-probing time scale is (a) extended dynamically based on ++ * estimated BDP to improve coexistence with Reno/CUBIC; (b) bounded by ++ * an interactive wall-clock time-scale to be more scalable and responsive ++ * than Reno and CUBIC. + * + * Here is a state transition diagram for BBR: + * +@@ -65,6 +66,13 @@ + #include + #include + ++#include ++#include "tcp_dctcp.h" ++ ++#define BBR_VERSION 3 ++ ++#define bbr_param(sk,name) (bbr_ ## name) ++ + /* Scale factor for rate in pkt/uSec unit to avoid truncation in bandwidth + * estimation. The rate unit ~= (1500 bytes / 1 usec / 2^24) ~= 715 bps. + * This handles bandwidths from 0.06pps (715bps) to 256Mpps (3Tbps) in a u32. +@@ -85,36 +93,41 @@ + BBR_PROBE_RTT, /* cut inflight to min to probe min_rtt */ + }; + ++/* How does the incoming ACK stream relate to our bandwidth probing? */ ++enum bbr_ack_phase { ++ BBR_ACKS_INIT, /* not probing; not getting probe feedback */ ++ BBR_ACKS_REFILLING, /* sending at est. bw to fill pipe */ ++ BBR_ACKS_PROBE_STARTING, /* inflight rising to probe bw */ ++ BBR_ACKS_PROBE_FEEDBACK, /* getting feedback from bw probing */ ++ BBR_ACKS_PROBE_STOPPING, /* stopped probing; still getting feedback */ ++}; ++ + /* BBR congestion control block */ + struct bbr { + u32 min_rtt_us; /* min RTT in min_rtt_win_sec window */ + u32 min_rtt_stamp; /* timestamp of min_rtt_us */ + u32 probe_rtt_done_stamp; /* end time for BBR_PROBE_RTT mode */ +- struct minmax bw; /* Max recent delivery rate in pkts/uS << 24 */ +- u32 rtt_cnt; /* count of packet-timed rounds elapsed */ ++ u32 probe_rtt_min_us; /* min RTT in probe_rtt_win_ms win */ ++ u32 probe_rtt_min_stamp; /* timestamp of probe_rtt_min_us*/ + u32 next_rtt_delivered; /* scb->tx.delivered at end of round */ + u64 cycle_mstamp; /* time of this cycle phase start */ +- u32 mode:3, /* current bbr_mode in state machine */ ++ u32 mode:2, /* current bbr_mode in state machine */ + prev_ca_state:3, /* CA state on previous ACK */ +- packet_conservation:1, /* use packet conservation? */ + round_start:1, /* start of packet-timed tx->ack round? */ ++ ce_state:1, /* If most recent data has CE bit set */ ++ bw_probe_up_rounds:5, /* cwnd-limited rounds in PROBE_UP */ ++ try_fast_path:1, /* can we take fast path? */ + idle_restart:1, /* restarting after idle? */ + probe_rtt_round_done:1, /* a BBR_PROBE_RTT round at 4 pkts? */ +- unused:13, +- lt_is_sampling:1, /* taking long-term ("LT") samples now? */ +- lt_rtt_cnt:7, /* round trips in long-term interval */ +- lt_use_bw:1; /* use lt_bw as our bw estimate? */ +- u32 lt_bw; /* LT est delivery rate in pkts/uS << 24 */ +- u32 lt_last_delivered; /* LT intvl start: tp->delivered */ +- u32 lt_last_stamp; /* LT intvl start: tp->delivered_mstamp */ +- u32 lt_last_lost; /* LT intvl start: tp->lost */ ++ init_cwnd:7, /* initial cwnd */ ++ unused_1:10; + u32 pacing_gain:10, /* current gain for setting pacing rate */ + cwnd_gain:10, /* current gain for setting cwnd */ + full_bw_reached:1, /* reached full bw in Startup? */ + full_bw_cnt:2, /* number of rounds without large bw gains */ +- cycle_idx:3, /* current index in pacing_gain cycle array */ ++ cycle_idx:2, /* current index in pacing_gain cycle array */ + has_seen_rtt:1, /* have we seen an RTT sample yet? */ +- unused_b:5; ++ unused_2:6; + u32 prior_cwnd; /* prior cwnd upon entering loss recovery */ + u32 full_bw; /* recent bw, to estimate if pipe is full */ + +@@ -124,19 +137,67 @@ + u32 ack_epoch_acked:20, /* packets (S)ACKed in sampling epoch */ + extra_acked_win_rtts:5, /* age of extra_acked, in round trips */ + extra_acked_win_idx:1, /* current index in extra_acked array */ +- unused_c:6; ++ /* BBR v3 state: */ ++ full_bw_now:1, /* recently reached full bw plateau? */ ++ startup_ecn_rounds:2, /* consecutive hi ECN STARTUP rounds */ ++ loss_in_cycle:1, /* packet loss in this cycle? */ ++ ecn_in_cycle:1, /* ECN in this cycle? */ ++ unused_3:1; ++ u32 loss_round_delivered; /* scb->tx.delivered ending loss round */ ++ u32 undo_bw_lo; /* bw_lo before latest losses */ ++ u32 undo_inflight_lo; /* inflight_lo before latest losses */ ++ u32 undo_inflight_hi; /* inflight_hi before latest losses */ ++ u32 bw_latest; /* max delivered bw in last round trip */ ++ u32 bw_lo; /* lower bound on sending bandwidth */ ++ u32 bw_hi[2]; /* max recent measured bw sample */ ++ u32 inflight_latest; /* max delivered data in last round trip */ ++ u32 inflight_lo; /* lower bound of inflight data range */ ++ u32 inflight_hi; /* upper bound of inflight data range */ ++ u32 bw_probe_up_cnt; /* packets delivered per inflight_hi incr */ ++ u32 bw_probe_up_acks; /* packets (S)ACKed since inflight_hi incr */ ++ u32 probe_wait_us; /* PROBE_DOWN until next clock-driven probe */ ++ u32 prior_rcv_nxt; /* tp->rcv_nxt when CE state last changed */ ++ u32 ecn_eligible:1, /* sender can use ECN (RTT, handshake)? */ ++ ecn_alpha:9, /* EWMA delivered_ce/delivered; 0..256 */ ++ bw_probe_samples:1, /* rate samples reflect bw probing? */ ++ prev_probe_too_high:1, /* did last PROBE_UP go too high? */ ++ stopped_risky_probe:1, /* last PROBE_UP stopped due to risk? */ ++ rounds_since_probe:8, /* packet-timed rounds since probed bw */ ++ loss_round_start:1, /* loss_round_delivered round trip? */ ++ loss_in_round:1, /* loss marked in this round trip? */ ++ ecn_in_round:1, /* ECN marked in this round trip? */ ++ ack_phase:3, /* bbr_ack_phase: meaning of ACKs */ ++ loss_events_in_round:4,/* losses in STARTUP round */ ++ initialized:1; /* has bbr_init() been called? */ ++ u32 alpha_last_delivered; /* tp->delivered at alpha update */ ++ u32 alpha_last_delivered_ce; /* tp->delivered_ce at alpha update */ ++ ++ u8 unused_4; /* to preserve alignment */ ++ struct tcp_plb_state plb; + }; + +-#define CYCLE_LEN 8 /* number of phases in a pacing gain cycle */ ++struct bbr_context { ++ u32 sample_bw; ++}; + +-/* Window length of bw filter (in rounds): */ +-static const int bbr_bw_rtts = CYCLE_LEN + 2; + /* Window length of min_rtt filter (in sec): */ + static const u32 bbr_min_rtt_win_sec = 10; + /* Minimum time (in ms) spent at bbr_cwnd_min_target in BBR_PROBE_RTT mode: */ + static const u32 bbr_probe_rtt_mode_ms = 200; +-/* Skip TSO below the following bandwidth (bits/sec): */ +-static const int bbr_min_tso_rate = 1200000; ++/* Window length of probe_rtt_min_us filter (in ms), and consequently the ++ * typical interval between PROBE_RTT mode entries. The default is 5000ms. ++ * Note that bbr_probe_rtt_win_ms must be <= bbr_min_rtt_win_sec * MSEC_PER_SEC ++ */ ++static const u32 bbr_probe_rtt_win_ms = 5000; ++/* Proportion of cwnd to estimated BDP in PROBE_RTT, in units of BBR_UNIT: */ ++static const u32 bbr_probe_rtt_cwnd_gain = BBR_UNIT * 1 / 2; ++ ++/* Use min_rtt to help adapt TSO burst size, with smaller min_rtt resulting ++ * in bigger TSO bursts. We cut the RTT-based allowance in half ++ * for every 2^9 usec (aka 512 us) of RTT, so that the RTT-based allowance ++ * is below 1500 bytes after 6 * ~500 usec = 3ms. ++ */ ++static const u32 bbr_tso_rtt_shift = 9; + + /* Pace at ~1% below estimated bw, on average, to reduce queue at bottleneck. + * In order to help drive the network toward lower queues and low latency while +@@ -146,13 +207,15 @@ + */ + static const int bbr_pacing_margin_percent = 1; + +-/* We use a high_gain value of 2/ln(2) because it's the smallest pacing gain ++/* We use a startup_pacing_gain of 4*ln(2) because it's the smallest value + * that will allow a smoothly increasing pacing rate that will double each RTT + * and send the same number of packets per RTT that an un-paced, slow-starting + * Reno or CUBIC flow would: + */ +-static const int bbr_high_gain = BBR_UNIT * 2885 / 1000 + 1; +-/* The pacing gain of 1/high_gain in BBR_DRAIN is calculated to typically drain ++static const int bbr_startup_pacing_gain = BBR_UNIT * 277 / 100 + 1; ++/* The gain for deriving startup cwnd: */ ++static const int bbr_startup_cwnd_gain = BBR_UNIT * 2; ++/* The pacing gain in BBR_DRAIN is calculated to typically drain + * the queue created in BBR_STARTUP in a single round: + */ + static const int bbr_drain_gain = BBR_UNIT * 1000 / 2885; +@@ -160,13 +223,17 @@ + static const int bbr_cwnd_gain = BBR_UNIT * 2; + /* The pacing_gain values for the PROBE_BW gain cycle, to discover/share bw: */ + static const int bbr_pacing_gain[] = { +- BBR_UNIT * 5 / 4, /* probe for more available bw */ +- BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */ +- BBR_UNIT, BBR_UNIT, BBR_UNIT, /* cruise at 1.0*bw to utilize pipe, */ +- BBR_UNIT, BBR_UNIT, BBR_UNIT /* without creating excess queue... */ ++ BBR_UNIT * 5 / 4, /* UP: probe for more available bw */ ++ BBR_UNIT * 91 / 100, /* DOWN: drain queue and/or yield bw */ ++ BBR_UNIT, /* CRUISE: try to use pipe w/ some headroom */ ++ BBR_UNIT, /* REFILL: refill pipe to estimated 100% */ ++}; ++enum bbr_pacing_gain_phase { ++ BBR_BW_PROBE_UP = 0, /* push up inflight to probe for bw/vol */ ++ BBR_BW_PROBE_DOWN = 1, /* drain excess inflight from the queue */ ++ BBR_BW_PROBE_CRUISE = 2, /* use pipe, w/ headroom in queue/pipe */ ++ BBR_BW_PROBE_REFILL = 3, /* v2: refill the pipe again to 100% */ + }; +-/* Randomize the starting gain cycling phase over N phases: */ +-static const u32 bbr_cycle_rand = 7; + + /* Try to keep at least this many packets in flight, if things go smoothly. For + * smooth functioning, a sliding window protocol ACKing every other packet +@@ -174,24 +241,12 @@ + */ + static const u32 bbr_cwnd_min_target = 4; + +-/* To estimate if BBR_STARTUP mode (i.e. high_gain) has filled pipe... */ ++/* To estimate if BBR_STARTUP or BBR_BW_PROBE_UP has filled pipe... */ + /* If bw has increased significantly (1.25x), there may be more bw available: */ + static const u32 bbr_full_bw_thresh = BBR_UNIT * 5 / 4; + /* But after 3 rounds w/o significant bw growth, estimate pipe is full: */ + static const u32 bbr_full_bw_cnt = 3; + +-/* "long-term" ("LT") bandwidth estimator parameters... */ +-/* The minimum number of rounds in an LT bw sampling interval: */ +-static const u32 bbr_lt_intvl_min_rtts = 4; +-/* If lost/delivered ratio > 20%, interval is "lossy" and we may be policed: */ +-static const u32 bbr_lt_loss_thresh = 50; +-/* If 2 intervals have a bw ratio <= 1/8, their bw is "consistent": */ +-static const u32 bbr_lt_bw_ratio = BBR_UNIT / 8; +-/* If 2 intervals have a bw diff <= 4 Kbit/sec their bw is "consistent": */ +-static const u32 bbr_lt_bw_diff = 4000 / 8; +-/* If we estimate we're policed, use lt_bw for this many round trips: */ +-static const u32 bbr_lt_bw_max_rtts = 48; +- + /* Gain factor for adding extra_acked to target cwnd: */ + static const int bbr_extra_acked_gain = BBR_UNIT; + /* Window length of extra_acked window. */ +@@ -201,8 +256,121 @@ + /* Time period for clamping cwnd increment due to ack aggregation */ + static const u32 bbr_extra_acked_max_us = 100 * 1000; + ++/* Flags to control BBR ECN-related behavior... */ ++ ++/* Ensure ACKs only ACK packets with consistent ECN CE status? */ ++static const bool bbr_precise_ece_ack = true; ++ ++/* Max RTT (in usec) at which to use sender-side ECN logic. ++ * Disabled when 0 (ECN allowed at any RTT). ++ */ ++static const u32 bbr_ecn_max_rtt_us = 5000; ++ ++/* On losses, scale down inflight and pacing rate by beta scaled by BBR_SCALE. ++ * No loss response when 0. ++ */ ++static const u32 bbr_beta = BBR_UNIT * 30 / 100; ++ ++/* Gain factor for ECN mark ratio samples, scaled by BBR_SCALE (1/16 = 6.25%) */ ++static const u32 bbr_ecn_alpha_gain = BBR_UNIT * 1 / 16; ++ ++/* The initial value for ecn_alpha; 1.0 allows a flow to respond quickly ++ * to congestion if the bottleneck is congested when the flow starts up. ++ */ ++static const u32 bbr_ecn_alpha_init = BBR_UNIT; ++ ++/* On ECN, cut inflight_lo to (1 - ecn_factor * ecn_alpha) scaled by BBR_SCALE. ++ * No ECN based bounding when 0. ++ */ ++static const u32 bbr_ecn_factor = BBR_UNIT * 1 / 3; /* 1/3 = 33% */ ++ ++/* Estimate bw probing has gone too far if CE ratio exceeds this threshold. ++ * Scaled by BBR_SCALE. Disabled when 0. ++ */ ++static const u32 bbr_ecn_thresh = BBR_UNIT * 1 / 2; /* 1/2 = 50% */ ++ ++/* If non-zero, if in a cycle with no losses but some ECN marks, after ECN ++ * clears then make the first round's increment to inflight_hi the following ++ * fraction of inflight_hi. ++ */ ++static const u32 bbr_ecn_reprobe_gain = BBR_UNIT * 1 / 2; ++ ++/* Estimate bw probing has gone too far if loss rate exceeds this level. */ ++static const u32 bbr_loss_thresh = BBR_UNIT * 2 / 100; /* 2% loss */ ++ ++/* Slow down for a packet loss recovered by TLP? */ ++static const bool bbr_loss_probe_recovery = true; ++ ++/* Exit STARTUP if number of loss marking events in a Recovery round is >= N, ++ * and loss rate is higher than bbr_loss_thresh. ++ * Disabled if 0. ++ */ ++static const u32 bbr_full_loss_cnt = 6; ++ ++/* Exit STARTUP if number of round trips with ECN mark rate above ecn_thresh ++ * meets this count. ++ */ ++static const u32 bbr_full_ecn_cnt = 2; ++ ++/* Fraction of unutilized headroom to try to leave in path upon high loss. */ ++static const u32 bbr_inflight_headroom = BBR_UNIT * 15 / 100; ++ ++/* How much do we increase cwnd_gain when probing for bandwidth in ++ * BBR_BW_PROBE_UP? This specifies the increment in units of ++ * BBR_UNIT/4. The default is 1, meaning 0.25. ++ * The min value is 0 (meaning 0.0); max is 3 (meaning 0.75). ++ */ ++static const u32 bbr_bw_probe_cwnd_gain = 1; ++ ++/* Max number of packet-timed rounds to wait before probing for bandwidth. If ++ * we want to tolerate 1% random loss per round, and not have this cut our ++ * inflight too much, we must probe for bw periodically on roughly this scale. ++ * If low, limits Reno/CUBIC coexistence; if high, limits loss tolerance. ++ * We aim to be fair with Reno/CUBIC up to a BDP of at least: ++ * BDP = 25Mbps * .030sec /(1514bytes) = 61.9 packets ++ */ ++static const u32 bbr_bw_probe_max_rounds = 63; ++ ++/* Max amount of randomness to inject in round counting for Reno-coexistence. ++ */ ++static const u32 bbr_bw_probe_rand_rounds = 2; ++ ++/* Use BBR-native probe time scale starting at this many usec. ++ * We aim to be fair with Reno/CUBIC up to an inter-loss time epoch of at least: ++ * BDP*RTT = 25Mbps * .030sec /(1514bytes) * 0.030sec = 1.9 secs ++ */ ++static const u32 bbr_bw_probe_base_us = 2 * USEC_PER_SEC; /* 2 secs */ ++ ++/* Use BBR-native probes spread over this many usec: */ ++static const u32 bbr_bw_probe_rand_us = 1 * USEC_PER_SEC; /* 1 secs */ ++ ++/* Use fast path if app-limited, no loss/ECN, and target cwnd was reached? */ ++static const bool bbr_fast_path = true; ++ ++/* Use fast ack mode? */ ++static const bool bbr_fast_ack_mode = true; ++ ++static u32 bbr_max_bw(const struct sock *sk); ++static u32 bbr_bw(const struct sock *sk); ++static void bbr_exit_probe_rtt(struct sock *sk); ++static void bbr_reset_congestion_signals(struct sock *sk); ++static void bbr_run_loss_probe_recovery(struct sock *sk); ++ + static void bbr_check_probe_rtt_done(struct sock *sk); + ++/* This connection can use ECN if both endpoints have signaled ECN support in ++ * the handshake and the per-route settings indicated this is a ++ * shallow-threshold ECN environment, meaning both: ++ * (a) ECN CE marks indicate low-latency/shallow-threshold congestion, and ++ * (b) TCP endpoints provide precise ACKs that only ACK data segments ++ * with consistent ECN CE status ++ */ ++static bool bbr_can_use_ecn(const struct sock *sk) ++{ ++ return (tcp_sk(sk)->ecn_flags & TCP_ECN_OK) && ++ (tcp_sk(sk)->ecn_flags & TCP_ECN_LOW); ++} ++ + /* Do we estimate that STARTUP filled the pipe? */ + static bool bbr_full_bw_reached(const struct sock *sk) + { +@@ -214,17 +382,17 @@ + /* Return the windowed max recent bandwidth sample, in pkts/uS << BW_SCALE. */ + static u32 bbr_max_bw(const struct sock *sk) + { +- struct bbr *bbr = inet_csk_ca(sk); ++ const struct bbr *bbr = inet_csk_ca(sk); + +- return minmax_get(&bbr->bw); ++ return max(bbr->bw_hi[0], bbr->bw_hi[1]); + } + + /* Return the estimated bandwidth of the path, in pkts/uS << BW_SCALE. */ + static u32 bbr_bw(const struct sock *sk) + { +- struct bbr *bbr = inet_csk_ca(sk); ++ const struct bbr *bbr = inet_csk_ca(sk); + +- return bbr->lt_use_bw ? bbr->lt_bw : bbr_max_bw(sk); ++ return min(bbr_max_bw(sk), bbr->bw_lo); + } + + /* Return maximum extra acked in past k-2k round trips, +@@ -241,15 +409,23 @@ + * The order here is chosen carefully to avoid overflow of u64. This should + * work for input rates of up to 2.9Tbit/sec and gain of 2.89x. + */ +-static u64 bbr_rate_bytes_per_sec(struct sock *sk, u64 rate, int gain) ++static u64 bbr_rate_bytes_per_sec(struct sock *sk, u64 rate, int gain, ++ int margin) + { + unsigned int mss = tcp_sk(sk)->mss_cache; + + rate *= mss; + rate *= gain; + rate >>= BBR_SCALE; +- rate *= USEC_PER_SEC / 100 * (100 - bbr_pacing_margin_percent); +- return rate >> BW_SCALE; ++ rate *= USEC_PER_SEC / 100 * (100 - margin); ++ rate >>= BW_SCALE; ++ rate = max(rate, 1ULL); ++ return rate; ++} ++ ++static u64 bbr_bw_bytes_per_sec(struct sock *sk, u64 rate) ++{ ++ return bbr_rate_bytes_per_sec(sk, rate, BBR_UNIT, 0); + } + + /* Convert a BBR bw and gain factor to a pacing rate in bytes per second. */ +@@ -257,12 +433,13 @@ + { + u64 rate = bw; + +- rate = bbr_rate_bytes_per_sec(sk, rate, gain); ++ rate = bbr_rate_bytes_per_sec(sk, rate, gain, ++ bbr_pacing_margin_percent); + rate = min_t(u64, rate, sk->sk_max_pacing_rate); + return rate; + } + +-/* Initialize pacing rate to: high_gain * init_cwnd / RTT. */ ++/* Initialize pacing rate to: startup_pacing_gain * init_cwnd / RTT. */ + static void bbr_init_pacing_rate_from_rtt(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); +@@ -278,7 +455,8 @@ + } + bw = (u64)tcp_snd_cwnd(tp) * BW_UNIT; + do_div(bw, rtt_us); +- sk->sk_pacing_rate = bbr_bw_to_pacing_rate(sk, bw, bbr_high_gain); ++ sk->sk_pacing_rate = ++ bbr_bw_to_pacing_rate(sk, bw, bbr_param(sk, startup_pacing_gain)); + } + + /* Pace using current bw estimate and a gain factor. */ +@@ -294,26 +472,48 @@ + sk->sk_pacing_rate = rate; + } + +-/* override sysctl_tcp_min_tso_segs */ +-__bpf_kfunc static u32 bbr_min_tso_segs(struct sock *sk) ++/* Return the number of segments BBR would like in a TSO/GSO skb, given a ++ * particular max gso size as a constraint. TODO: make this simpler and more ++ * consistent by switching bbr to just call tcp_tso_autosize(). ++ */ ++static u32 bbr_tso_segs_generic(struct sock *sk, unsigned int mss_now, ++ u32 gso_max_size) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 segs, r; ++ u64 bytes; ++ ++ /* Budget a TSO/GSO burst size allowance based on bw (pacing_rate). */ ++ bytes = sk->sk_pacing_rate >> sk->sk_pacing_shift; ++ ++ /* Budget a TSO/GSO burst size allowance based on min_rtt. For every ++ * K = 2^tso_rtt_shift microseconds of min_rtt, halve the burst. ++ * The min_rtt-based burst allowance is: 64 KBytes / 2^(min_rtt/K) ++ */ ++ if (bbr_param(sk, tso_rtt_shift)) { ++ r = bbr->min_rtt_us >> bbr_param(sk, tso_rtt_shift); ++ if (r < BITS_PER_TYPE(u32)) /* prevent undefined behavior */ ++ bytes += GSO_LEGACY_MAX_SIZE >> r; ++ } ++ ++ bytes = min_t(u32, bytes, gso_max_size - 1 - MAX_TCP_HEADER); ++ segs = max_t(u32, bytes / mss_now, ++ sock_net(sk)->ipv4.sysctl_tcp_min_tso_segs); ++ return segs; ++} ++ ++/* Custom tcp_tso_autosize() for BBR, used at transmit time to cap skb size. */ ++__bpf_kfunc static u32 bbr_tso_segs(struct sock *sk, unsigned int mss_now) + { +- return sk->sk_pacing_rate < (bbr_min_tso_rate >> 3) ? 1 : 2; ++ return bbr_tso_segs_generic(sk, mss_now, sk->sk_gso_max_size); + } + ++/* Like bbr_tso_segs(), using mss_cache, ignoring driver's sk_gso_max_size. */ + static u32 bbr_tso_segs_goal(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); +- u32 segs, bytes; +- +- /* Sort of tcp_tso_autosize() but ignoring +- * driver provided sk_gso_max_size. +- */ +- bytes = min_t(unsigned long, +- sk->sk_pacing_rate >> READ_ONCE(sk->sk_pacing_shift), +- GSO_LEGACY_MAX_SIZE - 1 - MAX_TCP_HEADER); +- segs = max_t(u32, bytes / tp->mss_cache, bbr_min_tso_segs(sk)); + +- return min(segs, 0x7FU); ++ return bbr_tso_segs_generic(sk, tp->mss_cache, GSO_LEGACY_MAX_SIZE); + } + + /* Save "last known good" cwnd so we can restore it after losses or PROBE_RTT */ +@@ -333,7 +533,9 @@ + struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); + +- if (event == CA_EVENT_TX_START && tp->app_limited) { ++ if (event == CA_EVENT_TX_START) { ++ if (!tp->app_limited) ++ return; + bbr->idle_restart = 1; + bbr->ack_epoch_mstamp = tp->tcp_mstamp; + bbr->ack_epoch_acked = 0; +@@ -344,6 +546,16 @@ + bbr_set_pacing_rate(sk, bbr_bw(sk), BBR_UNIT); + else if (bbr->mode == BBR_PROBE_RTT) + bbr_check_probe_rtt_done(sk); ++ } else if ((event == CA_EVENT_ECN_IS_CE || ++ event == CA_EVENT_ECN_NO_CE) && ++ bbr_can_use_ecn(sk) && ++ bbr_param(sk, precise_ece_ack)) { ++ u32 state = bbr->ce_state; ++ dctcp_ece_ack_update(sk, event, &bbr->prior_rcv_nxt, &state); ++ bbr->ce_state = state; ++ } else if (event == CA_EVENT_TLP_RECOVERY && ++ bbr_param(sk, loss_probe_recovery)) { ++ bbr_run_loss_probe_recovery(sk); + } + } + +@@ -366,10 +578,10 @@ + * default. This should only happen when the connection is not using TCP + * timestamps and has retransmitted all of the SYN/SYNACK/data packets + * ACKed so far. In this case, an RTO can cut cwnd to 1, in which +- * case we need to slow-start up toward something safe: TCP_INIT_CWND. ++ * case we need to slow-start up toward something safe: initial cwnd. + */ + if (unlikely(bbr->min_rtt_us == ~0U)) /* no valid RTT samples yet? */ +- return TCP_INIT_CWND; /* be safe: cap at default initial cwnd*/ ++ return bbr->init_cwnd; /* be safe: cap at initial cwnd */ + + w = (u64)bw * bbr->min_rtt_us; + +@@ -386,23 +598,23 @@ + * - one skb in sending host Qdisc, + * - one skb in sending host TSO/GSO engine + * - one skb being received by receiver host LRO/GRO/delayed-ACK engine +- * Don't worry, at low rates (bbr_min_tso_rate) this won't bloat cwnd because +- * in such cases tso_segs_goal is 1. The minimum cwnd is 4 packets, ++ * Don't worry, at low rates this won't bloat cwnd because ++ * in such cases tso_segs_goal is small. The minimum cwnd is 4 packets, + * which allows 2 outstanding 2-packet sequences, to try to keep pipe + * full even with ACK-every-other-packet delayed ACKs. + */ + static u32 bbr_quantization_budget(struct sock *sk, u32 cwnd) + { + struct bbr *bbr = inet_csk_ca(sk); ++ u32 tso_segs_goal; + +- /* Allow enough full-sized skbs in flight to utilize end systems. */ +- cwnd += 3 * bbr_tso_segs_goal(sk); +- +- /* Reduce delayed ACKs by rounding up cwnd to the next even number. */ +- cwnd = (cwnd + 1) & ~1U; ++ tso_segs_goal = 3 * bbr_tso_segs_goal(sk); + ++ /* Allow enough full-sized skbs in flight to utilize end systems. */ ++ cwnd = max_t(u32, cwnd, tso_segs_goal); ++ cwnd = max_t(u32, cwnd, bbr_param(sk, cwnd_min_target)); + /* Ensure gain cycling gets inflight above BDP even for small BDPs. */ +- if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == 0) ++ if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP) + cwnd += 2; + + return cwnd; +@@ -457,10 +669,10 @@ + { + u32 max_aggr_cwnd, aggr_cwnd = 0; + +- if (bbr_extra_acked_gain && bbr_full_bw_reached(sk)) { ++ if (bbr_param(sk, extra_acked_gain)) { + max_aggr_cwnd = ((u64)bbr_bw(sk) * bbr_extra_acked_max_us) + / BW_UNIT; +- aggr_cwnd = (bbr_extra_acked_gain * bbr_extra_acked(sk)) ++ aggr_cwnd = (bbr_param(sk, extra_acked_gain) * bbr_extra_acked(sk)) + >> BBR_SCALE; + aggr_cwnd = min(aggr_cwnd, max_aggr_cwnd); + } +@@ -468,66 +680,27 @@ + return aggr_cwnd; + } + +-/* An optimization in BBR to reduce losses: On the first round of recovery, we +- * follow the packet conservation principle: send P packets per P packets acked. +- * After that, we slow-start and send at most 2*P packets per P packets acked. +- * After recovery finishes, or upon undo, we restore the cwnd we had when +- * recovery started (capped by the target cwnd based on estimated BDP). +- * +- * TODO(ycheng/ncardwell): implement a rate-based approach. +- */ +-static bool bbr_set_cwnd_to_recover_or_restore( +- struct sock *sk, const struct rate_sample *rs, u32 acked, u32 *new_cwnd) ++/* Returns the cwnd for PROBE_RTT mode. */ ++static u32 bbr_probe_rtt_cwnd(struct sock *sk) + { +- struct tcp_sock *tp = tcp_sk(sk); +- struct bbr *bbr = inet_csk_ca(sk); +- u8 prev_state = bbr->prev_ca_state, state = inet_csk(sk)->icsk_ca_state; +- u32 cwnd = tcp_snd_cwnd(tp); +- +- /* An ACK for P pkts should release at most 2*P packets. We do this +- * in two steps. First, here we deduct the number of lost packets. +- * Then, in bbr_set_cwnd() we slow start up toward the target cwnd. +- */ +- if (rs->losses > 0) +- cwnd = max_t(s32, cwnd - rs->losses, 1); +- +- if (state == TCP_CA_Recovery && prev_state != TCP_CA_Recovery) { +- /* Starting 1st round of Recovery, so do packet conservation. */ +- bbr->packet_conservation = 1; +- bbr->next_rtt_delivered = tp->delivered; /* start round now */ +- /* Cut unused cwnd from app behavior, TSQ, or TSO deferral: */ +- cwnd = tcp_packets_in_flight(tp) + acked; +- } else if (prev_state >= TCP_CA_Recovery && state < TCP_CA_Recovery) { +- /* Exiting loss recovery; restore cwnd saved before recovery. */ +- cwnd = max(cwnd, bbr->prior_cwnd); +- bbr->packet_conservation = 0; +- } +- bbr->prev_ca_state = state; +- +- if (bbr->packet_conservation) { +- *new_cwnd = max(cwnd, tcp_packets_in_flight(tp) + acked); +- return true; /* yes, using packet conservation */ +- } +- *new_cwnd = cwnd; +- return false; ++ return max_t(u32, bbr_param(sk, cwnd_min_target), ++ bbr_bdp(sk, bbr_bw(sk), bbr_param(sk, probe_rtt_cwnd_gain))); + } + + /* Slow-start up toward target cwnd (if bw estimate is growing, or packet loss + * has drawn us down below target), or snap down to target if we're above it. + */ + static void bbr_set_cwnd(struct sock *sk, const struct rate_sample *rs, +- u32 acked, u32 bw, int gain) ++ u32 acked, u32 bw, int gain, u32 cwnd, ++ struct bbr_context *ctx) + { + struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); +- u32 cwnd = tcp_snd_cwnd(tp), target_cwnd = 0; ++ u32 target_cwnd = 0; + + if (!acked) + goto done; /* no packet fully ACKed; just apply caps */ + +- if (bbr_set_cwnd_to_recover_or_restore(sk, rs, acked, &cwnd)) +- goto done; +- + target_cwnd = bbr_bdp(sk, bw, gain); + + /* Increment the cwnd to account for excess ACKed data that seems +@@ -536,74 +709,26 @@ + target_cwnd += bbr_ack_aggregation_cwnd(sk); + target_cwnd = bbr_quantization_budget(sk, target_cwnd); + +- /* If we're below target cwnd, slow start cwnd toward target cwnd. */ +- if (bbr_full_bw_reached(sk)) /* only cut cwnd if we filled the pipe */ +- cwnd = min(cwnd + acked, target_cwnd); +- else if (cwnd < target_cwnd || tp->delivered < TCP_INIT_CWND) +- cwnd = cwnd + acked; +- cwnd = max(cwnd, bbr_cwnd_min_target); ++ /* Update cwnd and enable fast path if cwnd reaches target_cwnd. */ ++ bbr->try_fast_path = 0; ++ if (bbr_full_bw_reached(sk)) { /* only cut cwnd if we filled the pipe */ ++ cwnd += acked; ++ if (cwnd >= target_cwnd) { ++ cwnd = target_cwnd; ++ bbr->try_fast_path = 1; ++ } ++ } else if (cwnd < target_cwnd || cwnd < 2 * bbr->init_cwnd) { ++ cwnd += acked; ++ } else { ++ bbr->try_fast_path = 1; ++ } + ++ cwnd = max_t(u32, cwnd, bbr_param(sk, cwnd_min_target)); + done: +- tcp_snd_cwnd_set(tp, min(cwnd, tp->snd_cwnd_clamp)); /* apply global cap */ ++ tcp_snd_cwnd_set(tp, min(cwnd, tp->snd_cwnd_clamp)); /* global cap */ + if (bbr->mode == BBR_PROBE_RTT) /* drain queue, refresh min_rtt */ +- tcp_snd_cwnd_set(tp, min(tcp_snd_cwnd(tp), bbr_cwnd_min_target)); +-} +- +-/* End cycle phase if it's time and/or we hit the phase's in-flight target. */ +-static bool bbr_is_next_cycle_phase(struct sock *sk, +- const struct rate_sample *rs) +-{ +- struct tcp_sock *tp = tcp_sk(sk); +- struct bbr *bbr = inet_csk_ca(sk); +- bool is_full_length = +- tcp_stamp_us_delta(tp->delivered_mstamp, bbr->cycle_mstamp) > +- bbr->min_rtt_us; +- u32 inflight, bw; +- +- /* The pacing_gain of 1.0 paces at the estimated bw to try to fully +- * use the pipe without increasing the queue. +- */ +- if (bbr->pacing_gain == BBR_UNIT) +- return is_full_length; /* just use wall clock time */ +- +- inflight = bbr_packets_in_net_at_edt(sk, rs->prior_in_flight); +- bw = bbr_max_bw(sk); +- +- /* A pacing_gain > 1.0 probes for bw by trying to raise inflight to at +- * least pacing_gain*BDP; this may take more than min_rtt if min_rtt is +- * small (e.g. on a LAN). We do not persist if packets are lost, since +- * a path with small buffers may not hold that much. +- */ +- if (bbr->pacing_gain > BBR_UNIT) +- return is_full_length && +- (rs->losses || /* perhaps pacing_gain*BDP won't fit */ +- inflight >= bbr_inflight(sk, bw, bbr->pacing_gain)); +- +- /* A pacing_gain < 1.0 tries to drain extra queue we added if bw +- * probing didn't find more bw. If inflight falls to match BDP then we +- * estimate queue is drained; persisting would underutilize the pipe. +- */ +- return is_full_length || +- inflight <= bbr_inflight(sk, bw, BBR_UNIT); +-} +- +-static void bbr_advance_cycle_phase(struct sock *sk) +-{ +- struct tcp_sock *tp = tcp_sk(sk); +- struct bbr *bbr = inet_csk_ca(sk); +- +- bbr->cycle_idx = (bbr->cycle_idx + 1) & (CYCLE_LEN - 1); +- bbr->cycle_mstamp = tp->delivered_mstamp; +-} +- +-/* Gain cycling: cycle pacing gain to converge to fair share of available bw. */ +-static void bbr_update_cycle_phase(struct sock *sk, +- const struct rate_sample *rs) +-{ +- struct bbr *bbr = inet_csk_ca(sk); +- +- if (bbr->mode == BBR_PROBE_BW && bbr_is_next_cycle_phase(sk, rs)) +- bbr_advance_cycle_phase(sk); ++ tcp_snd_cwnd_set(tp, min_t(u32, tcp_snd_cwnd(tp), ++ bbr_probe_rtt_cwnd(sk))); + } + + static void bbr_reset_startup_mode(struct sock *sk) +@@ -613,191 +738,49 @@ + bbr->mode = BBR_STARTUP; + } + +-static void bbr_reset_probe_bw_mode(struct sock *sk) +-{ +- struct bbr *bbr = inet_csk_ca(sk); +- +- bbr->mode = BBR_PROBE_BW; +- bbr->cycle_idx = CYCLE_LEN - 1 - get_random_u32_below(bbr_cycle_rand); +- bbr_advance_cycle_phase(sk); /* flip to next phase of gain cycle */ +-} +- +-static void bbr_reset_mode(struct sock *sk) +-{ +- if (!bbr_full_bw_reached(sk)) +- bbr_reset_startup_mode(sk); +- else +- bbr_reset_probe_bw_mode(sk); +-} +- +-/* Start a new long-term sampling interval. */ +-static void bbr_reset_lt_bw_sampling_interval(struct sock *sk) +-{ +- struct tcp_sock *tp = tcp_sk(sk); +- struct bbr *bbr = inet_csk_ca(sk); +- +- bbr->lt_last_stamp = div_u64(tp->delivered_mstamp, USEC_PER_MSEC); +- bbr->lt_last_delivered = tp->delivered; +- bbr->lt_last_lost = tp->lost; +- bbr->lt_rtt_cnt = 0; +-} +- +-/* Completely reset long-term bandwidth sampling. */ +-static void bbr_reset_lt_bw_sampling(struct sock *sk) +-{ +- struct bbr *bbr = inet_csk_ca(sk); +- +- bbr->lt_bw = 0; +- bbr->lt_use_bw = 0; +- bbr->lt_is_sampling = false; +- bbr_reset_lt_bw_sampling_interval(sk); +-} +- +-/* Long-term bw sampling interval is done. Estimate whether we're policed. */ +-static void bbr_lt_bw_interval_done(struct sock *sk, u32 bw) +-{ +- struct bbr *bbr = inet_csk_ca(sk); +- u32 diff; +- +- if (bbr->lt_bw) { /* do we have bw from a previous interval? */ +- /* Is new bw close to the lt_bw from the previous interval? */ +- diff = abs(bw - bbr->lt_bw); +- if ((diff * BBR_UNIT <= bbr_lt_bw_ratio * bbr->lt_bw) || +- (bbr_rate_bytes_per_sec(sk, diff, BBR_UNIT) <= +- bbr_lt_bw_diff)) { +- /* All criteria are met; estimate we're policed. */ +- bbr->lt_bw = (bw + bbr->lt_bw) >> 1; /* avg 2 intvls */ +- bbr->lt_use_bw = 1; +- bbr->pacing_gain = BBR_UNIT; /* try to avoid drops */ +- bbr->lt_rtt_cnt = 0; +- return; +- } +- } +- bbr->lt_bw = bw; +- bbr_reset_lt_bw_sampling_interval(sk); +-} +- +-/* Token-bucket traffic policers are common (see "An Internet-Wide Analysis of +- * Traffic Policing", SIGCOMM 2016). BBR detects token-bucket policers and +- * explicitly models their policed rate, to reduce unnecessary losses. We +- * estimate that we're policed if we see 2 consecutive sampling intervals with +- * consistent throughput and high packet loss. If we think we're being policed, +- * set lt_bw to the "long-term" average delivery rate from those 2 intervals. ++/* See if we have reached next round trip. Upon start of the new round, ++ * returns packets delivered since previous round start plus this ACK. + */ +-static void bbr_lt_bw_sampling(struct sock *sk, const struct rate_sample *rs) ++static u32 bbr_update_round_start(struct sock *sk, ++ const struct rate_sample *rs, struct bbr_context *ctx) + { + struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); +- u32 lost, delivered; +- u64 bw; +- u32 t; +- +- if (bbr->lt_use_bw) { /* already using long-term rate, lt_bw? */ +- if (bbr->mode == BBR_PROBE_BW && bbr->round_start && +- ++bbr->lt_rtt_cnt >= bbr_lt_bw_max_rtts) { +- bbr_reset_lt_bw_sampling(sk); /* stop using lt_bw */ +- bbr_reset_probe_bw_mode(sk); /* restart gain cycling */ +- } +- return; +- } +- +- /* Wait for the first loss before sampling, to let the policer exhaust +- * its tokens and estimate the steady-state rate allowed by the policer. +- * Starting samples earlier includes bursts that over-estimate the bw. +- */ +- if (!bbr->lt_is_sampling) { +- if (!rs->losses) +- return; +- bbr_reset_lt_bw_sampling_interval(sk); +- bbr->lt_is_sampling = true; +- } +- +- /* To avoid underestimates, reset sampling if we run out of data. */ +- if (rs->is_app_limited) { +- bbr_reset_lt_bw_sampling(sk); +- return; +- } +- +- if (bbr->round_start) +- bbr->lt_rtt_cnt++; /* count round trips in this interval */ +- if (bbr->lt_rtt_cnt < bbr_lt_intvl_min_rtts) +- return; /* sampling interval needs to be longer */ +- if (bbr->lt_rtt_cnt > 4 * bbr_lt_intvl_min_rtts) { +- bbr_reset_lt_bw_sampling(sk); /* interval is too long */ +- return; +- } +- +- /* End sampling interval when a packet is lost, so we estimate the +- * policer tokens were exhausted. Stopping the sampling before the +- * tokens are exhausted under-estimates the policed rate. +- */ +- if (!rs->losses) +- return; +- +- /* Calculate packets lost and delivered in sampling interval. */ +- lost = tp->lost - bbr->lt_last_lost; +- delivered = tp->delivered - bbr->lt_last_delivered; +- /* Is loss rate (lost/delivered) >= lt_loss_thresh? If not, wait. */ +- if (!delivered || (lost << BBR_SCALE) < bbr_lt_loss_thresh * delivered) +- return; +- +- /* Find average delivery rate in this sampling interval. */ +- t = div_u64(tp->delivered_mstamp, USEC_PER_MSEC) - bbr->lt_last_stamp; +- if ((s32)t < 1) +- return; /* interval is less than one ms, so wait */ +- /* Check if can multiply without overflow */ +- if (t >= ~0U / USEC_PER_MSEC) { +- bbr_reset_lt_bw_sampling(sk); /* interval too long; reset */ +- return; +- } +- t *= USEC_PER_MSEC; +- bw = (u64)delivered * BW_UNIT; +- do_div(bw, t); +- bbr_lt_bw_interval_done(sk, bw); +-} +- +-/* Estimate the bandwidth based on how fast packets are delivered */ +-static void bbr_update_bw(struct sock *sk, const struct rate_sample *rs) +-{ +- struct tcp_sock *tp = tcp_sk(sk); +- struct bbr *bbr = inet_csk_ca(sk); +- u64 bw; ++ u32 round_delivered = 0; + + bbr->round_start = 0; +- if (rs->delivered < 0 || rs->interval_us <= 0) +- return; /* Not a valid observation */ + + /* See if we've reached the next RTT */ +- if (!before(rs->prior_delivered, bbr->next_rtt_delivered)) { ++ if (rs->interval_us > 0 && ++ !before(rs->prior_delivered, bbr->next_rtt_delivered)) { ++ round_delivered = tp->delivered - bbr->next_rtt_delivered; + bbr->next_rtt_delivered = tp->delivered; +- bbr->rtt_cnt++; + bbr->round_start = 1; +- bbr->packet_conservation = 0; + } ++ return round_delivered; ++} + +- bbr_lt_bw_sampling(sk, rs); ++/* Calculate the bandwidth based on how fast packets are delivered */ ++static void bbr_calculate_bw_sample(struct sock *sk, ++ const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ u64 bw = 0; + + /* Divide delivered by the interval to find a (lower bound) bottleneck + * bandwidth sample. Delivered is in packets and interval_us in uS and + * ratio will be <<1 for most connections. So delivered is first scaled. ++ * Round up to allow growth at low rates, even with integer division. + */ +- bw = div64_long((u64)rs->delivered * BW_UNIT, rs->interval_us); ++ if (rs->interval_us > 0) { ++ if (WARN_ONCE(rs->delivered < 0, ++ "negative delivered: %d interval_us: %ld\n", ++ rs->delivered, rs->interval_us)) ++ return; + +- /* If this sample is application-limited, it is likely to have a very +- * low delivered count that represents application behavior rather than +- * the available network rate. Such a sample could drag down estimated +- * bw, causing needless slow-down. Thus, to continue to send at the +- * last measured network rate, we filter out app-limited samples unless +- * they describe the path bw at least as well as our bw model. +- * +- * So the goal during app-limited phase is to proceed with the best +- * network rate no matter how long. We automatically leave this +- * phase when app writes faster than the network can deliver :) +- */ +- if (!rs->is_app_limited || bw >= bbr_max_bw(sk)) { +- /* Incorporate new sample into our max bw filter. */ +- minmax_running_max(&bbr->bw, bbr_bw_rtts, bbr->rtt_cnt, bw); ++ bw = DIV_ROUND_UP_ULL((u64)rs->delivered * BW_UNIT, rs->interval_us); + } ++ ++ ctx->sample_bw = bw; + } + + /* Estimates the windowed max degree of ack aggregation. +@@ -811,7 +794,7 @@ + * + * Max extra_acked is clamped by cwnd and bw * bbr_extra_acked_max_us (100 ms). + * Max filter is an approximate sliding window of 5-10 (packet timed) round +- * trips. ++ * trips for non-startup phase, and 1-2 round trips for startup. + */ + static void bbr_update_ack_aggregation(struct sock *sk, + const struct rate_sample *rs) +@@ -819,15 +802,19 @@ + u32 epoch_us, expected_acked, extra_acked; + struct bbr *bbr = inet_csk_ca(sk); + struct tcp_sock *tp = tcp_sk(sk); ++ u32 extra_acked_win_rtts_thresh = bbr_param(sk, extra_acked_win_rtts); + +- if (!bbr_extra_acked_gain || rs->acked_sacked <= 0 || ++ if (!bbr_param(sk, extra_acked_gain) || rs->acked_sacked <= 0 || + rs->delivered < 0 || rs->interval_us <= 0) + return; + + if (bbr->round_start) { + bbr->extra_acked_win_rtts = min(0x1F, + bbr->extra_acked_win_rtts + 1); +- if (bbr->extra_acked_win_rtts >= bbr_extra_acked_win_rtts) { ++ if (!bbr_full_bw_reached(sk)) ++ extra_acked_win_rtts_thresh = 1; ++ if (bbr->extra_acked_win_rtts >= ++ extra_acked_win_rtts_thresh) { + bbr->extra_acked_win_rtts = 0; + bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? + 0 : 1; +@@ -861,49 +848,6 @@ + bbr->extra_acked[bbr->extra_acked_win_idx] = extra_acked; + } + +-/* Estimate when the pipe is full, using the change in delivery rate: BBR +- * estimates that STARTUP filled the pipe if the estimated bw hasn't changed by +- * at least bbr_full_bw_thresh (25%) after bbr_full_bw_cnt (3) non-app-limited +- * rounds. Why 3 rounds: 1: rwin autotuning grows the rwin, 2: we fill the +- * higher rwin, 3: we get higher delivery rate samples. Or transient +- * cross-traffic or radio noise can go away. CUBIC Hystart shares a similar +- * design goal, but uses delay and inter-ACK spacing instead of bandwidth. +- */ +-static void bbr_check_full_bw_reached(struct sock *sk, +- const struct rate_sample *rs) +-{ +- struct bbr *bbr = inet_csk_ca(sk); +- u32 bw_thresh; +- +- if (bbr_full_bw_reached(sk) || !bbr->round_start || rs->is_app_limited) +- return; +- +- bw_thresh = (u64)bbr->full_bw * bbr_full_bw_thresh >> BBR_SCALE; +- if (bbr_max_bw(sk) >= bw_thresh) { +- bbr->full_bw = bbr_max_bw(sk); +- bbr->full_bw_cnt = 0; +- return; +- } +- ++bbr->full_bw_cnt; +- bbr->full_bw_reached = bbr->full_bw_cnt >= bbr_full_bw_cnt; +-} +- +-/* If pipe is probably full, drain the queue and then enter steady-state. */ +-static void bbr_check_drain(struct sock *sk, const struct rate_sample *rs) +-{ +- struct bbr *bbr = inet_csk_ca(sk); +- +- if (bbr->mode == BBR_STARTUP && bbr_full_bw_reached(sk)) { +- bbr->mode = BBR_DRAIN; /* drain queue we created */ +- tcp_sk(sk)->snd_ssthresh = +- bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT); +- } /* fall through to check if in-flight is already small: */ +- if (bbr->mode == BBR_DRAIN && +- bbr_packets_in_net_at_edt(sk, tcp_packets_in_flight(tcp_sk(sk))) <= +- bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT)) +- bbr_reset_probe_bw_mode(sk); /* we estimate queue is drained */ +-} +- + static void bbr_check_probe_rtt_done(struct sock *sk) + { + struct tcp_sock *tp = tcp_sk(sk); +@@ -913,9 +857,9 @@ + after(tcp_jiffies32, bbr->probe_rtt_done_stamp))) + return; + +- bbr->min_rtt_stamp = tcp_jiffies32; /* wait a while until PROBE_RTT */ ++ bbr->probe_rtt_min_stamp = tcp_jiffies32; /* schedule next PROBE_RTT */ + tcp_snd_cwnd_set(tp, max(tcp_snd_cwnd(tp), bbr->prior_cwnd)); +- bbr_reset_mode(sk); ++ bbr_exit_probe_rtt(sk); + } + + /* The goal of PROBE_RTT mode is to have BBR flows cooperatively and +@@ -941,23 +885,35 @@ + { + struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); +- bool filter_expired; ++ bool probe_rtt_expired, min_rtt_expired; ++ u32 expire; + +- /* Track min RTT seen in the min_rtt_win_sec filter window: */ +- filter_expired = after(tcp_jiffies32, +- bbr->min_rtt_stamp + bbr_min_rtt_win_sec * HZ); ++ /* Track min RTT in probe_rtt_win_ms to time next PROBE_RTT state. */ ++ expire = bbr->probe_rtt_min_stamp + ++ msecs_to_jiffies(bbr_param(sk, probe_rtt_win_ms)); ++ probe_rtt_expired = after(tcp_jiffies32, expire); + if (rs->rtt_us >= 0 && +- (rs->rtt_us < bbr->min_rtt_us || +- (filter_expired && !rs->is_ack_delayed))) { +- bbr->min_rtt_us = rs->rtt_us; +- bbr->min_rtt_stamp = tcp_jiffies32; ++ (rs->rtt_us < bbr->probe_rtt_min_us || ++ (probe_rtt_expired && !rs->is_ack_delayed))) { ++ bbr->probe_rtt_min_us = rs->rtt_us; ++ bbr->probe_rtt_min_stamp = tcp_jiffies32; ++ } ++ /* Track min RTT seen in the min_rtt_win_sec filter window: */ ++ expire = bbr->min_rtt_stamp + bbr_param(sk, min_rtt_win_sec) * HZ; ++ min_rtt_expired = after(tcp_jiffies32, expire); ++ if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || ++ min_rtt_expired) { ++ bbr->min_rtt_us = bbr->probe_rtt_min_us; ++ bbr->min_rtt_stamp = bbr->probe_rtt_min_stamp; + } + +- if (bbr_probe_rtt_mode_ms > 0 && filter_expired && ++ if (bbr_param(sk, probe_rtt_mode_ms) > 0 && probe_rtt_expired && + !bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) { + bbr->mode = BBR_PROBE_RTT; /* dip, drain queue */ + bbr_save_cwnd(sk); /* note cwnd so we can restore it */ + bbr->probe_rtt_done_stamp = 0; ++ bbr->ack_phase = BBR_ACKS_PROBE_STOPPING; ++ bbr->next_rtt_delivered = tp->delivered; + } + + if (bbr->mode == BBR_PROBE_RTT) { +@@ -966,9 +922,9 @@ + (tp->delivered + tcp_packets_in_flight(tp)) ? : 1; + /* Maintain min packets in flight for max(200 ms, 1 round). */ + if (!bbr->probe_rtt_done_stamp && +- tcp_packets_in_flight(tp) <= bbr_cwnd_min_target) { ++ tcp_packets_in_flight(tp) <= bbr_probe_rtt_cwnd(sk)) { + bbr->probe_rtt_done_stamp = tcp_jiffies32 + +- msecs_to_jiffies(bbr_probe_rtt_mode_ms); ++ msecs_to_jiffies(bbr_param(sk, probe_rtt_mode_ms)); + bbr->probe_rtt_round_done = 0; + bbr->next_rtt_delivered = tp->delivered; + } else if (bbr->probe_rtt_done_stamp) { +@@ -989,18 +945,20 @@ + + switch (bbr->mode) { + case BBR_STARTUP: +- bbr->pacing_gain = bbr_high_gain; +- bbr->cwnd_gain = bbr_high_gain; ++ bbr->pacing_gain = bbr_param(sk, startup_pacing_gain); ++ bbr->cwnd_gain = bbr_param(sk, startup_cwnd_gain); + break; + case BBR_DRAIN: +- bbr->pacing_gain = bbr_drain_gain; /* slow, to drain */ +- bbr->cwnd_gain = bbr_high_gain; /* keep cwnd */ ++ bbr->pacing_gain = bbr_param(sk, drain_gain); /* slow, to drain */ ++ bbr->cwnd_gain = bbr_param(sk, startup_cwnd_gain); /* keep cwnd */ + break; + case BBR_PROBE_BW: +- bbr->pacing_gain = (bbr->lt_use_bw ? +- BBR_UNIT : +- bbr_pacing_gain[bbr->cycle_idx]); +- bbr->cwnd_gain = bbr_cwnd_gain; ++ bbr->pacing_gain = bbr_pacing_gain[bbr->cycle_idx]; ++ bbr->cwnd_gain = bbr_param(sk, cwnd_gain); ++ if (bbr_param(sk, bw_probe_cwnd_gain) && ++ bbr->cycle_idx == BBR_BW_PROBE_UP) ++ bbr->cwnd_gain += ++ BBR_UNIT * bbr_param(sk, bw_probe_cwnd_gain) / 4; + break; + case BBR_PROBE_RTT: + bbr->pacing_gain = BBR_UNIT; +@@ -1012,27 +970,1108 @@ + } + } + +-static void bbr_update_model(struct sock *sk, const struct rate_sample *rs) ++__bpf_kfunc static u32 bbr_sndbuf_expand(struct sock *sk) ++{ ++ /* Provision 3 * cwnd since BBR may slow-start even during recovery. */ ++ return 3; ++} ++ ++/* Incorporate a new bw sample into the current window of our max filter. */ ++static void bbr_take_max_bw_sample(struct sock *sk, u32 bw) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->bw_hi[1] = max(bw, bbr->bw_hi[1]); ++} ++ ++/* Keep max of last 1-2 cycles. Each PROBE_BW cycle, flip filter window. */ ++static void bbr_advance_max_bw_filter(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (!bbr->bw_hi[1]) ++ return; /* no samples in this window; remember old window */ ++ bbr->bw_hi[0] = bbr->bw_hi[1]; ++ bbr->bw_hi[1] = 0; ++} ++ ++/* Reset the estimator for reaching full bandwidth based on bw plateau. */ ++static void bbr_reset_full_bw(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->full_bw = 0; ++ bbr->full_bw_cnt = 0; ++ bbr->full_bw_now = 0; ++} ++ ++/* How much do we want in flight? Our BDP, unless congestion cut cwnd. */ ++static u32 bbr_target_inflight(struct sock *sk) ++{ ++ u32 bdp = bbr_inflight(sk, bbr_bw(sk), BBR_UNIT); ++ ++ return min(bdp, tcp_sk(sk)->snd_cwnd); ++} ++ ++static bool bbr_is_probing_bandwidth(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return (bbr->mode == BBR_STARTUP) || ++ (bbr->mode == BBR_PROBE_BW && ++ (bbr->cycle_idx == BBR_BW_PROBE_REFILL || ++ bbr->cycle_idx == BBR_BW_PROBE_UP)); ++} ++ ++/* Has the given amount of time elapsed since we marked the phase start? */ ++static bool bbr_has_elapsed_in_phase(const struct sock *sk, u32 interval_us) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ const struct bbr *bbr = inet_csk_ca(sk); ++ ++ return tcp_stamp_us_delta(tp->tcp_mstamp, ++ bbr->cycle_mstamp + interval_us) > 0; ++} ++ ++static void bbr_handle_queue_too_high_in_startup(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 bdp; /* estimated BDP in packets, with quantization budget */ ++ ++ bbr->full_bw_reached = 1; ++ ++ bdp = bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT); ++ bbr->inflight_hi = max(bdp, bbr->inflight_latest); ++} ++ ++/* Exit STARTUP upon N consecutive rounds with ECN mark rate > ecn_thresh. */ ++static void bbr_check_ecn_too_high_in_startup(struct sock *sk, u32 ce_ratio) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr_full_bw_reached(sk) || !bbr->ecn_eligible || ++ !bbr_param(sk, full_ecn_cnt) || !bbr_param(sk, ecn_thresh)) ++ return; ++ ++ if (ce_ratio >= bbr_param(sk, ecn_thresh)) ++ bbr->startup_ecn_rounds++; ++ else ++ bbr->startup_ecn_rounds = 0; ++ ++ if (bbr->startup_ecn_rounds >= bbr_param(sk, full_ecn_cnt)) { ++ bbr_handle_queue_too_high_in_startup(sk); ++ return; ++ } ++} ++ ++/* Updates ecn_alpha and returns ce_ratio. -1 if not available. */ ++static int bbr_update_ecn_alpha(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct net *net = sock_net(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ s32 delivered, delivered_ce; ++ u64 alpha, ce_ratio; ++ u32 gain; ++ bool want_ecn_alpha; ++ ++ /* See if we should use ECN sender logic for this connection. */ ++ if (!bbr->ecn_eligible && bbr_can_use_ecn(sk) && ++ bbr_param(sk, ecn_factor) && ++ (bbr->min_rtt_us <= bbr_ecn_max_rtt_us || ++ !bbr_ecn_max_rtt_us)) ++ bbr->ecn_eligible = 1; ++ ++ /* Skip updating alpha only if not ECN-eligible and PLB is disabled. */ ++ want_ecn_alpha = (bbr->ecn_eligible || ++ (bbr_can_use_ecn(sk) && ++ READ_ONCE(net->ipv4.sysctl_tcp_plb_enabled))); ++ if (!want_ecn_alpha) ++ return -1; ++ ++ delivered = tp->delivered - bbr->alpha_last_delivered; ++ delivered_ce = tp->delivered_ce - bbr->alpha_last_delivered_ce; ++ ++ if (delivered == 0 || /* avoid divide by zero */ ++ WARN_ON_ONCE(delivered < 0 || delivered_ce < 0)) /* backwards? */ ++ return -1; ++ ++ BUILD_BUG_ON(BBR_SCALE != TCP_PLB_SCALE); ++ ce_ratio = (u64)delivered_ce << BBR_SCALE; ++ do_div(ce_ratio, delivered); ++ ++ gain = bbr_param(sk, ecn_alpha_gain); ++ alpha = ((BBR_UNIT - gain) * bbr->ecn_alpha) >> BBR_SCALE; ++ alpha += (gain * ce_ratio) >> BBR_SCALE; ++ bbr->ecn_alpha = min_t(u32, alpha, BBR_UNIT); ++ ++ bbr->alpha_last_delivered = tp->delivered; ++ bbr->alpha_last_delivered_ce = tp->delivered_ce; ++ ++ bbr_check_ecn_too_high_in_startup(sk, ce_ratio); ++ return (int)ce_ratio; ++} ++ ++/* Protective Load Balancing (PLB). PLB rehashes outgoing data (to a new IPv6 ++ * flow label) if it encounters sustained congestion in the form of ECN marks. ++ */ ++static void bbr_plb(struct sock *sk, const struct rate_sample *rs, int ce_ratio) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->round_start && ce_ratio >= 0) ++ tcp_plb_update_state(sk, &bbr->plb, ce_ratio); ++ ++ tcp_plb_check_rehash(sk, &bbr->plb); ++} ++ ++/* Each round trip of BBR_BW_PROBE_UP, double volume of probing data. */ ++static void bbr_raise_inflight_hi_slope(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 growth_this_round, cnt; ++ ++ /* Calculate "slope": packets S/Acked per inflight_hi increment. */ ++ growth_this_round = 1 << bbr->bw_probe_up_rounds; ++ bbr->bw_probe_up_rounds = min(bbr->bw_probe_up_rounds + 1, 30); ++ cnt = tcp_snd_cwnd(tp) / growth_this_round; ++ cnt = max(cnt, 1U); ++ bbr->bw_probe_up_cnt = cnt; ++} ++ ++/* In BBR_BW_PROBE_UP, not seeing high loss/ECN/queue, so raise inflight_hi. */ ++static void bbr_probe_inflight_hi_upward(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 delta; ++ ++ if (!tp->is_cwnd_limited || tcp_snd_cwnd(tp) < bbr->inflight_hi) ++ return; /* not fully using inflight_hi, so don't grow it */ ++ ++ /* For each bw_probe_up_cnt packets ACKed, increase inflight_hi by 1. */ ++ bbr->bw_probe_up_acks += rs->acked_sacked; ++ if (bbr->bw_probe_up_acks >= bbr->bw_probe_up_cnt) { ++ delta = bbr->bw_probe_up_acks / bbr->bw_probe_up_cnt; ++ bbr->bw_probe_up_acks -= delta * bbr->bw_probe_up_cnt; ++ bbr->inflight_hi += delta; ++ bbr->try_fast_path = 0; /* Need to update cwnd */ ++ } ++ ++ if (bbr->round_start) ++ bbr_raise_inflight_hi_slope(sk); ++} ++ ++/* Does loss/ECN rate for this sample say inflight is "too high"? ++ * This is used by both the bbr_check_loss_too_high_in_startup() function, ++ * which can be used in either v1 or v2, and the PROBE_UP phase of v2, which ++ * uses it to notice when loss/ECN rates suggest inflight is too high. ++ */ ++static bool bbr_is_inflight_too_high(const struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ const struct bbr *bbr = inet_csk_ca(sk); ++ u32 loss_thresh, ecn_thresh; ++ ++ if (rs->lost > 0 && rs->tx_in_flight) { ++ loss_thresh = (u64)rs->tx_in_flight * bbr_param(sk, loss_thresh) >> ++ BBR_SCALE; ++ if (rs->lost > loss_thresh) { ++ return true; ++ } ++ } ++ ++ if (rs->delivered_ce > 0 && rs->delivered > 0 && ++ bbr->ecn_eligible && bbr_param(sk, ecn_thresh)) { ++ ecn_thresh = (u64)rs->delivered * bbr_param(sk, ecn_thresh) >> ++ BBR_SCALE; ++ if (rs->delivered_ce > ecn_thresh) { ++ return true; ++ } ++ } ++ ++ return false; ++} ++ ++/* Calculate the tx_in_flight level that corresponded to excessive loss. ++ * We find "lost_prefix" segs of the skb where loss rate went too high, ++ * by solving for "lost_prefix" in the following equation: ++ * lost / inflight >= loss_thresh ++ * (lost_prev + lost_prefix) / (inflight_prev + lost_prefix) >= loss_thresh ++ * Then we take that equation, convert it to fixed point, and ++ * round up to the nearest packet. ++ */ ++static u32 bbr_inflight_hi_from_lost_skb(const struct sock *sk, ++ const struct rate_sample *rs, ++ const struct sk_buff *skb) ++{ ++ const struct tcp_sock *tp = tcp_sk(sk); ++ u32 loss_thresh = bbr_param(sk, loss_thresh); ++ u32 pcount, divisor, inflight_hi; ++ s32 inflight_prev, lost_prev; ++ u64 loss_budget, lost_prefix; ++ ++ pcount = tcp_skb_pcount(skb); ++ ++ /* How much data was in flight before this skb? */ ++ inflight_prev = rs->tx_in_flight - pcount; ++ if (inflight_prev < 0) { ++ WARN_ONCE(tcp_skb_tx_in_flight_is_suspicious( ++ pcount, ++ TCP_SKB_CB(skb)->sacked, ++ rs->tx_in_flight), ++ "tx_in_flight: %u pcount: %u reneg: %u", ++ rs->tx_in_flight, pcount, tcp_sk(sk)->is_sack_reneg); ++ return ~0U; ++ } ++ ++ /* How much inflight data was marked lost before this skb? */ ++ lost_prev = rs->lost - pcount; ++ if (WARN_ONCE(lost_prev < 0, ++ "cwnd: %u ca: %d out: %u lost: %u pif: %u " ++ "tx_in_flight: %u tx.lost: %u tp->lost: %u rs->lost: %d " ++ "lost_prev: %d pcount: %d seq: %u end_seq: %u reneg: %u", ++ tcp_snd_cwnd(tp), inet_csk(sk)->icsk_ca_state, ++ tp->packets_out, tp->lost_out, tcp_packets_in_flight(tp), ++ rs->tx_in_flight, TCP_SKB_CB(skb)->tx.lost, tp->lost, ++ rs->lost, lost_prev, pcount, ++ TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq, ++ tp->is_sack_reneg)) ++ return ~0U; ++ ++ /* At what prefix of this lost skb did losss rate exceed loss_thresh? */ ++ loss_budget = (u64)inflight_prev * loss_thresh + BBR_UNIT - 1; ++ loss_budget >>= BBR_SCALE; ++ if (lost_prev >= loss_budget) { ++ lost_prefix = 0; /* previous losses crossed loss_thresh */ ++ } else { ++ lost_prefix = loss_budget - lost_prev; ++ lost_prefix <<= BBR_SCALE; ++ divisor = BBR_UNIT - loss_thresh; ++ if (WARN_ON_ONCE(!divisor)) /* loss_thresh is 8 bits */ ++ return ~0U; ++ do_div(lost_prefix, divisor); ++ } ++ ++ inflight_hi = inflight_prev + lost_prefix; ++ return inflight_hi; ++} ++ ++/* If loss/ECN rates during probing indicated we may have overfilled a ++ * buffer, return an operating point that tries to leave unutilized headroom in ++ * the path for other flows, for fairness convergence and lower RTTs and loss. ++ */ ++static u32 bbr_inflight_with_headroom(const struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 headroom, headroom_fraction; ++ ++ if (bbr->inflight_hi == ~0U) ++ return ~0U; ++ ++ headroom_fraction = bbr_param(sk, inflight_headroom); ++ headroom = ((u64)bbr->inflight_hi * headroom_fraction) >> BBR_SCALE; ++ headroom = max(headroom, 1U); ++ return max_t(s32, bbr->inflight_hi - headroom, ++ bbr_param(sk, cwnd_min_target)); ++} ++ ++/* Bound cwnd to a sensible level, based on our current probing state ++ * machine phase and model of a good inflight level (inflight_lo, inflight_hi). ++ */ ++static void bbr_bound_cwnd_for_inflight_model(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 cap; ++ ++ /* tcp_rcv_synsent_state_process() currently calls tcp_ack() ++ * and thus cong_control() without first initializing us(!). ++ */ ++ if (!bbr->initialized) ++ return; ++ ++ cap = ~0U; ++ if (bbr->mode == BBR_PROBE_BW && ++ bbr->cycle_idx != BBR_BW_PROBE_CRUISE) { ++ /* Probe to see if more packets fit in the path. */ ++ cap = bbr->inflight_hi; ++ } else { ++ if (bbr->mode == BBR_PROBE_RTT || ++ (bbr->mode == BBR_PROBE_BW && ++ bbr->cycle_idx == BBR_BW_PROBE_CRUISE)) ++ cap = bbr_inflight_with_headroom(sk); ++ } ++ /* Adapt to any loss/ECN since our last bw probe. */ ++ cap = min(cap, bbr->inflight_lo); ++ ++ cap = max_t(u32, cap, bbr_param(sk, cwnd_min_target)); ++ tcp_snd_cwnd_set(tp, min(cap, tcp_snd_cwnd(tp))); ++} ++ ++/* How should we multiplicatively cut bw or inflight limits based on ECN? */ ++u32 bbr_ecn_cut(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ return BBR_UNIT - ++ ((bbr->ecn_alpha * bbr_param(sk, ecn_factor)) >> BBR_SCALE); ++} ++ ++/* Init lower bounds if have not inited yet. */ ++static void bbr_init_lower_bounds(struct sock *sk, bool init_bw) + { +- bbr_update_bw(sk, rs); ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (init_bw && bbr->bw_lo == ~0U) ++ bbr->bw_lo = bbr_max_bw(sk); ++ if (bbr->inflight_lo == ~0U) ++ bbr->inflight_lo = tcp_snd_cwnd(tp); ++} ++ ++/* Reduce bw and inflight to (1 - beta). */ ++static void bbr_loss_lower_bounds(struct sock *sk, u32 *bw, u32 *inflight) ++{ ++ struct bbr* bbr = inet_csk_ca(sk); ++ u32 loss_cut = BBR_UNIT - bbr_param(sk, beta); ++ ++ *bw = max_t(u32, bbr->bw_latest, ++ (u64)bbr->bw_lo * loss_cut >> BBR_SCALE); ++ *inflight = max_t(u32, bbr->inflight_latest, ++ (u64)bbr->inflight_lo * loss_cut >> BBR_SCALE); ++} ++ ++/* Reduce inflight to (1 - alpha*ecn_factor). */ ++static void bbr_ecn_lower_bounds(struct sock *sk, u32 *inflight) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 ecn_cut = bbr_ecn_cut(sk); ++ ++ *inflight = (u64)bbr->inflight_lo * ecn_cut >> BBR_SCALE; ++} ++ ++/* Estimate a short-term lower bound on the capacity available now, based ++ * on measurements of the current delivery process and recent history. When we ++ * are seeing loss/ECN at times when we are not probing bw, then conservatively ++ * move toward flow balance by multiplicatively cutting our short-term ++ * estimated safe rate and volume of data (bw_lo and inflight_lo). We use a ++ * multiplicative decrease in order to converge to a lower capacity in time ++ * logarithmic in the magnitude of the decrease. ++ * ++ * However, we do not cut our short-term estimates lower than the current rate ++ * and volume of delivered data from this round trip, since from the current ++ * delivery process we can estimate the measured capacity available now. ++ * ++ * Anything faster than that approach would knowingly risk high loss, which can ++ * cause low bw for Reno/CUBIC and high loss recovery latency for ++ * request/response flows using any congestion control. ++ */ ++static void bbr_adapt_lower_bounds(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 ecn_inflight_lo = ~0U; ++ ++ /* We only use lower-bound estimates when not probing bw. ++ * When probing we need to push inflight higher to probe bw. ++ */ ++ if (bbr_is_probing_bandwidth(sk)) ++ return; ++ ++ /* ECN response. */ ++ if (bbr->ecn_in_round && bbr_param(sk, ecn_factor)) { ++ bbr_init_lower_bounds(sk, false); ++ bbr_ecn_lower_bounds(sk, &ecn_inflight_lo); ++ } ++ ++ /* Loss response. */ ++ if (bbr->loss_in_round) { ++ bbr_init_lower_bounds(sk, true); ++ bbr_loss_lower_bounds(sk, &bbr->bw_lo, &bbr->inflight_lo); ++ } ++ ++ /* Adjust to the lower of the levels implied by loss/ECN. */ ++ bbr->inflight_lo = min(bbr->inflight_lo, ecn_inflight_lo); ++ bbr->bw_lo = max(1U, bbr->bw_lo); ++} ++ ++/* Reset any short-term lower-bound adaptation to congestion, so that we can ++ * push our inflight up. ++ */ ++static void bbr_reset_lower_bounds(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->bw_lo = ~0U; ++ bbr->inflight_lo = ~0U; ++} ++ ++/* After bw probing (STARTUP/PROBE_UP), reset signals before entering a state ++ * machine phase where we adapt our lower bound based on congestion signals. ++ */ ++static void bbr_reset_congestion_signals(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->loss_in_round = 0; ++ bbr->ecn_in_round = 0; ++ bbr->loss_in_cycle = 0; ++ bbr->ecn_in_cycle = 0; ++ bbr->bw_latest = 0; ++ bbr->inflight_latest = 0; ++} ++ ++static void bbr_exit_loss_recovery(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ tcp_snd_cwnd_set(tp, max(tcp_snd_cwnd(tp), bbr->prior_cwnd)); ++ bbr->try_fast_path = 0; /* bound cwnd using latest model */ ++} ++ ++/* Update rate and volume of delivered data from latest round trip. */ ++static void bbr_update_latest_delivery_signals( ++ struct sock *sk, const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->loss_round_start = 0; ++ if (rs->interval_us <= 0 || !rs->acked_sacked) ++ return; /* Not a valid observation */ ++ ++ bbr->bw_latest = max_t(u32, bbr->bw_latest, ctx->sample_bw); ++ bbr->inflight_latest = max_t(u32, bbr->inflight_latest, rs->delivered); ++ ++ if (!before(rs->prior_delivered, bbr->loss_round_delivered)) { ++ bbr->loss_round_delivered = tp->delivered; ++ bbr->loss_round_start = 1; /* mark start of new round trip */ ++ } ++} ++ ++/* Once per round, reset filter for latest rate and volume of delivered data. */ ++static void bbr_advance_latest_delivery_signals( ++ struct sock *sk, const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ /* If ACK matches a TLP retransmit, persist the filter. If we detect ++ * that a TLP retransmit plugged a tail loss, we'll want to remember ++ * how much data the path delivered before the tail loss. ++ */ ++ if (bbr->loss_round_start && !rs->is_acking_tlp_retrans_seq) { ++ bbr->bw_latest = ctx->sample_bw; ++ bbr->inflight_latest = rs->delivered; ++ } ++} ++ ++/* Update (most of) our congestion signals: track the recent rate and volume of ++ * delivered data, presence of loss, and EWMA degree of ECN marking. ++ */ ++static void bbr_update_congestion_signals( ++ struct sock *sk, const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u64 bw; ++ ++ if (rs->interval_us <= 0 || !rs->acked_sacked) ++ return; /* Not a valid observation */ ++ bw = ctx->sample_bw; ++ ++ if (!rs->is_app_limited || bw >= bbr_max_bw(sk)) ++ bbr_take_max_bw_sample(sk, bw); ++ ++ bbr->loss_in_round |= (rs->losses > 0); ++ ++ if (!bbr->loss_round_start) ++ return; /* skip the per-round-trip updates */ ++ /* Now do per-round-trip updates. */ ++ bbr_adapt_lower_bounds(sk, rs); ++ ++ bbr->loss_in_round = 0; ++ bbr->ecn_in_round = 0; ++} ++ ++/* Bandwidth probing can cause loss. To help coexistence with loss-based ++ * congestion control we spread out our probing in a Reno-conscious way. Due to ++ * the shape of the Reno sawtooth, the time required between loss epochs for an ++ * idealized Reno flow is a number of round trips that is the BDP of that ++ * flow. We count packet-timed round trips directly, since measured RTT can ++ * vary widely, and Reno is driven by packet-timed round trips. ++ */ ++static bool bbr_is_reno_coexistence_probe_time(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 rounds; ++ ++ /* Random loss can shave some small percentage off of our inflight ++ * in each round. To survive this, flows need robust periodic probes. ++ */ ++ rounds = min_t(u32, bbr_param(sk, bw_probe_max_rounds), bbr_target_inflight(sk)); ++ return bbr->rounds_since_probe >= rounds; ++} ++ ++/* How long do we want to wait before probing for bandwidth (and risking ++ * loss)? We randomize the wait, for better mixing and fairness convergence. ++ * ++ * We bound the Reno-coexistence inter-bw-probe time to be 62-63 round trips. ++ * This is calculated to allow fairness with a 25Mbps, 30ms Reno flow, ++ * (eg 4K video to a broadband user): ++ * BDP = 25Mbps * .030sec /(1514bytes) = 61.9 packets ++ * ++ * We bound the BBR-native inter-bw-probe wall clock time to be: ++ * (a) higher than 2 sec: to try to avoid causing loss for a long enough time ++ * to allow Reno at 30ms to get 4K video bw, the inter-bw-probe time must ++ * be at least: 25Mbps * .030sec / (1514bytes) * 0.030sec = 1.9secs ++ * (b) lower than 3 sec: to ensure flows can start probing in a reasonable ++ * amount of time to discover unutilized bw on human-scale interactive ++ * time-scales (e.g. perhaps traffic from a web page download that we ++ * were competing with is now complete). ++ */ ++static void bbr_pick_probe_wait(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ /* Decide the random round-trip bound for wait until probe: */ ++ bbr->rounds_since_probe = ++ get_random_u32_below(bbr_param(sk, bw_probe_rand_rounds)); ++ /* Decide the random wall clock bound for wait until probe: */ ++ bbr->probe_wait_us = bbr_param(sk, bw_probe_base_us) + ++ get_random_u32_below(bbr_param(sk, bw_probe_rand_us)); ++} ++ ++static void bbr_set_cycle_idx(struct sock *sk, int cycle_idx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->cycle_idx = cycle_idx; ++ /* New phase, so need to update cwnd and pacing rate. */ ++ bbr->try_fast_path = 0; ++} ++ ++/* Send at estimated bw to fill the pipe, but not queue. We need this phase ++ * before PROBE_UP, because as soon as we send faster than the available bw ++ * we will start building a queue, and if the buffer is shallow we can cause ++ * loss. If we do not fill the pipe before we cause this loss, our bw_hi and ++ * inflight_hi estimates will underestimate. ++ */ ++static void bbr_start_bw_probe_refill(struct sock *sk, u32 bw_probe_up_rounds) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr_reset_lower_bounds(sk); ++ bbr->bw_probe_up_rounds = bw_probe_up_rounds; ++ bbr->bw_probe_up_acks = 0; ++ bbr->stopped_risky_probe = 0; ++ bbr->ack_phase = BBR_ACKS_REFILLING; ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr_set_cycle_idx(sk, BBR_BW_PROBE_REFILL); ++} ++ ++/* Now probe max deliverable data rate and volume. */ ++static void bbr_start_bw_probe_up(struct sock *sk, struct bbr_context *ctx) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr->ack_phase = BBR_ACKS_PROBE_STARTING; ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr->cycle_mstamp = tp->tcp_mstamp; ++ bbr_reset_full_bw(sk); ++ bbr->full_bw = ctx->sample_bw; ++ bbr_set_cycle_idx(sk, BBR_BW_PROBE_UP); ++ bbr_raise_inflight_hi_slope(sk); ++} ++ ++/* Start a new PROBE_BW probing cycle of some wall clock length. Pick a wall ++ * clock time at which to probe beyond an inflight that we think to be ++ * safe. This will knowingly risk packet loss, so we want to do this rarely, to ++ * keep packet loss rates low. Also start a round-trip counter, to probe faster ++ * if we estimate a Reno flow at our BDP would probe faster. ++ */ ++static void bbr_start_bw_probe_down(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr_reset_congestion_signals(sk); ++ bbr->bw_probe_up_cnt = ~0U; /* not growing inflight_hi any more */ ++ bbr_pick_probe_wait(sk); ++ bbr->cycle_mstamp = tp->tcp_mstamp; /* start wall clock */ ++ bbr->ack_phase = BBR_ACKS_PROBE_STOPPING; ++ bbr->next_rtt_delivered = tp->delivered; ++ bbr_set_cycle_idx(sk, BBR_BW_PROBE_DOWN); ++} ++ ++/* Cruise: maintain what we estimate to be a neutral, conservative ++ * operating point, without attempting to probe up for bandwidth or down for ++ * RTT, and only reducing inflight in response to loss/ECN signals. ++ */ ++static void bbr_start_bw_probe_cruise(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->inflight_lo != ~0U) ++ bbr->inflight_lo = min(bbr->inflight_lo, bbr->inflight_hi); ++ ++ bbr_set_cycle_idx(sk, BBR_BW_PROBE_CRUISE); ++} ++ ++/* Loss and/or ECN rate is too high while probing. ++ * Adapt (once per bw probe) by cutting inflight_hi and then restarting cycle. ++ */ ++static void bbr_handle_inflight_too_high(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ const u32 beta = bbr_param(sk, beta); ++ ++ bbr->prev_probe_too_high = 1; ++ bbr->bw_probe_samples = 0; /* only react once per probe */ ++ /* If we are app-limited then we are not robustly ++ * probing the max volume of inflight data we think ++ * might be safe (analogous to how app-limited bw ++ * samples are not known to be robustly probing bw). ++ */ ++ if (!rs->is_app_limited) { ++ bbr->inflight_hi = max_t(u32, rs->tx_in_flight, ++ (u64)bbr_target_inflight(sk) * ++ (BBR_UNIT - beta) >> BBR_SCALE); ++ } ++ if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP) ++ bbr_start_bw_probe_down(sk); ++} ++ ++/* If we're seeing bw and loss samples reflecting our bw probing, adapt ++ * using the signals we see. If loss or ECN mark rate gets too high, then adapt ++ * inflight_hi downward. If we're able to push inflight higher without such ++ * signals, push higher: adapt inflight_hi upward. ++ */ ++static bool bbr_adapt_upper_bounds(struct sock *sk, ++ const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ /* Track when we'll see bw/loss samples resulting from our bw probes. */ ++ if (bbr->ack_phase == BBR_ACKS_PROBE_STARTING && bbr->round_start) ++ bbr->ack_phase = BBR_ACKS_PROBE_FEEDBACK; ++ if (bbr->ack_phase == BBR_ACKS_PROBE_STOPPING && bbr->round_start) { ++ /* End of samples from bw probing phase. */ ++ bbr->bw_probe_samples = 0; ++ bbr->ack_phase = BBR_ACKS_INIT; ++ /* At this point in the cycle, our current bw sample is also ++ * our best recent chance at finding the highest available bw ++ * for this flow. So now is the best time to forget the bw ++ * samples from the previous cycle, by advancing the window. ++ */ ++ if (bbr->mode == BBR_PROBE_BW && !rs->is_app_limited) ++ bbr_advance_max_bw_filter(sk); ++ /* If we had an inflight_hi, then probed and pushed inflight all ++ * the way up to hit that inflight_hi without seeing any ++ * high loss/ECN in all the resulting ACKs from that probing, ++ * then probe up again, this time letting inflight persist at ++ * inflight_hi for a round trip, then accelerating beyond. ++ */ ++ if (bbr->mode == BBR_PROBE_BW && ++ bbr->stopped_risky_probe && !bbr->prev_probe_too_high) { ++ bbr_start_bw_probe_refill(sk, 0); ++ return true; /* yes, decided state transition */ ++ } ++ } ++ if (bbr_is_inflight_too_high(sk, rs)) { ++ if (bbr->bw_probe_samples) /* sample is from bw probing? */ ++ bbr_handle_inflight_too_high(sk, rs); ++ } else { ++ /* Loss/ECN rate is declared safe. Adjust upper bound upward. */ ++ ++ if (bbr->inflight_hi == ~0U) ++ return false; /* no excess queue signals yet */ ++ ++ /* To be resilient to random loss, we must raise bw/inflight_hi ++ * if we observe in any phase that a higher level is safe. ++ */ ++ if (rs->tx_in_flight > bbr->inflight_hi) { ++ bbr->inflight_hi = rs->tx_in_flight; ++ } ++ ++ if (bbr->mode == BBR_PROBE_BW && ++ bbr->cycle_idx == BBR_BW_PROBE_UP) ++ bbr_probe_inflight_hi_upward(sk, rs); ++ } ++ ++ return false; ++} ++ ++/* Check if it's time to probe for bandwidth now, and if so, kick it off. */ ++static bool bbr_check_time_to_probe_bw(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 n; ++ ++ /* If we seem to be at an operating point where we are not seeing loss ++ * but we are seeing ECN marks, then when the ECN marks cease we reprobe ++ * quickly (in case cross-traffic has ceased and freed up bw). ++ */ ++ if (bbr_param(sk, ecn_reprobe_gain) && bbr->ecn_eligible && ++ bbr->ecn_in_cycle && !bbr->loss_in_cycle && ++ inet_csk(sk)->icsk_ca_state == TCP_CA_Open) { ++ /* Calculate n so that when bbr_raise_inflight_hi_slope() ++ * computes growth_this_round as 2^n it will be roughly the ++ * desired volume of data (inflight_hi*ecn_reprobe_gain). ++ */ ++ n = ilog2((((u64)bbr->inflight_hi * ++ bbr_param(sk, ecn_reprobe_gain)) >> BBR_SCALE)); ++ bbr_start_bw_probe_refill(sk, n); ++ return true; ++ } ++ ++ if (bbr_has_elapsed_in_phase(sk, bbr->probe_wait_us) || ++ bbr_is_reno_coexistence_probe_time(sk)) { ++ bbr_start_bw_probe_refill(sk, 0); ++ return true; ++ } ++ return false; ++} ++ ++/* Is it time to transition from PROBE_DOWN to PROBE_CRUISE? */ ++static bool bbr_check_time_to_cruise(struct sock *sk, u32 inflight, u32 bw) ++{ ++ /* Always need to pull inflight down to leave headroom in queue. */ ++ if (inflight > bbr_inflight_with_headroom(sk)) ++ return false; ++ ++ return inflight <= bbr_inflight(sk, bw, BBR_UNIT); ++} ++ ++/* PROBE_BW state machine: cruise, refill, probe for bw, or drain? */ ++static void bbr_update_cycle_phase(struct sock *sk, ++ const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ bool is_bw_probe_done = false; ++ u32 inflight, bw; ++ ++ if (!bbr_full_bw_reached(sk)) ++ return; ++ ++ /* In DRAIN, PROBE_BW, or PROBE_RTT, adjust upper bounds. */ ++ if (bbr_adapt_upper_bounds(sk, rs, ctx)) ++ return; /* already decided state transition */ ++ ++ if (bbr->mode != BBR_PROBE_BW) ++ return; ++ ++ inflight = bbr_packets_in_net_at_edt(sk, rs->prior_in_flight); ++ bw = bbr_max_bw(sk); ++ ++ switch (bbr->cycle_idx) { ++ /* First we spend most of our time cruising with a pacing_gain of 1.0, ++ * which paces at the estimated bw, to try to fully use the pipe ++ * without building queue. If we encounter loss/ECN marks, we adapt ++ * by slowing down. ++ */ ++ case BBR_BW_PROBE_CRUISE: ++ if (bbr_check_time_to_probe_bw(sk, rs)) ++ return; /* already decided state transition */ ++ break; ++ ++ /* After cruising, when it's time to probe, we first "refill": we send ++ * at the estimated bw to fill the pipe, before probing higher and ++ * knowingly risking overflowing the bottleneck buffer (causing loss). ++ */ ++ case BBR_BW_PROBE_REFILL: ++ if (bbr->round_start) { ++ /* After one full round trip of sending in REFILL, we ++ * start to see bw samples reflecting our REFILL, which ++ * may be putting too much data in flight. ++ */ ++ bbr->bw_probe_samples = 1; ++ bbr_start_bw_probe_up(sk, ctx); ++ } ++ break; ++ ++ /* After we refill the pipe, we probe by using a pacing_gain > 1.0, to ++ * probe for bw. If we have not seen loss/ECN, we try to raise inflight ++ * to at least pacing_gain*BDP; note that this may take more than ++ * min_rtt if min_rtt is small (e.g. on a LAN). ++ * ++ * We terminate PROBE_UP bandwidth probing upon any of the following: ++ * ++ * (1) We've pushed inflight up to hit the inflight_hi target set in the ++ * most recent previous bw probe phase. Thus we want to start ++ * draining the queue immediately because it's very likely the most ++ * recently sent packets will fill the queue and cause drops. ++ * (2) If inflight_hi has not limited bandwidth growth recently, and ++ * yet delivered bandwidth has not increased much recently ++ * (bbr->full_bw_now). ++ * (3) Loss filter says loss rate is "too high". ++ * (4) ECN filter says ECN mark rate is "too high". ++ * ++ * (1) (2) checked here, (3) (4) checked in bbr_is_inflight_too_high() ++ */ ++ case BBR_BW_PROBE_UP: ++ if (bbr->prev_probe_too_high && ++ inflight >= bbr->inflight_hi) { ++ bbr->stopped_risky_probe = 1; ++ is_bw_probe_done = true; ++ } else { ++ if (tp->is_cwnd_limited && ++ tcp_snd_cwnd(tp) >= bbr->inflight_hi) { ++ /* inflight_hi is limiting bw growth */ ++ bbr_reset_full_bw(sk); ++ bbr->full_bw = ctx->sample_bw; ++ } else if (bbr->full_bw_now) { ++ /* Plateau in estimated bw. Pipe looks full. */ ++ is_bw_probe_done = true; ++ } ++ } ++ if (is_bw_probe_done) { ++ bbr->prev_probe_too_high = 0; /* no loss/ECN (yet) */ ++ bbr_start_bw_probe_down(sk); /* restart w/ down */ ++ } ++ break; ++ ++ /* After probing in PROBE_UP, we have usually accumulated some data in ++ * the bottleneck buffer (if bw probing didn't find more bw). We next ++ * enter PROBE_DOWN to try to drain any excess data from the queue. To ++ * do this, we use a pacing_gain < 1.0. We hold this pacing gain until ++ * our inflight is less then that target cruising point, which is the ++ * minimum of (a) the amount needed to leave headroom, and (b) the ++ * estimated BDP. Once inflight falls to match the target, we estimate ++ * the queue is drained; persisting would underutilize the pipe. ++ */ ++ case BBR_BW_PROBE_DOWN: ++ if (bbr_check_time_to_probe_bw(sk, rs)) ++ return; /* already decided state transition */ ++ if (bbr_check_time_to_cruise(sk, inflight, bw)) ++ bbr_start_bw_probe_cruise(sk); ++ break; ++ ++ default: ++ WARN_ONCE(1, "BBR invalid cycle index %u\n", bbr->cycle_idx); ++ } ++} ++ ++/* Exiting PROBE_RTT, so return to bandwidth probing in STARTUP or PROBE_BW. */ ++static void bbr_exit_probe_rtt(struct sock *sk) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ bbr_reset_lower_bounds(sk); ++ if (bbr_full_bw_reached(sk)) { ++ bbr->mode = BBR_PROBE_BW; ++ /* Raising inflight after PROBE_RTT may cause loss, so reset ++ * the PROBE_BW clock and schedule the next bandwidth probe for ++ * a friendly and randomized future point in time. ++ */ ++ bbr_start_bw_probe_down(sk); ++ /* Since we are exiting PROBE_RTT, we know inflight is ++ * below our estimated BDP, so it is reasonable to cruise. ++ */ ++ bbr_start_bw_probe_cruise(sk); ++ } else { ++ bbr->mode = BBR_STARTUP; ++ } ++} ++ ++/* Exit STARTUP based on loss rate > 1% and loss gaps in round >= N. Wait until ++ * the end of the round in recovery to get a good estimate of how many packets ++ * have been lost, and how many we need to drain with a low pacing rate. ++ */ ++static void bbr_check_loss_too_high_in_startup(struct sock *sk, ++ const struct rate_sample *rs) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr_full_bw_reached(sk)) ++ return; ++ ++ /* For STARTUP exit, check the loss rate at the end of each round trip ++ * of Recovery episodes in STARTUP. We check the loss rate at the end ++ * of the round trip to filter out noisy/low loss and have a better ++ * sense of inflight (extent of loss), so we can drain more accurately. ++ */ ++ if (rs->losses && bbr->loss_events_in_round < 0xf) ++ bbr->loss_events_in_round++; /* update saturating counter */ ++ if (bbr_param(sk, full_loss_cnt) && bbr->loss_round_start && ++ inet_csk(sk)->icsk_ca_state == TCP_CA_Recovery && ++ bbr->loss_events_in_round >= bbr_param(sk, full_loss_cnt) && ++ bbr_is_inflight_too_high(sk, rs)) { ++ bbr_handle_queue_too_high_in_startup(sk); ++ return; ++ } ++ if (bbr->loss_round_start) ++ bbr->loss_events_in_round = 0; ++} ++ ++/* Estimate when the pipe is full, using the change in delivery rate: BBR ++ * estimates bw probing filled the pipe if the estimated bw hasn't changed by ++ * at least bbr_full_bw_thresh (25%) after bbr_full_bw_cnt (3) non-app-limited ++ * rounds. Why 3 rounds: 1: rwin autotuning grows the rwin, 2: we fill the ++ * higher rwin, 3: we get higher delivery rate samples. Or transient ++ * cross-traffic or radio noise can go away. CUBIC Hystart shares a similar ++ * design goal, but uses delay and inter-ACK spacing instead of bandwidth. ++ */ ++static void bbr_check_full_bw_reached(struct sock *sk, ++ const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 bw_thresh, full_cnt, thresh; ++ ++ if (bbr->full_bw_now || rs->is_app_limited) ++ return; ++ ++ thresh = bbr_param(sk, full_bw_thresh); ++ full_cnt = bbr_param(sk, full_bw_cnt); ++ bw_thresh = (u64)bbr->full_bw * thresh >> BBR_SCALE; ++ if (ctx->sample_bw >= bw_thresh) { ++ bbr_reset_full_bw(sk); ++ bbr->full_bw = ctx->sample_bw; ++ return; ++ } ++ if (!bbr->round_start) ++ return; ++ ++bbr->full_bw_cnt; ++ bbr->full_bw_now = bbr->full_bw_cnt >= full_cnt; ++ bbr->full_bw_reached |= bbr->full_bw_now; ++} ++ ++/* If pipe is probably full, drain the queue and then enter steady-state. */ ++static void bbr_check_drain(struct sock *sk, const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ if (bbr->mode == BBR_STARTUP && bbr_full_bw_reached(sk)) { ++ bbr->mode = BBR_DRAIN; /* drain queue we created */ ++ /* Set ssthresh to export purely for monitoring, to signal ++ * completion of initial STARTUP by setting to a non- ++ * TCP_INFINITE_SSTHRESH value (ssthresh is not used by BBR). ++ */ ++ tcp_sk(sk)->snd_ssthresh = ++ bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT); ++ bbr_reset_congestion_signals(sk); ++ } /* fall through to check if in-flight is already small: */ ++ if (bbr->mode == BBR_DRAIN && ++ bbr_packets_in_net_at_edt(sk, tcp_packets_in_flight(tcp_sk(sk))) <= ++ bbr_inflight(sk, bbr_max_bw(sk), BBR_UNIT)) { ++ bbr->mode = BBR_PROBE_BW; ++ bbr_start_bw_probe_down(sk); ++ } ++} ++ ++static void bbr_update_model(struct sock *sk, const struct rate_sample *rs, ++ struct bbr_context *ctx) ++{ ++ bbr_update_congestion_signals(sk, rs, ctx); + bbr_update_ack_aggregation(sk, rs); +- bbr_update_cycle_phase(sk, rs); +- bbr_check_full_bw_reached(sk, rs); +- bbr_check_drain(sk, rs); ++ bbr_check_loss_too_high_in_startup(sk, rs); ++ bbr_check_full_bw_reached(sk, rs, ctx); ++ bbr_check_drain(sk, rs, ctx); ++ bbr_update_cycle_phase(sk, rs, ctx); + bbr_update_min_rtt(sk, rs); +- bbr_update_gains(sk); + } + +-__bpf_kfunc static void bbr_main(struct sock *sk, const struct rate_sample *rs) ++/* Fast path for app-limited case. ++ * ++ * On each ack, we execute bbr state machine, which primarily consists of: ++ * 1) update model based on new rate sample, and ++ * 2) update control based on updated model or state change. ++ * ++ * There are certain workload/scenarios, e.g. app-limited case, where ++ * either we can skip updating model or we can skip update of both model ++ * as well as control. This provides signifcant softirq cpu savings for ++ * processing incoming acks. ++ * ++ * In case of app-limited, if there is no congestion (loss/ecn) and ++ * if observed bw sample is less than current estimated bw, then we can ++ * skip some of the computation in bbr state processing: ++ * ++ * - if there is no rtt/mode/phase change: In this case, since all the ++ * parameters of the network model are constant, we can skip model ++ * as well control update. ++ * ++ * - else we can skip rest of the model update. But we still need to ++ * update the control to account for the new rtt/mode/phase. ++ * ++ * Returns whether we can take fast path or not. ++ */ ++static bool bbr_run_fast_path(struct sock *sk, bool *update_model, ++ const struct rate_sample *rs, struct bbr_context *ctx) ++{ ++ struct bbr *bbr = inet_csk_ca(sk); ++ u32 prev_min_rtt_us, prev_mode; ++ ++ if (bbr_param(sk, fast_path) && bbr->try_fast_path && ++ rs->is_app_limited && ctx->sample_bw < bbr_max_bw(sk) && ++ !bbr->loss_in_round && !bbr->ecn_in_round ) { ++ prev_mode = bbr->mode; ++ prev_min_rtt_us = bbr->min_rtt_us; ++ bbr_check_drain(sk, rs, ctx); ++ bbr_update_cycle_phase(sk, rs, ctx); ++ bbr_update_min_rtt(sk, rs); ++ ++ if (bbr->mode == prev_mode && ++ bbr->min_rtt_us == prev_min_rtt_us && ++ bbr->try_fast_path) { ++ return true; ++ } ++ ++ /* Skip model update, but control still needs to be updated */ ++ *update_model = false; ++ } ++ return false; ++} ++ ++__bpf_kfunc void bbr_main(struct sock *sk, const struct rate_sample *rs) + { ++ struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); +- u32 bw; ++ struct bbr_context ctx = { 0 }; ++ bool update_model = true; ++ u32 bw, round_delivered; ++ int ce_ratio = -1; ++ ++ round_delivered = bbr_update_round_start(sk, rs, &ctx); ++ if (bbr->round_start) { ++ bbr->rounds_since_probe = ++ min_t(s32, bbr->rounds_since_probe + 1, 0xFF); ++ ce_ratio = bbr_update_ecn_alpha(sk); ++ } ++ bbr_plb(sk, rs, ce_ratio); ++ ++ bbr->ecn_in_round |= (bbr->ecn_eligible && rs->is_ece); ++ bbr_calculate_bw_sample(sk, rs, &ctx); ++ bbr_update_latest_delivery_signals(sk, rs, &ctx); + +- bbr_update_model(sk, rs); ++ if (bbr_run_fast_path(sk, &update_model, rs, &ctx)) ++ goto out; + ++ if (update_model) ++ bbr_update_model(sk, rs, &ctx); ++ ++ bbr_update_gains(sk); + bw = bbr_bw(sk); + bbr_set_pacing_rate(sk, bw, bbr->pacing_gain); +- bbr_set_cwnd(sk, rs, rs->acked_sacked, bw, bbr->cwnd_gain); ++ bbr_set_cwnd(sk, rs, rs->acked_sacked, bw, bbr->cwnd_gain, ++ tcp_snd_cwnd(tp), &ctx); ++ bbr_bound_cwnd_for_inflight_model(sk); ++ ++out: ++ bbr_advance_latest_delivery_signals(sk, rs, &ctx); ++ bbr->prev_ca_state = inet_csk(sk)->icsk_ca_state; ++ bbr->loss_in_cycle |= rs->lost > 0; ++ bbr->ecn_in_cycle |= rs->delivered_ce > 0; + } + + __bpf_kfunc static void bbr_init(struct sock *sk) +@@ -1040,20 +2079,21 @@ + struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); + +- bbr->prior_cwnd = 0; ++ bbr->initialized = 1; ++ ++ bbr->init_cwnd = min(0x7FU, tcp_snd_cwnd(tp)); ++ bbr->prior_cwnd = tp->prior_cwnd; + tp->snd_ssthresh = TCP_INFINITE_SSTHRESH; +- bbr->rtt_cnt = 0; + bbr->next_rtt_delivered = tp->delivered; + bbr->prev_ca_state = TCP_CA_Open; +- bbr->packet_conservation = 0; + + bbr->probe_rtt_done_stamp = 0; + bbr->probe_rtt_round_done = 0; ++ bbr->probe_rtt_min_us = tcp_min_rtt(tp); ++ bbr->probe_rtt_min_stamp = tcp_jiffies32; + bbr->min_rtt_us = tcp_min_rtt(tp); + bbr->min_rtt_stamp = tcp_jiffies32; + +- minmax_reset(&bbr->bw, bbr->rtt_cnt, 0); /* init max bw to 0 */ +- + bbr->has_seen_rtt = 0; + bbr_init_pacing_rate_from_rtt(sk); + +@@ -1064,7 +2104,7 @@ + bbr->full_bw_cnt = 0; + bbr->cycle_mstamp = 0; + bbr->cycle_idx = 0; +- bbr_reset_lt_bw_sampling(sk); ++ + bbr_reset_startup_mode(sk); + + bbr->ack_epoch_mstamp = tp->tcp_mstamp; +@@ -1074,82 +2114,243 @@ + bbr->extra_acked[0] = 0; + bbr->extra_acked[1] = 0; + ++ bbr->ce_state = 0; ++ bbr->prior_rcv_nxt = tp->rcv_nxt; ++ bbr->try_fast_path = 0; ++ + cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED); ++ ++ /* Start sampling ECN mark rate after first full flight is ACKed: */ ++ bbr->loss_round_delivered = tp->delivered + 1; ++ bbr->loss_round_start = 0; ++ bbr->undo_bw_lo = 0; ++ bbr->undo_inflight_lo = 0; ++ bbr->undo_inflight_hi = 0; ++ bbr->loss_events_in_round = 0; ++ bbr->startup_ecn_rounds = 0; ++ bbr_reset_congestion_signals(sk); ++ bbr->bw_lo = ~0U; ++ bbr->bw_hi[0] = 0; ++ bbr->bw_hi[1] = 0; ++ bbr->inflight_lo = ~0U; ++ bbr->inflight_hi = ~0U; ++ bbr_reset_full_bw(sk); ++ bbr->bw_probe_up_cnt = ~0U; ++ bbr->bw_probe_up_acks = 0; ++ bbr->bw_probe_up_rounds = 0; ++ bbr->probe_wait_us = 0; ++ bbr->stopped_risky_probe = 0; ++ bbr->ack_phase = BBR_ACKS_INIT; ++ bbr->rounds_since_probe = 0; ++ bbr->bw_probe_samples = 0; ++ bbr->prev_probe_too_high = 0; ++ bbr->ecn_eligible = 0; ++ bbr->ecn_alpha = bbr_param(sk, ecn_alpha_init); ++ bbr->alpha_last_delivered = 0; ++ bbr->alpha_last_delivered_ce = 0; ++ bbr->plb.pause_until = 0; ++ ++ tp->fast_ack_mode = bbr_fast_ack_mode ? 1 : 0; ++ ++ if (bbr_can_use_ecn(sk)) ++ tp->ecn_flags |= TCP_ECN_ECT_PERMANENT; + } + +-__bpf_kfunc static u32 bbr_sndbuf_expand(struct sock *sk) ++/* BBR marks the current round trip as a loss round. */ ++static void bbr_note_loss(struct sock *sk) + { +- /* Provision 3 * cwnd since BBR may slow-start even during recovery. */ +- return 3; ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ ++ /* Capture "current" data over the full round trip of loss, to ++ * have a better chance of observing the full capacity of the path. ++ */ ++ if (!bbr->loss_in_round) /* first loss in this round trip? */ ++ bbr->loss_round_delivered = tp->delivered; /* set round trip */ ++ bbr->loss_in_round = 1; ++ bbr->loss_in_cycle = 1; + } + +-/* In theory BBR does not need to undo the cwnd since it does not +- * always reduce cwnd on losses (see bbr_main()). Keep it for now. +- */ ++/* Core TCP stack informs us that the given skb was just marked lost. */ ++__bpf_kfunc static void bbr_skb_marked_lost(struct sock *sk, ++ const struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ struct tcp_skb_cb *scb = TCP_SKB_CB(skb); ++ struct rate_sample rs = {}; ++ ++ bbr_note_loss(sk); ++ ++ if (!bbr->bw_probe_samples) ++ return; /* not an skb sent while probing for bandwidth */ ++ if (unlikely(!scb->tx.delivered_mstamp)) ++ return; /* skb was SACKed, reneged, marked lost; ignore it */ ++ /* We are probing for bandwidth. Construct a rate sample that ++ * estimates what happened in the flight leading up to this lost skb, ++ * then see if the loss rate went too high, and if so at which packet. ++ */ ++ rs.tx_in_flight = scb->tx.in_flight; ++ rs.lost = tp->lost - scb->tx.lost; ++ rs.is_app_limited = scb->tx.is_app_limited; ++ if (bbr_is_inflight_too_high(sk, &rs)) { ++ rs.tx_in_flight = bbr_inflight_hi_from_lost_skb(sk, &rs, skb); ++ bbr_handle_inflight_too_high(sk, &rs); ++ } ++} ++ ++static void bbr_run_loss_probe_recovery(struct sock *sk) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ struct bbr *bbr = inet_csk_ca(sk); ++ struct rate_sample rs = {0}; ++ ++ bbr_note_loss(sk); ++ ++ if (!bbr->bw_probe_samples) ++ return; /* not sent while probing for bandwidth */ ++ /* We are probing for bandwidth. Construct a rate sample that ++ * estimates what happened in the flight leading up to this ++ * loss, then see if the loss rate went too high. ++ */ ++ rs.lost = 1; /* TLP probe repaired loss of a single segment */ ++ rs.tx_in_flight = bbr->inflight_latest + rs.lost; ++ rs.is_app_limited = tp->tlp_orig_data_app_limited; ++ if (bbr_is_inflight_too_high(sk, &rs)) ++ bbr_handle_inflight_too_high(sk, &rs); ++} ++ ++/* Revert short-term model if current loss recovery event was spurious. */ + __bpf_kfunc static u32 bbr_undo_cwnd(struct sock *sk) + { + struct bbr *bbr = inet_csk_ca(sk); + +- bbr->full_bw = 0; /* spurious slow-down; reset full pipe detection */ +- bbr->full_bw_cnt = 0; +- bbr_reset_lt_bw_sampling(sk); +- return tcp_snd_cwnd(tcp_sk(sk)); ++ bbr_reset_full_bw(sk); /* spurious slow-down; reset full bw detector */ ++ bbr->loss_in_round = 0; ++ ++ /* Revert to cwnd and other state saved before loss episode. */ ++ bbr->bw_lo = max(bbr->bw_lo, bbr->undo_bw_lo); ++ bbr->inflight_lo = max(bbr->inflight_lo, bbr->undo_inflight_lo); ++ bbr->inflight_hi = max(bbr->inflight_hi, bbr->undo_inflight_hi); ++ bbr->try_fast_path = 0; /* take slow path to set proper cwnd, pacing */ ++ return bbr->prior_cwnd; + } + +-/* Entering loss recovery, so save cwnd for when we exit or undo recovery. */ ++/* Entering loss recovery, so save state for when we undo recovery. */ + __bpf_kfunc static u32 bbr_ssthresh(struct sock *sk) + { ++ struct bbr *bbr = inet_csk_ca(sk); ++ + bbr_save_cwnd(sk); ++ /* For undo, save state that adapts based on loss signal. */ ++ bbr->undo_bw_lo = bbr->bw_lo; ++ bbr->undo_inflight_lo = bbr->inflight_lo; ++ bbr->undo_inflight_hi = bbr->inflight_hi; + return tcp_sk(sk)->snd_ssthresh; + } + ++static enum tcp_bbr_phase bbr_get_phase(struct bbr *bbr) ++{ ++ switch (bbr->mode) { ++ case BBR_STARTUP: ++ return BBR_PHASE_STARTUP; ++ case BBR_DRAIN: ++ return BBR_PHASE_DRAIN; ++ case BBR_PROBE_BW: ++ break; ++ case BBR_PROBE_RTT: ++ return BBR_PHASE_PROBE_RTT; ++ default: ++ return BBR_PHASE_INVALID; ++ } ++ switch (bbr->cycle_idx) { ++ case BBR_BW_PROBE_UP: ++ return BBR_PHASE_PROBE_BW_UP; ++ case BBR_BW_PROBE_DOWN: ++ return BBR_PHASE_PROBE_BW_DOWN; ++ case BBR_BW_PROBE_CRUISE: ++ return BBR_PHASE_PROBE_BW_CRUISE; ++ case BBR_BW_PROBE_REFILL: ++ return BBR_PHASE_PROBE_BW_REFILL; ++ default: ++ return BBR_PHASE_INVALID; ++ } ++} ++ + static size_t bbr_get_info(struct sock *sk, u32 ext, int *attr, +- union tcp_cc_info *info) ++ union tcp_cc_info *info) + { + if (ext & (1 << (INET_DIAG_BBRINFO - 1)) || + ext & (1 << (INET_DIAG_VEGASINFO - 1))) { +- struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); +- u64 bw = bbr_bw(sk); +- +- bw = bw * tp->mss_cache * USEC_PER_SEC >> BW_SCALE; +- memset(&info->bbr, 0, sizeof(info->bbr)); +- info->bbr.bbr_bw_lo = (u32)bw; +- info->bbr.bbr_bw_hi = (u32)(bw >> 32); +- info->bbr.bbr_min_rtt = bbr->min_rtt_us; +- info->bbr.bbr_pacing_gain = bbr->pacing_gain; +- info->bbr.bbr_cwnd_gain = bbr->cwnd_gain; ++ u64 bw = bbr_bw_bytes_per_sec(sk, bbr_bw(sk)); ++ u64 bw_hi = bbr_bw_bytes_per_sec(sk, bbr_max_bw(sk)); ++ u64 bw_lo = bbr->bw_lo == ~0U ? ++ ~0ULL : bbr_bw_bytes_per_sec(sk, bbr->bw_lo); ++ struct tcp_bbr_info *bbr_info = &info->bbr; ++ ++ memset(bbr_info, 0, sizeof(*bbr_info)); ++ bbr_info->bbr_bw_lo = (u32)bw; ++ bbr_info->bbr_bw_hi = (u32)(bw >> 32); ++ bbr_info->bbr_min_rtt = bbr->min_rtt_us; ++ bbr_info->bbr_pacing_gain = bbr->pacing_gain; ++ bbr_info->bbr_cwnd_gain = bbr->cwnd_gain; ++ bbr_info->bbr_bw_hi_lsb = (u32)bw_hi; ++ bbr_info->bbr_bw_hi_msb = (u32)(bw_hi >> 32); ++ bbr_info->bbr_bw_lo_lsb = (u32)bw_lo; ++ bbr_info->bbr_bw_lo_msb = (u32)(bw_lo >> 32); ++ bbr_info->bbr_mode = bbr->mode; ++ bbr_info->bbr_phase = (__u8)bbr_get_phase(bbr); ++ bbr_info->bbr_version = (__u8)BBR_VERSION; ++ bbr_info->bbr_inflight_lo = bbr->inflight_lo; ++ bbr_info->bbr_inflight_hi = bbr->inflight_hi; ++ bbr_info->bbr_extra_acked = bbr_extra_acked(sk); + *attr = INET_DIAG_BBRINFO; +- return sizeof(info->bbr); ++ return sizeof(*bbr_info); + } + return 0; + } + + __bpf_kfunc static void bbr_set_state(struct sock *sk, u8 new_state) + { ++ struct tcp_sock *tp = tcp_sk(sk); + struct bbr *bbr = inet_csk_ca(sk); + + if (new_state == TCP_CA_Loss) { +- struct rate_sample rs = { .losses = 1 }; + + bbr->prev_ca_state = TCP_CA_Loss; +- bbr->full_bw = 0; +- bbr->round_start = 1; /* treat RTO like end of a round */ +- bbr_lt_bw_sampling(sk, &rs); ++ tcp_plb_update_state_upon_rto(sk, &bbr->plb); ++ /* The tcp_write_timeout() call to sk_rethink_txhash() likely ++ * repathed this flow, so re-learn the min network RTT on the ++ * new path: ++ */ ++ bbr_reset_full_bw(sk); ++ if (!bbr_is_probing_bandwidth(sk) && bbr->inflight_lo == ~0U) { ++ /* bbr_adapt_lower_bounds() needs cwnd before ++ * we suffered an RTO, to update inflight_lo: ++ */ ++ bbr->inflight_lo = ++ max(tcp_snd_cwnd(tp), bbr->prior_cwnd); ++ } ++ } else if (bbr->prev_ca_state == TCP_CA_Loss && ++ new_state != TCP_CA_Loss) { ++ bbr_exit_loss_recovery(sk); + } + } + ++ + static struct tcp_congestion_ops tcp_bbr_cong_ops __read_mostly = { +- .flags = TCP_CONG_NON_RESTRICTED, ++ .flags = TCP_CONG_NON_RESTRICTED | TCP_CONG_WANTS_CE_EVENTS, + .name = "bbr", + .owner = THIS_MODULE, + .init = bbr_init, + .cong_control = bbr_main, + .sndbuf_expand = bbr_sndbuf_expand, ++ .skb_marked_lost = bbr_skb_marked_lost, + .undo_cwnd = bbr_undo_cwnd, + .cwnd_event = bbr_cwnd_event, + .ssthresh = bbr_ssthresh, +- .min_tso_segs = bbr_min_tso_segs, ++ .tso_segs = bbr_tso_segs, + .get_info = bbr_get_info, + .set_state = bbr_set_state, + }; +@@ -1160,10 +2361,11 @@ + BTF_ID_FLAGS(func, bbr_init) + BTF_ID_FLAGS(func, bbr_main) + BTF_ID_FLAGS(func, bbr_sndbuf_expand) ++BTF_ID_FLAGS(func, bbr_skb_marked_lost) + BTF_ID_FLAGS(func, bbr_undo_cwnd) + BTF_ID_FLAGS(func, bbr_cwnd_event) + BTF_ID_FLAGS(func, bbr_ssthresh) +-BTF_ID_FLAGS(func, bbr_min_tso_segs) ++BTF_ID_FLAGS(func, bbr_tso_segs) + BTF_ID_FLAGS(func, bbr_set_state) + #endif + #endif +@@ -1198,5 +2400,12 @@ + MODULE_AUTHOR("Neal Cardwell "); + MODULE_AUTHOR("Yuchung Cheng "); + MODULE_AUTHOR("Soheil Hassas Yeganeh "); ++MODULE_AUTHOR("Priyaranjan Jha "); ++MODULE_AUTHOR("Yousuk Seung "); ++MODULE_AUTHOR("Kevin Yang "); ++MODULE_AUTHOR("Arjun Roy "); ++MODULE_AUTHOR("David Morley "); ++ + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("TCP BBR (Bottleneck Bandwidth and RTT)"); ++MODULE_VERSION(__stringify(BBR_VERSION)); +diff '--color=auto' -uraN a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c +--- a/net/ipv4/tcp_cong.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp_cong.c 2023-11-04 16:35:57.804649973 +0300 +@@ -241,6 +241,7 @@ + struct inet_connection_sock *icsk = inet_csk(sk); + + tcp_sk(sk)->prior_ssthresh = 0; ++ tcp_sk(sk)->fast_ack_mode = 0; + if (icsk->icsk_ca_ops->init) + icsk->icsk_ca_ops->init(sk); + if (tcp_ca_needs_ecn(sk)) +diff '--color=auto' -uraN a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c +--- a/net/ipv4/tcp_input.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp_input.c 2023-11-04 16:35:57.804649973 +0300 +@@ -361,7 +361,7 @@ + tcp_enter_quickack_mode(sk, 2); + break; + case INET_ECN_CE: +- if (tcp_ca_needs_ecn(sk)) ++ if (tcp_ca_wants_ce_events(sk)) + tcp_ca_event(sk, CA_EVENT_ECN_IS_CE); + + if (!(tp->ecn_flags & TCP_ECN_DEMAND_CWR)) { +@@ -372,7 +372,7 @@ + tp->ecn_flags |= TCP_ECN_SEEN; + break; + default: +- if (tcp_ca_needs_ecn(sk)) ++ if (tcp_ca_wants_ce_events(sk)) + tcp_ca_event(sk, CA_EVENT_ECN_NO_CE); + tp->ecn_flags |= TCP_ECN_SEEN; + break; +@@ -1091,7 +1091,12 @@ + */ + static void tcp_notify_skb_loss_event(struct tcp_sock *tp, const struct sk_buff *skb) + { ++ struct sock *sk = (struct sock *)tp; ++ const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops; ++ + tp->lost += tcp_skb_pcount(skb); ++ if (ca_ops->skb_marked_lost) ++ ca_ops->skb_marked_lost(sk, skb); + } + + void tcp_mark_skb_lost(struct sock *sk, struct sk_buff *skb) +@@ -1472,6 +1477,17 @@ + WARN_ON_ONCE(tcp_skb_pcount(skb) < pcount); + tcp_skb_pcount_add(skb, -pcount); + ++ /* Adjust tx.in_flight as pcount is shifted from skb to prev. */ ++ if (WARN_ONCE(TCP_SKB_CB(skb)->tx.in_flight < pcount, ++ "prev in_flight: %u skb in_flight: %u pcount: %u", ++ TCP_SKB_CB(prev)->tx.in_flight, ++ TCP_SKB_CB(skb)->tx.in_flight, ++ pcount)) ++ TCP_SKB_CB(skb)->tx.in_flight = 0; ++ else ++ TCP_SKB_CB(skb)->tx.in_flight -= pcount; ++ TCP_SKB_CB(prev)->tx.in_flight += pcount; ++ + /* 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 +@@ -3700,7 +3716,8 @@ + /* 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 + */ +-static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) ++static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag, ++ struct rate_sample *rs) + { + struct tcp_sock *tp = tcp_sk(sk); + +@@ -3717,6 +3734,7 @@ + /* ACK advances: there was a loss, so reduce cwnd. Reset + * tlp_high_seq in tcp_init_cwnd_reduction() + */ ++ tcp_ca_event(sk, CA_EVENT_TLP_RECOVERY); + tcp_init_cwnd_reduction(sk); + tcp_set_ca_state(sk, TCP_CA_CWR); + tcp_end_cwnd_reduction(sk); +@@ -3727,6 +3745,11 @@ + FLAG_NOT_DUP | FLAG_DATA_SACKED))) { + /* Pure dupack: original and TLP probe arrived; no loss */ + tp->tlp_high_seq = 0; ++ } else { ++ /* This ACK matches a TLP retransmit. We cannot yet tell if ++ * this ACK is for the original or the TLP retransmit. ++ */ ++ rs->is_acking_tlp_retrans_seq = 1; + } + } + +@@ -3831,6 +3854,7 @@ + + prior_fack = tcp_is_sack(tp) ? tcp_highest_sack_seq(tp) : tp->snd_una; + rs.prior_in_flight = tcp_packets_in_flight(tp); ++ tcp_rate_check_app_limited(sk); + + /* ts_recent update must be made after we are sure that the packet + * is in window. +@@ -3905,7 +3929,7 @@ + tcp_rack_update_reo_wnd(sk, &rs); + + if (tp->tlp_high_seq) +- tcp_process_tlp_ack(sk, ack, flag); ++ tcp_process_tlp_ack(sk, ack, flag, &rs); + + if (tcp_ack_is_dubious(sk, flag)) { + if (!(flag & (FLAG_SND_UNA_ADVANCED | +@@ -3929,6 +3953,7 @@ + delivered = tcp_newly_delivered(sk, delivered, flag); + lost = tp->lost - lost; /* freshly marked lost */ + rs.is_ack_delayed = !!(flag & FLAG_ACK_MAYBE_DELAYED); ++ rs.is_ece = !!(flag & FLAG_ECE); + 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); +@@ -3948,7 +3973,7 @@ + tcp_ack_probe(sk); + + if (tp->tlp_high_seq) +- tcp_process_tlp_ack(sk, ack, flag); ++ tcp_process_tlp_ack(sk, ack, flag, &rs); + return 1; + + old_ack: +@@ -5539,13 +5564,14 @@ + + /* More than one full frame received... */ + if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss && ++ (tp->fast_ack_mode == 1 || + /* ... and right edge of window advances far enough. + * (tcp_recvmsg() will send ACK otherwise). + * If application uses SO_RCVLOWAT, we want send ack now if + * we have not received enough bytes to satisfy the condition. + */ +- (tp->rcv_nxt - tp->copied_seq < sk->sk_rcvlowat || +- __tcp_select_window(sk) >= tp->rcv_wnd)) || ++ (tp->rcv_nxt - tp->copied_seq < sk->sk_rcvlowat || ++ __tcp_select_window(sk) >= tp->rcv_wnd))) || + /* We ACK each frame or... */ + tcp_in_quickack_mode(sk) || + /* Protocol state mandates a one-time immediate ACK */ +diff '--color=auto' -uraN a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c +--- a/net/ipv4/tcp_minisocks.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp_minisocks.c 2023-11-04 16:35:57.804649973 +0300 +@@ -440,6 +440,8 @@ + u32 ca_key = dst_metric(dst, RTAX_CC_ALGO); + bool ca_got_dst = false; + ++ tcp_set_ecn_low_from_dst(sk, dst); ++ + if (ca_key != TCP_CA_UNSPEC) { + const struct tcp_congestion_ops *ca; + +diff '--color=auto' -uraN a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c +--- a/net/ipv4/tcp_output.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp_output.c 2023-11-04 16:35:57.804649973 +0300 +@@ -324,10 +324,9 @@ + bool bpf_needs_ecn = tcp_bpf_ca_needs_ecn(sk); + bool use_ecn = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_ecn) == 1 || + tcp_ca_needs_ecn(sk) || bpf_needs_ecn; ++ const struct dst_entry *dst = __sk_dst_get(sk); + + if (!use_ecn) { +- const struct dst_entry *dst = __sk_dst_get(sk); +- + if (dst && dst_feature(dst, RTAX_FEATURE_ECN)) + use_ecn = true; + } +@@ -339,6 +338,9 @@ + tp->ecn_flags = TCP_ECN_OK; + if (tcp_ca_needs_ecn(sk) || bpf_needs_ecn) + INET_ECN_xmit(sk); ++ ++ if (dst) ++ tcp_set_ecn_low_from_dst(sk, dst); + } + } + +@@ -376,7 +378,8 @@ + th->cwr = 1; + skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN; + } +- } else if (!tcp_ca_needs_ecn(sk)) { ++ } else if (!(tp->ecn_flags & TCP_ECN_ECT_PERMANENT) && ++ !tcp_ca_needs_ecn(sk)) { + /* ACK or retransmitted segment: clear ECT|CE */ + INET_ECN_dontxmit(sk); + } +@@ -1531,7 +1534,7 @@ + { + struct tcp_sock *tp = tcp_sk(sk); + struct sk_buff *buff; +- int old_factor; ++ int old_factor, inflight_prev; + long limit; + int nlen; + u8 flags; +@@ -1606,6 +1609,30 @@ + + if (diff) + tcp_adjust_pcount(sk, skb, diff); ++ ++ inflight_prev = TCP_SKB_CB(skb)->tx.in_flight - old_factor; ++ if (inflight_prev < 0) { ++ WARN_ONCE(tcp_skb_tx_in_flight_is_suspicious( ++ old_factor, ++ TCP_SKB_CB(skb)->sacked, ++ TCP_SKB_CB(skb)->tx.in_flight), ++ "inconsistent: tx.in_flight: %u " ++ "old_factor: %d mss: %u sacked: %u " ++ "1st pcount: %d 2nd pcount: %d " ++ "1st len: %u 2nd len: %u ", ++ TCP_SKB_CB(skb)->tx.in_flight, old_factor, ++ mss_now, TCP_SKB_CB(skb)->sacked, ++ tcp_skb_pcount(skb), tcp_skb_pcount(buff), ++ skb->len, buff->len); ++ inflight_prev = 0; ++ } ++ /* Set 1st tx.in_flight as if 1st were sent by itself: */ ++ TCP_SKB_CB(skb)->tx.in_flight = inflight_prev + ++ tcp_skb_pcount(skb); ++ /* Set 2nd tx.in_flight with new 1st and 2nd pcounts: */ ++ TCP_SKB_CB(buff)->tx.in_flight = inflight_prev + ++ tcp_skb_pcount(skb) + ++ tcp_skb_pcount(buff); + } + + /* Link BUFF into the send queue. */ +@@ -1981,13 +2008,12 @@ + static u32 tcp_tso_segs(struct sock *sk, unsigned int mss_now) + { + const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops; +- u32 min_tso, tso_segs; +- +- min_tso = ca_ops->min_tso_segs ? +- ca_ops->min_tso_segs(sk) : +- READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_min_tso_segs); ++ u32 tso_segs; + +- tso_segs = tcp_tso_autosize(sk, mss_now, min_tso); ++ tso_segs = ca_ops->tso_segs ? ++ ca_ops->tso_segs(sk, mss_now) : ++ tcp_tso_autosize(sk, mss_now, ++ sock_net(sk)->ipv4.sysctl_tcp_min_tso_segs); + return min_t(u32, tso_segs, sk->sk_gso_max_segs); + } + +@@ -2686,6 +2712,7 @@ + skb_set_delivery_time(skb, tp->tcp_wstamp_ns, true); + list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue); + tcp_init_tso_segs(skb, mss_now); ++ tcp_set_tx_in_flight(sk, skb); + goto repair; /* Skip network transmission */ + } + +@@ -2899,6 +2926,7 @@ + if (WARN_ON(!skb || !tcp_skb_pcount(skb))) + goto rearm_timer; + ++ tp->tlp_orig_data_app_limited = TCP_SKB_CB(skb)->tx.is_app_limited; + if (__tcp_retransmit_skb(sk, skb, 1)) + goto rearm_timer; + +diff '--color=auto' -uraN a/net/ipv4/tcp_rate.c b/net/ipv4/tcp_rate.c +--- a/net/ipv4/tcp_rate.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp_rate.c 2023-11-04 16:35:57.804649973 +0300 +@@ -34,6 +34,24 @@ + * ready to send in the write queue. + */ + ++void tcp_set_tx_in_flight(struct sock *sk, struct sk_buff *skb) ++{ ++ struct tcp_sock *tp = tcp_sk(sk); ++ u32 in_flight; ++ ++ /* Check, sanitize, and record packets in flight after skb was sent. */ ++ in_flight = tcp_packets_in_flight(tp) + tcp_skb_pcount(skb); ++ if (WARN_ONCE(in_flight > TCPCB_IN_FLIGHT_MAX, ++ "insane in_flight %u cc %s mss %u " ++ "cwnd %u pif %u %u %u %u\n", ++ in_flight, inet_csk(sk)->icsk_ca_ops->name, ++ tp->mss_cache, tp->snd_cwnd, ++ tp->packets_out, tp->retrans_out, ++ tp->sacked_out, tp->lost_out)) ++ in_flight = TCPCB_IN_FLIGHT_MAX; ++ TCP_SKB_CB(skb)->tx.in_flight = in_flight; ++} ++ + /* Snapshot the current delivery information in the skb, to generate + * a rate sample later when the skb is (s)acked in tcp_rate_skb_delivered(). + */ +@@ -66,7 +84,9 @@ + TCP_SKB_CB(skb)->tx.delivered_mstamp = tp->delivered_mstamp; + TCP_SKB_CB(skb)->tx.delivered = tp->delivered; + TCP_SKB_CB(skb)->tx.delivered_ce = tp->delivered_ce; ++ TCP_SKB_CB(skb)->tx.lost = tp->lost; + TCP_SKB_CB(skb)->tx.is_app_limited = tp->app_limited ? 1 : 0; ++ tcp_set_tx_in_flight(sk, skb); + } + + /* When an skb is sacked or acked, we fill in the rate sample with the (prior) +@@ -91,18 +111,21 @@ + if (!rs->prior_delivered || + tcp_skb_sent_after(tx_tstamp, tp->first_tx_mstamp, + scb->end_seq, rs->last_end_seq)) { ++ rs->prior_lost = scb->tx.lost; + rs->prior_delivered_ce = scb->tx.delivered_ce; + rs->prior_delivered = scb->tx.delivered; + rs->prior_mstamp = scb->tx.delivered_mstamp; + rs->is_app_limited = scb->tx.is_app_limited; + rs->is_retrans = scb->sacked & TCPCB_RETRANS; ++ rs->tx_in_flight = scb->tx.in_flight; + rs->last_end_seq = scb->end_seq; + + /* Record send time of most recently ACKed packet: */ + tp->first_tx_mstamp = tx_tstamp; + /* Find the duration of the "send phase" of this window: */ +- rs->interval_us = tcp_stamp_us_delta(tp->first_tx_mstamp, +- scb->tx.first_tx_mstamp); ++ rs->interval_us = tcp_stamp32_us_delta( ++ tp->first_tx_mstamp, ++ scb->tx.first_tx_mstamp); + + } + /* Mark off the skb delivered once it's sacked to avoid being +@@ -144,6 +167,7 @@ + return; + } + rs->delivered = tp->delivered - rs->prior_delivered; ++ rs->lost = tp->lost - rs->prior_lost; + + rs->delivered_ce = tp->delivered_ce - rs->prior_delivered_ce; + /* delivered_ce occupies less than 32 bits in the skb control block */ +@@ -155,7 +179,7 @@ + * longer phase. + */ + snd_us = rs->interval_us; /* send phase */ +- ack_us = tcp_stamp_us_delta(tp->tcp_mstamp, ++ ack_us = tcp_stamp32_us_delta(tp->tcp_mstamp, + rs->prior_mstamp); /* ack phase */ + rs->interval_us = max(snd_us, ack_us); + +diff '--color=auto' -uraN a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c +--- a/net/ipv4/tcp_timer.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/net/ipv4/tcp_timer.c 2023-11-04 16:35:57.804649973 +0300 +@@ -642,6 +642,7 @@ + return; + } + ++ tcp_rate_check_app_limited(sk); + tcp_mstamp_refresh(tcp_sk(sk)); + event = icsk->icsk_pending; + +diff '--color=auto' -uraN a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o +--- a/scripts/Makefile.vmlinux_o 2023-10-25 13:16:30.000000000 +0300 ++++ b/scripts/Makefile.vmlinux_o 2023-11-04 16:35:57.827983725 +0300 +@@ -19,7 +19,7 @@ + + .tmp_initcalls.lds: $(srctree)/scripts/generate_initcall_order.pl \ + vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE +- $(call if_changed,gen_initcalls_lds) ++ +$(call if_changed,gen_initcalls_lds) + + targets := .tmp_initcalls.lds + +diff '--color=auto' -uraN a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile +--- a/sound/pci/hda/Makefile 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/pci/hda/Makefile 2023-11-04 16:36:53.528982602 +0300 +@@ -28,7 +28,7 @@ + snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o + + # side codecs +-snd-hda-scodec-cs35l41-objs := cs35l41_hda.o ++snd-hda-scodec-cs35l41-objs := cs35l41_hda.o cs35l41_hda_property.o + snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o + snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o + snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o +diff '--color=auto' -uraN a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +--- a/sound/pci/hda/cs35l41_hda.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/pci/hda/cs35l41_hda.c 2023-11-04 16:45:46.504039154 +0300 +@@ -19,6 +19,7 @@ + #include "hda_component.h" + #include "cs35l41_hda.h" + #include "hda_cs_dsp_ctl.h" ++#include "cs35l41_hda_property.h" + + #define CS35L41_FIRMWARE_ROOT "cirrus/" + #define CS35L41_PART "cs35l41" +@@ -58,8 +59,6 @@ + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000020 }, // DSP1RX5 SRC = ERRVOL +- { CS35L41_AMP_DIG_VOL_CTRL, 0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM 0.0 dB +- { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB + }; + + static const struct reg_sequence cs35l41_hda_config_dsp[] = { +@@ -82,6 +81,14 @@ + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000029 }, // DSP1RX5 SRC = VBSTMON ++}; ++ ++static const struct reg_sequence cs35l41_hda_unmute[] = { ++ { CS35L41_AMP_DIG_VOL_CTRL, 0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM 0.0 dB ++ { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB ++}; ++ ++static const struct reg_sequence cs35l41_hda_unmute_dsp[] = { + { CS35L41_AMP_DIG_VOL_CTRL, 0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM 0.0 dB + { CS35L41_AMP_GAIN_CTRL, 0x00000233 }, // AMP_GAIN_PCM = 17.5dB AMP_GAIN_PDM = 19.5dB + }; +@@ -526,73 +533,159 @@ + cs35l41->irq_errors = 0; + } + +-static void cs35l41_hda_playback_hook(struct device *dev, int action) ++static void cs35l41_hda_play_start(struct device *dev) + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct regmap *reg = cs35l41->regmap; +- int ret = 0; ++ ++ dev_dbg(dev, "Play (Start)\n"); ++ ++ if (cs35l41->playback_started) { ++ dev_dbg(dev, "Playback already started."); ++ return; ++ } ++ ++ cs35l41->playback_started = true; ++ ++ if (cs35l41->firmware_running) { ++ regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, ++ ARRAY_SIZE(cs35l41_hda_config_dsp)); ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, ++ CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, ++ 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); ++ cs35l41_set_cspl_mbox_cmd(cs35l41->dev, reg, CSPL_MBOX_CMD_RESUME); ++ } else { ++ regmap_multi_reg_write(reg, cs35l41_hda_config, ARRAY_SIZE(cs35l41_hda_config)); ++ } ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) ++ regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001); ++ ++} ++ ++static void cs35l41_hda_play_done(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ struct regmap *reg = cs35l41->regmap; ++ ++ dev_dbg(dev, "Play (Complete)\n"); ++ ++ 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, ++ ARRAY_SIZE(cs35l41_hda_unmute_dsp)); ++ } else { ++ regmap_multi_reg_write(reg, cs35l41_hda_unmute, ++ ARRAY_SIZE(cs35l41_hda_unmute)); ++ } ++} ++ ++static void cs35l41_hda_pause_start(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ struct regmap *reg = cs35l41->regmap; ++ ++ 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, ++ cs35l41->firmware_running); ++} ++ ++static void cs35l41_hda_pause_done(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ struct regmap *reg = cs35l41->regmap; ++ ++ dev_dbg(dev, "Pause (Complete)\n"); ++ ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) ++ regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); ++ if (cs35l41->firmware_running) { ++ cs35l41_set_cspl_mbox_cmd(dev, reg, CSPL_MBOX_CMD_PAUSE); ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, ++ CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, ++ 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); ++ } ++ cs35l41_irq_release(cs35l41); ++ cs35l41->playback_started = false; ++} ++ ++static void cs35l41_hda_pre_playback_hook(struct device *dev, int action) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + + switch (action) { +- case HDA_GEN_PCM_ACT_OPEN: +- pm_runtime_get_sync(dev); ++ case HDA_GEN_PCM_ACT_CLEANUP: + mutex_lock(&cs35l41->fw_mutex); +- cs35l41->playback_started = true; +- if (cs35l41->firmware_running) { +- regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, +- ARRAY_SIZE(cs35l41_hda_config_dsp)); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, +- 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); +- cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, +- CSPL_MBOX_CMD_RESUME); +- } else { +- regmap_multi_reg_write(reg, cs35l41_hda_config, +- ARRAY_SIZE(cs35l41_hda_config)); +- } +- ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, +- CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); +- if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) +- regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001); ++ cs35l41_hda_pause_start(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; ++ default: ++ break; ++ } ++} ++static void cs35l41_hda_playback_hook(struct device *dev, int action) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_OPEN: ++ /* ++ * All amps must be resumed before we can start playing back. ++ * This ensures, for external boost, that all amps are in AMP_SAFE mode. ++ * Do this in HDA_GEN_PCM_ACT_OPEN, since this is run prior to any of the ++ * other actions. ++ */ ++ pm_runtime_get_sync(dev); ++ break; + case HDA_GEN_PCM_ACT_PREPARE: + mutex_lock(&cs35l41->fw_mutex); +- ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1, NULL); ++ cs35l41_hda_play_start(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + mutex_lock(&cs35l41->fw_mutex); +- regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); +- ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0, NULL); ++ cs35l41_hda_pause_done(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLOSE: + mutex_lock(&cs35l41->fw_mutex); +- ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, +- CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); +- if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) +- regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); +- if (cs35l41->firmware_running) { +- cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, +- CSPL_MBOX_CMD_PAUSE); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, +- 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); ++ if (!cs35l41->firmware_running && cs35l41->request_fw_load && ++ !cs35l41->fw_request_ongoing) { ++ dev_info(dev, "Requesting Firmware Load after HDA_GEN_PCM_ACT_CLOSE\n"); ++ cs35l41->fw_request_ongoing = true; ++ schedule_work(&cs35l41->fw_load_work); + } +- cs35l41_irq_release(cs35l41); +- cs35l41->playback_started = false; + mutex_unlock(&cs35l41->fw_mutex); + ++ /* ++ * Playback must be finished for all amps before we start runtime suspend. ++ * This ensures no amps are playing back when we start putting them to sleep. ++ */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + break; + default: +- dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action); + break; + } ++} + +- if (ret) +- dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret); ++static void cs35l41_hda_post_playback_hook(struct device *dev, int action) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_PREPARE: ++ mutex_lock(&cs35l41->fw_mutex); ++ cs35l41_hda_play_done(dev); ++ mutex_unlock(&cs35l41->fw_mutex); ++ break; ++ default: ++ break; ++ } + } + + static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsigned int *tx_slot, +@@ -615,21 +708,62 @@ + rx_slot); + } + +-static void cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) ++static int cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) + { ++ int ret = 0; ++ + mutex_lock(&cs35l41->fw_mutex); + if (cs35l41->firmware_running) { + + regcache_cache_only(cs35l41->regmap, false); + +- cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); ++ ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); ++ if (ret) { ++ dev_warn(cs35l41->dev, "Unable to exit Hibernate."); ++ goto err; ++ } ++ ++ /* Test key needs to be unlocked to allow the OTP settings to re-apply */ ++ cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); ++ ret = regcache_sync(cs35l41->regmap); ++ cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); ++ if (ret) { ++ dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); ++ goto err; ++ } ++ ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) ++ cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); ++ + cs35l41_shutdown_dsp(cs35l41); + cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); ++ } ++err: ++ regcache_cache_only(cs35l41->regmap, true); ++ regcache_mark_dirty(cs35l41->regmap); ++ ++ mutex_unlock(&cs35l41->fw_mutex); ++ ++ return ret; ++} + +- regcache_cache_only(cs35l41->regmap, true); +- regcache_mark_dirty(cs35l41->regmap); ++static int cs35l41_system_suspend_prep(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ ++ dev_dbg(cs35l41->dev, "System Suspend Prepare\n"); ++ ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) { ++ dev_err_once(cs35l41->dev, "System Suspend not supported\n"); ++ return 0; /* don't block the whole system suspend */ + } ++ ++ mutex_lock(&cs35l41->fw_mutex); ++ if (cs35l41->playback_started) ++ cs35l41_hda_pause_start(dev); + mutex_unlock(&cs35l41->fw_mutex); ++ ++ return 0; + } + + static int cs35l41_system_suspend(struct device *dev) +@@ -644,18 +778,28 @@ + return 0; /* don't block the whole system suspend */ + } + ++ mutex_lock(&cs35l41->fw_mutex); ++ if (cs35l41->playback_started) ++ cs35l41_hda_pause_done(dev); ++ mutex_unlock(&cs35l41->fw_mutex); ++ + ret = pm_runtime_force_suspend(dev); +- if (ret) ++ if (ret) { ++ dev_err(dev, "System Suspend Failed, unable to runtime suspend: %d\n", ret); + return ret; ++ } + + /* Shutdown DSP before system suspend */ +- cs35l41_ready_for_reset(cs35l41); ++ ret = cs35l41_ready_for_reset(cs35l41); ++ ++ if (ret) ++ dev_err(dev, "System Suspend Failed, not ready for Reset: %d\n", ret); + + /* + * Reset GPIO may be shared, so cannot reset here. + * However beyond this point, amps may be powered down. + */ +- return 0; ++ return ret; + } + + static int cs35l41_system_resume(struct device *dev) +@@ -678,9 +822,14 @@ + usleep_range(2000, 2100); + + ret = pm_runtime_force_resume(dev); ++ if (ret) { ++ dev_err(dev, "System Resume Failed: Unable to runtime resume: %d\n", ret); ++ return ret; ++ } + + mutex_lock(&cs35l41->fw_mutex); +- if (!ret && cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) { ++ ++ if (cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) { + cs35l41->fw_request_ongoing = true; + schedule_work(&cs35l41->fw_load_work); + } +@@ -712,20 +861,6 @@ + + mutex_lock(&cs35l41->fw_mutex); + +- if (cs35l41->playback_started) { +- regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute, +- ARRAY_SIZE(cs35l41_hda_mute)); +- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, NULL); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); +- if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) +- regmap_write(cs35l41->regmap, CS35L41_GPIO1_CTRL1, 0x00000001); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, +- 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); +- cs35l41->playback_started = false; +- } +- + if (cs35l41->firmware_running) { + ret = cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, + cs35l41->hw_cfg.bst_type); +@@ -821,7 +956,12 @@ + goto clean_dsp; + } + +- cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); ++ ret = cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); ++ if (ret) { ++ dev_err(cs35l41->dev, "Error waiting for DSP to pause: %u\n", ret); ++ goto clean_dsp; ++ } ++ + cs35l41->firmware_running = true; + + return 0; +@@ -980,6 +1120,7 @@ + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct hda_component *comps = master_data; ++ unsigned int sleep_flags; + int ret = 0; + + if (!comps || cs35l41->index < 0 || cs35l41->index >= HDA_MAX_COMPONENTS) +@@ -1014,9 +1155,16 @@ + ret = cs35l41_create_controls(cs35l41); + + comps->playback_hook = cs35l41_hda_playback_hook; ++ comps->pre_playback_hook = cs35l41_hda_pre_playback_hook; ++ comps->post_playback_hook = cs35l41_hda_post_playback_hook; + + mutex_unlock(&cs35l41->fw_mutex); + ++ sleep_flags = lock_system_sleep(); ++ if (!device_link_add(&comps->codec->core.dev, cs35l41->dev, DL_FLAG_STATELESS)) ++ dev_warn(dev, "Unable to create device link\n"); ++ unlock_system_sleep(sleep_flags); ++ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + +@@ -1027,9 +1175,14 @@ + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct hda_component *comps = master_data; ++ unsigned int sleep_flags; + +- if (comps[cs35l41->index].dev == dev) ++ if (comps[cs35l41->index].dev == dev) { + memset(&comps[cs35l41->index], 0, sizeof(*comps)); ++ sleep_flags = lock_system_sleep(); ++ device_link_remove(&comps->codec->core.dev, cs35l41->dev); ++ unlock_system_sleep(sleep_flags); ++ } + } + + static const struct component_ops cs35l41_hda_comp_ops = { +@@ -1199,8 +1352,7 @@ + return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos); + } + +-static int cs35l41_get_speaker_id(struct device *dev, int amp_index, +- int num_amps, int fixed_gpio_id) ++int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int fixed_gpio_id) + { + struct gpio_desc *speaker_id_desc; + int speaker_id = -ENODEV; +@@ -1254,49 +1406,6 @@ + return speaker_id; + } + +-/* +- * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work. +- * And devices created by serial-multi-instantiate don't have their device struct +- * pointing to the correct fwnode, so acpi_dev must be used here. +- * And devm functions expect that the device requesting the resource has the correct +- * fwnode. +- */ +-static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physdev, int id, +- const char *hid) +-{ +- struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; +- +- /* check I2C address to assign the index */ +- 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->gpio2.func = CS35L41_INTERRUPT; +- hw_cfg->gpio2.valid = true; +- hw_cfg->valid = true; +- +- if (strncmp(hid, "CLSA0100", 8) == 0) { +- hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; +- } else if (strncmp(hid, "CLSA0101", 8) == 0) { +- hw_cfg->bst_type = CS35L41_EXT_BOOST; +- hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; +- hw_cfg->gpio1.valid = true; +- } else { +- /* +- * Note: CLSA010(0/1) are special cases which use a slightly different design. +- * All other HIDs e.g. CSC3551 require valid ACPI _DSD properties to be supported. +- */ +- dev_err(cs35l41->dev, "Error: ACPI _DSD Properties are missing for HID %s.\n", hid); +- hw_cfg->valid = false; +- hw_cfg->gpio1.valid = false; +- hw_cfg->gpio2.valid = false; +- return -EINVAL; +- } +- +- return 0; +-} +- + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) + { + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; +@@ -1322,12 +1431,17 @@ + sub = NULL; + cs35l41->acpi_subsystem_id = sub; + ++ ret = cs35l41_add_dsd_properties(cs35l41, physdev, id, hid); ++ if (!ret) { ++ dev_info(cs35l41->dev, "Using extra _DSD properties, bypassing _DSD in ACPI\n"); ++ goto put_physdev; ++ } ++ + property = "cirrus,dev-index"; + ret = device_property_count_u32(physdev, property); +- if (ret <= 0) { +- ret = cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid); +- goto err_put_physdev; +- } ++ if (ret <= 0) ++ goto err; ++ + if (ret > ARRAY_SIZE(values)) { + ret = -EINVAL; + goto err; +@@ -1417,7 +1531,10 @@ + + err: + dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret); +-err_put_physdev: ++ hw_cfg->valid = false; ++ hw_cfg->gpio1.valid = false; ++ hw_cfg->gpio2.valid = false; ++put_physdev: + put_device(physdev); + + return ret; +@@ -1469,27 +1586,27 @@ + 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; + } + +@@ -1512,7 +1629,7 @@ + + 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; + } + +@@ -1520,6 +1637,11 @@ + if (ret) + goto err; + ++ ret = regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute, ++ ARRAY_SIZE(cs35l41_hda_mute)); ++ if (ret) ++ goto err; ++ + INIT_WORK(&cs35l41->fw_load_work, cs35l41_fw_load_work); + mutex_init(&cs35l41->fw_mutex); + +@@ -1538,9 +1660,8 @@ + + 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; ++ dev_err_probe(cs35l41->dev, ret, "Register component failed\n"); ++ goto err_pm; + } + + dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", regid, reg_revid); +@@ -1548,6 +1669,7 @@ + return 0; + + err_pm: ++ pm_runtime_dont_use_autosuspend(cs35l41->dev); + pm_runtime_disable(cs35l41->dev); + pm_runtime_put_noidle(cs35l41->dev); + +@@ -1566,6 +1688,7 @@ + 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) +@@ -1585,6 +1708,7 @@ + const struct dev_pm_ops cs35l41_hda_pm_ops = { + RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, + cs35l41_runtime_idle) ++ .prepare = cs35l41_system_suspend_prep, + SYSTEM_SLEEP_PM_OPS(cs35l41_system_suspend, cs35l41_system_resume) + }; + EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41); +diff '--color=auto' -uraN a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h +--- a/sound/pci/hda/cs35l41_hda.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/pci/hda/cs35l41_hda.h 2023-11-04 16:36:53.528982602 +0300 +@@ -83,5 +83,6 @@ + int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, + struct regmap *regmap); + void cs35l41_hda_remove(struct device *dev); ++int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int fixed_gpio_id); + + #endif /*__CS35L41_HDA_H__*/ +diff '--color=auto' -uraN a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c +--- a/sound/pci/hda/cs35l41_hda_property.c 1970-01-01 03:00:00.000000000 +0300 ++++ b/sound/pci/hda/cs35l41_hda_property.c 2023-11-04 16:36:53.565649926 +0300 +@@ -0,0 +1,156 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// CS35L41 ALSA HDA Property driver ++// ++// Copyright 2023 Cirrus Logic, Inc. ++// ++// Author: Stefan Binding ++ ++#include ++#include ++#include ++#include ++#include "cs35l41_hda_property.h" ++ ++/* ++ * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work. ++ * And devices created by serial-multi-instantiate don't have their device struct ++ * pointing to the correct fwnode, so acpi_dev must be used here. ++ * And devm functions expect that the device requesting the resource has the correct ++ * fwnode. ++ */ ++static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid) ++{ ++ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; ++ ++ /* check I2C address to assign the index */ ++ 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->gpio2.func = CS35L41_INTERRUPT; ++ hw_cfg->gpio2.valid = true; ++ hw_cfg->valid = true; ++ ++ if (strcmp(hid, "CLSA0100") == 0) { ++ hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; ++ } else if (strcmp(hid, "CLSA0101") == 0) { ++ hw_cfg->bst_type = CS35L41_EXT_BOOST; ++ hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; ++ hw_cfg->gpio1.valid = true; ++ } ++ ++ 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; ++ ++ /* check SPI or I2C address to assign the index */ ++ cs35l41->index = (id == 0 || id == 0x40) ? 0 : 1; ++ cs35l41->channel_index = 0; ++ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2); ++ 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, "10431473") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431483") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431493") == 0) { ++ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 1, GPIOD_OUT_HIGH); ++ } else { ++ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); ++ } ++ ++ hw_cfg->valid = true; ++ ++ 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; ++ int (*add_prop)(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid); ++}; ++ ++const struct cs35l41_prop_model cs35l41_prop_model_table[] = { ++ { "CLSA0100", NULL, lenovo_legion_no_acpi }, ++ { "CLSA0101", NULL, lenovo_legion_no_acpi }, ++ { "CSC3551", "10431433", asus_rog_2023_spkr_id2 }, // ASUS GS650P - i2c ++ { "CSC3551", "10431463", asus_rog_2023_spkr_id2 }, // ASUS GA402X/N - i2c ++ { "CSC3551", "10431473", asus_rog_2023_spkr_id2 }, // ASUS GU604V - spi, reset gpio 1 ++ { "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_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 ++ { "CSC3551", "10431D1F", asus_rog_2023_spkr_id2 }, // ASUS G713P - i2c ++ { "CSC3551", "10431F1F", asus_rog_2023_spkr_id2 }, // ASUS H7604JV - spi, reset gpio 0 ++ {} ++}; ++ ++int cs35l41_add_dsd_properties(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid) ++{ ++ const struct cs35l41_prop_model *model; ++ ++ for (model = cs35l41_prop_model_table; model->hid > 0; model++) { ++ if (!strcmp(model->hid, hid) && ++ (!model->ssid || ++ (cs35l41->acpi_subsystem_id && ++ !strcmp(model->ssid, cs35l41->acpi_subsystem_id)))) ++ return model->add_prop(cs35l41, physdev, id, hid); ++ } ++ ++ return -ENOENT; ++} +diff '--color=auto' -uraN a/sound/pci/hda/cs35l41_hda_property.h b/sound/pci/hda/cs35l41_hda_property.h +--- a/sound/pci/hda/cs35l41_hda_property.h 1970-01-01 03:00:00.000000000 +0300 ++++ b/sound/pci/hda/cs35l41_hda_property.h 2023-11-04 16:36:53.528982602 +0300 +@@ -0,0 +1,18 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * CS35L41 ALSA HDA Property driver ++ * ++ * Copyright 2023 Cirrus Logic, Inc. ++ * ++ * Author: Stefan Binding ++ */ ++ ++#ifndef CS35L41_HDA_PROP_H ++#define CS35L41_HDA_PROP_H ++ ++#include ++#include "cs35l41_hda.h" ++ ++int cs35l41_add_dsd_properties(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid); ++#endif /* CS35L41_HDA_PROP_H */ +diff '--color=auto' -uraN a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h +--- a/sound/pci/hda/hda_component.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/pci/hda/hda_component.h 2023-11-04 16:36:53.532315995 +0300 +@@ -15,5 +15,7 @@ + struct device *dev; + char name[HDA_MAX_NAME_SIZE]; + struct hda_codec *codec; ++ void (*pre_playback_hook)(struct device *dev, int action); + void (*playback_hook)(struct device *dev, int action); ++ void (*post_playback_hook)(struct device *dev, int action); + }; +diff '--color=auto' -uraN a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c +--- a/sound/pci/hda/patch_realtek.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/pci/hda/patch_realtek.c 2023-11-04 16:36:53.532315995 +0300 +@@ -6732,9 +6732,17 @@ + int i; + + for (i = 0; i < HDA_MAX_COMPONENTS; i++) { +- if (spec->comps[i].dev) ++ if (spec->comps[i].dev && spec->comps[i].pre_playback_hook) ++ spec->comps[i].pre_playback_hook(spec->comps[i].dev, action); ++ } ++ for (i = 0; i < HDA_MAX_COMPONENTS; i++) { ++ if (spec->comps[i].dev && spec->comps[i].playback_hook) + spec->comps[i].playback_hook(spec->comps[i].dev, action); + } ++ for (i = 0; i < HDA_MAX_COMPONENTS; i++) { ++ if (spec->comps[i].dev && spec->comps[i].post_playback_hook) ++ spec->comps[i].post_playback_hook(spec->comps[i].dev, action); ++ } + } + + struct cs35l41_dev_name { +@@ -9748,6 +9756,7 @@ + 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), ++ SND_PCI_QUIRK(0x1043, 0x18d3, "Asus Zenbook", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), +diff '--color=auto' -uraN a/sound/soc/codecs/cs35l41-i2c.c b/sound/soc/codecs/cs35l41-i2c.c +--- a/sound/soc/codecs/cs35l41-i2c.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/soc/codecs/cs35l41-i2c.c 2023-11-04 16:36:53.562316533 +0300 +@@ -35,7 +35,6 @@ + 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 @@ + + 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); + } +@@ -83,7 +80,7 @@ + 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 '--color=auto' -uraN a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c +--- a/sound/soc/codecs/cs35l41-lib.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/soc/codecs/cs35l41-lib.c 2023-11-04 16:36:53.562316533 +0300 +@@ -1080,28 +1080,32 @@ + { 0x00000040, 0x00000033 }, + }; + +-static const struct reg_sequence cs35l41_active_to_safe[] = { ++static const struct reg_sequence cs35l41_active_to_safe_start[] = { + { 0x00000040, 0x00000055 }, + { 0x00000040, 0x000000AA }, + { 0x00007438, 0x00585941 }, + { CS35L41_PWR_CTRL1, 0x00000000 }, +- { 0x0000742C, 0x00000009, 3000 }, ++ { 0x0000742C, 0x00000009 }, ++}; ++ ++static const struct reg_sequence cs35l41_active_to_safe_end[] = { + { 0x00007438, 0x00580941 }, + { 0x00000040, 0x000000CC }, + { 0x00000040, 0x00000033 }, + }; + +-static const struct reg_sequence cs35l41_safe_to_active[] = { ++static const struct reg_sequence cs35l41_safe_to_active_start[] = { + { 0x00000040, 0x00000055 }, + { 0x00000040, 0x000000AA }, + { 0x0000742C, 0x0000000F }, + { 0x0000742C, 0x00000079 }, + { 0x00007438, 0x00585941 }, +- { CS35L41_PWR_CTRL1, 0x00000001, 3000 }, // GLOBAL_EN = 1 ++ { CS35L41_PWR_CTRL1, 0x00000001 }, // GLOBAL_EN = 1 ++}; ++ ++static const struct reg_sequence cs35l41_safe_to_active_en_spk[] = { + { 0x0000742C, 0x000000F9 }, + { 0x00007438, 0x00580941 }, +- { 0x00000040, 0x000000CC }, +- { 0x00000040, 0x00000033 }, + }; + + static const struct reg_sequence cs35l41_reset_to_safe[] = { +@@ -1188,21 +1192,51 @@ + } + EXPORT_SYMBOL_GPL(cs35l41_safe_reset); + +-int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable, +- struct completion *pll_lock) ++/* ++ * 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, bool firmware_running) + { + int ret; +- unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3; ++ unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status, pup_pdn_mask; ++ unsigned int pwr_ctl1_val; + struct reg_sequence cs35l41_mdsync_down_seq[] = { + {CS35L41_PWR_CTRL3, 0}, + {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; ++ ++ ret = regmap_read(regmap, CS35L41_PWR_CTRL1, &pwr_ctl1_val); ++ if (ret) ++ return ret; ++ ++ if ((pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && enable) { ++ dev_dbg(dev, "Cannot set Global Enable - already set.\n"); ++ return 0; ++ } else if (!(pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && !enable) { ++ dev_dbg(dev, "Cannot unset Global Enable - not set.\n"); ++ return 0; ++ } + + switch (b_type) { + case CS35L41_SHD_BOOST_ACTV: +@@ -1222,38 +1256,91 @@ + 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 (!enable) +- break; +- +- if (!pll_lock) +- return -EINVAL; ++ /* Activation to be completed later via cs35l41_mdsync_up() */ ++ if (ret || enable) ++ return ret; ++ ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, ++ int_status, int_status & pup_pdn_mask, ++ 1000, 100000); ++ if (ret) ++ dev_err(dev, "Enable(%d) failed: %d\n", enable, ret); + +- 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)); +- } ++ /* Clear PUP/PDN status */ ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask); + break; + case CS35L41_INT_BOOST: + ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK, + enable << CS35L41_GLOBAL_EN_SHIFT); +- usleep_range(3000, 3100); ++ if (ret) { ++ dev_err(dev, "CS35L41_PWR_CTRL1 set failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, ++ int_status, int_status & pup_pdn_mask, ++ 1000, 100000); ++ if (ret) ++ dev_err(dev, "Enable(%d) failed: %d\n", enable, ret); ++ ++ /* Clear PUP/PDN status */ ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask); + break; + case CS35L41_EXT_BOOST: + case CS35L41_EXT_BOOST_NO_VSPK_SWITCH: +- if (enable) +- ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active, +- ARRAY_SIZE(cs35l41_safe_to_active)); +- else +- ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe, +- ARRAY_SIZE(cs35l41_active_to_safe)); ++ if (enable) { ++ /* Test Key is unlocked here */ ++ ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active_start, ++ ARRAY_SIZE(cs35l41_safe_to_active_start)); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, int_status, ++ int_status & CS35L41_PUP_DONE_MASK, 1000, 100000); ++ if (ret) { ++ dev_err(dev, "Failed waiting for CS35L41_PUP_DONE_MASK: %d\n", ret); ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ return ret; ++ } ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, CS35L41_PUP_DONE_MASK); ++ ++ if (firmware_running) ++ ret = cs35l41_set_cspl_mbox_cmd(dev, regmap, ++ CSPL_MBOX_CMD_SPK_OUT_ENABLE); ++ else ++ ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active_en_spk, ++ ARRAY_SIZE(cs35l41_safe_to_active_en_spk)); ++ ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ } else { ++ /* Test Key is unlocked here */ ++ ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_start, ++ ARRAY_SIZE(cs35l41_active_to_safe_start)); ++ if (ret) { ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ return ret; ++ } ++ ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, int_status, ++ int_status & CS35L41_PDN_DONE_MASK, 1000, 100000); ++ if (ret) { ++ dev_err(dev, "Failed waiting for CS35L41_PDN_DONE_MASK: %d\n", ret); ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ return ret; ++ } ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, CS35L41_PDN_DONE_MASK); ++ ++ /* Test Key is locked here */ ++ ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_end, ++ ARRAY_SIZE(cs35l41_active_to_safe_end)); ++ } + break; + default: + ret = -EINVAL; +@@ -1264,6 +1351,17 @@ + } + 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; +@@ -1344,6 +1442,8 @@ + return (sts == CSPL_MBOX_STS_RUNNING); + case CSPL_MBOX_CMD_STOP_PRE_REINIT: + return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT); ++ case CSPL_MBOX_CMD_SPK_OUT_ENABLE: ++ return (sts == CSPL_MBOX_STS_RUNNING); + default: + return false; + } +diff '--color=auto' -uraN a/sound/soc/codecs/cs35l41-spi.c b/sound/soc/codecs/cs35l41-spi.c +--- a/sound/soc/codecs/cs35l41-spi.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/soc/codecs/cs35l41-spi.c 2023-11-04 16:36:53.562316533 +0300 +@@ -32,7 +32,6 @@ + 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 @@ + + 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; +@@ -83,7 +80,7 @@ + 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 '--color=auto' -uraN a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c +--- a/sound/soc/codecs/cs35l41.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/soc/codecs/cs35l41.c 2023-11-04 16:36:53.562316533 +0300 +@@ -386,10 +386,18 @@ + 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, +@@ -459,7 +467,19 @@ + + 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; + } + +@@ -491,7 +511,6 @@ + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); +- unsigned int val; + int ret = 0; + + switch (event) { +@@ -500,21 +519,12 @@ + cs35l41_pup_patch, + ARRAY_SIZE(cs35l41_pup_patch)); + +- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 1, +- &cs35l41->pll_lock); ++ ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, ++ 1, cs35l41->dsp.cs_dsp.running); + break; + case SND_SOC_DAPM_POST_PMD: +- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, +- &cs35l41->pll_lock); +- +- ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1, +- val, val & CS35L41_PDN_DONE_MASK, +- 1000, 100000); +- if (ret) +- dev_warn(cs35l41->dev, "PDN failed: %d\n", ret); +- +- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, +- CS35L41_PDN_DONE_MASK); ++ ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, ++ 0, cs35l41->dsp.cs_dsp.running); + + regmap_multi_reg_write_bypassed(cs35l41->regmap, + cs35l41_pdn_patch, +@@ -812,10 +822,6 @@ + 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, +@@ -1184,16 +1190,14 @@ + + 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", +@@ -1205,8 +1209,8 @@ + 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; + } + } +@@ -1222,8 +1226,8 @@ + 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; + } + +@@ -1236,13 +1240,13 @@ + + 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; + } + +@@ -1267,7 +1271,7 @@ + + 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; + } + +@@ -1287,13 +1291,13 @@ + 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; + } + +@@ -1305,8 +1309,6 @@ + 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); +@@ -1318,7 +1320,7 @@ + &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; + } + +@@ -1330,6 +1332,7 @@ + return 0; + + err_pm: ++ pm_runtime_dont_use_autosuspend(cs35l41->dev); + pm_runtime_disable(cs35l41->dev); + pm_runtime_put_noidle(cs35l41->dev); + +@@ -1346,6 +1349,7 @@ + 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); +@@ -1364,7 +1368,7 @@ + } + 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); + +@@ -1381,7 +1385,7 @@ + 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; +@@ -1410,7 +1414,7 @@ + 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); + +@@ -1420,7 +1424,7 @@ + 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); + +@@ -1430,7 +1434,7 @@ + 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); + +@@ -1440,7 +1444,7 @@ + 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); + +@@ -1450,13 +1454,12 @@ + 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, "); +diff '--color=auto' -uraN a/sound/soc/codecs/cs35l41.h b/sound/soc/codecs/cs35l41.h +--- a/sound/soc/codecs/cs35l41.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/soc/codecs/cs35l41.h 2023-11-04 16:36:53.562316533 +0300 +@@ -33,7 +33,6 @@ + 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); +diff '--color=auto' -uraN a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +--- a/sound/soc/codecs/rt5645.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/soc/codecs/rt5645.c 2023-11-04 16:36:53.538982781 +0300 +@@ -3718,6 +3718,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 a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c 2023-11-04 16:36:53.538982781 +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"), ++ }, ++ }, + { } + }; + +diff '--color=auto' -uraN a/tools/include/uapi/linux/sched.h b/tools/include/uapi/linux/sched.h +--- a/tools/include/uapi/linux/sched.h 2023-10-25 13:16:30.000000000 +0300 ++++ b/tools/include/uapi/linux/sched.h 2023-11-04 16:36:05.671457708 +0300 +@@ -132,6 +132,7 @@ + #define SCHED_FLAG_KEEP_PARAMS 0x10 + #define SCHED_FLAG_UTIL_CLAMP_MIN 0x20 + #define SCHED_FLAG_UTIL_CLAMP_MAX 0x40 ++#define SCHED_FLAG_LATENCY_NICE 0x80 + + #define SCHED_FLAG_KEEP_ALL (SCHED_FLAG_KEEP_POLICY | \ + SCHED_FLAG_KEEP_PARAMS) +@@ -143,6 +144,7 @@ + SCHED_FLAG_RECLAIM | \ + SCHED_FLAG_DL_OVERRUN | \ + SCHED_FLAG_KEEP_ALL | \ +- SCHED_FLAG_UTIL_CLAMP) ++ SCHED_FLAG_UTIL_CLAMP | \ ++ SCHED_FLAG_LATENCY_NICE) + + #endif /* _UAPI_LINUX_SCHED_H */ +diff '--color=auto' -uraN a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c +--- a/tools/testing/selftests/mm/ksm_functional_tests.c 2023-10-25 13:16:30.000000000 +0300 ++++ b/tools/testing/selftests/mm/ksm_functional_tests.c 2023-11-04 16:35:57.841317298 +0300 +@@ -29,6 +29,8 @@ + + static int ksm_fd; + static int ksm_full_scans_fd; ++static int proc_self_ksm_stat_fd; ++static int ksm_use_zero_pages_fd; + static int pagemap_fd; + static size_t pagesize; + +@@ -59,6 +61,33 @@ + return false; + } + ++static long get_my_ksm_zero_pages(void) ++{ ++ char buf[200]; ++ char *substr_ksm_zero; ++ size_t value_pos; ++ ssize_t read_size; ++ unsigned long my_ksm_zero_pages; ++ ++ if (!proc_self_ksm_stat_fd) ++ return 0; ++ ++ read_size = pread(proc_self_ksm_stat_fd, buf, sizeof(buf) - 1, 0); ++ if (read_size < 0) ++ return -errno; ++ ++ buf[read_size] = 0; ++ ++ substr_ksm_zero = strstr(buf, "ksm_zero_pages"); ++ if (!substr_ksm_zero) ++ return 0; ++ ++ value_pos = strcspn(substr_ksm_zero, "0123456789"); ++ my_ksm_zero_pages = strtol(substr_ksm_zero + value_pos, NULL, 10); ++ ++ return my_ksm_zero_pages; ++} ++ + static long ksm_get_full_scans(void) + { + char buf[10]; +@@ -159,6 +188,70 @@ + munmap(map, size); + } + ++static void test_unmerge_zero_pages(void) ++{ ++ const unsigned int size = 2 * MiB; ++ char *map; ++ unsigned int offs; ++ unsigned long pages_expected; ++ ++ ksft_print_msg("[RUN] %s\n", __func__); ++ ++ if (proc_self_ksm_stat_fd < 0) { ++ ksft_test_result_skip("open(\"/proc/self/ksm_stat\") failed\n"); ++ return; ++ } ++ if (ksm_use_zero_pages_fd < 0) { ++ ksft_test_result_skip("open \"/sys/kernel/mm/ksm/use_zero_pages\" failed\n"); ++ return; ++ } ++ if (write(ksm_use_zero_pages_fd, "1", 1) != 1) { ++ ksft_test_result_skip("write \"/sys/kernel/mm/ksm/use_zero_pages\" failed\n"); ++ return; ++ } ++ ++ /* Let KSM deduplicate zero pages. */ ++ map = mmap_and_merge_range(0x00, size, false); ++ if (map == MAP_FAILED) ++ return; ++ ++ /* Check if ksm_zero_pages is updated correctly after KSM merging */ ++ pages_expected = size / pagesize; ++ if (pages_expected != get_my_ksm_zero_pages()) { ++ ksft_test_result_fail("'ksm_zero_pages' updated after merging\n"); ++ goto unmap; ++ } ++ ++ /* Try to unmerge half of the region */ ++ if (madvise(map, size / 2, MADV_UNMERGEABLE)) { ++ ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); ++ goto unmap; ++ } ++ ++ /* Check if ksm_zero_pages is updated correctly after unmerging */ ++ pages_expected /= 2; ++ if (pages_expected != get_my_ksm_zero_pages()) { ++ ksft_test_result_fail("'ksm_zero_pages' updated after unmerging\n"); ++ goto unmap; ++ } ++ ++ /* Trigger unmerging of the other half by writing to the pages. */ ++ for (offs = size / 2; offs < size; offs += pagesize) ++ *((unsigned int *)&map[offs]) = offs; ++ ++ /* Now we should have no zeropages remaining. */ ++ if (get_my_ksm_zero_pages()) { ++ ksft_test_result_fail("'ksm_zero_pages' updated after write fault\n"); ++ goto unmap; ++ } ++ ++ /* Check if ksm zero pages are really unmerged */ ++ ksft_test_result(!range_maps_duplicates(map, size), ++ "KSM zero pages were unmerged\n"); ++unmap: ++ munmap(map, size); ++} ++ + static void test_unmerge_discarded(void) + { + const unsigned int size = 2 * MiB; +@@ -358,7 +451,7 @@ + + int main(int argc, char **argv) + { +- unsigned int tests = 5; ++ unsigned int tests = 6; + int err; + + #ifdef __NR_userfaultfd +@@ -379,8 +472,11 @@ + pagemap_fd = open("/proc/self/pagemap", O_RDONLY); + if (pagemap_fd < 0) + ksft_exit_skip("open(\"/proc/self/pagemap\") failed\n"); ++ proc_self_ksm_stat_fd = open("/proc/self/ksm_stat", O_RDONLY); ++ ksm_use_zero_pages_fd = open("/sys/kernel/mm/ksm/use_zero_pages", O_RDWR); + + test_unmerge(); ++ test_unmerge_zero_pages(); + test_unmerge_discarded(); + #ifdef __NR_userfaultfd + test_unmerge_uffd_wp(); diff --git a/patches/0001-cachy-all.patch b/patches/cachyos/0001-cachyos-base-all.patch similarity index 98% rename from patches/0001-cachy-all.patch rename to patches/cachyos/0001-cachyos-base-all.patch index 17502f6..426c3f7 100644 --- a/patches/0001-cachy-all.patch +++ b/patches/cachyos/0001-cachyos-base-all.patch @@ -1,4 +1,4 @@ -From 064c49c5d144094d580b510ca4ca110469b1203d Mon Sep 17 00:00:00 2001 +From 14a8a0f784475571c3532816ab77afea589d90b5 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Wed, 13 Sep 2023 14:31:11 +0200 Subject: [PATCH 1/7] amd-hdr @@ -28,7 +28,7 @@ Signed-off-by: Peter Jung 20 files changed, 1446 insertions(+), 128 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h -index 32fe05c810c6f..84bf501b02f4c 100644 +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 { @@ -110,7 +110,7 @@ index 32fe05c810c6f..84bf501b02f4c 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 e0d556cf919f7..f8bc642d658cd 100644 +index c9959bd8147d..4a2300db8c99 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4015,6 +4015,11 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) @@ -197,7 +197,7 @@ index e0d556cf919f7..f8bc642d658cd 100644 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 9fb5bb3a75a77..f92bbd7ed867b 100644 +index 9fb5bb3a75a7..f92bbd7ed867 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -51,6 +51,8 @@ @@ -340,7 +340,7 @@ index 9fb5bb3a75a77..f92bbd7ed867b 100644 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 a4cb23d059bd6..0a51af44efd5f 100644 +index a4cb23d059bd..0a51af44efd5 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 @@ @@ -1286,7 +1286,7 @@ index a4cb23d059bd6..0a51af44efd5f 100644 + 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 440fc0869a34b..d746f0aa0f11c 100644 +index 440fc0869a34..d746f0aa0f11 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 @@ -253,6 +253,7 @@ static struct drm_crtc_state *dm_crtc_duplicate_state(struct drm_crtc *crtc) @@ -1390,7 +1390,7 @@ index 440fc0869a34b..d746f0aa0f11c 100644 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 6c84ca2ae373a..ea03c22437293 100644 +index 6c84ca2ae373..ea03c2243729 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 @@ -1324,8 +1324,14 @@ static void dm_drm_plane_reset(struct drm_plane *plane) @@ -1659,7 +1659,7 @@ index 6c84ca2ae373a..ea03c22437293 100644 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 3538973bd0c6c..04b2e04b68f33 100644 +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, @@ -1777,7 +1777,7 @@ index 3538973bd0c6c..04b2e04b68f33 100644 } } 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 4cd4ae07d73dc..4fb4e9ec03f1e 100644 +index 4cd4ae07d73d..4fb4e9ec03f1 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, @@ -1825,7 +1825,7 @@ index 4cd4ae07d73dc..4fb4e9ec03f1e 100644 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 a24a8e33a3d28..cb34ca932a5ff 100644 +index a24a8e33a3d2..cb34ca932a5f 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, @@ -1839,7 +1839,7 @@ index a24a8e33a3d28..cb34ca932a5ff 100644 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 61205cdbe2d5a..fdbe3d42cd7b6 100644 +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 @@ @@ -1852,7 +1852,7 @@ index 61205cdbe2d5a..fdbe3d42cd7b6 100644 .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 d4cf7ead1d877..84da1dd34efd1 100644 +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 }; @@ -1875,7 +1875,7 @@ index d4cf7ead1d877..84da1dd34efd1 100644 * @brief * Initialization routines diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c -index dc01c43f61930..d72c22dcf6855 100644 +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, @@ -1888,7 +1888,7 @@ index dc01c43f61930..d72c22dcf6855 100644 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 c277b198fa3fa..c3df45f901456 100644 +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, @@ -1900,7 +1900,7 @@ index c277b198fa3fa..c3df45f901456 100644 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 784e63d70a421..25bb0859fda74 100644 +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, @@ -1912,7 +1912,7 @@ index 784e63d70a421..25bb0859fda74 100644 EXPORT_SYMBOL(__drm_atomic_helper_plane_duplicate_state); diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c -index d867e7f9f2cd5..a6a9ee5086ddb 100644 +index d867e7f9f2cd..a6a9ee5086dd 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -362,39 +362,6 @@ static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state, @@ -2001,7 +2001,7 @@ index d867e7f9f2cd5..a6a9ee5086ddb 100644 val, sizeof(struct hdr_output_metadata), -1, diff --git a/drivers/gpu/drm/drm_property.c b/drivers/gpu/drm/drm_property.c -index dfec479830e49..f72ef6493340a 100644 +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, @@ -2061,7 +2061,7 @@ index dfec479830e49..f72ef6493340a 100644 void *data, struct drm_file *file_priv) { diff --git a/include/drm/drm_mode_object.h b/include/drm/drm_mode_object.h -index 912f1e4156853..08d7a7f0188fe 100644 +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 { @@ -2074,7 +2074,7 @@ index 912f1e4156853..08d7a7f0188fe 100644 * 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 51291983ea445..52c3287da0daa 100644 +index 51291983ea44..52c3287da0da 100644 --- a/include/drm/drm_plane.h +++ b/include/drm/drm_plane.h @@ -237,6 +237,13 @@ struct drm_plane_state { @@ -2092,7 +2092,7 @@ index 51291983ea445..52c3287da0daa 100644 static inline struct drm_rect diff --git a/include/drm/drm_property.h b/include/drm/drm_property.h -index 65bc9710a4702..082f29156b3e3 100644 +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, @@ -2109,7 +2109,7 @@ index 65bc9710a4702..082f29156b3e3 100644 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 43691058d28fb..23fc194009980 100644 +index 43691058d28f..23fc19400998 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -843,6 +843,14 @@ struct drm_color_ctm { @@ -2130,28 +2130,28 @@ index 43691058d28fb..23fc194009980 100644 -- 2.42.0 -From ec46dbc46816854bf4a300cc5772da05f743df52 Mon Sep 17 00:00:00 2001 +From e91c7c0b6a3581ad777540fdfb5cd35c7f0390e6 Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Tue, 19 Sep 2023 14:30:49 +0200 +Date: Sun, 22 Oct 2023 09:05:08 +0200 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 | 58 +++++- + 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-ut.c | 4 +- - drivers/cpufreq/amd-pstate.c | 197 ++++++++++++++++-- + drivers/cpufreq/amd-pstate.c | 206 ++++++++++++++++-- drivers/cpufreq/cpufreq.c | 13 ++ include/acpi/cppc_acpi.h | 5 + - include/linux/amd-pstate.h | 6 + + include/linux/amd-pstate.h | 10 + include/linux/cpufreq.h | 5 + - 11 files changed, 293 insertions(+), 24 deletions(-) + 11 files changed, 308 insertions(+), 23 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt -index 23ebe34ff901e..f23ec4dc6c4b9 100644 +index 23ebe34ff901..f23ec4dc6c4b 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -363,6 +363,11 @@ @@ -2167,7 +2167,7 @@ index 23ebe34ff901e..f23ec4dc6c4b9 100644 Map of devices attached to JOY0DAT and JOY1DAT Format: , diff --git a/Documentation/admin-guide/pm/amd-pstate.rst b/Documentation/admin-guide/pm/amd-pstate.rst -index 1cf40f69278cd..b729bc6dabd80 100644 +index 1cf40f69278c..0b832ff529db 100644 --- a/Documentation/admin-guide/pm/amd-pstate.rst +++ b/Documentation/admin-guide/pm/amd-pstate.rst @@ -300,8 +300,8 @@ platforms. The AMD P-States mechanism is the more performance and energy @@ -2230,7 +2230,7 @@ index 1cf40f69278cd..b729bc6dabd80 100644 User Space Interface in ``sysfs`` - General =========================================== -@@ -385,6 +427,18 @@ control its functionality at the system level. They are located in the +@@ -385,6 +427,19 @@ control its functionality at the system level. They are located in the to the operation mode represented by that string - or to be unregistered in the "disable" case. @@ -2244,13 +2244,14 @@ index 1cf40f69278cd..b729bc6dabd80 100644 + Disable the ``amd-pstate`` preferred core + + -+ This attribute is read-only to check the state of preferred core. ++ This attribute is read-only to check the state of preferred core set ++ by the kernel parameter. + ``cpupower`` tool support for ``amd-pstate`` =============================================== diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig -index e36261b4ea14f..14d0741139801 100644 +index 68ce4f786dcd..b6ac7d85487a 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -1052,8 +1052,9 @@ config SCHED_MC @@ -2266,7 +2267,7 @@ index e36261b4ea14f..14d0741139801 100644 default y help diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c -index 7ff269a78c208..ad388a0e84842 100644 +index 7ff269a78c20..ad388a0e8484 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1154,6 +1154,19 @@ int cppc_get_nominal_perf(int cpunum, u64 *nominal_perf) @@ -2290,7 +2291,7 @@ index 7ff269a78c208..ad388a0e84842 100644 * cppc_get_epp_perf - Get the epp register value. * @cpunum: CPU from which to get epp preference value. diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c -index 4bd16b3f07814..29b2fb68a35db 100644 +index 4bd16b3f0781..29b2fb68a35d 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -27,6 +27,7 @@ @@ -2314,7 +2315,7 @@ index 4bd16b3f07814..29b2fb68a35db 100644 acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event); break; diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c -index 502d494499ae8..f04ae67dda372 100644 +index 502d494499ae..f04ae67dda37 100644 --- a/drivers/cpufreq/amd-pstate-ut.c +++ b/drivers/cpufreq/amd-pstate-ut.c @@ -127,8 +127,6 @@ static void amd_pstate_ut_check_perf(u32 index) @@ -2343,7 +2344,7 @@ index 502d494499ae8..f04ae67dda372 100644 lowest_nonlinear_perf = AMD_CPPC_LOWNONLIN_PERF(cap1); lowest_perf = AMD_CPPC_LOWEST_PERF(cap1); diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c -index 9a1e194d5cf88..97b1d4674b4f0 100644 +index 9a1e194d5cf8..1c1f04eab389 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -37,6 +37,7 @@ @@ -2363,20 +2364,15 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 /* * TODO: We need more time to fine tune processors with shared memory solution -@@ -65,6 +68,12 @@ static struct cpufreq_driver amd_pstate_epp_driver; +@@ -64,6 +67,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; ++static bool amd_pstate_prefcore = true; -+/*HW preferred Core featue is supported*/ -+static bool hw_prefcore = true; -+ -+/*Preferred Core featue is supported*/ -+static bool prefcore = true; -+ /* * AMD Energy Preference Performance (EPP) - * The EPP is used in the CCLK DPM controller to drive -@@ -290,27 +299,26 @@ static inline int amd_pstate_enable(bool enable) +@@ -290,27 +294,26 @@ static inline int amd_pstate_enable(bool enable) static int pstate_init_perf(struct amd_cpudata *cpudata) { u64 cap1; @@ -2401,7 +2397,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 - highest_perf = AMD_CPPC_HIGHEST_PERF(cap1); - - WRITE_ONCE(cpudata->highest_perf, highest_perf); -+ if (hw_prefcore) ++ if (cpudata->hw_prefcore) + WRITE_ONCE(cpudata->highest_perf, AMD_PSTATE_PREFCORE_THRESHOLD); + else + WRITE_ONCE(cpudata->highest_perf, AMD_CPPC_HIGHEST_PERF(cap1)); @@ -2413,7 +2409,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 return 0; } -@@ -318,22 +326,21 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) +@@ -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; @@ -2428,7 +2424,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 - highest_perf = cppc_perf.highest_perf; - - WRITE_ONCE(cpudata->highest_perf, highest_perf); -+ if (hw_prefcore) ++ if (cpudata->hw_prefcore) + WRITE_ONCE(cpudata->highest_perf, AMD_PSTATE_PREFCORE_THRESHOLD); + else + WRITE_ONCE(cpudata->highest_perf, cppc_perf.highest_perf); @@ -2441,7 +2437,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 if (cppc_state == AMD_PSTATE_ACTIVE) return 0; -@@ -540,7 +547,7 @@ static void amd_pstate_adjust_perf(unsigned int cpu, +@@ -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); @@ -2450,7 +2446,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 if (_min_perf < capacity) min_perf = DIV_ROUND_UP(cap_perf * _min_perf, capacity); -@@ -676,6 +683,116 @@ static void amd_perf_ctl_reset(unsigned int cpu) +@@ -676,6 +678,124 @@ static void amd_perf_ctl_reset(unsigned int cpu) wrmsrl_on_cpu(cpu, MSR_AMD_PERF_CTL, 0); } @@ -2474,7 +2470,6 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 +static int amd_pstate_get_highest_perf(int cpu, u32 *highest_perf) +{ + int ret; -+ u64 cppc_highest_perf; + + if (boot_cpu_has(X86_FEATURE_CPPC)) { + u64 cap1; @@ -2484,40 +2479,44 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 + return ret; + WRITE_ONCE(*highest_perf, AMD_CPPC_HIGHEST_PERF(cap1)); + } else { ++ u64 cppc_highest_perf; ++ + ret = cppc_get_highest_perf(cpu, &cppc_highest_perf); -+ *highest_perf = (u32)(cppc_highest_perf & 0xFFFF); ++ WRITE_ONCE(*highest_perf, cppc_highest_perf); + } + + return (ret); +} + -+static void amd_pstate_init_prefcore(unsigned int cpu) ++static void amd_pstate_init_prefcore(struct amd_cpudata *cpudata) +{ -+ int ret; ++ int ret, prio; + u32 highest_perf; + static u32 max_highest_perf = 0, min_highest_perf = U32_MAX; + -+ if (!prefcore) -+ return; -+ -+ ret = amd_pstate_get_highest_perf(cpu, &highest_perf); ++ ret = amd_pstate_get_highest_perf(cpudata->cpu, &highest_perf); + if (ret) + return; + ++ cpudata->hw_prefcore = true; ++ /* check if CPPC preferred core feature is enabled*/ ++ if (highest_perf == AMD_PSTATE_MAX_CPPC_PERF) { ++ pr_debug("AMD CPPC preferred core is unsupported!\n"); ++ cpudata->hw_prefcore = false; ++ return; ++ } ++ ++ 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 + * update them at any time after it has been called. + */ -+ sched_set_itmt_core_prio(highest_perf, cpu); -+ -+ /* check if CPPC preferred core feature is enabled*/ -+ if (highest_perf == AMD_PSTATE_MAX_CPPC_PERF) { -+ pr_debug("AMD CPPC preferred core is unsupported!\n"); -+ hw_prefcore = false; -+ prefcore = false; -+ return; -+ } ++ sched_set_itmt_core_prio(prio, cpudata->cpu); + + if (max_highest_perf <= min_highest_perf) { + if (highest_perf > max_highest_perf) @@ -2545,7 +2544,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 + u32 prev_high = 0, cur_high = 0; + int ret; + -+ if (!prefcore) ++ if ((!amd_pstate_prefcore) || (!cpudata->hw_prefcore)) + return; + + ret = amd_pstate_get_highest_perf(cpu, &cur_high); @@ -2557,8 +2556,13 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 + prev_high = READ_ONCE(cpudata->prefcore_ranking); + + if (prev_high != cur_high) { ++ int prio; ++ + WRITE_ONCE(cpudata->prefcore_ranking, cur_high); -+ sched_set_itmt_core_prio(cur_high, cpu); ++ ++ /* The maximum value of highest perf is 255 */ ++ prio = (int)(cur_high & 0xff); ++ sched_set_itmt_core_prio(prio, cpu); + } + + cpufreq_cpu_put(policy); @@ -2567,61 +2571,60 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 static int amd_pstate_cpu_init(struct cpufreq_policy *policy) { int min_freq, max_freq, nominal_freq, lowest_nonlinear_freq, ret; -@@ -697,6 +814,8 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) +@@ -697,6 +817,8 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) cpudata->cpu = policy->cpu; -+ amd_pstate_init_prefcore(policy->cpu); ++ amd_pstate_init_prefcore(cpudata); + ret = amd_pstate_init_perf(cpudata); if (ret) goto free_cpudata1; -@@ -763,6 +882,22 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) - return ret; - } - -+static int amd_pstate_cpu_online(struct cpufreq_policy *policy) -+{ -+ struct amd_cpudata *cpudata = policy->driver_data; -+ -+ pr_debug("CPU %d going online\n", cpudata->cpu); -+ -+ amd_pstate_init_prefcore(cpudata->cpu); -+ -+ return 0; -+} -+ -+static int amd_pstate_cpu_offline(struct cpufreq_policy *policy) -+{ -+ return 0; -+} -+ - static int amd_pstate_cpu_exit(struct cpufreq_policy *policy) - { - struct amd_cpudata *cpudata = policy->driver_data; -@@ -840,7 +975,7 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, - u32 perf; - struct amd_cpudata *cpudata = policy->driver_data; - -- perf = READ_ONCE(cpudata->highest_perf); -+ perf = READ_ONCE(cpudata->prefcore_ranking); - +@@ -845,6 +967,28 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, return sysfs_emit(buf, "%u\n", perf); } -@@ -1037,6 +1172,12 @@ static ssize_t status_store(struct device *a, struct device_attribute *b, + ++static ssize_t show_amd_pstate_prefcore_ranking(struct cpufreq_policy *policy, ++ char *buf) ++{ ++ u32 perf; ++ struct amd_cpudata *cpudata = policy->driver_data; ++ ++ perf = READ_ONCE(cpudata->prefcore_ranking); ++ ++ return sysfs_emit(buf, "%u\n", perf); ++} ++ ++static ssize_t show_amd_pstate_hw_prefcore(struct cpufreq_policy *policy, ++ char *buf) ++{ ++ bool hw_prefcore; ++ struct amd_cpudata *cpudata = policy->driver_data; ++ ++ hw_prefcore = READ_ONCE(cpudata->hw_prefcore); ++ ++ return sysfs_emit(buf, "%s\n", hw_prefcore ? "supported" : "unsupported"); ++} ++ + 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, 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", prefcore ? "enabled" : "disabled"); ++ return sysfs_emit(buf, "%s\n", amd_pstate_prefcore ? "enabled" : "disabled"); +} + cpufreq_freq_attr_ro(amd_pstate_max_freq); cpufreq_freq_attr_ro(amd_pstate_lowest_nonlinear_freq); -@@ -1044,6 +1185,7 @@ cpufreq_freq_attr_ro(amd_pstate_highest_perf); + cpufreq_freq_attr_ro(amd_pstate_highest_perf); ++cpufreq_freq_attr_ro(amd_pstate_prefcore_ranking); ++cpufreq_freq_attr_ro(amd_pstate_hw_prefcore); cpufreq_freq_attr_rw(energy_performance_preference); cpufreq_freq_attr_ro(energy_performance_available_preferences); static DEVICE_ATTR_RW(status); @@ -2629,7 +2632,23 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 static struct freq_attr *amd_pstate_attr[] = { &amd_pstate_max_freq, -@@ -1063,6 +1205,7 @@ static struct freq_attr *amd_pstate_epp_attr[] = { + &amd_pstate_lowest_nonlinear_freq, + &amd_pstate_highest_perf, ++ &amd_pstate_prefcore_ranking, ++ &amd_pstate_hw_prefcore, + NULL, + }; + +@@ -1056,6 +1211,8 @@ static struct freq_attr *amd_pstate_epp_attr[] = { + &amd_pstate_max_freq, + &amd_pstate_lowest_nonlinear_freq, + &amd_pstate_highest_perf, ++ &amd_pstate_prefcore_ranking, ++ &amd_pstate_hw_prefcore, + &energy_performance_preference, + &energy_performance_available_preferences, + NULL, +@@ -1063,6 +1220,7 @@ static struct freq_attr *amd_pstate_epp_attr[] = { static struct attribute *pstate_global_attributes[] = { &dev_attr_status.attr, @@ -2637,30 +2656,16 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 NULL }; -@@ -1114,6 +1257,8 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) +@@ -1114,6 +1272,8 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) cpudata->cpu = policy->cpu; cpudata->epp_policy = 0; -+ amd_pstate_init_prefcore(policy->cpu); ++ amd_pstate_init_prefcore(cpudata); + ret = amd_pstate_init_perf(cpudata); if (ret) goto free_cpudata1; -@@ -1285,6 +1430,8 @@ static int amd_pstate_epp_cpu_online(struct cpufreq_policy *policy) - - pr_debug("AMD CPU Core %d going online\n", cpudata->cpu); - -+ amd_pstate_init_prefcore(cpudata->cpu); -+ - if (cppc_state == AMD_PSTATE_ACTIVE) { - amd_pstate_epp_reenable(cpudata); - cpudata->suspended = false; -@@ -1389,9 +1536,12 @@ static struct cpufreq_driver amd_pstate_driver = { - .fast_switch = amd_pstate_fast_switch, - .init = amd_pstate_cpu_init, - .exit = amd_pstate_cpu_exit, -+ .offline = amd_pstate_cpu_offline, -+ .online = amd_pstate_cpu_online, +@@ -1392,6 +1552,7 @@ static struct cpufreq_driver amd_pstate_driver = { .suspend = amd_pstate_cpu_suspend, .resume = amd_pstate_cpu_resume, .set_boost = amd_pstate_set_boost, @@ -2668,7 +2673,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 .name = "amd-pstate", .attr = amd_pstate_attr, }; -@@ -1406,6 +1556,7 @@ static struct cpufreq_driver amd_pstate_epp_driver = { +@@ -1406,6 +1567,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, @@ -2676,7 +2681,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 .name = "amd-pstate-epp", .attr = amd_pstate_epp_attr, }; -@@ -1527,7 +1678,17 @@ static int __init amd_pstate_param(char *str) +@@ -1527,7 +1689,17 @@ static int __init amd_pstate_param(char *str) return amd_pstate_set_driver(mode_idx); } @@ -2684,7 +2689,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 +static int __init amd_prefcore_param(char *str) +{ + if (!strcmp(str, "disable")) -+ prefcore = false; ++ amd_pstate_prefcore = false; + + return 0; +} @@ -2695,7 +2700,7 @@ index 9a1e194d5cf88..97b1d4674b4f0 100644 MODULE_AUTHOR("Huang Rui "); MODULE_DESCRIPTION("AMD Processor P-state Frequency Driver"); diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c -index 5c655d7b96d4f..abefdb30fcaa2 100644 +index 5c655d7b96d4..abefdb30fcaa 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2677,6 +2677,19 @@ void cpufreq_update_limits(unsigned int cpu) @@ -2719,7 +2724,7 @@ index 5c655d7b96d4f..abefdb30fcaa2 100644 * BOOST * *********************************************************************/ diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h -index 6126c977ece04..c0b69ffe7bdb4 100644 +index 6126c977ece0..c0b69ffe7bdb 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -139,6 +139,7 @@ struct cppc_cpudata { @@ -2742,7 +2747,7 @@ index 6126c977ece04..c0b69ffe7bdb4 100644 { return -ENOTSUPP; diff --git a/include/linux/amd-pstate.h b/include/linux/amd-pstate.h -index 446394f846064..030a6a97c2b94 100644 +index 446394f84606..426822612373 100644 --- a/include/linux/amd-pstate.h +++ b/include/linux/amd-pstate.h @@ -39,11 +39,16 @@ struct amd_aperf_mperf { @@ -2762,7 +2767,17 @@ index 446394f846064..030a6a97c2b94 100644 * @max_freq: the frequency that mapped to highest_perf * @min_freq: the frequency that mapped to lowest_perf * @nominal_freq: the frequency that mapped to nominal_perf -@@ -70,6 +75,7 @@ struct amd_cpudata { +@@ -52,6 +57,9 @@ struct amd_aperf_mperf { + * @prev: Last Aperf/Mperf/tsc count value read from register + * @freq: current cpu frequency value + * @boost_supported: check whether the Processor or SBIOS supports boost mode ++ * @hw_prefcore: check whether HW supports preferred core featue. ++ * Only when hw_prefcore and early prefcore param are true, ++ * AMD P-State driver supports preferred core featue. + * @epp_policy: Last saved policy used to set energy-performance preference + * @epp_cached: Cached CPPC energy-performance preference value + * @policy: Cpufreq policy value +@@ -70,6 +78,7 @@ struct amd_cpudata { u32 nominal_perf; u32 lowest_nonlinear_perf; u32 lowest_perf; @@ -2770,8 +2785,16 @@ index 446394f846064..030a6a97c2b94 100644 u32 max_freq; u32 min_freq; +@@ -81,6 +90,7 @@ struct amd_cpudata { + + u64 freq; + bool boost_supported; ++ bool hw_prefcore; + + /* EPP feature related attributes*/ + s16 epp_policy; diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h -index 172ff51c1b2a4..9ca50c4e19d35 100644 +index 172ff51c1b2a..9ca50c4e19d3 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -231,6 +231,7 @@ int cpufreq_get_policy(struct cpufreq_policy *policy, unsigned int cpu); @@ -2803,7 +2826,7 @@ index 172ff51c1b2a4..9ca50c4e19d35 100644 -- 2.42.0 -From 726724b1b77008f7c4532a424379d373fb8c405d Mon Sep 17 00:00:00 2001 +From adf8583387461676e6b72ecfedcd2112f48e9c44 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Wed, 13 Sep 2023 14:31:43 +0200 Subject: [PATCH 3/7] bbr3 @@ -2828,7 +2851,7 @@ Signed-off-by: Peter Jung 15 files changed, 1934 insertions(+), 551 deletions(-) diff --git a/include/linux/tcp.h b/include/linux/tcp.h -index 91a37c99ba665..ae0ee688c3f7b 100644 +index 91a37c99ba66..ae0ee688c3f7 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -255,7 +255,9 @@ struct tcp_sock { @@ -2843,7 +2866,7 @@ index 91a37c99ba665..ae0ee688c3f7b 100644 u32 chrono_stat[3]; /* Time in jiffies for chrono_stat stats */ u8 chrono_type:2, /* current chronograph type */ diff --git a/include/net/inet_connection_sock.h b/include/net/inet_connection_sock.h -index c2b15f7e55161..a400a84088d38 100644 +index c2b15f7e5516..a400a84088d3 100644 --- a/include/net/inet_connection_sock.h +++ b/include/net/inet_connection_sock.h @@ -135,8 +135,8 @@ struct inet_connection_sock { @@ -2858,10 +2881,10 @@ index c2b15f7e55161..a400a84088d38 100644 #define ICSK_TIME_RETRANS 1 /* Retransmit timer */ diff --git a/include/net/tcp.h b/include/net/tcp.h -index 10fc5c5928f71..f0f4a343df8cf 100644 +index 7f684806c291..5238ef036e55 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h -@@ -369,6 +369,8 @@ static inline void tcp_dec_quickack_mode(struct sock *sk, +@@ -374,6 +374,8 @@ static inline void tcp_dec_quickack_mode(struct sock *sk) #define TCP_ECN_QUEUE_CWR 2 #define TCP_ECN_DEMAND_CWR 4 #define TCP_ECN_SEEN 8 @@ -2870,7 +2893,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 enum tcp_tw_status { TCP_TW_SUCCESS = 0, -@@ -722,6 +724,15 @@ static inline void tcp_fast_path_check(struct sock *sk) +@@ -727,6 +729,15 @@ static inline void tcp_fast_path_check(struct sock *sk) tcp_fast_path_on(tp); } @@ -2886,7 +2909,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 /* Compute the actual rto_min value */ static inline u32 tcp_rto_min(struct sock *sk) { -@@ -818,6 +829,11 @@ static inline u32 tcp_stamp_us_delta(u64 t1, u64 t0) +@@ -823,6 +834,11 @@ static inline u32 tcp_stamp_us_delta(u64 t1, u64 t0) return max_t(s64, t1 - t0, 0); } @@ -2898,7 +2921,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 static inline u32 tcp_skb_timestamp(const struct sk_buff *skb) { return tcp_ns_to_ts(skb->skb_mstamp_ns); -@@ -893,9 +909,14 @@ struct tcp_skb_cb { +@@ -898,9 +914,14 @@ struct tcp_skb_cb { /* pkts S/ACKed so far upon tx of skb, incl retrans: */ __u32 delivered; /* start of send pipeline phase */ @@ -2915,7 +2938,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 } tx; /* only used for outgoing skbs */ union { struct inet_skb_parm h4; -@@ -999,6 +1020,7 @@ enum tcp_ca_event { +@@ -1004,6 +1025,7 @@ enum tcp_ca_event { CA_EVENT_LOSS, /* loss timeout */ CA_EVENT_ECN_NO_CE, /* ECT set, but not CE marked */ CA_EVENT_ECN_IS_CE, /* received CE marked IP packet */ @@ -2923,7 +2946,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 }; /* Information about inbound ACK, passed to cong_ops->in_ack_event() */ -@@ -1021,7 +1043,11 @@ enum tcp_ca_ack_event_flags { +@@ -1026,7 +1048,11 @@ enum tcp_ca_ack_event_flags { #define TCP_CONG_NON_RESTRICTED 0x1 /* Requires ECN/ECT set on all packets */ #define TCP_CONG_NEEDS_ECN 0x2 @@ -2936,7 +2959,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 union tcp_cc_info; -@@ -1041,10 +1067,13 @@ struct ack_sample { +@@ -1046,10 +1072,13 @@ struct ack_sample { */ struct rate_sample { u64 prior_mstamp; /* starting timestamp for interval */ @@ -2951,7 +2974,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 long interval_us; /* time for tp->delivered to incr "delivered" */ u32 snd_interval_us; /* snd interval for delivered packets */ u32 rcv_interval_us; /* rcv interval for delivered packets */ -@@ -1055,7 +1084,9 @@ struct rate_sample { +@@ -1060,7 +1089,9 @@ struct rate_sample { u32 last_end_seq; /* end_seq of most recently ACKed packet */ bool is_app_limited; /* is sample from packet with bubble in pipe? */ bool is_retrans; /* is sample from retransmission? */ @@ -2961,7 +2984,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 }; struct tcp_congestion_ops { -@@ -1079,8 +1110,11 @@ struct tcp_congestion_ops { +@@ -1084,8 +1115,11 @@ struct tcp_congestion_ops { /* hook for packet ack accounting (optional) */ void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample); @@ -2975,7 +2998,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 /* call when packets are delivered to update cwnd and pacing rate, * after all the ca_state processing. (optional) -@@ -1146,6 +1180,14 @@ static inline char *tcp_ca_get_name_by_key(u32 key, char *buffer) +@@ -1151,6 +1185,14 @@ static inline char *tcp_ca_get_name_by_key(u32 key, char *buffer) } #endif @@ -2990,7 +3013,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 static inline bool tcp_ca_needs_ecn(const struct sock *sk) { const struct inet_connection_sock *icsk = inet_csk(sk); -@@ -1165,6 +1207,7 @@ static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) +@@ -1170,6 +1212,7 @@ static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) void tcp_set_ca_state(struct sock *sk, const u8 ca_state); /* From tcp_rate.c */ @@ -2998,7 +3021,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 void tcp_rate_skb_sent(struct sock *sk, struct sk_buff *skb); void tcp_rate_skb_delivered(struct sock *sk, struct sk_buff *skb, struct rate_sample *rs); -@@ -1177,6 +1220,21 @@ static inline bool tcp_skb_sent_after(u64 t1, u64 t2, u32 seq1, u32 seq2) +@@ -1182,6 +1225,21 @@ static inline bool tcp_skb_sent_after(u64 t1, u64 t2, u32 seq1, u32 seq2) return t1 > t2 || (t1 == t2 && after(seq1, seq2)); } @@ -3020,7 +3043,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 /* These functions determine how the current flow behaves in respect of SACK * handling. SACK is negotiated with the peer, and therefore it can vary * between different flows. -@@ -2176,7 +2234,7 @@ struct tcp_plb_state { +@@ -2181,7 +2239,7 @@ struct tcp_plb_state { u8 consec_cong_rounds:5, /* consecutive congested rounds */ unused:3; u32 pause_until; /* jiffies32 when PLB can resume rerouting */ @@ -3030,7 +3053,7 @@ index 10fc5c5928f71..f0f4a343df8cf 100644 static inline void tcp_plb_init(const struct sock *sk, struct tcp_plb_state *plb) diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h -index 50655de04c9b6..82f8bd8f0d161 100644 +index 50655de04c9b..82f8bd8f0d16 100644 --- a/include/uapi/linux/inet_diag.h +++ b/include/uapi/linux/inet_diag.h @@ -229,6 +229,29 @@ struct tcp_bbr_info { @@ -3064,7 +3087,7 @@ index 50655de04c9b6..82f8bd8f0d161 100644 union tcp_cc_info { diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h -index 51c13cf9c5aee..de8dcba26becc 100644 +index 51c13cf9c5ae..de8dcba26bec 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -506,9 +506,11 @@ enum { @@ -3081,7 +3104,7 @@ index 51c13cf9c5aee..de8dcba26becc 100644 struct rta_session { __u8 proto; diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h -index 879eeb0a084b4..77270053a5e39 100644 +index 879eeb0a084b..77270053a5e3 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h @@ -170,6 +170,7 @@ enum tcp_fastopen_client_fail { @@ -3093,7 +3116,7 @@ index 879eeb0a084b4..77270053a5e39 100644 /* * Sender's congestion state indicating normal or abnormal situations diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig -index 2dfb12230f089..2e14db3bee704 100644 +index 2dfb12230f08..2e14db3bee70 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -668,15 +668,18 @@ config TCP_CONG_BBR @@ -3125,10 +3148,10 @@ index 2dfb12230f089..2e14db3bee704 100644 choice prompt "Default TCP congestion control" diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c -index 75f24b931a185..25136face23c0 100644 +index 9bdc1b2eaf73..fdc880d6fe50 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c -@@ -3083,6 +3083,7 @@ int tcp_disconnect(struct sock *sk, int flags) +@@ -3077,6 +3077,7 @@ int tcp_disconnect(struct sock *sk, int flags) tp->rx_opt.dsack = 0; tp->rx_opt.num_sacks = 0; tp->rcv_ooopack = 0; @@ -3136,7 +3159,7 @@ index 75f24b931a185..25136face23c0 100644 /* Clean up fastopen related fields */ -@@ -3778,6 +3779,8 @@ void tcp_get_info(struct sock *sk, struct tcp_info *info) +@@ -3772,6 +3773,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; @@ -3146,7 +3169,7 @@ index 75f24b931a185..25136face23c0 100644 info->tcpi_options |= TCPI_OPT_SYN_DATA; diff --git a/net/ipv4/tcp_bbr.c b/net/ipv4/tcp_bbr.c -index 146792cd26fed..f4f477a69917d 100644 +index 146792cd26fe..f4f477a69917 100644 --- a/net/ipv4/tcp_bbr.c +++ b/net/ipv4/tcp_bbr.c @@ -1,18 +1,19 @@ @@ -5791,7 +5814,7 @@ index 146792cd26fed..f4f477a69917d 100644 MODULE_DESCRIPTION("TCP BBR (Bottleneck Bandwidth and RTT)"); +MODULE_VERSION(__stringify(BBR_VERSION)); diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c -index 1b34050a7538b..66d40449b3f4f 100644 +index 1b34050a7538..66d40449b3f4 100644 --- a/net/ipv4/tcp_cong.c +++ b/net/ipv4/tcp_cong.c @@ -241,6 +241,7 @@ void tcp_init_congestion_control(struct sock *sk) @@ -5803,10 +5826,10 @@ index 1b34050a7538b..66d40449b3f4f 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 48c2b96b08435..64f90414294b9 100644 +index a5781f86ac37..dbadaf0d2300 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c -@@ -348,7 +348,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) +@@ -361,7 +361,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) tcp_enter_quickack_mode(sk, 2); break; case INET_ECN_CE: @@ -5815,7 +5838,7 @@ index 48c2b96b08435..64f90414294b9 100644 tcp_ca_event(sk, CA_EVENT_ECN_IS_CE); if (!(tp->ecn_flags & TCP_ECN_DEMAND_CWR)) { -@@ -359,7 +359,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) +@@ -372,7 +372,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) tp->ecn_flags |= TCP_ECN_SEEN; break; default: @@ -5824,7 +5847,7 @@ index 48c2b96b08435..64f90414294b9 100644 tcp_ca_event(sk, CA_EVENT_ECN_NO_CE); tp->ecn_flags |= TCP_ECN_SEEN; break; -@@ -1078,7 +1078,12 @@ static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb) +@@ -1091,7 +1091,12 @@ static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb) */ static void tcp_notify_skb_loss_event(struct tcp_sock *tp, const struct sk_buff *skb) { @@ -5837,7 +5860,7 @@ index 48c2b96b08435..64f90414294b9 100644 } void tcp_mark_skb_lost(struct sock *sk, struct sk_buff *skb) -@@ -1459,6 +1464,17 @@ static bool tcp_shifted_skb(struct sock *sk, struct sk_buff *prev, +@@ -1472,6 +1477,17 @@ static bool tcp_shifted_skb(struct sock *sk, struct sk_buff *prev, WARN_ON_ONCE(tcp_skb_pcount(skb) < pcount); tcp_skb_pcount_add(skb, -pcount); @@ -5855,7 +5878,7 @@ index 48c2b96b08435..64f90414294b9 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 -@@ -3687,7 +3703,8 @@ static void tcp_replace_ts_recent(struct tcp_sock *tp, u32 seq) +@@ -3700,7 +3716,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 */ @@ -5865,7 +5888,7 @@ index 48c2b96b08435..64f90414294b9 100644 { struct tcp_sock *tp = tcp_sk(sk); -@@ -3704,6 +3721,7 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) +@@ -3717,6 +3734,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() */ @@ -5873,7 +5896,7 @@ index 48c2b96b08435..64f90414294b9 100644 tcp_init_cwnd_reduction(sk); tcp_set_ca_state(sk, TCP_CA_CWR); tcp_end_cwnd_reduction(sk); -@@ -3714,6 +3732,11 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) +@@ -3727,6 +3745,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; @@ -5885,7 +5908,7 @@ index 48c2b96b08435..64f90414294b9 100644 } } -@@ -3818,6 +3841,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3831,6 +3854,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); @@ -5893,7 +5916,7 @@ index 48c2b96b08435..64f90414294b9 100644 /* ts_recent update must be made after we are sure that the packet * is in window. -@@ -3892,7 +3916,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3905,7 +3929,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) @@ -5902,7 +5925,7 @@ index 48c2b96b08435..64f90414294b9 100644 if (tcp_ack_is_dubious(sk, flag)) { if (!(flag & (FLAG_SND_UNA_ADVANCED | -@@ -3916,6 +3940,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3929,6 +3953,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); @@ -5910,7 +5933,7 @@ index 48c2b96b08435..64f90414294b9 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); -@@ -3935,7 +3960,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3948,7 +3973,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) tcp_ack_probe(sk); if (tp->tlp_high_seq) @@ -5919,7 +5942,7 @@ index 48c2b96b08435..64f90414294b9 100644 return 1; old_ack: -@@ -5526,13 +5551,14 @@ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) +@@ -5539,13 +5564,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 && @@ -5937,7 +5960,7 @@ index 48c2b96b08435..64f90414294b9 100644 tcp_in_quickack_mode(sk) || /* Protocol state mandates a one-time immediate ACK */ diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c -index c8f2aa0033871..fdf51e436899f 100644 +index c8f2aa003387..fdf51e436899 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -440,6 +440,8 @@ void tcp_ca_openreq_child(struct sock *sk, const struct dst_entry *dst) @@ -5950,10 +5973,10 @@ index c8f2aa0033871..fdf51e436899f 100644 const struct tcp_congestion_ops *ca; diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c -index 9f9ca68c47026..9affccab1a942 100644 +index afa819eede6a..cabfeefa368b 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c -@@ -325,10 +325,9 @@ static void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) +@@ -324,10 +324,9 @@ static void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) bool bpf_needs_ecn = tcp_bpf_ca_needs_ecn(sk); bool use_ecn = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_ecn) == 1 || tcp_ca_needs_ecn(sk) || bpf_needs_ecn; @@ -5965,7 +5988,7 @@ index 9f9ca68c47026..9affccab1a942 100644 if (dst && dst_feature(dst, RTAX_FEATURE_ECN)) use_ecn = true; } -@@ -340,6 +339,9 @@ static void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) +@@ -339,6 +338,9 @@ static void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) tp->ecn_flags = TCP_ECN_OK; if (tcp_ca_needs_ecn(sk) || bpf_needs_ecn) INET_ECN_xmit(sk); @@ -5975,7 +5998,7 @@ index 9f9ca68c47026..9affccab1a942 100644 } } -@@ -377,7 +379,8 @@ static void tcp_ecn_send(struct sock *sk, struct sk_buff *skb, +@@ -376,7 +378,8 @@ static void tcp_ecn_send(struct sock *sk, struct sk_buff *skb, th->cwr = 1; skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN; } @@ -5985,7 +6008,7 @@ index 9f9ca68c47026..9affccab1a942 100644 /* ACK or retransmitted segment: clear ECT|CE */ INET_ECN_dontxmit(sk); } -@@ -1532,7 +1535,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, +@@ -1531,7 +1534,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, { struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *buff; @@ -5994,7 +6017,7 @@ index 9f9ca68c47026..9affccab1a942 100644 long limit; int nlen; u8 flags; -@@ -1607,6 +1610,30 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, +@@ -1606,6 +1609,30 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, if (diff) tcp_adjust_pcount(sk, skb, diff); @@ -6025,7 +6048,7 @@ index 9f9ca68c47026..9affccab1a942 100644 } /* Link BUFF into the send queue. */ -@@ -1982,13 +2009,12 @@ static u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, +@@ -1981,13 +2008,12 @@ static u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, static u32 tcp_tso_segs(struct sock *sk, unsigned int mss_now) { const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops; @@ -6044,7 +6067,7 @@ index 9f9ca68c47026..9affccab1a942 100644 return min_t(u32, tso_segs, sk->sk_gso_max_segs); } -@@ -2674,6 +2700,7 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, +@@ -2686,6 +2712,7 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, skb_set_delivery_time(skb, tp->tcp_wstamp_ns, true); list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue); tcp_init_tso_segs(skb, mss_now); @@ -6052,7 +6075,7 @@ index 9f9ca68c47026..9affccab1a942 100644 goto repair; /* Skip network transmission */ } -@@ -2886,6 +2913,7 @@ void tcp_send_loss_probe(struct sock *sk) +@@ -2899,6 +2926,7 @@ void tcp_send_loss_probe(struct sock *sk) if (WARN_ON(!skb || !tcp_skb_pcount(skb))) goto rearm_timer; @@ -6061,7 +6084,7 @@ index 9f9ca68c47026..9affccab1a942 100644 goto rearm_timer; diff --git a/net/ipv4/tcp_rate.c b/net/ipv4/tcp_rate.c -index a8f6d9d06f2eb..8737f21346481 100644 +index a8f6d9d06f2e..8737f2134648 100644 --- a/net/ipv4/tcp_rate.c +++ b/net/ipv4/tcp_rate.c @@ -34,6 +34,24 @@ @@ -6141,7 +6164,7 @@ index a8f6d9d06f2eb..8737f21346481 100644 rs->interval_us = max(snd_us, ack_us); diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c -index a9f6200f12f15..445c4df7406f7 100644 +index a9f6200f12f1..445c4df7406f 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c @@ -642,6 +642,7 @@ void tcp_write_timer_handler(struct sock *sk) @@ -6155,7 +6178,7 @@ index a9f6200f12f15..445c4df7406f7 100644 -- 2.42.0 -From 824c9adf2297e355eb9d6067869825bb179f248e Mon Sep 17 00:00:00 2001 +From 8cef81ab87bb29cd5c0557971d92de42bee60887 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 25 Sep 2023 18:09:53 +0200 Subject: [PATCH 4/7] cachy @@ -6223,7 +6246,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 f23ec4dc6c4b9..851e413f89676 100644 +index f23ec4dc6c4b..851e413f8967 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4276,6 +4276,15 @@ @@ -6243,7 +6266,7 @@ index f23ec4dc6c4b9..851e413f89676 100644 Safety option to keep boot IRQs enabled. This should never be necessary. diff --git a/Makefile b/Makefile -index 7545d2b0e7b71..4300dd7bd1356 100644 +index fc83c079c471..9ad36395d50d 100644 --- a/Makefile +++ b/Makefile @@ -831,6 +831,9 @@ KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member) @@ -6269,7 +6292,7 @@ index 7545d2b0e7b71..4300dd7bd1356 100644 KBUILD_CFLAGS += -Werror=date-time diff --git a/arch/arc/configs/axs101_defconfig b/arch/arc/configs/axs101_defconfig -index 81764160451f7..2c15d3bf747a9 100644 +index 81764160451f..2c15d3bf747a 100644 --- a/arch/arc/configs/axs101_defconfig +++ b/arch/arc/configs/axs101_defconfig @@ -9,6 +9,7 @@ CONFIG_NAMESPACES=y @@ -6281,7 +6304,7 @@ index 81764160451f7..2c15d3bf747a9 100644 CONFIG_PERF_EVENTS=y # CONFIG_VM_EVENT_COUNTERS is not set diff --git a/arch/arc/configs/axs103_defconfig b/arch/arc/configs/axs103_defconfig -index d5181275490ed..7d868e148d9a4 100644 +index d5181275490e..7d868e148d9a 100644 --- a/arch/arc/configs/axs103_defconfig +++ b/arch/arc/configs/axs103_defconfig @@ -9,6 +9,7 @@ CONFIG_NAMESPACES=y @@ -6293,7 +6316,7 @@ index d5181275490ed..7d868e148d9a4 100644 CONFIG_PERF_EVENTS=y # CONFIG_VM_EVENT_COUNTERS is not set diff --git a/arch/arc/configs/axs103_smp_defconfig b/arch/arc/configs/axs103_smp_defconfig -index 07c89281c2e3a..1513324ddb008 100644 +index 07c89281c2e3..1513324ddb00 100644 --- a/arch/arc/configs/axs103_smp_defconfig +++ b/arch/arc/configs/axs103_smp_defconfig @@ -9,6 +9,7 @@ CONFIG_NAMESPACES=y @@ -6305,7 +6328,7 @@ index 07c89281c2e3a..1513324ddb008 100644 CONFIG_PERF_EVENTS=y # CONFIG_VM_EVENT_COUNTERS is not set diff --git a/arch/arc/configs/haps_hs_defconfig b/arch/arc/configs/haps_hs_defconfig -index 8c3ed5d6e6c35..2db643853e8f4 100644 +index 8c3ed5d6e6c3..2db643853e8f 100644 --- a/arch/arc/configs/haps_hs_defconfig +++ b/arch/arc/configs/haps_hs_defconfig @@ -11,6 +11,7 @@ CONFIG_NAMESPACES=y @@ -6317,7 +6340,7 @@ index 8c3ed5d6e6c35..2db643853e8f4 100644 CONFIG_PERF_EVENTS=y # CONFIG_COMPAT_BRK is not set diff --git a/arch/arc/configs/haps_hs_smp_defconfig b/arch/arc/configs/haps_hs_smp_defconfig -index 61107e8bac336..d764007e5adad 100644 +index 61107e8bac33..d764007e5ada 100644 --- a/arch/arc/configs/haps_hs_smp_defconfig +++ b/arch/arc/configs/haps_hs_smp_defconfig @@ -11,6 +11,7 @@ CONFIG_NAMESPACES=y @@ -6329,7 +6352,7 @@ index 61107e8bac336..d764007e5adad 100644 CONFIG_PERF_EVENTS=y # CONFIG_VM_EVENT_COUNTERS is not set diff --git a/arch/arc/configs/hsdk_defconfig b/arch/arc/configs/hsdk_defconfig -index 4ee2a1507b57f..ce6a4431a76dd 100644 +index 4ee2a1507b57..ce6a4431a76d 100644 --- a/arch/arc/configs/hsdk_defconfig +++ b/arch/arc/configs/hsdk_defconfig @@ -9,6 +9,7 @@ CONFIG_NAMESPACES=y @@ -6341,7 +6364,7 @@ index 4ee2a1507b57f..ce6a4431a76dd 100644 CONFIG_PERF_EVENTS=y # CONFIG_VM_EVENT_COUNTERS is not set diff --git a/arch/arc/configs/nsim_700_defconfig b/arch/arc/configs/nsim_700_defconfig -index 3e98297759925..5044609540cc3 100644 +index 3e9829775992..5044609540cc 100644 --- a/arch/arc/configs/nsim_700_defconfig +++ b/arch/arc/configs/nsim_700_defconfig @@ -11,6 +11,7 @@ CONFIG_NAMESPACES=y @@ -6353,7 +6376,7 @@ index 3e98297759925..5044609540cc3 100644 CONFIG_EMBEDDED=y CONFIG_PERF_EVENTS=y diff --git a/arch/arc/configs/nsimosci_defconfig b/arch/arc/configs/nsimosci_defconfig -index 502c87f351c87..748c809d1c4c6 100644 +index 502c87f351c8..748c809d1c4c 100644 --- a/arch/arc/configs/nsimosci_defconfig +++ b/arch/arc/configs/nsimosci_defconfig @@ -10,6 +10,7 @@ CONFIG_NAMESPACES=y @@ -6365,7 +6388,7 @@ index 502c87f351c87..748c809d1c4c6 100644 CONFIG_EMBEDDED=y CONFIG_PERF_EVENTS=y diff --git a/arch/arc/configs/nsimosci_hs_defconfig b/arch/arc/configs/nsimosci_hs_defconfig -index f721cc3997d02..205c32b0074ca 100644 +index f721cc3997d0..205c32b0074c 100644 --- a/arch/arc/configs/nsimosci_hs_defconfig +++ b/arch/arc/configs/nsimosci_hs_defconfig @@ -10,6 +10,7 @@ CONFIG_NAMESPACES=y @@ -6377,7 +6400,7 @@ index f721cc3997d02..205c32b0074ca 100644 CONFIG_EMBEDDED=y CONFIG_PERF_EVENTS=y diff --git a/arch/arc/configs/nsimosci_hs_smp_defconfig b/arch/arc/configs/nsimosci_hs_smp_defconfig -index 1419fc946a083..2477b7c809771 100644 +index 1419fc946a08..2477b7c80977 100644 --- a/arch/arc/configs/nsimosci_hs_smp_defconfig +++ b/arch/arc/configs/nsimosci_hs_smp_defconfig @@ -8,6 +8,7 @@ CONFIG_IKCONFIG_PROC=y @@ -6389,7 +6412,7 @@ index 1419fc946a083..2477b7c809771 100644 # CONFIG_COMPAT_BRK is not set CONFIG_KPROBES=y diff --git a/arch/arc/configs/tb10x_defconfig b/arch/arc/configs/tb10x_defconfig -index 941bbadd6bf2c..e61132ba4f890 100644 +index 941bbadd6bf2..e61132ba4f89 100644 --- a/arch/arc/configs/tb10x_defconfig +++ b/arch/arc/configs/tb10x_defconfig @@ -14,6 +14,7 @@ CONFIG_INITRAMFS_SOURCE="../tb10x-rootfs.cpio" @@ -6401,7 +6424,7 @@ index 941bbadd6bf2c..e61132ba4f890 100644 # CONFIG_AIO is not set CONFIG_EMBEDDED=y diff --git a/arch/arc/configs/vdk_hs38_defconfig b/arch/arc/configs/vdk_hs38_defconfig -index d3ef189c75f8b..922b1b24f5184 100644 +index d3ef189c75f8..922b1b24f518 100644 --- a/arch/arc/configs/vdk_hs38_defconfig +++ b/arch/arc/configs/vdk_hs38_defconfig @@ -4,6 +4,7 @@ CONFIG_HIGH_RES_TIMERS=y @@ -6413,7 +6436,7 @@ index d3ef189c75f8b..922b1b24f5184 100644 CONFIG_PERF_EVENTS=y # CONFIG_VM_EVENT_COUNTERS is not set diff --git a/arch/arc/configs/vdk_hs38_smp_defconfig b/arch/arc/configs/vdk_hs38_smp_defconfig -index 944b347025fd1..ed64319f7eb29 100644 +index 944b347025fd..ed64319f7eb2 100644 --- a/arch/arc/configs/vdk_hs38_smp_defconfig +++ b/arch/arc/configs/vdk_hs38_smp_defconfig @@ -4,6 +4,7 @@ CONFIG_HIGH_RES_TIMERS=y @@ -6425,7 +6448,7 @@ index 944b347025fd1..ed64319f7eb29 100644 CONFIG_PERF_EVENTS=y # CONFIG_VM_EVENT_COUNTERS is not set diff --git a/arch/x86/Kconfig.cpu b/arch/x86/Kconfig.cpu -index 00468adf180f1..46cc91cb622fc 100644 +index 00468adf180f..46cc91cb622f 100644 --- a/arch/x86/Kconfig.cpu +++ b/arch/x86/Kconfig.cpu @@ -157,7 +157,7 @@ config MPENTIUM4 @@ -6958,7 +6981,7 @@ index 00468adf180f1..46cc91cb622fc 100644 config IA32_FEAT_CTL def_bool y diff --git a/arch/x86/Makefile b/arch/x86/Makefile -index fdc2e3abd6152..63845db8bf8a5 100644 +index fdc2e3abd615..63845db8bf8a 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -67,7 +67,7 @@ export BITS @@ -7022,7 +7045,7 @@ index fdc2e3abd6152..63845db8bf8a5 100644 KBUILD_CFLAGS += $(cflags-y) diff --git a/arch/x86/include/asm/pci.h b/arch/x86/include/asm/pci.h -index b40c462b4af36..c4e66e60d559d 100644 +index b40c462b4af3..c4e66e60d559 100644 --- a/arch/x86/include/asm/pci.h +++ b/arch/x86/include/asm/pci.h @@ -27,6 +27,7 @@ struct pci_sysdata { @@ -7046,7 +7069,7 @@ index b40c462b4af36..c4e66e60d559d 100644 already-configured bus numbers - to be used for buggy BIOSes or architectures with incomplete PCI setup by the loader */ diff --git a/arch/x86/include/asm/vermagic.h b/arch/x86/include/asm/vermagic.h -index 75884d2cdec37..02c1386eb653e 100644 +index 75884d2cdec3..02c1386eb653 100644 --- a/arch/x86/include/asm/vermagic.h +++ b/arch/x86/include/asm/vermagic.h @@ -17,6 +17,54 @@ @@ -7138,7 +7161,7 @@ index 75884d2cdec37..02c1386eb653e 100644 #define MODULE_PROC_FAMILY "ELAN " #elif defined CONFIG_MCRUSOE diff --git a/arch/x86/pci/common.c b/arch/x86/pci/common.c -index ddb798603201e..7c20387d82029 100644 +index ddb798603201..7c20387d8202 100644 --- a/arch/x86/pci/common.c +++ b/arch/x86/pci/common.c @@ -723,12 +723,15 @@ int pci_ext_cfg_avail(void) @@ -7160,7 +7183,7 @@ index ddb798603201e..7c20387d82029 100644 } -#endif diff --git a/block/bfq-iosched.c b/block/bfq-iosched.c -index 3cce6de464a7b..9176bc4f07daa 100644 +index 3cce6de464a7..9176bc4f07da 100644 --- a/block/bfq-iosched.c +++ b/block/bfq-iosched.c @@ -7627,6 +7627,7 @@ MODULE_ALIAS("bfq-iosched"); @@ -7184,7 +7207,7 @@ index 3cce6de464a7b..9176bc4f07daa 100644 slab_kill: diff --git a/drivers/Makefile b/drivers/Makefile -index 7241d80a7b293..ac0ca3498f43e 100644 +index 7241d80a7b29..ac0ca3498f43 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -64,15 +64,8 @@ obj-y += char/ @@ -7219,7 +7242,7 @@ index 7241d80a7b293..ac0ca3498f43e 100644 obj-$(CONFIG_MTD) += mtd/ obj-$(CONFIG_SPI) += spi/ diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c -index 7907b09fc27eb..a31562d18506d 100644 +index 7907b09fc27e..a31562d18506 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -1524,7 +1524,7 @@ static irqreturn_t ahci_thunderx_irq_handler(int irq, void *dev_instance) @@ -7275,7 +7298,7 @@ index 7907b09fc27eb..a31562d18506d 100644 sysfs_add_file_to_group(&pdev->dev.kobj, &dev_attr_remapped_nvme.attr, diff --git a/drivers/cpufreq/Kconfig.x86 b/drivers/cpufreq/Kconfig.x86 -index 438c9e75a04dc..1bbfeca5f01ec 100644 +index 438c9e75a04d..1bbfeca5f01e 100644 --- a/drivers/cpufreq/Kconfig.x86 +++ b/drivers/cpufreq/Kconfig.x86 @@ -9,7 +9,6 @@ config X86_INTEL_PSTATE @@ -7295,7 +7318,7 @@ index 438c9e75a04dc..1bbfeca5f01ec 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 9cfe8fc509d7d..efc3b0c0b4adb 100644 +index 9cfe8fc509d7..efc3b0c0b4ad 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -229,6 +229,15 @@ config I2C_CHT_WC @@ -7315,7 +7338,7 @@ index 9cfe8fc509d7d..efc3b0c0b4adb 100644 tristate "Nvidia nForce2, nForce3 and nForce4" depends on PCI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile -index af56fe2c75c09..76be74584719e 100644 +index af56fe2c75c0..76be74584719 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_I2C_CHT_WC) += i2c-cht-wc.o @@ -7328,7 +7351,7 @@ index af56fe2c75c09..76be74584719e 100644 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 0000000000000..e919d1e10c515 +index 000000000000..e919d1e10c51 --- /dev/null +++ b/drivers/i2c/busses/i2c-nct6775.c @@ -0,0 +1,648 @@ @@ -7981,7 +8004,7 @@ index 0000000000000..e919d1e10c515 +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 809fbd014cd68..d54b35b147ee9 100644 +index 809fbd014cd6..d54b35b147ee 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) @@ -7999,10 +8022,10 @@ index 809fbd014cd68..d54b35b147ee9 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 1dc6227d353ec..bab1009ccef79 100644 +index dc0463bf3c2c..e6121b9a105e 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) +@@ -3241,6 +3241,11 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) goto bad; } @@ -8015,7 +8038,7 @@ index 1dc6227d353ec..bab1009ccef79 100644 if (ret < 0) goto bad; diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile -index 37c8663de7fe1..897d19f92edeb 100644 +index 37c8663de7fe..897d19f92ede 100644 --- a/drivers/pci/controller/Makefile +++ b/drivers/pci/controller/Makefile @@ -1,4 +1,10 @@ @@ -8031,7 +8054,7 @@ index 37c8663de7fe1..897d19f92edeb 100644 obj-$(CONFIG_PCI_IXP4XX) += pci-ixp4xx.o diff --git a/drivers/pci/controller/intel-nvme-remap.c b/drivers/pci/controller/intel-nvme-remap.c new file mode 100644 -index 0000000000000..e105e6f5cc91d +index 000000000000..e105e6f5cc91 --- /dev/null +++ b/drivers/pci/controller/intel-nvme-remap.c @@ -0,0 +1,462 @@ @@ -8498,7 +8521,7 @@ index 0000000000000..e105e6f5cc91d +MODULE_AUTHOR("Daniel Drake "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c -index 321156ca273d5..5dda26c737e2c 100644 +index 321156ca273d..5dda26c737e2 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3718,6 +3718,106 @@ static void quirk_no_bus_reset(struct pci_dev *dev) @@ -8617,7 +8640,7 @@ index 321156ca273d5..5dda26c737e2c 100644 }; diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 49c2c4cd8d000..956f4eff85b5b 100644 +index 49c2c4cd8d00..956f4eff85b5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -643,6 +643,16 @@ config THINKPAD_LMI @@ -8659,7 +8682,7 @@ index 49c2c4cd8d000..956f4eff85b5b 100644 config P2SB diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 52dfdf574ac2d..71f65ef04f9e1 100644 +index 52dfdf574ac2..71f65ef04f9e 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o @@ -8679,7 +8702,7 @@ index 52dfdf574ac2d..71f65ef04f9e1 100644 +obj-$(CONFIG_STEAMDECK) += steamdeck.o diff --git a/drivers/platform/x86/legion-laptop.c b/drivers/platform/x86/legion-laptop.c new file mode 100644 -index 0000000000000..7275105071d27 +index 000000000000..7275105071d2 --- /dev/null +++ b/drivers/platform/x86/legion-laptop.c @@ -0,0 +1,5858 @@ @@ -14543,7 +14566,7 @@ index 0000000000000..7275105071d27 +module_exit(legion_exit); diff --git a/drivers/platform/x86/steamdeck.c b/drivers/platform/x86/steamdeck.c new file mode 100644 -index 0000000000000..77a6677ec19e6 +index 000000000000..77a6677ec19e --- /dev/null +++ b/drivers/platform/x86/steamdeck.c @@ -0,0 +1,523 @@ @@ -15071,7 +15094,7 @@ index 0000000000000..77a6677ec19e6 +MODULE_DESCRIPTION("Steam Deck ACPI platform driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mm.h b/include/linux/mm.h -index 34f9dba17c1a7..4527f319019aa 100644 +index 34f9dba17c1a..4527f319019a 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -191,7 +191,7 @@ static inline void __mm_zero_struct_page(struct page *page) @@ -15084,7 +15107,7 @@ index 34f9dba17c1a7..4527f319019aa 100644 extern int sysctl_max_map_count; diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h -index 716953ee1ebdb..dace360dc38d7 100644 +index 716953ee1ebd..dace360dc38d 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -1181,7 +1181,7 @@ struct readahead_control { @@ -15097,7 +15120,7 @@ index 716953ee1ebdb..dace360dc38d7 100644 void page_cache_ra_unbounded(struct readahead_control *, unsigned long nr_to_read, unsigned long lookahead_count); diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h -index 45f09bec02c48..87b20e2ee2744 100644 +index 45f09bec02c4..87b20e2ee274 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -148,6 +148,8 @@ static inline void set_userns_rlimit_max(struct user_namespace *ns, @@ -15119,7 +15142,7 @@ index 45f09bec02c48..87b20e2ee2744 100644 { return &init_user_ns; diff --git a/init/Kconfig b/init/Kconfig -index 5e7d4885d1bf8..25193a9d5c617 100644 +index 5e7d4885d1bf..25193a9d5c61 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -123,6 +123,10 @@ config THREAD_INFO_IN_TASK @@ -15170,7 +15193,7 @@ index 5e7d4885d1bf8..25193a9d5c617 100644 bool "Optimize for size (-Os)" help diff --git a/kernel/Kconfig.hz b/kernel/Kconfig.hz -index 38ef6d06888ef..0f78364efd4f2 100644 +index 38ef6d06888e..0f78364efd4f 100644 --- a/kernel/Kconfig.hz +++ b/kernel/Kconfig.hz @@ -40,6 +40,27 @@ choice @@ -15212,7 +15235,7 @@ index 38ef6d06888ef..0f78364efd4f2 100644 config SCHED_HRTICK diff --git a/kernel/fork.c b/kernel/fork.c -index f81149739eb9f..36fb0b711541d 100644 +index f81149739eb9..36fb0b711541 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -100,6 +100,10 @@ @@ -15251,7 +15274,7 @@ index f81149739eb9f..36fb0b711541d 100644 if (err) goto bad_unshare_out; diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c -index 1d9c2482c5a35..ff33866916263 100644 +index 1d9c2482c5a3..ff3386691626 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -69,9 +69,13 @@ @@ -15311,7 +15334,7 @@ index 1d9c2482c5a35..ff33866916263 100644 #ifdef CONFIG_NUMA_BALANCING /* Restrict the NUMA promotion throughput (MB/s) for each target node. */ diff --git a/kernel/sysctl.c b/kernel/sysctl.c -index 354a2d294f526..4dc780aa3bcc8 100644 +index 354a2d294f52..4dc780aa3bcc 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -95,6 +95,9 @@ EXPORT_SYMBOL_GPL(sysctl_long_vals); @@ -15341,7 +15364,7 @@ index 354a2d294f526..4dc780aa3bcc8 100644 { .procname = "tainted", diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c -index 1d8e47bed3f11..fec01d016a351 100644 +index 1d8e47bed3f1..fec01d016a35 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -22,6 +22,13 @@ @@ -15359,7 +15382,7 @@ index 1d8e47bed3f11..fec01d016a351 100644 static DEFINE_MUTEX(userns_state_mutex); diff --git a/lib/scatterlist.c b/lib/scatterlist.c -index c65566b4dc662..d3c8aaa68c5d3 100644 +index c65566b4dc66..d3c8aaa68c5d 100644 --- a/lib/scatterlist.c +++ b/lib/scatterlist.c @@ -150,31 +150,12 @@ EXPORT_SYMBOL(sg_init_one); @@ -15397,7 +15420,7 @@ index c65566b4dc662..d3c8aaa68c5d3 100644 /** diff --git a/mm/Kconfig b/mm/Kconfig -index 09130434e30d3..f772ba88df878 100644 +index 09130434e30d..f772ba88df87 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -631,7 +631,7 @@ config COMPACTION @@ -15410,7 +15433,7 @@ index 09130434e30d3..f772ba88df878 100644 # diff --git a/mm/internal.h b/mm/internal.h -index 8ed127c1c808c..2f3040ec707d7 100644 +index 8ed127c1c808..2f3040ec707d 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -421,6 +421,7 @@ extern void prep_compound_page(struct page *page, unsigned int order); @@ -15422,7 +15445,7 @@ index 8ed127c1c808c..2f3040ec707d7 100644 extern void free_unref_page(struct page *page, unsigned int order); extern void free_unref_page_list(struct list_head *list); diff --git a/mm/list_lru.c b/mm/list_lru.c -index a05e5bef3b400..0ead8e6651df0 100644 +index a05e5bef3b40..0ead8e6651df 100644 --- a/mm/list_lru.c +++ b/mm/list_lru.c @@ -178,6 +178,7 @@ EXPORT_SYMBOL_GPL(list_lru_isolate_move); @@ -15444,7 +15467,7 @@ index a05e5bef3b400..0ead8e6651df0 100644 EXPORT_SYMBOL_GPL(list_lru_count_one); diff --git a/mm/page-writeback.c b/mm/page-writeback.c -index d3f42009bb702..39b9fd0606304 100644 +index d3f42009bb70..39b9fd060630 100644 --- a/mm/page-writeback.c +++ b/mm/page-writeback.c @@ -71,7 +71,11 @@ static long ratelimit_pages = 32; @@ -15472,7 +15495,7 @@ index d3f42009bb702..39b9fd0606304 100644 EXPORT_SYMBOL_GPL(dirty_writeback_interval); diff --git a/mm/page_alloc.c b/mm/page_alloc.c -index 7d3460c7a480b..bd2a12f4e04de 100644 +index d322bfae8f69..c67954222af5 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -204,6 +204,8 @@ EXPORT_SYMBOL(node_states); @@ -15582,7 +15605,7 @@ index 7d3460c7a480b..bd2a12f4e04de 100644 } diff --git a/mm/swap.c b/mm/swap.c -index cd8f0150ba3aa..42c405a4f114c 100644 +index cd8f0150ba3a..42c405a4f114 100644 --- a/mm/swap.c +++ b/mm/swap.c @@ -1090,6 +1090,10 @@ void folio_batch_remove_exceptionals(struct folio_batch *fbatch) @@ -15603,7 +15626,7 @@ index cd8f0150ba3aa..42c405a4f114c 100644 +#endif } diff --git a/mm/vmpressure.c b/mm/vmpressure.c -index 22c6689d93027..bf65bd9abdf34 100644 +index 22c6689d9302..bf65bd9abdf3 100644 --- a/mm/vmpressure.c +++ b/mm/vmpressure.c @@ -43,7 +43,11 @@ static const unsigned long vmpressure_win = SWAP_CLUSTER_MAX * 16; @@ -15619,7 +15642,7 @@ index 22c6689d93027..bf65bd9abdf34 100644 /* diff --git a/mm/vmscan.c b/mm/vmscan.c -index da152407bc2b1..a958dc4e00245 100644 +index da152407bc2b..a958dc4e0024 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -186,7 +186,11 @@ struct scan_control { @@ -15713,7 +15736,7 @@ index da152407bc2b1..a958dc4e00245 100644 -- 2.42.0 -From 2ca2b1765d7ca43358ccdd33d4f1c5c29a7b507f Mon Sep 17 00:00:00 2001 +From 215c1496035a68adae4f992d995d0ccd58412e91 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Sat, 23 Sep 2023 13:08:08 +0200 Subject: [PATCH 5/7] fixes @@ -15725,28 +15748,24 @@ Signed-off-by: Peter Jung Documentation/leds/index.rst | 1 + Documentation/leds/ledtrig-blkdev.rst | 158 +++ drivers/bluetooth/btusb.c | 2 +- - drivers/gpu/drm/i915/gem/i915_gem_shmem.c | 11 +- drivers/leds/trigger/Kconfig | 9 + drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-blkdev.c | 1218 +++++++++++++++++ .../net/wireless/mediatek/mt76/mt7921/init.c | 9 +- fs/btrfs/extent-tree.c | 61 +- fs/btrfs/extent-tree.h | 13 +- - fs/btrfs/inode.c | 29 +- include/linux/pageblock-flags.h | 2 +- kernel/padata.c | 4 +- - mm/slab_common.c | 12 +- scripts/Makefile.vmlinux_o | 2 +- sound/pci/hda/cs35l41_hda.c | 2 +- sound/pci/hda/patch_realtek.c | 1 + - .../intel/common/soc-acpi-intel-adl-match.c | 12 +- - 20 files changed, 1548 insertions(+), 87 deletions(-) + 16 files changed, 1506 insertions(+), 65 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 diff --git a/Documentation/ABI/stable/sysfs-block b/Documentation/ABI/stable/sysfs-block -index c57e5b7cb5326..2d1df6c9b4635 100644 +index c57e5b7cb532..2d1df6c9b463 100644 --- a/Documentation/ABI/stable/sysfs-block +++ b/Documentation/ABI/stable/sysfs-block @@ -101,6 +101,16 @@ Description: @@ -15768,7 +15787,7 @@ index c57e5b7cb5326..2d1df6c9b4635 100644 Contact: Martin K. Petersen diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev new file mode 100644 -index 0000000000000..28ce8c814fb76 +index 000000000000..28ce8c814fb7 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev @@ -0,0 +1,78 @@ @@ -15851,7 +15870,7 @@ index 0000000000000..28ce8c814fb76 + may not match the device special file paths written to + link_device and unlink_device.) diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst -index 3ade16c18328a..3fd55a2cbfb5f 100644 +index 3ade16c18328..3fd55a2cbfb5 100644 --- a/Documentation/leds/index.rst +++ b/Documentation/leds/index.rst @@ -10,6 +10,7 @@ LEDs @@ -15864,7 +15883,7 @@ index 3ade16c18328a..3fd55a2cbfb5f 100644 ledtrig-usbport diff --git a/Documentation/leds/ledtrig-blkdev.rst b/Documentation/leds/ledtrig-blkdev.rst new file mode 100644 -index 0000000000000..9ff5b99de4514 +index 000000000000..9ff5b99de451 --- /dev/null +++ b/Documentation/leds/ledtrig-blkdev.rst @@ -0,0 +1,158 @@ @@ -16027,7 +16046,7 @@ index 0000000000000..9ff5b99de4514 + A device can be associated with multiple LEDs, and an LED can be associated + with multiple devices. diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index dfdfb72d350fe..daabd554ef37f 100644 +index ca9e2a210fff..1fc271adce26 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -960,7 +960,7 @@ static void btusb_qca_cmd_timeout(struct hci_dev *hdev) @@ -16039,47 +16058,8 @@ index dfdfb72d350fe..daabd554ef37f 100644 gpiod_set_value_cansleep(reset_gpio, 1); return; -diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c -index 8f1633c3fb935..73a4a4eb29e08 100644 ---- a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c -+++ b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c -@@ -100,6 +100,7 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, struct sg_table *st, - st->nents = 0; - for (i = 0; i < page_count; i++) { - struct folio *folio; -+ unsigned long nr_pages; - const unsigned int shrink[] = { - I915_SHRINK_BOUND | I915_SHRINK_UNBOUND, - 0, -@@ -150,6 +151,8 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, struct sg_table *st, - } - } while (1); - -+ nr_pages = min_t(unsigned long, -+ folio_nr_pages(folio), page_count - i); - if (!i || - sg->length >= max_segment || - folio_pfn(folio) != next_pfn) { -@@ -157,13 +160,13 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, struct sg_table *st, - sg = sg_next(sg); - - st->nents++; -- sg_set_folio(sg, folio, folio_size(folio), 0); -+ sg_set_folio(sg, folio, nr_pages * PAGE_SIZE, 0); - } else { - /* XXX: could overflow? */ -- sg->length += folio_size(folio); -+ sg->length += nr_pages * PAGE_SIZE; - } -- next_pfn = folio_pfn(folio) + folio_nr_pages(folio); -- i += folio_nr_pages(folio) - 1; -+ next_pfn = folio_pfn(folio) + nr_pages; -+ i += nr_pages - 1; - - /* Check that the i965g/gm workaround works. */ - GEM_BUG_ON(gfp & __GFP_DMA32 && next_pfn >= 0x00100000UL); diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig -index 2a57328eca207..05e80cfd0ed8d 100644 +index 2a57328eca20..05e80cfd0ed8 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -155,4 +155,13 @@ config LEDS_TRIGGER_TTY @@ -16097,7 +16077,7 @@ index 2a57328eca207..05e80cfd0ed8d 100644 + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile -index 25c4db97cdd4c..d53bab5d93f1c 100644 +index 25c4db97cdd4..d53bab5d93f1 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o @@ -16107,7 +16087,7 @@ index 25c4db97cdd4c..d53bab5d93f1c 100644 +obj-$(CONFIG_LEDS_TRIGGER_BLKDEV) += ledtrig-blkdev.o diff --git a/drivers/leds/trigger/ledtrig-blkdev.c b/drivers/leds/trigger/ledtrig-blkdev.c new file mode 100644 -index 0000000000000..9e0c4b66ea27d +index 000000000000..9e0c4b66ea27 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-blkdev.c @@ -0,0 +1,1218 @@ @@ -17330,7 +17310,7 @@ index 0000000000000..9e0c4b66ea27d +MODULE_AUTHOR("Ian Pilcher "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c -index f41975e37d06a..8d8f3dea3450e 100644 +index f41975e37d06..8d8f3dea3450 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c @@ -99,7 +99,8 @@ mt7921_init_wiphy(struct ieee80211_hw *hw) @@ -17357,7 +17337,7 @@ index f41975e37d06a..8d8f3dea3450e 100644 if (!mt76_is_mmio(&dev->mt76)) hw->extra_tx_headroom += MT_SDIO_TXD_SIZE + MT_SDIO_HDR_SIZE; diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c -index 0917c5f39e3d0..c8a598c3e11bd 100644 +index 2cf8d646085c..6e45a471ed37 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -3481,7 +3481,6 @@ btrfs_release_block_group(struct btrfs_block_group *cache, @@ -17487,7 +17467,7 @@ index 0917c5f39e3d0..c8a598c3e11bd 100644 cond_resched(); } diff --git a/fs/btrfs/extent-tree.h b/fs/btrfs/extent-tree.h -index 429d5c5700618..6bfba2f22fdd4 100644 +index 429d5c570061..6bfba2f22fdd 100644 --- a/fs/btrfs/extent-tree.h +++ b/fs/btrfs/extent-tree.h @@ -48,16 +48,11 @@ struct find_free_extent_ctl { @@ -17511,71 +17491,8 @@ index 429d5c5700618..6bfba2f22fdd4 100644 /* If current block group is cached */ int cached; -diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c -index db2b33a822fcd..d5c112f6091b1 100644 ---- a/fs/btrfs/inode.c -+++ b/fs/btrfs/inode.c -@@ -5931,20 +5931,24 @@ static int btrfs_set_inode_index_count(struct btrfs_inode *inode) - - static int btrfs_get_dir_last_index(struct btrfs_inode *dir, u64 *index) - { -- if (dir->index_cnt == (u64)-1) { -- int ret; -+ int ret = 0; - -+ btrfs_inode_lock(dir, 0); -+ if (dir->index_cnt == (u64)-1) { - ret = btrfs_inode_delayed_dir_index_count(dir); - if (ret) { - ret = btrfs_set_inode_index_count(dir); - if (ret) -- return ret; -+ goto out; - } - } - -- *index = dir->index_cnt; -+ /* index_cnt is the index number of next new entry, so decrement it. */ -+ *index = dir->index_cnt - 1; -+out: -+ btrfs_inode_unlock(dir, 0); - -- return 0; -+ return ret; - } - - /* -@@ -5979,6 +5983,19 @@ static int btrfs_opendir(struct inode *inode, struct file *file) - return 0; - } - -+static loff_t btrfs_dir_llseek(struct file *file, loff_t offset, int whence) -+{ -+ struct btrfs_file_private *private = file->private_data; -+ int ret; -+ -+ ret = btrfs_get_dir_last_index(BTRFS_I(file_inode(file)), -+ &private->last_index); -+ if (ret) -+ return ret; -+ -+ return generic_file_llseek(file, offset, whence); -+} -+ - struct dir_entry { - u64 ino; - u64 offset; -@@ -11059,7 +11076,7 @@ static const struct inode_operations btrfs_dir_inode_operations = { - }; - - static const struct file_operations btrfs_dir_file_operations = { -- .llseek = generic_file_llseek, -+ .llseek = btrfs_dir_llseek, - .read = generic_read_dir, - .iterate_shared = btrfs_real_readdir, - .open = btrfs_opendir, diff --git a/include/linux/pageblock-flags.h b/include/linux/pageblock-flags.h -index e83c4c0950417..21b8dfa5d8286 100644 +index e83c4c095041..21b8dfa5d828 100644 --- a/include/linux/pageblock-flags.h +++ b/include/linux/pageblock-flags.h @@ -48,7 +48,7 @@ extern unsigned int pageblock_order; @@ -17588,7 +17505,7 @@ index e83c4c0950417..21b8dfa5d8286 100644 #endif /* CONFIG_HUGETLB_PAGE */ diff --git a/kernel/padata.c b/kernel/padata.c -index 222d60195de66..b8e6b7c48746e 100644 +index 222d60195de6..b8e6b7c48746 100644 --- a/kernel/padata.c +++ b/kernel/padata.c @@ -45,7 +45,7 @@ struct padata_mt_job_state { @@ -17609,44 +17526,8 @@ index 222d60195de66..b8e6b7c48746e 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/mm/slab_common.c b/mm/slab_common.c -index d1555ea2981ac..5658da50a2d07 100644 ---- a/mm/slab_common.c -+++ b/mm/slab_common.c -@@ -479,7 +479,7 @@ void slab_kmem_cache_release(struct kmem_cache *s) - - void kmem_cache_destroy(struct kmem_cache *s) - { -- int refcnt; -+ int err = -EBUSY; - bool rcu_set; - - if (unlikely(!s) || !kasan_check_byte(s)) -@@ -490,17 +490,17 @@ void kmem_cache_destroy(struct kmem_cache *s) - - rcu_set = s->flags & SLAB_TYPESAFE_BY_RCU; - -- refcnt = --s->refcount; -- if (refcnt) -+ s->refcount--; -+ if (s->refcount) - goto out_unlock; - -- WARN(shutdown_cache(s), -- "%s %s: Slab cache still has objects when called from %pS", -+ err = shutdown_cache(s); -+ WARN(err, "%s %s: Slab cache still has objects when called from %pS", - __func__, s->name, (void *)_RET_IP_); - out_unlock: - mutex_unlock(&slab_mutex); - cpus_read_unlock(); -- if (!refcnt && !rcu_set) -+ if (!err && !rcu_set) - kmem_cache_release(s); - } - EXPORT_SYMBOL(kmem_cache_destroy); diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o -index 0edfdb40364b8..ae52d3b3f0637 100644 +index 0edfdb40364b..ae52d3b3f063 100644 --- a/scripts/Makefile.vmlinux_o +++ b/scripts/Makefile.vmlinux_o @@ -19,7 +19,7 @@ quiet_cmd_gen_initcalls_lds = GEN $@ @@ -17659,10 +17540,10 @@ index 0edfdb40364b8..ae52d3b3f0637 100644 targets := .tmp_initcalls.lds diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c -index ce5faa6205170..1f0f2b8df3005 100644 +index 9ba77e685126..398fa8b31536 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c -@@ -1235,7 +1235,7 @@ static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physd +@@ -1278,7 +1278,7 @@ static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physd if (strncmp(hid, "CLSA0100", 8) == 0) { hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; @@ -17672,10 +17553,10 @@ index ce5faa6205170..1f0f2b8df3005 100644 hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; hw_cfg->gpio1.valid = true; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c -index dc7b7a407638a..26f5da23e3bdc 100644 +index c2fbf484b110..b79c16bc2ed7 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c -@@ -9658,6 +9658,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { +@@ -9748,6 +9748,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), @@ -17683,39 +17564,10 @@ index dc7b7a407638a..26f5da23e3bdc 100644 SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), -diff --git a/sound/soc/intel/common/soc-acpi-intel-adl-match.c b/sound/soc/intel/common/soc-acpi-intel-adl-match.c -index bcd66e0094b4b..c4b57cca6b228 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-adl-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-adl-match.c -@@ -648,18 +648,18 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_sdw_machines[] = { - .drv_name = "sof_sdw", - .sof_tplg_filename = "sof-adl-rt1316-l2-mono-rt714-l3.tplg", - }, -- { -- .link_mask = 0x3, /* rt1316 on link1 & rt714 on link0 */ -- .links = adl_sdw_rt1316_link1_rt714_link0, -- .drv_name = "sof_sdw", -- .sof_tplg_filename = "sof-adl-rt1316-l1-mono-rt714-l0.tplg", -- }, - { - .link_mask = 0x7, /* rt714 on link0 & two rt1316s on link1 and link2 */ - .links = adl_sdw_rt1316_link12_rt714_link0, - .drv_name = "sof_sdw", - .sof_tplg_filename = "sof-adl-rt1316-l12-rt714-l0.tplg", - }, -+ { -+ .link_mask = 0x3, /* rt1316 on link1 & rt714 on link0 */ -+ .links = adl_sdw_rt1316_link1_rt714_link0, -+ .drv_name = "sof_sdw", -+ .sof_tplg_filename = "sof-adl-rt1316-l1-mono-rt714-l0.tplg", -+ }, - { - .link_mask = 0x5, /* 2 active links required */ - .links = adl_sdw_rt1316_link2_rt714_link0, -- 2.42.0 -From 0aeaaa0f640430f7144d2d10165b89750651cd3c Mon Sep 17 00:00:00 2001 +From cbedf8cd5469fafe8876450ab9d41ce50e830ea9 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Wed, 13 Sep 2023 14:33:16 +0200 Subject: [PATCH 6/7] ksm @@ -17755,7 +17607,7 @@ Signed-off-by: Peter Jung 30 files changed, 390 insertions(+), 18 deletions(-) diff --git a/Documentation/admin-guide/mm/ksm.rst b/Documentation/admin-guide/mm/ksm.rst -index 7626392fe82cb..5c5be7bd84b81 100644 +index 7626392fe82c..5c5be7bd84b8 100644 --- a/Documentation/admin-guide/mm/ksm.rst +++ b/Documentation/admin-guide/mm/ksm.rst @@ -173,6 +173,13 @@ stable_node_chains @@ -17806,7 +17658,7 @@ index 7626392fe82cb..5c5be7bd84b81 100644 From the perspective of application, a high ratio of ``ksm_rmap_items`` to ``ksm_merging_pages`` means a bad madvise-applied policy, so developers or diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl -index 1f13995d00d7b..4a5bc2a91fa74 100644 +index 1f13995d00d7..4a5bc2a91fa7 100644 --- a/arch/alpha/kernel/syscalls/syscall.tbl +++ b/arch/alpha/kernel/syscalls/syscall.tbl @@ -491,3 +491,6 @@ @@ -17817,7 +17669,7 @@ index 1f13995d00d7b..4a5bc2a91fa74 100644 +563 common process_ksm_disable sys_process_ksm_disable +564 common process_ksm_status sys_process_ksm_status diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl -index 8ebed8a138747..d616dcc060df3 100644 +index 8ebed8a13874..d616dcc060df 100644 --- a/arch/arm/tools/syscall.tbl +++ b/arch/arm/tools/syscall.tbl @@ -465,3 +465,6 @@ @@ -17828,7 +17680,7 @@ index 8ebed8a138747..d616dcc060df3 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/arm64/include/asm/unistd.h b/arch/arm64/include/asm/unistd.h -index 64a514f90131b..63a8a9c4abc16 100644 +index 64a514f90131..63a8a9c4abc1 100644 --- a/arch/arm64/include/asm/unistd.h +++ b/arch/arm64/include/asm/unistd.h @@ -39,7 +39,7 @@ @@ -17841,7 +17693,7 @@ index 64a514f90131b..63a8a9c4abc16 100644 #define __ARCH_WANT_SYS_CLONE diff --git a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h -index d952a28463e01..c99c8260489b8 100644 +index d952a28463e0..c99c8260489b 100644 --- a/arch/arm64/include/asm/unistd32.h +++ b/arch/arm64/include/asm/unistd32.h @@ -909,6 +909,12 @@ __SYSCALL(__NR_futex_waitv, sys_futex_waitv) @@ -17858,7 +17710,7 @@ index d952a28463e01..c99c8260489b8 100644 /* * Please add new compat syscalls above this comment and update diff --git a/arch/ia64/kernel/syscalls/syscall.tbl b/arch/ia64/kernel/syscalls/syscall.tbl -index f8c74ffeeefbe..735157909c6fb 100644 +index f8c74ffeeefb..735157909c6f 100644 --- a/arch/ia64/kernel/syscalls/syscall.tbl +++ b/arch/ia64/kernel/syscalls/syscall.tbl @@ -372,3 +372,6 @@ @@ -17869,7 +17721,7 @@ index f8c74ffeeefbe..735157909c6fb 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl -index 4f504783371fc..25b22d311f108 100644 +index 4f504783371f..25b22d311f10 100644 --- a/arch/m68k/kernel/syscalls/syscall.tbl +++ b/arch/m68k/kernel/syscalls/syscall.tbl @@ -451,3 +451,6 @@ @@ -17880,7 +17732,7 @@ index 4f504783371fc..25b22d311f108 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl -index 858d22bf275c2..e548c182a33ef 100644 +index 858d22bf275c..e548c182a33e 100644 --- a/arch/microblaze/kernel/syscalls/syscall.tbl +++ b/arch/microblaze/kernel/syscalls/syscall.tbl @@ -457,3 +457,6 @@ @@ -17891,7 +17743,7 @@ index 858d22bf275c2..e548c182a33ef 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl -index 1976317d4e8b0..fed21167be444 100644 +index 1976317d4e8b..fed21167be44 100644 --- a/arch/mips/kernel/syscalls/syscall_n32.tbl +++ b/arch/mips/kernel/syscalls/syscall_n32.tbl @@ -390,3 +390,6 @@ @@ -17902,7 +17754,7 @@ index 1976317d4e8b0..fed21167be444 100644 +453 n32 process_ksm_disable sys_process_ksm_disable +454 n32 process_ksm_status sys_process_ksm_status diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl -index cfda2511badf3..b27ae871f676f 100644 +index cfda2511badf..b27ae871f676 100644 --- a/arch/mips/kernel/syscalls/syscall_n64.tbl +++ b/arch/mips/kernel/syscalls/syscall_n64.tbl @@ -366,3 +366,6 @@ @@ -17913,7 +17765,7 @@ index cfda2511badf3..b27ae871f676f 100644 +453 n64 process_ksm_disable sys_process_ksm_disable +454 n64 process_ksm_status sys_process_ksm_status diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl -index 7692234c37683..59f298413c292 100644 +index 7692234c3768..59f298413c29 100644 --- a/arch/mips/kernel/syscalls/syscall_o32.tbl +++ b/arch/mips/kernel/syscalls/syscall_o32.tbl @@ -439,3 +439,6 @@ @@ -17924,7 +17776,7 @@ index 7692234c37683..59f298413c292 100644 +453 o32 process_ksm_disable sys_process_ksm_disable +454 o32 process_ksm_status sys_process_ksm_status diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl -index a0a9145b6dd4f..494b59d1185fa 100644 +index a0a9145b6dd4..494b59d1185f 100644 --- a/arch/parisc/kernel/syscalls/syscall.tbl +++ b/arch/parisc/kernel/syscalls/syscall.tbl @@ -450,3 +450,6 @@ @@ -17935,7 +17787,7 @@ index a0a9145b6dd4f..494b59d1185fa 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl -index 8c0b08b7a80ec..499d7b233a431 100644 +index 8c0b08b7a80e..499d7b233a43 100644 --- a/arch/powerpc/kernel/syscalls/syscall.tbl +++ b/arch/powerpc/kernel/syscalls/syscall.tbl @@ -538,3 +538,6 @@ @@ -17946,7 +17798,7 @@ index 8c0b08b7a80ec..499d7b233a431 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl -index a6935af2235ca..97b36ce151556 100644 +index a6935af2235c..97b36ce15155 100644 --- a/arch/s390/kernel/syscalls/syscall.tbl +++ b/arch/s390/kernel/syscalls/syscall.tbl @@ -454,3 +454,6 @@ @@ -17957,7 +17809,7 @@ index a6935af2235ca..97b36ce151556 100644 +453 common process_ksm_disable sys_process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status sys_process_ksm_status diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl -index 97377e8c50251..bd3827e1fc8d9 100644 +index 97377e8c5025..bd3827e1fc8d 100644 --- a/arch/sh/kernel/syscalls/syscall.tbl +++ b/arch/sh/kernel/syscalls/syscall.tbl @@ -454,3 +454,6 @@ @@ -17968,7 +17820,7 @@ index 97377e8c50251..bd3827e1fc8d9 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl -index faa835f3c54a5..c05e62a0ca026 100644 +index faa835f3c54a..c05e62a0ca02 100644 --- a/arch/sparc/kernel/syscalls/syscall.tbl +++ b/arch/sparc/kernel/syscalls/syscall.tbl @@ -497,3 +497,6 @@ @@ -17979,7 +17831,7 @@ index faa835f3c54a5..c05e62a0ca026 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl -index bc0a3c941b35c..c79bd2dd758da 100644 +index bc0a3c941b35..c79bd2dd758d 100644 --- a/arch/x86/entry/syscalls/syscall_32.tbl +++ b/arch/x86/entry/syscalls/syscall_32.tbl @@ -456,3 +456,6 @@ @@ -17990,7 +17842,7 @@ index bc0a3c941b35c..c79bd2dd758da 100644 +453 i386 process_ksm_disable sys_process_ksm_disable +454 i386 process_ksm_status sys_process_ksm_status diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl -index 227538b0ce801..e146a70cc299f 100644 +index 227538b0ce80..e146a70cc299 100644 --- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl @@ -373,6 +373,9 @@ @@ -18004,7 +17856,7 @@ index 227538b0ce801..e146a70cc299f 100644 # # Due to a historical design error, certain syscalls are numbered differently diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl -index 2b69c3c035b6a..b7bf81a3ba133 100644 +index 2b69c3c035b6..b7bf81a3ba13 100644 --- a/arch/xtensa/kernel/syscalls/syscall.tbl +++ b/arch/xtensa/kernel/syscalls/syscall.tbl @@ -422,3 +422,6 @@ @@ -18015,7 +17867,7 @@ index 2b69c3c035b6a..b7bf81a3ba133 100644 +453 common process_ksm_disable sys_process_ksm_disable +454 common process_ksm_status sys_process_ksm_status diff --git a/fs/proc/base.c b/fs/proc/base.c -index ee4b824658a0a..9b0beb26cbd48 100644 +index ee4b824658a0..9b0beb26cbd4 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -3207,6 +3207,7 @@ static int proc_pid_ksm_stat(struct seq_file *m, struct pid_namespace *ns, @@ -18027,7 +17879,7 @@ index ee4b824658a0a..9b0beb26cbd48 100644 seq_printf(m, "ksm_process_profit %ld\n", ksm_process_profit(mm)); mmput(mm); diff --git a/include/linux/ksm.h b/include/linux/ksm.h -index 899a314bc4872..c2dd786a30e1f 100644 +index 899a314bc487..c2dd786a30e1 100644 --- a/include/linux/ksm.h +++ b/include/linux/ksm.h @@ -26,6 +26,22 @@ int ksm_disable(struct mm_struct *mm); @@ -18065,7 +17917,7 @@ index 899a314bc4872..c2dd786a30e1f 100644 static inline void collect_procs_ksm(struct page *page, struct list_head *to_kill, int force_early) diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h -index 7d30dc4ff0ff1..d8d8cc1348d6c 100644 +index 7d30dc4ff0ff..d8d8cc1348d6 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -812,7 +812,7 @@ struct mm_struct { @@ -18092,7 +17944,7 @@ index 7d30dc4ff0ff1..d8d8cc1348d6c 100644 struct { /* this mm_struct is on lru_gen_mm_list */ diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h -index 03e3d0121d5e3..16597dea90f40 100644 +index 03e3d0121d5e..16597dea90f4 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -813,6 +813,9 @@ asmlinkage long sys_madvise(unsigned long start, size_t len, int behavior); @@ -18106,7 +17958,7 @@ index 03e3d0121d5e3..16597dea90f40 100644 unsigned long prot, unsigned long pgoff, unsigned long flags); diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h -index fd6c1cb585db4..11d0fc82c4378 100644 +index fd6c1cb585db..11d0fc82c437 100644 --- a/include/uapi/asm-generic/unistd.h +++ b/include/uapi/asm-generic/unistd.h @@ -820,8 +820,17 @@ __SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node) @@ -18129,7 +17981,7 @@ index fd6c1cb585db4..11d0fc82c4378 100644 /* * 32 bit systems traditionally used different diff --git a/kernel/sys.c b/kernel/sys.c -index 2410e3999ebe5..b0841a2dd2b7a 100644 +index 2410e3999ebe..b0841a2dd2b7 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -2727,6 +2727,153 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, @@ -18287,7 +18139,7 @@ index 2410e3999ebe5..b0841a2dd2b7a 100644 struct getcpu_cache __user *, unused) { diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c -index 781de7cc6a4e1..49a35d35d0f97 100644 +index 781de7cc6a4e..49a35d35d0f9 100644 --- a/kernel/sys_ni.c +++ b/kernel/sys_ni.c @@ -184,6 +184,9 @@ COND_SYSCALL(mincore); @@ -18301,7 +18153,7 @@ index 781de7cc6a4e1..49a35d35d0f97 100644 COND_SYSCALL(mbind); COND_SYSCALL(get_mempolicy); diff --git a/mm/khugepaged.c b/mm/khugepaged.c -index 78c8d5d8b6284..4b8b8673d5d9f 100644 +index 78c8d5d8b628..4b8b8673d5d9 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -19,6 +19,7 @@ @@ -18321,7 +18173,7 @@ index 78c8d5d8b6284..4b8b8673d5d9f 100644 } else { src_page = pte_page(pteval); diff --git a/mm/ksm.c b/mm/ksm.c -index d7b5b95e936e9..6b7b8928fb965 100644 +index d7b5b95e936e..6b7b8928fb96 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -278,6 +278,9 @@ static unsigned int zero_checksum __read_mostly; @@ -18400,7 +18252,7 @@ index d7b5b95e936e9..6b7b8928fb965 100644 #ifdef CONFIG_NUMA &merge_across_nodes_attr.attr, diff --git a/mm/memory.c b/mm/memory.c -index cdc4d4c1c858a..428943ecda254 100644 +index cdc4d4c1c858..428943ecda25 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1433,8 +1433,10 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb, @@ -18424,7 +18276,7 @@ index cdc4d4c1c858a..428943ecda254 100644 } flush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte)); diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c -index 26853badae705..0de9d33cd565d 100644 +index 26853badae70..0de9d33cd565 100644 --- a/tools/testing/selftests/mm/ksm_functional_tests.c +++ b/tools/testing/selftests/mm/ksm_functional_tests.c @@ -29,6 +29,8 @@ @@ -18565,7 +18417,7 @@ index 26853badae705..0de9d33cd565d 100644 -- 2.42.0 -From 0de12707e489714de0eb3d25f004f02c01436204 Mon Sep 17 00:00:00 2001 +From fb826c80eee7ebf2b9dfeca032b64ac680558a6f Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Wed, 13 Sep 2023 14:33:28 +0200 Subject: [PATCH 7/7] zstd @@ -18635,7 +18487,7 @@ Signed-off-by: Peter Jung create mode 100644 lib/zstd/common/bits.h diff --git a/include/linux/zstd.h b/include/linux/zstd.h -index 113408eef6ece..f109d49f43f80 100644 +index 113408eef6ec..f109d49f43f8 100644 --- a/include/linux/zstd.h +++ b/include/linux/zstd.h @@ -1,6 +1,6 @@ @@ -18647,7 +18499,7 @@ index 113408eef6ece..f109d49f43f80 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/include/linux/zstd_errors.h b/include/linux/zstd_errors.h -index 58b6dd45a969f..6d5cf55f0bf3e 100644 +index 58b6dd45a969..6d5cf55f0bf3 100644 --- a/include/linux/zstd_errors.h +++ b/include/linux/zstd_errors.h @@ -1,5 +1,6 @@ @@ -18713,7 +18565,7 @@ index 58b6dd45a969f..6d5cf55f0bf3e 100644 } ZSTD_ErrorCode; diff --git a/include/linux/zstd_lib.h b/include/linux/zstd_lib.h -index 79d55465d5c1d..8b4ffe649df57 100644 +index 79d55465d5c1..8b4ffe649df5 100644 --- a/include/linux/zstd_lib.h +++ b/include/linux/zstd_lib.h @@ -1,5 +1,6 @@ @@ -19905,7 +19757,7 @@ index 79d55465d5c1d..8b4ffe649df57 100644 #endif /* ZSTD_H_ZSTD_STATIC_LINKING_ONLY */ diff --git a/lib/zstd/Makefile b/lib/zstd/Makefile -index 20f08c644b71a..464c410b2768c 100644 +index 20f08c644b71..464c410b2768 100644 --- a/lib/zstd/Makefile +++ b/lib/zstd/Makefile @@ -1,6 +1,6 @@ @@ -19918,7 +19770,7 @@ index 20f08c644b71a..464c410b2768c 100644 # This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/common/allocations.h b/lib/zstd/common/allocations.h new file mode 100644 -index 0000000000000..05adbbeccaa9b +index 000000000000..05adbbeccaa9 --- /dev/null +++ b/lib/zstd/common/allocations.h @@ -0,0 +1,56 @@ @@ -19980,7 +19832,7 @@ index 0000000000000..05adbbeccaa9b +#endif /* ZSTD_ALLOCATIONS_H */ diff --git a/lib/zstd/common/bits.h b/lib/zstd/common/bits.h new file mode 100644 -index 0000000000000..aa3487ec4b6a7 +index 000000000000..aa3487ec4b6a --- /dev/null +++ b/lib/zstd/common/bits.h @@ -0,0 +1,149 @@ @@ -20134,7 +19986,7 @@ index 0000000000000..aa3487ec4b6a7 + +#endif /* ZSTD_BITS_H */ diff --git a/lib/zstd/common/bitstream.h b/lib/zstd/common/bitstream.h -index feef3a1b1d600..444dc4f85c649 100644 +index feef3a1b1d60..444dc4f85c64 100644 --- a/lib/zstd/common/bitstream.h +++ b/lib/zstd/common/bitstream.h @@ -1,7 +1,8 @@ @@ -20261,7 +20113,7 @@ index feef3a1b1d600..444dc4f85c649 100644 if (bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8)) /* overflow detected, like end of stream */ return BIT_DStream_overflow; diff --git a/lib/zstd/common/compiler.h b/lib/zstd/common/compiler.h -index c42d39faf9bd8..c437e09755750 100644 +index c42d39faf9bd..c437e0975575 100644 --- a/lib/zstd/common/compiler.h +++ b/lib/zstd/common/compiler.h @@ -1,5 +1,6 @@ @@ -20291,7 +20143,7 @@ index c42d39faf9bd8..c437e09755750 100644 #endif /* ZSTD_COMPILER_H */ diff --git a/lib/zstd/common/cpu.h b/lib/zstd/common/cpu.h -index 0db7b42407eea..d8319a2bef4ce 100644 +index 0db7b42407ee..d8319a2bef4c 100644 --- a/lib/zstd/common/cpu.h +++ b/lib/zstd/common/cpu.h @@ -1,5 +1,6 @@ @@ -20303,7 +20155,7 @@ index 0db7b42407eea..d8319a2bef4ce 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 bb863c9ea6164..e56ff6464e918 100644 +index bb863c9ea616..e56ff6464e91 100644 --- a/lib/zstd/common/debug.c +++ b/lib/zstd/common/debug.c @@ -1,7 +1,8 @@ @@ -20317,7 +20169,7 @@ index bb863c9ea6164..e56ff6464e918 100644 * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/lib/zstd/common/debug.h b/lib/zstd/common/debug.h -index 6dd88d1fbd02c..da0dbfc614b88 100644 +index 6dd88d1fbd02..da0dbfc614b8 100644 --- a/lib/zstd/common/debug.h +++ b/lib/zstd/common/debug.h @@ -1,7 +1,8 @@ @@ -20331,7 +20183,7 @@ index 6dd88d1fbd02c..da0dbfc614b88 100644 * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c -index fef67056f0524..6cdd82233fb59 100644 +index fef67056f052..6cdd82233fb5 100644 --- a/lib/zstd/common/entropy_common.c +++ b/lib/zstd/common/entropy_common.c @@ -1,6 +1,7 @@ @@ -20449,7 +20301,7 @@ index fef67056f0524..6cdd82233fb59 100644 return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); } diff --git a/lib/zstd/common/error_private.c b/lib/zstd/common/error_private.c -index 6d1135f8c3733..a4062d30d1703 100644 +index 6d1135f8c373..a4062d30d170 100644 --- a/lib/zstd/common/error_private.c +++ b/lib/zstd/common/error_private.c @@ -1,5 +1,6 @@ @@ -20497,7 +20349,7 @@ index 6d1135f8c3733..a4062d30d1703 100644 default: return notErrorCode; } diff --git a/lib/zstd/common/error_private.h b/lib/zstd/common/error_private.h -index ca5101e542faa..9a4699a38a881 100644 +index ca5101e542fa..9a4699a38a88 100644 --- a/lib/zstd/common/error_private.h +++ b/lib/zstd/common/error_private.h @@ -1,5 +1,6 @@ @@ -20509,7 +20361,7 @@ index ca5101e542faa..9a4699a38a881 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/common/fse.h b/lib/zstd/common/fse.h -index 4507043b2287c..c4e25a2191429 100644 +index 4507043b2287..c4e25a219142 100644 --- a/lib/zstd/common/fse.h +++ b/lib/zstd/common/fse.h @@ -1,7 +1,8 @@ @@ -20661,7 +20513,7 @@ index 4507043b2287c..c4e25a2191429 100644 * note 2 : if freq[symbolValue]==0, @return a fake cost of tableLog+1 bits */ MEM_STATIC U32 FSE_getMaxNbBits(const void* symbolTTPtr, U32 symbolValue) diff --git a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c -index a0d06095be83d..45cf457f31ef8 100644 +index a0d06095be83..45cf457f31ef 100644 --- a/lib/zstd/common/fse_decompress.c +++ b/lib/zstd/common/fse_decompress.c @@ -1,6 +1,7 @@ @@ -20821,7 +20673,7 @@ index a0d06095be83d..45cf457f31ef8 100644 - #endif /* FSE_COMMONDEFS_ONLY */ diff --git a/lib/zstd/common/huf.h b/lib/zstd/common/huf.h -index 5042ff8703087..8e7943092ed1a 100644 +index 5042ff870308..8e7943092ed1 100644 --- a/lib/zstd/common/huf.h +++ b/lib/zstd/common/huf.h @@ -1,7 +1,8 @@ @@ -21148,7 +21000,7 @@ index 5042ff8703087..8e7943092ed1a 100644 +#endif /* HUF_H_298734234 */ diff --git a/lib/zstd/common/mem.h b/lib/zstd/common/mem.h -index 1d9cc03924ca9..a7231822b6e32 100644 +index 1d9cc03924ca..a7231822b6e3 100644 --- a/lib/zstd/common/mem.h +++ b/lib/zstd/common/mem.h @@ -1,6 +1,6 @@ @@ -21160,7 +21012,7 @@ index 1d9cc03924ca9..a7231822b6e32 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/common/portability_macros.h b/lib/zstd/common/portability_macros.h -index 0e3b2c0a527db..7ede8cf1ffe57 100644 +index 0e3b2c0a527d..7ede8cf1ffe5 100644 --- a/lib/zstd/common/portability_macros.h +++ b/lib/zstd/common/portability_macros.h @@ -1,5 +1,6 @@ @@ -21214,7 +21066,7 @@ index 0e3b2c0a527db..7ede8cf1ffe57 100644 + #endif /* ZSTD_PORTABILITY_MACROS_H */ diff --git a/lib/zstd/common/zstd_common.c b/lib/zstd/common/zstd_common.c -index 3d7e35b309b5d..44b95b25344a1 100644 +index 3d7e35b309b5..44b95b25344a 100644 --- a/lib/zstd/common/zstd_common.c +++ b/lib/zstd/common/zstd_common.c @@ -1,5 +1,6 @@ @@ -21272,7 +21124,7 @@ index 3d7e35b309b5d..44b95b25344a1 100644 - } -} diff --git a/lib/zstd/common/zstd_deps.h b/lib/zstd/common/zstd_deps.h -index 2c34e8a33a1c1..670c5fa2a952d 100644 +index 2c34e8a33a1c..670c5fa2a952 100644 --- a/lib/zstd/common/zstd_deps.h +++ b/lib/zstd/common/zstd_deps.h @@ -1,6 +1,6 @@ @@ -21306,7 +21158,7 @@ index 2c34e8a33a1c1..670c5fa2a952d 100644 +#endif /* ZSTD_DEPS_STDINT */ +#endif /* ZSTD_DEPS_NEED_STDINT */ diff --git a/lib/zstd/common/zstd_internal.h b/lib/zstd/common/zstd_internal.h -index 93305d9b41bba..7f023e4d47740 100644 +index 93305d9b41bb..7f023e4d4774 100644 --- a/lib/zstd/common/zstd_internal.h +++ b/lib/zstd/common/zstd_internal.h @@ -1,5 +1,6 @@ @@ -21492,7 +21344,7 @@ index 93305d9b41bba..7f023e4d47740 100644 /* ZSTD_invalidateRepCodes() : diff --git a/lib/zstd/compress/clevels.h b/lib/zstd/compress/clevels.h -index d9a76112ec3af..6ab8be6532efc 100644 +index d9a76112ec3a..6ab8be6532ef 100644 --- a/lib/zstd/compress/clevels.h +++ b/lib/zstd/compress/clevels.h @@ -1,5 +1,6 @@ @@ -21504,7 +21356,7 @@ index d9a76112ec3af..6ab8be6532efc 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/compress/fse_compress.c b/lib/zstd/compress/fse_compress.c -index ec5b1ca6d71af..e46ca6621b488 100644 +index ec5b1ca6d71a..e46ca6621b48 100644 --- a/lib/zstd/compress/fse_compress.c +++ b/lib/zstd/compress/fse_compress.c @@ -1,6 +1,7 @@ @@ -21623,7 +21475,7 @@ index ec5b1ca6d71af..e46ca6621b488 100644 - #endif /* FSE_COMMONDEFS_ONLY */ diff --git a/lib/zstd/compress/hist.c b/lib/zstd/compress/hist.c -index 3ddc6dfb68948..0b12587cc14b1 100644 +index 3ddc6dfb6894..0b12587cc14b 100644 --- a/lib/zstd/compress/hist.c +++ b/lib/zstd/compress/hist.c @@ -1,7 +1,8 @@ @@ -21637,7 +21489,7 @@ index 3ddc6dfb68948..0b12587cc14b1 100644 * You can contact the author at : * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/lib/zstd/compress/hist.h b/lib/zstd/compress/hist.h -index fc1830abc9c63..f7687b0fc20a0 100644 +index fc1830abc9c6..f7687b0fc20a 100644 --- a/lib/zstd/compress/hist.h +++ b/lib/zstd/compress/hist.h @@ -1,7 +1,8 @@ @@ -21651,7 +21503,7 @@ index fc1830abc9c63..f7687b0fc20a0 100644 * You can contact the author at : * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy diff --git a/lib/zstd/compress/huf_compress.c b/lib/zstd/compress/huf_compress.c -index 74ef0db476210..83241abafe35e 100644 +index 74ef0db47621..83241abafe35 100644 --- a/lib/zstd/compress/huf_compress.c +++ b/lib/zstd/compress/huf_compress.c @@ -1,6 +1,7 @@ @@ -22408,7 +22260,7 @@ index 74ef0db476210..83241abafe35e 100644 } - diff --git a/lib/zstd/compress/zstd_compress.c b/lib/zstd/compress/zstd_compress.c -index f620cafca633b..c1c316e9e289f 100644 +index f620cafca633..c1c316e9e289 100644 --- a/lib/zstd/compress/zstd_compress.c +++ b/lib/zstd/compress/zstd_compress.c @@ -1,5 +1,6 @@ @@ -25790,7 +25642,7 @@ index f620cafca633b..c1c316e9e289f 100644 + } +} diff --git a/lib/zstd/compress/zstd_compress_internal.h b/lib/zstd/compress/zstd_compress_internal.h -index 71697a11ae305..899f5e2de8e96 100644 +index 71697a11ae30..899f5e2de8e9 100644 --- a/lib/zstd/compress/zstd_compress_internal.h +++ b/lib/zstd/compress/zstd_compress_internal.h @@ -1,5 +1,6 @@ @@ -26344,7 +26196,7 @@ index 71697a11ae305..899f5e2de8e96 100644 + #endif /* ZSTD_COMPRESS_H */ diff --git a/lib/zstd/compress/zstd_compress_literals.c b/lib/zstd/compress/zstd_compress_literals.c -index 52b0a8059aba9..3e9ea46a670a6 100644 +index 52b0a8059aba..3e9ea46a670a 100644 --- a/lib/zstd/compress/zstd_compress_literals.c +++ b/lib/zstd/compress/zstd_compress_literals.c @@ -1,5 +1,6 @@ @@ -26586,7 +26438,7 @@ index 52b0a8059aba9..3e9ea46a670a6 100644 MEM_writeLE32(ostart, lhc); ostart[4] = (BYTE)(cLitSize >> 10); diff --git a/lib/zstd/compress/zstd_compress_literals.h b/lib/zstd/compress/zstd_compress_literals.h -index 9775fb97cb702..a2a85d6b69e53 100644 +index 9775fb97cb70..a2a85d6b69e5 100644 --- a/lib/zstd/compress/zstd_compress_literals.h +++ b/lib/zstd/compress/zstd_compress_literals.h @@ -1,5 +1,6 @@ @@ -26630,7 +26482,7 @@ index 9775fb97cb702..a2a85d6b69e53 100644 #endif /* ZSTD_COMPRESS_LITERALS_H */ diff --git a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c -index 21ddc1b37acf8..5c028c78d889b 100644 +index 21ddc1b37acf..5c028c78d889 100644 --- a/lib/zstd/compress/zstd_compress_sequences.c +++ b/lib/zstd/compress/zstd_compress_sequences.c @@ -1,5 +1,6 @@ @@ -26660,7 +26512,7 @@ index 21ddc1b37acf8..5c028c78d889b 100644 * If basic encoding isn't possible, always choose RLE. */ diff --git a/lib/zstd/compress/zstd_compress_sequences.h b/lib/zstd/compress/zstd_compress_sequences.h -index 7991364c2f71f..7fe6f4ff5cf25 100644 +index 7991364c2f71..7fe6f4ff5cf2 100644 --- a/lib/zstd/compress/zstd_compress_sequences.h +++ b/lib/zstd/compress/zstd_compress_sequences.h @@ -1,5 +1,6 @@ @@ -26672,7 +26524,7 @@ index 7991364c2f71f..7fe6f4ff5cf25 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/compress/zstd_compress_superblock.c b/lib/zstd/compress/zstd_compress_superblock.c -index 17d836cc84e8f..dbacbaf727338 100644 +index 17d836cc84e8..dbacbaf72733 100644 --- a/lib/zstd/compress/zstd_compress_superblock.c +++ b/lib/zstd/compress/zstd_compress_superblock.c @@ -1,5 +1,6 @@ @@ -26771,7 +26623,7 @@ index 17d836cc84e8f..dbacbaf727338 100644 ZSTD_memcpy(nextCBlock->rep, &rep, sizeof(rep)); } diff --git a/lib/zstd/compress/zstd_compress_superblock.h b/lib/zstd/compress/zstd_compress_superblock.h -index 224ece79546eb..826bbc9e029b1 100644 +index 224ece79546e..826bbc9e029b 100644 --- a/lib/zstd/compress/zstd_compress_superblock.h +++ b/lib/zstd/compress/zstd_compress_superblock.h @@ -1,5 +1,6 @@ @@ -26783,7 +26635,7 @@ index 224ece79546eb..826bbc9e029b1 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/compress/zstd_cwksp.h b/lib/zstd/compress/zstd_cwksp.h -index 349fc923c355a..65ea53b628447 100644 +index 349fc923c355..65ea53b62844 100644 --- a/lib/zstd/compress/zstd_cwksp.h +++ b/lib/zstd/compress/zstd_cwksp.h @@ -1,5 +1,6 @@ @@ -27088,7 +26940,7 @@ index 349fc923c355a..65ea53b628447 100644 diff --git a/lib/zstd/compress/zstd_double_fast.c b/lib/zstd/compress/zstd_double_fast.c -index 76933dea2624e..ab9440a996039 100644 +index 76933dea2624..ab9440a99603 100644 --- a/lib/zstd/compress/zstd_double_fast.c +++ b/lib/zstd/compress/zstd_double_fast.c @@ -1,5 +1,6 @@ @@ -27401,7 +27253,7 @@ index 76933dea2624e..ab9440a996039 100644 hashLong[ZSTD_hashPtr(ip, hBitsL, 8)] = current2; ip += repLength2; diff --git a/lib/zstd/compress/zstd_double_fast.h b/lib/zstd/compress/zstd_double_fast.h -index 6822bde65a1d8..0204f12e4cf70 100644 +index 6822bde65a1d..0204f12e4cf7 100644 --- a/lib/zstd/compress/zstd_double_fast.h +++ b/lib/zstd/compress/zstd_double_fast.h @@ -1,5 +1,6 @@ @@ -27423,7 +27275,7 @@ index 6822bde65a1d8..0204f12e4cf70 100644 ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); diff --git a/lib/zstd/compress/zstd_fast.c b/lib/zstd/compress/zstd_fast.c -index a752e6beab52e..3399b39c5dbc5 100644 +index a752e6beab52..3399b39c5dbc 100644 --- a/lib/zstd/compress/zstd_fast.c +++ b/lib/zstd/compress/zstd_fast.c @@ -1,5 +1,6 @@ @@ -28206,7 +28058,7 @@ index a752e6beab52e..3399b39c5dbc5 100644 { default: /* includes case 3 */ diff --git a/lib/zstd/compress/zstd_fast.h b/lib/zstd/compress/zstd_fast.h -index fddc2f532d21d..e64d9e1b2d393 100644 +index fddc2f532d21..e64d9e1b2d39 100644 --- a/lib/zstd/compress/zstd_fast.h +++ b/lib/zstd/compress/zstd_fast.h @@ -1,5 +1,6 @@ @@ -28228,7 +28080,7 @@ index fddc2f532d21d..e64d9e1b2d393 100644 ZSTD_matchState_t* ms, seqStore_t* seqStore, U32 rep[ZSTD_REP_NUM], void const* src, size_t srcSize); diff --git a/lib/zstd/compress/zstd_lazy.c b/lib/zstd/compress/zstd_lazy.c -index 0298a01a7504a..f6b4978ceba7f 100644 +index 0298a01a7504..f6b4978ceba7 100644 --- a/lib/zstd/compress/zstd_lazy.c +++ b/lib/zstd/compress/zstd_lazy.c @@ -1,5 +1,6 @@ @@ -29291,7 +29143,7 @@ index 0298a01a7504a..f6b4978ceba7f 100644 return ZSTD_compressBlock_lazy_extDict_generic(ms, seqStore, rep, src, srcSize, search_rowHash, 2); } diff --git a/lib/zstd/compress/zstd_lazy.h b/lib/zstd/compress/zstd_lazy.h -index e5bdf4df8dde0..9505bed93c031 100644 +index e5bdf4df8dde..9505bed93c03 100644 --- a/lib/zstd/compress/zstd_lazy.h +++ b/lib/zstd/compress/zstd_lazy.h @@ -1,5 +1,6 @@ @@ -29321,7 +29173,7 @@ index e5bdf4df8dde0..9505bed93c031 100644 #endif /* ZSTD_LAZY_H */ diff --git a/lib/zstd/compress/zstd_ldm.c b/lib/zstd/compress/zstd_ldm.c -index dd86fc83e7dde..b7da76b0db7c4 100644 +index dd86fc83e7dd..b7da76b0db7c 100644 --- a/lib/zstd/compress/zstd_ldm.c +++ b/lib/zstd/compress/zstd_ldm.c @@ -1,5 +1,6 @@ @@ -29365,7 +29217,7 @@ index dd86fc83e7dde..b7da76b0db7c4 100644 ip += sequence.matchLength; } diff --git a/lib/zstd/compress/zstd_ldm.h b/lib/zstd/compress/zstd_ldm.h -index fbc6a5e88fd7a..c540731abde72 100644 +index fbc6a5e88fd7..c540731abde7 100644 --- a/lib/zstd/compress/zstd_ldm.h +++ b/lib/zstd/compress/zstd_ldm.h @@ -1,5 +1,6 @@ @@ -29377,7 +29229,7 @@ index fbc6a5e88fd7a..c540731abde72 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/compress/zstd_ldm_geartab.h b/lib/zstd/compress/zstd_ldm_geartab.h -index 647f865be2903..cfccfc46f6f7b 100644 +index 647f865be290..cfccfc46f6f7 100644 --- a/lib/zstd/compress/zstd_ldm_geartab.h +++ b/lib/zstd/compress/zstd_ldm_geartab.h @@ -1,5 +1,6 @@ @@ -29389,7 +29241,7 @@ index 647f865be2903..cfccfc46f6f7b 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/compress/zstd_opt.c b/lib/zstd/compress/zstd_opt.c -index fd82acfda62f6..1e41cb04f4820 100644 +index fd82acfda62f..1e41cb04f482 100644 --- a/lib/zstd/compress/zstd_opt.c +++ b/lib/zstd/compress/zstd_opt.c @@ -1,5 +1,6 @@ @@ -29871,7 +29723,7 @@ index fd82acfda62f6..1e41cb04f4820 100644 ZSTD_initStats_ultra(ms, seqStore, rep, src, srcSize); } diff --git a/lib/zstd/compress/zstd_opt.h b/lib/zstd/compress/zstd_opt.h -index 22b862858ba7a..faa73ff4b03dc 100644 +index 22b862858ba7..faa73ff4b03d 100644 --- a/lib/zstd/compress/zstd_opt.h +++ b/lib/zstd/compress/zstd_opt.h @@ -1,5 +1,6 @@ @@ -29883,7 +29735,7 @@ index 22b862858ba7a..faa73ff4b03dc 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 60958afebc415..d172e35fbd9a6 100644 +index 60958afebc41..d172e35fbd9a 100644 --- a/lib/zstd/decompress/huf_decompress.c +++ b/lib/zstd/decompress/huf_decompress.c @@ -1,7 +1,8 @@ @@ -31074,7 +30926,7 @@ index 60958afebc415..d172e35fbd9a6 100644 } - diff --git a/lib/zstd/decompress/zstd_ddict.c b/lib/zstd/decompress/zstd_ddict.c -index dbbc7919de534..30ef65e1ab5ca 100644 +index dbbc7919de53..30ef65e1ab5c 100644 --- a/lib/zstd/decompress/zstd_ddict.c +++ b/lib/zstd/decompress/zstd_ddict.c @@ -1,5 +1,6 @@ @@ -31116,7 +30968,7 @@ index dbbc7919de534..30ef65e1ab5ca 100644 + return ddict->dictID; } diff --git a/lib/zstd/decompress/zstd_ddict.h b/lib/zstd/decompress/zstd_ddict.h -index 8c1a79d666f89..de459a0dacd19 100644 +index 8c1a79d666f8..de459a0dacd1 100644 --- a/lib/zstd/decompress/zstd_ddict.h +++ b/lib/zstd/decompress/zstd_ddict.h @@ -1,5 +1,6 @@ @@ -31128,7 +30980,7 @@ index 8c1a79d666f89..de459a0dacd19 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/decompress/zstd_decompress.c b/lib/zstd/decompress/zstd_decompress.c -index 6b3177c947114..03dbdf39109f9 100644 +index 6b3177c94711..03dbdf39109f 100644 --- a/lib/zstd/decompress/zstd_decompress.c +++ b/lib/zstd/decompress/zstd_decompress.c @@ -1,5 +1,6 @@ @@ -31685,7 +31537,7 @@ index 6b3177c947114..03dbdf39109f9 100644 + } } diff --git a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c -index c1913b8e7c897..9f5577e5bc19d 100644 +index c1913b8e7c89..9f5577e5bc19 100644 --- a/lib/zstd/decompress/zstd_decompress_block.c +++ b/lib/zstd/decompress/zstd_decompress_block.c @@ -1,5 +1,6 @@ @@ -32222,7 +32074,7 @@ index c1913b8e7c897..9f5577e5bc19d 100644 + return ZSTD_decompressBlock_deprecated(dctx, dst, dstCapacity, src, srcSize); +} diff --git a/lib/zstd/decompress/zstd_decompress_block.h b/lib/zstd/decompress/zstd_decompress_block.h -index 3d2d57a5d25a7..5888e6cc788b5 100644 +index 3d2d57a5d25a..5888e6cc788b 100644 --- a/lib/zstd/decompress/zstd_decompress_block.h +++ b/lib/zstd/decompress/zstd_decompress_block.h @@ -1,5 +1,6 @@ @@ -32245,7 +32097,7 @@ index 3d2d57a5d25a7..5888e6cc788b5 100644 #endif /* ZSTD_DEC_BLOCK_H */ diff --git a/lib/zstd/decompress/zstd_decompress_internal.h b/lib/zstd/decompress/zstd_decompress_internal.h -index 98102edb6a832..32f79fb2873df 100644 +index 98102edb6a83..32f79fb2873d 100644 --- a/lib/zstd/decompress/zstd_decompress_internal.h +++ b/lib/zstd/decompress/zstd_decompress_internal.h @@ -1,5 +1,6 @@ @@ -32280,7 +32132,7 @@ index 98102edb6a832..32f79fb2873df 100644 /* streaming */ ZSTD_dStreamStage streamStage; diff --git a/lib/zstd/decompress_sources.h b/lib/zstd/decompress_sources.h -index a06ca187aab5f..8a47eb2a45145 100644 +index a06ca187aab5..8a47eb2a4514 100644 --- a/lib/zstd/decompress_sources.h +++ b/lib/zstd/decompress_sources.h @@ -1,6 +1,6 @@ @@ -32292,7 +32144,7 @@ index a06ca187aab5f..8a47eb2a45145 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/zstd_common_module.c b/lib/zstd/zstd_common_module.c -index 22686e367e6f0..466828e357525 100644 +index 22686e367e6f..466828e35752 100644 --- a/lib/zstd/zstd_common_module.c +++ b/lib/zstd/zstd_common_module.c @@ -1,6 +1,6 @@ @@ -32314,7 +32166,7 @@ index 22686e367e6f0..466828e357525 100644 MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("Zstd Common"); diff --git a/lib/zstd/zstd_compress_module.c b/lib/zstd/zstd_compress_module.c -index 04e1b5c01d9b6..8ecf43226af2f 100644 +index 04e1b5c01d9b..8ecf43226af2 100644 --- a/lib/zstd/zstd_compress_module.c +++ b/lib/zstd/zstd_compress_module.c @@ -1,6 +1,6 @@ @@ -32326,7 +32178,7 @@ index 04e1b5c01d9b6..8ecf43226af2f 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 f4ed952ed4852..eb1c49e69722f 100644 +index f4ed952ed485..eb1c49e69722 100644 --- a/lib/zstd/zstd_decompress_module.c +++ b/lib/zstd/zstd_decompress_module.c @@ -1,6 +1,6 @@ @@ -32339,3 +32191,5 @@ index f4ed952ed4852..eb1c49e69722f 100644 * This source code is licensed under both the BSD-style license (found in the -- 2.42.0 + + diff --git a/patches/0002-eevdf.patch b/patches/cachyos/0002-eevdf.patch similarity index 100% rename from patches/0002-eevdf.patch rename to patches/cachyos/0002-eevdf.patch diff --git a/patches/0002-eevdfbore.patch b/patches/cachyos/0002-eevdfbore.patch similarity index 100% rename from patches/0002-eevdfbore.patch rename to patches/cachyos/0002-eevdfbore.patch diff --git a/patches/0004-Allow-to-set-custom-USB-pollrate-for-specific-device.patch b/patches/nobara/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch similarity index 99% rename from patches/0004-Allow-to-set-custom-USB-pollrate-for-specific-device.patch rename to patches/nobara/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch index 64cfa5c..ecd0304 100644 --- a/patches/0004-Allow-to-set-custom-USB-pollrate-for-specific-device.patch +++ b/patches/nobara/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch @@ -18,7 +18,7 @@ index dbd26fde4..c9b8b80af 100644 @@ -6552,6 +6552,14 @@ delay after resetting its port); Example: quirks=0781:5580:bk,0a5c:5834:gij - + + usbcore.interrupt_interval_override= + [USB] A list of USB devices for which a different polling + interval than the default shall be used on all interrupt-type @@ -29,15 +29,15 @@ index dbd26fde4..c9b8b80af 100644 + usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. - + diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 48bc8a481..84bd550ad 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -19,6 +19,149 @@ #define USB_MAXCONFIG 8 /* Arbitrary limit */ - - + + +/* A struct associated with the interrupt_interval_override module parameter, representing + an user's choice to force a specific interrupt interval upon all interrupt endpoints of + a certain device. */ @@ -191,12 +191,12 @@ index 48bc8a481..84bd550ad 100644 - unsigned int maxp; + unsigned int maxp, ival; const unsigned short *maxpacket_maxes; - + d = (struct usb_endpoint_descriptor *) buffer; @@ -386,6 +529,23 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, endpoint->desc.bInterval = n; } - + + /* Override the interrupt polling interval if a module parameter tells us to do so. */ + if (usb_endpoint_xfer_int(d)) { + ival = usb_check_interrupt_interval_override(udev); @@ -235,7 +235,7 @@ index 11b15d7b3..ec52c6322 100644 +++ b/drivers/usb/core/usb.c @@ -1066,6 +1066,7 @@ static void __exit usb_exit(void) return; - + usb_release_quirk_list(); + usb_release_interrupt_interval_override_list(); usb_deregister_device_driver(&usb_generic_driver); @@ -253,5 +253,6 @@ index 82538daac..b6faa897c 100644 extern bool usb_endpoint_is_ignored(struct usb_device *udev, struct usb_host_interface *intf, struct usb_endpoint_descriptor *epd); --- +-- 2.39.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 new file mode 100644 index 0000000..e68df11 --- /dev/null +++ b/patches/nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch @@ -0,0 +1,34 @@ +From 4b4ce124699c160925e5fdeb147a78f79d38351f Mon Sep 17 00:00:00 2001 +From: Simon May +Date: Sun, 19 Sep 2021 23:45:59 +0200 +Subject: [PATCH] Revert "PCI: Add a REBAR size quirk for Sapphire RX 5600 XT + Pulse" + +This reverts commit 907830b0fc9e374d00f3c83de5e426157b482c01. +--- + drivers/pci/pci.c | 9 +-------- + 1 file changed, 1 insertion(+), 8 deletions(-) + +diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c +index 3f353572588d..1c8cc4b98f95 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) + return 0; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); +- cap &= PCI_REBAR_CAP_SIZES; +- +- /* 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; +- +- return cap >> 4; ++ 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 new file mode 100644 index 0000000..b44c15f --- /dev/null +++ b/patches/nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch @@ -0,0 +1,46 @@ +From 7d86ca8db51f6b75b5c1470d6294c6f24221f560 Mon Sep 17 00:00:00 2001 +From: GloriousEggroll +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. +--- + drivers/nvme/host/pci.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c +index 3f0c9ee09a12..bc11bfe6f87a 100644 +--- a/drivers/nvme/host/pci.c ++++ b/drivers/nvme/host/pci.c +@@ -5,6 +5,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -2537,6 +2538,7 @@ static int nvme_pci_enable(struct nvme_dev *dev) + + nvme_map_cmb(dev); + ++ pci_enable_pcie_error_reporting(pdev); + pci_save_state(pdev); + + result = nvme_pci_configure_admin_queue(dev); +@@ -2601,8 +2603,10 @@ static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown) + nvme_suspend_io_queues(dev); + nvme_suspend_queue(dev, 0); + pci_free_irq_vectors(pdev); +- if (pci_is_enabled(pdev)) ++ if (pci_is_enabled(pdev)) { ++ pci_disable_pcie_error_reporting(pdev); + pci_disable_device(pdev); ++ } + nvme_reap_pending_cqes(dev); + + nvme_cancel_tagset(&dev->ctrl); +-- +2.41.0 + diff --git a/patches/nobara/0001-acpi-proc-idle-skip-dummy-wait.patch b/patches/nobara/0001-acpi-proc-idle-skip-dummy-wait.patch new file mode 100644 index 0000000..eb3cf54 --- /dev/null +++ b/patches/nobara/0001-acpi-proc-idle-skip-dummy-wait.patch @@ -0,0 +1,125 @@ +Processors based on the Zen microarchitecture support IOPORT based deeper +C-states. The idle driver reads the acpi_gbl_FADT.xpm_timer_block.address +in the IOPORT based C-state exit path which is claimed to be a +"Dummy wait op" and has been around since ACPI introduction to Linux +dating back to Andy Grover's Mar 14, 2002 posting [1]. +The comment above the dummy operation was elaborated by Andreas Mohr back +in 2006 in commit b488f02156d3d ("ACPI: restore comment justifying 'extra' +P_LVLx access") [2] where the commit log claims: +"this dummy read was about: STPCLK# doesn't get asserted in time on +(some) chipsets, which is why we need to have a dummy I/O read to delay +further instruction processing until the CPU is fully stopped." + +However, sampling certain workloads with IBS on AMD Zen3 system shows +that a significant amount of time is spent in the dummy op, which +incorrectly gets accounted as C-State residency. A large C-State +residency value can prime the cpuidle governor to recommend a deeper +C-State during the subsequent idle instances, starting a vicious cycle, +leading to performance degradation on workloads that rapidly switch +between busy and idle phases. + +One such workload is tbench where a massive performance degradation can +be observed during certain runs. Following are some statistics gathered +by running tbench with 128 clients, on a dual socket (2 x 64C/128T) Zen3 +system with the baseline kernel, baseline kernel keeping C2 disabled, +and baseline kernel with this patch applied keeping C2 enabled: + +baseline kernel was tip:sched/core at +commit f3dd3f674555 ("sched: Remove the limitation of WF_ON_CPU on +wakelist if wakee cpu is idle") + +Kernel : baseline baseline + C2 disabled baseline + patch + +Min (MB/s) : 2215.06 33072.10 (+1393.05%) 33016.10 (+1390.52%) +Max (MB/s) : 32938.80 34399.10 34774.50 +Median (MB/s) : 32191.80 33476.60 33805.70 +AMean (MB/s) : 22448.55 33649.27 (+49.89%) 33865.43 (+50.85%) +AMean Stddev : 17526.70 680.14 880.72 +AMean CoefVar : 78.07% 2.02% 2.60% + +The data shows there are edge cases that can cause massive regressions +in case of tbench. Profiling the bad runs with IBS shows a significant +amount of time being spent in acpi_idle_do_entry method: + +Overhead Command Shared Object Symbol + 74.76% swapper [kernel.kallsyms] [k] acpi_idle_do_entry + 0.71% tbench [kernel.kallsyms] [k] update_sd_lb_stats.constprop.0 + 0.69% tbench_srv [kernel.kallsyms] [k] update_sd_lb_stats.constprop.0 + 0.49% swapper [kernel.kallsyms] [k] psi_group_change + ... + +Annotation of acpi_idle_do_entry method reveals almost all the time in +acpi_idle_do_entry is spent on the port I/O in wait_for_freeze(): + + 0.14 │ in (%dx),%al # <------ First "in" corresponding to inb(cx->address) + 0.51 │ mov 0x144d64d(%rip),%rax + 0.00 │ test $0x80000000,%eax + │ ↓ jne 62 # <------ Skip if running in guest + 0.00 │ mov 0x19800c3(%rip),%rdx + 99.33 │ in (%dx),%eax # <------ Second "in" corresponding to inl(acpi_gbl_FADT.xpm_timer_block.address) + 0.00 │62: mov -0x8(%rbp),%r12 + 0.00 │ leave + 0.00 │ ← ret + +This overhead is reflected in the C2 residency on the test system where +C2 is an IOPORT based C-State. The total C-state residency reported by +"cpupower idle-info" on CPU0 for good and bad case over the 80s tbench +run is as follows (all numbers are in microseconds): + + Good Run Bad Run + (Baseline) + +POLL: 43338 6231 (-85.62%) +C1 (MWAIT Based): 23576156 363861 (-98.45%) +C2 (IOPORT Based): 10781218 77027280 (+614.45%) + +The larger residency value in bad case leads to the system recommending +C2 state again for subsequent idle instances. The pattern lasts till the +end of the tbench run. Following is the breakdown of "entry_method" +passed to acpi_idle_do_entry during good run and bad run: + + Good Run Bad Run + (Baseline) + +Number of times acpi_idle_do_entry was called: 6149573 6149050 (-0.01%) + |-> Number of times entry_method was "ACPI_CSTATE_FFH": 6141494 88144 (-98.56%) + |-> Number of times entry_method was "ACPI_CSTATE_HALT": 0 0 (+0.00%) + |-> Number of times entry_method was "ACPI_CSTATE_SYSTEMIO": 8079 6060906 (+74920.49%) + +For processors based on the Zen microarchitecture, this dummy wait op is +unnecessary and can be skipped when choosing IOPORT based C-States to +avoid polluting the C-state residency information. + +Link: https://git.kernel.org/pub/scm/linux/kernel/git/mpe/linux-fullhistory.git/commit/?id=972c16130d9dc182cedcdd408408d9eacc7d6a2d [1] +Link: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b488f02156d3deb08f5ad7816d565c370a8cc6f1 [2] + +Suggested-by: Calvin Ong +Cc: stable@vger.kernel.org +Cc: regressions@lists.linux.dev +Signed-off-by: K Prateek Nayak +--- + drivers/acpi/processor_idle.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c +index 16a1663d02d4..18850aa2b79b 100644 +--- a/drivers/acpi/processor_idle.c ++++ b/drivers/acpi/processor_idle.c +@@ -529,9 +529,11 @@ static __cpuidle void io_idle(unsigned long addr) + inb(addr); + + #ifdef CONFIG_X86 +- /* No delay is needed if we are in guest */ +- if (boot_cpu_has(X86_FEATURE_HYPERVISOR)) +- return; ++ /* ++ * No delay is needed if we are in guest or on a processor ++ * based on the Zen microarchitecture. ++ */ ++ if (boot_cpu_has(X86_FEATURE_HYPERVISOR) || boot_cpu_has(X86_FEATURE_ZEN)) + /* + * Modern (>=Nehalem) Intel systems use ACPI via intel_idle, + * not this code. Assume that any Intel systems using this + +-- +2.25.1 diff --git a/patches/nobara/0001-drm-i915-quirks-disable-async-flipping-on-specific-d.patch b/patches/nobara/0001-drm-i915-quirks-disable-async-flipping-on-specific-d.patch new file mode 100644 index 0000000..757f777 --- /dev/null +++ b/patches/nobara/0001-drm-i915-quirks-disable-async-flipping-on-specific-d.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jan200101 +Date: Mon, 14 Nov 2022 20:13:53 +0100 +Subject: [PATCH] drm/i915/quirks: disable async flipping on specific devices + +Signed-off-by: Jan200101 +--- + drivers/gpu/drm/i915/display/intel_quirks.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +index a280448df771..1596114dd9ae 100644 +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -14,6 +14,12 @@ static void intel_set_quirk(struct drm_i915_private *i915, enum intel_quirk_id q + i915->display.quirks.mask |= BIT(quirk); + } + ++static void quirk_async_page_flips_force_disable(struct drm_i915_private *i915) ++{ ++ i915->drm.mode_config.async_page_flip = false; ++ drm_info(&i915->drm, "applying async flip disable quirk\n"); ++} ++ + /* + * Some machines (Lenovo U160) do not work with SSC on LVDS for some reason + */ +@@ -136,6 +142,20 @@ static const struct intel_dmi_quirk intel_dmi_quirks[] = { + }, + .hook = quirk_no_pps_backlight_power_hook, + }, ++ { ++ .dmi_id_list = &(const struct dmi_system_id[]) { ++ { ++ .callback = NULL, ++ .ident = "ASUS TUF DASH F15", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "ASUS TUF Dash F15 FX516PC_FX516PC"), ++ }, ++ }, ++ { } ++ }, ++ .hook = quirk_async_page_flips_force_disable, ++ }, + }; + + static struct intel_quirk intel_quirks[] = { diff --git a/patches/nobara/0002-drm-i915-add-kernel-parameter-to-disable-async-page-.patch b/patches/nobara/0002-drm-i915-add-kernel-parameter-to-disable-async-page-.patch new file mode 100644 index 0000000..24f6807 --- /dev/null +++ b/patches/nobara/0002-drm-i915-add-kernel-parameter-to-disable-async-page-.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jan200101 +Date: Wed, 8 Mar 2023 20:51:16 +0100 +Subject: [PATCH] drm/i915: add kernel parameter to disable async page flipping + +Signed-off-by: Jan200101 +--- + drivers/gpu/drm/i915/display/intel_display_driver.c | 2 +- + drivers/gpu/drm/i915/i915_params.c | 4 ++++ + drivers/gpu/drm/i915/i915_params.h | 3 ++- + 3 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/drm/i915/i915_params.c b/drivers/gpu/drm/i915/i915_params.c +index ade744cccfea..119be26b5641 100644 +--- a/drivers/gpu/drm/i915/i915_params.c ++++ b/drivers/gpu/drm/i915/i915_params.c +@@ -222,6 +222,10 @@ i915_param_named_unsafe(lmem_size, uint, 0400, + i915_param_named_unsafe(lmem_bar_size, uint, 0400, + "Set the lmem bar size(in MiB)."); + ++i915_param_named_unsafe(disable_async_page_flip, bool, 0400, ++ "Disable async page flipping" ++ "(0=disabled [default], 1=enabled)"); ++ + static void _param_print_bool(struct drm_printer *p, const char *name, + bool val) + { +diff --git a/drivers/gpu/drm/i915/i915_params.h b/drivers/gpu/drm/i915/i915_params.h +index 3f51f90145b6..37f25ec1b874 100644 +--- a/drivers/gpu/drm/i915/i915_params.h ++++ b/drivers/gpu/drm/i915/i915_params.h +@@ -85,7 +85,8 @@ struct drm_printer; + param(bool, verbose_state_checks, true, 0) \ + param(bool, nuclear_pageflip, false, 0400) \ + param(bool, enable_dp_mst, true, 0600) \ +- param(bool, enable_gvt, false, IS_ENABLED(CONFIG_DRM_I915_GVT) ? 0400 : 0) ++ param(bool, enable_gvt, false, IS_ENABLED(CONFIG_DRM_I915_GVT) ? 0400 : 0) \ ++ param(bool, disable_async_page_flip, false, 0400) + + #define MEMBER(T, member, ...) T member; + struct i915_params { +diff --git a/drivers/gpu/drm/i915/display/intel_display_driver.c b/drivers/gpu/drm/i915/display/intel_display_driver.c +index b909814ae..918b8b589 100644 +--- a/drivers/gpu/drm/i915/display/intel_display_driver.c ++++ b/drivers/gpu/drm/i915/display/intel_display_driver.c +@@ -121,7 +121,7 @@ static void intel_mode_config_init(struct drm_i915_private *i915) + mode_config->funcs = &intel_mode_funcs; + mode_config->helper_private = &intel_mode_config_funcs; + +- mode_config->async_page_flip = HAS_ASYNC_FLIPS(i915); ++ mode_config->async_page_flip = HAS_ASYNC_FLIPS(i915) && !i915->params.disable_async_page_flip; + + /* + * Maximum framebuffer dimensions, chosen to match 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/0005-amdgpu-si-cik-default.patch b/patches/nobara/amdgpu-si-cik-default.patch similarity index 99% rename from patches/0005-amdgpu-si-cik-default.patch rename to patches/nobara/amdgpu-si-cik-default.patch index db30938..5479769 100644 --- a/patches/0005-amdgpu-si-cik-default.patch +++ b/patches/nobara/amdgpu-si-cik-default.patch @@ -16,7 +16,7 @@ index 0ead08ba58c2..95a59d203922 100644 @@ -575,15 +575,10 @@ module_param_named(timeout_period, amdgpu_watchdog_timer.period, uint, 0644); */ #ifdef CONFIG_DRM_AMDGPU_SI - + -#if defined(CONFIG_DRM_RADEON) || defined(CONFIG_DRM_RADEON_MODULE) -int amdgpu_si_support = 0; -MODULE_PARM_DESC(si_support, "SI support (1 = enabled, 0 = disabled (default))"); @@ -28,12 +28,12 @@ index 0ead08ba58c2..95a59d203922 100644 module_param_named(si_support, amdgpu_si_support, int, 0444); + #endif - + /** @@ -594,15 +589,10 @@ module_param_named(si_support, amdgpu_si_support, int, 0444); */ #ifdef CONFIG_DRM_AMDGPU_CIK - + -#if defined(CONFIG_DRM_RADEON) || defined(CONFIG_DRM_RADEON_MODULE) -int amdgpu_cik_support = 0; -MODULE_PARM_DESC(cik_support, "CIK support (1 = enabled, 0 = disabled (default))"); @@ -45,7 +45,7 @@ index 0ead08ba58c2..95a59d203922 100644 module_param_named(cik_support, amdgpu_cik_support, int, 0444); + #endif - + /** diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c index 956c72b5aa33..5102711ece53 100644 @@ -54,7 +54,7 @@ index 956c72b5aa33..5102711ece53 100644 @@ -272,12 +272,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))"); @@ -63,7 +63,7 @@ index 956c72b5aa33..5102711ece53 100644 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))"); @@ -72,7 +72,8 @@ index 956c72b5aa33..5102711ece53 100644 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[] = { --- +-- 2.35.1 + diff --git a/patches/nobara/asus-linux.patch b/patches/nobara/asus-linux.patch new file mode 100644 index 0000000..491e5be --- /dev/null +++ b/patches/nobara/asus-linux.patch @@ -0,0 +1,2177 @@ +From 5a57dbe832b2dc8cc79516977f4fbbfed64c4743 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Sun, 4 Jun 2023 18:48:11 +1200 +Subject: [PATCH 01/13] platform/x86: asus-wmi: add support for showing charger + mode + +Expose a WMI method in sysfs platform for showing which connected +charger the laptop is currently using. + +Signed-off-by: Luke D. Jones +--- + .../ABI/testing/sysfs-platform-asus-wmi | 10 +++++++++ + drivers/platform/x86/asus-wmi.c | 21 +++++++++++++++++++ + include/linux/platform_data/x86/asus-wmi.h | 3 +++ + 3 files changed, 34 insertions(+) + +diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi +index a77a004a1baa..eb29e3023c7b 100644 +--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi ++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi +@@ -98,3 +98,13 @@ Description: + Enable an LCD response-time boost to reduce or remove ghosting: + * 0 - Disable, + * 1 - Enable ++ ++What: /sys/devices/platform//charge_mode ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Get the current charging mode being used: ++ * 1 - Barrel connected charger, ++ * 2 - USB-C charging ++ * 3 - Both connected, barrel used for charging +\ No newline at end of file +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 8bef66a2f0ce..cf82ae6323f8 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -237,6 +237,7 @@ struct asus_wmi { + u8 fan_boost_mode_mask; + u8 fan_boost_mode; + ++ bool charge_mode_available; + bool egpu_enable_available; + bool dgpu_disable_available; + bool gpu_mux_mode_available; +@@ -586,6 +587,22 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus) + asus_wmi_tablet_sw_report(asus, result); + } + ++/* Charging mode, 1=Barrel, 2=USB ******************************************/ ++static ssize_t charge_mode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int result, value; ++ ++ result = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CHARGE_MODE, &value); ++ if (result < 0) ++ return result; ++ ++ return sysfs_emit(buf, "%d\n", value & 0xff); ++} ++ ++static DEVICE_ATTR_RO(charge_mode); ++ + /* dGPU ********************************************************************/ + static ssize_t dgpu_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -3472,6 +3489,7 @@ static struct attribute *platform_attributes[] = { + &dev_attr_camera.attr, + &dev_attr_cardr.attr, + &dev_attr_touchpad.attr, ++ &dev_attr_charge_mode.attr, + &dev_attr_egpu_enable.attr, + &dev_attr_dgpu_disable.attr, + &dev_attr_gpu_mux_mode.attr, +@@ -3501,6 +3519,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, + devid = ASUS_WMI_DEVID_LID_RESUME; + else if (attr == &dev_attr_als_enable.attr) + devid = ASUS_WMI_DEVID_ALS_ENABLE; ++ else if (attr == &dev_attr_charge_mode.attr) ++ ok = asus->charge_mode_available; + else if (attr == &dev_attr_egpu_enable.attr) + ok = asus->egpu_enable_available; + else if (attr == &dev_attr_dgpu_disable.attr) +@@ -3767,6 +3787,7 @@ static int asus_wmi_add(struct platform_device *pdev) + if (err) + goto fail_platform; + ++ asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE); + asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); + asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); + asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX); +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index 28234dc9fa6a..f90cafe26af1 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -95,6 +95,9 @@ + /* Keyboard dock */ + #define ASUS_WMI_DEVID_KBD_DOCK 0x00120063 + ++/* Charging mode - 1=Barrel, 2=USB */ ++#define ASUS_WMI_DEVID_CHARGE_MODE 0x0012006C ++ + /* dgpu on/off */ + #define ASUS_WMI_DEVID_EGPU 0x00090019 + +-- +2.41.0 + +From 6c0e89067d0608fedd3b75844bdea5566a0c249f Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Sun, 4 Jun 2023 19:07:31 +1200 +Subject: [PATCH 02/13] platform/x86: asus-wmi: add support for showing middle + fan RPM + +Some newer ASUS ROG laptops now have a middle/center fan in addition +to the CPU and GPU fans. This new fan typically blows across the +heatpipes and VRMs betweent eh CPU and GPU. + +This commit exposes that fan to PWM control plus showing RPM. + +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 91 ++++++++++++++++++++++ + include/linux/platform_data/x86/asus-wmi.h | 1 + + 2 files changed, 92 insertions(+) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index cf82ae6323f8..069251d8040f 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -72,6 +72,7 @@ module_param(fnlock_default, bool, 0444); + + #define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0) + ++#define ASUS_MID_FAN_DESC "mid_fan" + #define ASUS_GPU_FAN_DESC "gpu_fan" + #define ASUS_FAN_DESC "cpu_fan" + #define ASUS_FAN_MFUN 0x13 +@@ -229,8 +230,10 @@ struct asus_wmi { + + enum fan_type fan_type; + enum fan_type gpu_fan_type; ++ enum fan_type mid_fan_type; + int fan_pwm_mode; + int gpu_fan_pwm_mode; ++ int mid_fan_pwm_mode; + int agfn_pwm; + + bool fan_boost_mode_available; +@@ -2139,6 +2142,31 @@ static ssize_t fan2_label_show(struct device *dev, + return sysfs_emit(buf, "%s\n", ASUS_GPU_FAN_DESC); + } + ++/* Middle/Center fan on modern ROG laptops */ ++static ssize_t fan3_input_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int value; ++ int ret; ++ ++ ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_MID_FAN_CTRL, &value); ++ if (ret < 0) ++ return ret; ++ ++ value &= 0xffff; ++ ++ return sysfs_emit(buf, "%d\n", value * 100); ++} ++ ++static ssize_t fan3_label_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "%s\n", ASUS_MID_FAN_DESC); ++} ++ + static ssize_t pwm2_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +@@ -2185,6 +2213,52 @@ static ssize_t pwm2_enable_store(struct device *dev, + return count; + } + ++static ssize_t pwm3_enable_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ return sysfs_emit(buf, "%d\n", asus->mid_fan_pwm_mode); ++} ++ ++static ssize_t pwm3_enable_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int state; ++ int value; ++ int ret; ++ u32 retval; ++ ++ ret = kstrtouint(buf, 10, &state); ++ if (ret) ++ return ret; ++ ++ switch (state) { /* standard documented hwmon values */ ++ case ASUS_FAN_CTRL_FULLSPEED: ++ value = 1; ++ break; ++ case ASUS_FAN_CTRL_AUTO: ++ value = 0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_MID_FAN_CTRL, ++ value, &retval); ++ if (ret) ++ return ret; ++ ++ if (retval != 1) ++ return -EIO; ++ ++ asus->mid_fan_pwm_mode = state; ++ return count; ++} ++ + /* Fan1 */ + static DEVICE_ATTR_RW(pwm1); + static DEVICE_ATTR_RW(pwm1_enable); +@@ -2194,6 +2268,10 @@ static DEVICE_ATTR_RO(fan1_label); + static DEVICE_ATTR_RW(pwm2_enable); + static DEVICE_ATTR_RO(fan2_input); + static DEVICE_ATTR_RO(fan2_label); ++/* Fan3 - Middle/center fan */ ++static DEVICE_ATTR_RW(pwm3_enable); ++static DEVICE_ATTR_RO(fan3_input); ++static DEVICE_ATTR_RO(fan3_label); + + /* Temperature */ + static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL); +@@ -2202,10 +2280,13 @@ static struct attribute *hwmon_attributes[] = { + &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm2_enable.attr, ++ &dev_attr_pwm3_enable.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_label.attr, + &dev_attr_fan2_input.attr, + &dev_attr_fan2_label.attr, ++ &dev_attr_fan3_input.attr, ++ &dev_attr_fan3_label.attr, + + &dev_attr_temp1_input.attr, + NULL +@@ -2231,6 +2312,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, + || attr == &dev_attr_pwm2_enable.attr) { + if (asus->gpu_fan_type == FAN_TYPE_NONE) + return 0; ++ } else if (attr == &dev_attr_fan3_input.attr ++ || attr == &dev_attr_fan3_label.attr ++ || attr == &dev_attr_pwm3_enable.attr) { ++ if (asus->mid_fan_type == FAN_TYPE_NONE) ++ return 0; + } else if (attr == &dev_attr_temp1_input.attr) { + int err = asus_wmi_get_devstate(asus, + ASUS_WMI_DEVID_THERMAL_CTRL, +@@ -2274,6 +2360,7 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus) + static int asus_wmi_fan_init(struct asus_wmi *asus) + { + asus->gpu_fan_type = FAN_TYPE_NONE; ++ asus->mid_fan_type = FAN_TYPE_NONE; + asus->fan_type = FAN_TYPE_NONE; + asus->agfn_pwm = -1; + +@@ -2288,6 +2375,10 @@ static int asus_wmi_fan_init(struct asus_wmi *asus) + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_FAN_CTRL)) + asus->gpu_fan_type = FAN_TYPE_SPEC83; + ++ /* Some models also have a center/middle fan */ ++ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MID_FAN_CTRL)) ++ asus->mid_fan_type = FAN_TYPE_SPEC83; ++ + if (asus->fan_type == FAN_TYPE_NONE) + return -ENODEV; + +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index f90cafe26af1..2c03bda7703f 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -80,6 +80,7 @@ + #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */ + #define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013 + #define ASUS_WMI_DEVID_GPU_FAN_CTRL 0x00110014 ++#define ASUS_WMI_DEVID_MID_FAN_CTRL 0x00110031 + #define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024 + #define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025 + +-- +2.41.0 + +From 60f66172c03e8cf8417818173c253824527a6d69 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Sun, 4 Jun 2023 19:37:34 +1200 +Subject: [PATCH 03/13] platform/x86: asus-wmi: support middle fan custom + curves + +Adds support for fan curves defined for the middle fan which +is available on some ASUS ROG laptops. + +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 77 +++++++++++++++++++++- + include/linux/platform_data/x86/asus-wmi.h | 1 + + 2 files changed, 76 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 069251d8040f..89867b18e8f7 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -113,6 +113,7 @@ module_param(fnlock_default, bool, 0444); + #define FAN_CURVE_BUF_LEN 32 + #define FAN_CURVE_DEV_CPU 0x00 + #define FAN_CURVE_DEV_GPU 0x01 ++#define FAN_CURVE_DEV_MID 0x02 + /* Mask to determine if setting temperature or percentage */ + #define FAN_CURVE_PWM_MASK 0x04 + +@@ -253,7 +254,8 @@ struct asus_wmi { + + bool cpu_fan_curve_available; + bool gpu_fan_curve_available; +- struct fan_curve_data custom_fan_curves[2]; ++ bool mid_fan_curve_available; ++ struct fan_curve_data custom_fan_curves[3]; + + struct platform_profile_handler platform_profile_handler; + bool platform_profile_support; +@@ -2090,6 +2092,8 @@ static ssize_t pwm1_enable_store(struct device *dev, + asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false; + if (asus->gpu_fan_curve_available) + asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false; ++ if (asus->mid_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false; + + return count; + } +@@ -2541,6 +2545,9 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev) + if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE) + fan_idx = FAN_CURVE_DEV_GPU; + ++ if (fan_dev == ASUS_WMI_DEVID_MID_FAN_CURVE) ++ fan_idx = FAN_CURVE_DEV_MID; ++ + curves = &asus->custom_fan_curves[fan_idx]; + err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf, + FAN_CURVE_BUF_LEN); +@@ -2829,6 +2836,42 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_pwm, fan_curve, + static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve, + FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7); + ++/* MID */ ++static SENSOR_DEVICE_ATTR_RW(pwm3_enable, fan_curve_enable, FAN_CURVE_DEV_GPU); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 7); ++ ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7); ++ + static struct attribute *asus_fan_curve_attr[] = { + /* CPU */ + &sensor_dev_attr_pwm1_enable.dev_attr.attr, +@@ -2866,6 +2909,24 @@ static struct attribute *asus_fan_curve_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, ++ /* MID */ ++ &sensor_dev_attr_pwm3_enable.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_point1_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point5_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point6_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point7_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point8_pwm.dev_attr.attr, + NULL + }; + +@@ -2885,6 +2946,9 @@ static umode_t asus_fan_curve_is_visible(struct kobject *kobj, + if (asus->gpu_fan_curve_available && attr->name[3] == '2') + return 0644; + ++ if (asus->mid_fan_curve_available && attr->name[3] == '3') ++ return 0644; ++ + return 0; + } + +@@ -2914,7 +2978,14 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) + if (err) + return err; + +- if (!asus->cpu_fan_curve_available && !asus->gpu_fan_curve_available) ++ err = fan_curve_check_present(asus, &asus->mid_fan_curve_available, ++ ASUS_WMI_DEVID_MID_FAN_CURVE); ++ if (err) ++ return err; ++ ++ if (!asus->cpu_fan_curve_available ++ && !asus->gpu_fan_curve_available ++ && !asus->mid_fan_curve_available) + return 0; + + hwmon = devm_hwmon_device_register_with_groups( +@@ -2983,6 +3054,8 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus) + asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false; + if (asus->gpu_fan_curve_available) + asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false; ++ if (asus->mid_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false; + + return 0; + } +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index 2c03bda7703f..329efc086993 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -83,6 +83,7 @@ + #define ASUS_WMI_DEVID_MID_FAN_CTRL 0x00110031 + #define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024 + #define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025 ++#define ASUS_WMI_DEVID_MID_FAN_CURVE 0x00110032 + + /* Power */ + #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 +-- +2.41.0 + +From 0b90e1673515c0cf89f43c9a7f5cd06db9c7b3f2 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Sun, 4 Jun 2023 20:01:57 +1200 +Subject: [PATCH 04/13] platform/x86: asus-wmi: add WMI method to show if egpu + connected + +Exposes the WMI method which tells if the eGPU is properly connected +on the devices that support it. + +Signed-off-by: Luke D. Jones +--- + .../ABI/testing/sysfs-platform-asus-wmi | 11 +++++++++- + drivers/platform/x86/asus-wmi.c | 21 +++++++++++++++++++ + include/linux/platform_data/x86/asus-wmi.h | 4 +++- + 3 files changed, 34 insertions(+), 2 deletions(-) + +diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi +index eb29e3023c7b..878daf7c2036 100644 +--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi ++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi +@@ -107,4 +107,13 @@ Description: + Get the current charging mode being used: + * 1 - Barrel connected charger, + * 2 - USB-C charging +- * 3 - Both connected, barrel used for charging +\ No newline at end of file ++ * 3 - Both connected, barrel used for charging ++ ++What: /sys/devices/platform//egpu_connected ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Show if the egpu (XG Mobile) is correctly connected: ++ * 0 - False, ++ * 1 - True +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 89867b18e8f7..a65cf8599124 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -243,6 +243,7 @@ struct asus_wmi { + + bool charge_mode_available; + bool egpu_enable_available; ++ bool egpu_connect_available; + bool dgpu_disable_available; + bool gpu_mux_mode_available; + +@@ -709,6 +710,22 @@ static ssize_t egpu_enable_store(struct device *dev, + } + static DEVICE_ATTR_RW(egpu_enable); + ++/* Is eGPU connected? *********************************************************/ ++static ssize_t egpu_connected_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int result; ++ ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); ++ if (result < 0) ++ return result; ++ ++ return sysfs_emit(buf, "%d\n", result); ++} ++ ++static DEVICE_ATTR_RO(egpu_connected); ++ + /* gpu mux switch *************************************************************/ + static ssize_t gpu_mux_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -3655,6 +3672,7 @@ static struct attribute *platform_attributes[] = { + &dev_attr_touchpad.attr, + &dev_attr_charge_mode.attr, + &dev_attr_egpu_enable.attr, ++ &dev_attr_egpu_connected.attr, + &dev_attr_dgpu_disable.attr, + &dev_attr_gpu_mux_mode.attr, + &dev_attr_lid_resume.attr, +@@ -3687,6 +3705,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, + ok = asus->charge_mode_available; + else if (attr == &dev_attr_egpu_enable.attr) + ok = asus->egpu_enable_available; ++ else if (attr == &dev_attr_egpu_connected.attr) ++ ok = asus->egpu_connect_available; + else if (attr == &dev_attr_dgpu_disable.attr) + ok = asus->dgpu_disable_available; + else if (attr == &dev_attr_gpu_mux_mode.attr) +@@ -3953,6 +3973,7 @@ static int asus_wmi_add(struct platform_device *pdev) + + asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE); + asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); ++ asus->egpu_connect_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); + asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); + asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX); + asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE); +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index 329efc086993..2034648f8cdf 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -100,7 +100,9 @@ + /* Charging mode - 1=Barrel, 2=USB */ + #define ASUS_WMI_DEVID_CHARGE_MODE 0x0012006C + +-/* dgpu on/off */ ++/* epu is connected? 1 == true */ ++#define ASUS_WMI_DEVID_EGPU_CONNECTED 0x00090018 ++/* egpu on/off */ + #define ASUS_WMI_DEVID_EGPU 0x00090019 + + /* dgpu on/off */ +-- +2.41.0 + +From 1bddf53ccac067e043857d28c1598401cd9db7f4 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Tue, 20 Jun 2023 12:26:51 +1200 +Subject: [PATCH 05/13] platform/x86: asus-wmi: don't allow eGPU switching if + eGPU not connected + +Check the ASUS_WMI_DEVID_EGPU_CONNECTED method for eGPU connection +before allowing the ASUS_WMI_DEVID_EGPU method to run. + +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index a65cf8599124..3cb7cee110e2 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -693,6 +693,15 @@ static ssize_t egpu_enable_store(struct device *dev, + if (enable > 1) + return -EINVAL; + ++ err = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); ++ if (err < 0) ++ return err; ++ if (err < 1) { ++ err = -ENODEV; ++ pr_warn("Failed to set egpu disable: %d\n", err); ++ return err; ++ } ++ + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result); + if (err) { + pr_warn("Failed to set egpu disable: %d\n", err); +-- +2.41.0 + +From 64b96869a3ed4b7c9e41c1a3e8410c3ec2582ca9 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Tue, 20 Jun 2023 12:48:31 +1200 +Subject: [PATCH 06/13] platform/x86: asus-wmi: add safety checks to gpu + switching + +Add safety checking to dgpu_disable, egpu_enable, gpu_mux_mode. + +These checks prevent users from doing such things as: +- disabling the dGPU while is muxed to drive the internal screen +- enabling the eGPU which also disables the dGPU, while muxed to + the internal screen +- switching the MUX to dGPU while the dGPU is disabled + +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 50 ++++++++++++++++++++++++++++++++- + 1 file changed, 49 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 3cb7cee110e2..7e80ea2a802a 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -645,6 +645,18 @@ static ssize_t dgpu_disable_store(struct device *dev, + if (disable > 1) + return -EINVAL; + ++ if (asus->gpu_mux_mode_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX); ++ if (result < 0) ++ /* An error here may signal greater failure of GPU handling */ ++ return result; ++ if (!result && disable) { ++ err = -ENODEV; ++ pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err); ++ return err; ++ } ++ } ++ + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result); + if (err) { + pr_warn("Failed to set dgpu disable: %d\n", err); +@@ -693,7 +705,7 @@ static ssize_t egpu_enable_store(struct device *dev, + if (enable > 1) + return -EINVAL; + +- err = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); + if (err < 0) + return err; + if (err < 1) { +@@ -702,6 +714,18 @@ static ssize_t egpu_enable_store(struct device *dev, + return err; + } + ++ if (asus->gpu_mux_mode_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX); ++ if (result < 0) ++ /* An error here may signal greater failure of GPU handling */ ++ return result; ++ if (!result && enable) { ++ err = -ENODEV; ++ pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err); ++ return err; ++ } ++ } ++ + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result); + if (err) { + pr_warn("Failed to set egpu disable: %d\n", err); +@@ -764,6 +788,30 @@ static ssize_t gpu_mux_mode_store(struct device *dev, + if (optimus > 1) + return -EINVAL; + ++ if (asus->dgpu_disable_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU); ++ if (result < 0) ++ /* An error here may signal greater failure of GPU handling */ ++ return result; ++ if (result && !optimus) { ++ err = -ENODEV; ++ pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %d\n", err); ++ return err; ++ } ++ } ++ ++ if (asus->egpu_enable_available) { ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU); ++ if (result < 0) ++ /* An error here may signal greater failure of GPU handling */ ++ return result; ++ if (result && !optimus) { ++ err = -ENODEV; ++ pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", err); ++ return err; ++ } ++ } ++ + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result); + if (err) { + dev_err(dev, "Failed to set GPU MUX mode: %d\n", err); +-- +2.41.0 + +From 76d73c965c18d6b5e1d8d9ab6ae446e2f1913b6b Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Sun, 4 Jun 2023 20:21:10 +1200 +Subject: [PATCH 07/13] platform/x86: asus-wmi: support setting mini-LED mode + +Support changing the mini-LED mode on some of the newer ASUS laptops. + +Signed-off-by: Luke D. Jones +--- + .../ABI/testing/sysfs-platform-asus-wmi | 9 ++++ + drivers/platform/x86/asus-wmi.c | 53 +++++++++++++++++++ + include/linux/platform_data/x86/asus-wmi.h | 1 + + 3 files changed, 63 insertions(+) + +diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi +index 878daf7c2036..5624bdef49cb 100644 +--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi ++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi +@@ -117,3 +117,12 @@ Description: + Show if the egpu (XG Mobile) is correctly connected: + * 0 - False, + * 1 - True ++ ++What: /sys/devices/platform//mini_led_mode ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Change the mini-LED mode: ++ * 0 - Single-zone, ++ * 1 - Multi-zone +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 7e80ea2a802a..9b3dd262f6e4 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -265,6 +265,7 @@ struct asus_wmi { + bool battery_rsoc_available; + + bool panel_overdrive_available; ++ bool mini_led_mode_available; + + struct hotplug_slot hotplug_slot; + struct mutex hotplug_lock; +@@ -1830,6 +1831,54 @@ static ssize_t panel_od_store(struct device *dev, + } + static DEVICE_ATTR_RW(panel_od); + ++/* Mini-LED mode **************************************************************/ ++static ssize_t mini_led_mode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ int result; ++ ++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE); ++ if (result < 0) ++ return result; ++ ++ return sysfs_emit(buf, "%d\n", result); ++} ++ ++static ssize_t mini_led_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 mode; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &mode); ++ if (result) ++ return result; ++ ++ if (mode > 1) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MINI_LED_MODE, mode, &result); ++ ++ if (err) { ++ pr_warn("Failed to set mini-LED: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set mini-LED mode (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mini_led_mode"); ++ ++ return count; ++} ++static DEVICE_ATTR_RW(mini_led_mode); ++ + /* Quirks *********************************************************************/ + + static void asus_wmi_set_xusb2pr(struct asus_wmi *asus) +@@ -3737,6 +3786,7 @@ static struct attribute *platform_attributes[] = { + &dev_attr_fan_boost_mode.attr, + &dev_attr_throttle_thermal_policy.attr, + &dev_attr_panel_od.attr, ++ &dev_attr_mini_led_mode.attr, + NULL + }; + +@@ -3774,6 +3824,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, + ok = asus->throttle_thermal_policy_available; + else if (attr == &dev_attr_panel_od.attr) + ok = asus->panel_overdrive_available; ++ else if (attr == &dev_attr_mini_led_mode.attr) ++ ok = asus->mini_led_mode_available; + + if (devid != -1) + ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); +@@ -4036,6 +4088,7 @@ static int asus_wmi_add(struct platform_device *pdev) + asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE); + asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); + 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); + + err = fan_boost_mode_check_present(asus); + if (err) +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index 2034648f8cdf..ea80361ac6c7 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -66,6 +66,7 @@ + #define ASUS_WMI_DEVID_CAMERA 0x00060013 + #define ASUS_WMI_DEVID_LID_FLIP 0x00060062 + #define ASUS_WMI_DEVID_LID_FLIP_ROG 0x00060077 ++#define ASUS_WMI_DEVID_MINI_LED_MODE 0x0005001E + + /* Storage */ + #define ASUS_WMI_DEVID_CARDREADER 0x00080013 +-- +2.41.0 + +From 3172f65f82ae6b36ab30a91ff73ba703a902da0a Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Tue, 6 Jun 2023 15:05:01 +1200 +Subject: [PATCH 08/13] platform/x86: asus-wmi: expose dGPU and CPU tunables + for ROG + +Expose various CPU and dGPU tunables that are available on many ASUS +ROG laptops. The tunables shown in sysfs will vary depending on the CPU +and dGPU vendor. + +All of these variables are write only and there is no easy way to find +what the defaults are. In general they seem to default to the max value +the vendor sets for the CPU and dGPU package - this is not the same as +the min/max writable value. Values written to these variables that are +beyond the capabilities of the CPU are ignored by the laptop. + +Signed-off-by: Luke D. Jones +--- + .../ABI/testing/sysfs-platform-asus-wmi | 58 ++++ + drivers/platform/x86/asus-wmi.c | 285 ++++++++++++++++++ + include/linux/platform_data/x86/asus-wmi.h | 9 + + 3 files changed, 352 insertions(+) + +diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi +index 5624bdef49cb..caaccd28fabf 100644 +--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi ++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi +@@ -126,3 +126,61 @@ Description: + Change the mini-LED mode: + * 0 - Single-zone, + * 1 - Multi-zone ++ ++What: /sys/devices/platform//ppt_pl1_spl ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD. ++ Shown on Intel+Nvidia or AMD+Nvidia based systems. ++ * min=5, max=250 ++ ++What: /sys/devices/platform//ppt_pl2_sppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT, ++ on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems. ++ * min=5, max=250 ++ ++What: /sys/devices/platform//ppt_fppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only. ++ * min=5, max=250 ++ ++What: /sys/devices/platform//ppt_apu_sppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the APU SPPT limit. Shown on full AMD systems only. ++ * min=5, max=130 ++ ++What: /sys/devices/platform//ppt_platform_sppt ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the platform SPPT limit. Shown on full AMD systems only. ++ * min=5, max=130 ++ ++What: /sys/devices/platform//nv_dynamic_boost ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the dynamic boost limit of the Nvidia dGPU: ++ * min=5, max=25 ++ ++What: /sys/devices/platform//nv_temp_target ++Date: Jun 2023 ++KernelVersion: 6.5 ++Contact: "Luke Jones" ++Description: ++ Set the target temperature limit of the Nvidia dGPU: ++ * min=75, max=87 +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 9b3dd262f6e4..c732610b3fef 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -117,6 +117,16 @@ module_param(fnlock_default, bool, 0444); + /* Mask to determine if setting temperature or percentage */ + #define FAN_CURVE_PWM_MASK 0x04 + ++/* Limits for tunables available on ASUS ROG laptops */ ++#define PPT_TOTAL_MIN 5 ++#define PPT_TOTAL_MAX 250 ++#define PPT_CPU_MIN 5 ++#define PPT_CPU_MAX 130 ++#define NVIDIA_BOOST_MIN 5 ++#define NVIDIA_BOOST_MAX 25 ++#define NVIDIA_TEMP_MIN 75 ++#define NVIDIA_TEMP_MAX 87 ++ + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; + + static int throttle_thermal_policy_write(struct asus_wmi *); +@@ -247,6 +257,15 @@ struct asus_wmi { + bool dgpu_disable_available; + bool gpu_mux_mode_available; + ++ /* Tunables provided by ASUS for gaming laptops */ ++ bool ppt_pl2_sppt_available; ++ bool ppt_pl1_spl_available; ++ bool ppt_apu_sppt_available; ++ bool ppt_plat_sppt_available; ++ bool ppt_fppt_available; ++ bool nv_dyn_boost_available; ++ bool nv_temp_tgt_available; ++ + bool kbd_rgb_mode_available; + bool kbd_rgb_state_available; + +@@ -956,6 +975,244 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = { + NULL, + }; + ++/* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/ ++static ssize_t ppt_pl2_sppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL2_SPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_pl2_sppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_pl2_sppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_pl2_sppt); ++ ++/* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/ ++static ssize_t ppt_pl1_spl_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL1_SPL, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_pl1_spl: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_pl1_spl (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_pl1_spl); ++ ++/* Tunable: PPT APU FPPT ******************************************************/ ++static ssize_t ppt_fppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_FPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_fppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_fppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_fppt); ++ ++/* Tunable: PPT APU SPPT *****************************************************/ ++static ssize_t ppt_apu_sppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_APU_SPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_apu_sppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_apu_sppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_apu_sppt); ++ ++/* Tunable: PPT platform SPPT ************************************************/ ++static ssize_t ppt_platform_sppt_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PLAT_SPPT, value, &result); ++ if (err) { ++ pr_warn("Failed to set ppt_platform_sppt: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set ppt_platform_sppt (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(ppt_platform_sppt); ++ ++/* Tunable: NVIDIA dynamic boost *********************************************/ ++static ssize_t nv_dynamic_boost_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < NVIDIA_BOOST_MIN || value > NVIDIA_BOOST_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_DYN_BOOST, value, &result); ++ if (err) { ++ pr_warn("Failed to set nv_dynamic_boost: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set nv_dynamic_boost (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(nv_dynamic_boost); ++ ++/* Tunable: NVIDIA temperature target ****************************************/ ++static ssize_t nv_temp_target_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 value; ++ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ ++ result = kstrtou32(buf, 10, &value); ++ if (result) ++ return result; ++ ++ if (value < NVIDIA_TEMP_MIN || value > NVIDIA_TEMP_MAX) ++ return -EINVAL; ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_THERM_TARGET, value, &result); ++ if (err) { ++ pr_warn("Failed to set nv_temp_target: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set nv_temp_target (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(nv_temp_target); ++ + /* Battery ********************************************************************/ + + /* The battery maximum charging percentage */ +@@ -3785,6 +4042,13 @@ static struct attribute *platform_attributes[] = { + &dev_attr_als_enable.attr, + &dev_attr_fan_boost_mode.attr, + &dev_attr_throttle_thermal_policy.attr, ++ &dev_attr_ppt_pl2_sppt.attr, ++ &dev_attr_ppt_pl1_spl.attr, ++ &dev_attr_ppt_fppt.attr, ++ &dev_attr_ppt_apu_sppt.attr, ++ &dev_attr_ppt_platform_sppt.attr, ++ &dev_attr_nv_dynamic_boost.attr, ++ &dev_attr_nv_temp_target.attr, + &dev_attr_panel_od.attr, + &dev_attr_mini_led_mode.attr, + NULL +@@ -3822,6 +4086,20 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, + ok = asus->fan_boost_mode_available; + else if (attr == &dev_attr_throttle_thermal_policy.attr) + ok = asus->throttle_thermal_policy_available; ++ else if (attr == &dev_attr_ppt_pl2_sppt.attr) ++ ok = asus->ppt_pl2_sppt_available; ++ else if (attr == &dev_attr_ppt_pl1_spl.attr) ++ ok = asus->ppt_pl1_spl_available; ++ else if (attr == &dev_attr_ppt_fppt.attr) ++ ok = asus->ppt_fppt_available; ++ else if (attr == &dev_attr_ppt_apu_sppt.attr) ++ ok = asus->ppt_apu_sppt_available; ++ else if (attr == &dev_attr_ppt_platform_sppt.attr) ++ ok = asus->ppt_plat_sppt_available; ++ else if (attr == &dev_attr_nv_dynamic_boost.attr) ++ ok = asus->nv_dyn_boost_available; ++ else if (attr == &dev_attr_nv_temp_target.attr) ++ ok = asus->nv_temp_tgt_available; + else if (attr == &dev_attr_panel_od.attr) + ok = asus->panel_overdrive_available; + else if (attr == &dev_attr_mini_led_mode.attr) +@@ -4087,6 +4365,13 @@ static int asus_wmi_add(struct platform_device *pdev) + asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX); + asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE); + asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); ++ asus->ppt_pl2_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL2_SPPT); ++ asus->ppt_pl1_spl_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL1_SPL); ++ asus->ppt_fppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_FPPT); ++ asus->ppt_apu_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_APU_SPPT); ++ asus->ppt_plat_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PLAT_SPPT); ++ asus->nv_dyn_boost_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_DYN_BOOST); ++ 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); + +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index ea80361ac6c7..16e99a1c37fc 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -86,6 +86,15 @@ + #define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025 + #define ASUS_WMI_DEVID_MID_FAN_CURVE 0x00110032 + ++/* Tunables for AUS ROG laptops */ ++#define ASUS_WMI_DEVID_PPT_PL2_SPPT 0x001200A0 ++#define ASUS_WMI_DEVID_PPT_PL1_SPL 0x001200A3 ++#define ASUS_WMI_DEVID_PPT_APU_SPPT 0x001200B0 ++#define ASUS_WMI_DEVID_PPT_PLAT_SPPT 0x001200B1 ++#define ASUS_WMI_DEVID_PPT_FPPT 0x001200C1 ++#define ASUS_WMI_DEVID_NV_DYN_BOOST 0x001200C0 ++#define ASUS_WMI_DEVID_NV_THERM_TARGET 0x001200C2 ++ + /* Power */ + #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 + +-- +2.41.0 + +From 73622204c837e2ab4729bc2af2c8bb6d58f4b3b0 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Sun, 23 Jul 2023 21:29:11 +1200 +Subject: [PATCH 09/13] Fixes: a23870110a38 ("asus-wmi: add support for showing + middle fan RPM") + +After the addition of the mid fan custom curve functionality various +incorrect behaviour was uncovered. This commit fixes these areas. + +- Ensure mid fan attributes actually use the correct fan ID +- Correction to a bit mask for selecting the correct fan data +- Refactor the curve show/store functions to be cleaner and and + match each others layout + +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 78 ++++++++++++++++----------------- + 1 file changed, 38 insertions(+), 40 deletions(-) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index c732610b3fef..496d03e88595 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -2910,9 +2910,8 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev) + { + struct fan_curve_data *curves; + u8 buf[FAN_CURVE_BUF_LEN]; +- int fan_idx = 0; ++ int err, fan_idx; + u8 mode = 0; +- int err; + + if (asus->throttle_thermal_policy_available) + mode = asus->throttle_thermal_policy_mode; +@@ -2922,13 +2921,6 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev) + else if (mode == 1) + mode = 2; + +- if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE) +- fan_idx = FAN_CURVE_DEV_GPU; +- +- if (fan_dev == ASUS_WMI_DEVID_MID_FAN_CURVE) +- fan_idx = FAN_CURVE_DEV_MID; +- +- curves = &asus->custom_fan_curves[fan_idx]; + err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf, + FAN_CURVE_BUF_LEN); + if (err) { +@@ -2936,9 +2928,17 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev) + return err; + } + +- fan_curve_copy_from_buf(curves, buf); ++ fan_idx = FAN_CURVE_DEV_CPU; ++ if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE) ++ fan_idx = FAN_CURVE_DEV_GPU; ++ ++ if (fan_dev == ASUS_WMI_DEVID_MID_FAN_CURVE) ++ fan_idx = FAN_CURVE_DEV_MID; ++ ++ curves = &asus->custom_fan_curves[fan_idx]; + curves->device_id = fan_dev; + ++ fan_curve_copy_from_buf(curves, buf); + return 0; + } + +@@ -2968,7 +2968,7 @@ static struct fan_curve_data *fan_curve_attr_select(struct asus_wmi *asus, + { + int index = to_sensor_dev_attr(attr)->index; + +- return &asus->custom_fan_curves[index & FAN_CURVE_DEV_GPU]; ++ return &asus->custom_fan_curves[index]; + } + + /* Determine which fan the attribute is for if SENSOR_ATTR_2 */ +@@ -2977,7 +2977,7 @@ static struct fan_curve_data *fan_curve_attr_2_select(struct asus_wmi *asus, + { + int nr = to_sensor_dev_attr_2(attr)->nr; + +- return &asus->custom_fan_curves[nr & FAN_CURVE_DEV_GPU]; ++ return &asus->custom_fan_curves[nr & ~FAN_CURVE_PWM_MASK]; + } + + static ssize_t fan_curve_show(struct device *dev, +@@ -2986,13 +2986,13 @@ static ssize_t fan_curve_show(struct device *dev, + struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); + struct asus_wmi *asus = dev_get_drvdata(dev); + struct fan_curve_data *data; +- int value, index, nr; ++ int value, pwm, index; + + data = fan_curve_attr_2_select(asus, attr); ++ pwm = dev_attr->nr & FAN_CURVE_PWM_MASK; + index = dev_attr->index; +- nr = dev_attr->nr; + +- if (nr & FAN_CURVE_PWM_MASK) ++ if (pwm) + value = data->percents[index]; + else + value = data->temps[index]; +@@ -3035,23 +3035,21 @@ static ssize_t fan_curve_store(struct device *dev, + struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); + struct asus_wmi *asus = dev_get_drvdata(dev); + struct fan_curve_data *data; ++ int err, pwm, index; + u8 value; +- int err; +- +- int pwm = dev_attr->nr & FAN_CURVE_PWM_MASK; +- int index = dev_attr->index; + + data = fan_curve_attr_2_select(asus, attr); ++ pwm = dev_attr->nr & FAN_CURVE_PWM_MASK; ++ index = dev_attr->index; + + err = kstrtou8(buf, 10, &value); + if (err < 0) + return err; + +- if (pwm) { ++ if (pwm) + data->percents[index] = value; +- } else { ++ else + data->temps[index] = value; +- } + + /* + * Mark as disabled so the user has to explicitly enable to apply a +@@ -3164,7 +3162,7 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_temp, fan_curve, + FAN_CURVE_DEV_CPU, 7); + + static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, fan_curve, +- FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0); ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0); + static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, fan_curve, + FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 1); + static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, fan_curve, +@@ -3217,40 +3215,40 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve, + FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7); + + /* MID */ +-static SENSOR_DEVICE_ATTR_RW(pwm3_enable, fan_curve_enable, FAN_CURVE_DEV_GPU); ++static SENSOR_DEVICE_ATTR_RW(pwm3_enable, fan_curve_enable, FAN_CURVE_DEV_MID); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 0); ++ FAN_CURVE_DEV_MID, 0); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 1); ++ FAN_CURVE_DEV_MID, 1); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 2); ++ FAN_CURVE_DEV_MID, 2); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 3); ++ FAN_CURVE_DEV_MID, 3); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 4); ++ FAN_CURVE_DEV_MID, 4); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 5); ++ FAN_CURVE_DEV_MID, 5); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 6); ++ FAN_CURVE_DEV_MID, 6); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp, fan_curve, +- FAN_CURVE_DEV_GPU, 7); ++ FAN_CURVE_DEV_MID, 7); + + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 0); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 0); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 1); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 1); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 2); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 2); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 3); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 3); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 4); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 4); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 5); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 5); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 6); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 6); + static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_pwm, fan_curve, +- FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7); ++ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 7); + + static struct attribute *asus_fan_curve_attr[] = { + /* CPU */ +-- +2.41.0 + +From ebd35946f15047d89fd1bb68b0f75dc5c367af6e Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Tue, 15 Aug 2023 17:10:33 +0100 +Subject: [PATCH 10/13] ALSA: hda: cs35l41: Support systems with missing _DSD + properties + +Some systems using CS35L41 with HDA were released without some +required _DSD properties in ACPI. To support these special cases, +add an api to configure the correct properties for systems with +this issue. + +This initial commit moves the no _DSD support for Lenovo +Legion Laptops (CLSA0100, CLSA0101) into a new framework which +can be extended to support additional laptops in the future. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/Makefile | 2 +- + sound/pci/hda/cs35l41_hda.c | 65 ++++++------------------- + sound/pci/hda/cs35l41_hda.h | 1 + + sound/pci/hda/cs35l41_hda_property.c | 73 ++++++++++++++++++++++++++++ + sound/pci/hda/cs35l41_hda_property.h | 18 +++++++ + 5 files changed, 108 insertions(+), 51 deletions(-) + create mode 100644 sound/pci/hda/cs35l41_hda_property.c + create mode 100644 sound/pci/hda/cs35l41_hda_property.h + +diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile +index 00d306104484..3b259239c48a 100644 +--- a/sound/pci/hda/Makefile ++++ b/sound/pci/hda/Makefile +@@ -28,7 +28,7 @@ snd-hda-codec-via-objs := patch_via.o + snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o + + # side codecs +-snd-hda-scodec-cs35l41-objs := cs35l41_hda.o ++snd-hda-scodec-cs35l41-objs := cs35l41_hda.o cs35l41_hda_property.o + snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o + snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o + snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index ce5faa620517..692b69f24138 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -19,6 +19,7 @@ + #include "hda_component.h" + #include "cs35l41_hda.h" + #include "hda_cs_dsp_ctl.h" ++#include "cs35l41_hda_property.h" + + #define CS35L41_FIRMWARE_ROOT "cirrus/" + #define CS35L41_PART "cs35l41" +@@ -1156,8 +1157,7 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41) + return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos); + } + +-static int cs35l41_get_speaker_id(struct device *dev, int amp_index, +- int num_amps, int fixed_gpio_id) ++int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int fixed_gpio_id) + { + struct gpio_desc *speaker_id_desc; + int speaker_id = -ENODEV; +@@ -1211,49 +1211,6 @@ static int cs35l41_get_speaker_id(struct device *dev, int amp_index, + return speaker_id; + } + +-/* +- * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work. +- * And devices created by serial-multi-instantiate don't have their device struct +- * pointing to the correct fwnode, so acpi_dev must be used here. +- * And devm functions expect that the device requesting the resource has the correct +- * fwnode. +- */ +-static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physdev, int id, +- const char *hid) +-{ +- struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; +- +- /* check I2C address to assign the index */ +- 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->gpio2.func = CS35L41_INTERRUPT; +- hw_cfg->gpio2.valid = true; +- hw_cfg->valid = true; +- +- if (strncmp(hid, "CLSA0100", 8) == 0) { +- hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; +- } else if (strncmp(hid, "CLSA0101", 8) == 0) { +- hw_cfg->bst_type = CS35L41_EXT_BOOST; +- hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; +- hw_cfg->gpio1.valid = true; +- } else { +- /* +- * Note: CLSA010(0/1) are special cases which use a slightly different design. +- * All other HIDs e.g. CSC3551 require valid ACPI _DSD properties to be supported. +- */ +- dev_err(cs35l41->dev, "Error: ACPI _DSD Properties are missing for HID %s.\n", hid); +- hw_cfg->valid = false; +- hw_cfg->gpio1.valid = false; +- hw_cfg->gpio2.valid = false; +- return -EINVAL; +- } +- +- return 0; +-} +- + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) + { + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; +@@ -1279,12 +1236,17 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + sub = NULL; + cs35l41->acpi_subsystem_id = sub; + ++ ret = cs35l41_add_dsd_properties(cs35l41, physdev, id, hid); ++ if (!ret) { ++ dev_info(cs35l41->dev, "Using extra _DSD properties, bypassing _DSD in ACPI\n"); ++ goto put_physdev; ++ } ++ + property = "cirrus,dev-index"; + ret = device_property_count_u32(physdev, property); +- if (ret <= 0) { +- ret = cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid); +- goto err_put_physdev; +- } ++ if (ret <= 0) ++ goto err; ++ + if (ret > ARRAY_SIZE(values)) { + ret = -EINVAL; + goto err; +@@ -1374,7 +1336,10 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + + err: + dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret); +-err_put_physdev: ++ hw_cfg->valid = false; ++ hw_cfg->gpio1.valid = false; ++ hw_cfg->gpio2.valid = false; ++put_physdev: + put_device(physdev); + + return ret; +diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h +index bdb35f3be68a..b93bf762976e 100644 +--- a/sound/pci/hda/cs35l41_hda.h ++++ b/sound/pci/hda/cs35l41_hda.h +@@ -83,5 +83,6 @@ extern const struct dev_pm_ops cs35l41_hda_pm_ops; + int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, + struct regmap *regmap); + void cs35l41_hda_remove(struct device *dev); ++int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int fixed_gpio_id); + + #endif /*__CS35L41_HDA_H__*/ +diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c +new file mode 100644 +index 000000000000..673f23257a09 +--- /dev/null ++++ b/sound/pci/hda/cs35l41_hda_property.c +@@ -0,0 +1,73 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// CS35L41 ALSA HDA Property driver ++// ++// Copyright 2023 Cirrus Logic, Inc. ++// ++// Author: Stefan Binding ++ ++#include ++#include ++#include "cs35l41_hda_property.h" ++ ++/* ++ * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work. ++ * And devices created by serial-multi-instantiate don't have their device struct ++ * pointing to the correct fwnode, so acpi_dev must be used here. ++ * And devm functions expect that the device requesting the resource has the correct ++ * fwnode. ++ */ ++static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid) ++{ ++ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; ++ ++ /* check I2C address to assign the index */ ++ 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->gpio2.func = CS35L41_INTERRUPT; ++ hw_cfg->gpio2.valid = true; ++ hw_cfg->valid = true; ++ ++ if (strcmp(hid, "CLSA0100") == 0) { ++ hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; ++ } else if (strcmp(hid, "CLSA0101") == 0) { ++ hw_cfg->bst_type = CS35L41_EXT_BOOST; ++ hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; ++ hw_cfg->gpio1.valid = true; ++ } ++ ++ return 0; ++} ++ ++struct cs35l41_prop_model { ++ const char *hid; ++ const char *ssid; ++ int (*add_prop)(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid); ++}; ++ ++const struct cs35l41_prop_model cs35l41_prop_model_table[] = { ++ { "CLSA0100", NULL, lenovo_legion_no_acpi }, ++ { "CLSA0101", NULL, lenovo_legion_no_acpi }, ++ {} ++}; ++ ++int cs35l41_add_dsd_properties(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid) ++{ ++ const struct cs35l41_prop_model *model; ++ ++ for (model = cs35l41_prop_model_table; model->hid > 0; model++) { ++ if (!strcmp(model->hid, hid) && ++ (!model->ssid || ++ (cs35l41->acpi_subsystem_id && ++ !strcmp(model->ssid, cs35l41->acpi_subsystem_id)))) ++ return model->add_prop(cs35l41, physdev, id, hid); ++ } ++ ++ return -ENOENT; ++} +diff --git a/sound/pci/hda/cs35l41_hda_property.h b/sound/pci/hda/cs35l41_hda_property.h +new file mode 100644 +index 000000000000..fd834042e2fd +--- /dev/null ++++ b/sound/pci/hda/cs35l41_hda_property.h +@@ -0,0 +1,18 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * CS35L41 ALSA HDA Property driver ++ * ++ * Copyright 2023 Cirrus Logic, Inc. ++ * ++ * Author: Stefan Binding ++ */ ++ ++#ifndef CS35L41_HDA_PROP_H ++#define CS35L41_HDA_PROP_H ++ ++#include ++#include "cs35l41_hda.h" ++ ++int cs35l41_add_dsd_properties(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid); ++#endif /* CS35L41_HDA_PROP_H */ +-- +2.41.0 + +From d6f191b6827fae534a0635afb8d4380b59cb409c Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Wed, 23 Aug 2023 11:05:59 +1200 +Subject: [PATCH 11/13] 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 | 47 ++++++++++++++++++++++++++++ + 1 file changed, 47 insertions(+) + +diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c +index 673f23257a09..b39f9443e1d6 100644 +--- a/sound/pci/hda/cs35l41_hda_property.c ++++ b/sound/pci/hda/cs35l41_hda_property.c +@@ -43,6 +43,41 @@ 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; ++ ++ /* check SPI or I2C address to assign the index */ ++ cs35l41->index = (id == 0 || id == 0x40) ? 0 : 1; ++ cs35l41->channel_index = 0; ++ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2); ++ 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, "10431473") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431483") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431493") == 0) { ++ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 1, GPIOD_OUT_HIGH); ++ } else { ++ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); ++ } ++ ++ hw_cfg->valid = true; ++ ++ return 0; ++} ++ + struct cs35l41_prop_model { + const char *hid; + const char *ssid; +@@ -53,6 +88,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", "10431433", asus_rog_2023_spkr_id2 }, // ASUS GS650P - i2c ++ { "CSC3551", "10431463", asus_rog_2023_spkr_id2 }, // ASUS GA402X/N - i2c ++ { "CSC3551", "10431473", asus_rog_2023_spkr_id2 }, // ASUS GU604V - spi, reset gpio 1 ++ { "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", "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 + +From f2e032c25d0fac01e8272176c71d5080e0123081 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Mon, 28 Aug 2023 11:05:16 +1200 +Subject: [PATCH 12/13] platform/x86: asus-wmi: corrections to egpu safety + check + +An incorrect if statement was preventing the enablement of the egpu. + +Fixes: 1bddf53ccac0 ("platform/x86: asus-wmi: don't allow eGPU switching if eGPU not connected") +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 15 +++++++-------- + 1 file changed, 7 insertions(+), 8 deletions(-) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 496d03e88595..13547e55ae82 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -726,19 +726,18 @@ static ssize_t egpu_enable_store(struct device *dev, + return -EINVAL; + + result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED); +- if (err < 0) +- return err; +- if (err < 1) { +- err = -ENODEV; +- pr_warn("Failed to set egpu disable: %d\n", err); ++ if (err < 0) { ++ pr_warn("Failed to get egpu connection status: %d\n", err); + return err; + } + + if (asus->gpu_mux_mode_available) { + result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX); +- if (result < 0) ++ if (result < 0) { + /* An error here may signal greater failure of GPU handling */ ++ pr_warn("Failed to get gpu mux status: %d\n", err); + return result; ++ } + if (!result && enable) { + err = -ENODEV; + pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err); +@@ -748,12 +747,12 @@ static ssize_t egpu_enable_store(struct device *dev, + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result); + if (err) { +- pr_warn("Failed to set egpu disable: %d\n", err); ++ pr_warn("Failed to set egpu state: %d\n", err); + return err; + } + + if (result > 1) { +- pr_warn("Failed to set egpu disable (retval): 0x%x\n", result); ++ pr_warn("Failed to set egpu state (retval): 0x%x\n", result); + return -EIO; + } + +-- +2.41.0 + +From cc5628b9a4c5fea304346202f753b48bc8f6c622 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Sun, 30 Apr 2023 10:56:34 +1200 +Subject: [PATCH 13/13] platform/x86: asus-wmi: add support for ASUS screenpad + +Add support for the WMI methods used to turn off and adjust the +brightness of the secondary "screenpad" device found on some high-end +ASUS laptops like the GX650P series and others. + +These methods are utilised in a new backlight device named asus_screenpad. + +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 131 +++++++++++++++++++++ + drivers/platform/x86/asus-wmi.h | 1 + + include/linux/platform_data/x86/asus-wmi.h | 4 + + 3 files changed, 136 insertions(+) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 13547e55ae82..2801c691133a 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -25,6 +25,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -127,6 +128,9 @@ 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 ++ + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; + + static int throttle_thermal_policy_write(struct asus_wmi *); +@@ -212,6 +216,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 +3781,123 @@ static int is_display_toggle(int code) + return 0; + } + ++/* Screenpad backlight *******************************************************/ ++ ++static int read_screenpad_backlight_power(struct asus_wmi *asus) ++{ ++ int ret; ++ ++ ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER); ++ if (ret < 0) ++ return ret; ++ /* 1 == powered */ ++ return ret ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; ++} ++ ++static int read_screenpad_brightness(struct backlight_device *bd) ++{ ++ struct asus_wmi *asus = bl_get_data(bd); ++ u32 retval; ++ int err; ++ ++ err = read_screenpad_backlight_power(asus); ++ if (err < 0) ++ return err; ++ /* The device brightness can only be read if powered, so return stored */ ++ if (err == FB_BLANK_POWERDOWN) ++ return asus->driver->screenpad_brightness; ++ ++ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &retval); ++ if (err < 0) ++ return err; ++ ++ return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) - ASUS_SCREENPAD_BRIGHT_MIN; ++} ++ ++static int update_screenpad_bl_status(struct backlight_device *bd) ++{ ++ struct asus_wmi *asus = bl_get_data(bd); ++ int power, err = 0; ++ u32 ctrl_param; ++ ++ power = read_screenpad_backlight_power(asus); ++ if (power < 0) ++ return power; ++ ++ if (bd->props.power != power) { ++ if (power != FB_BLANK_UNBLANK) { ++ /* Only brightness > 0 can power it back on */ ++ ctrl_param = max(ASUS_SCREENPAD_BRIGHT_MIN, asus->driver->screenpad_brightness); ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ++ ctrl_param, NULL); ++ } else { ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); ++ } ++ } else if (power == FB_BLANK_UNBLANK) { ++ /* Only set brightness if powered on or we get invalid/unsync state */ ++ ctrl_param = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL); ++ } ++ ++ /* Ensure brightness is stored to turn back on with */ ++ asus->driver->screenpad_brightness = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; ++ ++ return err; ++} ++ ++static const struct backlight_ops asus_screenpad_bl_ops = { ++ .get_brightness = read_screenpad_brightness, ++ .update_status = update_screenpad_bl_status, ++ .options = BL_CORE_SUSPENDRESUME, ++}; ++ ++static int asus_screenpad_init(struct asus_wmi *asus) ++{ ++ struct backlight_device *bd; ++ struct backlight_properties props; ++ int err, power; ++ int brightness = 0; ++ ++ power = read_screenpad_backlight_power(asus); ++ if (power < 0) ++ return power; ++ ++ if (power != FB_BLANK_POWERDOWN) { ++ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness); ++ if (err < 0) ++ return err; ++ } ++ /* default to an acceptable min brightness on boot if too low */ ++ if (brightness < ASUS_SCREENPAD_BRIGHT_MIN) ++ brightness = ASUS_SCREENPAD_BRIGHT_MIN; ++ ++ memset(&props, 0, sizeof(struct backlight_properties)); ++ props.type = BACKLIGHT_RAW; /* ensure this bd is last to be picked */ ++ props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX - ASUS_SCREENPAD_BRIGHT_MIN; ++ bd = backlight_device_register("asus_screenpad", ++ &asus->platform_device->dev, asus, ++ &asus_screenpad_bl_ops, &props); ++ if (IS_ERR(bd)) { ++ pr_err("Could not register backlight device\n"); ++ return PTR_ERR(bd); ++ } ++ ++ asus->screenpad_backlight_device = bd; ++ asus->driver->screenpad_brightness = brightness; ++ bd->props.brightness = brightness; ++ bd->props.power = power; ++ backlight_update_status(bd); ++ ++ return 0; ++} ++ ++static void asus_screenpad_exit(struct asus_wmi *asus) ++{ ++ backlight_device_unregister(asus->screenpad_backlight_device); ++ ++ asus->screenpad_backlight_device = NULL; ++} ++ + /* Fn-lock ********************************************************************/ + + static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus) +@@ -4431,6 +4553,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) ++ goto fail_screenpad; ++ } ++ + if (asus_wmi_has_fnlock_key(asus)) { + asus->fnlock_locked = fnlock_default; + asus_wmi_fnlock_update(asus); +@@ -4454,6 +4582,8 @@ static int asus_wmi_add(struct platform_device *pdev) + asus_wmi_backlight_exit(asus); + fail_backlight: + asus_wmi_rfkill_exit(asus); ++fail_screenpad: ++ asus_screenpad_exit(asus); + fail_rfkill: + asus_wmi_led_exit(asus); + fail_leds: +@@ -4480,6 +4610,7 @@ static int asus_wmi_remove(struct platform_device *device) + asus = platform_get_drvdata(device); + wmi_remove_notify_handler(asus->driver->event_guid); + asus_wmi_backlight_exit(asus); ++ asus_screenpad_exit(asus); + asus_wmi_input_exit(asus); + asus_wmi_led_exit(asus); + asus_wmi_rfkill_exit(asus); +diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h +index a478ebfd34df..5fbdd0eafa02 100644 +--- a/drivers/platform/x86/asus-wmi.h ++++ b/drivers/platform/x86/asus-wmi.h +@@ -57,6 +57,7 @@ struct quirk_entry { + struct asus_wmi_driver { + int brightness; + 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 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -58,6 +58,10 @@ + #define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 + #define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* ?? */ + #define ASUS_WMI_DEVID_LIGHTBAR 0x00050025 ++/* This can only be used to disable the screen, not re-enable */ ++#define ASUS_WMI_DEVID_SCREENPAD_POWER 0x00050031 ++/* Writing a brightness re-enables the screen if disabled */ ++#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 + diff --git a/patches/nobara/chimera-ALSA.patch b/patches/nobara/chimera-ALSA.patch new file mode 100644 index 0000000..c7bd726 --- /dev/null +++ b/patches/nobara/chimera-ALSA.patch @@ -0,0 +1,1170 @@ +From c21139c6470a5b08c7463e451f2ff404e55f652f Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:06 +0100 +Subject: [PATCH 01/11] ALSA: cs35l41: Use mbox command to enable speaker + output for external boost + +To enable the speaker output in external boost mode, 2 registers must +be set, one after another. The longer the time between the writes of +the two registers, the more likely, and more loudly a pop may occur. +To minimize this, an mbox command can be used to allow the firmware +to perform this action, minimizing any delay between write, thus +minimizing any pop or click as a result. The old method will remain +when running without firmware. + +Acked-by: Mark Brown +Signed-off-by: Stefan Binding +--- + include/sound/cs35l41.h | 5 ++- + sound/pci/hda/cs35l41_hda.c | 9 ++-- + sound/soc/codecs/cs35l41-lib.c | 76 +++++++++++++++++++++++++++------- + sound/soc/codecs/cs35l41.c | 8 ++-- + 4 files changed, 74 insertions(+), 24 deletions(-) + +diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h +index 7239d943942c..1bf757901d02 100644 +--- a/include/sound/cs35l41.h ++++ b/include/sound/cs35l41.h +@@ -829,6 +829,7 @@ enum cs35l41_cspl_mbox_cmd { + CSPL_MBOX_CMD_STOP_PRE_REINIT = 4, + CSPL_MBOX_CMD_HIBERNATE = 5, + CSPL_MBOX_CMD_OUT_OF_HIBERNATE = 6, ++ CSPL_MBOX_CMD_SPK_OUT_ENABLE = 7, + CSPL_MBOX_CMD_UNKNOWN_CMD = -1, + CSPL_MBOX_CMD_INVALID_SEQUENCE = -2, + }; +@@ -901,7 +902,7 @@ 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_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable, +- struct completion *pll_lock); ++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); + + #endif /* __CS35L41_H */ +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index ce5faa620517..f9c97270db6f 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -514,13 +514,15 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action) + break; + case HDA_GEN_PCM_ACT_PREPARE: + mutex_lock(&cs35l41->fw_mutex); +- ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1, NULL); ++ ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL, ++ cs35l41->firmware_running); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + mutex_lock(&cs35l41->fw_mutex); + regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); +- ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0, NULL); ++ ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL, ++ cs35l41->firmware_running); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLOSE: +@@ -672,7 +674,8 @@ static int cs35l41_runtime_suspend(struct device *dev) + if (cs35l41->playback_started) { + regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute, + ARRAY_SIZE(cs35l41_hda_mute)); +- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, NULL); ++ cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, ++ NULL, cs35l41->firmware_running); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) +diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c +index 1e4205295a0d..a7556fa33cdd 100644 +--- a/sound/soc/codecs/cs35l41-lib.c ++++ b/sound/soc/codecs/cs35l41-lib.c +@@ -1080,28 +1080,32 @@ static const struct reg_sequence cs35l41_safe_to_reset[] = { + { 0x00000040, 0x00000033 }, + }; + +-static const struct reg_sequence cs35l41_active_to_safe[] = { ++static const struct reg_sequence cs35l41_active_to_safe_start[] = { + { 0x00000040, 0x00000055 }, + { 0x00000040, 0x000000AA }, + { 0x00007438, 0x00585941 }, + { CS35L41_PWR_CTRL1, 0x00000000 }, +- { 0x0000742C, 0x00000009, 3000 }, ++ { 0x0000742C, 0x00000009 }, ++}; ++ ++static const struct reg_sequence cs35l41_active_to_safe_end[] = { + { 0x00007438, 0x00580941 }, + { 0x00000040, 0x000000CC }, + { 0x00000040, 0x00000033 }, + }; + +-static const struct reg_sequence cs35l41_safe_to_active[] = { ++static const struct reg_sequence cs35l41_safe_to_active_start[] = { + { 0x00000040, 0x00000055 }, + { 0x00000040, 0x000000AA }, + { 0x0000742C, 0x0000000F }, + { 0x0000742C, 0x00000079 }, + { 0x00007438, 0x00585941 }, +- { CS35L41_PWR_CTRL1, 0x00000001, 3000 }, // GLOBAL_EN = 1 ++ { CS35L41_PWR_CTRL1, 0x00000001 }, // GLOBAL_EN = 1 ++}; ++ ++static const struct reg_sequence cs35l41_safe_to_active_en_spk[] = { + { 0x0000742C, 0x000000F9 }, + { 0x00007438, 0x00580941 }, +- { 0x00000040, 0x000000CC }, +- { 0x00000040, 0x00000033 }, + }; + + static const struct reg_sequence cs35l41_reset_to_safe[] = { +@@ -1188,11 +1192,11 @@ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type) + } + EXPORT_SYMBOL_GPL(cs35l41_safe_reset); + +-int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable, +- struct completion *pll_lock) ++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 ret; +- unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3; ++ unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status; + struct reg_sequence cs35l41_mdsync_down_seq[] = { + {CS35L41_PWR_CTRL3, 0}, + {CS35L41_GPIO_PAD_CONTROL, 0}, +@@ -1204,6 +1208,14 @@ int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, + {CS35L41_PWR_CTRL1, 0x00000001, 3000}, + }; + ++ if ((pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && enable) { ++ dev_dbg(dev, "Cannot set Global Enable - already set.\n"); ++ return 0; ++ } else if (!(pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && !enable) { ++ dev_dbg(dev, "Cannot unset Global Enable - not set.\n"); ++ return 0; ++ } ++ + switch (b_type) { + case CS35L41_SHD_BOOST_ACTV: + case CS35L41_SHD_BOOST_PASS: +@@ -1244,16 +1256,48 @@ int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, + case CS35L41_INT_BOOST: + ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK, + enable << CS35L41_GLOBAL_EN_SHIFT); ++ if (ret) { ++ dev_err(dev, "CS35L41_PWR_CTRL1 set failed: %d\n", ret); ++ return ret; ++ } + usleep_range(3000, 3100); + break; + case CS35L41_EXT_BOOST: + case CS35L41_EXT_BOOST_NO_VSPK_SWITCH: +- if (enable) +- ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active, +- ARRAY_SIZE(cs35l41_safe_to_active)); +- else +- ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe, +- ARRAY_SIZE(cs35l41_active_to_safe)); ++ if (enable) { ++ /* Test Key is unlocked here */ ++ ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active_start, ++ ARRAY_SIZE(cs35l41_safe_to_active_start)); ++ if (ret) ++ return ret; ++ ++ usleep_range(3000, 3100); ++ ++ if (firmware_running) ++ ret = cs35l41_set_cspl_mbox_cmd(dev, regmap, ++ CSPL_MBOX_CMD_SPK_OUT_ENABLE); ++ else ++ ret = regmap_multi_reg_write(regmap, cs35l41_safe_to_active_en_spk, ++ ARRAY_SIZE(cs35l41_safe_to_active_en_spk)); ++ ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ } else { ++ /* Test Key is unlocked here */ ++ ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_start, ++ ARRAY_SIZE(cs35l41_active_to_safe_start)); ++ if (ret) { ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ return ret; ++ } ++ ++ usleep_range(3000, 3100); ++ ++ /* Test Key is locked here */ ++ ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_end, ++ ARRAY_SIZE(cs35l41_active_to_safe_end)); ++ } + break; + default: + ret = -EINVAL; +@@ -1344,6 +1388,8 @@ static bool cs35l41_check_cspl_mbox_sts(enum cs35l41_cspl_mbox_cmd cmd, + return (sts == CSPL_MBOX_STS_RUNNING); + case CSPL_MBOX_CMD_STOP_PRE_REINIT: + return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT); ++ case CSPL_MBOX_CMD_SPK_OUT_ENABLE: ++ return (sts == CSPL_MBOX_STS_RUNNING); + default: + return false; + } +diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c +index 6ac501f008ec..d4e9c9d9b50a 100644 +--- a/sound/soc/codecs/cs35l41.c ++++ b/sound/soc/codecs/cs35l41.c +@@ -500,12 +500,12 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w, + cs35l41_pup_patch, + ARRAY_SIZE(cs35l41_pup_patch)); + +- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 1, +- &cs35l41->pll_lock); ++ ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, ++ 1, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running); + break; + case SND_SOC_DAPM_POST_PMD: +- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, +- &cs35l41->pll_lock); ++ ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, ++ 0, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running); + + ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + val, val & CS35L41_PDN_DONE_MASK, +-- +2.41.0 + +From 437f5415c5ac8e49b0675f74132b6e1308b6e5c7 Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:07 +0100 +Subject: [PATCH 02/11] ALSA: cs35l41: Poll for Power Up/Down rather than + waiting a fixed delay + +To ensure the chip has correctly powered up or down before continuing, +the driver will now poll a register, rather than wait a fixed delay. + +Acked-by: Mark Brown +Signed-off-by: Stefan Binding +--- + sound/soc/codecs/cs35l41-lib.c | 48 +++++++++++++++++++++++++++++++--- + sound/soc/codecs/cs35l41.c | 10 ------- + 2 files changed, 44 insertions(+), 14 deletions(-) + +diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c +index a7556fa33cdd..a9c559a676e7 100644 +--- a/sound/soc/codecs/cs35l41-lib.c ++++ b/sound/soc/codecs/cs35l41-lib.c +@@ -1196,7 +1196,8 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 + int enable, struct completion *pll_lock, bool firmware_running) + { + int ret; +- unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status; ++ unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status, pup_pdn_mask; ++ unsigned int pwr_ctl1_val; + struct reg_sequence cs35l41_mdsync_down_seq[] = { + {CS35L41_PWR_CTRL3, 0}, + {CS35L41_GPIO_PAD_CONTROL, 0}, +@@ -1208,6 +1209,12 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 + {CS35L41_PWR_CTRL1, 0x00000001, 3000}, + }; + ++ pup_pdn_mask = enable ? CS35L41_PUP_DONE_MASK : CS35L41_PDN_DONE_MASK; ++ ++ ret = regmap_read(regmap, CS35L41_PWR_CTRL1, &pwr_ctl1_val); ++ if (ret) ++ return ret; ++ + if ((pwr_ctl1_val & CS35L41_GLOBAL_EN_MASK) && enable) { + dev_dbg(dev, "Cannot set Global Enable - already set.\n"); + return 0; +@@ -1252,6 +1259,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 + ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq, + ARRAY_SIZE(cs35l41_mdsync_up_seq)); + } ++ ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, ++ int_status, int_status & pup_pdn_mask, ++ 1000, 100000); ++ if (ret) ++ dev_err(dev, "Enable(%d) failed: %d\n", enable, ret); ++ ++ // Clear PUP/PDN status ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask); + break; + case CS35L41_INT_BOOST: + ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK, +@@ -1260,7 +1276,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 + dev_err(dev, "CS35L41_PWR_CTRL1 set failed: %d\n", ret); + return ret; + } +- usleep_range(3000, 3100); ++ ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, ++ int_status, int_status & pup_pdn_mask, ++ 1000, 100000); ++ if (ret) ++ dev_err(dev, "Enable(%d) failed: %d\n", enable, ret); ++ ++ /* Clear PUP/PDN status */ ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask); + break; + case CS35L41_EXT_BOOST: + case CS35L41_EXT_BOOST_NO_VSPK_SWITCH: +@@ -1271,7 +1295,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 + if (ret) + return ret; + +- usleep_range(3000, 3100); ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, int_status, ++ int_status & CS35L41_PUP_DONE_MASK, 1000, 100000); ++ if (ret) { ++ dev_err(dev, "Failed waiting for CS35L41_PUP_DONE_MASK: %d\n", ret); ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ return ret; ++ } ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, CS35L41_PUP_DONE_MASK); + + if (firmware_running) + ret = cs35l41_set_cspl_mbox_cmd(dev, regmap, +@@ -1292,7 +1324,15 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 + return ret; + } + +- usleep_range(3000, 3100); ++ ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, int_status, ++ int_status & CS35L41_PDN_DONE_MASK, 1000, 100000); ++ if (ret) { ++ dev_err(dev, "Failed waiting for CS35L41_PDN_DONE_MASK: %d\n", ret); ++ /* Lock the test key, it was unlocked during the multi_reg_write */ ++ cs35l41_test_key_lock(dev, regmap); ++ return ret; ++ } ++ regmap_write(regmap, CS35L41_IRQ1_STATUS1, CS35L41_PDN_DONE_MASK); + + /* Test Key is locked here */ + ret = regmap_multi_reg_write(regmap, cs35l41_active_to_safe_end, +diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c +index d4e9c9d9b50a..2b3c36f02edb 100644 +--- a/sound/soc/codecs/cs35l41.c ++++ b/sound/soc/codecs/cs35l41.c +@@ -491,7 +491,6 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w, + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); +- unsigned int val; + int ret = 0; + + switch (event) { +@@ -507,15 +506,6 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w, + ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, + 0, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running); + +- ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1, +- val, val & CS35L41_PDN_DONE_MASK, +- 1000, 100000); +- if (ret) +- dev_warn(cs35l41->dev, "PDN failed: %d\n", ret); +- +- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, +- CS35L41_PDN_DONE_MASK); +- + regmap_multi_reg_write_bypassed(cs35l41->regmap, + cs35l41_pdn_patch, + ARRAY_SIZE(cs35l41_pdn_patch)); +-- +2.41.0 + +From 796af5ec6c6bb2eadf78a96f629e2c7fba11123b Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:08 +0100 +Subject: [PATCH 03/11] ALSA: hda: cs35l41: Check mailbox status of pause + command after firmware load + +Currently, we do not check the return status of the pause command, +immediately after we load firmware. If the pause has failed, +the firmware is not running. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index f9c97270db6f..29f1dce45f1d 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -781,7 +781,12 @@ static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) + goto clean_dsp; + } + +- cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); ++ ret = cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); ++ if (ret) { ++ dev_err(cs35l41->dev, "Error waiting for DSP to pause: %u\n", ret); ++ goto clean_dsp; ++ } ++ + cs35l41->firmware_running = true; + + return 0; +-- +2.41.0 + +From 9684d3a1fbe55573eccd6c7e5f72dd519a4e406b Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:09 +0100 +Subject: [PATCH 04/11] ALSA: hda: cs35l41: Ensure we correctly re-sync regmap + before system suspending. + +In order to properly system suspend, it is necessary to unload the firmware +and ensure the chip is ready for shutdown (if necessary). If the system +is currently in runtime suspend, it is necessary to wake up the device, +and then make it ready. Currently, the wake does not correctly resync +the device, which may mean it cannot suspend correctly. Fix this by +performaing a resync. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 32 +++++++++++++++++++++++++++----- + 1 file changed, 27 insertions(+), 5 deletions(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index 29f1dce45f1d..f42457147ce4 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -574,21 +574,43 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi + rx_slot); + } + +-static void cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) ++static int cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) + { ++ int ret = 0; ++ + mutex_lock(&cs35l41->fw_mutex); + if (cs35l41->firmware_running) { + + regcache_cache_only(cs35l41->regmap, false); + +- cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); ++ ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); ++ if (ret) { ++ dev_warn(cs35l41->dev, "Unable to exit Hibernate."); ++ goto err; ++ } ++ ++ /* Test key needs to be unlocked to allow the OTP settings to re-apply */ ++ cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); ++ ret = regcache_sync(cs35l41->regmap); ++ cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); ++ if (ret) { ++ dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); ++ goto err; ++ } ++ ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) ++ cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); ++ + cs35l41_shutdown_dsp(cs35l41); + cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); +- +- regcache_cache_only(cs35l41->regmap, true); +- regcache_mark_dirty(cs35l41->regmap); + } ++err: ++ regcache_cache_only(cs35l41->regmap, true); ++ regcache_mark_dirty(cs35l41->regmap); ++ + mutex_unlock(&cs35l41->fw_mutex); ++ ++ return ret; + } + + static int cs35l41_system_suspend(struct device *dev) +-- +2.41.0 + +From 05bfc01172a34466e660465922d1cab5b460880f Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:10 +0100 +Subject: [PATCH 05/11] ALSA: hda: cs35l41: Ensure we pass up any errors during + system suspend. + +There are several steps required to put the system into system suspend. +Some of these steps may fail, so the driver should pass up the errors +if they occur. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 17 +++++++++++++---- + 1 file changed, 13 insertions(+), 4 deletions(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index f42457147ce4..d4a11f7b5dbd 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -626,17 +626,22 @@ static int cs35l41_system_suspend(struct device *dev) + } + + ret = pm_runtime_force_suspend(dev); +- if (ret) ++ if (ret) { ++ dev_err(dev, "System Suspend Failed, unable to runtime suspend: %d\n", ret); + return ret; ++ } + + /* Shutdown DSP before system suspend */ +- cs35l41_ready_for_reset(cs35l41); ++ ret = cs35l41_ready_for_reset(cs35l41); ++ ++ if (ret) ++ dev_err(dev, "System Suspend Failed, not ready for Reset: %d\n", ret); + + /* + * Reset GPIO may be shared, so cannot reset here. + * However beyond this point, amps may be powered down. + */ +- return 0; ++ return ret; + } + + static int cs35l41_system_resume(struct device *dev) +@@ -659,9 +664,13 @@ static int cs35l41_system_resume(struct device *dev) + usleep_range(2000, 2100); + + ret = pm_runtime_force_resume(dev); ++ if (ret) { ++ dev_err(dev, "System Resume Failed: Unable to runtime resume: %d\n", ret); ++ return ret; ++ } + + mutex_lock(&cs35l41->fw_mutex); +- if (!ret && cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) { ++ if (cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) { + cs35l41->fw_request_ongoing = true; + schedule_work(&cs35l41->fw_load_work); + } +-- +2.41.0 + +From f352ce9e5389e4746f25bfec33f4e0ee4dcbf690 Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:11 +0100 +Subject: [PATCH 06/11] ALSA: hda: cs35l41: Move Play and Pause into separate + functions + +This allows play and pause to be called from multiple places, +which is necessary for system suspend and resume. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 131 ++++++++++++++++++++++-------------- + 1 file changed, 79 insertions(+), 52 deletions(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index d4a11f7b5dbd..f77583b46b6b 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -483,63 +483,103 @@ static void cs35l41_irq_release(struct cs35l41_hda *cs35l41) + cs35l41->irq_errors = 0; + } + +-static void cs35l41_hda_playback_hook(struct device *dev, int action) ++static void cs35l41_hda_play_start(struct device *dev) + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct regmap *reg = cs35l41->regmap; +- int ret = 0; ++ ++ dev_dbg(dev, "Play (Start)\n"); ++ ++ if (cs35l41->playback_started) { ++ dev_dbg(dev, "Playback already started."); ++ return; ++ } ++ ++ cs35l41->playback_started = true; ++ ++ if (cs35l41->firmware_running) { ++ regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, ++ ARRAY_SIZE(cs35l41_hda_config_dsp)); ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, ++ CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, ++ 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); ++ cs35l41_set_cspl_mbox_cmd(cs35l41->dev, reg, CSPL_MBOX_CMD_RESUME); ++ } else { ++ regmap_multi_reg_write(reg, cs35l41_hda_config, ARRAY_SIZE(cs35l41_hda_config)); ++ } ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) ++ regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001); ++ ++} ++ ++static void cs35l41_hda_play_done(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ struct regmap *reg = cs35l41->regmap; ++ ++ dev_dbg(dev, "Play (Complete)\n"); ++ ++ cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL, ++ cs35l41->firmware_running); ++} ++ ++static void cs35l41_hda_pause_start(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ struct regmap *reg = cs35l41->regmap; ++ ++ 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->firmware_running); ++} ++ ++static void cs35l41_hda_pause_done(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ struct regmap *reg = cs35l41->regmap; ++ ++ dev_dbg(dev, "Pause (Complete)\n"); ++ ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) ++ regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); ++ if (cs35l41->firmware_running) { ++ cs35l41_set_cspl_mbox_cmd(dev, reg, CSPL_MBOX_CMD_PAUSE); ++ regmap_update_bits(reg, CS35L41_PWR_CTRL2, ++ CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, ++ 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); ++ } ++ cs35l41_irq_release(cs35l41); ++ cs35l41->playback_started = false; ++} ++ ++static void cs35l41_hda_playback_hook(struct device *dev, int action) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + + switch (action) { + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + mutex_lock(&cs35l41->fw_mutex); +- cs35l41->playback_started = true; +- if (cs35l41->firmware_running) { +- regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, +- ARRAY_SIZE(cs35l41_hda_config_dsp)); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, +- 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); +- cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, +- CSPL_MBOX_CMD_RESUME); +- } else { +- regmap_multi_reg_write(reg, cs35l41_hda_config, +- ARRAY_SIZE(cs35l41_hda_config)); +- } +- ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, +- CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); +- if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) +- regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001); ++ cs35l41_hda_play_start(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_PREPARE: + mutex_lock(&cs35l41->fw_mutex); +- ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL, +- cs35l41->firmware_running); ++ cs35l41_hda_play_done(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + mutex_lock(&cs35l41->fw_mutex); +- regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); +- ret = cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL, +- cs35l41->firmware_running); ++ cs35l41_hda_pause_start(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLOSE: + mutex_lock(&cs35l41->fw_mutex); +- ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, +- CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); +- if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) +- regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); +- if (cs35l41->firmware_running) { +- cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, +- CSPL_MBOX_CMD_PAUSE); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, +- 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); +- } +- cs35l41_irq_release(cs35l41); +- cs35l41->playback_started = false; ++ cs35l41_hda_pause_done(dev); + mutex_unlock(&cs35l41->fw_mutex); + + pm_runtime_mark_last_busy(dev); +@@ -549,9 +589,6 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action) + dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action); + break; + } +- +- if (ret) +- dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret); + } + + static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsigned int *tx_slot, +@@ -703,18 +740,8 @@ static int cs35l41_runtime_suspend(struct device *dev) + mutex_lock(&cs35l41->fw_mutex); + + if (cs35l41->playback_started) { +- regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute, +- ARRAY_SIZE(cs35l41_hda_mute)); +- cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, +- NULL, cs35l41->firmware_running); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); +- if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) +- regmap_write(cs35l41->regmap, CS35L41_GPIO1_CTRL1, 0x00000001); +- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, +- CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, +- 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); +- cs35l41->playback_started = false; ++ cs35l41_hda_pause_start(dev); ++ cs35l41_hda_pause_done(dev); + } + + if (cs35l41->firmware_running) { +-- +2.41.0 + +From c1bf8ed3a5f3d011276d975c7b1f62039bed160e Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:12 +0100 +Subject: [PATCH 07/11] ALSA: hda: hda_component: Add pre and post playback + hooks to hda_component + +These hooks can be used to add callbacks that would be run before and after +the main playback hooks. These hooks would be called for all amps, before +moving on to the next hook, i.e. pre_playback_hook would be called for +all amps, before the playback_hook is called for all amps, then finally +the post_playback_hook is called for all amps. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/hda_component.h | 2 ++ + sound/pci/hda/patch_realtek.c | 10 +++++++++- + 2 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h +index 534e845b9cd1..f170aec967c1 100644 +--- a/sound/pci/hda/hda_component.h ++++ b/sound/pci/hda/hda_component.h +@@ -15,5 +15,7 @@ struct hda_component { + struct device *dev; + char name[HDA_MAX_NAME_SIZE]; + struct hda_codec *codec; ++ void (*pre_playback_hook)(struct device *dev, int action); + void (*playback_hook)(struct device *dev, int action); ++ void (*post_playback_hook)(struct device *dev, int action); + }; +diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c +index 44fccfb93cff..dff92679ae72 100644 +--- a/sound/pci/hda/patch_realtek.c ++++ b/sound/pci/hda/patch_realtek.c +@@ -6716,9 +6716,17 @@ static void comp_generic_playback_hook(struct hda_pcm_stream *hinfo, struct hda_ + int i; + + for (i = 0; i < HDA_MAX_COMPONENTS; i++) { +- if (spec->comps[i].dev) ++ if (spec->comps[i].dev && spec->comps[i].pre_playback_hook) ++ spec->comps[i].pre_playback_hook(spec->comps[i].dev, action); ++ } ++ for (i = 0; i < HDA_MAX_COMPONENTS; i++) { ++ if (spec->comps[i].dev && spec->comps[i].playback_hook) + spec->comps[i].playback_hook(spec->comps[i].dev, action); + } ++ for (i = 0; i < HDA_MAX_COMPONENTS; i++) { ++ if (spec->comps[i].dev && spec->comps[i].post_playback_hook) ++ spec->comps[i].post_playback_hook(spec->comps[i].dev, action); ++ } + } + + struct cs35l41_dev_name { +-- +2.41.0 + +From 4f3b42e2f126f96b1e512871d7073fb10d9a7283 Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:13 +0100 +Subject: [PATCH 08/11] ALSA: hda: cs35l41: Use pre and post playback hooks + +Use new hooks to ensure separation between play/pause actions, +as required by external boost. + +External Boost on CS35L41 requires the amp to go through a +particular sequence of steps. One of these steps involes +the setting of a GPIO. This GPIO is connected to one or +more of the amps, and it may control the boost for all of +the amps. To ensure that the GPIO is set when it is safe +to do so, and to ensure that boost is ready for the rest of +the sequence to be able to continue, we must ensure that +the each part of the sequence is executed for each amp +before moving on to the next part of the sequence. + +Some of the Play and Pause actions have moved from Open to +Prepare. This is because Open is not guaranteed to be called +again on system resume, whereas Prepare should. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 53 ++++++++++++++++++++++++++++++------- + 1 file changed, 43 insertions(+), 10 deletions(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index f77583b46b6b..a482d4752b3f 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -556,37 +556,68 @@ static void cs35l41_hda_pause_done(struct device *dev) + cs35l41->playback_started = false; + } + ++static void cs35l41_hda_pre_playback_hook(struct device *dev, int action) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ mutex_lock(&cs35l41->fw_mutex); ++ cs35l41_hda_pause_start(dev); ++ mutex_unlock(&cs35l41->fw_mutex); ++ break; ++ default: ++ break; ++ } ++} + static void cs35l41_hda_playback_hook(struct device *dev, int action) + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + + switch (action) { + case HDA_GEN_PCM_ACT_OPEN: ++ /* ++ * All amps must be resumed before we can start playing back. ++ * This ensures, for external boost, that all amps are in AMP_SAFE mode. ++ * Do this in HDA_GEN_PCM_ACT_OPEN, since this is run prior to any of the ++ * other actions. ++ */ + pm_runtime_get_sync(dev); +- mutex_lock(&cs35l41->fw_mutex); +- cs35l41_hda_play_start(dev); +- mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_PREPARE: + mutex_lock(&cs35l41->fw_mutex); +- cs35l41_hda_play_done(dev); ++ cs35l41_hda_play_start(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + mutex_lock(&cs35l41->fw_mutex); +- cs35l41_hda_pause_start(dev); ++ cs35l41_hda_pause_done(dev); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLOSE: +- mutex_lock(&cs35l41->fw_mutex); +- cs35l41_hda_pause_done(dev); +- mutex_unlock(&cs35l41->fw_mutex); +- ++ /* ++ * Playback must be finished for all amps before we start runtime suspend. ++ * This ensures no amps are playing back when we start putting them to sleep. ++ */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + break; + default: +- dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action); ++ break; ++ } ++} ++ ++static void cs35l41_hda_post_playback_hook(struct device *dev, int action) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_PREPARE: ++ mutex_lock(&cs35l41->fw_mutex); ++ cs35l41_hda_play_done(dev); ++ mutex_unlock(&cs35l41->fw_mutex); ++ break; ++ default: + break; + } + } +@@ -1037,6 +1068,8 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas + ret = cs35l41_create_controls(cs35l41); + + comps->playback_hook = cs35l41_hda_playback_hook; ++ comps->pre_playback_hook = cs35l41_hda_pre_playback_hook; ++ comps->post_playback_hook = cs35l41_hda_post_playback_hook; + + mutex_unlock(&cs35l41->fw_mutex); + +-- +2.41.0 + +From 5091ba7ad9ea6a88db464b84b4993cc9e5033a84 Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:14 +0100 +Subject: [PATCH 09/11] ALSA: hda: cs35l41: Rework System Suspend to ensure + correct call separation + +In order to correctly pause audio on suspend, amps using external boost +require parts of the pause sequence to be called for all amps before moving +on to the next steps. +For example, as part of pausing the audio, the VSPK GPIO must be disabled, +but since this GPIO is controlled by one amp, but controls the boost for +all amps, it is required to separate the calls. +During playback this is achieved by using the pre and post playback hooks, +however during system suspend, this is not possible, so to separate the +calls, we use both the .prepare and .suspend calls to pause the audio. + +Currently, for this reason, we do not restart audio on system resume. +However, we can support this by relying on the playback hook to resume +playback after system suspend. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 40 ++++++++++++++++++++++++++++++++----- + 1 file changed, 35 insertions(+), 5 deletions(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index a482d4752b3f..70aa819cfbd6 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -595,6 +595,15 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action) + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLOSE: ++ mutex_lock(&cs35l41->fw_mutex); ++ if (!cs35l41->firmware_running && cs35l41->request_fw_load && ++ !cs35l41->fw_request_ongoing) { ++ dev_info(dev, "Requesting Firmware Load after HDA_GEN_PCM_ACT_CLOSE\n"); ++ cs35l41->fw_request_ongoing = true; ++ schedule_work(&cs35l41->fw_load_work); ++ } ++ mutex_unlock(&cs35l41->fw_mutex); ++ + /* + * Playback must be finished for all amps before we start runtime suspend. + * This ensures no amps are playing back when we start putting them to sleep. +@@ -681,6 +690,25 @@ static int cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) + return ret; + } + ++static int cs35l41_system_suspend_prep(struct device *dev) ++{ ++ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); ++ ++ dev_dbg(cs35l41->dev, "System Suspend Prepare\n"); ++ ++ if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) { ++ dev_err_once(cs35l41->dev, "System Suspend not supported\n"); ++ return 0; /* don't block the whole system suspend */ ++ } ++ ++ mutex_lock(&cs35l41->fw_mutex); ++ if (cs35l41->playback_started) ++ cs35l41_hda_pause_start(dev); ++ mutex_unlock(&cs35l41->fw_mutex); ++ ++ return 0; ++} ++ + static int cs35l41_system_suspend(struct device *dev) + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); +@@ -693,6 +721,11 @@ static int cs35l41_system_suspend(struct device *dev) + return 0; /* don't block the whole system suspend */ + } + ++ mutex_lock(&cs35l41->fw_mutex); ++ if (cs35l41->playback_started) ++ cs35l41_hda_pause_done(dev); ++ mutex_unlock(&cs35l41->fw_mutex); ++ + ret = pm_runtime_force_suspend(dev); + if (ret) { + dev_err(dev, "System Suspend Failed, unable to runtime suspend: %d\n", ret); +@@ -738,6 +771,7 @@ static int cs35l41_system_resume(struct device *dev) + } + + mutex_lock(&cs35l41->fw_mutex); ++ + if (cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) { + cs35l41->fw_request_ongoing = true; + schedule_work(&cs35l41->fw_load_work); +@@ -770,11 +804,6 @@ static int cs35l41_runtime_suspend(struct device *dev) + + mutex_lock(&cs35l41->fw_mutex); + +- if (cs35l41->playback_started) { +- cs35l41_hda_pause_start(dev); +- cs35l41_hda_pause_done(dev); +- } +- + if (cs35l41->firmware_running) { + ret = cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, + cs35l41->hw_cfg.bst_type); +@@ -1641,6 +1670,7 @@ EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41); + const struct dev_pm_ops cs35l41_hda_pm_ops = { + RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, + cs35l41_runtime_idle) ++ .prepare = cs35l41_system_suspend_prep, + SYSTEM_SLEEP_PM_OPS(cs35l41_system_suspend, cs35l41_system_resume) + }; + EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41); +-- +2.41.0 + +From 74c165859e33b62888b93891d82680350b9a615f Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:15 +0100 +Subject: [PATCH 10/11] ALSA: hda: cs35l41: Add device_link between HDA and + cs35l41_hda + +To ensure consistency between the HDA core and the CS35L41 HDA +driver, add a device_link between them. This ensures that the +HDA core will suspend first, and resume second, meaning the +amp driver will not miss any events from the playback hook from +the HDA core during system suspend and resume. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index 70aa819cfbd6..175378cdf9df 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -1063,6 +1063,7 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct hda_component *comps = master_data; ++ unsigned int sleep_flags; + int ret = 0; + + if (!comps || cs35l41->index < 0 || cs35l41->index >= HDA_MAX_COMPONENTS) +@@ -1102,6 +1103,11 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas + + mutex_unlock(&cs35l41->fw_mutex); + ++ sleep_flags = lock_system_sleep(); ++ if (!device_link_add(&comps->codec->core.dev, cs35l41->dev, DL_FLAG_STATELESS)) ++ dev_warn(dev, "Unable to create device link\n"); ++ unlock_system_sleep(sleep_flags); ++ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + +@@ -1112,9 +1118,14 @@ static void cs35l41_hda_unbind(struct device *dev, struct device *master, void * + { + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct hda_component *comps = master_data; ++ unsigned int sleep_flags; + +- if (comps[cs35l41->index].dev == dev) ++ if (comps[cs35l41->index].dev == dev) { + memset(&comps[cs35l41->index], 0, sizeof(*comps)); ++ sleep_flags = lock_system_sleep(); ++ device_link_remove(&comps->codec->core.dev, cs35l41->dev); ++ unlock_system_sleep(sleep_flags); ++ } + } + + static const struct component_ops cs35l41_hda_comp_ops = { +-- +2.41.0 + +From 6f1a7b41a626a567fcfe915e9dbe3aea34b6c3ec Mon Sep 17 00:00:00 2001 +From: Stefan Binding +Date: Fri, 21 Jul 2023 16:18:16 +0100 +Subject: [PATCH 11/11] ALSA: hda: cs35l41: Ensure amp is only unmuted during + playback + +Currently we only mute after playback has finished, and unmute +prior to setting global enable. To prevent any possible pops +and clicks, mute at probe, and then only unmute after global +enable is set. + +Signed-off-by: Stefan Binding +--- + sound/pci/hda/cs35l41_hda.c | 22 ++++++++++++++++++++-- + 1 file changed, 20 insertions(+), 2 deletions(-) + +diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c +index 175378cdf9df..98feb5ccd586 100644 +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -58,8 +58,6 @@ static const struct reg_sequence cs35l41_hda_config[] = { + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000020 }, // DSP1RX5 SRC = ERRVOL +- { CS35L41_AMP_DIG_VOL_CTRL, 0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM 0.0 dB +- { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB + }; + + static const struct reg_sequence cs35l41_hda_config_dsp[] = { +@@ -82,6 +80,14 @@ static const struct reg_sequence cs35l41_hda_config_dsp[] = { + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000029 }, // DSP1RX5 SRC = VBSTMON ++}; ++ ++static const struct reg_sequence cs35l41_hda_unmute[] = { ++ { CS35L41_AMP_DIG_VOL_CTRL, 0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM 0.0 dB ++ { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB ++}; ++ ++static const struct reg_sequence cs35l41_hda_unmute_dsp[] = { + { CS35L41_AMP_DIG_VOL_CTRL, 0x00008000 }, // AMP_HPF_PCM_EN = 1, AMP_VOL_PCM 0.0 dB + { CS35L41_AMP_GAIN_CTRL, 0x00000233 }, // AMP_GAIN_PCM = 17.5dB AMP_GAIN_PDM = 19.5dB + }; +@@ -522,6 +528,13 @@ static void cs35l41_hda_play_done(struct device *dev) + + cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL, + cs35l41->firmware_running); ++ if (cs35l41->firmware_running) { ++ regmap_multi_reg_write(reg, cs35l41_hda_unmute_dsp, ++ ARRAY_SIZE(cs35l41_hda_unmute_dsp)); ++ } else { ++ regmap_multi_reg_write(reg, cs35l41_hda_unmute, ++ ARRAY_SIZE(cs35l41_hda_unmute)); ++ } + } + + static void cs35l41_hda_pause_start(struct device *dev) +@@ -1616,6 +1629,11 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i + if (ret) + goto err; + ++ ret = regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute, ++ ARRAY_SIZE(cs35l41_hda_mute)); ++ if (ret) ++ goto err; ++ + INIT_WORK(&cs35l41->fw_load_work, cs35l41_fw_load_work); + mutex_init(&cs35l41->fw_mutex); + +-- +2.41.0 + 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/linux-surface.patch b/patches/nobara/linux-surface.patch new file mode 100644 index 0000000..f22f6b1 --- /dev/null +++ b/patches/nobara/linux-surface.patch @@ -0,0 +1,8136 @@ +From 38f9bee60e9c7c742358e862c6c9422964f1d41a 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 ca4602bcc7de..490b9731068a 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 a506d940a2ea..2766484b8d2c 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3717,6 +3717,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 cdcbf04b8832..958305779b12 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"), ++ }, ++ }, + { } + }; + +-- +2.42.0 + +From b8dec23a399dc5deb88ac30d71dd6270c1794ba0 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 6697132ecc97..f06b4ebc5bd8 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); ++ + /* 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 dd6d21f1dbfd..f46b06f8d643 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 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 d6ff964aec5b..5d30ae39d65e 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" + + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.42.0 + +From 44360e255e0cfebd6e0584e75e13c2cc69c7d41a 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 f06b4ebc5bd8..07f13b52ddb9 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) + { + 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 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* 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; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d643..99b024ecbade 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 5d30ae39d65e..c14eb56eb911 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 9203ef88e0067699c993d4715c48ff9deaea92be 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 dfdfb72d350f..44ef02efba46 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) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -468,6 +469,7 @@ static const struct usb_device_id blacklist_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 }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, +@@ -4317,6 +4319,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.42.0 + +From 4ec07cef92d6b7ce42ccc5e1b0e73678cf023dcb 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 6cdb225b7eac..19c036751fb1 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 6e7929685df7d87379ad03942e364d7e22122624 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 bdc65d50b945..08723c01d727 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 676d566f38dd..6b37dd1f8b2a 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 390bee34de9e6382d9a0e0af9e515cb1f114b210 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 4a9d9e82847d..6387f3a6eccf 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -37,6 +37,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) +@@ -287,12 +289,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; + +@@ -2548,6 +2552,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; +@@ -2855,6 +2862,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); +@@ -4755,6 +4765,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); +@@ -4790,6 +4811,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 13b6ad2c284cf321144851a527dda96e6d4b9064 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 dc456c86e956..b35203b9a7d8 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1335,4 +1335,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" + + source "drivers/hid/surface-hid/Kconfig" + ++source "drivers/hid/ipts/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 7a9e160158f7..f58610f27216 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -168,3 +168,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/ +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 000000000000..297401bd388d +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config HID_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 000000000000..883896f68e6a +--- /dev/null ++++ b/drivers/hid/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_HID_IPTS) += ipts.o ++ipts-objs := cmd.o ++ipts-objs += control.o ++ipts-objs += eds1.o ++ipts-objs += eds2.o ++ipts-objs += hid.o ++ipts-objs += main.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += thread.o +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 000000000000..63a4934bbc5f +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "mei.h" ++#include "spec-device.h" ++ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ /* ++ * In a response, the command code will have the most significant bit flipped to 1. ++ * If code is passed to ipts_mei_recv as is, no messages will be received. ++ */ ++ ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); ++ if (ret < 0) ++ return ret; ++ ++ dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); ++ ++ /* ++ * Some devices will always return this error. ++ * It is allowed to ignore it and to try continuing. ++ */ ++ if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) ++ rsp->status = IPTS_STATUS_SUCCESS; ++ ++ return 0; ++} ++ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) ++{ ++ struct ipts_command cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.cmd = code; ++ ++ if (data && size > 0) ++ memcpy(cmd.payload, data, size); ++ ++ 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 --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 000000000000..2b4079075b64 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h +@@ -0,0 +1,60 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CMD_H ++#define IPTS_CMD_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++/* ++ * The default timeout for receiving responses ++ */ ++#define IPTS_CMD_DEFAULT_TIMEOUT 1000 ++ ++/** ++ * ipts_cmd_recv_timeout() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp, u64 timeout); ++ ++/** ++ * ipts_cmd_recv() - Receives a response to a command. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command / response. ++ * @rsp: The address that the received response will be copied to. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); ++} ++ ++/** ++ * ipts_cmd_send() - Executes a command on the device. ++ * @ipts: The IPTS driver context. ++ * @code: The type of the command to execute. ++ * @data: The payload containing parameters for the command. ++ * @size: The size of the payload. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); ++ ++#endif /* IPTS_CMD_H */ +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 000000000000..ba33259f1f7c +--- /dev/null ++++ b/drivers/hid/ipts/context.h +@@ -0,0 +1,52 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTEXT_H ++#define IPTS_CONTEXT_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mei.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++struct ipts_context { ++ struct device *dev; ++ struct ipts_mei mei; ++ ++ enum ipts_mode mode; ++ ++ /* ++ * Prevents concurrent GET_FEATURE reports. ++ */ ++ struct mutex feature_lock; ++ struct completion feature_event; ++ ++ /* ++ * These are not inside of struct ipts_resources ++ * because they don't own the memory they point to. ++ */ ++ struct ipts_buffer feature_report; ++ struct ipts_buffer descriptor; ++ ++ bool hid_active; ++ struct hid_device *hid; ++ ++ struct ipts_device_info info; ++ struct ipts_resources resources; ++ ++ struct ipts_thread receiver_loop; ++}; ++ ++#endif /* IPTS_CONTEXT_H */ +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 000000000000..5360842d260b +--- /dev/null ++++ b/drivers/hid/ipts/control.c +@@ -0,0 +1,486 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "hid.h" ++#include "receiver.h" ++#include "resources.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!info) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ memcpy(info, rsp.payload, sizeof(*info)); ++ return 0; ++} ++ ++static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ struct ipts_set_mode cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.mode = mode; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) ++{ ++ int i = 0; ++ int ret = 0; ++ struct ipts_mem_window cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); ++ cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); ++ cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); ++ cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); ++ ++ cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); ++ cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++static int ipts_control_get_descriptor(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_data_header *header = NULL; ++ struct ipts_get_descriptor cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.descriptor.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); ++ ++ cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); ++ cmd.magic = 8; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ header = (struct ipts_data_header *)ipts->resources.descriptor.address; ++ ++ if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { ++ ipts->descriptor.address = &header->data[8]; ++ ipts->descriptor.size = header->size - 8; ++ ++ return 0; ++ } ++ ++ return -ENODATA; ++} ++ ++int ipts_control_request_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_quiesce_io cmd = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); ++ if (ret) ++ dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_flush(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_request_data(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); ++ ++ return ret; ++} ++ ++int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) ++{ ++ int ret = 0; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!shutdown) ++ ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); ++ else ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); ++ ++ if (ret) { ++ if (ret != -EAGAIN) ++ dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); ++ ++ return ret; ++ } ++ ++ /* ++ * During shutdown, it is possible that the sensor has already been disabled. ++ */ ++ if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) ++ return 0; ++ ++ if (rsp.status == IPTS_STATUS_TIMEOUT) ++ return -EAGAIN; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ int ret = 0; ++ struct ipts_feedback cmd = { 0 }; ++ struct ipts_response rsp = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ cmd.buffer = buffer; ++ ++ ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); ++ if (ret) { ++ dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * We don't know what feedback data looks like so we are sending zeros. ++ * See also ipts_control_refill_buffer. ++ */ ++ if (rsp.status == IPTS_STATUS_INVALID_PARAMS) ++ return 0; ++ ++ if (rsp.status != IPTS_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); ++ return -EBADR; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size) ++{ ++ struct ipts_feedback_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->resources.hid2me.address) ++ return -EFAULT; ++ ++ memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); ++ header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; ++ ++ header->cmd_type = cmd; ++ header->data_type = type; ++ header->size = size; ++ header->buffer = IPTS_HID2ME_BUFFER; ++ ++ if (size + sizeof(*header) > ipts->resources.hid2me.size) ++ return -EINVAL; ++ ++ if (data && size > 0) ++ memcpy(header->payload, data, size); ++ ++ return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ struct ipts_device_info info = { 0 }; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ++ ret = ipts_control_get_device_info(ipts, &info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to get device info: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->info = info; ++ ++ ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); ++ return ret; ++ } ++ ++ dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); ++ ++ /* ++ * Handle newer devices ++ */ ++ if (info.intf_eds > 1) { ++ /* ++ * Fetching the descriptor will only work on newer devices. ++ * For older devices, a fallback descriptor will be used. ++ */ ++ ret = ipts_control_get_descriptor(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Newer devices can be directly initialized in polling mode. ++ */ ++ ipts->mode = IPTS_MODE_POLL; ++ } ++ ++ ret = ipts_control_set_mode(ipts, ipts->mode); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set mode: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_set_mem_window(ipts, &ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_receiver_start(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ return ret; ++ } ++ ++ ipts_hid_enable(ipts); ++ ++ ret = ipts_hid_init(ipts, info); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int _ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ipts_hid_disable(ipts); ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ++ ret = ipts_receiver_stop(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_resources_free(&ipts->resources); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free resources: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_hid_free(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ ret = _ipts_control_stop(ipts); ++ if (ret) ++ return ret; ++ ++ /* ++ * Wait a second to give the sensor time to fully shut down. ++ */ ++ msleep(1000); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 000000000000..26629c5144ed +--- /dev/null ++++ b/drivers/hid/ipts/control.h +@@ -0,0 +1,126 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_CONTROL_H ++#define IPTS_CONTROL_H ++ ++#include ++ ++#include "context.h" ++#include "spec-data.h" ++#include "spec-device.h" ++ ++/** ++ * ipts_control_request_flush() - Stop the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Runs the command to stop the data flow on the device. ++ * All outstanding data needs to be acknowledged using feedback before the command will return. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Wait until data flow has been stopped. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_wait_flush(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_flush() - Notify the device that the driver can receive new data. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_request_data(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_wait_data() - Wait until new data is available. ++ * @ipts: The IPTS driver context. ++ * @block: Whether to block execution until data is available. ++ * ++ * In poll mode, this function will never return while the data flow is active. Instead, ++ * the poll will be incremented when new data is available. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. ++ */ ++int ipts_control_wait_data(struct ipts_context *ipts, bool block); ++ ++/** ++ * ipts_control_send_feedback() - Submits a feedback buffer to the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The ID of the buffer containing feedback data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++ ++/** ++ * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. ++ * @ipts: The IPTS driver context. ++ * @cmd: The command that will be run on the device. ++ * @type: The type of the payload that is sent to the device. ++ * @data: The payload of the feedback command. ++ * @size: The size of the payload. ++ * ++ * HID2ME feedback is a special type of feedback, because it allows interfacing with ++ * the HID API of the device at any moment, without requiring a buffer that has to ++ * be acknowledged. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, ++ enum ipts_feedback_data_type type, void *data, size_t size); ++ ++/** ++ * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer that has been processed and can be refilled. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) ++{ ++ /* ++ * IPTS expects structured data in the feedback buffer matching the buffer that will be ++ * refilled. We don't know what that data looks like, so we just keep the buffer empty. ++ * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. ++ * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling ++ * the buffers on some devices. ++ */ ++ ++ return ipts_control_send_feedback(ipts, buffer); ++} ++ ++/** ++ * ipts_control_start() - Initialized the device and starts the data flow. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_start(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_stop() - Stops the data flow and resets the device. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_stop(struct ipts_context *ipts); ++ ++/** ++ * ipts_control_restart() - Stops the device and starts it again. ++ * @ipts: The IPTS driver context. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_control_restart(struct ipts_context *ipts); ++ ++#endif /* IPTS_CONTROL_H */ +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 000000000000..307438c7c80c +--- /dev/null ++++ b/drivers/hid/ipts/desc.h +@@ -0,0 +1,80 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_DESC_H ++#define IPTS_DESC_H ++ ++#include ++ ++#define IPTS_HID_REPORT_SINGLETOUCH 64 ++#define IPTS_HID_REPORT_DATA 65 ++#define IPTS_HID_REPORT_SET_MODE 66 ++ ++#define IPTS_HID_REPORT_DATA_SIZE 7485 ++ ++/* ++ * HID descriptor for singletouch data. ++ * This descriptor should be present on all IPTS devices. ++ */ ++static const u8 ipts_singletouch_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x04, /* Usage (Touchscreen), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x40, /* Report ID (64), */ ++ 0x09, 0x42, /* Usage (Tip Switch), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x95, 0x07, /* Report Count (7), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x30, /* Usage (X), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xA4, /* Push, */ ++ 0x55, 0x0E, /* Unit Exponent (14), */ ++ 0x65, 0x11, /* Unit (Centimeter), */ ++ 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x31, /* Usage (Y), */ ++ 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ ++ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0xB4, /* Pop, */ ++ 0xC0, /* End Collection */ ++}; ++ ++/* ++ * Fallback HID descriptor for older devices that do not have ++ * the ability to query their HID descriptor. ++ */ ++static const u8 ipts_fallback_descriptor[] = { ++ 0x05, 0x0D, /* Usage Page (Digitizer), */ ++ 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x41, /* Report ID (65), */ ++ 0x09, 0x56, /* Usage (Scan Time), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x09, 0x61, /* Usage (Gesture Char Quality), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x96, 0x3D, 0x1D, /* Report Count (7485), */ ++ 0x81, 0x03, /* Input (Constant, Variable), */ ++ 0x85, 0x42, /* Report ID (66), */ ++ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ ++ 0x09, 0xC8, /* Usage (C8h), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x01, /* Report Count (1), */ ++ 0xB1, 0x02, /* Feature (Variable), */ ++ 0xC0, /* End Collection, */ ++}; ++ ++#endif /* IPTS_DESC_H */ +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 000000000000..ecbb3a8bdaf6 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "spec-device.h" ++ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, ++ sizeof(ipts_fallback_descriptor)); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == mode) ++ return 0; ++ ++ ipts->mode = mode; ++ ++ ret = ipts_control_restart(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); ++ ++ return ret; ++} ++ ++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) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_id != IPTS_HID_REPORT_SET_MODE) ++ return -EIO; ++ ++ if (report_type != HID_FEATURE_REPORT) ++ return -EIO; ++ ++ if (size != 2) ++ return -EINVAL; ++ ++ /* ++ * Implement mode switching report for older devices without native HID support. ++ */ ++ ++ if (request_type == HID_REQ_GET_REPORT) { ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ buffer[1] = ipts->mode; ++ } else if (request_type == HID_REQ_SET_REPORT) { ++ return ipts_eds1_switch_mode(ipts, buffer[1]); ++ } else { ++ return -EIO; ++ } ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 000000000000..eeeb6575e3e8 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds1_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++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 --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 000000000000..198dc65d7887 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c +@@ -0,0 +1,144 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "desc.h" ++#include "spec-data.h" ++ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) ++{ ++ size_t size = 0; ++ u8 *buffer = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!desc_buffer) ++ return -EFAULT; ++ ++ if (!desc_size) ++ return -EFAULT; ++ ++ size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; ++ ++ buffer = kzalloc(size, GFP_KERNEL); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); ++ memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, ++ ipts->descriptor.size); ++ ++ *desc_size = size; ++ *desc_buffer = buffer; ++ ++ return 0; ++} ++ ++static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ mutex_lock(&ipts->feature_lock); ++ ++ memset(buffer, 0, size); ++ buffer[0] = report_id; ++ ++ memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); ++ reinit_completion(&ipts->feature_event); ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ goto out; ++ } ++ ++ ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); ++ if (ret == 0) { ++ dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (!ipts->feature_report.address) { ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ if (ipts->feature_report.size > size) { ++ ret = -ETOOSMALL; ++ goto out; ++ } ++ ++ ret = ipts->feature_report.size; ++ memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); ++ ++out: ++ mutex_unlock(&ipts->feature_lock); ++ return ret; ++} ++ ++static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, ++ enum ipts_feedback_data_type type) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ buffer[0] = report_id; ++ ++ ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); ++ ++ return ret; ++} ++ ++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) ++{ ++ enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; ++ else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) ++ feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ else ++ return -EIO; ++ ++ if (request_type == HID_REQ_GET_REPORT) ++ return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); ++ else ++ return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); ++} +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 000000000000..064e3716907a +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h +@@ -0,0 +1,35 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++ ++#include "context.h" ++ ++/** ++ * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. ++ * @ipts: The IPTS driver context. ++ * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. ++ * @desc_size: A pointer to the location where the size of the allocated buffer is stored. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); ++ ++/** ++ * ipts_eds2_raw_request() - Executes an output or feature report on the device. ++ * @ipts: The IPTS driver context. ++ * @buffer: The buffer containing the report. ++ * @size: The size of the buffer. ++ * @report_id: The HID report ID. ++ * @report_type: Whether this report is an output or a feature report. ++ * @request_type: Whether this report requests or sends data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++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 --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 000000000000..e34a1a4f9fa7 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "desc.h" ++#include "eds1.h" ++#include "eds2.h" ++#include "hid.h" ++#include "spec-data.h" ++#include "spec-hid.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, true); ++} ++ ++void ipts_hid_disable(struct ipts_context *ipts) ++{ ++ WRITE_ONCE(ipts->hid_active, false); ++} ++ ++static int ipts_hid_start(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void ipts_hid_stop(struct hid_device *hid) ++{ ++} ++ ++static int ipts_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ u8 *buffer = NULL; ++ size_t size = 0; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) ++ ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); ++ else ++ ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ ret = hid_parse_report(hid, buffer, size); ++ kfree(buffer); ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, ++ size_t size, unsigned char report_type, int request_type) ++{ ++ struct ipts_context *ipts = NULL; ++ ++ if (!hid) ++ return -ENODEV; ++ ++ ipts = hid->driver_data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ if (ipts->info.intf_eds == 1) { ++ return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } else { ++ return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, ++ request_type); ++ } ++} ++ ++static struct hid_ll_driver ipts_hid_driver = { ++ .start = ipts_hid_start, ++ .stop = ipts_hid_stop, ++ .open = ipts_hid_start, ++ .close = ipts_hid_stop, ++ .parse = ipts_hid_parse, ++ .raw_request = ipts_hid_raw_request, ++}; ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) ++{ ++ u8 *temp = NULL; ++ struct ipts_hid_header *frame = NULL; ++ struct ipts_data_header *header = NULL; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return -ENODEV; ++ ++ if (!READ_ONCE(ipts->hid_active)) ++ return -ENODEV; ++ ++ header = (struct ipts_data_header *)ipts->resources.data[buffer].address; ++ ++ temp = ipts->resources.report.address; ++ memset(temp, 0, ipts->resources.report.size); ++ ++ if (!header) ++ return -EFAULT; ++ ++ if (header->size == 0) ++ return 0; ++ ++ if (header->type == IPTS_DATA_TYPE_HID) ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); ++ ++ if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { ++ ipts->feature_report.address = header->data; ++ ipts->feature_report.size = header->size; ++ ++ complete_all(&ipts->feature_event); ++ return 0; ++ } ++ ++ if (header->type != IPTS_DATA_TYPE_FRAME) ++ return 0; ++ ++ if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) ++ return -ERANGE; ++ ++ /* ++ * Synthesize a HID report matching the devices that natively send HID reports ++ */ ++ temp[0] = IPTS_HID_REPORT_DATA; ++ ++ frame = (struct ipts_hid_header *)&temp[3]; ++ frame->type = IPTS_HID_FRAME_TYPE_RAW; ++ frame->size = header->size + sizeof(*frame); ++ ++ memcpy(frame->data, header->data, header->size); ++ ++ return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); ++} ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->hid) ++ return 0; ++ ++ ipts->hid = hid_allocate_device(); ++ if (IS_ERR(ipts->hid)) { ++ int err = PTR_ERR(ipts->hid); ++ ++ dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); ++ return err; ++ } ++ ++ ipts->hid->driver_data = ipts; ++ ipts->hid->dev.parent = ipts->dev; ++ ipts->hid->ll_driver = &ipts_hid_driver; ++ ++ ipts->hid->vendor = info.vendor; ++ ipts->hid->product = info.product; ++ ipts->hid->group = HID_GROUP_GENERIC; ++ ++ snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, ++ info.product); ++ ++ ret = hid_add_device(ipts->hid); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); ++ ipts_hid_free(ipts); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_hid_free(struct ipts_context *ipts) ++{ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (!ipts->hid) ++ return 0; ++ ++ hid_destroy_device(ipts->hid); ++ ipts->hid = NULL; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 000000000000..1ebe77447903 +--- /dev/null ++++ b/drivers/hid/ipts/hid.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2022-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_HID_H ++#define IPTS_HID_H ++ ++#include ++ ++#include "context.h" ++#include "spec-device.h" ++ ++void ipts_hid_enable(struct ipts_context *ipts); ++void ipts_hid_disable(struct ipts_context *ipts); ++ ++int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); ++ ++int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); ++int ipts_hid_free(struct ipts_context *ipts); ++ ++#endif /* IPTS_HID_H */ +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 000000000000..fb5b5c13ee3e +--- /dev/null ++++ b/drivers/hid/ipts/main.c +@@ -0,0 +1,126 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "mei.h" ++#include "receiver.h" ++#include "spec-device.h" ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ if (!cldev) ++ return -EFAULT; ++ ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ ret = ipts_set_dma_mask(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ret = ipts_mei_init(&ipts->mei, cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); ++ return ret; ++ } ++ ++ ipts->dev = &cldev->dev; ++ ipts->mode = IPTS_MODE_EVENT; ++ ++ mutex_init(&ipts->feature_lock); ++ init_completion(&ipts->feature_event); ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ret = ipts_control_start(ipts); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipts_remove(struct mei_cl_device *cldev) ++{ ++ int ret = 0; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = ipts_control_stop(ipts); ++ if (ret) ++ dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); ++ ++ mei_cldev_disable(cldev); ++} ++ ++static struct mei_cl_device_id ipts_device_id_table[] = { ++ { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id_table); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id_table, ++ .name = "ipts", ++ .probe = ipts_probe, ++ .remove = ipts_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 000000000000..1e0395ceae4a +--- /dev/null ++++ b/drivers/hid/ipts/mei.c +@@ -0,0 +1,188 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "mei.h" ++ ++static void locked_list_add(struct list_head *new, struct list_head *head, ++ struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_add(new, head); ++ up_write(lock); ++} ++ ++static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) ++{ ++ down_write(lock); ++ list_del(entry); ++ up_write(lock); ++} ++ ++static void ipts_mei_incoming(struct mei_cl_device *cldev) ++{ ++ ssize_t ret = 0; ++ struct ipts_mei_message *entry = NULL; ++ struct ipts_context *ipts = NULL; ++ ++ if (!cldev) { ++ pr_err("MEI device is NULL!"); ++ return; ++ } ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ if (!ipts) { ++ pr_err("IPTS driver context is NULL!"); ++ return; ++ } ++ ++ entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); ++ if (!entry) ++ return; ++ ++ INIT_LIST_HEAD(&entry->list); ++ ++ do { ++ ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) { ++ dev_err(ipts->dev, "Error while reading response: %ld\n", ret); ++ return; ++ } ++ ++ if (ret == 0) { ++ dev_err(ipts->dev, "Received empty response\n"); ++ return; ++ } ++ ++ locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); ++ wake_up_all(&ipts->mei.message_queue); ++} ++ ++static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, ++ struct ipts_response *rsp) ++{ ++ struct ipts_mei_message *entry = NULL; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!rsp) ++ return -EFAULT; ++ ++ down_read(&mei->message_lock); ++ ++ /* ++ * Iterate over the list of received messages, and check if there is one ++ * matching the requested command code. ++ */ ++ list_for_each_entry(entry, &mei->messages, list) { ++ if (entry->rsp.cmd == code) ++ break; ++ } ++ ++ up_read(&mei->message_lock); ++ ++ /* ++ * If entry is not the list head, this means that the loop above has been stopped early, ++ * and that we found a matching element. We drop the message from the list and return it. ++ */ ++ if (!list_entry_is_head(entry, &mei->messages, list)) { ++ locked_list_del(&entry->list, &mei->message_lock); ++ ++ *rsp = entry->rsp; ++ devm_kfree(&mei->cldev->dev, entry); ++ ++ return 0; ++ } ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ /* ++ * A timeout of 0 means check and return immideately. ++ */ ++ if (timeout == 0) ++ return ipts_mei_search(mei, code, rsp); ++ ++ /* ++ * A timeout of less than 0 means to wait forever. ++ */ ++ if (timeout < 0) { ++ wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); ++ return 0; ++ } ++ ++ ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, ++ msecs_to_jiffies(timeout)); ++ ++ if (ret > 0) ++ return 0; ++ ++ return -EAGAIN; ++} ++ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) ++{ ++ int ret = 0; ++ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!mei->cldev) ++ return -EFAULT; ++ ++ if (!data) ++ return -EFAULT; ++ ++ do { ++ ret = mei_cldev_send(mei->cldev, (u8 *)data, length); ++ } while (ret == -EINTR); ++ ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) ++{ ++ if (!mei) ++ return -EFAULT; ++ ++ if (!cldev) ++ return -EFAULT; ++ ++ mei->cldev = cldev; ++ ++ INIT_LIST_HEAD(&mei->messages); ++ init_waitqueue_head(&mei->message_queue); ++ init_rwsem(&mei->message_lock); ++ ++ mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 000000000000..973bade6b0fd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_MEI_H ++#define IPTS_MEI_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_mei_message { ++ struct list_head list; ++ struct ipts_response rsp; ++}; ++ ++struct ipts_mei { ++ struct mei_cl_device *cldev; ++ ++ struct list_head messages; ++ ++ wait_queue_head_t message_queue; ++ struct rw_semaphore message_lock; ++}; ++ ++/** ++ * ipts_mei_recv() - Receive data from a MEI device. ++ * @mei: The IPTS MEI device context. ++ * @code: The IPTS command code to look for. ++ * @rsp: The address that the received data will be copied to. ++ * @timeout: How many milliseconds the function will wait at most. ++ * ++ * A negative timeout means to wait forever. ++ * ++ * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. ++ */ ++int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, ++ u64 timeout); ++ ++/** ++ * ipts_mei_send() - Send data to a MEI device. ++ * @ipts: The IPTS MEI device context. ++ * @data: The data to send. ++ * @size: The size of the data. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); ++ ++/** ++ * ipts_mei_init() - Initialize the MEI device context. ++ * @mei: The MEI device context to initialize. ++ * @cldev: The MEI device the context will be bound to. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); ++ ++#endif /* IPTS_MEI_H */ +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 000000000000..ef66c3c9db80 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c +@@ -0,0 +1,250 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "resources.h" ++#include "spec-device.h" ++#include "thread.h" ++ ++static void ipts_receiver_next_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ *doorbell = *doorbell + 1; ++} ++ ++static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) ++{ ++ u32 *doorbell = (u32 *)ipts->resources.doorbell.address; ++ return *doorbell; ++} ++ ++static void ipts_receiver_backoff(time64_t last, u32 n) ++{ ++ /* ++ * If the last change was less than n seconds ago, ++ * sleep for a shorter period so that new data can be ++ * processed quickly. If there was no change for more than ++ * n seconds, sleep longer to avoid wasting CPU cycles. ++ */ ++ if (last + n > ktime_get_seconds()) ++ usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); ++ else ++ msleep(200); ++} ++ ++static int ipts_receiver_event_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in event mode\n"); ++ ++ while (!ipts_thread_should_stop(thread)) { ++ int i = 0; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_control_wait_data(ipts, false); ++ if (ret == -EAGAIN) ++ break; ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ continue; ++ } ++ ++ buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; ++ ipts_receiver_next_doorbell(ipts); ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ ret = ipts_control_request_data(ipts); ++ if (ret) ++ dev_err(ipts->dev, "Failed to request data: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ } ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++static int ipts_receiver_poll_loop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ u32 buffer = 0; ++ ++ u32 doorbell = 0; ++ u32 lastdb = 0; ++ ++ struct ipts_context *ipts = NULL; ++ time64_t last = ktime_get_seconds(); ++ ++ if (!thread) ++ return -EFAULT; ++ ++ ipts = thread->data; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ dev_info(ipts->dev, "IPTS running in poll mode\n"); ++ ++ while (true) { ++ if (ipts_thread_should_stop(thread)) { ++ ret = ipts_control_request_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to request flush: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ doorbell = ipts_receiver_current_doorbell(ipts); ++ ++ /* ++ * After filling up one of the data buffers, IPTS will increment ++ * the doorbell. The value of the doorbell stands for the *next* ++ * buffer that IPTS is going to fill. ++ */ ++ while (lastdb != doorbell) { ++ buffer = lastdb % IPTS_BUFFERS; ++ ++ ret = ipts_hid_input_data(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); ++ ++ ret = ipts_control_refill_buffer(ipts, buffer); ++ if (ret) ++ dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); ++ ++ last = ktime_get_seconds(); ++ lastdb++; ++ } ++ ++ if (ipts_thread_should_stop(thread)) ++ break; ++ ++ ipts_receiver_backoff(last, 5); ++ } ++ ++ ret = ipts_control_wait_data(ipts, true); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ ret = ipts_control_wait_flush(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); ++ ++ if (ret != -EAGAIN) ++ return ret; ++ else ++ return 0; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_start(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ if (ipts->mode == IPTS_MODE_EVENT) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, ++ "ipts_event"); ++ } else if (ipts->mode == IPTS_MODE_POLL) { ++ ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, ++ "ipts_poll"); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_receiver_stop(struct ipts_context *ipts) ++{ ++ int ret = 0; ++ ++ if (!ipts) ++ return -EFAULT; ++ ++ ret = ipts_thread_stop(&ipts->receiver_loop); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 000000000000..3de7da62d40c +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RECEIVER_H ++#define IPTS_RECEIVER_H ++ ++#include "context.h" ++ ++int ipts_receiver_start(struct ipts_context *ipts); ++int ipts_receiver_stop(struct ipts_context *ipts); ++ ++#endif /* IPTS_RECEIVER_H */ +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 000000000000..cc14653b2a9f +--- /dev/null ++++ b/drivers/hid/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "desc.h" ++#include "resources.h" ++#include "spec-device.h" ++ ++static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) ++{ ++ if (!buffer) ++ return -EFAULT; ++ ++ if (buffer->address) ++ return 0; ++ ++ buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); ++ ++ if (!buffer->address) ++ return -ENOMEM; ++ ++ buffer->size = size; ++ buffer->device = dev; ++ ++ return 0; ++} ++ ++static void ipts_resources_free_buffer(struct ipts_buffer *buffer) ++{ ++ if (!buffer->address) ++ return; ++ ++ dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); ++ ++ buffer->address = NULL; ++ buffer->size = 0; ++ ++ buffer->dma_address = 0; ++ buffer->device = NULL; ++} ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) ++{ ++ int ret = 0; ++ ++ /* ++ * Some compilers (AOSP clang) complain about a redefined ++ * variable when this is declared inside of the for loop. ++ */ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); ++ if (ret) ++ goto err; ++ } ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); ++ if (ret) ++ goto err; ++ } ++ ++ ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); ++ if (ret) ++ goto err; ++ ++ ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); ++ if (ret) ++ goto err; ++ ++ if (!res->report.address) { ++ res->report.size = IPTS_HID_REPORT_DATA_SIZE; ++ res->report.address = kzalloc(res->report.size, GFP_KERNEL); ++ ++ if (!res->report.address) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ } ++ ++ return 0; ++ ++err: ++ ++ ipts_resources_free(res); ++ return ret; ++} ++ ++int ipts_resources_free(struct ipts_resources *res) ++{ ++ int i = 0; ++ ++ if (!res) ++ return -EFAULT; ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->data[i]); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ ipts_resources_free_buffer(&res->feedback[i]); ++ ++ ipts_resources_free_buffer(&res->doorbell); ++ ipts_resources_free_buffer(&res->workqueue); ++ ipts_resources_free_buffer(&res->hid2me); ++ ipts_resources_free_buffer(&res->descriptor); ++ ++ kfree(res->report.address); ++ res->report.address = NULL; ++ res->report.size = 0; ++ ++ return 0; ++} +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 000000000000..2068e13285f0 +--- /dev/null ++++ b/drivers/hid/ipts/resources.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_RESOURCES_H ++#define IPTS_RESOURCES_H ++ ++#include ++#include ++ ++#include "spec-device.h" ++ ++struct ipts_buffer { ++ u8 *address; ++ size_t size; ++ ++ dma_addr_t dma_address; ++ struct device *device; ++}; ++ ++struct ipts_resources { ++ struct ipts_buffer data[IPTS_BUFFERS]; ++ struct ipts_buffer feedback[IPTS_BUFFERS]; ++ ++ struct ipts_buffer doorbell; ++ struct ipts_buffer workqueue; ++ struct ipts_buffer hid2me; ++ ++ struct ipts_buffer descriptor; ++ ++ // Buffer for synthesizing HID reports ++ struct ipts_buffer report; ++}; ++ ++int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); ++int ipts_resources_free(struct ipts_resources *res); ++ ++#endif /* IPTS_RESOURCES_H */ +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 000000000000..e8dd98895a7e +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DATA_H ++#define IPTS_SPEC_DATA_H ++ ++#include ++#include ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Defines what data a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_header - Header that is prefixed to the data in a feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of data that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data to the sensor. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_header { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback_header) == 64); ++ ++/** ++ * enum ipts_data_type - Defines what type of data a buffer contains. ++ * @IPTS_DATA_TYPE_FRAME: Raw data frame. ++ * @IPTS_DATA_TYPE_ERROR: Error data. ++ * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. ++ * @IPTS_DATA_TYPE_HID: A HID report. ++ * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. ++ */ ++enum ipts_data_type { ++ IPTS_DATA_TYPE_FRAME = 0x00, ++ IPTS_DATA_TYPE_ERROR = 0x01, ++ IPTS_DATA_TYPE_VENDOR = 0x02, ++ IPTS_DATA_TYPE_HID = 0x03, ++ IPTS_DATA_TYPE_GET_FEATURES = 0x04, ++ IPTS_DATA_TYPE_DESCRIPTOR = 0x05, ++}; ++ ++/** ++ * struct ipts_data_header - Header that is prefixed to the data in a data buffer. ++ * @type: What data the buffer contains. ++ * @size: How much data the buffer contains. ++ * @buffer: Which buffer the data is in. ++ */ ++struct ipts_data_header { ++ enum ipts_data_type type; ++ u32 size; ++ u32 buffer; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_data_header) == 64); ++ ++#endif /* IPTS_SPEC_DATA_H */ +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 000000000000..41845f9d9025 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h +@@ -0,0 +1,290 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_DEVICE_H ++#define IPTS_SPEC_DEVICE_H ++ ++#include ++#include ++ ++/* ++ * The amount of buffers that IPTS can use for data transfer. ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The buffer ID that is used for HID2ME feedback ++ */ ++#define IPTS_HID2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_command - Commands that can be sent to the IPTS hardware. ++ * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. ++ * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. ++ * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. ++ * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. ++ * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. ++ * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. ++ * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. ++ * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. ++ * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. ++ */ ++enum ipts_command_code { ++ IPTS_CMD_GET_DEVICE_INFO = 0x01, ++ IPTS_CMD_SET_MODE = 0x02, ++ IPTS_CMD_SET_MEM_WINDOW = 0x03, ++ IPTS_CMD_QUIESCE_IO = 0x04, ++ IPTS_CMD_READY_FOR_DATA = 0x05, ++ IPTS_CMD_FEEDBACK = 0x06, ++ IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, ++ IPTS_CMD_RESET_SENSOR = 0x0B, ++ IPTS_CMD_GET_DESCRIPTOR = 0x0F, ++}; ++ ++/** ++ * enum ipts_status - Possible status codes returned by the IPTS device. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by the driver. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported an error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. ++ * The driver can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0x00, ++ IPTS_STATUS_INVALID_PARAMS = 0x01, ++ IPTS_STATUS_ACCESS_DENIED = 0x02, ++ IPTS_STATUS_CMD_SIZE_ERROR = 0x03, ++ IPTS_STATUS_NOT_READY = 0x04, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, ++ IPTS_STATUS_NO_SENSOR_FOUND = 0x06, ++ IPTS_STATUS_OUT_OF_MEMORY = 0x07, ++ IPTS_STATUS_INTERNAL_ERROR = 0x08, ++ IPTS_STATUS_SENSOR_DISABLED = 0x09, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, ++ IPTS_STATUS_RESET_FAILED = 0x0D, ++ IPTS_STATUS_TIMEOUT = 0x0E, ++ IPTS_STATUS_TEST_MODE_FAIL = 0x0F, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, ++}; ++ ++/** ++ * struct ipts_command - Message that is sent to the device for calling a command. ++ * @cmd: The command that will be called. ++ * @payload: Payload containing parameters for the called command. ++ */ ++struct ipts_command { ++ enum ipts_command_code cmd; ++ u8 payload[320]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_command) == 324); ++ ++/** ++ * enum ipts_mode - Configures what data the device produces and how its sent. ++ * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. ++ * Older devices will return singletouch data in this mode. ++ * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. ++ * Older devices will return multitouch data in this mode. ++ */ ++enum ipts_mode { ++ IPTS_MODE_EVENT = 0x00, ++ IPTS_MODE_POLL = 0x01, ++}; ++ ++/** ++ * struct ipts_set_mode - Payload for the SET_MODE command. ++ * @mode: Changes the mode that IPTS will operate in. ++ */ ++struct ipts_set_mode { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode) == 16); ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. ++ * @data_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. ++ * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. ++ * @hid2me_size: Size of the hid2me feedback buffer. ++ * @workqueue_item_size: Magic value. Must be 16. ++ * @workqueue_size: Magic value. Must be 8192. ++ * ++ * The workqueue related items in this struct are required for using ++ * GuC submission with binary processing firmware. Since this driver does ++ * not use GuC submission and instead exports raw data to userspace, these ++ * items are not actually used, but they need to be allocated and passed ++ * to the device, otherwise initialization will fail. ++ */ ++struct ipts_mem_window { ++ u32 data_addr_lower[IPTS_BUFFERS]; ++ u32 data_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_addr_upper[IPTS_BUFFERS]; ++ u32 hid2me_addr_lower; ++ u32 hid2me_addr_upper; ++ u32 hid2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_mem_window) == 320); ++ ++/** ++ * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. ++ */ ++struct ipts_quiesce_io { ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_quiesce_io) == 12); ++ ++/** ++ * struct ipts_feedback - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the device should refill. ++ */ ++struct ipts_feedback { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_feedback) == 16); ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the device. ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0x00, ++ IPTS_RESET_TYPE_SOFT = 0x01, ++}; ++ ++/** ++ * struct ipts_reset - Payload for the RESET_SENSOR command. ++ * @type: How the device should get reset. ++ */ ++struct ipts_reset_sensor { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_reset_sensor) == 8); ++ ++/** ++ * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. ++ * @addr_lower: The lower 32 bits of the descriptor buffer address. ++ * @addr_upper: The upper 32 bits of the descriptor buffer address. ++ * @magic: A magic value. Must be 8. ++ */ ++struct ipts_get_descriptor { ++ u32 addr_lower; ++ u32 addr_upper; ++ u32 magic; ++ u8 reserved[12]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_get_descriptor) == 24); ++ ++/* ++ * The type of a response is indicated by a ++ * command code, with the most significant bit flipped to 1. ++ */ ++#define IPTS_RSP_BIT BIT(31) ++ ++/** ++ * struct ipts_response - Data returned from the device in response to a command. ++ * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). ++ * @status: The return code of the command. ++ * @payload: The data that was produced by the command. ++ */ ++struct ipts_response { ++ enum ipts_command_code cmd; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_response) == 88); ++ ++/** ++ * struct ipts_device_info - Vendor information of the IPTS device. ++ * @vendor: Vendor ID of this device. ++ * @product: Product ID of this device. ++ * @hw_version: Hardware revision of this device. ++ * @fw_version: Firmware revision of this device. ++ * @data_size: Requested size for a data buffer. ++ * @feedback_size: Requested size for a feedback buffer. ++ * @mode: Mode that the device currently operates in. ++ * @max_contacts: Maximum amount of concurrent touches the sensor can process. ++ * @sensor_min_eds: The minimum EDS version supported by the sensor. ++ * @sensor_max_eds: The maximum EDS version supported by the sensor. ++ * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. ++ * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. ++ * @intf_eds: The EDS version implemented by the interface between ME and host. ++ */ ++struct ipts_device_info { ++ u16 vendor; ++ u16 product; ++ u32 hw_version; ++ u32 fw_version; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved1[3]; ++ u8 sensor_min_eds; ++ u8 sensor_maj_eds; ++ u8 me_min_eds; ++ u8 me_maj_eds; ++ u8 intf_eds; ++ u8 reserved2[11]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++ ++#endif /* IPTS_SPEC_DEVICE_H */ +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 000000000000..5a58d4a0a610 +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2020-2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_SPEC_HID_H ++#define IPTS_SPEC_HID_H ++ ++#include ++#include ++ ++/* ++ * Made-up type for passing raw IPTS data in a HID report. ++ */ ++#define IPTS_HID_FRAME_TYPE_RAW 0xEE ++ ++/** ++ * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. ++ * @size: Size of the data inside the report, including this header. ++ * @type: What type of data does this report contain. ++ */ ++struct ipts_hid_header { ++ u32 size; ++ u8 reserved1; ++ u8 type; ++ u8 reserved2; ++ u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct ipts_hid_header) == 7); ++ ++#endif /* IPTS_SPEC_HID_H */ +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 000000000000..355e92bea26f +--- /dev/null ++++ b/drivers/hid/ipts/thread.c +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "thread.h" ++ ++bool ipts_thread_should_stop(struct ipts_thread *thread) ++{ ++ if (!thread) ++ return false; ++ ++ return READ_ONCE(thread->should_stop); ++} ++ ++static int ipts_thread_runner(void *data) ++{ ++ int ret = 0; ++ struct ipts_thread *thread = data; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->threadfn) ++ return -EFAULT; ++ ++ ret = thread->threadfn(thread); ++ complete_all(&thread->done); ++ ++ return ret; ++} ++ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char *name) ++{ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!threadfn) ++ return -EFAULT; ++ ++ init_completion(&thread->done); ++ ++ thread->data = data; ++ thread->should_stop = false; ++ thread->threadfn = threadfn; ++ ++ thread->thread = kthread_run(ipts_thread_runner, thread, name); ++ return PTR_ERR_OR_ZERO(thread->thread); ++} ++ ++int ipts_thread_stop(struct ipts_thread *thread) ++{ ++ int ret = 0; ++ ++ if (!thread) ++ return -EFAULT; ++ ++ if (!thread->thread) ++ return 0; ++ ++ WRITE_ONCE(thread->should_stop, true); ++ ++ /* ++ * Make sure that the write has gone through before waiting. ++ */ ++ wmb(); ++ ++ wait_for_completion(&thread->done); ++ ret = kthread_stop(thread->thread); ++ ++ thread->thread = NULL; ++ thread->data = NULL; ++ thread->threadfn = NULL; ++ ++ return ret; ++} +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 000000000000..1f966b8b32c4 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2023 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef IPTS_THREAD_H ++#define IPTS_THREAD_H ++ ++#include ++#include ++#include ++ ++/* ++ * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible ++ * to issue MEI commands from that thread while it shuts itself down. By using a custom ++ * boolean variable and a completion object, we can call kthread_stop only when the thread ++ * already finished all of its work and has returned. ++ */ ++struct ipts_thread { ++ struct task_struct *thread; ++ ++ bool should_stop; ++ struct completion done; ++ ++ void *data; ++ int (*threadfn)(struct ipts_thread *thread); ++}; ++ ++/** ++ * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. ++ * @thread: The current thread. ++ * ++ * Returns: true if the thread should stop, false if not. ++ */ ++bool ipts_thread_should_stop(struct ipts_thread *thread); ++ ++/** ++ * ipts_thread_start() - Starts an IPTS thread. ++ * @thread: The thread to initialize and start. ++ * @threadfn: The function to execute. ++ * @data: An argument that will be passed to threadfn. ++ * @name: The name of the new thread. ++ * ++ * Returns: 0 on success, <0 on error. ++ */ ++int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), ++ void *data, const char name[]); ++ ++/** ++ * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. ++ * @thread: The thread that should stop. ++ * ++ * Returns: The return value of the thread function. ++ */ ++int ipts_thread_stop(struct ipts_thread *thread); ++ ++#endif /* IPTS_THREAD_H */ +-- +2.42.0 + +From cc8157a9538ba31fb72482b9fa52803241f0887d 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 08f56326e2f8..75218b38995c 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 764c414fc6b257517d0d0ffc76eb9ef4d4577a84 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 b35203b9a7d8..3259f2764dc4 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1337,4 +1337,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 f58610f27216..656a0783c709 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -170,3 +170,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 000000000000..aea83f2ac07b +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild +@@ -0,0 +1,6 @@ ++obj-$(CONFIG_HID_ITHC) := ithc.o ++ ++ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-debug.o ++ ++ccflags-y := -std=gnu11 -Wno-declaration-after-statement ++ +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 000000000000..ede713023609 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig +@@ -0,0 +1,12 @@ ++config HID_ITHC ++ tristate "Intel Touch Host Controller" ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Touch Host Controller (ITHC / IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ithc. +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 000000000000..57bf125c45bd +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -0,0 +1,96 @@ ++#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++) { ++ u32 x = readl(cur + i); ++ if (x != prev[i]) { ++ pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); ++ prev[i] = x; ++ } ++ } ++} ++ ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, loff_t *offset) { ++ 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; ++ cmd[len] = 0; ++ if (cmd[len-1] == '\n') cmd[len-1] = 0; ++ pci_info(ithc->pci, "debug command: %s\n", cmd); ++ 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; ++ char *e; ++ a[n++] = simple_strtoul(s, &e, 0); ++ if (e == s) return -EINVAL; ++ s = e; ++ } ++ ithc_log_regs(ithc); ++ 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]); ++ 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)); ++ 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; ++ 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]); ++ 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; ++ 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"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ithc_log_regs(ithc); ++ return len; ++} ++ ++static const struct file_operations ithc_debugfops_cmd = { ++ .owner = THIS_MODULE, ++ .write = ithc_debugfs_cmd_write, ++}; ++ ++static void ithc_debugfs_devres_release(struct device *dev, void *res) { ++ struct dentry **dbgm = res; ++ 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; ++ devres_add(&ithc->pci->dev, dbgm); ++ struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); ++ 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); ++ ++ return 0; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 000000000000..7e89b3496918 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -0,0 +1,258 @@ ++#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) { ++ p->num_pages = num_pages; ++ p->dir = dir; ++ 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; ++ return 0; ++} ++ ++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) { ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_sg(sgt, sg, i) { ++ struct page *p = sg_page(sg); ++ if (p) __free_page(p); ++ } ++ sg_free_table(sgt); ++} ++static void ithc_dma_data_devres_release(struct device *dev, void *res) { ++ struct ithc_sg_table *sgt = res; ++ 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). ++ struct page *pages[16]; ++ 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; ++ 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; } ++ 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; ++ return 0; ++ } ++ ithc_dma_sgtable_free(&sgt->sgt); ++ } ++ devres_free(sgt); ++ 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) { ++ 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; } ++ b->active_idx = idx; ++ if (prds->dir == DMA_TO_DEVICE) { ++ 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) { ++ struct scatterlist *sg; ++ int i; ++ for_each_sgtable_dma_sg(b->sgt, sg, i) { ++ prd->addr = sg_dma_address(sg) >> 10; ++ prd->size = sg_dma_len(sg); ++ prd++; ++ } ++ prd[-1].size |= PRD_FLAG_END; ++ } ++ dma_wmb(); // for the prds ++ dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); ++ 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) { ++ 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; } ++ b->active_idx = -1; ++ if (prds->dir == DMA_FROM_DEVICE) { ++ 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; ++ 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; } ++ prd++; ++ } ++ invalidate_kernel_vmap_range(b->addr, b->data_size); ++ } ++ dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); ++ return 0; ++} ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname) { ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ mutex_init(&rx->mutex); ++ 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); ++ 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++) ++ CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ 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++) ++ 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); ++} ++ ++int ithc_dma_tx_init(struct ithc *ithc) { ++ struct ithc_dma_tx *tx = &ithc->dma_tx; ++ mutex_init(&tx->mutex); ++ 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); ++ 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); ++ lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); ++ writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ 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) { ++ 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) { ++ CHECK(ithc_reset, ithc); ++ } 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. ++ // 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); ++ } ++ } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { ++ 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) { ++ CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); ++ } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { ++ 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; ++ 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); ++ } 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); ++ } ++ return 0; ++} ++ ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) { ++ struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; ++ unsigned n = rx->num_received; ++ u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); ++ while (1) { ++ u8 tail = n % NUM_RX_BUF; ++ u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); ++ 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; ++ ++ // take the buffer that the device just filled ++ struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); ++ rx->num_received = ++n; ++ ++ // process data ++ CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); ++ ++ // give the buffer back to the device ++ CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); ++ } ++} ++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); ++ mutex_unlock(&rx->mutex); ++ return ret; ++} ++ ++static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) { ++ pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); ++ struct ithc_dma_tx_header *hdr; ++ 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; ++ CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); ++ ++ ithc->dma_tx.buf.data_size = fullsize; ++ hdr = ithc->dma_tx.buf.addr; ++ hdr->code = cmdcode; ++ hdr->data_size = datasize; ++ u8 *dest = (void *)(hdr + 1); ++ memcpy(dest, data, datasize); ++ dest += datasize; ++ 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); ++ ++ 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) { ++ mutex_lock(&ithc->dma_tx.mutex); ++ int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); ++ mutex_unlock(&ithc->dma_tx.mutex); ++ return ret; ++} ++ +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 000000000000..d9f2c19a13f3 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -0,0 +1,67 @@ ++#define PRD_SIZE_MASK 0xffffff ++#define PRD_FLAG_END 0x1000000 ++#define PRD_FLAG_SUCCESS 0x2000000 ++#define PRD_FLAG_ERROR 0x4000000 ++ ++struct ithc_phys_region_desc { ++ u64 addr; // physical addr/1024 ++ u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds ++ u32 unused; ++}; ++ ++#define DMA_RX_CODE_INPUT_REPORT 3 ++#define DMA_RX_CODE_FEATURE_REPORT 4 ++#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 ++#define DMA_RX_CODE_RESET 7 ++ ++struct ithc_dma_rx_header { ++ u32 code; ++ u32 data_size; ++ u32 _unknown[14]; ++}; ++ ++#define DMA_TX_CODE_SET_FEATURE 3 ++#define DMA_TX_CODE_GET_FEATURE 4 ++#define DMA_TX_CODE_OUTPUT_REPORT 5 ++#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 ++ ++struct ithc_dma_tx_header { ++ u32 code; ++ u32 data_size; ++}; ++ ++struct ithc_dma_prd_buffer { ++ void *addr; ++ dma_addr_t dma_addr; ++ u32 size; ++ u32 num_pages; // per data buffer ++ enum dma_data_direction dir; ++}; ++ ++struct ithc_dma_data_buffer { ++ void *addr; ++ struct sg_table *sgt; ++ int active_idx; ++ u32 data_size; ++}; ++ ++struct ithc_dma_tx { ++ struct mutex mutex; ++ u32 max_size; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer buf; ++}; ++ ++struct ithc_dma_rx { ++ struct mutex mutex; ++ u32 num_received; ++ struct ithc_dma_prd_buffer prds; ++ struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; ++}; ++ ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname); ++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); ++int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); ++ +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 000000000000..09512b9cb4d3 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c +@@ -0,0 +1,534 @@ ++#include "ithc.h" ++ ++MODULE_DESCRIPTION("Intel Touch Host Controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++ ++// Lakefield ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 ++#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 ++// Tiger Lake ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 ++#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 ++// Alder Lake ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 ++#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 ++// Raptor Lake ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 ++#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 ++// Meteor Lake ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 ++#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a ++ ++static const struct pci_device_id ithc_pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, ++ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, ++ { 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) }, ++ {} ++}; ++MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); ++ ++// Module parameters ++ ++static bool ithc_use_polling = false; ++module_param_named(poll, ithc_use_polling, bool, 0); ++MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); ++ ++static bool ithc_use_rx0 = false; ++module_param_named(rx0, ithc_use_rx0, bool, 0); ++MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); ++ ++static bool ithc_use_rx1 = true; ++module_param_named(rx1, ithc_use_rx1, bool, 0); ++MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); ++ ++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) { ++ return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; ++} ++ ++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; ++ 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) { ++ struct ithc *ithc = dev_get_drvdata(dev); ++ 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) { ++ struct ithc *ithc = dev_get_drvdata(dev); ++ 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) { ++ struct ithc *ithc = dev_get_drvdata(dev); ++ 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); ++} ++static DEVICE_ATTR_RO(fw_version); ++ ++static const struct attribute_group *ithc_attribute_groups[] = { ++ &(const struct attribute_group){ ++ .name = DEVNAME, ++ .attrs = (struct attribute *[]){ ++ &dev_attr_vendor.attr, ++ &dev_attr_product.attr, ++ &dev_attr_revision.attr, ++ &dev_attr_fw_version.attr, ++ NULL ++ }, ++ }, ++ NULL ++}; ++ ++// HID setup ++ ++static int ithc_hid_start(struct hid_device *hdev) { return 0; } ++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) { ++ 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; ++} ++ ++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; ++ 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); ++ return -EINVAL; ++ } ++ buf[0] = reportnum; ++ if (reqtype == HID_REQ_GET_REPORT) { ++ 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); ++ 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; ++ } ++ mutex_lock(&ithc->hid_get_feature_mutex); ++ ithc->hid_get_feature_buf = NULL; ++ if (!r) r = ithc->hid_get_feature_size; ++ mutex_unlock(&ithc->hid_get_feature_mutex); ++ return r; ++ } ++ CHECK_RET(ithc_dma_tx, ithc, code, len, buf); ++ return 0; ++} ++ ++static struct hid_ll_driver ithc_ll_driver = { ++ .start = ithc_hid_start, ++ .stop = ithc_hid_stop, ++ .open = ithc_hid_open, ++ .close = ithc_hid_close, ++ .parse = ithc_hid_parse, ++ .raw_request = ithc_hid_raw_request, ++}; ++ ++static void ithc_hid_devres_release(struct device *dev, void *res) { ++ struct hid_device **hidm = res; ++ 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; ++ devres_add(&ithc->pci->dev, hidm); ++ struct hid_device *hid = hid_allocate_device(); ++ if (IS_ERR(hid)) return PTR_ERR(hid); ++ *hidm = hid; ++ ++ strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); ++ strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); ++ hid->ll_driver = &ithc_ll_driver; ++ hid->bus = BUS_PCI; ++ hid->vendor = ithc->config.vendor_id; ++ hid->product = ithc->config.product_id; ++ hid->version = 0x100; ++ hid->dev.parent = &ithc->pci->dev; ++ hid->driver_data = ithc; ++ ++ ithc->hid = hid; ++ return 0; ++} ++ ++// 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); ++} ++ ++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 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); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); ++ 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_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); ++} ++ ++static void ithc_process(struct ithc *ithc) { ++ ithc_log_regs(ithc); ++ ++ // 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); ++ } ++ ++ // process DMA rx ++ if (ithc_use_rx0) { ++ ithc_clear_dma_rx_interrupts(ithc, 0); ++ ithc_dma_rx(ithc, 0); ++ } ++ if (ithc_use_rx1) { ++ ithc_clear_dma_rx_interrupts(ithc, 1); ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ ithc_log_regs(ithc); ++} ++ ++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), ++ readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), ++ readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), ++ readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), ++ readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); ++ ithc_process(ithc); ++ return IRQ_HANDLED; ++} ++ ++static int ithc_poll_thread(void *arg) { ++ struct ithc *ithc = arg; ++ unsigned 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); ++ msleep_interruptible(sleep); ++ } ++ return 0; ++} ++ ++// Device initialization and shutdown ++ ++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); ++ bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); ++ bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); ++ bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); ++ ithc_disable_interrupts(ithc); ++ ithc_clear_interrupts(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); ++ 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; ++ 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 (retries > 5) { ++ pci_err(ithc->pci, "too many retries, failed to reset device\n"); ++ return -ETIMEDOUT; ++ } ++ pci_err(ithc->pci, "invalid state, retrying reset\n"); ++ bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); ++ if (msleep_interruptible(1000)) return -EINTR; ++ } ++ ithc_log_regs(ithc); ++ ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); ++ ++ // read config ++ 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); ++ 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 (retries > 10) { ++ 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; ++ } ++ 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)); ++ 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? ++ 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); ++ ithc_log_regs(ithc); ++ pci_dbg(ithc->pci, "reset completed\n"); ++ return 0; ++} ++ ++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); ++ CHECK(ithc_set_device_enabled, ithc, false); ++ ithc_disable(ithc); ++ del_timer_sync(&ithc->activity_timer); ++ cpu_latency_qos_remove_request(&ithc->activity_qos); ++ // clear dma config ++ for(unsigned 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); ++ writeb(0, &ithc->regs->dma_rx[i].num_prds); ++ } ++ 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) { ++ struct pci_dev *pci = res; ++ pci_set_drvdata(pci, NULL); ++} ++ ++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; ++ ++ 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)); ++ 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); ++ ++ CHECK_RET(pcim_enable_device, pci); ++ pci_set_master(pci); ++ CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); ++ CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); ++ CHECK_RET(pci_set_power_state, pci, PCI_D0); ++ ithc->regs = pcim_iomap_table(pci)[0]; ++ ++ 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; ++ } ++ ++ 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); ++ 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); ++ ++ // 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); ++ ++ 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 ++ ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); ++ if (IS_ERR(ithc->poll_thread)) { ++ int err = PTR_ERR(ithc->poll_thread); ++ ithc->poll_thread = NULL; ++ return err; ++ } ++ } else { ++ 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); ++ ++ // 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); ++ ++ pci_dbg(pci, "started\n"); ++ return 0; ++} ++ ++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) { ++ pci_dbg(pci, "device remove\n"); ++ // all cleanup is handled by devres ++} ++ ++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) { ++ 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) { ++ 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) { ++ 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) { ++ struct pci_dev *pci = to_pci_dev(dev); ++ pci_dbg(pci, "pm restore\n"); ++ return ithc_start(pci); ++} ++ ++static struct pci_driver ithc_driver = { ++ .name = DEVNAME, ++ .id_table = ithc_pci_tbl, ++ .probe = ithc_probe, ++ .remove = ithc_remove, ++ .driver.pm = &(const struct dev_pm_ops) { ++ .suspend = ithc_suspend, ++ .resume = ithc_resume, ++ .freeze = ithc_freeze, ++ .thaw = ithc_thaw, ++ .restore = ithc_restore, ++ }, ++ //.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) { ++ return pci_register_driver(&ithc_driver); ++} ++ ++static void __exit ithc_exit(void) { ++ pci_unregister_driver(&ithc_driver); ++} ++ ++module_init(ithc_init); ++module_exit(ithc_exit); ++ +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 000000000000..85d567b05761 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -0,0 +1,64 @@ ++#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); ++ 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); ++ 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); ++ 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); ++ 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); ++ 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); ++ return -ETIMEDOUT; ++ } ++ pci_dbg(ithc->pci, "done waiting\n"); ++ return 0; ++} ++ ++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; ++ 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) { ++ 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; ++ CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ 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]); ++ 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]); ++ 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 +new file mode 100644 +index 000000000000..1a96092ed7ee +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -0,0 +1,186 @@ ++#define CONTROL_QUIESCE BIT(1) ++#define CONTROL_IS_QUIESCED BIT(2) ++#define CONTROL_NRESET BIT(3) ++#define CONTROL_READY BIT(29) ++ ++#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) ++#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) ++#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) ++#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? ++ ++#define ERROR_CONTROL_UNKNOWN_0 BIT(0) ++#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs ++#define ERROR_CONTROL_UNKNOWN_2 BIT(2) ++#define ERROR_CONTROL_UNKNOWN_3 BIT(3) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) ++#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) ++#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? ++#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs ++ ++#define ERROR_STATUS_DMA BIT(28) ++#define ERROR_STATUS_SPI BIT(30) ++ ++#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_UNKNOWN_13 BIT(13) ++#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) ++#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) ++#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) ++#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) ++#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) ++#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) ++ ++#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define SPI_CMD_CONTROL_IRQ BIT(1) ++ ++#define SPI_CMD_CODE_READ 4 ++#define SPI_CMD_CODE_WRITE 6 ++ ++#define SPI_CMD_STATUS_DONE BIT(0) ++#define SPI_CMD_STATUS_ERROR BIT(1) ++#define SPI_CMD_STATUS_BUSY BIT(3) ++ ++#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete ++#define DMA_TX_CONTROL_IRQ BIT(3) ++ ++#define DMA_TX_STATUS_DONE BIT(0) ++#define DMA_TX_STATUS_ERROR BIT(1) ++#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) ++#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? ++ ++#define DMA_RX_CONTROL_ENABLE BIT(0) ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? ++#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? ++#define DMA_RX_CONTROL_IRQ_DATA BIT(5) ++ ++#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? ++#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices ++ ++#define DMA_RX_WRAP_FLAG BIT(7) ++ ++#define DMA_RX_STATUS_ERROR BIT(3) ++#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) ++#define DMA_RX_STATUS_HAVE_DATA BIT(5) ++#define DMA_RX_STATUS_ENABLED BIT(8) ++ ++#define COUNTER_RESET BIT(31) ++ ++struct ithc_registers { ++ /* 0000 */ u32 _unknown_0000[1024]; ++ /* 1000 */ u32 _unknown_1000; ++ /* 1004 */ u32 _unknown_1004; ++ /* 1008 */ u32 control_bits; ++ /* 100c */ u32 _unknown_100c; ++ /* 1010 */ u32 spi_config; ++ /* 1014 */ u32 _unknown_1014[3]; ++ /* 1020 */ u32 error_control; ++ /* 1024 */ u32 error_status; // write to clear ++ /* 1028 */ u32 error_flags; // write to clear ++ /* 102c */ u32 _unknown_102c[5]; ++ struct { ++ /* 1040 */ u8 control; ++ /* 1041 */ u8 code; ++ /* 1042 */ u16 size; ++ /* 1044 */ u32 status; // write to clear ++ /* 1048 */ u32 offset; ++ /* 104c */ u32 data[16]; ++ /* 108c */ u32 _unknown_108c; ++ } spi_cmd; ++ struct { ++ /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1098 */ u8 control; ++ /* 1099 */ u8 _unknown_1099; ++ /* 109a */ u8 _unknown_109a; ++ /* 109b */ u8 num_prds; ++ /* 109c */ u32 status; // write to clear ++ } dma_tx; ++ /* 10a0 */ u32 _unknown_10a0[7]; ++ /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET ++ /* 10c0 */ u32 _unknown_10c0[8]; ++ /* 10e0 */ u32 _unknown_10e0_counters[3]; ++ /* 10ec */ u32 _unknown_10ec[5]; ++ struct { ++ /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() ++ /* 1108/1208 */ u8 num_bufs; ++ /* 1109/1209 */ u8 num_prds; ++ /* 110a/120a */ u16 _unknown_110a; ++ /* 110c/120c */ u8 control; ++ /* 110d/120d */ u8 head; ++ /* 110e/120e */ u8 tail; ++ /* 110f/120f */ u8 control2; ++ /* 1110/1210 */ u32 status; // write to clear ++ /* 1114/1214 */ u32 _unknown_1114; ++ /* 1118/1218 */ u64 _unknown_1118_guc_addr; ++ /* 1120/1220 */ u32 _unknown_1120_guc; ++ /* 1124/1224 */ u32 _unknown_1124_guc; ++ /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related ++ /* 112c/122c */ u32 _unknown_112c; ++ /* 1130/1230 */ u64 _unknown_1130_guc_addr; ++ /* 1138/1238 */ u32 _unknown_1138_guc; ++ /* 113c/123c */ u32 _unknown_113c; ++ /* 1140/1240 */ u32 _unknown_1140_guc; ++ /* 1144/1244 */ u32 _unknown_1144[23]; ++ /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; ++ /* 11b8/12b8 */ u32 _unknown_11b8[18]; ++ } dma_rx[2]; ++}; ++static_assert(sizeof(struct ithc_registers) == 0x1300); ++ ++#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) ++#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) ++ ++#define DEVCFG_TOUCH_MASK 0x3f ++#define DEVCFG_TOUCH_ENABLE BIT(0) ++#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) ++#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) ++#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) ++#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) ++#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) ++#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) ++ ++#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" ++ ++#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_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) ++ ++struct ithc_device_config { ++ u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) ++ u32 _unknown_04; // 04 = 0x00000000 ++ u32 dma_buf_sizes; // 08 = 0x000a00ff ++ u32 touch_cfg; // 0c = 0x0000001c ++ u32 _unknown_10; // 10 = 0x0000001c ++ u32 device_id; // 14 = 0x43495424 = "$TIC" ++ u32 spi_config; // 18 = 0xfda00a2e ++ 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 _unknown_28; // 28 = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 ++ 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) ++ u32 _unknown_3c; // 3c = 0x00000002 ++}; ++ ++void bitsl(__iomem u32 *reg, u32 mask, u32 val); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val); ++#define bitsl_set(reg, x) bitsl(reg, x, x) ++#define bitsb_set(reg, x) bitsb(reg, x, x) ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); ++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 --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 000000000000..6a9b0d480bc1 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h +@@ -0,0 +1,60 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVNAME "ithc" ++#define DEVFULLNAME "Intel Touch Host Controller" ++ ++#undef pr_fmt ++#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 NUM_RX_BUF 16 ++ ++struct ithc; ++ ++#include "ithc-regs.h" ++#include "ithc-dma.h" ++ ++struct ithc { ++ char phys[32]; ++ struct pci_dev *pci; ++ int irq; ++ struct task_struct *poll_thread; ++ struct pm_qos_request activity_qos; ++ struct timer_list activity_timer; ++ ++ struct hid_device *hid; ++ bool hid_parse_done; ++ wait_queue_head_t wait_hid_parse; ++ wait_queue_head_t wait_hid_get_feature; ++ struct mutex hid_get_feature_mutex; ++ void *hid_get_feature_buf; ++ size_t hid_get_feature_size; ++ ++ struct ithc_registers __iomem *regs; ++ struct ithc_registers *prev_regs; // for debugging ++ struct ithc_device_config config; ++ struct ithc_dma_rx dma_rx[2]; ++ struct ithc_dma_tx dma_tx; ++}; ++ ++int ithc_reset(struct ithc *ithc); ++void ithc_set_active(struct ithc *ithc); ++int ithc_debug_init(struct ithc *ithc); ++void ithc_log_regs(struct ithc *ithc); ++ +-- +2.42.0 + +From 642eb8739ac75599047abc3afaa640f5f46e9640 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 0fe5be539652..0d8c8395c588 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 041d23936593cbf33286afbc6e9eab906af93908 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 d6037a328669..a290ebc77aea 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; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret = AE_OK; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -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, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.42.0 + +From 683b0d78d21da8074069c4ef6b810e2356bd8154 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 b629e82af97c..68656e8f309e 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 53344330939b..7efcd0cdb532 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 000000000000..8b816ed8f35c +--- /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 ef021ca0f2cf173c2f72608697a04a2acc65cd3e 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 e79f5497948b..2bddbe6e9ea4 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 +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -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 +- +- // 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 (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- 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; + } + + /* +-- +2.42.0 + +From 1611f61d93343d14c7e9653bbd9feab765252173 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 2755601f979c..4240c98ca226 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 e4d765b3b8ab178a2a4e61a29d4e6db812ccf720 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 15e9bd180a1d..0d70461d01e1 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 e22f7ae247a393daae02512a7f2820468a359870 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 521b2ffb4244..c8f3d05c8866 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); +@@ -2219,6 +2310,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 a24a54b9da7cf6aa6d309e76a4f47ac62849ca4c 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 c8f3d05c8866..1c6e4d66e762 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 22539e90de237f528ce33100f723b19eb868081b 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 41ee3dd8cecb..0bc473c2c187 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 321156ca273d..37e06e778ce2 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6138,3 +6138,39 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a2d, dpc_log_size); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a2f, dpc_log_size); + 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. ++ */ ++ { ++ .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 +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 7ee498cd1f37..740049a82343 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -464,6 +464,7 @@ struct pci_dev { + unsigned int no_vf_scan:1; /* Don't scan for VFs after IOV enablement */ + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ ++ 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 5aa11cbfd07f069b5a1643c8ef3c357077337c15 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 c219b840d491..69c4352e8406 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 11d8ad04e7da34a592105d7c009e83607d926c4a 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 87e385542576..1183d09c13a6 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2105,6 +2105,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 e8ca600a8a03ad05713dd64a2c1f68f5cb0e88f8 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 6387f3a6eccf..d75107c47de0 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -37,6 +37,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) ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) + #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9d3e)) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) +@@ -290,12 +296,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_gfx = 1; + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 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_IPU 8 + #define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; +@@ -2553,6 +2561,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; + } +@@ -2862,6 +2873,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; + +@@ -4765,6 +4779,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + dmar_map_gfx = 0; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4776,6 +4802,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); +@@ -4811,6 +4838,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); + +-- +2.42.0 + +From 986860085817d537757e9a33a0396e4e388e0c0c 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 1e107fd49f82..e3e1696e7f0e 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 7fc4050c6efecee40530b5b66e414ed2bb2344cf Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 4 May 2022 23:21:45 +0100 +Subject: [PATCH] media: ipu3-cio2: Move functionality from .complete() to + .bound() + +Creating links and registering subdev nodes during the .complete() +callback has the unfortunate effect of preventing all cameras that +connect to a notifier from working if any one of their drivers fails +to probe. Moving the functionality from .complete() to .bound() allows +those camera sensor drivers that did probe correctly to work regardless. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 59 +++++++------------ + 1 file changed, 21 insertions(+), 38 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +index ca51776a961f..c027b2bfd851 100644 +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +@@ -1386,7 +1386,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, + { + struct cio2_device *cio2 = to_cio2_device(notifier); + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); ++ struct device *dev = &cio2->pci_dev->dev; + struct cio2_queue *q; ++ unsigned int pad; ++ int ret; + + if (cio2->queue[s_asd->csi2.port].sensor) + return -EBUSY; +@@ -1397,7 +1400,24 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, + 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 */ +@@ -1411,46 +1431,9 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, + cio2->queue[s_asd->csi2.port].sensor = NULL; + } + +-/* .complete() is called after all subdevices have been located */ +-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_subdev *asd; +- struct cio2_queue *q; +- int ret; +- +- list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { +- 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); +-} +- + static const struct v4l2_async_notifier_operations cio2_async_ops = { + .bound = cio2_notifier_bound, + .unbind = cio2_notifier_unbind, +- .complete = cio2_notifier_complete, + }; + + static int cio2_parse_firmware(struct cio2_device *cio2) +-- +2.42.0 + +From 00fb5f5ad89ed5daa556c0871d160ff10248957c Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 2 Jun 2022 22:15:56 +0100 +Subject: [PATCH] media: ipu3-cio2: Re-add .complete() to ipu3-cio2 + +Removing the .complete() callback had some unintended consequences. +Because the VCM driver is not directly linked to the ipu3-cio2 +driver .bound() never gets called for it, which means its devnode +is never created if it probes late. Because .complete() waits for +any sub-notifiers to also be complete it is captured in that call. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +index c027b2bfd851..031acee26553 100644 +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +@@ -1431,9 +1431,18 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, + cio2->queue[s_asd->csi2.port].sensor = NULL; + } + ++/* .complete() is called after all subdevices have been located */ ++static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) ++{ ++ struct cio2_device *cio2 = to_cio2_device(notifier); ++ ++ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); ++} ++ + static const struct v4l2_async_notifier_operations cio2_async_ops = { + .bound = cio2_notifier_bound, + .unbind = cio2_notifier_unbind, ++ .complete = cio2_notifier_complete, + }; + + static int cio2_parse_firmware(struct cio2_device *cio2) +-- +2.42.0 + +From 69b483c39110f77a6664ac0bd2d4112d2bc156c9 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 28 Oct 2021 21:55:16 +0100 +Subject: [PATCH] media: i2c: Add driver for DW9719 VCM + +Add a driver for the DW9719 VCM. The driver creates a v4l2 subdevice +and registers a control to set the desired focus. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + MAINTAINERS | 7 + + drivers/media/i2c/Kconfig | 11 + + drivers/media/i2c/Makefile | 1 + + drivers/media/i2c/dw9719.c | 425 +++++++++++++++++++++++++++++++++++++ + 4 files changed, 444 insertions(+) + create mode 100644 drivers/media/i2c/dw9719.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 4cc6bf79fdd8..439cf523b80e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -6251,6 +6251,13 @@ T: git git://linuxtv.org/media_tree.git + F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.yaml + F: drivers/media/i2c/dw9714.c + ++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 ++ + DONGWOON DW9768 LENS VOICE COIL DRIVER + M: Dongchun Zhu + L: linux-media@vger.kernel.org +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 0669aea3eba3..6959ee1a89fb 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -855,6 +855,17 @@ config VIDEO_DW9714 + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + ++config VIDEO_DW9719 ++ tristate "DW9719 lens voice coil support" ++ depends on I2C && VIDEO_V4L2 ++ 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 --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +index c743aeb5d1ad..db1ebf5cadfe 100644 +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -29,6 +29,7 @@ obj-$(CONFIG_VIDEO_CS5345) += cs5345.o + obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o + obj-$(CONFIG_VIDEO_CX25840) += cx25840/ + obj-$(CONFIG_VIDEO_DW9714) += dw9714.o ++obj-$(CONFIG_VIDEO_DW9719) += dw9719.o + obj-$(CONFIG_VIDEO_DW9768) += dw9768.o + obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o + obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +new file mode 100644 +index 000000000000..180b04d2a6b3 +--- /dev/null ++++ b/drivers/media/i2c/dw9719.c +@@ -0,0 +1,425 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2012 Intel Corporation ++ ++/* ++ * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: ++ * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#define DW9719_MAX_FOCUS_POS 1023 ++#define DW9719_CTRL_STEPS 16 ++#define DW9719_CTRL_DELAY_US 1000 ++#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) ++ ++#define DW9719_INFO 0 ++#define DW9719_ID 0xF1 ++#define DW9719_CONTROL 2 ++#define DW9719_VCM_CURRENT 3 ++ ++#define DW9719_MODE 6 ++#define DW9719_VCM_FREQ 7 ++ ++#define DW9719_MODE_SAC3 0x40 ++#define DW9719_DEFAULT_VCM_FREQ 0x60 ++#define DW9719_ENABLE_RINGING 0x02 ++ ++#define NUM_REGULATORS 2 ++ ++#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) ++ ++struct dw9719_device { ++ struct device *dev; ++ struct i2c_client *client; ++ struct regulator_bulk_data regulators[NUM_REGULATORS]; ++ struct v4l2_subdev sd; ++ ++ struct dw9719_v4l2_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *focus; ++ } ctrls; ++}; ++ ++static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) ++{ ++ struct i2c_msg msg[2]; ++ u8 buf[2] = { reg }; ++ int ret; ++ ++ msg[0].addr = client->addr; ++ msg[0].flags = 0; ++ msg[0].len = 1; ++ msg[0].buf = buf; ++ ++ msg[1].addr = client->addr; ++ msg[1].flags = I2C_M_RD; ++ msg[1].len = 1; ++ msg[1].buf = &buf[1]; ++ *val = 0; ++ ++ ret = i2c_transfer(client->adapter, msg, 2); ++ if (ret < 0) ++ return ret; ++ ++ *val = buf[1]; ++ ++ return 0; ++} ++ ++static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) ++{ ++ struct i2c_msg msg; ++ int ret; ++ ++ u8 buf[2] = { reg, val }; ++ ++ msg.addr = client->addr; ++ msg.flags = 0; ++ msg.len = sizeof(buf); ++ msg.buf = buf; ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) ++{ ++ struct i2c_msg msg; ++ u8 buf[3] = { reg }; ++ int ret; ++ ++ put_unaligned_be16(val, buf + 1); ++ ++ msg.addr = client->addr; ++ msg.flags = 0; ++ msg.len = sizeof(buf); ++ msg.buf = buf; ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++static int dw9719_detect(struct dw9719_device *dw9719) ++{ ++ int ret; ++ u8 val; ++ ++ ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); ++ if (ret < 0) ++ return ret; ++ ++ if (val != DW9719_ID) { ++ dev_err(dw9719->dev, "Failed to detect correct id\n"); ++ ret = -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static int dw9719_power_down(struct dw9719_device *dw9719) ++{ ++ return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); ++} ++ ++static int dw9719_power_up(struct dw9719_device *dw9719) ++{ ++ int ret; ++ ++ ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); ++ if (ret) ++ return ret; ++ ++ /* Jiggle SCL pin to wake up device */ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); ++ ++ /* Need 100us to transit from SHUTDOWN to STANDBY*/ ++ usleep_range(100, 1000); ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, ++ DW9719_ENABLE_RINGING); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, ++ DW9719_DEFAULT_VCM_FREQ); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ return 0; ++ ++fail_powerdown: ++ dw9719_power_down(dw9719); ++ return ret; ++} ++ ++static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) ++{ ++ int ret; ++ ++ value = clamp(value, 0, DW9719_MAX_FOCUS_POS); ++ ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct dw9719_device *dw9719 = container_of(ctrl->handler, ++ struct dw9719_device, ++ ctrls.handler); ++ int ret; ++ ++ /* Only apply changes to the controls if the device is powered up */ ++ if (!pm_runtime_get_if_in_use(dw9719->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_FOCUS_ABSOLUTE: ++ ret = dw9719_t_focus_abs(dw9719, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ pm_runtime_put(dw9719->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { ++ .s_ctrl = dw9719_set_ctrl, ++}; ++ ++static int __maybe_unused dw9719_suspend(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct dw9719_device *dw9719 = to_dw9719_device(sd); ++ int ret; ++ int val; ++ ++ for (val = dw9719->ctrls.focus->val; val >= 0; ++ val -= DW9719_CTRL_STEPS) { ++ ret = dw9719_t_focus_abs(dw9719, val); ++ if (ret) ++ return ret; ++ ++ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); ++ } ++ ++ return dw9719_power_down(dw9719); ++} ++ ++static int __maybe_unused dw9719_resume(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct dw9719_device *dw9719 = to_dw9719_device(sd); ++ int current_focus = dw9719->ctrls.focus->val; ++ int ret; ++ int val; ++ ++ ret = dw9719_power_up(dw9719); ++ if (ret) ++ return ret; ++ ++ for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; ++ val += DW9719_CTRL_STEPS) { ++ ret = dw9719_t_focus_abs(dw9719, val); ++ if (ret) ++ goto err_power_down; ++ ++ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); ++ } ++ ++ return 0; ++ ++err_power_down: ++ dw9719_power_down(dw9719); ++ return ret; ++} ++ ++static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ return pm_runtime_resume_and_get(sd->dev); ++} ++ ++static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ pm_runtime_put(sd->dev); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { ++ .open = dw9719_open, ++ .close = dw9719_close, ++}; ++ ++static int dw9719_init_controls(struct dw9719_device *dw9719) ++{ ++ const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; ++ int ret; ++ ++ ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); ++ if (ret) ++ return ret; ++ ++ dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, ++ V4L2_CID_FOCUS_ABSOLUTE, 0, ++ DW9719_MAX_FOCUS_POS, 1, 0); ++ ++ if (dw9719->ctrls.handler.error) { ++ dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); ++ ret = dw9719->ctrls.handler.error; ++ goto err_free_handler; ++ } ++ ++ dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; ++ ++ return ret; ++ ++err_free_handler: ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ return ret; ++} ++ ++static const struct v4l2_subdev_ops dw9719_ops = { }; ++ ++static int dw9719_probe(struct i2c_client *client) ++{ ++ struct dw9719_device *dw9719; ++ int ret; ++ ++ dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); ++ if (!dw9719) ++ return -ENOMEM; ++ ++ dw9719->client = client; ++ dw9719->dev = &client->dev; ++ ++ dw9719->regulators[0].supply = "vdd"; ++ /* ++ * The DW9719 has only the 1 VDD voltage input, but some PMICs such as ++ * the TPS68470 PMIC have I2C passthrough capability, to disconnect the ++ * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) ++ * is off, because some sensors then short these pins to ground; ++ * and the DW9719 might sit behind this passthrough, this it needs to ++ * enable VSIO as that will also enable the I2C passthrough. ++ */ ++ dw9719->regulators[1].supply = "vsio"; ++ ++ ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, ++ dw9719->regulators); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, "getting regulators\n"); ++ ++ v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); ++ dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ dw9719->sd.internal_ops = &dw9719_internal_ops; ++ ++ ret = dw9719_init_controls(dw9719); ++ if (ret) ++ return ret; ++ ++ ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); ++ if (ret < 0) ++ goto err_free_ctrl_handler; ++ ++ dw9719->sd.entity.function = MEDIA_ENT_F_LENS; ++ ++ /* ++ * We need the driver to work in the event that pm runtime is disable in ++ * the kernel, so power up and verify the chip now. In the event that ++ * runtime pm is disabled this will leave the chip on, so that the lens ++ * will work. ++ */ ++ ++ ret = dw9719_power_up(dw9719); ++ if (ret) ++ goto err_cleanup_media; ++ ++ ret = dw9719_detect(dw9719); ++ if (ret) ++ goto err_powerdown; ++ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_get_noresume(&client->dev); ++ pm_runtime_enable(&client->dev); ++ ++ ret = v4l2_async_register_subdev(&dw9719->sd); ++ if (ret < 0) ++ goto err_pm_runtime; ++ ++ pm_runtime_set_autosuspend_delay(&client->dev, 1000); ++ pm_runtime_use_autosuspend(&client->dev); ++ pm_runtime_put_autosuspend(&client->dev); ++ ++ return ret; ++ ++err_pm_runtime: ++ pm_runtime_disable(&client->dev); ++ pm_runtime_put_noidle(&client->dev); ++err_powerdown: ++ dw9719_power_down(dw9719); ++err_cleanup_media: ++ media_entity_cleanup(&dw9719->sd.entity); ++err_free_ctrl_handler: ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ ++ return ret; ++} ++ ++static void dw9719_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, ++ sd); ++ ++ pm_runtime_disable(&client->dev); ++ v4l2_async_unregister_subdev(sd); ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ media_entity_cleanup(&dw9719->sd.entity); ++} ++ ++static const struct i2c_device_id dw9719_id_table[] = { ++ { "dw9719" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, dw9719_id_table); ++ ++static const struct dev_pm_ops dw9719_pm_ops = { ++ SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) ++}; ++ ++static struct i2c_driver dw9719_i2c_driver = { ++ .driver = { ++ .name = "dw9719", ++ .pm = &dw9719_pm_ops, ++ }, ++ .probe_new = dw9719_probe, ++ .remove = dw9719_remove, ++ .id_table = dw9719_id_table, ++}; ++module_i2c_driver(dw9719_i2c_driver); ++ ++MODULE_AUTHOR("Daniel Scally "); ++MODULE_DESCRIPTION("DW9719 VCM Driver"); ++MODULE_LICENSE("GPL"); +-- +2.42.0 + +From ab6404e7e82a96ce092699fb5f2911e4cb3464c1 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 15 Jul 2022 23:48:00 +0200 +Subject: [PATCH] drivers/media/i2c: Fix DW9719 dependencies + +It should depend on VIDEO_DEV instead of VIDEO_V4L2. + +Signed-off-by: Maximilian Luz +Patchset: cameras +--- + drivers/media/i2c/Kconfig | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 6959ee1a89fb..1d5082fe9ce3 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -857,7 +857,7 @@ config VIDEO_DW9714 + + config VIDEO_DW9719 + tristate "DW9719 lens voice coil support" +- depends on I2C && VIDEO_V4L2 ++ depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC +-- +2.42.0 + +From ad4cef0cd9d3ef994cd33e3ec11fb101ed196a52 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 e33c2d75975c..c0c90ae66b70 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; + } + ++ /* ++ * 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 e1fe190e285e4899f0758ad94754d2c3523ae0e0 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 675fb37a6fea..43b30db08c9e 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 1bffb71a0630c59ccc0a1294bb24fce2f7d5a92e 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 b16b5f4cb91e..33739a979cbc 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -760,6 +760,10 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) + struct v4l2_async_notifier *notifier; + int ret; + ++ 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 +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index 4fa9225aa3d9..ed4c75253cbc 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1314,10 +1314,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_nf_init(notifier); + +- 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 657b91e305e9a9246672cd125a684c44ed4f183b 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 e3e1696e7f0e..423dc555093f 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 ee65106e9ec9aceaa81c73e6554cd7599cdfc448 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 7807fa329db0..2d2abb25b944 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 d2dbedc1bd6f2bb52a7f994e844668f730f1f4bd 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 6046dfeca16f..385c06e4f1d3 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -864,6 +864,18 @@ config LEDS_TPS6105X + It is a single boost converter primarily for white LEDs and + audio amplifiers. + ++config LEDS_TPS68470 ++ tristate "LED support for TI TPS68470" ++ depends on LEDS_CLASS ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver supports TPS68470 PMIC with LED chip. ++ It provides two LED controllers, with the ability to drive 2 ++ indicator LEDs and 2 flash LEDs. ++ ++ To compile this driver as a module, choose M and it will be ++ called leds-tps68470 ++ + config LEDS_IP30 + tristate "LED support for SGI Octane machines" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index d71f1226540c..e2002b535967 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -83,6 +83,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 ++obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o + 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 --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 000000000000..35aeb5db89c8 +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * LED driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2023 Red Hat ++ * ++ * Authors: ++ * Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define lcdev_to_led(led_cdev) \ ++ container_of(led_cdev, struct tps68470_led, lcdev) ++ ++#define led_to_tps68470(led, index) \ ++ container_of(led, struct tps68470_device, leds[index]) ++ ++enum tps68470_led_ids { ++ TPS68470_ILED_A, ++ TPS68470_ILED_B, ++ TPS68470_NUM_LEDS ++}; ++ ++static const char *tps68470_led_names[] = { ++ [TPS68470_ILED_A] = "tps68470-iled_a", ++ [TPS68470_ILED_B] = "tps68470-iled_b", ++}; ++ ++struct tps68470_led { ++ unsigned int led_id; ++ struct led_classdev lcdev; ++}; ++ ++struct tps68470_device { ++ struct device *dev; ++ struct regmap *regmap; ++ struct tps68470_led leds[TPS68470_NUM_LEDS]; ++}; ++ ++enum ctrlb_current { ++ CTRLB_2MA = 0, ++ CTRLB_4MA = 1, ++ CTRLB_8MA = 2, ++ CTRLB_16MA = 3, ++}; ++ ++static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, ++ brightness ? TPS68470_ILEDCTL_ENA : 0); ++ case TPS68470_ILED_B: ++ return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, ++ brightness ? TPS68470_ILEDCTL_ENB : 0); ++ } ++ return -EINVAL; ++} ++ ++static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct tps68470_led *led = lcdev_to_led(led_cdev); ++ struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); ++ struct regmap *regmap = tps68470->regmap; ++ int ret = 0; ++ int value = 0; ++ ++ ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); ++ if (ret) ++ return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); ++ ++ switch (led->led_id) { ++ case TPS68470_ILED_A: ++ value = value & TPS68470_ILEDCTL_ENA; ++ break; ++ case TPS68470_ILED_B: ++ value = value & TPS68470_ILEDCTL_ENB; ++ break; ++ } ++ ++ return value ? LED_ON : LED_OFF; ++} ++ ++ ++static int tps68470_ledb_current_init(struct platform_device *pdev, ++ struct tps68470_device *tps68470) ++{ ++ int ret = 0; ++ unsigned int curr; ++ ++ /* configure LEDB current if the properties can be got */ ++ if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { ++ if (curr > CTRLB_16MA) { ++ dev_err(&pdev->dev, ++ "Invalid LEDB current value: %d\n", ++ curr); ++ return -EINVAL; ++ } ++ ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, ++ TPS68470_ILEDCTL_CTRLB, curr); ++ } ++ return ret; ++} ++ ++static int tps68470_leds_probe(struct platform_device *pdev) ++{ ++ int i = 0; ++ int ret = 0; ++ struct tps68470_device *tps68470; ++ struct tps68470_led *led; ++ struct led_classdev *lcdev; ++ ++ tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), ++ GFP_KERNEL); ++ if (!tps68470) ++ return -ENOMEM; ++ ++ tps68470->dev = &pdev->dev; ++ tps68470->regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ led = &tps68470->leds[i]; ++ lcdev = &led->lcdev; ++ ++ led->led_id = i; ++ ++ lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", ++ tps68470_led_names[i], LED_FUNCTION_INDICATOR); ++ if (!lcdev->name) ++ return -ENOMEM; ++ ++ lcdev->max_brightness = 1; ++ lcdev->brightness = 0; ++ lcdev->brightness_set_blocking = tps68470_brightness_set; ++ lcdev->brightness_get = tps68470_brightness_get; ++ lcdev->dev = &pdev->dev; ++ ++ ret = devm_led_classdev_register(tps68470->dev, lcdev); ++ if (ret) { ++ dev_err_probe(tps68470->dev, ret, ++ "error registering led\n"); ++ goto err_exit; ++ } ++ ++ if (i == TPS68470_ILED_B) { ++ ret = tps68470_ledb_current_init(pdev, tps68470); ++ if (ret) ++ goto err_exit; ++ } ++ } ++ ++err_exit: ++ if (ret) { ++ for (i = 0; i < TPS68470_NUM_LEDS; i++) { ++ if (tps68470->leds[i].lcdev.name) ++ devm_led_classdev_unregister(&pdev->dev, ++ &tps68470->leds[i].lcdev); ++ } ++ } ++ ++ return ret; ++} ++static struct platform_driver tps68470_led_driver = { ++ .driver = { ++ .name = "tps68470-led", ++ }, ++ .probe = tps68470_leds_probe, ++}; ++ ++module_platform_driver(tps68470_led_driver); ++ ++MODULE_ALIAS("platform:tps68470-led"); ++MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); ++MODULE_LICENSE("GPL v2"); +-- +2.42.0 + +From 3b0867c107c3f3a20e0358e70050bad478582ed3 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 53369c57751e..1ec1a9015178 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1256,6 +1257,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1311,6 +1323,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); + ++ 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(); + +-- +2.42.0 + +From 5b47d68b33ac45102fe63dcd8354d7ba02037d04 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 1ec1a9015178..a7d40015e46a 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1259,12 +1259,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_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.42.0 + +From 236e9f12e98f7fb45f79b361a4e4850b3db38c90 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 | 35 ++++++++++++++++++++++++----------- + 1 file changed, 24 insertions(+), 11 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index e9b8e8305e23..944276934e7e 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 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) + { +@@ -480,15 +488,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, + + 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, +@@ -563,13 +570,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); +@@ -604,11 +616,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + return -ENODEV; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- return -ENODEV; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; +@@ -637,6 +644,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..4bb775c --- /dev/null +++ b/patches/nobara/mt76:-mt7921:-Disable-powersave-features-by-default.patch @@ -0,0 +1,43 @@ +From ca89780690f7492c2d357e0ed2213a1d027341ae Mon Sep 17 00:00:00 2001 +From: Sultan Alsawaf +Date: Sun, 29 May 2022 01:32:19 -0700 +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: Sultan Alsawaf +--- + drivers/net/wireless/mediatek/mt76/mt7921/init.c | 9 ++------- + 1 file changed, 2 insertions(+), 7 deletions(-) + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c +index 91fc41922d95..cfa0bb51004d 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c +@@ -99,7 +99,8 @@ + wiphy->n_iface_combinations = ARRAY_SIZE(if_comb); + } + wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP | +- WIPHY_FLAG_4ADDR_STATION); ++ WIPHY_FLAG_4ADDR_STATION | ++ WIPHY_FLAG_PS_ON_BY_DEFAULT); + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | +@@ -408,12 +409,6 @@ + dev->pm.idle_timeout = MT7921_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; diff --git a/patches/nobara/rog-ally-alsa.patch b/patches/nobara/rog-ally-alsa.patch new file mode 100644 index 0000000..6790d0e --- /dev/null +++ b/patches/nobara/rog-ally-alsa.patch @@ -0,0 +1,1007 @@ +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-bmc150.patch b/patches/nobara/rog-ally-bmc150.patch new file mode 100644 index 0000000..e83d6e3 --- /dev/null +++ b/patches/nobara/rog-ally-bmc150.patch @@ -0,0 +1,2672 @@ +From 622ea77bfccd751247b1c08c3126d7ab716f0423 Mon Sep 17 00:00:00 2001 +From: Denis +Date: Mon, 25 Sep 2023 03:38:49 +0200 +Subject: [PATCH] This commit adds support to the bmi323 device on top of the + pre-existing bmc150 kernel module. + +Some new devices for example the ROG Ally and the Air Plus identify this chip in the ACPI table as a bmc150 so previously the original module was loaded, +but was erroring out as it cannot handle such device. + +The device I own does not allow me to use the interrupt part of the device as the interrupt pin is not connected (or not advertised to be connected) hence +I avoided including on this commit anything related to IRQ. + +This driver has already been proved to work well enough to be used in the switch emulator "yuzu". + +While designing this module my main focus was not to alter the original driver and not to limit the original author in regard to future mofications, +and I was mostly able to achive this, except: +1) I added a new structure on top of the original one and added a field that is responsible for holding information +on what type of chip the module is currently managing +2) the previous point required the init function of the original driver to write that field in order to be sure no bmi323 code +was executed when the old part of the module is managing the device +3) as the original driver issued an i2c write on some register not really meant to be written in the bmi323 device I have made sure an i2c read to discover +the bmi323 is performed prior to that code: such read SHOULD fail in the older bmc150 IC for two reasons: + - the i2c address is not reported in the memory map of the bmc150 in its datasheet + - the i2c read attempts to get 4 bytes out of a 8-bit device + - the fourth bit (the one that cannot be read from a bmc150 device) is initialized to 0 and bmi323 presence is signaled with a 1 in the LSB + that is the fourth coming out of the device in temporal order +--- + drivers/iio/accel/bmc150-accel-core.c | 2307 ++++++++++++++++++++++++- + drivers/iio/accel/bmc150-accel-i2c.c | 100 +- + drivers/iio/accel/bmc150-accel.h | 94 +- + 3 files changed, 2495 insertions(+), 6 deletions(-) + +diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c +index 110591804b4c..9a2c1732c9ef 100644 +--- a/drivers/iio/accel/bmc150-accel-core.c ++++ b/drivers/iio/accel/bmc150-accel-core.c +@@ -130,6 +130,73 @@ + #define BMC150_ACCEL_REG_FIFO_DATA 0x3F + #define BMC150_ACCEL_FIFO_LENGTH 32 + ++#define BMC150_BMI323_TEMPER_CENTER_VAL 23 ++#define BMC150_BMI323_TEMPER_LSB_PER_KELVIN_VAL 512 ++ ++#define BMC150_BMI323_AUTO_SUSPEND_DELAY_MS 2000 ++ ++#define BMC150_BMI323_CHIP_ID_REG 0x00 ++#define BMC150_BMI323_SOFT_RESET_REG 0x7E ++#define BMC150_BMI323_SOFT_RESET_VAL 0xDEAFU ++#define BMC150_BMI323_DATA_BASE_REG 0x03 ++#define BMC150_BMI323_TEMPERATURE_DATA_REG 0x09 ++#define BMC150_BMI323_FIFO_FILL_LEVEL_REG 0x15 ++#define BMC150_BMI323_FIFO_DATA_REG 0x16 ++#define BMC150_BMI323_ACC_CONF_REG 0x20 ++#define BMC150_BMI323_GYR_CONF_REG 0x21 ++#define BMC150_BMI323_FIFO_CONF_REG 0x36 ++ ++// these are bits [0:3] of ACC_CONF.acc_odr, sample rate in Hz for the accel part of the chip ++#define BMC150_BMI323_ACCEL_ODR_0_78123_VAL 0x0001 ++#define BMC150_BMI323_ACCEL_ODR_1_5625_VAL 0x0002 ++#define BMC150_BMI323_ACCEL_ODR_3_125_VAL 0x0003 ++#define BMC150_BMI323_ACCEL_ODR_6_25_VAL 0x0004 ++#define BMC150_BMI323_ACCEL_ODR_12_5_VAL 0x0005 ++#define BMC150_BMI323_ACCEL_ODR_25_VAL 0x0006 ++#define BMC150_BMI323_ACCEL_ODR_50_VAL 0x0007 ++#define BMC150_BMI323_ACCEL_ODR_100_VAL 0x0008 ++#define BMC150_BMI323_ACCEL_ODR_200_VAL 0x0009 ++#define BMC150_BMI323_ACCEL_ODR_400_VAL 0x000A ++#define BMC150_BMI323_ACCEL_ODR_800_VAL 0x000B ++#define BMC150_BMI323_ACCEL_ODR_1600_VAL 0x000C ++#define BMC150_BMI323_ACCEL_ODR_3200_VAL 0x000D ++#define BMC150_BMI323_ACCEL_ODR_6400_VAL 0x000E ++ ++#define BMC150_BMI323_ACCEL_BW_ODR_2_VAL 0x0000 ++#define BMC150_BMI323_ACCEL_BW_ODR_4_VAL 0x0001 ++ ++// these are bits [4:6] of ACC_CONF.acc_range, full scale resolution ++#define BMC150_BMI323_ACCEL_RANGE_2_VAL 0x0000 // +/-2g, 16.38 LSB/mg ++#define BMC150_BMI323_ACCEL_RANGE_4_VAL 0x0001 // +/-4g, 8.19 LSB/mg ++#define BMC150_BMI323_ACCEL_RANGE_8_VAL 0x0002 // +/-8g, 4.10 LSB/mg ++#define BMC150_BMI323_ACCEL_RANGE_16_VAL 0x0003 // +/-4g, 2.05 LSB/mg ++ ++// these are bits [0:3] of GYR_CONF.gyr_odr, sample rate in Hz for the gyro part of the chip ++#define BMC150_BMI323_GYRO_ODR_0_78123_VAL 0x0001 ++#define BMC150_BMI323_GYRO_ODR_1_5625_VAL 0x0002 ++#define BMC150_BMI323_GYRO_ODR_3_125_VAL 0x0003 ++#define BMC150_BMI323_GYRO_ODR_6_25_VAL 0x0004 ++#define BMC150_BMI323_GYRO_ODR_12_5_VAL 0x0005 ++#define BMC150_BMI323_GYRO_ODR_25_VAL 0x0006 ++#define BMC150_BMI323_GYRO_ODR_50_VAL 0x0007 ++#define BMC150_BMI323_GYRO_ODR_100_VAL 0x0008 ++#define BMC150_BMI323_GYRO_ODR_200_VAL 0x0009 ++#define BMC150_BMI323_GYRO_ODR_400_VAL 0x000A ++#define BMC150_BMI323_GYRO_ODR_800_VAL 0x000B ++#define BMC150_BMI323_GYRO_ODR_1600_VAL 0x000C ++#define BMC150_BMI323_GYRO_ODR_3200_VAL 0x000D ++#define BMC150_BMI323_GYRO_ODR_6400_VAL 0x000E ++ ++#define BMC150_BMI323_GYRO_BW_ODR_2_VAL 0x0000 ++#define BMC150_BMI323_GYRO_BW_ODR_4_VAL 0x0001 ++ ++// these are bits [4:6] of GYR_CONF.gyr_range, full scale resolution ++#define BMC150_BMI323_GYRO_RANGE_125_VAL 0x0000 // +/-125°/s, 262.144 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_250_VAL 0x0001 // +/-250°/s, 131.2 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_500_VAL 0x0002 // +/-500°/s, 65.6 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_1000_VAL 0x0003 // +/-1000°/s, 32.8 LSB/°/s ++#define BMC150_BMI323_GYRO_RANGE_2000_VAL 0x0004 // +/-2000°/s, 16.4 LSB/°/s ++ + enum bmc150_accel_axis { + AXIS_X, + AXIS_Y, +@@ -149,6 +216,654 @@ struct bmc150_scale_info { + u8 reg_range; + }; + ++/* ++ * This enum MUST not be altered as there are parts in the code that ++ * uses an int conversion to get the correct device register to read. ++ */ ++enum bmi323_axis { ++ BMI323_ACCEL_AXIS_X = 0, ++ BMI323_ACCEL_AXIS_Y, ++ BMI323_ACCEL_AXIS_Z, ++ BMI323_GYRO_AXIS_X, ++ BMI323_GYRO_AXIS_Y, ++ BMI323_GYRO_AXIS_Z, ++ BMI323_TEMP, ++ BMI323_AXIS_MAX, ++}; ++ ++static const struct bmi323_scale_accel_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_accel_scale_map[] = { ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_2_VAL << (u16)4, ++ .val = 0, ++ .val2 = 598, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_4_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1196, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_8_VAL << (u16)4, ++ .val = 0, ++ .val2 = 2392, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_16_VAL << (u16)4, ++ .val = 0, ++ .val2 = 4785, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++}; ++ ++static const struct bmi323_scale_gyro_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_gyro_scale_map[] = { ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4, ++ .val = 0, ++ .val2 = 66545, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4, ++ .val = 0, ++ .val2 = 66, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_250_VAL << (u16)4, ++ .val = 0, ++ .val2 = 133090, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_250_VAL << (u16)4, ++ .val = 0, ++ .val2 = 133, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_500_VAL << (u16)4, ++ .val = 0, ++ .val2 = 266181, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_500_VAL << (u16)4, ++ .val = 0, ++ .val2 = 266, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_1000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 532362, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_1000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 532, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1064724, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ // this shouldn't be necessary, but iio seems to have a wrong rounding of this value... ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1064, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++ { ++ .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, ++ .val = 0, ++ .val2 = 1065, ++ .ret_type = IIO_VAL_INT_PLUS_NANO, ++ }, ++}; ++ ++/* ++ * this reflects the frequency map that is following. ++ * For each index i of that map index i*2 and i*2+1 of of this ++ * holds ODR/2 and ODR/4 ++ */ ++static const struct bmi323_3db_freq_cutoff_accel_info { ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_accel_3db_freq_cutoff[] = { ++ { ++ .val = 0, ++ .val2 = 390615, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 195308, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 781300, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 390650, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 562500, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 78125, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 250000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++}; ++ ++static const struct bmi323_freq_accel_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ s64 time_ns; ++} bmi323_accel_odr_map[] = { ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_0_78123_VAL, ++ .val = 0, ++ .val2 = 781230, ++ .time_ns = 1280032769, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_1_5625_VAL, ++ .val = 1, ++ .val2 = 562600, ++ .time_ns = 886522247, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_3_125_VAL, ++ .val = 3, ++ .val2 = 125000, ++ .time_ns = 320000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_6_25_VAL, ++ .val = 6, ++ .val2 = 250000, ++ .time_ns = 160000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_12_5_VAL, ++ .val = 12, ++ .val2 = 500000, ++ .time_ns = 80000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_25_VAL, ++ .val = 25, ++ .val2 = 0, ++ .time_ns = 40000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_50_VAL, ++ .val = 50, ++ .val2 = 0, ++ .time_ns = 20000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_100_VAL, ++ .val = 100, ++ .val2 = 0, ++ .time_ns = 10000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_200_VAL, ++ .val = 200, ++ .val2 = 0, ++ .time_ns = 5000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_400_VAL, ++ .val = 400, ++ .val2 = 0, ++ .time_ns = 2500000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_800_VAL, ++ .val = 800, ++ .val2 = 0, ++ .time_ns = 1250000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_1600_VAL, ++ .val = 1600, ++ .val2 = 0, ++ .time_ns = 625000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_3200_VAL, ++ .val = 3200, ++ .val2 = 0, ++ .time_ns = 312500, ++ }, ++ { ++ .hw_val = BMC150_BMI323_ACCEL_ODR_6400_VAL, ++ .val = 6400, ++ .val2 = 0, ++ .time_ns = 156250, ++ }, ++}; ++ ++static const struct bmi323_freq_gyro_info { ++ u8 hw_val; ++ int val; ++ int val2; ++ s64 time_ns; ++} bmi323_gyro_odr_map[] = { ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_0_78123_VAL, ++ .val = 0, ++ .val2 = 781230, ++ .time_ns = 1280032769, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_1_5625_VAL, ++ .val = 1, ++ .val2 = 562600, ++ .time_ns = 886522247, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_3_125_VAL, ++ .val = 3, ++ .val2 = 125000, ++ .time_ns = 320000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_6_25_VAL, ++ .val = 6, ++ .val2 = 250000, ++ .time_ns = 160000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_12_5_VAL, ++ .val = 12, ++ .val2 = 500000, ++ .time_ns = 80000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_25_VAL, ++ .val = 25, ++ .val2 = 0, ++ .time_ns = 40000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_50_VAL, ++ .val = 50, ++ .val2 = 0, ++ .time_ns = 20000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_100_VAL, ++ .val = 100, ++ .val2 = 0, ++ .time_ns = 10000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_200_VAL, ++ .val = 200, ++ .val2 = 0, ++ .time_ns = 5000000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_400_VAL, ++ .val = 400, ++ .val2 = 0, ++ .time_ns = 2500000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_800_VAL, ++ .val = 800, ++ .val2 = 0, ++ .time_ns = 1250000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_1600_VAL, ++ .val = 1600, ++ .val2 = 0, ++ .time_ns = 625000, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_3200_VAL, ++ .val = 3200, ++ .val2 = 0, ++ .time_ns = 312500, ++ }, ++ { ++ .hw_val = BMC150_BMI323_GYRO_ODR_6400_VAL, ++ .val = 6400, ++ .val2 = 0, ++ .time_ns = 156250, ++ }, ++}; ++ ++static const struct bmi323_3db_freq_cutoff_gyro_info { ++ int val; ++ int val2; ++ int ret_type; ++} bmi323_gyro_3db_freq_cutoff[] = { ++ { ++ .val = 0, ++ .val2 = 390615, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 1953075, // TODO: check if this gets reported correctly... ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 781300, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 390650, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 562500, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 0, ++ .val2 = 78125, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 6, ++ .val2 = 250000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 12, ++ .val2 = 500000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 25, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 50, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 100, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 400, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 800, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 3200, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++ { ++ .val = 1600, ++ .val2 = 000000, ++ .ret_type = IIO_VAL_INT_PLUS_MICRO, ++ }, ++}; ++ ++static const int bmi323_accel_scales[] = { ++ 0, 598, 0, 1196, 0, 2392, 0, 4785, ++}; ++ ++static const int bmi323_gyro_scales[] = { ++ 0, 66545, 0, 133090, 0, 266181, 0, 532362, 0, 1064724, ++}; ++ ++static const int bmi323_sample_freqs[] = { ++ 0, 781230, 1, 562600, 3, 125000, 6, 250000, 12, 500000, ++ 25, 0, 50, 0, 100, 0, 200, 0, 400, 0, ++ 800, 0, 1600, 0, 3200, 0, 6400, 0, ++}; ++ ++static const struct { ++ int val; ++ int val2; // IIO_VAL_INT_PLUS_MICRO ++ u8 bw_bits; ++} bmi323_samp_freq_table[] = { { 15, 620000, 0x08 }, { 31, 260000, 0x09 }, ++ { 62, 500000, 0x0A }, { 125, 0, 0x0B }, ++ { 250, 0, 0x0C }, { 500, 0, 0x0D }, ++ { 1000, 0, 0x0E }, { 2000, 0, 0x0F } }; ++ + struct bmc150_accel_chip_info { + const char *name; + u8 chip_id; +@@ -1113,6 +1828,52 @@ static const struct iio_event_spec bmc150_accel_event = { + .num_event_specs = 1 \ + } + ++#define BMI323_ACCEL_CHANNEL(_axis, bits) \ ++ { \ ++ .type = IIO_ACCEL, .modified = 1, .channel2 = IIO_MOD_##_axis, \ ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ ++ .info_mask_shared_by_type = \ ++ BIT(IIO_CHAN_INFO_SCALE) | \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ ++ .info_mask_shared_by_type_available = \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_SCALE), \ ++ .scan_index = BMI323_ACCEL_AXIS_##_axis, \ ++ .scan_type = { \ ++ .sign = 's', \ ++ .realbits = (bits), \ ++ .storagebits = 16, \ ++ .shift = 16 - (bits), \ ++ .endianness = IIO_LE, \ ++ }, \ ++ } ++ ++#define BMI323_GYRO_CHANNEL(_axis, bits) \ ++ { \ ++ .type = IIO_ANGL_VEL, .modified = 1, \ ++ .channel2 = IIO_MOD_##_axis, \ ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ ++ .info_mask_shared_by_type = \ ++ BIT(IIO_CHAN_INFO_SCALE) | \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ ++ .info_mask_shared_by_type_available = \ ++ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ ++ BIT(IIO_CHAN_INFO_SCALE), \ ++ .scan_index = BMI323_GYRO_AXIS_##_axis, \ ++ .scan_type = { \ ++ .sign = 's', \ ++ .realbits = (bits), \ ++ .storagebits = 16, \ ++ .shift = 16 - (bits), \ ++ .endianness = IIO_LE, \ ++ }, \ ++ /*.ext_info = bmi323_accel_ext_info,*/ \ ++ /*.event_spec = &bmi323_accel_event,*/ \ ++ /*.num_event_specs = 1*/ \ ++ } ++ + #define BMC150_ACCEL_CHANNELS(bits) { \ + { \ + .type = IIO_TEMP, \ +@@ -1595,7 +2356,7 @@ static int bmc150_accel_chip_init(struct bmc150_accel_data *data) + struct device *dev = regmap_get_device(data->regmap); + int ret, i; + unsigned int val; +- ++ + /* + * Reset chip to get it in a known good state. A delay of 1.8ms after + * reset is required according to the data sheets of supported chips. +@@ -1677,6 +2438,11 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + ++ /* ++ * Setting the dev_type here is necessary to avoid having it left uninitialized ++ * and therefore potentially executing bmi323 functions for the original bmc150 model. ++ */ ++ data->dev_type = BMC150; + data->regmap = regmap; + data->type = type; + +@@ -1826,12 +2592,1407 @@ void bmc150_accel_core_remove(struct device *dev) + } + EXPORT_SYMBOL_NS_GPL(bmc150_accel_core_remove, IIO_BMC150); + +-#ifdef CONFIG_PM_SLEEP +-static int bmc150_accel_suspend(struct device *dev) ++struct device *bmi323_get_managed_device(struct bmi323_private_data *bmi323) ++{ ++ if (bmi323->i2c_client != NULL) ++ return &bmi323->i2c_client->dev; ++ ++ return &bmi323->spi_client->dev; ++} ++ ++static int bmi323_set_power_state(struct bmi323_private_data *bmi323, bool on) ++{ ++#ifdef CONFIG_PM ++ struct device *dev = bmi323_get_managed_device(bmi323); ++ int ret; ++ ++ if (on) ++ ret = pm_runtime_get_sync(dev); ++ else { ++ pm_runtime_mark_last_busy(dev); ++ ret = pm_runtime_put_autosuspend(dev); ++ } ++ ++ if (ret < 0) { ++ dev_err(dev, "bmi323_set_power_state failed with %d\n", on); ++ ++ if (on) ++ pm_runtime_put_noidle(dev); ++ ++ return ret; ++ } ++#endif ++ ++ return 0; ++} ++ ++int bmi323_write_u16(struct bmi323_private_data *bmi323, u8 in_reg, ++ u16 in_value) ++{ ++ s32 ret; ++ ++ if (bmi323->i2c_client != NULL) { ++ ret = i2c_smbus_write_i2c_block_data(bmi323->i2c_client, in_reg, ++ sizeof(in_value), ++ (u8 *)(&in_value)); ++ if (ret != 0) { ++ return -2; ++ } ++ ++ return 0; ++ } else if (bmi323->spi_client != NULL) { ++ /* ++ * To whoever may need this: implementing this should be straightforward: ++ * it's specular to the i2c part. ++ */ ++ ++ return -EINVAL; // TODO: change with 0 once implemented ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_write_u16, IIO_BMC150); ++ ++int bmi323_read_u16(struct bmi323_private_data *bmi323, u8 in_reg, ++ u16 *out_value) ++{ ++ s32 ret; ++ u8 read_bytes[4]; ++ ++ if (bmi323->i2c_client != NULL) { ++ ret = i2c_smbus_read_i2c_block_data(bmi323->i2c_client, in_reg, ++ sizeof(read_bytes), ++ &read_bytes[0]); ++ if (ret != 4) { ++ return ret; ++ } ++ ++ // DUMMY = read_bytes[0] ++ // DUMMY = read_bytes[1] ++ // LSB = read_bytes[2] ++ // MSB = read_bytes[3] ++ u8 *o = (u8 *)out_value; ++ o[0] = read_bytes[2]; ++ o[1] = read_bytes[3]; ++ ++ return 0; ++ } else if (bmi323->spi_client != NULL) { ++ printk(KERN_CRIT ++ "bmi323: SPI interface is not yet implemented.\n"); ++ ++ /* ++ * To whoever may need this: implementing this should be straightforward: ++ * it's specular to the i2c part except that the dummy data is just 1 byte. ++ */ ++ ++ return -EINVAL; // TODO: change with 0 once implemented ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_read_u16, IIO_BMC150); ++ ++int bmi323_chip_check(struct bmi323_private_data *bmi323) ++{ ++ u16 chip_id; ++ int ret; ++ ++ ret = bmi323_read_u16(bmi323, BMC150_BMI323_CHIP_ID_REG, &chip_id); ++ if (ret != 0) { ++ return ret; ++ } ++ ++ if (((chip_id)&0x00FF) != cpu_to_le16((u16)0x0043U)) { ++ dev_err(bmi323->dev, ++ "bmi323_chip_check failed with: %d; chip_id = 0x%04x", ++ ret, chip_id); ++ ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_chip_check, IIO_BMC150); ++ ++static int bmi323_buffer_preenable(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ const int ret = bmi323_set_power_state(&data->bmi323, true); ++ ++ if (ret == 0) { ++ mutex_lock(&data->bmi323.mutex); ++ data->bmi323.fifo_frame_time_diff_ns = ++ (data->bmi323.acc_odr_time_ns >= ++ data->bmi323.gyr_odr_time_ns) ? ++ data->bmi323.acc_odr_time_ns : ++ data->bmi323.gyr_odr_time_ns; ++ mutex_unlock(&data->bmi323.mutex); ++ } ++ ++ return ret; ++} ++ ++static int bmi323_buffer_postenable(struct iio_dev *indio_dev) ++{ ++ //struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ /* ++ * This code is a placeholder until I can get a way to test it ++ */ ++ ++ return 0; ++} ++ ++static int bmi323_buffer_predisable(struct iio_dev *indio_dev) ++{ ++ //struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ /* ++ * This code is a placeholder until I can get a way to test it ++ */ ++ ++ return 0; ++} ++ ++static int bmi323_buffer_postdisable(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ return bmi323_set_power_state(&data->bmi323, true); ++} ++ ++static const struct iio_buffer_setup_ops bmi323_buffer_ops = { ++ .preenable = bmi323_buffer_preenable, ++ .postenable = bmi323_buffer_postenable, ++ .predisable = bmi323_buffer_predisable, ++ .postdisable = bmi323_buffer_postdisable, ++}; ++ ++int bmi323_chip_rst(struct bmi323_private_data *bmi323) ++{ ++ u16 sensor_status = 0x0000, device_status = 0x0000; ++ int ret; ++ ++ ret = bmi323_write_u16(bmi323, BMC150_BMI323_SOFT_RESET_REG, ++ cpu_to_le16((u16)BMC150_BMI323_SOFT_RESET_VAL)); ++ if (ret != 0) { ++ dev_err(bmi323->dev, ++ "bmi323: error while issuing the soft-reset command: %d", ++ ret); ++ return ret; ++ } ++ ++ /* wait the specified amount of time... I agree with the bmc150 module: better safe than sorry. */ ++ msleep(5); ++ ++ // if the device is connected over SPI a dummy read is to be performed once after each reset ++ if (bmi323->spi_client != NULL) { ++ dev_info(bmi323->dev, ++ "issuing the dummy read to switch mode to SPI"); ++ ++ // do not even check the result of that... it's just a dummy read ++ bmi323_chip_check(bmi323); ++ } ++ ++ ret = bmi323_chip_check(bmi323); ++ if (ret != 0) { ++ return ret; ++ } ++ ++ /* now check the correct initialization status as per datasheet */ ++ ret = bmi323_read_u16(bmi323, 0x01, &device_status); ++ if (ret != 0) { ++ return -EINVAL; ++ } ++ ++ if ((device_status & cpu_to_le16((u16)0x00FFU)) != ++ cpu_to_le16((u16)0x0000U)) { ++ dev_err(bmi323->dev, ++ "bmi323: device_status incorrect: %d; device_status = 0x%04x", ++ ret, device_status); ++ ++ /* from the datasheet: power error */ ++ return -EINVAL; ++ } ++ ++ /* from the datasheet: power ok */ ++ ret = bmi323_read_u16(bmi323, 0x02, &sensor_status); ++ if (ret != 0) { ++ return -EINVAL; ++ } ++ ++ if ((sensor_status & cpu_to_le16((u16)0x00FFU)) != ++ cpu_to_le16((u16)0x0001U)) { ++ dev_err(bmi323->dev, ++ "bmi323: sensor_status incorrect: %d; sensor_status = 0x%04x", ++ ret, sensor_status); ++ ++ /* from the datasheet: initialization error */ ++ return -EINVAL; ++ } ++ ++ /* from the datasheet: initialization ok */ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_chip_rst, IIO_BMC150); ++ ++static const struct iio_chan_spec bmi323_channels[] = { ++ BMI323_ACCEL_CHANNEL(X, 16), ++ BMI323_ACCEL_CHANNEL(Y, 16), ++ BMI323_ACCEL_CHANNEL(Z, 16), ++ BMI323_GYRO_CHANNEL(X, 16), ++ BMI323_GYRO_CHANNEL(Y, 16), ++ BMI323_GYRO_CHANNEL(Z, 16), ++ { ++ .type = IIO_TEMP, ++ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | ++ BIT(IIO_CHAN_INFO_SCALE) | ++ BIT(IIO_CHAN_INFO_OFFSET), ++ .scan_index = BMI323_TEMP, ++ }, ++ IIO_CHAN_SOFT_TIMESTAMP(BMI323_AXIS_MAX), ++}; ++ ++static int bmi323_read_raw(struct iio_dev *indio_dev, ++ struct iio_chan_spec const *chan, int *val, ++ int *val2, long mask) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ int ret = -EINVAL, was_sleep_modified = -1; ++ u16 raw_read = 0x8000; ++ ++ mutex_lock(&data->bmi323.mutex); ++ ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ dev_err(data->bmi323.dev, ++ "bmi323 error: device has not being woken up correctly."); ++ mutex_unlock(&data->bmi323.mutex); ++ return -EBUSY; ++ } ++ ++ switch (mask) { ++ case IIO_CHAN_INFO_RAW: { ++ switch (chan->type) { ++ case IIO_TEMP: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_read_raw_error; ++ } ++ ++ was_sleep_modified = ++ bmi323_set_power_state(&data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_read_raw_error_power; ++ } ++ ++ ret = iio_device_claim_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_TEMP iio_device_claim_direct_mode returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ ret = bmi323_read_u16( ++ &data->bmi323, ++ BMC150_BMI323_TEMPERATURE_DATA_REG, &raw_read); ++ iio_device_release_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_TEMP bmi323_read_u16 returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ *val = sign_extend32(le16_to_cpu(raw_read), 15); ++ bmi323_set_power_state(&data->bmi323, false); ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ case IIO_ACCEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_read_raw_error; ++ } ++ ++ was_sleep_modified = ++ bmi323_set_power_state(&data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_read_raw_error_power; ++ } ++ ++ ret = iio_device_claim_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ACCEL iio_device_claim_direct_mode returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ ret = bmi323_read_u16(&data->bmi323, ++ BMC150_BMI323_DATA_BASE_REG + ++ (u8)(chan->scan_index), ++ &raw_read); ++ iio_device_release_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ACCEL bmi323_read_u16 returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ *val = sign_extend32(le16_to_cpu(raw_read), 15); ++ bmi323_set_power_state(&data->bmi323, false); ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ case IIO_ANGL_VEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_read_raw_error; ++ } ++ ++ was_sleep_modified = ++ bmi323_set_power_state(&data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_read_raw_error_power; ++ } ++ ++ ret = iio_device_claim_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ANGL_VEL iio_device_claim_direct_mode returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ ret = bmi323_read_u16(&data->bmi323, ++ BMC150_BMI323_DATA_BASE_REG + ++ (u8)(chan->scan_index), ++ &raw_read); ++ iio_device_release_direct_mode(indio_dev); ++ if (ret != 0) { ++ printk(KERN_CRIT ++ "bmc150 bmi323_read_raw IIO_ANGL_VEL bmi323_read_u16 returned %d\n", ++ ret); ++ goto bmi323_read_raw_error; ++ } ++ ++ *val = sign_extend32(le16_to_cpu(raw_read), 15); ++ bmi323_set_power_state(&data->bmi323, false); ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ default: ++ goto bmi323_read_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_OFFSET: { ++ switch (chan->type) { ++ case IIO_TEMP: ++ *val = BMC150_BMI323_TEMPER_CENTER_VAL; ++ *val2 = 0; ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT; ++ ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_SCALE: ++ switch (chan->type) { ++ case IIO_TEMP: { ++ *val = 0; ++ *val2 = BMC150_BMI323_TEMPER_LSB_PER_KELVIN_VAL; ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_FRACTIONAL; ++ } ++ case IIO_ACCEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.acc_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_scale_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0b01110000U)) == ++ (bmi323_accel_scale_map[s].hw_val)) { ++ *val = bmi323_accel_scale_map[s].val; ++ *val2 = bmi323_accel_scale_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return bmi323_accel_scale_map[s] ++ .ret_type; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_ANGL_VEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.gyr_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_scale_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0b01110000U)) == ++ (bmi323_gyro_scale_map[s].hw_val)) { ++ *val = bmi323_gyro_scale_map[s].val; ++ *val2 = bmi323_gyro_scale_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return bmi323_gyro_scale_map[s].ret_type; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: ++ switch (chan->type) { ++ case IIO_ACCEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.acc_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_accel_odr_map[s].hw_val)) { ++ /* ++ * from tha datasheed: -3dB cut-off frequency can be configured with the bit 7 of GYR_confm, ++ * also called acc_bw that can either be 0 or 1, where 1 means odr/4 and 0 means odr/2 ++ */ ++ int freq_adj_idx = ++ (((le_raw_read[0]) & ++ ((u8)0x80U)) == (u8)0x00U) ? ++ (s * 2) + 0 : ++ (s * 2) + 1; ++ *val = bmi323_accel_3db_freq_cutoff ++ [freq_adj_idx] ++ .val; ++ *val2 = bmi323_accel_3db_freq_cutoff ++ [freq_adj_idx] ++ .val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_ANGL_VEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.gyr_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_gyro_odr_map[s].hw_val)) { ++ /* ++ * from tha datasheed: -3dB cut-off frequency can be configured with the bit 7 of GYR_confm, ++ * also called acc_bw that can either be 0 or 1, where 1 means odr/4 and 0 means odr/2 ++ */ ++ int freq_adj_idx = ++ (((le_raw_read[0]) & ++ ((u8)0x80U)) == (u8)0x0000U) ? ++ (s * 2) + 0 : ++ (s * 2) + 1; ++ *val = bmi323_gyro_3db_freq_cutoff ++ [freq_adj_idx] ++ .val; ++ *val2 = bmi323_gyro_3db_freq_cutoff ++ [freq_adj_idx] ++ .val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return bmi323_gyro_3db_freq_cutoff ++ [freq_adj_idx] ++ .ret_type; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: { ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_SAMP_FREQ: ++ switch (chan->type) { ++ case IIO_TEMP: { ++ ++ // while in normal or power mode the temperature sensur has a 50Hz sampling frequency ++ *val = 50; ++ *val2 = 0; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ case IIO_ACCEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.acc_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_accel_odr_map[s].hw_val)) { ++ *val = bmi323_accel_odr_map[s].val; ++ *val2 = bmi323_accel_odr_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ case IIO_ANGL_VEL: { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323.gyr_conf_reg_value; ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); ++ ++s) { ++ if (((le_raw_read[0]) & ((u16)0x0FU)) == ++ (bmi323_gyro_odr_map[s].hw_val)) { ++ *val = bmi323_gyro_odr_map[s].val; ++ *val2 = bmi323_gyro_odr_map[s].val2; ++ ++ mutex_unlock(&data->bmi323.mutex); ++ return IIO_VAL_INT_PLUS_MICRO; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ default: ++ ret = -EINVAL; ++ goto bmi323_read_raw_error; ++ } ++ ++bmi323_read_raw_error: ++ if (was_sleep_modified == 0) { ++ bmi323_set_power_state(&data->bmi323, false); ++ } ++ ++bmi323_read_raw_error_power: ++ mutex_unlock(&data->bmi323.mutex); ++ return ret; ++} ++ ++static int bmi323_write_raw(struct iio_dev *indio_dev, ++ struct iio_chan_spec const *chan, int val, int val2, ++ long mask) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ int ret = -EINVAL, was_sleep_modified = -1; ++ ++ mutex_lock(&data->bmi323.mutex); ++ ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ dev_err(data->bmi323.dev, ++ "bmi323 error: device has not being woken up correctly."); ++ mutex_unlock(&data->bmi323.mutex); ++ return -EBUSY; ++ } ++ ++ switch (mask) { ++ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: ++ switch (chan->type) { ++ default: { ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ } ++ case IIO_CHAN_INFO_SAMP_FREQ: ++ switch (chan->type) { ++ case IIO_ACCEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); ++ ++s) { ++ if ((bmi323_accel_odr_map[s].val == val) && ++ (bmi323_accel_odr_map[s].val2 == val2)) { ++ const u16 conf_backup = ++ data->bmi323.acc_conf_reg_value; ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .acc_conf_reg_value; ++ le_raw_read[0] &= (u8)0b11110000U; ++ le_raw_read[0] |= ++ ((u8)bmi323_gyro_odr_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ data->bmi323.acc_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ data->bmi323.acc_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error; ++ } ++ ++ data->bmi323.acc_odr_time_ns = ++ bmi323_accel_odr_map[s].time_ns; ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ case IIO_ANGL_VEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); ++ ++s) { ++ if ((bmi323_gyro_odr_map[s].val == val) && ++ (bmi323_gyro_odr_map[s].val2 == val2)) { ++ const u16 conf_backup = ++ data->bmi323.gyr_conf_reg_value; ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .gyr_conf_reg_value; ++ le_raw_read[0] &= (u8)0b11110000U; ++ le_raw_read[0] |= ++ ((u8)bmi323_gyro_odr_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ data->bmi323.gyr_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ data->bmi323.gyr_conf_reg_value = ++ conf_backup; ++ goto bmi323_write_raw_error; ++ } ++ ++ data->bmi323.gyr_odr_time_ns = ++ bmi323_gyro_odr_map[s].time_ns; ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ ++ /* Termometer also ends up here: its sampling frequency depends on the chip configuration and cannot be changed */ ++ default: ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ ++ break; ++ case IIO_CHAN_INFO_SCALE: ++ switch (chan->type) { ++ case IIO_ACCEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_accel_scale_map); ++ ++s) { ++ if ((bmi323_accel_scale_map[s].val == val) && ++ (bmi323_accel_scale_map[s].val2 == val2)) { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .acc_conf_reg_value; ++ le_raw_read[0] &= (u8)0b10001111U; ++ le_raw_read[0] |= ++ ((u8)bmi323_accel_scale_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_write_raw_error; ++ } ++ ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ dev_warn( ++ data->bmi323.dev, ++ "bmi323 error: accel scale val=%d,val2=%d unavailable: ignoring.", ++ val, val2); ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ case IIO_ANGL_VEL: ++ if (iio_buffer_enabled(indio_dev)) { ++ ret = -EBUSY; ++ goto bmi323_write_raw_error; ++ } ++ ++ for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_scale_map); ++ ++s) { ++ if ((bmi323_gyro_scale_map[s].val == val) && ++ (bmi323_gyro_scale_map[s].val2 == val2)) { ++ u8 *le_raw_read = ++ (u8 *)&data->bmi323 ++ .gyr_conf_reg_value; ++ le_raw_read[0] &= (u8)0b10001111U; ++ le_raw_read[0] |= ++ ((u8)bmi323_gyro_scale_map[s] ++ .hw_val); ++ ++ was_sleep_modified = ++ bmi323_set_power_state( ++ &data->bmi323, true); ++ if (was_sleep_modified != 0) { ++ ret = was_sleep_modified; ++ goto bmi323_write_raw_error_power; ++ } ++ ++ ret = bmi323_write_u16( ++ &data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_write_raw_error; ++ } ++ ++ bmi323_set_power_state(&data->bmi323, ++ false); ++ mutex_unlock(&data->bmi323.mutex); ++ return 0; ++ } ++ } ++ ++ dev_warn( ++ data->bmi323.dev, ++ "bmi323 error: gyro scale val=%d,val2=%d unavailable: ignoring.", ++ val, val2); ++ ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ ++ default: ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ ++ default: ++ ret = -EINVAL; ++ goto bmi323_write_raw_error; ++ } ++ ++bmi323_write_raw_error: ++ if (was_sleep_modified == 0) { ++ bmi323_set_power_state(&data->bmi323, false); ++ } ++ ++bmi323_write_raw_error_power: ++ mutex_unlock(&data->bmi323.mutex); ++ return ret; ++} ++ ++static int bmi323_read_avail(struct iio_dev *indio_dev, ++ struct iio_chan_spec const *chan, const int **vals, ++ int *type, int *length, long mask) ++{ ++ switch (mask) { ++ case IIO_CHAN_INFO_SCALE: ++ switch (chan->type) { ++ case IIO_ACCEL: ++ *type = IIO_VAL_INT_PLUS_MICRO; ++ *vals = bmi323_accel_scales; ++ *length = ARRAY_SIZE(bmi323_accel_scales); ++ return IIO_AVAIL_LIST; ++ case IIO_ANGL_VEL: ++ *type = IIO_VAL_INT_PLUS_NANO; ++ *vals = bmi323_gyro_scales; ++ *length = ARRAY_SIZE(bmi323_gyro_scales); ++ return IIO_AVAIL_LIST; ++ default: ++ return -EINVAL; ++ } ++ case IIO_CHAN_INFO_SAMP_FREQ: ++ *type = IIO_VAL_INT_PLUS_MICRO; ++ *vals = bmi323_sample_freqs; ++ *length = ARRAY_SIZE(bmi323_sample_freqs); ++ return IIO_AVAIL_LIST; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static const struct iio_info bmi323_accel_info = { ++ .read_raw = bmi323_read_raw, ++ .write_raw = bmi323_write_raw, ++ .read_avail = bmi323_read_avail, ++ //.hwfifo_flush_to_buffer = bmi323_fifo_flush, ++}; ++ ++static int bmi323_fifo_flush(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ int ret; ++ ++ ret = bmi323_write_u16(&data->bmi323, 0x37, cpu_to_le16(0x01)); ++ ++ return ret; ++} ++ ++static const u16 stub_value = 0x8000; ++ ++#define ADVANCE_AT_REQ_OR_AVAIL(req, avail, dst, dst_offset, src, src_offset) \ ++ if (req) { \ ++ if (gyr_avail) { \ ++ memcpy((void *)(dst + dst_offset), \ ++ (const void *)(src + src_offset), 2); \ ++ src_offset += 2; \ ++ } else { \ ++ memcpy((void *)(dst + dst_offset), \ ++ (const void *)((const u8 *)(&stub_value)), 2); \ ++ } \ ++ dst_offset += 2; \ ++ } else { \ ++ if (avail) { \ ++ src_offset += 2; \ ++ } \ ++ } ++ ++static irqreturn_t iio_bmi323_trigger_h(int irq, void *p) ++{ ++ printk(KERN_WARNING "bmi323 executed iio_bmi323_trigger_h"); ++ ++ struct iio_poll_func *pf = p; ++ struct iio_dev *indio_dev = pf->indio_dev; ++ struct bmc150_accel_data *indio_data = iio_priv(indio_dev); ++ ++ mutex_lock(&indio_data->bmi323.mutex); ++ ++ const bool temp_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000100000000000))) != 0); ++ const bool gyr_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000010000000000))) != 0); ++ const bool acc_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000001000000000))) != 0); ++ const bool time_avail = ((indio_data->bmi323.fifo_conf_reg_value & ++ (cpu_to_le16(0b0000000100000000))) != 0); ++ ++ /* Calculate the number of bytes for a frame */ ++ const u16 frames_aggregate_size_in_words = ++ /* 2 * */ ((temp_avail ? 1 : 0) + (gyr_avail ? 3 : 0) + ++ (acc_avail ? 3 : 0) + (time_avail ? 1 : 0)); ++ ++ u16 available_words = 0; ++ const int available_words_read_res = bmi323_read_u16( ++ &indio_data->bmi323, BMC150_BMI323_FIFO_FILL_LEVEL_REG, ++ &available_words); ++ if (available_words_read_res != 0) { ++ goto bmi323_irq_done; ++ } ++ ++ const u16 available_frame_aggregates = (le16_to_cpu(available_words)) / ++ (frames_aggregate_size_in_words); ++ ++ const s64 current_timestamp_ns = iio_get_time_ns(indio_dev); ++ const s64 fifo_frame_time_ns = ++ indio_data->bmi323.fifo_frame_time_diff_ns; ++ const s64 first_sample_timestamp_ns = ++ current_timestamp_ns - ++ (fifo_frame_time_ns * (s64)(available_frame_aggregates)); ++ ++ /* This can hold one full block */ ++ u8 temp_data[16]; ++ ++ /* This is fifo data as read from the sensor */ ++ u8 fifo_data[32]; ++ ++ /* ++ | CHANNEL | scan_index ++ |============================ ++ | | | ++ | ACCEL_X | 0 | ++ | ACCEL_Y | 1 | ++ | ACCEL_Y | 2 | ++ | GYRO_X | 3 | ++ | GYRO_Y | 4 | ++ | GYRO_Z | 5 | ++ | TEMP | 6 | ++ | TIMESTAMP | ? | ++ */ ++ bool accel_x_requested = false; ++ bool accel_y_requested = false; ++ bool accel_z_requested = false; ++ bool gyro_x_requested = false; ++ bool gyro_y_requested = false; ++ bool gyro_z_requested = false; ++ bool temp_requested = false; ++ ++ int j = 0; ++ for_each_set_bit(j, indio_dev->active_scan_mask, ++ indio_dev->masklength) { ++ switch (j) { ++ case 0: ++ accel_x_requested = true; ++ break; ++ case 1: ++ accel_y_requested = true; ++ break; ++ case 2: ++ accel_z_requested = true; ++ break; ++ case 3: ++ gyro_x_requested = true; ++ break; ++ case 4: ++ gyro_y_requested = true; ++ break; ++ case 5: ++ gyro_z_requested = true; ++ break; ++ case 6: ++ temp_requested = true; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ u16 current_fifo_buffer_offset_bytes = 0; ++ for (u16 f = 0; f < available_frame_aggregates; ++f) { ++ u16 current_sample_buffer_offset = 0; ++ ++ /* Read data from the raw device */ ++ if (indio_data->bmi323.i2c_client != NULL) { ++ const int bytes_to_read = ++ 2 + (2 * frames_aggregate_size_in_words); ++ int read_block_ret = i2c_smbus_read_i2c_block_data( ++ indio_data->bmi323.i2c_client, ++ BMC150_BMI323_FIFO_DATA_REG, bytes_to_read, ++ &fifo_data[0]); ++ if (read_block_ret < bytes_to_read) { ++ dev_warn( ++ &indio_data->bmi323.i2c_client->dev, ++ "bmi323: i2c_smbus_read_i2c_block_data wrong return: expected %d bytes, %d arrived. Doing what is possible with recovered data.\n", ++ bytes_to_read, read_block_ret); ++ ++ /* at this point FIFO buffer must be flushed to avoid interpreting data incorrectly the next trigger */ ++ const int flush_res = ++ bmi323_fifo_flush(indio_dev); ++ if (flush_res != 0) { ++ dev_err(&indio_data->bmi323.i2c_client ++ ->dev, ++ "bmi323: Could not flush FIFO (%d). Following buffer data might be corrupted.\n", ++ flush_res); ++ } ++ ++ goto bmi323_irq_done; ++ } ++ ++ /* Discard 2-bytes dummy data from I2C */ ++ current_fifo_buffer_offset_bytes = 2; ++ } else if (indio_data->bmi323.spi_client != NULL) { ++ printk(KERN_CRIT ++ "bmi323: SPI interface is not yet implemented.\n"); ++ ++ /* ++ * To whoever may need this: implementing this should be straightforward: ++ * it's specular to the i2c part. ++ */ ++ ++ /* Discard 1-byte dummy data from SPI */ ++ current_fifo_buffer_offset_bytes = 1; ++ ++ goto bmi323_irq_done; ++ } ++ ++ ADVANCE_AT_REQ_OR_AVAIL(accel_x_requested, acc_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(accel_y_requested, acc_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(accel_z_requested, acc_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(gyro_x_requested, gyr_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(gyro_y_requested, gyr_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(gyro_z_requested, gyr_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ADVANCE_AT_REQ_OR_AVAIL(temp_requested, temp_avail, ++ (u8 *)&temp_data[0], ++ current_sample_buffer_offset, ++ (u8 *)&fifo_data[0], ++ current_fifo_buffer_offset_bytes); ++ ++#ifdef BMC150_BMI232_DEBUG_EN ++ /* The following is code only used for debugging */ ++ u16 timestamp = 0; ++ if (time_avail) { ++ memcpy((u8 *)×tamp, ++ (const u8 ++ *)(&fifo_data ++ [current_fifo_buffer_offset_bytes]), ++ 2); ++ current_fifo_buffer_offset_bytes += 2; ++ } ++ ++ u16 *debg = (u16 *)&temp_data[0]; ++ if (!time_avail) { ++ printk(KERN_WARNING ++ "bmi323 pushing to buffer %d/%d -- accel: %d %d %d gyro: %d %d %d", ++ (int)(f + 1), (int)available_frame_aggregates, ++ (int)(*((s16 *)&debg[0])), ++ (int)(*((s16 *)&debg[1])), ++ (int)(*((s16 *)&debg[2])), ++ (int)(*((s16 *)&debg[3])), ++ (int)(*((s16 *)&debg[4])), ++ (int)(*((s16 *)&debg[5]))); ++ } else { ++ printk(KERN_WARNING ++ "bmi323 pushing to buffer %d/%d -- time: %d accel: %d %d %d gyro: %d %d %d", ++ (int)(f + 1), (int)available_frame_aggregates, ++ (int)timestamp, (int)(*((s16 *)&debg[0])), ++ (int)(*((s16 *)&debg[1])), ++ (int)(*((s16 *)&debg[2])), ++ (int)(*((s16 *)&debg[3])), ++ (int)(*((s16 *)&debg[4])), ++ (int)(*((s16 *)&debg[5]))); ++ } ++#endif ++ ++ iio_push_to_buffers_with_timestamp( ++ indio_dev, &temp_data[0], ++ first_sample_timestamp_ns + ++ (fifo_frame_time_ns * (s64)j)); ++ } ++ ++bmi323_irq_done: ++ mutex_unlock(&indio_data->bmi323.mutex); ++ ++ /* ++ * Tell the core we are done with this trigger and ready for the ++ * next one. ++ */ ++ iio_trigger_notify_done(indio_dev->trig); ++ ++ return IRQ_HANDLED; ++} ++ ++int bmi323_set_trigger_state(struct iio_trigger *trig, bool state) ++{ ++ return 0; ++} ++ ++/* ++// The following is meant to be used in a IRQ-enabled hardware ++static const struct iio_trigger_ops time_trigger_ops = { ++ .set_trigger_state = &bmi323_set_trigger_state, ++ //.reenable = NULL, ++ .validate_device = &iio_trigger_validate_own_device, ++}; ++*/ ++ ++/* ++ * A very basic scan mask: everything can work in conjunction with everything else so no need to worry about ++ * managing conbinations of mutually exclusive data sources... ++ */ ++static const unsigned long bmi323_accel_scan_masks[] = { ++ BIT(BMI323_ACCEL_AXIS_X) | BIT(BMI323_ACCEL_AXIS_Y) | ++ BIT(BMI323_ACCEL_AXIS_Z) | BIT(BMI323_GYRO_AXIS_X) | ++ BIT(BMI323_GYRO_AXIS_Y) | ++ BIT(BMI323_GYRO_AXIS_Z) /*| BIT(BMI323_TEMP)*/, ++ 0 ++}; ++ ++int bmi323_iio_init(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ struct irq_data *irq_desc = NULL; ++ ++ if (data->bmi323.i2c_client != NULL) { ++ data->bmi323.dev = &data->bmi323.i2c_client->dev; ++ } else if (data->bmi323.spi_client != NULL) { ++ data->bmi323.dev = &data->bmi323.spi_client->dev; ++ } else { ++ return -ENODEV; ++ } ++ ++ int ret = 0; ++ ++ /* change to 8 for a default 200Hz sampling rate */ ++ const int gyr_odr_conf_idx = 7; ++ const int acc_odr_conf_idx = 7; ++ ++ mutex_init(&data->bmi323.mutex); ++ ++ data->bmi323.acc_odr_time_ns = ++ bmi323_accel_odr_map[acc_odr_conf_idx].time_ns; ++ data->bmi323.gyr_odr_time_ns = ++ bmi323_gyro_odr_map[gyr_odr_conf_idx].time_ns; ++ ++ // FIFO enabled for gyro, accel and temp. Overwrite older samples. ++ data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0F00U); ++ //data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0E00U); ++ //data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0600U); // working ++ ++ // now set the (default) normal mode... ++ // normal mode: 0x4000 ++ // no averaging: 0x0000 ++ data->bmi323.acc_conf_reg_value = cpu_to_le16( ++ 0x4000 | ((u16)BMC150_BMI323_ACCEL_RANGE_2_VAL << (u16)4U) | ++ ((u16)bmi323_accel_odr_map[acc_odr_conf_idx].hw_val)); ++ ++ // now set the (default) normal mode... ++ // normal mode: 0x4000 ++ // no averaging: 0x0000 ++ // filtering to ODR/2: 0x0000 ++ data->bmi323.gyr_conf_reg_value = cpu_to_le16( ++ 0x4000 | ((u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4U) | ++ ((u16)bmi323_gyro_odr_map[gyr_odr_conf_idx].hw_val)); ++ ++ // the datasheet states that FIFO buffer MUST be enabled before enabling any sensor ++ ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_FIFO_CONF_REG, ++ data->bmi323.fifo_conf_reg_value); ++ if (ret != 0) { ++ return -1; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ return -1; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ return -2; ++ } ++ ++ indio_dev->channels = bmi323_channels; ++ indio_dev->num_channels = ARRAY_SIZE(bmi323_channels); ++ indio_dev->name = "bmi323"; ++ indio_dev->available_scan_masks = bmi323_accel_scan_masks; ++ indio_dev->modes = INDIO_DIRECT_MODE; ++ indio_dev->info = &bmi323_accel_info; ++ indio_dev->label = "bmi323-accel_base"; ++ ++ if (data->bmi323.irq > 0) { ++ dev_info(data->bmi323.dev, "IRQ pin reported as connected: %d", ++ data->bmi323.irq); ++ ++ irq_desc = irq_get_irq_data(data->bmi323.irq); ++ if (!irq_desc) { ++ dev_err(data->bmi323.dev, ++ "Could not find IRQ %d. ignoring it.\n", ++ data->bmi323.irq); ++ goto bmi323_iio_init_missing_irq_pin; ++ } ++ ++ //data->bmi323.trig[0] = devm_iio_trigger_alloc(data->bmi323.dev, "trig-fifo_full-%s-%d", indio_dev->name, iio_device_id(indio_dev)); ++ //if (data->bmi323.trig[0] == NULL) { ++ // ret = -ENOMEM; ++ // goto bmi323_iio_init_err_trigger_unregister; ++ //} ++ // ++ //data->bmi323.trig[0]->ops = &time_trigger_ops; ++ //iio_trigger_set_drvdata(data->bmi323.trig[0], indio_dev); ++ //ret = devm_iio_trigger_register(data->bmi323.dev, data->bmi323.trig[0]); ++ //if (ret) { ++ // dev_err(data->bmi323.dev, "iio trigger register failed\n"); ++ // goto bmi323_iio_init_err_trigger_unregister; ++ //} ++ ++ /* ++ * register triggers BEFORE buffer setup so that they are cleared ++ * on emergence exit by bmi323_iio_init_err_trigger_unregister. ++ * ++ * This is just a placeholder until I can get my hands on a bmi323 ++ * device that has the IRQ pin actually connected to the CPU. ++ */ ++ ++ /* here resume operation with the module part common to irq and non-irq enabled code. */ ++ goto bmi323_iio_init_common_irq_and_not_irq; ++ } ++ ++bmi323_iio_init_missing_irq_pin: ++ dev_info( ++ data->bmi323.dev, ++ "IRQ pin NOT connected (irq=%d). Will continue normally without triggers.", ++ data->bmi323.irq); ++ ++bmi323_iio_init_common_irq_and_not_irq: ++ ++ /* Once orientation matrix is implemented switch this to iio_triggered_buffer_setup_ext. */ ++ ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, ++ iio_bmi323_trigger_h, ++ &bmi323_buffer_ops); ++ if (ret < 0) { ++ dev_err(data->bmi323.dev, ++ "Failed: iio triggered buffer setup: %d\n", ret); ++ goto bmi323_iio_init_err_trigger_unregister; ++ } ++ ++ ret = pm_runtime_set_active(data->bmi323.dev); ++ if (ret) { ++ dev_err(data->bmi323.dev, ++ "bmi323 unable to initialize runtime PD: pm_runtime_set_active returned %d\n", ++ ret); ++ goto bmi323_iio_init_err_buffer_cleanup; ++ } ++ ++ pm_runtime_enable(data->bmi323.dev); ++ pm_runtime_set_autosuspend_delay(data->bmi323.dev, ++ BMC150_BMI323_AUTO_SUSPEND_DELAY_MS); ++ pm_runtime_use_autosuspend(data->bmi323.dev); ++ ++ ret = iio_device_register(indio_dev); ++ if (ret < 0) { ++ dev_err(data->bmi323.dev, ++ "bmi323 unable to register iio device: %d\n", ret); ++ goto bmi323_iio_init_err_pm_cleanup; ++ } ++ ++ return 0; ++ ++bmi323_iio_init_err_pm_cleanup: ++ pm_runtime_dont_use_autosuspend(data->bmi323.dev); ++ pm_runtime_disable(data->bmi323.dev); ++bmi323_iio_init_err_buffer_cleanup: ++ iio_triggered_buffer_cleanup(indio_dev); ++bmi323_iio_init_err_trigger_unregister: ++ /* ++ * unregister triggers if they have been setup already. ++ * iio_trigger_unregister shall be used in that regard. ++ * ++ * This is just a placeholder until I can get my hands on a bmi323 ++ * device that has the IRQ pin actually connected to the CPU. ++ */ ++ //if (data->bmi323.trig[0] != NULL) { ++ // iio_trigger_unregister(data->bmi323.trig[0]); ++ //} ++ ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_iio_init, IIO_BMC150); ++ ++void bmi323_iio_deinit(struct iio_dev *indio_dev) ++{ ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ struct device *dev = bmi323_get_managed_device(&data->bmi323); ++ ++ iio_device_unregister(indio_dev); ++ ++ pm_runtime_disable(dev); ++ pm_runtime_set_suspended(dev); ++ pm_runtime_put_noidle(dev); ++ ++ iio_triggered_buffer_cleanup(indio_dev); ++ ++ //iio_device_free(indio_dev); // this isn't done in the bmg160 driver nor in other drivers so I guess I shouldn't do it too ++ ++ mutex_unlock(&data->bmi323.mutex); ++ bmi323_chip_rst(&data->bmi323); ++ mutex_unlock(&data->bmi323.mutex); ++} ++EXPORT_SYMBOL_NS_GPL(bmi323_iio_deinit, IIO_BMC150); ++ ++#ifdef CONFIG_PM_SLEEP ++static int bmc150_accel_suspend(struct device *dev) + { + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + ++ if (data->dev_type == BMI323) { ++ int ret; ++ ++ //dev_warn(dev, "bmi323 suspending driver..."); ++ ++ // here push the register GYRO & ACCEL configuration and issue a reset so that chip goes to sleep mode (the default one after a reset) ++ mutex_unlock(&data->bmi323.mutex); ++ ++ ret = bmi323_chip_rst(&data->bmi323); ++ mutex_unlock(&data->bmi323.mutex); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 error in suspend on bmi323_chip_rst: %d\n", ++ ret); ++ data->bmi323.flags |= BMI323_FLAGS_RESET_FAILED; ++ return -EAGAIN; ++ } ++ ++ return 0; ++ } ++ + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + mutex_unlock(&data->mutex); +@@ -1844,6 +4005,63 @@ static int bmc150_accel_resume(struct device *dev) + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + ++ if (data->dev_type == BMI323) { ++ int ret; ++ ++ //dev_warn(dev, "bmi323 resuming driver..."); ++ ++ // here pop the register GYRO & ACCEL configuration and issue a reset so that chip goes to sleep mode (the default one after a reset) ++ mutex_lock(&data->bmi323.mutex); ++ ++ // this was done already in runtime_sleep function. ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ ret = bmi323_chip_rst(&data->bmi323); ++ if (ret == 0) { ++ data->bmi323.flags &= ++ ~BMI323_FLAGS_RESET_FAILED; ++ } else { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_FIFO_CONF_REG, ++ data->bmi323.fifo_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ goto bmi323_bmc150_accel_resume_terminate; ++ } ++ ++bmi323_bmc150_accel_resume_terminate: ++ mutex_unlock(&data->bmi323.mutex); ++ if (ret != 0) { ++ return -EAGAIN; ++ } ++ ++ /* ++ * datasheet says "Start-up time": suspend to high performance mode is tipically 30ms, ++ * however when setting this to 32 or even higher the first reading from the gyro (unlike accel part) ++ * is actually the (wrong) default value 0x8000 so it is better to sleep a bit longer ++ * to prevent issues and give time to the sensor to pick up first readings... ++ */ ++ msleep_interruptible(64); ++ ++ return 0; ++ } ++ + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + bmc150_accel_fifo_set_mode(data); +@@ -1863,6 +4081,25 @@ static int bmc150_accel_runtime_suspend(struct device *dev) + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + ++ if (data->dev_type == BMI323) { ++ //dev_warn(dev, "bmi323 suspending runtime..."); ++ ++ /* ++ * Every operation requiring this function have the mutex locked already: ++ * with mutex_lock(&data->bmi323.mutex); ++ */ ++ ret = bmi323_chip_rst(&data->bmi323); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 error in runtime_suspend on bmi323_chip_rst: %d\n", ++ ret); ++ data->bmi323.flags |= BMI323_FLAGS_RESET_FAILED; ++ return -EAGAIN; ++ } ++ ++ return 0; ++ } ++ + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + if (ret < 0) + return -EAGAIN; +@@ -1877,6 +4114,70 @@ static int bmc150_accel_runtime_resume(struct device *dev) + int ret; + int sleep_val; + ++ if (data->dev_type == BMI323) { ++ //dev_warn(dev, "bmi323 resuming runtime..."); ++ ++ /* ++ * Every operation requiring this function have the mutex locked already: ++ * with mutex_lock(&data->bmi323.mutex); ++ */ ++ ++ // recover from a bad state if it was left that way on reuntime_suspend ++ if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { ++ ret = bmi323_chip_rst(&data->bmi323); ++ if (ret == 0) { ++ data->bmi323.flags &= ++ ~BMI323_FLAGS_RESET_FAILED; ++ } else { ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_FIFO_CONF_REG, ++ data->bmi323.fifo_conf_reg_value); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 writing to GYR_CONF register failed"); ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_GYR_CONF_REG, ++ data->bmi323.gyr_conf_reg_value); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 writing to GYR_CONF register failed"); ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ ++ ret = bmi323_write_u16(&data->bmi323, ++ BMC150_BMI323_ACC_CONF_REG, ++ data->bmi323.acc_conf_reg_value); ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 writing to ACC_CONF register failed"); ++ goto bmi323_bmc150_accel_runtime_resume_terminate; ++ } ++ ++bmi323_bmc150_accel_runtime_resume_terminate: ++ if (ret != 0) { ++ dev_err(dev, ++ "bmi323 bmc150_accel_runtime_resume -EAGAIN"); ++ return -EAGAIN; ++ } ++ ++ /* ++ * datasheet says "Start-up time": suspend to high performance mode is tipically 30ms, ++ * however when setting this to 32 or even higher the first reading from the gyro (unlike accel part) ++ * is actually the (wrong) default value 0x8000 so it is better to sleep a bit longer ++ * to prevent issues and give time to the sensor to pick up first readings... ++ */ ++ msleep_interruptible(64); ++ ++ return 0; ++ } ++ + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + if (ret < 0) + return ret; +diff --git a/drivers/iio/accel/bmc150-accel-i2c.c b/drivers/iio/accel/bmc150-accel-i2c.c +index ee1ba134ad42..0d6ee304b3e7 100644 +--- a/drivers/iio/accel/bmc150-accel-i2c.c ++++ b/drivers/iio/accel/bmc150-accel-i2c.c +@@ -173,15 +173,102 @@ static void bmc150_acpi_dual_accel_remove(struct i2c_client *client) {} + + static int bmc150_accel_probe(struct i2c_client *client) + { ++ int ret; ++ u8 chip_id_first[4] = { 0x00, 0x00, 0x00, 0x00 }; ++ enum bmc150_device_type dev_type = BMC150; + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name = NULL; + enum bmc150_type type = BOSCH_UNKNOWN; ++ ++ /* reads 4 bytes (2 dummy + 2 good) from the i2c CHIP_ID device register */ ++ ret = i2c_smbus_read_i2c_block_data(client, 0x00, 4, &chip_id_first[0]); ++ if (ret != 4) { ++ dev_info( ++ &client->dev, ++ "error checking if the bmc150 is in fact a bmi323: i2c_smbus_read_i2c_block_data = %d: reg = 0x%02x.\n\tIt probably is a bmc150 as correctly reported by the ACPI entry.", ++ (int)ret, 0x00); ++ goto bmi150_old_probe; ++ } ++ ++ // at this point we have enough data to know what chip we are handling ++ dev_type = (chip_id_first[2] == 0x43) ? BMI323 : dev_type; ++ ++ if (dev_type == BMI323) { ++ dev_warn( ++ &client->dev, ++ "bmc323: what the ACPI table reported as a bmc150 is in fact a bmc323\n"); ++ ++ struct iio_dev *indio_dev = devm_iio_device_alloc( ++ &client->dev, sizeof(struct bmc150_accel_data)); ++ if (!indio_dev) { ++ dev_err(&client->dev, ++ "bmc323 init process failed: out of memory\n"); ++ ++ return -ENOMEM; ++ } ++ ++ dev_set_drvdata(&client->dev, indio_dev); ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ data->dev_type = dev_type; ++ ++ struct bmi323_private_data *bmi323_data = &data->bmi323; ++ bmi323_data->i2c_client = client; ++ bmi323_data->spi_client = NULL; ++ bmi323_data->irq = client->irq; ++ ++ /* ++ * VDD is the analog and digital domain voltage supply ++ * VDDIO is the digital I/O voltage supply ++ */ ++ bmi323_data->regulators[0].supply = "vdd"; ++ bmi323_data->regulators[1].supply = "vddio"; ++ ret = devm_regulator_bulk_get( ++ &client->dev, ARRAY_SIZE(bmi323_data->regulators), ++ bmi323_data->regulators); ++ if (ret) { ++ return dev_err_probe(&client->dev, ret, ++ "failed to get regulators\n"); ++ } ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(bmi323_data->regulators), ++ bmi323_data->regulators); ++ if (ret) { ++ iio_device_free(indio_dev); ++ ++ dev_err(&client->dev, ++ "failed to enable regulators: %d\n", ret); ++ return ret; ++ } ++ ++ ret = bmi323_chip_rst(bmi323_data); ++ if (ret != 0) { ++ dev_err(&client->dev, ++ "bmc323: error issuing the chip reset: %d\n", ++ ret); ++ return ret; ++ } ++ ++ dev_info( ++ &client->dev, ++ "bmc323: chip reset success: starting the iio subsystem binding\n"); ++ ++ ret = bmi323_iio_init(indio_dev); ++ if (ret != 0) { ++ return ret; ++ } ++ ++ return 0; ++ } ++ ++bmi150_old_probe: ++ dev_info(&client->dev, ++ "executing the normal procedure for a bmc150..."); ++ + bool block_supported = + i2c_check_functionality(client->adapter, I2C_FUNC_I2C) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK); +- int ret; + + regmap = devm_regmap_init_i2c(client, &bmc150_regmap_conf); + if (IS_ERR(regmap)) { +@@ -198,7 +285,7 @@ static int bmc150_accel_probe(struct i2c_client *client) + type, name, block_supported); + if (ret) + return ret; +- ++ + /* + * The !id check avoids recursion when probe() gets called + * for the second client. +@@ -211,6 +298,15 @@ static int bmc150_accel_probe(struct i2c_client *client) + + static void bmc150_accel_remove(struct i2c_client *client) + { ++ struct iio_dev *indio_dev = dev_get_drvdata(&client->dev); ++ struct bmc150_accel_data *data = iio_priv(indio_dev); ++ ++ if (data->dev_type == BMI323) { ++ bmi323_iio_deinit(indio_dev); ++ ++ return; ++ } ++ + bmc150_acpi_dual_accel_remove(client); + + bmc150_accel_core_remove(&client->dev); +diff --git a/drivers/iio/accel/bmc150-accel.h b/drivers/iio/accel/bmc150-accel.h +index 7775c5edaeef..65ec208960df 100644 +--- a/drivers/iio/accel/bmc150-accel.h ++++ b/drivers/iio/accel/bmc150-accel.h +@@ -8,6 +8,14 @@ + #include + #include + ++/* ++ * the bmi323 needs raw access to spi and i2c: I cannot use regmap ++ * as this device expects i2c writes to be 2 bytes, ++ * spi reads to be 3 bytes and i2c reads to be 4 bytes. ++ */ ++#include ++#include ++ + struct regmap; + struct i2c_client; + struct bmc150_accel_chip_info; +@@ -34,6 +42,11 @@ struct bmc150_accel_interrupt { + atomic_t users; + }; + ++enum bmc150_device_type { ++ BMC150, ++ BMI323, ++}; ++ + struct bmc150_accel_trigger { + struct bmc150_accel_data *data; + struct iio_trigger *indio_trig; +@@ -55,6 +68,25 @@ enum bmc150_accel_trigger_id { + BMC150_ACCEL_TRIGGERS, + }; + ++#define BMI323_FLAGS_RESET_FAILED 0x00000001U ++ ++struct bmi323_private_data { ++ struct regulator_bulk_data regulators[2]; ++ struct i2c_client *i2c_client; ++ struct spi_device *spi_client; ++ struct device *dev; /* pointer at i2c_client->dev or spi_client->dev */ ++ struct mutex mutex; ++ int irq; ++ u32 flags; ++ u16 acc_conf_reg_value; ++ u16 gyr_conf_reg_value; ++ u16 fifo_conf_reg_value; ++ struct iio_trigger *trig[1]; ++ s64 fifo_frame_time_diff_ns; ++ s64 acc_odr_time_ns; ++ s64 gyr_odr_time_ns; ++}; ++ + struct bmc150_accel_data { + struct regmap *regmap; + struct regulator_bulk_data regulators[2]; +@@ -83,7 +115,67 @@ struct bmc150_accel_data { + void (*resume_callback)(struct device *dev); + struct delayed_work resume_work; + struct iio_mount_matrix orientation; +-}; ++ enum bmc150_device_type dev_type; ++ struct bmi323_private_data bmi323; ++ }; ++ ++/** ++ * This function performs a write of a u16 little-endian (regardless of CPU architecture) integer ++ * to a device register. Returns 0 on success or an error code otherwise. ++ * ++ * PRE: in_value holds the data to be sent to the sensor, in little endian format even on big endian ++ * architectures. ++ * ++ * NOTE: bmi323->dev can be NULL (not yet initialized) when this function is called ++ * therefore it is not needed and is not used inside the function ++ * ++ * WARNING: this function does not lock any mutex and synchronization MUST be performed by the caller ++ */ ++int bmi323_write_u16(struct bmi323_private_data *bmi323, u8 in_reg, u16 in_value); ++ ++/** ++ * This function performs a read of "good" values from the bmi323 discarding what ++ * in the datasheet is described as "dummy data": additional useles bytes. ++ * ++ * PRE: bmi323 has been partially initialized: i2c_device and spi_devices MUST be set to either ++ * the correct value or NULL ++ * ++ * NOTE: bmi323->dev can be NULL (not yet initialized) when this function is called ++ * therefore it is not needed and is not used inside the function ++ * ++ * POST: on success out_value is written with data from the sensor, as it came out, so the ++ * content is little-endian even on big endian architectures ++ * ++ * WARNING: this function does not lock any mutex and synchronization MUST be performed by the caller ++ */ ++int bmi323_read_u16(struct bmi323_private_data *bmi323, u8 in_reg, u16* out_value); ++ ++int bmi323_chip_check(struct bmi323_private_data *bmi323); ++ ++/** ++ * Reset the chip in a known state that is ready to accept commands, but is not configured therefore after calling this function ++ * it is required to load a new configuration to start data acquisition. ++ * ++ * PRE: bmi323 has been fully identified and partially initialized ++ * ++ * NOTE: after issuing a reset the the chip will be in what it is called "suspended mode" and the feature angine is ++ * ready to be set. This mode has everything disabled and consumes aroud 15uA. ++ * ++ * When removing the driver or suspend has been requested it's best to reset the chip so that power consumption ++ * will be the lowest possible. ++ */ ++int bmi323_chip_rst(struct bmi323_private_data *bmi323); ++ ++/** ++ * This function MUST be called in probe and is responsible for registering the userspace sysfs. ++ * ++ * The indio_dev MUST have been allocated but not registered. This function will perform userspace registration. ++ * ++ * @param indio_dev the industrual io device already allocated but not yet registered ++ */ ++int bmi323_iio_init(struct iio_dev *indio_dev); ++ ++void bmi323_iio_deinit(struct iio_dev *indio_dev); + + int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, + enum bmc150_type type, const char *name, +-- +2.42.0 + diff --git a/patches/nobara/rog-ally-side-keys-fix.patch b/patches/nobara/rog-ally-side-keys-fix.patch new file mode 100644 index 0000000..4559566 --- /dev/null +++ b/patches/nobara/rog-ally-side-keys-fix.patch @@ -0,0 +1,51 @@ +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/set-ps4-bt-poll-rate-1000hz.patch b/patches/nobara/set-ps4-bt-poll-rate-1000hz.patch new file mode 100644 index 0000000..8431cf7 --- /dev/null +++ b/patches/nobara/set-ps4-bt-poll-rate-1000hz.patch @@ -0,0 +1,27 @@ +From 0f2c07ab93dca496a1f34399ad2ff8a954690a72 Mon Sep 17 00:00:00 2001 +From: GloriousEggroll +Date: Mon, 29 May 2023 17:15:14 -0600 +Subject: [PATCH] set ds controller bluetooth pollrate to 1 ms + +--- + drivers/hid/hid-playstation.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c +index 8ac8f7b8e..1130663c3 100644 +--- a/drivers/hid/hid-playstation.c ++++ b/drivers/hid/hid-playstation.c +@@ -330,8 +330,8 @@ struct dualsense_output_report { + * 0x3F - disabled + */ + #define DS4_OUTPUT_HWCTL_BT_POLL_MASK 0x3F +-/* Default to 4ms poll interval, which is same as USB (not adjustable). */ +-#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 4 ++/* Default to 1ms poll interval (1000Hz, lower latency). */ ++#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 1 + #define DS4_OUTPUT_HWCTL_CRC32 0x40 + #define DS4_OUTPUT_HWCTL_HID 0x80 + +-- +2.40.1 + diff --git a/patches/nobara/steam-deck.patch b/patches/nobara/steam-deck.patch new file mode 100644 index 0000000..696f642 --- /dev/null +++ b/patches/nobara/steam-deck.patch @@ -0,0 +1,2575 @@ +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 b899859fe49cccda9e8739d29d883dbd6dd057f3 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 20ebaf7b44ff03078cf53e43306d6c5a3d0613e6 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 146e98d6f595e3a4e6c09a00ee9ec2d48a6703cb 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 4b1dd1ebfd2d3f123212e1296d304909e5b3a406 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 187582492c359d56865759f120214cfe6fa4ed50 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 d4490c88bed06b4c18af4a6029d67374df5218e1 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 58a8667b251984ecc85a503c5dec3fc8f98028ff 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 + + +From 7460867bd78651a6187ac44c73d1be653c09973b Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Fri, 24 Mar 2023 10:42:27 -0700 +Subject: [PATCH 08/10] Input: xpad - fix support for some third-party + controllers + +Some third-party controllers, such as the HORPIAD FPS for Nintendo Switch and +Gamesir-G3w, require a specific packet that the first-party XInput driver sends +before it will start sending reports. It's not currently known what this packet +does, but since the first-party driver always sends it's unlikely that this +could cause issues with existing controllers. + +Co-authored-by: Andrey Smirnov +Signed-off-by: Vicki Pfau +Link: https://lore.kernel.org/r/20230324040446.3487725-3-vi@endrift.com +Signed-off-by: Dmitry Torokhov +--- + drivers/input/joystick/xpad.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c +index cdb193317c3b6..fc680b45f936e 100644 +--- a/drivers/input/joystick/xpad.c ++++ b/drivers/input/joystick/xpad.c +@@ -264,6 +264,7 @@ static const struct xpad_device { + { 0x0f0d, 0x0067, "HORIPAD ONE", 0, XTYPE_XBOXONE }, + { 0x0f0d, 0x0078, "Hori Real Arcade Pro V Kai Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, + { 0x0f0d, 0x00c5, "Hori Fighting Commander ONE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, ++ { 0x0f0d, 0x00dc, "HORIPAD FPS for Nintendo Switch", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 }, + { 0x0f30, 0x010b, "Philips Recoil", 0, XTYPE_XBOX }, + { 0x0f30, 0x0202, "Joytech Advanced Controller", 0, XTYPE_XBOX }, + { 0x0f30, 0x8888, "BigBen XBMiniPad Controller", 0, XTYPE_XBOX }, +@@ -1720,6 +1721,27 @@ static int xpad_start_input(struct usb_xpad *xpad) + return error; + } + } ++ if (xpad->xtype == XTYPE_XBOX360) { ++ /* ++ * Some third-party controllers Xbox 360-style controllers ++ * require this message to finish initialization. ++ */ ++ u8 dummy[20]; ++ ++ error = usb_control_msg_recv(xpad->udev, 0, ++ /* bRequest */ 0x01, ++ /* bmRequestType */ ++ USB_TYPE_VENDOR | USB_DIR_IN | ++ USB_RECIP_INTERFACE, ++ /* wValue */ 0x100, ++ /* wIndex */ 0x00, ++ dummy, sizeof(dummy), ++ 25, GFP_KERNEL); ++ if (error) ++ dev_warn(&xpad->dev->dev, ++ "unable to receive magic message: %d\n", ++ error); ++ } + + return 0; + } +-- +2.41.0 + + +From 469ab7efd0383f60e83c086347526273ed1d1a33 Mon Sep 17 00:00:00 2001 +From: Timothee Besset +Date: Mon, 22 May 2023 20:25:57 -0500 +Subject: [PATCH 09/10] Input: xpad - Add GameSir VID for Xbox One controllers + +Co-authored-by: Sam Lantinga +Signed-off-by: Sam Lantinga +Signed-off-by: Vicki Pfau +--- + drivers/input/joystick/xpad.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c +index fc680b45f936e..bb2f69faa2a81 100644 +--- a/drivers/input/joystick/xpad.c ++++ b/drivers/input/joystick/xpad.c +@@ -500,6 +500,7 @@ static const struct usb_device_id xpad_table[] = { + XPAD_XBOX360_VENDOR(0x2f24), /* GameSir controllers */ + XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */ + XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */ ++ XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */ + { } + }; + +-- +2.41.0 + + +From 4fd74c574f8554056facabd4e36e5e397f2e6b98 Mon Sep 17 00:00:00 2001 +From: Jonathan Frederick +Date: Fri, 7 Jul 2023 15:11:33 -0700 +Subject: [PATCH 10/10] Input: xpad - add GameSir T4 Kaleid Controller support + +Add VID and PID to the xpad_device table to allow driver +to use the GameSir T4 Kaleid Controller, which is +XTYPE_XBOX360 compatible in xinput mode. + +Signed-off-by: Jonathan Frederick +Link: https://lore.kernel.org/r/ZKeKSbP3faIPv5jB@dbj-hp-flip +Signed-off-by: Dmitry Torokhov +--- + drivers/input/joystick/xpad.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c +index bb2f69faa2a81..ede380551e55c 100644 +--- a/drivers/input/joystick/xpad.c ++++ b/drivers/input/joystick/xpad.c +@@ -366,6 +366,7 @@ static const struct xpad_device { + { 0x31e3, 0x1300, "Wooting 60HE (AVR)", 0, XTYPE_XBOX360 }, + { 0x31e3, 0x1310, "Wooting 60HE (ARM)", 0, XTYPE_XBOX360 }, + { 0x3285, 0x0607, "Nacon GC-100", 0, XTYPE_XBOX360 }, ++ { 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 }, + { 0x3767, 0x0101, "Fanatec Speedster 3 Forceshock Wheel", 0, XTYPE_XBOX }, + { 0xffff, 0xffff, "Chinese-made Xbox Controller", 0, XTYPE_XBOX }, + { 0x0000, 0x0000, "Generic X-Box pad", 0, XTYPE_UNKNOWN } +@@ -500,6 +501,7 @@ static const struct usb_device_id xpad_table[] = { + XPAD_XBOX360_VENDOR(0x2f24), /* GameSir controllers */ + XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */ + XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */ ++ XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */ + XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */ + { } + }; +-- +2.41.0 + + diff --git a/patches/series b/patches/series old mode 100755 new mode 100644 index 1c48323..48b3c99 --- a/patches/series +++ b/patches/series @@ -1,10 +1,23 @@ -# Cachy patches are here: https://github.com/CachyOS/kernel-patches/ -# orig patch from cachy - 0001-cachyos-base-all.patch -0001-cachy-all.patch -0002-eevdf.patch -0002-eevdfbore.patch -# Nobara patches are here: https://github.com/sammilucia/nobara-kernel-fork -# Allow setting custom pollrates for usb devices -0004-Allow-to-set-custom-USB-pollrate-for-specific-device.patch -# Allow pre polaris cards to use the amdgpu kernel module -0005-amdgpu-si-cik-default.patch +### These patches Have some conflicts to be fixed later for now the conflicts are solved in a mega patch +#0001-cachyos-base-all.patch +#0002-eevdf.patch +#0002-eevdfbore.patch +#0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch +#amdgpu-si-cik-default.patch +#0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch +#0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch +#0001-acpi-proc-idle-skip-dummy-wait.patch +#0001-drm-i915-quirks-disable-async-flipping-on-specific-d.patch +#0002-drm-i915-add-kernel-parameter-to-disable-async-page-.patch +#OpenRGB.patch +#asus-linux.patch +#chimera-ALSA.patch +#lenovo-legion-laptop.patch +#linux-surface.patch +#mt76:-mt7921:-Disable-powersave-features-by-default.patch +#rog-ally-alsa.patch +#rog-ally-bmc150.patch +#rog-ally-side-keys-fix.patch +#set-ps4-bt-poll-rate-1000hz.patch +#steam-deck.patch +0001-pikaos-base-all.patch