diff --git a/VERSION b/VERSION index 10abd6a..5f6c086 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.6.6 +6.8.1 diff --git a/config b/config index e193761..53b954e 100644 --- a/config +++ b/config @@ -1,30 +1,29 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/x86 6.6.0 Kernel Configuration +# Linux/x86 6.8.1 Kernel Configuration # CONFIG_CC_VERSION_TEXT="gcc (GCC) 13.2.1 20230801" CONFIG_CC_IS_GCC=y CONFIG_GCC_VERSION=130201 CONFIG_CLANG_VERSION=0 CONFIG_AS_IS_GNU=y -CONFIG_AS_VERSION=24100 +CONFIG_AS_VERSION=24200 CONFIG_LD_IS_BFD=y -CONFIG_LD_VERSION=24100 +CONFIG_LD_VERSION=24200 CONFIG_LLD_VERSION=0 CONFIG_RUST_IS_AVAILABLE=y CONFIG_CC_CAN_LINK=y CONFIG_CC_CAN_LINK_STATIC=y CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y CONFIG_CC_HAS_ASM_GOTO_TIED_OUTPUT=y +CONFIG_GCC_ASM_GOTO_OUTPUT_WORKAROUND=y CONFIG_TOOLS_SUPPORT_RELR=y CONFIG_CC_HAS_ASM_INLINE=y CONFIG_CC_HAS_NO_PROFILE_FN_ATTR=y -CONFIG_PAHOLE_VERSION=125 +CONFIG_PAHOLE_VERSION=126 CONFIG_IRQ_WORK=y CONFIG_BUILDTIME_TABLE_SORT=y CONFIG_THREAD_INFO_IN_TASK=y -# CONFIG_TT_SCHED is not set -# CONFIG_TT_ACCOUNTING_STATS is not set # # General setup @@ -139,6 +138,7 @@ CONFIG_PREEMPT_COUNT=y CONFIG_PREEMPTION=y CONFIG_PREEMPT_DYNAMIC=y CONFIG_SCHED_CORE=y +# CONFIG_SCHED_CLASS_EXT is not set # # CPU/Task time and stats accounting @@ -201,15 +201,16 @@ CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y # CONFIG_UCLAMP_TASK=y CONFIG_UCLAMP_BUCKETS_COUNT=5 -# CONFIG_SCHED_ALT is not set # end of Scheduler features CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y CONFIG_CC_HAS_INT128=y CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough=5" -CONFIG_GCC11_NO_ARRAY_BOUNDS=y +CONFIG_GCC10_NO_ARRAY_BOUNDS=y CONFIG_CC_NO_ARRAY_BOUNDS=y +CONFIG_GCC_NO_STRINGOP_OVERFLOW=y +CONFIG_CC_NO_STRINGOP_OVERFLOW=y CONFIG_ARCH_SUPPORTS_INT128=y CONFIG_NUMA_BALANCING=y CONFIG_NUMA_BALANCING_DEFAULT_ENABLED=y @@ -224,6 +225,7 @@ CONFIG_CGROUP_SCHED=y CONFIG_FAIR_GROUP_SCHED=y CONFIG_CFS_BANDWIDTH=y # CONFIG_RT_GROUP_SCHED is not set +# CONFIG_EXT_GROUP_SCHED is not set CONFIG_SCHED_MM_CID=y CONFIG_UCLAMP_TASK_GROUP=y CONFIG_CGROUP_PIDS=y @@ -296,15 +298,15 @@ CONFIG_AIO=y CONFIG_IO_URING=y CONFIG_ADVISE_SYSCALLS=y CONFIG_MEMBARRIER=y +CONFIG_KCMP=y +CONFIG_RSEQ=y +CONFIG_CACHESTAT_SYSCALL=y CONFIG_KALLSYMS=y # CONFIG_KALLSYMS_SELFTEST is not set CONFIG_KALLSYMS_ALL=y CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y CONFIG_KALLSYMS_BASE_RELATIVE=y CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y -CONFIG_KCMP=y -CONFIG_RSEQ=y -CONFIG_CACHESTAT_SYSCALL=y CONFIG_HAVE_PERF_EVENTS=y CONFIG_GUEST_PERF_EVENTS=y @@ -456,6 +458,7 @@ CONFIG_X86_INTERNODE_CACHE_SHIFT=6 CONFIG_X86_L1_CACHE_SHIFT=6 CONFIG_X86_USE_PPRO_CHECKSUM=y CONFIG_X86_TSC=y +CONFIG_X86_HAVE_PAE=y CONFIG_X86_CMPXCHG64=y CONFIG_X86_CMOV=y CONFIG_X86_MINIMUM_CPU_FAMILY=64 @@ -571,6 +574,7 @@ 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_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION=y CONFIG_PHYSICAL_START=0x1000000 CONFIG_RELOCATABLE=y CONFIG_RANDOMIZE_BASE=y @@ -612,6 +616,7 @@ CONFIG_CPU_IBRS_ENTRY=y CONFIG_CPU_SRSO=y CONFIG_SLS=y # CONFIG_GDS_FORCE_MITIGATION is not set +CONFIG_MITIGATION_RFDS=y CONFIG_ARCH_HAS_ADD_PAGES=y # @@ -647,6 +652,7 @@ CONFIG_ACPI_LEGACY_TABLES_LOOKUP=y CONFIG_ARCH_MIGHT_HAVE_ACPI_PDC=y CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT=y CONFIG_ACPI_TABLE_LIB=y +CONFIG_ACPI_THERMAL_LIB=y # CONFIG_ACPI_DEBUGGER is not set CONFIG_ACPI_SPCR_TABLE=y CONFIG_ACPI_FPDT=y @@ -786,6 +792,7 @@ CONFIG_AMD_NB=y # Binary Emulations # CONFIG_IA32_EMULATION=y +# CONFIG_IA32_EMULATION_DEFAULT_DISABLED is not set # CONFIG_X86_X32_ABI is not set CONFIG_COMPAT_32=y CONFIG_COMPAT=y @@ -793,14 +800,13 @@ CONFIG_COMPAT_FOR_U64_ALIGNMENT=y # end of Binary Emulations CONFIG_HAVE_KVM=y +CONFIG_KVM_COMMON=y CONFIG_HAVE_KVM_PFNCACHE=y CONFIG_HAVE_KVM_IRQCHIP=y -CONFIG_HAVE_KVM_IRQFD=y CONFIG_HAVE_KVM_IRQ_ROUTING=y CONFIG_HAVE_KVM_DIRTY_RING=y CONFIG_HAVE_KVM_DIRTY_RING_TSO=y CONFIG_HAVE_KVM_DIRTY_RING_ACQ_REL=y -CONFIG_HAVE_KVM_EVENTFD=y CONFIG_KVM_MMIO=y CONFIG_KVM_ASYNC_PF=y CONFIG_HAVE_KVM_MSI=y @@ -813,6 +819,7 @@ CONFIG_HAVE_KVM_NO_POLL=y CONFIG_KVM_XFER_TO_GUEST_WORK=y CONFIG_HAVE_KVM_PM_NOTIFIER=y CONFIG_KVM_GENERIC_HARDWARE_ENABLING=y +CONFIG_KVM_GENERIC_MMU_NOTIFIER=y CONFIG_VIRTUALIZATION=y CONFIG_KVM=m CONFIG_KVM_INTEL=m @@ -820,8 +827,10 @@ CONFIG_X86_SGX_KVM=y CONFIG_KVM_AMD=m CONFIG_KVM_AMD_SEV=y CONFIG_KVM_SMM=y +CONFIG_KVM_HYPERV=y CONFIG_KVM_XEN=y CONFIG_KVM_EXTERNAL_WRITE_TRACKING=y +CONFIG_KVM_MAX_NR_VCPUS=1024 CONFIG_AS_AVX512=y CONFIG_AS_SHA1_NI=y CONFIG_AS_SHA256_NI=y @@ -868,6 +877,7 @@ CONFIG_ARCH_HAS_FORTIFY_SOURCE=y CONFIG_ARCH_HAS_SET_MEMORY=y CONFIG_ARCH_HAS_SET_DIRECT_MAP=y CONFIG_ARCH_HAS_CPU_FINALIZE_INIT=y +CONFIG_ARCH_HAS_CPU_PASID=y CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y CONFIG_ARCH_WANTS_NO_INSTR=y @@ -939,9 +949,9 @@ CONFIG_SOFTIRQ_ON_OWN_STACK=y CONFIG_ARCH_HAS_ELF_RANDOMIZE=y CONFIG_HAVE_ARCH_MMAP_RND_BITS=y CONFIG_HAVE_EXIT_THREAD=y -CONFIG_ARCH_MMAP_RND_BITS=28 +CONFIG_ARCH_MMAP_RND_BITS=32 CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS=y -CONFIG_ARCH_MMAP_RND_COMPAT_BITS=8 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS=16 CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES=y CONFIG_PAGE_SIZE_LESS_THAN_64KB=y CONFIG_PAGE_SIZE_LESS_THAN_256KB=y @@ -981,6 +991,7 @@ CONFIG_ARCH_HAS_ELFCORE_COMPAT=y CONFIG_ARCH_HAS_PARANOID_L1D_FLUSH=y CONFIG_DYNAMIC_SIGFRAME=y CONFIG_HAVE_ARCH_NODE_DEV_GROUP=y +CONFIG_ARCH_HAS_HW_PTE_YOUNG=y CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG=y # @@ -1013,11 +1024,12 @@ CONFIG_MODULE_SRCVERSION_ALL=y CONFIG_MODULE_SIG=y # CONFIG_MODULE_SIG_FORCE is not set CONFIG_MODULE_SIG_ALL=y -# CONFIG_MODULE_SIG_SHA1 is not set -# CONFIG_MODULE_SIG_SHA224 is not set # CONFIG_MODULE_SIG_SHA256 is not set # CONFIG_MODULE_SIG_SHA384 is not set CONFIG_MODULE_SIG_SHA512=y +# CONFIG_MODULE_SIG_SHA3_256 is not set +# CONFIG_MODULE_SIG_SHA3_384 is not set +# CONFIG_MODULE_SIG_SHA3_512 is not set CONFIG_MODULE_SIG_HASH="sha512" # CONFIG_MODULE_COMPRESS_NONE is not set # CONFIG_MODULE_COMPRESS_GZIP is not set @@ -1037,6 +1049,7 @@ CONFIG_BLK_ICQ=y CONFIG_BLK_DEV_BSGLIB=y CONFIG_BLK_DEV_INTEGRITY=y CONFIG_BLK_DEV_INTEGRITY_T10=y +CONFIG_BLK_DEV_WRITE_MOUNTED=y CONFIG_BLK_DEV_ZONED=y CONFIG_BLK_DEV_THROTTLING=y CONFIG_BLK_DEV_THROTTLING_LOW=y @@ -1131,6 +1144,7 @@ CONFIG_SWAP=y CONFIG_ZSWAP=y CONFIG_ZSWAP_DEFAULT_ON=y # CONFIG_ZSWAP_EXCLUSIVE_LOADS_DEFAULT_ON is not set +CONFIG_ZSWAP_SHRINKER_DEFAULT_ON=y # CONFIG_ZSWAP_COMPRESSOR_DEFAULT_DEFLATE is not set # CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZO is not set # CONFIG_ZSWAP_COMPRESSOR_DEFAULT_842 is not set @@ -1149,9 +1163,8 @@ CONFIG_ZSMALLOC_STAT=y CONFIG_ZSMALLOC_CHAIN_SIZE=8 # -# SLAB allocator options +# Slab allocator options # -# CONFIG_SLAB_DEPRECATED is not set CONFIG_SLUB=y CONFIG_SLAB_MERGE_DEFAULT=y CONFIG_SLAB_FREELIST_RANDOM=y @@ -1159,7 +1172,7 @@ 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 +# end of Slab allocator options CONFIG_SHUFFLE_PAGE_ALLOCATOR=y # CONFIG_COMPAT_BRK is not set @@ -1193,6 +1206,7 @@ CONFIG_DEVICE_MIGRATION=y CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION=y CONFIG_ARCH_ENABLE_THP_MIGRATION=y CONFIG_CONTIG_ALLOC=y +CONFIG_PCP_BATCH_SCALE_MAX=5 CONFIG_PHYS_ADDR_T_64BIT=y CONFIG_MMU_NOTIFIER=y CONFIG_KSM=y @@ -1205,6 +1219,7 @@ CONFIG_ARCH_WANTS_THP_SWAP=y CONFIG_TRANSPARENT_HUGEPAGE=y CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y # CONFIG_TRANSPARENT_HUGEPAGE_MADVISE is not set +# CONFIG_TRANSPARENT_HUGEPAGE_NEVER is not set CONFIG_THP_SWAP=y CONFIG_READ_ONLY_THP_FOR_FS=y CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y @@ -1242,16 +1257,18 @@ CONFIG_MAPPING_DIRTY_HELPERS=y CONFIG_MEMFD_CREATE=y CONFIG_SECRETMEM=y CONFIG_ANON_VMA_NAME=y -CONFIG_USERFAULTFD=y CONFIG_HAVE_ARCH_USERFAULTFD_WP=y CONFIG_HAVE_ARCH_USERFAULTFD_MINOR=y +CONFIG_USERFAULTFD=y CONFIG_PTE_MARKER_UFFD_WP=y CONFIG_LRU_GEN=y CONFIG_LRU_GEN_ENABLED=y # CONFIG_LRU_GEN_STATS is not set +CONFIG_LRU_GEN_WALKS_MMU=y CONFIG_ARCH_SUPPORTS_PER_VMA_LOCK=y CONFIG_PER_VMA_LOCK=y CONFIG_LOCK_MM_AND_FIND_VMA=y +CONFIG_IOMMU_MM_DATA=y # # Data Access Monitoring @@ -1282,8 +1299,8 @@ CONFIG_TLS_DEVICE=y # CONFIG_TLS_TOE is not set CONFIG_XFRM=y CONFIG_XFRM_OFFLOAD=y -CONFIG_XFRM_ALGO=y -CONFIG_XFRM_USER=y +CONFIG_XFRM_ALGO=m +CONFIG_XFRM_USER=m # CONFIG_XFRM_USER_COMPAT is not set CONFIG_XFRM_INTERFACE=m CONFIG_XFRM_SUB_POLICY=y @@ -1357,6 +1374,8 @@ CONFIG_TCP_CONG_BBR=y CONFIG_DEFAULT_BBR=y # CONFIG_DEFAULT_RENO is not set CONFIG_DEFAULT_TCP_CONG="bbr" +CONFIG_TCP_SIGPOOL=y +CONFIG_TCP_AO=y CONFIG_TCP_MD5SIG=y CONFIG_IPV6=y CONFIG_IPV6_ROUTER_PREF=y @@ -1751,7 +1770,6 @@ CONFIG_BRIDGE_EBT_REDIRECT=m CONFIG_BRIDGE_EBT_SNAT=m CONFIG_BRIDGE_EBT_LOG=m CONFIG_BRIDGE_EBT_NFLOG=m -# CONFIG_BPFILTER is not set # CONFIG_IP_DCCP is not set CONFIG_IP_SCTP=m # CONFIG_SCTP_DBG_OBJCNT is not set @@ -1820,9 +1838,6 @@ CONFIG_VLAN_8021Q_MVRP=y CONFIG_LLC=m CONFIG_LLC2=m CONFIG_ATALK=m -CONFIG_DEV_APPLETALK=m -CONFIG_IPDDP=m -CONFIG_IPDDP_ENCAP=y # CONFIG_X25 is not set # CONFIG_LAPB is not set CONFIG_PHONET=m @@ -2226,6 +2241,7 @@ CONFIG_PCIEPORTBUS=y CONFIG_HOTPLUG_PCI_PCIE=y CONFIG_PCIEAER=y CONFIG_PCIEAER_INJECT=m +CONFIG_PCIEAER_CXL=y CONFIG_PCIE_ECRC=y CONFIG_PCIEASPM=y CONFIG_PCIEASPM_DEFAULT=y @@ -2278,7 +2294,7 @@ CONFIG_PCI_HYPERV_INTERFACE=m # CONFIG_PCIE_DW=y CONFIG_PCIE_DW_HOST=y -CONFIG_PCI_MESON=y +CONFIG_PCI_MESON=m CONFIG_PCIE_DW_PLAT=y CONFIG_PCIE_DW_PLAT_HOST=y # end of DesignWare-based PCIe controllers @@ -2367,6 +2383,7 @@ CONFIG_DEV_COREDUMP=y CONFIG_HMEM_REPORTING=y # CONFIG_TEST_ASYNC_DRIVER_PROBE is not set CONFIG_SYS_HYPERVISOR=y +CONFIG_GENERIC_CPU_DEVICES=y CONFIG_GENERIC_CPU_AUTOPROBE=y CONFIG_GENERIC_CPU_VULNERABILITIES=y CONFIG_SOC_BUS=y @@ -2463,6 +2480,11 @@ CONFIG_EFI_EMBEDDED_FIRMWARE=y CONFIG_UEFI_CPER=y CONFIG_UEFI_CPER_X86=y +# +# Qualcomm firmware drivers +# +# end of Qualcomm firmware drivers + # # Tegra firmware driver # @@ -2481,7 +2503,6 @@ CONFIG_MTD=m # # Partition parsers # -# CONFIG_MTD_AR7_PARTS is not set # CONFIG_MTD_CMDLINE_PARTS is not set # CONFIG_MTD_REDBOOT_PARTS is not set # end of Partition parsers @@ -2638,6 +2659,7 @@ CONFIG_ZRAM_DEF_COMP_ZSTD=y # CONFIG_ZRAM_DEF_COMP_842 is not set CONFIG_ZRAM_DEF_COMP="zstd" CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_TRACK_ENTRY_ACTIME=y CONFIG_ZRAM_MEMORY_TRACKING=y CONFIG_ZRAM_MULTI_COMP=y CONFIG_BLK_DEV_LOOP=m @@ -2665,7 +2687,8 @@ CONFIG_BLK_DEV_RNBD_SERVER=m # # NVME Support # -CONFIG_NVME_COMMON=m +CONFIG_NVME_KEYRING=m +CONFIG_NVME_AUTH=m CONFIG_NVME_CORE=m CONFIG_BLK_DEV_NVME=m CONFIG_NVME_MULTIPATH=y @@ -2675,7 +2698,8 @@ CONFIG_NVME_FABRICS=m CONFIG_NVME_RDMA=m CONFIG_NVME_FC=m CONFIG_NVME_TCP=m -CONFIG_NVME_AUTH=y +CONFIG_NVME_TCP_TLS=y +CONFIG_NVME_HOST_AUTH=y CONFIG_NVME_TARGET=m CONFIG_NVME_TARGET_PASSTHRU=y CONFIG_NVME_TARGET_LOOP=m @@ -2683,6 +2707,7 @@ CONFIG_NVME_TARGET_RDMA=m CONFIG_NVME_TARGET_FC=m CONFIG_NVME_TARGET_FCLOOP=m CONFIG_NVME_TARGET_TCP=m +CONFIG_NVME_TARGET_TCP_TLS=y CONFIG_NVME_TARGET_AUTH=y # end of NVME Support @@ -2720,6 +2745,7 @@ CONFIG_XILINX_SDFEC=m CONFIG_MISC_RTSX=m CONFIG_TPS6594_ESM=m CONFIG_TPS6594_PFSM=m +CONFIG_NSM=m CONFIG_C2PORT=m CONFIG_C2PORT_DURAMAR_2150=m @@ -2728,7 +2754,6 @@ CONFIG_C2PORT_DURAMAR_2150=m # CONFIG_EEPROM_AT24=m # CONFIG_EEPROM_AT25 is not set -CONFIG_EEPROM_LEGACY=m CONFIG_EEPROM_MAX6875=m CONFIG_EEPROM_93CX6=m # CONFIG_EEPROM_93XX46 is not set @@ -2752,6 +2777,8 @@ CONFIG_INTEL_MEI=m CONFIG_INTEL_MEI_ME=m CONFIG_INTEL_MEI_TXE=m CONFIG_INTEL_MEI_GSC=m +CONFIG_INTEL_MEI_VSC_HW=m +CONFIG_INTEL_MEI_VSC=m CONFIG_INTEL_MEI_HDCP=m CONFIG_INTEL_MEI_PXP=m CONFIG_INTEL_MEI_GSC_PROXY=m @@ -2878,7 +2905,6 @@ CONFIG_SCSI_INITIO=m CONFIG_SCSI_INIA100=m CONFIG_SCSI_PPA=m CONFIG_SCSI_IMM=m -# CONFIG_SCSI_IZIP_EPP16 is not set # CONFIG_SCSI_IZIP_SLOW_CTR is not set CONFIG_SCSI_STEX=m CONFIG_SCSI_SYM53C8XX_2=m @@ -3043,17 +3069,13 @@ 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 CONFIG_MD_RAID10=m CONFIG_MD_RAID456=m -CONFIG_MD_MULTIPATH=m -CONFIG_MD_FAULTY=m CONFIG_MD_CLUSTER=m CONFIG_BCACHE=m # CONFIG_BCACHE_DEBUG is not set -# CONFIG_BCACHE_CLOSURES_DEBUG is not set CONFIG_BCACHE_ASYNC_REGISTRATION=y CONFIG_BLK_DEV_DM_BUILTIN=y CONFIG_BLK_DEV_DM=m @@ -3166,6 +3188,7 @@ CONFIG_TAP=m CONFIG_VETH=m CONFIG_VIRTIO_NET=m CONFIG_NLMON=m +CONFIG_NETKIT=y CONFIG_NET_VRF=m CONFIG_VSOCKMON=m CONFIG_MHI_NET=m @@ -3393,10 +3416,12 @@ CONFIG_I40E_DCB=y CONFIG_IAVF=m CONFIG_I40EVF=m CONFIG_ICE=m +CONFIG_ICE_HWMON=y CONFIG_ICE_SWITCHDEV=y CONFIG_ICE_HWTS=y CONFIG_FM10K=m CONFIG_IGC=m +CONFIG_IDPF=m CONFIG_JME=m CONFIG_NET_VENDOR_ADI=y CONFIG_ADIN1110=m @@ -3436,6 +3461,7 @@ CONFIG_MLX5_EN_TLS=y CONFIG_MLX5_SW_STEERING=y CONFIG_MLX5_SF=y CONFIG_MLX5_SF_MANAGER=y +CONFIG_MLX5_DPLL=m CONFIG_MLXSW_CORE=m CONFIG_MLXSW_CORE_HWMON=y CONFIG_MLXSW_CORE_THERMAL=y @@ -3521,6 +3547,7 @@ CONFIG_8139TOO_TUNE_TWISTER=y CONFIG_8139TOO_8129=y # CONFIG_8139_OLD_RX_RESET is not set CONFIG_R8169=m +CONFIG_R8169_LEDS=y CONFIG_NET_VENDOR_RENESAS=y CONFIG_NET_VENDOR_ROCKER=y CONFIG_ROCKER=m @@ -3557,7 +3584,6 @@ CONFIG_STMMAC_ETH=m CONFIG_STMMAC_PLATFORM=m CONFIG_DWMAC_GENERIC=m CONFIG_DWMAC_INTEL=m -CONFIG_DWMAC_LOONGSON=m CONFIG_STMMAC_PCI=m CONFIG_NET_VENDOR_SUN=y CONFIG_HAPPYMEAL=m @@ -3661,6 +3687,7 @@ CONFIG_DP83848_PHY=m CONFIG_DP83867_PHY=m CONFIG_DP83869_PHY=m CONFIG_DP83TD510_PHY=m +CONFIG_DP83TG720_PHY=m CONFIG_VITESSE_PHY=m CONFIG_XILINX_GMII2RGMII=m CONFIG_MICREL_KS8995MA=m @@ -3899,9 +3926,6 @@ CONFIG_ATH12K=m CONFIG_ATH12K_DEBUG=y CONFIG_ATH12K_TRACING=y CONFIG_WLAN_VENDOR_ATMEL=y -CONFIG_ATMEL=m -CONFIG_PCI_ATMEL=m -CONFIG_PCMCIA_ATMEL=m CONFIG_AT76C50X_USB=m CONFIG_WLAN_VENDOR_BROADCOM=y CONFIG_B43=m @@ -3944,9 +3968,6 @@ CONFIG_BRCMFMAC_USB=y CONFIG_BRCMFMAC_PCIE=y CONFIG_BRCM_TRACING=y CONFIG_BRCMDBG=y -CONFIG_WLAN_VENDOR_CISCO=y -CONFIG_AIRO=m -CONFIG_AIRO_CS=m CONFIG_WLAN_VENDOR_INTEL=y CONFIG_IPW2100=m CONFIG_IPW2100_MONITOR=y @@ -3985,22 +4006,6 @@ CONFIG_IWLWIFI_DEVICE_TRACING=y # end of Debugging Options CONFIG_WLAN_VENDOR_INTERSIL=y -CONFIG_HOSTAP=m -CONFIG_HOSTAP_FIRMWARE=y -CONFIG_HOSTAP_FIRMWARE_NVRAM=y -CONFIG_HOSTAP_PLX=m -CONFIG_HOSTAP_PCI=m -CONFIG_HOSTAP_CS=m -CONFIG_HERMES=m -CONFIG_HERMES_PRISM=y -CONFIG_HERMES_CACHE_FW_ON_INIT=y -CONFIG_PLX_HERMES=m -CONFIG_TMD_HERMES=m -CONFIG_NORTEL_HERMES=m -CONFIG_PCI_HERMES=m -CONFIG_PCMCIA_HERMES=m -CONFIG_PCMCIA_SPECTRUM=m -CONFIG_ORINOCO_USB=m CONFIG_P54_COMMON=m CONFIG_P54_USB=m CONFIG_P54_PCI=m @@ -4010,7 +4015,6 @@ CONFIG_P54_LEDS=y CONFIG_WLAN_VENDOR_MARVELL=y CONFIG_LIBERTAS=m CONFIG_LIBERTAS_USB=m -CONFIG_LIBERTAS_CS=m CONFIG_LIBERTAS_SDIO=m CONFIG_LIBERTAS_SPI=m # CONFIG_LIBERTAS_DEBUG is not set @@ -4052,6 +4056,9 @@ CONFIG_MT7921E=m CONFIG_MT7921S=m CONFIG_MT7921U=m CONFIG_MT7996E=m +CONFIG_MT7925_COMMON=m +CONFIG_MT7925E=m +CONFIG_MT7925U=m CONFIG_WLAN_VENDOR_MICROCHIP=y CONFIG_WILC1000=m CONFIG_WILC1000_SDIO=m @@ -4170,15 +4177,11 @@ CONFIG_WL18XX=m CONFIG_WLCORE=m CONFIG_WLCORE_SDIO=m CONFIG_WLAN_VENDOR_ZYDAS=y -CONFIG_USB_ZD1201=m CONFIG_ZD1211RW=m # CONFIG_ZD1211RW_DEBUG is not set CONFIG_WLAN_VENDOR_QUANTENNA=y CONFIG_QTNFMAC=m CONFIG_QTNFMAC_PCIE=m -CONFIG_PCMCIA_RAYCS=m -CONFIG_PCMCIA_WL3501=m -CONFIG_USB_NET_RNDIS_WLAN=m CONFIG_MAC80211_HWSIM=m CONFIG_VIRT_WIFI=m # CONFIG_WAN is not set @@ -4197,7 +4200,7 @@ CONFIG_IEEE802154_HWSIM=m # # Wireless WAN # -CONFIG_WWAN=y +CONFIG_WWAN=m CONFIG_WWAN_DEBUGFS=y CONFIG_WWAN_HWSIM=m CONFIG_MHI_WWAN_CTRL=m @@ -4363,6 +4366,7 @@ CONFIG_JOYSTICK_PXRC=m CONFIG_JOYSTICK_QWIIC=m CONFIG_JOYSTICK_FSIA6B=m CONFIG_JOYSTICK_SENSEHAT=m +CONFIG_JOYSTICK_SEESAW=m CONFIG_INPUT_TABLET=y CONFIG_TABLET_USB_ACECAD=m CONFIG_TABLET_USB_AIPTEK=m @@ -4427,7 +4431,6 @@ CONFIG_TOUCHSCREEN_PENMOUNT=m CONFIG_TOUCHSCREEN_EDT_FT5X06=m CONFIG_TOUCHSCREEN_TOUCHRIGHT=m CONFIG_TOUCHSCREEN_TOUCHWIN=m -CONFIG_TOUCHSCREEN_TI_AM335X_TSC=m CONFIG_TOUCHSCREEN_PIXCIR=m CONFIG_TOUCHSCREEN_WDT87XX_I2C=m CONFIG_TOUCHSCREEN_WM831X=m @@ -4626,9 +4629,9 @@ CONFIG_SERIAL_8250_DWLIB=y CONFIG_SERIAL_8250_DFL=m CONFIG_SERIAL_8250_DW=m CONFIG_SERIAL_8250_RT288X=y -CONFIG_SERIAL_8250_LPSS=y -CONFIG_SERIAL_8250_MID=y -CONFIG_SERIAL_8250_PERICOM=y +CONFIG_SERIAL_8250_LPSS=m +CONFIG_SERIAL_8250_MID=m +CONFIG_SERIAL_8250_PERICOM=m # # Non-8250 serial port support @@ -4819,6 +4822,7 @@ CONFIG_I2C_XILINX=m # CONFIG_I2C_DIOLAN_U2C=m CONFIG_I2C_DLN2=m +CONFIG_I2C_LJCA=m CONFIG_I2C_CP2615=m CONFIG_I2C_PARPORT=m CONFIG_I2C_PCI1XXXX=m @@ -4871,6 +4875,7 @@ CONFIG_SPI_INTEL=m CONFIG_SPI_INTEL_PCI=m CONFIG_SPI_INTEL_PLATFORM=m CONFIG_SPI_LM70_LLP=m +CONFIG_SPI_LJCA=m CONFIG_SPI_MICROCHIP_CORE=m CONFIG_SPI_MICROCHIP_CORE_QSPI=m # CONFIG_SPI_LANTIQ_SSC is not set @@ -4903,7 +4908,7 @@ CONFIG_SPI_SLAVE_SYSTEM_CONTROL=m CONFIG_SPI_DYNAMIC=y # CONFIG_SPMI is not set # CONFIG_HSI is not set -CONFIG_PPS=y +CONFIG_PPS=m # CONFIG_PPS_DEBUG is not set # @@ -4921,8 +4926,8 @@ CONFIG_PPS_CLIENT_GPIO=m # # PTP clock support # -CONFIG_PTP_1588_CLOCK=y -CONFIG_PTP_1588_CLOCK_OPTIONAL=y +CONFIG_PTP_1588_CLOCK=m +CONFIG_PTP_1588_CLOCK_OPTIONAL=m CONFIG_DP83640_PHY=m CONFIG_PTP_1588_CLOCK_INES=m CONFIG_PTP_1588_CLOCK_KVM=m @@ -4959,23 +4964,25 @@ CONFIG_PINCTRL_CS47L92=y # CONFIG_PINCTRL_BAYTRAIL=y CONFIG_PINCTRL_CHERRYVIEW=y -CONFIG_PINCTRL_LYNXPOINT=y +CONFIG_PINCTRL_LYNXPOINT=m CONFIG_PINCTRL_INTEL=y -CONFIG_PINCTRL_ALDERLAKE=y -CONFIG_PINCTRL_BROXTON=y -CONFIG_PINCTRL_CANNONLAKE=y -CONFIG_PINCTRL_CEDARFORK=y -CONFIG_PINCTRL_DENVERTON=y -CONFIG_PINCTRL_ELKHARTLAKE=y -CONFIG_PINCTRL_EMMITSBURG=y -CONFIG_PINCTRL_GEMINILAKE=y -CONFIG_PINCTRL_ICELAKE=y -CONFIG_PINCTRL_JASPERLAKE=y -CONFIG_PINCTRL_LAKEFIELD=y -CONFIG_PINCTRL_LEWISBURG=y -CONFIG_PINCTRL_METEORLAKE=y -CONFIG_PINCTRL_SUNRISEPOINT=y -CONFIG_PINCTRL_TIGERLAKE=y +CONFIG_PINCTRL_INTEL_PLATFORM=m +CONFIG_PINCTRL_ALDERLAKE=m +CONFIG_PINCTRL_BROXTON=m +CONFIG_PINCTRL_CANNONLAKE=m +CONFIG_PINCTRL_CEDARFORK=m +CONFIG_PINCTRL_DENVERTON=m +CONFIG_PINCTRL_ELKHARTLAKE=m +CONFIG_PINCTRL_EMMITSBURG=m +CONFIG_PINCTRL_GEMINILAKE=m +CONFIG_PINCTRL_ICELAKE=m +CONFIG_PINCTRL_JASPERLAKE=m +CONFIG_PINCTRL_LAKEFIELD=m +CONFIG_PINCTRL_LEWISBURG=m +CONFIG_PINCTRL_METEORLAKE=m +CONFIG_PINCTRL_METEORPOINT=m +CONFIG_PINCTRL_SUNRISEPOINT=m +CONFIG_PINCTRL_TIGERLAKE=m # end of Intel pinctrl drivers # @@ -5049,6 +5056,7 @@ CONFIG_GPIO_DLN2=m CONFIG_GPIO_ELKHARTLAKE=m CONFIG_GPIO_JANZ_TTL=m CONFIG_GPIO_KEMPLD=m +CONFIG_GPIO_LJCA=m CONFIG_GPIO_LP3943=m CONFIG_GPIO_LP873X=m CONFIG_GPIO_MADERA=m @@ -5110,6 +5118,7 @@ CONFIG_W1_CON=y # # 1-wire Bus Masters # +CONFIG_W1_MASTER_AMD_AXI=m CONFIG_W1_MASTER_MATROX=m CONFIG_W1_MASTER_DS2490=m CONFIG_W1_MASTER_DS2482=m @@ -5193,7 +5202,7 @@ CONFIG_CHARGER_TWL4030=m CONFIG_CHARGER_LP8727=m CONFIG_CHARGER_LP8788=m CONFIG_CHARGER_GPIO=m -CONFIG_CHARGER_MANAGER=y +CONFIG_CHARGER_MANAGER=m CONFIG_CHARGER_LT3651=m CONFIG_CHARGER_LTC4162L=m CONFIG_CHARGER_MAX14577=m @@ -5228,6 +5237,7 @@ CONFIG_CHARGER_WILCO=m CONFIG_BATTERY_SURFACE=m CONFIG_CHARGER_SURFACE=m CONFIG_BATTERY_UG3105=m +CONFIG_FUEL_GAUGE_MM8013=m CONFIG_HWMON=y CONFIG_HWMON_VID=m # CONFIG_HWMON_DEBUG_CHIP is not set @@ -5281,6 +5291,7 @@ CONFIG_SENSORS_F75375S=m CONFIG_SENSORS_MC13783_ADC=m CONFIG_SENSORS_FSCHMD=m CONFIG_SENSORS_FTSTEUTATES=m +CONFIG_SENSORS_GIGABYTE_WATERFORCE=m CONFIG_SENSORS_GL518SM=m CONFIG_SENSORS_GL520SM=m CONFIG_SENSORS_G760A=m @@ -5294,6 +5305,7 @@ CONFIG_SENSORS_I5500=m CONFIG_SENSORS_CORETEMP=m CONFIG_SENSORS_IT87=m CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_POWERZ=m CONFIG_SENSORS_POWR1220=m CONFIG_SENSORS_LINEAGE=m CONFIG_SENSORS_LTC2945=m @@ -5301,6 +5313,7 @@ CONFIG_SENSORS_LTC2947=m CONFIG_SENSORS_LTC2947_I2C=m CONFIG_SENSORS_LTC2947_SPI=m CONFIG_SENSORS_LTC2990=m +CONFIG_SENSORS_LTC2991=m CONFIG_SENSORS_LTC2992=m CONFIG_SENSORS_LTC4151=m CONFIG_SENSORS_LTC4215=m @@ -5388,6 +5401,7 @@ CONFIG_SENSORS_LT7182S=m CONFIG_SENSORS_LTC2978=m # CONFIG_SENSORS_LTC2978_REGULATOR is not set CONFIG_SENSORS_LTC3815=m +CONFIG_SENSORS_LTC4286=y CONFIG_SENSORS_MAX15301=m CONFIG_SENSORS_MAX16064=m CONFIG_SENSORS_MAX16601=m @@ -5396,10 +5410,12 @@ CONFIG_SENSORS_MAX20751=m CONFIG_SENSORS_MAX31785=m CONFIG_SENSORS_MAX34440=m CONFIG_SENSORS_MAX8688=m +CONFIG_SENSORS_MP2856=m CONFIG_SENSORS_MP2888=m CONFIG_SENSORS_MP2975=m CONFIG_SENSORS_MP2975_REGULATOR=y CONFIG_SENSORS_MP5023=m +CONFIG_SENSORS_MP5990=m CONFIG_SENSORS_MPQ7932_REGULATOR=y CONFIG_SENSORS_MPQ7932=m CONFIG_SENSORS_PIM4328=m @@ -5489,9 +5505,9 @@ CONFIG_SENSORS_HP_WMI=m CONFIG_THERMAL=y CONFIG_THERMAL_NETLINK=y # CONFIG_THERMAL_STATISTICS is not set +# CONFIG_THERMAL_DEBUGFS is not set CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=100 CONFIG_THERMAL_HWMON=y -CONFIG_THERMAL_ACPI=y CONFIG_THERMAL_WRITABLE_TRIPS=y CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y # CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set @@ -5738,11 +5754,10 @@ CONFIG_MFD_SM501=m CONFIG_MFD_SM501_GPIO=y CONFIG_MFD_SKY81452=m CONFIG_MFD_SYSCON=y -CONFIG_MFD_TI_AM335X_TSCADC=m CONFIG_MFD_LP3943=m CONFIG_MFD_LP8788=y CONFIG_MFD_TI_LMU=m -CONFIG_MFD_PALMAS=y +CONFIG_MFD_PALMAS=m CONFIG_TPS6105X=m CONFIG_TPS65010=m CONFIG_TPS6507X=m @@ -5793,6 +5808,7 @@ CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED_VOLTAGE=m CONFIG_REGULATOR_VIRTUAL_CONSUMER=m CONFIG_REGULATOR_USERSPACE_CONSUMER=m +CONFIG_REGULATOR_NETLINK_EVENTS=y CONFIG_REGULATOR_88PG86X=m CONFIG_REGULATOR_88PM800=m CONFIG_REGULATOR_88PM8607=m @@ -5827,6 +5843,7 @@ CONFIG_REGULATOR_LTC3589=m CONFIG_REGULATOR_LTC3676=m CONFIG_REGULATOR_MAX14577=m CONFIG_REGULATOR_MAX1586=m +CONFIG_REGULATOR_MAX77503=m CONFIG_REGULATOR_MAX77541=m CONFIG_REGULATOR_MAX77857=m CONFIG_REGULATOR_MAX8649=m @@ -5996,7 +6013,6 @@ CONFIG_V4L2_CCI_I2C=m # Media controller options # CONFIG_MEDIA_CONTROLLER_DVB=y -CONFIG_MEDIA_CONTROLLER_REQUEST_API=y # end of Media controller options # @@ -6174,6 +6190,7 @@ CONFIG_MEDIA_PCI_SUPPORT=y # # Media capture support # +CONFIG_VIDEO_MGB4=m CONFIG_VIDEO_SOLO6X10=m CONFIG_VIDEO_TW5864=m CONFIG_VIDEO_TW68=m @@ -6316,6 +6333,10 @@ CONFIG_VIDEO_CAFE_CCIC=m # Microchip Technology, Inc. media platform drivers # +# +# Nuvoton media platform drivers +# + # # NVidia media platform drivers # @@ -6425,7 +6446,10 @@ CONFIG_VIDEO_IR_I2C=m CONFIG_VIDEO_CAMERA_SENSOR=y CONFIG_VIDEO_APTINA_PLL=m CONFIG_VIDEO_CCS_PLL=m +CONFIG_VIDEO_ALVIUM_CSI2=m CONFIG_VIDEO_AR0521=m +CONFIG_VIDEO_GC0308=m +CONFIG_VIDEO_GC2145=m CONFIG_VIDEO_HI556=m CONFIG_VIDEO_HI846=m CONFIG_VIDEO_HI847=m @@ -6441,6 +6465,7 @@ CONFIG_VIDEO_IMX355=m CONFIG_VIDEO_MAX9271_LIB=m CONFIG_VIDEO_MT9M001=m CONFIG_VIDEO_MT9M111=m +CONFIG_VIDEO_MT9M114=m CONFIG_VIDEO_MT9P031=m CONFIG_VIDEO_MT9T112=m CONFIG_VIDEO_MT9V011=m @@ -6465,6 +6490,7 @@ CONFIG_VIDEO_OV5670=m CONFIG_VIDEO_OV5675=m CONFIG_VIDEO_OV5693=m CONFIG_VIDEO_OV5695=m +CONFIG_VIDEO_OV64A40=m CONFIG_VIDEO_OV6650=m CONFIG_VIDEO_OV7251=m CONFIG_VIDEO_OV7640=m @@ -6486,6 +6512,12 @@ CONFIG_VIDEO_S5K6A3=m CONFIG_VIDEO_CCS=m CONFIG_VIDEO_ET8EK8=m +# +# Camera ISPs +# +CONFIG_VIDEO_THP7312=m +# end of Camera ISPs + # # Lens drivers # @@ -6792,6 +6824,7 @@ CONFIG_DRM_DP_AUX_CHARDEV=y CONFIG_DRM_DP_CEC=y CONFIG_DRM_TTM=m CONFIG_DRM_EXEC=m +CONFIG_DRM_GPUVM=m CONFIG_DRM_BUDDY=m CONFIG_DRM_VRAM_HELPER=m CONFIG_DRM_TTM_HELPER=m @@ -6845,6 +6878,7 @@ CONFIG_NOUVEAU_DEBUG_DEFAULT=3 # CONFIG_NOUVEAU_DEBUG_PUSH is not set CONFIG_DRM_NOUVEAU_BACKLIGHT=y CONFIG_DRM_NOUVEAU_SVM=y +CONFIG_DRM_NOUVEAU_GSP_DEFAULT=y CONFIG_DRM_I915=m CONFIG_DRM_I915_FORCE_PROBE="*" CONFIG_DRM_I915_CAPTURE_ERROR=y @@ -6862,6 +6896,17 @@ CONFIG_DRM_I915_MAX_REQUEST_BUSYWAIT=8000 CONFIG_DRM_I915_STOP_TIMEOUT=100 CONFIG_DRM_I915_TIMESLICE_DURATION=1 CONFIG_DRM_I915_GVT=y +CONFIG_DRM_XE=m +CONFIG_DRM_XE_DISPLAY=y +CONFIG_DRM_XE_FORCE_PROBE="" +CONFIG_DRM_XE_JOB_TIMEOUT_MAX=10000 +CONFIG_DRM_XE_JOB_TIMEOUT_MIN=1 +CONFIG_DRM_XE_TIMESLICE_MAX=10000000 +CONFIG_DRM_XE_TIMESLICE_MIN=1 +CONFIG_DRM_XE_PREEMPT_TIMEOUT=640000 +CONFIG_DRM_XE_PREEMPT_TIMEOUT_MAX=10000000 +CONFIG_DRM_XE_PREEMPT_TIMEOUT_MIN=1 +CONFIG_DRM_XE_ENABLE_SCHEDTIMEOUT_LIMIT=y CONFIG_DRM_VGEM=m CONFIG_DRM_VKMS=m CONFIG_DRM_VMWGFX=m @@ -6894,7 +6939,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 @@ -6918,7 +6962,6 @@ CONFIG_DRM_SSD130X=m CONFIG_DRM_SSD130X_I2C=m CONFIG_DRM_SSD130X_SPI=m CONFIG_DRM_HYPERV=m -# CONFIG_DRM_LEGACY is not set CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y CONFIG_DRM_PRIVACY_SCREEN=y @@ -6943,7 +6986,6 @@ CONFIG_FB_EFI=y # CONFIG_FB_NVIDIA is not set # CONFIG_FB_RIVA is not set # CONFIG_FB_I740 is not set -# CONFIG_FB_LE80578 is not set # CONFIG_FB_MATROX is not set # CONFIG_FB_RADEON is not set # CONFIG_FB_ATY128 is not set @@ -6983,9 +7025,10 @@ 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_SYSMEM_FOPS=y CONFIG_FB_DEFERRED_IO=y CONFIG_FB_DMAMEM_HELPERS=y +CONFIG_FB_IOMEM_FOPS=y CONFIG_FB_IOMEM_HELPERS=y CONFIG_FB_SYSMEM_HELPERS=y CONFIG_FB_SYSMEM_HELPERS_DEFERRED=y @@ -7033,6 +7076,7 @@ CONFIG_BACKLIGHT_LM3630A=m CONFIG_BACKLIGHT_LM3639=m CONFIG_BACKLIGHT_LP855X=m CONFIG_BACKLIGHT_LP8788=m +CONFIG_BACKLIGHT_MP3309C=m CONFIG_BACKLIGHT_PANDORA=m CONFIG_BACKLIGHT_SKY81452=m CONFIG_BACKLIGHT_AS3711=m @@ -7220,6 +7264,7 @@ CONFIG_SND_HDA_RECONFIG=y CONFIG_SND_HDA_INPUT_BEEP=y CONFIG_SND_HDA_INPUT_BEEP_MODE=0 CONFIG_SND_HDA_PATCH_LOADER=y +CONFIG_SND_HDA_CIRRUS_SCODEC=m CONFIG_SND_HDA_SCODEC_CS35L41=m CONFIG_SND_HDA_CS_DSP_CONTROLS=m CONFIG_SND_HDA_SCODEC_CS35L41_I2C=m @@ -7319,6 +7364,8 @@ CONFIG_SND_SOC_AMD_ACP_PCM=m CONFIG_SND_SOC_AMD_ACP_PCI=m CONFIG_SND_AMD_ASOC_RENOIR=m CONFIG_SND_AMD_ASOC_REMBRANDT=m +CONFIG_SND_AMD_ASOC_ACP63=m +CONFIG_SND_AMD_ASOC_ACP70=m CONFIG_SND_SOC_AMD_MACH_COMMON=m CONFIG_SND_SOC_AMD_LEGACY_MACH=m CONFIG_SND_SOC_AMD_SOF_MACH=m @@ -7345,7 +7392,6 @@ CONFIG_SND_DESIGNWARE_PCM=y # CONFIG_SND_SOC_FSL_ESAI is not set # CONFIG_SND_SOC_FSL_MICFIL is not set CONFIG_SND_SOC_FSL_XCVR=m -CONFIG_SND_SOC_FSL_RPMSG=m # CONFIG_SND_SOC_IMX_AUDMUX is not set # end of SoC Audio for Freescale CPUs @@ -7400,6 +7446,7 @@ 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_RT5514=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 @@ -7411,6 +7458,9 @@ CONFIG_SND_SOC_INTEL_HDA_DSP_COMMON=m CONFIG_SND_SOC_INTEL_SOF_MAXIM_COMMON=m CONFIG_SND_SOC_INTEL_SOF_REALTEK_COMMON=m CONFIG_SND_SOC_INTEL_SOF_CIRRUS_COMMON=m +CONFIG_SND_SOC_INTEL_SOF_NUVOTON_COMMON=m +CONFIG_SND_SOC_INTEL_SOF_SSP_COMMON=m +CONFIG_SND_SOC_INTEL_SOF_BOARD_HELPERS=m CONFIG_SND_SOC_INTEL_HASWELL_MACH=m CONFIG_SND_SOC_INTEL_BDW_RT5650_MACH=m CONFIG_SND_SOC_INTEL_BDW_RT5677_MACH=m @@ -7449,7 +7499,7 @@ CONFIG_SND_SOC_INTEL_SOF_ES8336_MACH=m CONFIG_SND_SOC_INTEL_SOF_NAU8825_MACH=m CONFIG_SND_SOC_INTEL_CML_LP_DA7219_MAX98357A_MACH=m CONFIG_SND_SOC_INTEL_SOF_CML_RT1011_RT5682_MACH=m -CONFIG_SND_SOC_INTEL_SOF_DA7219_MAX98373_MACH=m +CONFIG_SND_SOC_INTEL_SOF_DA7219_MACH=m CONFIG_SND_SOC_INTEL_SOF_SSP_AMP_MACH=m CONFIG_SND_SOC_INTEL_EHL_RT5660_MACH=m CONFIG_SND_SOC_INTEL_SOUNDWIRE_SOF_MACH=m @@ -7464,13 +7514,14 @@ CONFIG_SND_SOC_SOF_CLIENT=m CONFIG_SND_SOC_SOF=m CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE=y CONFIG_SND_SOC_SOF_IPC3=y -CONFIG_SND_SOC_SOF_INTEL_IPC4=y +CONFIG_SND_SOC_SOF_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_AMD_ACP63=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 @@ -7554,6 +7605,8 @@ CONFIG_SND_SOC_AW8738=m CONFIG_SND_SOC_AW88395_LIB=m CONFIG_SND_SOC_AW88395=m CONFIG_SND_SOC_AW88261=m +CONFIG_SND_SOC_AW87390=m +CONFIG_SND_SOC_AW88399=m CONFIG_SND_SOC_BD28623=m # CONFIG_SND_SOC_BT_SCO is not set CONFIG_SND_SOC_CHV3_CODEC=m @@ -7605,6 +7658,7 @@ CONFIG_SND_SOC_DMIC=m CONFIG_SND_SOC_HDMI_CODEC=m CONFIG_SND_SOC_ES7134=m CONFIG_SND_SOC_ES7241=m +CONFIG_SND_SOC_ES83XX_DSM_COMMON=m CONFIG_SND_SOC_ES8316=m CONFIG_SND_SOC_ES8326=m CONFIG_SND_SOC_ES8328=m @@ -7697,6 +7751,7 @@ CONFIG_SND_SOC_RT715=m CONFIG_SND_SOC_RT715_SDW=m CONFIG_SND_SOC_RT715_SDCA_SDW=m CONFIG_SND_SOC_RT9120=m +CONFIG_SND_SOC_RTQ9128=m # CONFIG_SND_SOC_SDW_MOCKUP is not set CONFIG_SND_SOC_SGTL5000=m CONFIG_SND_SOC_SI476X=m @@ -7821,7 +7876,7 @@ CONFIG_HID=y CONFIG_HID_BATTERY_STRENGTH=y CONFIG_HIDRAW=y CONFIG_UHID=m -CONFIG_HID_GENERIC=y +CONFIG_HID_GENERIC=m # # Special HID drivers @@ -7957,6 +8012,7 @@ CONFIG_HID_ZYDACRON=m CONFIG_HID_SENSOR_HUB=m CONFIG_HID_SENSOR_CUSTOM_SENSOR=m CONFIG_HID_ALPS=m +CONFIG_HID_MCP2200=m CONFIG_HID_MCP2221=m # end of Special HID drivers @@ -8009,6 +8065,7 @@ CONFIG_USB_CONN_GPIO=m CONFIG_USB_ARCH_HAS_HCD=y CONFIG_USB=y CONFIG_USB_PCI=y +CONFIG_USB_PCI_AMD=y CONFIG_USB_ANNOUNCE_NEW_DEVICES=y # @@ -8152,6 +8209,7 @@ CONFIG_USB_CHIPIDEA_UDC=y CONFIG_USB_CHIPIDEA_HOST=y CONFIG_USB_CHIPIDEA_PCI=m CONFIG_USB_CHIPIDEA_MSM=m +CONFIG_USB_CHIPIDEA_NPCM=m CONFIG_USB_CHIPIDEA_GENERIC=m CONFIG_USB_ISP1760=m CONFIG_USB_ISP1760_HCD=y @@ -8236,6 +8294,7 @@ CONFIG_USB_CYTHERM=m CONFIG_USB_IDMOUSE=m CONFIG_USB_APPLEDISPLAY=m CONFIG_APPLE_MFI_FASTCHARGE=m +CONFIG_USB_LJCA=m CONFIG_USB_SISUSBVGA=m CONFIG_USB_LD=m CONFIG_USB_TRANCEVIBRATOR=m @@ -8407,6 +8466,8 @@ CONFIG_TYPEC_MUX_GPIO_SBU=m CONFIG_TYPEC_MUX_PI3USB30532=m CONFIG_TYPEC_MUX_INTEL_PMC=m CONFIG_TYPEC_MUX_NB7VPQ904M=m +CONFIG_TYPEC_MUX_PTN36502=m +CONFIG_TYPEC_MUX_WCD939X_USBSS=m # end of USB Type-C Multiplexer/DeMultiplexer Switch support # @@ -8567,6 +8628,7 @@ CONFIG_LEDS_TRIGGER_HEARTBEAT=m CONFIG_LEDS_TRIGGER_BACKLIGHT=m CONFIG_LEDS_TRIGGER_CPU=y CONFIG_LEDS_TRIGGER_ACTIVITY=m +CONFIG_LEDS_TRIGGER_GPIO=m CONFIG_LEDS_TRIGGER_DEFAULT_ON=m # @@ -8655,7 +8717,7 @@ CONFIG_EDAC_SUPPORT=y CONFIG_EDAC=y CONFIG_EDAC_LEGACY_SYSFS=y # CONFIG_EDAC_DEBUG is not set -CONFIG_EDAC_DECODE_MCE=m +CONFIG_EDAC_DECODE_MCE=y CONFIG_EDAC_GHES=y CONFIG_EDAC_AMD64=m CONFIG_EDAC_E752X=m @@ -8711,6 +8773,7 @@ CONFIG_RTC_DRV_MAX8907=m CONFIG_RTC_DRV_MAX8925=m CONFIG_RTC_DRV_MAX8998=m CONFIG_RTC_DRV_MAX8997=m +CONFIG_RTC_DRV_MAX31335=m CONFIG_RTC_DRV_RS5C372=m CONFIG_RTC_DRV_ISL1208=m CONFIG_RTC_DRV_ISL12022=m @@ -8725,6 +8788,7 @@ CONFIG_RTC_DRV_M41T80_WDT=y CONFIG_RTC_DRV_BQ32K=m CONFIG_RTC_DRV_PALMAS=m CONFIG_RTC_DRV_TPS6586X=m +CONFIG_RTC_DRV_TPS6594=m CONFIG_RTC_DRV_TPS65910=m CONFIG_RTC_DRV_RC5T583=m CONFIG_RTC_DRV_S35390A=m @@ -8838,7 +8902,7 @@ CONFIG_DW_DMAC=m CONFIG_DW_DMAC_PCI=y CONFIG_DW_EDMA=m CONFIG_DW_EDMA_PCIE=m -CONFIG_HSU_DMA=y +CONFIG_HSU_DMA=m CONFIG_SF_PDMA=m CONFIG_INTEL_LDMA=y @@ -8884,6 +8948,7 @@ CONFIG_VFIO_CONTAINER=y CONFIG_VFIO_IOMMU_TYPE1=m # CONFIG_VFIO_NOIOMMU is not set CONFIG_VFIO_VIRQFD=y +CONFIG_VFIO_DEBUGFS=y # # VFIO support for PCI devices @@ -8896,6 +8961,7 @@ CONFIG_VFIO_PCI_VGA=y CONFIG_VFIO_PCI_IGD=y CONFIG_MLX5_VFIO_PCI=m CONFIG_PDS_VFIO_PCI=m +CONFIG_VIRTIO_VFIO_PCI=m # end of VFIO support for PCI devices CONFIG_VFIO_MDEV=m @@ -8905,6 +8971,7 @@ CONFIG_VMGENID=y CONFIG_VBOXGUEST=m CONFIG_NITRO_ENCLAVES=m CONFIG_ACRN_HSM=m +CONFIG_TSM_REPORTS=m CONFIG_EFI_SECRET=m CONFIG_SEV_GUEST=m CONFIG_TDX_GUEST_DRIVER=m @@ -8914,6 +8981,7 @@ CONFIG_VIRTIO_PCI_LIB=m CONFIG_VIRTIO_PCI_LIB_LEGACY=m CONFIG_VIRTIO_MENU=y CONFIG_VIRTIO_PCI=m +CONFIG_VIRTIO_PCI_ADMIN_LEGACY=y CONFIG_VIRTIO_PCI_LEGACY=y CONFIG_VIRTIO_VDPA=m CONFIG_VIRTIO_PMEM=m @@ -8978,10 +9046,10 @@ CONFIG_SWIOTLB_XEN=y CONFIG_XEN_PCI_STUB=y CONFIG_XEN_PCIDEV_BACKEND=m CONFIG_XEN_PVCALLS_FRONTEND=m -CONFIG_XEN_PVCALLS_BACKEND=y +CONFIG_XEN_PVCALLS_BACKEND=m CONFIG_XEN_SCSI_BACKEND=m CONFIG_XEN_PRIVCMD=m -CONFIG_XEN_PRIVCMD_IRQFD=y +CONFIG_XEN_PRIVCMD_EVENTFD=y CONFIG_XEN_ACPI_PROCESSOR=m CONFIG_XEN_MCE_LOG=y CONFIG_XEN_HAVE_PVMMU=y @@ -9001,7 +9069,6 @@ CONFIG_XEN_VIRTIO=y # CONFIG_COMEDI is not set CONFIG_STAGING=y CONFIG_PRISM2_USB=m -CONFIG_RTL8192U=m CONFIG_RTLLIB=m CONFIG_RTLLIB_CRYPTO_CCMP=m CONFIG_RTLLIB_CRYPTO_TKIP=m @@ -9050,12 +9117,6 @@ CONFIG_AD9834=m # CONFIG_AD5933=m # end of Network Analyzer, Impedance Converters - -# -# Resolver to digital converters -# -CONFIG_AD2S1210=m -# end of Resolver to digital converters # end of IIO staging drivers # CONFIG_FB_SM750 is not set @@ -9067,6 +9128,10 @@ CONFIG_DVB_AV7110_OSD=y CONFIG_DVB_BUDGET_PATCH=m CONFIG_DVB_SP8870=m CONFIG_VIDEO_IPU3_IMGU=m + +# +# StarFive media platform drivers +# # CONFIG_STAGING_MEDIA_DEPRECATED is not set CONFIG_LTE_GDM724X=m # CONFIG_FB_TFT is not set @@ -9077,7 +9142,6 @@ CONFIG_MOST_I2C=m CONFIG_KS7010=m CONFIG_PI433=m CONFIG_FIELDBUS_DEV=m -CONFIG_QLGE=m # CONFIG_VME_BUS is not set CONFIG_CHROME_PLATFORMS=y CONFIG_CHROMEOS_ACPI=m @@ -9144,6 +9208,7 @@ CONFIG_AMD_PMF=m # CONFIG_AMD_PMF_DEBUG is not set CONFIG_AMD_PMC=m CONFIG_AMD_HSMP=m +CONFIG_AMD_WBRF=y CONFIG_ADV_SWBUTTON=m CONFIG_APPLE_GMUX=m CONFIG_ASUS_LAPTOP=m @@ -9199,7 +9264,7 @@ CONFIG_INTEL_ATOMISP2_PM=m CONFIG_INTEL_IFS=m CONFIG_INTEL_SAR_INT1092=m CONFIG_INTEL_SKL_INT3472=m -CONFIG_INTEL_PMC_CORE=y +CONFIG_INTEL_PMC_CORE=m CONFIG_INTEL_PMT_CLASS=m CONFIG_INTEL_PMT_TELEMETRY=m CONFIG_INTEL_PMT_CRASHLOG=m @@ -9262,6 +9327,7 @@ CONFIG_TOPSTAR_LAPTOP=m CONFIG_SERIAL_MULTI_INSTANTIATE=m CONFIG_MLX_PLATFORM=m CONFIG_TOUCHSCREEN_DMI=y +CONFIG_INSPUR_PLATFORM_PROFILE=m CONFIG_X86_ANDROID_TABLETS=m CONFIG_FW_ATTR_CLASS=m CONFIG_INTEL_IPS=m @@ -9275,6 +9341,7 @@ 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_SILICOM_PLATFORM=m CONFIG_WINMATE_FM07_KEYS=m CONFIG_SEL3350_PLATFORM=m CONFIG_STEAMDECK=m @@ -9291,6 +9358,7 @@ CONFIG_COMMON_CLK_SI544=m CONFIG_COMMON_CLK_CDCE706=m CONFIG_COMMON_CLK_TPS68470=m CONFIG_COMMON_CLK_CS2000_CP=m +CONFIG_CLK_TWL=m CONFIG_CLK_TWL6040=m CONFIG_COMMON_CLK_PALMAS=m CONFIG_COMMON_CLK_PWM=m @@ -9310,6 +9378,7 @@ CONFIG_PCC=y CONFIG_ALTERA_MBOX=m CONFIG_IOMMU_IOVA=y CONFIG_IOMMU_API=y +CONFIG_IOMMUFD_DRIVER=y CONFIG_IOMMU_SUPPORT=y # @@ -9325,7 +9394,6 @@ CONFIG_IOMMU_DEFAULT_DMA_LAZY=y CONFIG_IOMMU_DMA=y CONFIG_IOMMU_SVA=y CONFIG_AMD_IOMMU=y -CONFIG_AMD_IOMMU_V2=y CONFIG_DMAR_TABLE=y CONFIG_INTEL_IOMMU=y CONFIG_INTEL_IOMMU_SVM=y @@ -9407,6 +9475,7 @@ CONFIG_WPCM450_SOC=m # # Qualcomm SoC drivers # +CONFIG_QCOM_PMIC_PDCHARGER_ULOG=m CONFIG_QCOM_QMI_HELPERS=m # end of Qualcomm SoC drivers @@ -9418,6 +9487,31 @@ CONFIG_SOC_TI=y # end of Xilinx SoC drivers # end of SOC (System On Chip) specific Drivers +# +# PM Domains +# + +# +# Amlogic PM Domains +# +# end of Amlogic PM Domains + +# +# Broadcom PM Domains +# +# end of Broadcom PM Domains + +# +# i.MX PM Domains +# +# end of i.MX PM Domains + +# +# Qualcomm PM Domains +# +# end of Qualcomm PM Domains +# end of PM Domains + CONFIG_PM_DEVFREQ=y # @@ -9544,7 +9638,9 @@ CONFIG_STK8BA50=m # CONFIG_AD_SIGMA_DELTA=m CONFIG_AD4130=m +CONFIG_AD7091R=m CONFIG_AD7091R5=m +CONFIG_AD7091R8=m CONFIG_AD7124=m CONFIG_AD7192=m CONFIG_AD7266=m @@ -9576,6 +9672,7 @@ CONFIG_HX711=m CONFIG_INA2XX_ADC=m CONFIG_INTEL_MRFLD_ADC=m CONFIG_LP8788_ADC=m +CONFIG_LTC2309=m CONFIG_LTC2471=m CONFIG_LTC2485=m CONFIG_LTC2496=m @@ -9587,10 +9684,12 @@ CONFIG_MAX11205=m CONFIG_MAX11410=m CONFIG_MAX1241=m CONFIG_MAX1363=m +CONFIG_MAX34408=m CONFIG_MAX77541_ADC=m CONFIG_MAX9611=m CONFIG_MCP320X=m CONFIG_MCP3422=m +CONFIG_MCP3564=m CONFIG_MCP3911=m CONFIG_MEDIATEK_MT6360_ADC=m CONFIG_MEDIATEK_MT6370_ADC=m @@ -9615,7 +9714,6 @@ CONFIG_TI_ADS8344=m CONFIG_TI_ADS8688=m CONFIG_TI_ADS124S08=m CONFIG_TI_ADS131E08=m -CONFIG_TI_AM335X_ADC=m CONFIG_TI_LMP92064=m CONFIG_TI_TLC4541=m CONFIG_TI_TSC2046=m @@ -9657,6 +9755,7 @@ CONFIG_AD7746=m # # Chemical Sensors # +CONFIG_AOSONG_AGS02MA=m CONFIG_ATLAS_PH_SENSOR=m CONFIG_ATLAS_EZO_SENSOR=m CONFIG_BME680=m @@ -9747,6 +9846,7 @@ CONFIG_MAX5522=m CONFIG_MAX5821=m CONFIG_MCP4725=m CONFIG_MCP4728=m +CONFIG_MCP4821=m CONFIG_MCP4922=m CONFIG_TI_DAC082S085=m CONFIG_TI_DAC5571=m @@ -9835,6 +9935,7 @@ CONFIG_AM2315=m CONFIG_DHT11=m CONFIG_HDC100X=m CONFIG_HDC2010=m +CONFIG_HDC3020=m CONFIG_HID_SENSOR_HUMIDITY=m CONFIG_HTS221=m CONFIG_HTS221_I2C=m @@ -9854,6 +9955,9 @@ CONFIG_ADIS16480=m CONFIG_BMI160=m CONFIG_BMI160_I2C=m CONFIG_BMI160_SPI=m +CONFIG_BMI323=m +CONFIG_BMI323_I2C=m +CONFIG_BMI323_SPI=m CONFIG_BOSCH_BNO055=m CONFIG_BOSCH_BNO055_SERIAL=m CONFIG_BOSCH_BNO055_I2C=m @@ -9903,6 +10007,7 @@ CONFIG_IQS621_ALS=m CONFIG_SENSORS_ISL29018=m CONFIG_SENSORS_ISL29028=m CONFIG_ISL29125=m +CONFIG_ISL76682=m CONFIG_HID_SENSOR_ALS=m CONFIG_HID_SENSOR_PROX=m CONFIG_JSA1212=m @@ -9910,6 +10015,7 @@ CONFIG_ROHM_BU27008=m CONFIG_ROHM_BU27034=m CONFIG_RPR0521=m CONFIG_SENSORS_LM3533=m +CONFIG_LTR390=m CONFIG_LTR501=m CONFIG_LTRF216A=m CONFIG_LV0104CS=m @@ -9937,6 +10043,7 @@ CONFIG_VCNL4000=m CONFIG_VCNL4035=m CONFIG_VEML6030=m CONFIG_VEML6070=m +CONFIG_VEML6075=m CONFIG_VL6180=m CONFIG_ZOPT2201=m # end of Light sensors @@ -10022,6 +10129,7 @@ CONFIG_LMP91000=m # Pressure sensors # CONFIG_ABP060MG=m +CONFIG_ROHM_BM1390=m CONFIG_BMP280=m CONFIG_BMP280_I2C=m CONFIG_BMP280_SPI=m @@ -10030,6 +10138,9 @@ CONFIG_DLHL60D=m CONFIG_DPS310=m CONFIG_HID_SENSOR_PRESS=m CONFIG_HP03=m +CONFIG_HSC030PA=m +CONFIG_HSC030PA_I2C=m +CONFIG_HSC030PA_SPI=m CONFIG_ICP10100=m CONFIG_MPL115=m CONFIG_MPL115_I2C=m @@ -10082,6 +10193,7 @@ CONFIG_VL53L0X_I2C=m # CONFIG_AD2S90=m CONFIG_AD2S1200=m +CONFIG_AD2S1210=m # end of Resolver to digital converters # @@ -10093,6 +10205,7 @@ CONFIG_MAXIM_THERMOCOUPLE=m CONFIG_HID_SENSOR_TEMP=m CONFIG_MLX90614=m CONFIG_MLX90632=m +CONFIG_MLX90635=m CONFIG_TMP006=m CONFIG_TMP007=m CONFIG_TMP117=m @@ -10101,6 +10214,7 @@ CONFIG_TSYS02D=m CONFIG_MAX30208=m CONFIG_MAX31856=m CONFIG_MAX31865=m +CONFIG_MCP9600=m # end of Temperature sensors CONFIG_NTB=m @@ -10119,8 +10233,9 @@ CONFIG_PWM=y CONFIG_PWM_SYSFS=y # CONFIG_PWM_DEBUG is not set CONFIG_PWM_CLK=m -CONFIG_PWM_CRC=y +CONFIG_PWM_CRC=m CONFIG_PWM_CROS_EC=m +CONFIG_PWM_DWC_CORE=m CONFIG_PWM_DWC=m CONFIG_PWM_IQS620A=m CONFIG_PWM_LP3943=m @@ -10148,6 +10263,7 @@ CONFIG_RESET_TI_TPS380X=m # PHY Subsystem # CONFIG_GENERIC_PHY=y +CONFIG_GENERIC_PHY_MIPI_DPHY=y CONFIG_USB_LGM_PHY=m CONFIG_PHY_CAN_TRANSCEIVER=m @@ -10179,6 +10295,7 @@ CONFIG_MCB_LPC=m # # Performance monitor support # +CONFIG_DWC_PCIE_PMU=m # end of Performance monitor support CONFIG_RAS=y @@ -10197,7 +10314,7 @@ CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder" # CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set # end of Android -CONFIG_LIBNVDIMM=y +CONFIG_LIBNVDIMM=m CONFIG_BLK_DEV_PMEM=m CONFIG_ND_CLAIM=y CONFIG_ND_BTT=m @@ -10205,6 +10322,8 @@ CONFIG_BTT=y CONFIG_ND_PFN=m CONFIG_NVDIMM_PFN=y CONFIG_NVDIMM_DAX=y +CONFIG_NVDIMM_KEYS=y +# CONFIG_NVDIMM_SECURITY_TEST is not set CONFIG_DAX=y CONFIG_DEV_DAX=m CONFIG_DEV_DAX_PMEM=m @@ -10214,14 +10333,7 @@ CONFIG_DEV_DAX_HMEM_DEVICES=y CONFIG_DEV_DAX_KMEM=m CONFIG_NVMEM=y CONFIG_NVMEM_SYSFS=y - -# -# Layout Types -# -CONFIG_NVMEM_LAYOUT_SL28_VPD=m -CONFIG_NVMEM_LAYOUT_ONIE_TLV=m -# end of Layout Types - +# CONFIG_NVMEM_LAYOUTS is not set CONFIG_NVMEM_RAVE_SP_EEPROM=m CONFIG_NVMEM_RMEM=m @@ -10294,6 +10406,7 @@ CONFIG_MOST_CDEV=m CONFIG_MOST_SND=m # CONFIG_PECI is not set CONFIG_HTE=y +CONFIG_DPLL=y # end of Device Drivers # @@ -10302,6 +10415,7 @@ CONFIG_HTE=y CONFIG_DCACHE_WORD_ACCESS=y CONFIG_VALIDATE_FS_PARSER=y CONFIG_FS_IOMAP=y +CONFIG_FS_STACK=y CONFIG_BUFFER_HEAD=y CONFIG_LEGACY_DIRECT_IO=y # CONFIG_EXT2_FS is not set @@ -10347,7 +10461,6 @@ CONFIG_OCFS2_DEBUG_MASKLOG=y # CONFIG_OCFS2_DEBUG_FS is not set CONFIG_BTRFS_FS=m CONFIG_BTRFS_FS_POSIX_ACL=y -# CONFIG_BTRFS_FS_CHECK_INTEGRITY is not set # CONFIG_BTRFS_FS_RUN_SANITY_TESTS is not set # CONFIG_BTRFS_DEBUG is not set # CONFIG_BTRFS_ASSERT is not set @@ -10368,12 +10481,15 @@ CONFIG_F2FS_FS_LZ4HC=y CONFIG_F2FS_FS_ZSTD=y CONFIG_F2FS_IOSTAT=y CONFIG_F2FS_UNFAIR_RWSEM=y -CONFIG_BCACHEFS_FS=y +CONFIG_BCACHEFS_FS=m CONFIG_BCACHEFS_QUOTA=y +# CONFIG_BCACHEFS_ERASURE_CODING is not set CONFIG_BCACHEFS_POSIX_ACL=y # CONFIG_BCACHEFS_DEBUG is not set # CONFIG_BCACHEFS_TESTS is not set CONFIG_BCACHEFS_LOCK_TIME_STATS=y +# CONFIG_BCACHEFS_NO_LATENCY_ACCT is not set +CONFIG_BCACHEFS_SIX_OPTIMISTIC_SPIN=y CONFIG_ZONEFS_FS=m CONFIG_FS_DAX=y CONFIG_FS_DAX_PMD=y @@ -10416,7 +10532,7 @@ CONFIG_OVERLAY_FS_METACOPY=y # CONFIG_NETFS_SUPPORT=m CONFIG_NETFS_STATS=y -CONFIG_FSCACHE=m +CONFIG_FSCACHE=y CONFIG_FSCACHE_STATS=y # CONFIG_FSCACHE_DEBUG is not set CONFIG_CACHEFILES=m @@ -10472,9 +10588,9 @@ CONFIG_TMPFS_XATTR=y CONFIG_TMPFS_INODE64=y CONFIG_TMPFS_QUOTA=y CONFIG_HUGETLBFS=y +# CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON is not set CONFIG_HUGETLB_PAGE=y CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y -# CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON is not set CONFIG_ARCH_HAS_GIGANTIC_PAGE=y CONFIG_CONFIGFS_FS=y CONFIG_EFIVAR_FS=y @@ -10602,6 +10718,7 @@ CONFIG_NFSD_SCSILAYOUT=y # CONFIG_NFSD_FLEXFILELAYOUT is not set CONFIG_NFSD_V4_2_INTER_SSC=y CONFIG_NFSD_V4_SECURITY_LABEL=y +# CONFIG_NFSD_LEGACY_CLIENT_TRACKING is not set CONFIG_GRACE_PERIOD=m CONFIG_LOCKD=m CONFIG_LOCKD_V4=y @@ -10633,7 +10750,7 @@ CONFIG_CIFS_DEBUG=y # CONFIG_CIFS_DEBUG_DUMP_KEYS is not set CONFIG_CIFS_DFS_UPCALL=y CONFIG_CIFS_SWN_UPCALL=y -# CONFIG_CIFS_SMB_DIRECT is not set +CONFIG_CIFS_SMB_DIRECT=y CONFIG_CIFS_FSCACHE=y CONFIG_SMB_SERVER=m CONFIG_SMB_SERVER_SMBDIRECT=y @@ -10903,14 +11020,12 @@ CONFIG_CRYPTO_TWOFISH_COMMON=m CONFIG_CRYPTO_ADIANTUM=m CONFIG_CRYPTO_CHACHA20=m CONFIG_CRYPTO_CBC=m -CONFIG_CRYPTO_CFB=m CONFIG_CRYPTO_CTR=y CONFIG_CRYPTO_CTS=m -CONFIG_CRYPTO_ECB=m +CONFIG_CRYPTO_ECB=y CONFIG_CRYPTO_HCTR2=m CONFIG_CRYPTO_KEYWRAP=m CONFIG_CRYPTO_LRW=m -CONFIG_CRYPTO_OFB=m CONFIG_CRYPTO_PCBC=m CONFIG_CRYPTO_XCTR=m CONFIG_CRYPTO_XTS=m @@ -10986,7 +11101,9 @@ CONFIG_CRYPTO_DRBG_HASH=y CONFIG_CRYPTO_DRBG_CTR=y CONFIG_CRYPTO_DRBG=y CONFIG_CRYPTO_JITTERENTROPY=y -# CONFIG_CRYPTO_JITTERENTROPY_TESTINTERFACE is not set +CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKS=64 +CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKSIZE=32 +CONFIG_CRYPTO_JITTERENTROPY_OSR=1 CONFIG_CRYPTO_KDF800108_CTR=y # end of Random number generation @@ -11065,9 +11182,12 @@ CONFIG_CRYPTO_DEV_QAT_DH895xCC=m CONFIG_CRYPTO_DEV_QAT_C3XXX=m CONFIG_CRYPTO_DEV_QAT_C62X=m CONFIG_CRYPTO_DEV_QAT_4XXX=m +CONFIG_CRYPTO_DEV_QAT_420XX=m CONFIG_CRYPTO_DEV_QAT_DH895xCCVF=m CONFIG_CRYPTO_DEV_QAT_C3XXXVF=m CONFIG_CRYPTO_DEV_QAT_C62XVF=m +CONFIG_CRYPTO_DEV_IAA_CRYPTO=m +# CONFIG_CRYPTO_DEV_IAA_CRYPTO_STATS is not set CONFIG_CRYPTO_DEV_CHELSIO=m CONFIG_CRYPTO_DEV_VIRTIO=m CONFIG_CRYPTO_DEV_SAFEXCEL=m @@ -11092,6 +11212,7 @@ CONFIG_SYSTEM_TRUSTED_KEYRING=y CONFIG_SYSTEM_TRUSTED_KEYS="" # CONFIG_SYSTEM_EXTRA_CERTIFICATE is not set CONFIG_SECONDARY_TRUSTED_KEYRING=y +# CONFIG_SECONDARY_TRUSTED_KEYRING_SIGNED_BY_BUILTIN is not set CONFIG_SYSTEM_BLACKLIST_KEYRING=y CONFIG_SYSTEM_BLACKLIST_HASH_LIST="" CONFIG_SYSTEM_REVOCATION_LIST=y @@ -11179,7 +11300,6 @@ CONFIG_ZSTD_DECOMPRESS=y CONFIG_XZ_DEC=y CONFIG_XZ_DEC_X86=y CONFIG_XZ_DEC_POWERPC=y -CONFIG_XZ_DEC_IA64=y CONFIG_XZ_DEC_ARM=y CONFIG_XZ_DEC_ARMTHUMB=y CONFIG_XZ_DEC_SPARC=y @@ -11207,6 +11327,7 @@ CONFIG_INTERVAL_TREE=y CONFIG_INTERVAL_TREE_SPAN_ITER=y CONFIG_XARRAY_MULTI=y CONFIG_ASSOCIATIVE_ARRAY=y +CONFIG_CLOSURES=y CONFIG_HAS_IOMEM=y CONFIG_HAS_IOPORT=y CONFIG_HAS_IOPORT_MAP=y @@ -11275,14 +11396,17 @@ CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y CONFIG_ARCH_HAS_COPY_MC=y CONFIG_ARCH_STACKWALK=y CONFIG_STACKDEPOT=y +CONFIG_STACKDEPOT_MAX_FRAMES=64 CONFIG_SBITMAP=y CONFIG_PARMAN=m CONFIG_OBJAGG=m +# CONFIG_LWQ_TEST is not set # end of Library routines CONFIG_PLDMFW=y CONFIG_ASN1_ENCODER=m CONFIG_POLYNOMIAL=m +CONFIG_FIRMWARE_TABLE=y # # Kernel hacking @@ -11311,7 +11435,7 @@ CONFIG_DEBUG_KERNEL=y # Compile-time checks and compiler options # CONFIG_DEBUG_INFO=y -CONFIG_AS_HAS_NON_CONST_LEB128=y +CONFIG_AS_HAS_NON_CONST_ULEB128=y # CONFIG_DEBUG_INFO_NONE is not set # CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT is not set # CONFIG_DEBUG_INFO_DWARF4 is not set @@ -11482,15 +11606,14 @@ CONFIG_STACKTRACE=y # # Debug kernel data structures # -CONFIG_DEBUG_LIST=y +# CONFIG_DEBUG_LIST is not set # CONFIG_DEBUG_PLIST is not set # CONFIG_DEBUG_SG is not set # CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_CLOSURES is not set # CONFIG_DEBUG_MAPLE_TREE is not set # end of Debug kernel data structures -# CONFIG_DEBUG_CREDENTIALS is not set - # # RCU Debugging # @@ -11500,13 +11623,14 @@ CONFIG_DEBUG_LIST=y CONFIG_RCU_CPU_STALL_TIMEOUT=60 CONFIG_RCU_EXP_CPU_STALL_TIMEOUT=0 # CONFIG_RCU_CPU_STALL_CPUTIME is not set +# CONFIG_RCU_CPU_STALL_NOTIFIER is not set # CONFIG_RCU_TRACE is not set # CONFIG_RCU_EQS_DEBUG is not set # end of RCU Debugging # CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set # CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set -CONFIG_LATENCYTOP=y +# CONFIG_LATENCYTOP is not set # CONFIG_DEBUG_CGROUP_REF is not set CONFIG_USER_STACKTRACE_SUPPORT=y CONFIG_NOP_TRACER=y @@ -11681,6 +11805,7 @@ CONFIG_ASYNC_RAID6_TEST=m # CONFIG_TEST_FREE_PAGES is not set # CONFIG_TEST_FPU is not set # CONFIG_TEST_CLOCKSOURCE_WATCHDOG is not set +# CONFIG_TEST_OBJPOOL is not set CONFIG_ARCH_USE_MEMTEST=y CONFIG_MEMTEST=y # CONFIG_HYPERV_TESTING is not set @@ -11690,5 +11815,4 @@ CONFIG_MEMTEST=y # Rust hacking # # end of Rust hacking -# end of Kernel hacking - +# end of Kernel hacking \ No newline at end of file diff --git a/patches/cachyos/0001-bore-cachy.patch b/patches/cachyos/0001-bore-cachy.patch index d92f986..8aeeab5 100644 --- a/patches/cachyos/0001-bore-cachy.patch +++ b/patches/cachyos/0001-bore-cachy.patch @@ -1,23 +1,24 @@ -From b6a7058a13f345d5aa5426466f9104da43d47ce4 Mon Sep 17 00:00:00 2001 +From 1ab81cfa061f454316364a32761ce45a7479e616 Mon Sep 17 00:00:00 2001 From: Piotr Gorski -Date: Tue, 5 Dec 2023 15:49:10 +0100 +Date: Thu, 7 Mar 2024 22:28:47 +0100 Subject: [PATCH] bore-cachy Signed-off-by: Piotr Gorski --- - include/linux/sched.h | 10 ++ - init/Kconfig | 19 ++++ - kernel/sched/core.c | 128 ++++++++++++++++++++++++ - kernel/sched/debug.c | 3 + - kernel/sched/fair.c | 216 +++++++++++++++++++++++++++++++++++++--- + include/linux/sched.h | 12 ++ + init/Kconfig | 19 +++ + kernel/sched/core.c | 148 +++++++++++++++++++ + kernel/sched/debug.c | 61 +++++++- + kernel/sched/fair.c | 319 ++++++++++++++++++++++++++++++++++++---- kernel/sched/features.h | 4 + - 6 files changed, 366 insertions(+), 14 deletions(-) + kernel/sched/sched.h | 7 + + 7 files changed, 542 insertions(+), 28 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h -index 77f01ac38..01f2839ad 100644 +index ffe8f618a..7ac6163f9 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h -@@ -559,6 +559,16 @@ struct sched_entity { +@@ -547,6 +547,18 @@ struct sched_entity { u64 sum_exec_runtime; u64 prev_sum_exec_runtime; u64 vruntime; @@ -26,19 +27,21 @@ index 77f01ac38..01f2839ad 100644 + u8 prev_burst_penalty; + u8 curr_burst_penalty; + u8 burst_penalty; -+ u8 slice_score; ++ u8 burst_score; ++ u32 burst_load; ++ bool on_cfs_rq; + u8 child_burst; -+ u16 child_burst_cnt; ++ u32 child_burst_cnt; + u64 child_burst_last_cached; +#endif // CONFIG_SCHED_BORE s64 vlag; u64 slice; diff --git a/init/Kconfig b/init/Kconfig -index 9dee4c100..49d343e97 100644 +index 47671886d..c99132cf6 100644 --- a/init/Kconfig +++ b/init/Kconfig -@@ -1278,6 +1278,25 @@ config CHECKPOINT_RESTORE +@@ -1299,6 +1299,25 @@ config CHECKPOINT_RESTORE If unsure, say N here. @@ -65,10 +68,10 @@ index 9dee4c100..49d343e97 100644 bool "Automatic process group scheduling" select CGROUPS diff --git a/kernel/sched/core.c b/kernel/sched/core.c -index a854b7183..a98cfa7ab 100644 +index 9116bcc90..43e4311db 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c -@@ -4488,6 +4488,123 @@ int wake_up_state(struct task_struct *p, unsigned int state) +@@ -4507,6 +4507,143 @@ int wake_up_state(struct task_struct *p, unsigned int state) return try_to_wake_up(p, state, 0); } @@ -77,20 +80,24 @@ index a854b7183..a98cfa7ab 100644 +extern u8 sched_burst_fork_atavistic; +extern uint sched_burst_cache_lifetime; + -+void __init sched_init_bore(void) { ++static void __init sched_init_bore(void) { + init_task.se.burst_time = 0; + init_task.se.prev_burst_penalty = 0; + init_task.se.curr_burst_penalty = 0; + init_task.se.burst_penalty = 0; -+ init_task.se.slice_score = 0; ++ init_task.se.burst_score = 0; ++ init_task.se.on_cfs_rq = false; + init_task.se.child_burst_last_cached = 0; ++ init_task.se.burst_load = 0; +} + +void inline sched_fork_bore(struct task_struct *p) { + p->se.burst_time = 0; + p->se.curr_burst_penalty = 0; -+ p->se.slice_score = 0; ++ p->se.burst_score = 0; ++ p->se.on_cfs_rq = false; + p->se.child_burst_last_cached = 0; ++ p->se.burst_load = 0; +} + +static u32 count_child_tasks(struct task_struct *p) { @@ -100,8 +107,14 @@ index a854b7183..a98cfa7ab 100644 + return cnt; +} + ++static inline bool task_is_inheritable(struct task_struct *p) { ++ return (p->sched_class == &fair_sched_class); ++} ++ +static inline bool child_burst_cache_expired(struct task_struct *p, u64 now) { -+ return (p->se.child_burst_last_cached + sched_burst_cache_lifetime < now); ++ u64 expiration_time = ++ p->se.child_burst_last_cached + sched_burst_cache_lifetime; ++ return ((s64)(expiration_time - now) < 0); +} + +static void __update_child_burst_cache( @@ -113,13 +126,13 @@ index a854b7183..a98cfa7ab 100644 + p->se.child_burst_last_cached = now; +} + -+static void update_child_burst_cache(struct task_struct *p, u64 now) { ++static inline void update_child_burst_direct(struct task_struct *p, u64 now) { + struct task_struct *child; + u32 cnt = 0; + u32 sum = 0; + + list_for_each_entry(child, &p->children, sibling) { -+ if (child->sched_class != &fair_sched_class) continue; ++ if (!task_is_inheritable(child)) continue; + cnt++; + sum += child->se.burst_penalty; + } @@ -127,7 +140,15 @@ index a854b7183..a98cfa7ab 100644 + __update_child_burst_cache(p, cnt, sum, now); +} + -+static void update_child_burst_cache_atavistic( ++static inline u8 __inherit_burst_direct(struct task_struct *p, u64 now) { ++ struct task_struct *parent = p->real_parent; ++ if (child_burst_cache_expired(parent, now)) ++ update_child_burst_direct(parent, now); ++ ++ return parent->se.child_burst; ++} ++ ++static void update_child_burst_topological( + struct task_struct *p, u64 now, u32 depth, u32 *acnt, u32 *asum) { + struct task_struct *child, *dec; + u32 cnt = 0, dcnt = 0; @@ -139,7 +160,7 @@ index a854b7183..a98cfa7ab 100644 + dec = list_first_entry(&dec->children, struct task_struct, sibling); + + if (!dcnt || !depth) { -+ if (dec->sched_class != &fair_sched_class) continue; ++ if (!task_is_inheritable(dec)) continue; + cnt++; + sum += dec->se.burst_penalty; + continue; @@ -149,7 +170,7 @@ index a854b7183..a98cfa7ab 100644 + sum += (u32)dec->se.child_burst * dec->se.child_burst_cnt; + continue; + } -+ update_child_burst_cache_atavistic(dec, now, depth - 1, &cnt, &sum); ++ update_child_burst_topological(dec, now, depth - 1, &cnt, &sum); + } + + __update_child_burst_cache(p, cnt, sum, now); @@ -157,42 +178,44 @@ index a854b7183..a98cfa7ab 100644 + *asum += sum; +} + -+static void sched_post_fork_bore(struct task_struct *p) { -+ struct sched_entity *se = &p->se; -+ struct task_struct *anc; -+ u64 now; -+ u32 cnt = 0, sum = 0, depth; ++static inline u8 __inherit_burst_topological(struct task_struct *p, u64 now) { ++ struct task_struct *anc = p->real_parent; ++ u32 cnt = 0, sum = 0; ++ ++ while (anc->real_parent != anc && count_child_tasks(anc) == 1) ++ anc = anc->real_parent; ++ ++ if (child_burst_cache_expired(anc, now)) ++ update_child_burst_topological( ++ anc, now, sched_burst_fork_atavistic - 1, &cnt, &sum); ++ ++ return anc->se.child_burst; ++} ++ ++static inline void inherit_burst(struct task_struct *p) { + u8 burst_cache; ++ u64 now = ktime_get_ns(); + -+ if (likely(sched_bore)) { -+ now = ktime_get_ns(); -+ read_lock(&tasklist_lock); ++ read_lock(&tasklist_lock); ++ burst_cache = likely(sched_burst_fork_atavistic)? ++ __inherit_burst_topological(p, now): ++ __inherit_burst_direct(p, now); ++ read_unlock(&tasklist_lock); + -+ anc = p->real_parent; -+ depth = sched_burst_fork_atavistic; -+ if (likely(depth)) { -+ while ((anc->real_parent != anc) && (count_child_tasks(anc) == 1)) -+ anc = anc->real_parent; -+ if (child_burst_cache_expired(anc, now)) -+ update_child_burst_cache_atavistic( -+ anc, now, depth - 1, &cnt, &sum); -+ } else -+ if (child_burst_cache_expired(anc, now)) -+ update_child_burst_cache(anc, now); ++ p->se.prev_burst_penalty = max(p->se.prev_burst_penalty, burst_cache); ++} + -+ burst_cache = anc->se.child_burst; -+ -+ read_unlock(&tasklist_lock); -+ se->prev_burst_penalty = max(se->prev_burst_penalty, burst_cache); -+ } -+ se->burst_penalty = se->prev_burst_penalty; ++static void sched_post_fork_bore(struct task_struct *p) { ++ if (p->sched_class == &fair_sched_class && likely(sched_bore)) ++ inherit_burst(p); ++ p->se.burst_penalty = p->se.prev_burst_penalty; +} +#endif // CONFIG_SCHED_BORE + /* * Perform scheduler related setup for a newly forked process p. * p is forked by current. -@@ -4504,6 +4621,9 @@ static void __sched_fork(unsigned long clone_flags, struct task_struct *p) +@@ -4523,6 +4660,9 @@ static void __sched_fork(unsigned long clone_flags, struct task_struct *p) p->se.prev_sum_exec_runtime = 0; p->se.nr_migrations = 0; p->se.vruntime = 0; @@ -202,7 +225,7 @@ index a854b7183..a98cfa7ab 100644 p->se.vlag = 0; p->se.slice = sysctl_sched_base_slice; INIT_LIST_HEAD(&p->se.group_node); -@@ -4823,6 +4943,9 @@ void sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) +@@ -4839,6 +4979,9 @@ void sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) void sched_post_fork(struct task_struct *p) { @@ -212,34 +235,128 @@ index a854b7183..a98cfa7ab 100644 uclamp_post_fork(p); } -@@ -9922,6 +10045,11 @@ void __init sched_init(void) +@@ -9910,6 +10053,11 @@ void __init sched_init(void) BUG_ON(&dl_sched_class != &stop_sched_class + 1); #endif +#ifdef CONFIG_SCHED_BORE + sched_init_bore(); -+ printk(KERN_INFO "BORE (Burst-Oriented Response Enhancer) CPU Scheduler modification 3.5.7 by Masahito Suzuki"); ++ printk(KERN_INFO "BORE (Burst-Oriented Response Enhancer) CPU Scheduler modification 4.5.2 by Masahito Suzuki"); +#endif // CONFIG_SCHED_BORE + wait_bit_init(); #ifdef CONFIG_FAIR_GROUP_SCHED diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c -index 4c3d0d9f3..e37fdfad1 100644 +index 8d5d98a58..a565363fd 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c -@@ -595,6 +595,9 @@ print_task(struct seq_file *m, struct rq *rq, struct task_struct *p) +@@ -167,7 +167,52 @@ static const struct file_operations sched_feat_fops = { + }; + + #ifdef CONFIG_SMP ++#ifdef CONFIG_SCHED_BORE ++static ssize_t sched_min_base_slice_write(struct file *filp, const char __user *ubuf, ++ size_t cnt, loff_t *ppos) ++{ ++ char buf[16]; ++ unsigned int value; ++ ++ if (cnt > 15) ++ cnt = 15; ++ ++ if (copy_from_user(&buf, ubuf, cnt)) ++ return -EFAULT; ++ buf[cnt] = '\0'; ++ ++ if (kstrtouint(buf, 10, &value)) ++ return -EINVAL; + ++ if (!value) ++ return -EINVAL; ++ ++ sysctl_sched_min_base_slice = value; ++ sched_update_min_base_slice(); ++ ++ *ppos += cnt; ++ return cnt; ++} ++ ++static int sched_min_base_slice_show(struct seq_file *m, void *v) ++{ ++ seq_printf(m, "%d\n", sysctl_sched_min_base_slice); ++ return 0; ++} ++ ++static int sched_min_base_slice_open(struct inode *inode, struct file *filp) ++{ ++ return single_open(filp, sched_min_base_slice_show, NULL); ++} ++ ++static const struct file_operations sched_min_base_slice_fops = { ++ .open = sched_min_base_slice_open, ++ .write = sched_min_base_slice_write, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++#else // !CONFIG_SCHED_BORE + static ssize_t sched_scaling_write(struct file *filp, const char __user *ubuf, + size_t cnt, loff_t *ppos) + { +@@ -213,7 +258,7 @@ static const struct file_operations sched_scaling_fops = { + .llseek = seq_lseek, + .release = single_release, + }; +- ++#endif // CONFIG_SCHED_BORE + #endif /* SMP */ + + #ifdef CONFIG_PREEMPT_DYNAMIC +@@ -347,13 +392,20 @@ static __init int sched_init_debug(void) + debugfs_create_file("preempt", 0644, debugfs_sched, NULL, &sched_dynamic_fops); + #endif + ++#ifdef CONFIG_SCHED_BORE ++ debugfs_create_file("min_base_slice_ns", 0644, debugfs_sched, NULL, &sched_min_base_slice_fops); ++ debugfs_create_u32("base_slice_ns", 0400, debugfs_sched, &sysctl_sched_base_slice); ++#else // !CONFIG_SCHED_BORE + debugfs_create_u32("base_slice_ns", 0644, debugfs_sched, &sysctl_sched_base_slice); ++#endif // CONFIG_SCHED_BORE + + 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); + + #ifdef CONFIG_SMP ++#if !defined(CONFIG_SCHED_BORE) + debugfs_create_file("tunable_scaling", 0644, debugfs_sched, NULL, &sched_scaling_fops); ++#endif // CONFIG_SCHED_BORE + debugfs_create_u32("migration_cost_ns", 0644, debugfs_sched, &sysctl_sched_migration_cost); + debugfs_create_u32("nr_migrate", 0644, debugfs_sched, &sysctl_sched_nr_migrate); + +@@ -595,6 +647,9 @@ print_task(struct seq_file *m, struct rq *rq, struct task_struct *p) 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", p->se.slice_score); -+#endif ++ SEQ_printf(m, " %2d", p->se.burst_score); ++#endif // CONFIG_SCHED_BORE #ifdef CONFIG_NUMA_BALANCING SEQ_printf(m, " %d %d", task_node(p), task_numa_group_id(p)); #endif +@@ -1068,6 +1123,10 @@ void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, + + P(se.load.weight); + #ifdef CONFIG_SMP ++#ifdef CONFIG_SCHED_BORE ++ P(se.burst_load); ++ P(se.burst_score); ++#endif // CONFIG_SCHED_BORE + P(se.avg.load_sum); + P(se.avg.runnable_sum); + P(se.avg.util_sum); diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c -index fa9fff0f9..5e4f0ccff 100644 +index fc0a9de42..3ee4e7e70 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -19,6 +19,9 @@ @@ -248,11 +365,11 @@ index fa9fff0f9..5e4f0ccff 100644 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra + * + * Burst-Oriented Response Enhancer (BORE) CPU Scheduler -+ * Copyright (C) 2021-2023 Masahito Suzuki ++ * Copyright (C) 2021-2024 Masahito Suzuki */ #include #include -@@ -66,17 +69,28 @@ +@@ -64,28 +67,128 @@ * SCHED_TUNABLESCALING_LOG - scaled logarithmical, *1+ilog(ncpus) * SCHED_TUNABLESCALING_LINEAR - scaled linear, *ncpus * @@ -262,7 +379,7 @@ index fa9fff0f9..5e4f0ccff 100644 */ +#ifdef CONFIG_SCHED_BORE +unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_NONE; -+#else // CONFIG_SCHED_BORE ++#else // !CONFIG_SCHED_BORE unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; +#endif // CONFIG_SCHED_BORE @@ -270,34 +387,42 @@ index fa9fff0f9..5e4f0ccff 100644 * Minimal preemption granularity for CPU-bound tasks: * - * (default: 0.75 msec * (1 + ilog(ncpus)), units: nanoseconds) -+ * (BORE default: 3 msec constant, units: nanoseconds) ++ * (BORE default: max(1 sec / HZ, min_base_slice) constant, units: nanoseconds) + * (EEVDF default: 0.75 msec * (1 + ilog(ncpus)), units: nanoseconds) */ +-#ifdef CONFIG_CACHY +-unsigned int sysctl_sched_base_slice = 350000ULL; +-static unsigned int normalized_sysctl_sched_base_slice = 350000ULL; +-#else +#ifdef CONFIG_SCHED_BORE -+unsigned int sysctl_sched_base_slice = 3000000ULL; -+static unsigned int normalized_sysctl_sched_base_slice = 3000000ULL; -+#else // CONFIG_SCHED_BORE ++unsigned int sysctl_sched_base_slice = 1000000000ULL / HZ; ++static unsigned int configured_sched_base_slice = 1000000000ULL / HZ; ++unsigned int sysctl_sched_min_base_slice = 2000000ULL; ++#else // !CONFIG_SCHED_BORE unsigned int sysctl_sched_base_slice = 750000ULL; static unsigned int normalized_sysctl_sched_base_slice = 750000ULL; +-#endif +#endif // CONFIG_SCHED_BORE - /* - * After fork, child runs first. If set to 0 (default) then -@@ -86,6 +100,68 @@ unsigned int sysctl_sched_child_runs_first __read_mostly; - +-#ifdef CONFIG_CACHY +-const_debug unsigned int sysctl_sched_migration_cost = 300000UL; +-#else const_debug unsigned int sysctl_sched_migration_cost = 500000UL; - +-#endif ++ +#ifdef CONFIG_SCHED_BORE -+bool __read_mostly sched_bore = 1; -+bool __read_mostly sched_burst_score_rounding = 0; -+bool __read_mostly sched_burst_smoothness_long = 1; -+bool __read_mostly sched_burst_smoothness_short = 0; ++u8 __read_mostly sched_bore = 1; ++u8 __read_mostly sched_burst_score_rounding = 0; ++u8 __read_mostly sched_burst_smoothness_long = 1; ++u8 __read_mostly sched_burst_smoothness_short = 0; +u8 __read_mostly sched_burst_fork_atavistic = 2; +u8 __read_mostly sched_burst_penalty_offset = 22; +uint __read_mostly sched_burst_penalty_scale = 1280; +uint __read_mostly sched_burst_cache_lifetime = 60000000; -+static u8 sixty_four = 64; -+static uint maxval_12_bits = 4095; ++u8 __read_mostly sched_vlag_deviation_limit = 11; ++static int __maybe_unused thirty_two = 32; ++static int __maybe_unused sixty_four = 64; ++static int __maybe_unused maxval_12_bits = 4095; + +#define MAX_BURST_PENALTY (39U <<2) + @@ -319,19 +444,38 @@ index fa9fff0f9..5e4f0ccff 100644 + return min(MAX_BURST_PENALTY, scaled_penalty); +} + -+static inline void update_burst_penalty(struct sched_entity *se) { -+ se->curr_burst_penalty = calc_burst_penalty(se->burst_time); -+ se->burst_penalty = max(se->prev_burst_penalty, se->curr_burst_penalty); ++static inline u64 scale_slice(u64 delta, struct sched_entity *se) { ++ return mul_u64_u32_shr(delta, sched_prio_to_wmult[se->burst_score], 22); +} + -+static inline void update_slice_score(struct sched_entity *se) { ++static inline u64 __unscale_slice(u64 delta, u8 score) { ++ return mul_u64_u32_shr(delta, sched_prio_to_weight[score], 10); ++} ++ ++static inline u64 unscale_slice(u64 delta, struct sched_entity *se) { ++ return __unscale_slice(delta, se->burst_score); ++} ++ ++static void avg_vruntime_add(struct cfs_rq *cfs_rq, struct sched_entity *se); ++static void avg_vruntime_sub(struct cfs_rq *cfs_rq, struct sched_entity *se); ++ ++static void update_burst_score(struct sched_entity *se) { ++ struct cfs_rq *cfs_rq = cfs_rq_of(se); ++ u8 prev_score = se->burst_score; + u32 penalty = se->burst_penalty; + if (sched_burst_score_rounding) penalty += 0x2U; -+ se->slice_score = penalty >> 2; ++ se->burst_score = penalty >> 2; ++ ++ if ((se->burst_score != prev_score) && se->on_cfs_rq) { ++ avg_vruntime_sub(cfs_rq, se); ++ avg_vruntime_add(cfs_rq, se); ++ } +} + -+static inline u64 scale_slice(u64 delta, struct sched_entity *se) { -+ return mul_u64_u32_shr(delta, sched_prio_to_wmult[se->slice_score], 22); ++static 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); ++ update_burst_score(se); +} + +static inline u32 binary_smooth(u32 new, u32 old) { @@ -346,13 +490,39 @@ index fa9fff0f9..5e4f0ccff 100644 + binary_smooth(se->curr_burst_penalty, se->prev_burst_penalty); + se->curr_burst_penalty = 0; + se->burst_time = 0; ++ update_burst_score(se); ++} ++ ++static void restart_burst_rescale_deadline(struct sched_entity *se) { ++ s64 vscaled, wremain, vremain = se->deadline - se->vruntime; ++ u8 prev_score = se->burst_score; ++ restart_burst(se); ++ if (prev_score > se->burst_score) { ++ wremain = __unscale_slice(abs(vremain), prev_score); ++ vscaled = scale_slice(wremain, se); ++ if (unlikely(vremain < 0)) ++ vscaled = -vscaled; ++ se->deadline = se->vruntime + vscaled; ++ } +} +#endif // CONFIG_SCHED_BORE -+ + int sched_thermal_decay_shift; static int __init setup_sched_thermal_decay_shift(char *str) - { -@@ -145,6 +221,70 @@ static unsigned int sysctl_numa_balancing_promote_rate_limit = 65536; +@@ -136,12 +239,8 @@ int __weak arch_asym_cpu_priority(int cpu) + * + * (default: 5 msec, units: microseconds) + */ +-#ifdef CONFIG_CACHY +-static unsigned int sysctl_sched_cfs_bandwidth_slice = 3000UL; +-#else + static unsigned int sysctl_sched_cfs_bandwidth_slice = 5000UL; + #endif +-#endif + + #ifdef CONFIG_NUMA_BALANCING + /* Restrict the NUMA promotion throughput (MB/s) for each target node. */ +@@ -150,6 +249,87 @@ static unsigned int sysctl_numa_balancing_promote_rate_limit = 65536; #ifdef CONFIG_SYSCTL static struct ctl_table sched_fair_sysctls[] = { @@ -360,9 +530,65 @@ index fa9fff0f9..5e4f0ccff 100644 + { + .procname = "sched_bore", + .data = &sched_bore, -+ .maxlen = sizeof(bool), ++ .maxlen = sizeof(u8), + .mode = 0644, -+ .proc_handler = &proc_dobool, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_burst_score_rounding", ++ .data = &sched_burst_score_rounding, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_burst_smoothness_long", ++ .data = &sched_burst_smoothness_long, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_burst_smoothness_short", ++ .data = &sched_burst_smoothness_short, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_burst_fork_atavistic", ++ .data = &sched_burst_fork_atavistic, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_THREE, ++ }, ++ { ++ .procname = "sched_burst_penalty_offset", ++ .data = &sched_burst_penalty_offset, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &sixty_four, ++ }, ++ { ++ .procname = "sched_burst_penalty_scale", ++ .data = &sched_burst_penalty_scale, ++ .maxlen = sizeof(uint), ++ .mode = 0644, ++ .proc_handler = proc_douintvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &maxval_12_bits, + }, + { + .procname = "sched_burst_cache_lifetime", @@ -372,58 +598,41 @@ index fa9fff0f9..5e4f0ccff 100644 + .proc_handler = proc_douintvec, + }, + { -+ .procname = "sched_burst_fork_atavistic", -+ .data = &sched_burst_fork_atavistic, ++ .procname = "sched_vlag_deviation_limit", ++ .data = &sched_vlag_deviation_limit, + .maxlen = sizeof(u8), + .mode = 0644, -+ .proc_handler = &proc_dou8vec_minmax, ++ .proc_handler = proc_dou8vec_minmax, + .extra1 = SYSCTL_ZERO, -+ .extra2 = SYSCTL_THREE, -+ }, -+ { -+ .procname = "sched_burst_penalty_offset", -+ .data = &sched_burst_penalty_offset, -+ .maxlen = sizeof(u8), -+ .mode = 0644, -+ .proc_handler = &proc_dou8vec_minmax, -+ .extra1 = SYSCTL_ZERO, -+ .extra2 = &sixty_four, -+ }, -+ { -+ .procname = "sched_burst_penalty_scale", -+ .data = &sched_burst_penalty_scale, -+ .maxlen = sizeof(uint), -+ .mode = 0644, -+ .proc_handler = &proc_douintvec_minmax, -+ .extra1 = SYSCTL_ZERO, -+ .extra2 = &maxval_12_bits, -+ }, -+ { -+ .procname = "sched_burst_score_rounding", -+ .data = &sched_burst_score_rounding, -+ .maxlen = sizeof(bool), -+ .mode = 0644, -+ .proc_handler = &proc_dobool, -+ }, -+ { -+ .procname = "sched_burst_smoothness_long", -+ .data = &sched_burst_smoothness_long, -+ .maxlen = sizeof(bool), -+ .mode = 0644, -+ .proc_handler = &proc_dobool, -+ }, -+ { -+ .procname = "sched_burst_smoothness_short", -+ .data = &sched_burst_smoothness_short, -+ .maxlen = sizeof(bool), -+ .mode = 0644, -+ .proc_handler = &proc_dobool, ++ .extra2 = &thirty_two, + }, +#endif // CONFIG_SCHED_BORE + #ifdef CONFIG_CFS_BANDWIDTH { - .procname = "sched_child_runs_first", - .data = &sysctl_sched_child_runs_first, -@@ -313,6 +453,9 @@ static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) + .procname = "sched_cfs_bandwidth_slice_us", +@@ -208,6 +388,13 @@ static inline void update_load_set(struct load_weight *lw, unsigned long w) + * + * This idea comes from the SD scheduler of Con Kolivas: + */ ++#ifdef CONFIG_SCHED_BORE ++static void update_sysctl(void) { ++ sysctl_sched_base_slice = ++ max(sysctl_sched_min_base_slice, configured_sched_base_slice); ++} ++void sched_update_min_base_slice(void) { update_sysctl(); } ++#else // !CONFIG_SCHED_BORE + static unsigned int get_update_sysctl_factor(void) + { + unsigned int cpus = min_t(unsigned int, num_online_cpus(), 8); +@@ -238,6 +425,7 @@ static void update_sysctl(void) + SET_SYSCTL(sched_base_slice); + #undef SET_SYSCTL + } ++#endif // CONFIG_SCHED_BORE + + void __init sched_init_granularity(void) + { +@@ -311,6 +499,9 @@ static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) if (unlikely(se->load.weight != NICE_0_LOAD)) delta = __calc_delta(delta, NICE_0_LOAD, &se->load); @@ -433,7 +642,49 @@ index fa9fff0f9..5e4f0ccff 100644 return delta; } -@@ -668,7 +811,7 @@ void avg_vruntime_update(struct cfs_rq *cfs_rq, s64 delta) +@@ -637,10 +828,26 @@ static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se) + * + * As measured, the max (key * weight) value was ~44 bits for a kernel build. + */ ++#if !defined(CONFIG_SCHED_BORE) ++#define entity_weight(se) scale_load_down(se->load.weight) ++#else // CONFIG_SCHED_BORE ++static unsigned long entity_weight(struct sched_entity *se) { ++ unsigned long weight = se->load.weight; ++ if (likely(sched_bore)) weight = unscale_slice(weight, se); ++#ifdef CONFIG_64BIT ++ weight >>= SCHED_FIXEDPOINT_SHIFT - 3; ++#endif // CONFIG_64BIT ++ return weight; ++} ++#endif // CONFIG_SCHED_BORE ++ + static void + avg_vruntime_add(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +- unsigned long weight = scale_load_down(se->load.weight); ++ unsigned long weight = entity_weight(se); ++#ifdef CONFIG_SCHED_BORE ++ se->burst_load = weight; ++#endif // CONFIG_SCHED_BORE + s64 key = entity_key(cfs_rq, se); + + cfs_rq->avg_vruntime += key * weight; +@@ -650,7 +857,12 @@ avg_vruntime_add(struct cfs_rq *cfs_rq, struct sched_entity *se) + static void + avg_vruntime_sub(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +- unsigned long weight = scale_load_down(se->load.weight); ++#if !defined(CONFIG_SCHED_BORE) ++ unsigned long weight = entity_weight(se); ++#else // CONFIG_SCHED_BORE ++ unsigned long weight = se->burst_load; ++ se->burst_load = 0; ++#endif // CONFIG_SCHED_BORE + s64 key = entity_key(cfs_rq, se); + + cfs_rq->avg_vruntime -= key * weight; +@@ -670,14 +882,14 @@ void avg_vruntime_update(struct cfs_rq *cfs_rq, s64 delta) * Specifically: avg_runtime() + 0 must result in entity_eligible() := true * For this to be so, the result of this function must have a left bias. */ @@ -442,161 +693,198 @@ index fa9fff0f9..5e4f0ccff 100644 { struct sched_entity *curr = cfs_rq->curr; s64 avg = cfs_rq->avg_vruntime; -@@ -688,7 +831,11 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) - avg = div_s64(avg, load); + long load = cfs_rq->avg_load; + + if (curr && curr->on_rq) { +- unsigned long weight = scale_load_down(curr->load.weight); ++ unsigned long weight = entity_weight(curr); + + avg += entity_key(cfs_rq, curr) * weight; + load += weight; +@@ -687,12 +899,15 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) + /* sign flips effective floor / ceil */ + if (avg < 0) + avg -= (load - 1); +- avg = div_s64(avg, load); ++ avg = div64_s64(avg, load); } - return cfs_rq->min_vruntime + avg; + return avg; -+} -+ -+inline u64 avg_vruntime(struct cfs_rq *cfs_rq) { + } + ++u64 avg_vruntime(struct cfs_rq *cfs_rq) { + return cfs_rq->min_vruntime + avg_key(cfs_rq); - } - ++} /* -@@ -709,13 +856,8 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) - */ - static void update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se) - { -- s64 lag, limit; -- - SCHED_WARN_ON(!se->on_rq); -- lag = avg_vruntime(cfs_rq) - se->vruntime; -- -- limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se); -- se->vlag = clamp(lag, -limit, limit); -+ se->vlag = avg_vruntime(cfs_rq) - se->vruntime; + * lag_i = S - s_i = w_i * (V - v_i) + * +@@ -717,6 +932,9 @@ static void update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se) + lag = avg_vruntime(cfs_rq) - se->vruntime; + + limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se); ++#ifdef CONFIG_SCHED_BORE ++ if (likely(sched_bore)) limit >>= 1; ++#endif // CONFIG_SCHED_BORE + se->vlag = clamp(lag, -limit, limit); } - /* -@@ -981,7 +1123,6 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq) - return se; +@@ -744,7 +962,7 @@ static int vruntime_eligible(struct cfs_rq *cfs_rq, u64 vruntime) + long load = cfs_rq->avg_load; + + if (curr && curr->on_rq) { +- unsigned long weight = scale_load_down(curr->load.weight); ++ unsigned long weight = entity_weight(curr); + + avg += entity_key(cfs_rq, curr) * weight; + load += weight; +@@ -840,10 +1058,16 @@ static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) + se->min_vruntime = se->vruntime; + rb_add_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, + __entity_less, &min_vruntime_cb); ++#ifdef CONFIG_SCHED_BORE ++ se->on_cfs_rq = true; ++#endif // CONFIG_SCHED_BORE } --#ifdef CONFIG_SCHED_DEBUG - struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) + static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) { - struct rb_node *last = rb_last(&cfs_rq->tasks_timeline.rb_root); -@@ -995,6 +1136,7 @@ struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) - /************************************************************** ++#ifdef CONFIG_SCHED_BORE ++ se->on_cfs_rq = false; ++#endif // CONFIG_SCHED_BORE + rb_erase_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, + &min_vruntime_cb); + avg_vruntime_sub(cfs_rq, se); +@@ -968,6 +1192,7 @@ struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) * Scheduling class statistics methods: */ -+#ifdef CONFIG_SCHED_DEBUG #ifdef CONFIG_SMP ++#if !defined(CONFIG_SCHED_BORE) int sched_update_scaling(void) { -@@ -1031,6 +1173,9 @@ static void update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se) - /* - * EEVDF: vd_i = ve_i + r_i / w_i - */ -+#ifdef CONFIG_SCHED_BORE -+ update_slice_score(se); + unsigned int factor = get_update_sysctl_factor(); +@@ -979,6 +1204,7 @@ int sched_update_scaling(void) + + return 0; + } +#endif // CONFIG_SCHED_BORE - se->deadline = se->vruntime + calc_delta_fair(se->slice, se); + #endif + #endif - /* -@@ -1173,7 +1318,11 @@ static void update_curr(struct cfs_rq *cfs_rq) - curr->sum_exec_runtime += delta_exec; - schedstat_add(cfs_rq->exec_clock, delta_exec); +@@ -1178,7 +1404,13 @@ static void update_curr(struct cfs_rq *cfs_rq) + if (unlikely(delta_exec <= 0)) + return; -- 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)); ++#else // !CONFIG_SCHED_BORE + curr->vruntime += calc_delta_fair(delta_exec, curr); ++#endif // CONFIG_SCHED_BORE update_deadline(cfs_rq, curr); update_min_vruntime(cfs_rq); -@@ -5066,6 +5215,9 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) - s64 lag = 0; +@@ -5170,8 +5402,8 @@ static inline void update_misfit_status(struct task_struct *p, struct rq *rq) {} + static void + place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + { +- u64 vslice, vruntime = avg_vruntime(cfs_rq); +- s64 lag = 0; ++ s64 lag = 0, key = avg_key(cfs_rq); ++ u64 vslice, vruntime = cfs_rq->min_vruntime + key; se->slice = sysctl_sched_base_slice; -+#ifdef CONFIG_SCHED_BORE -+ update_slice_score(se); -+#endif // CONFIG_SCHED_BORE vslice = calc_delta_fair(se->slice, se); - - /* -@@ -5080,7 +5232,13 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) +@@ -5184,6 +5416,9 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + * + * EEVDF: placement strategy #1 / #2 + */ ++#ifdef CONFIG_SCHED_BORE ++ if (unlikely(!sched_bore) || se->vlag) ++#endif // CONFIG_SCHED_BORE + if (sched_feat(PLACE_LAG) && cfs_rq->nr_running) { struct sched_entity *curr = cfs_rq->curr; unsigned long load; +@@ -5244,12 +5479,22 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + */ + load = cfs_rq->avg_load; + if (curr && curr->on_rq) +- load += scale_load_down(curr->load.weight); ++ load += entity_weight(curr); -- lag = se->vlag; -+ u64 slice = se->slice; -+#ifdef CONFIG_SCHED_BORE -+ if (unlikely(!sched_bore)) -+#endif // CONFIG_SCHED_BORE -+ slice *= 2; -+ s64 limit = calc_delta_fair(max_t(u64, slice, TICK_NSEC), se); -+ lag = clamp(se->vlag, -limit, limit); - - /* - * If we want to place a task and preserve lag, we have to -@@ -5142,6 +5300,21 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) +- lag *= load + scale_load_down(se->load.weight); ++ lag *= load + entity_weight(se); ++#if !defined(CONFIG_SCHED_BORE) if (WARN_ON_ONCE(!load)) ++#else // CONFIG_SCHED_BORE ++ if (unlikely(!load)) ++#endif // CONFIG_SCHED_BORE load = 1; - lag = div_s64(lag, load); -+ +- lag = div_s64(lag, load); ++ lag = div64_s64(lag, load); +#ifdef CONFIG_SCHED_BORE -+ if (flags & ENQUEUE_MIGRATED && likely(sched_bore)) { -+ s64 left_vruntime = vruntime, right_vruntime = vruntime; -+ struct sched_entity *first = __pick_first_entity(cfs_rq), -+ *last = __pick_last_entity(cfs_rq); -+ -+ if (first) left_vruntime = first->vruntime; -+ if (last) right_vruntime = last->vruntime; -+ -+ lag = clamp(lag, -+ (s64)vruntime - right_vruntime, -+ (s64)vruntime - left_vruntime); ++ if (likely(sched_bore)) { ++ s64 limit = vslice << sched_vlag_deviation_limit; ++ lag = clamp(lag, -limit, limit); + } +#endif // CONFIG_SCHED_BORE } se->vruntime = vruntime - lag; -@@ -6698,6 +6871,12 @@ static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) +@@ -6816,6 +7061,14 @@ static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) bool was_sched_idle = sched_idle_rq(rq); util_est_dequeue(&rq->cfs, p); +#ifdef CONFIG_SCHED_BORE + if (task_sleep) { -+ update_curr(cfs_rq_of(se)); ++ cfs_rq = cfs_rq_of(se); ++ if (cfs_rq->curr == se) ++ update_curr(cfs_rq); + restart_burst(se); + } +#endif // CONFIG_SCHED_BORE for_each_sched_entity(se) { cfs_rq = cfs_rq_of(se); -@@ -8429,8 +8608,13 @@ static void yield_task_fair(struct rq *rq) +@@ -8565,16 +8818,25 @@ static void yield_task_fair(struct rq *rq) /* * 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); -+ update_slice_score(se); -+#endif // CONFIG_SCHED_BORE ++#if !defined(CONFIG_SCHED_BORE) + if (unlikely(rq->nr_running == 1)) return; -+ } clear_buddies(cfs_rq, se); ++#endif // CONFIG_SCHED_BORE -@@ -8439,6 +8623,10 @@ static void yield_task_fair(struct rq *rq) + update_rq_clock(rq); + /* * Update run-time statistics of the 'current'. */ update_curr(cfs_rq); +#ifdef CONFIG_SCHED_BORE -+ restart_burst(se); -+ update_slice_score(se); ++ restart_burst_rescale_deadline(se); ++ if (unlikely(rq->nr_running == 1)) ++ return; ++ ++ clear_buddies(cfs_rq, se); +#endif // CONFIG_SCHED_BORE /* * Tell update_rq_clock() that we've just updated, * so we don't do microscopic update in schedule() +@@ -12664,6 +12926,9 @@ static void task_fork_fair(struct task_struct *p) + curr = cfs_rq->curr; + if (curr) + update_curr(cfs_rq); ++#ifdef CONFIG_SCHED_BORE ++ update_burst_score(se); ++#endif // CONFIG_SCHED_BORE + place_entity(cfs_rq, se, ENQUEUE_INITIAL); + rq_unlock(rq, &rf); + } diff --git a/kernel/sched/features.h b/kernel/sched/features.h -index f77016823..a2e09c04f 100644 +index 143f55df8..3f0fe409f 100644 --- a/kernel/sched/features.h +++ b/kernel/sched/features.h @@ -6,7 +6,11 @@ @@ -605,12 +893,37 @@ index f77016823..a2e09c04f 100644 SCHED_FEAT(PLACE_DEADLINE_INITIAL, true) +#ifdef CONFIG_SCHED_BORE +SCHED_FEAT(RUN_TO_PARITY, false) -+#else // CONFIG_SCHED_BORE ++#else // !CONFIG_SCHED_BORE SCHED_FEAT(RUN_TO_PARITY, true) +#endif // CONFIG_SCHED_BORE /* * Prefer to schedule the task we woke last (assuming it failed +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +index ed5c758c7..9d62372ae 100644 +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -1965,7 +1965,11 @@ static inline void dirty_sched_domain_sysctl(int cpu) + } + #endif + ++#ifdef CONFIG_SCHED_BORE ++extern void sched_update_min_base_slice(void); ++#else // !CONFIG_SCHED_BORE + extern int sched_update_scaling(void); ++#endif // CONFIG_SCHED_BORE + + static inline const struct cpumask *task_user_cpus(struct task_struct *p) + { +@@ -2552,6 +2556,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_BORE ++extern unsigned int sysctl_sched_min_base_slice; ++#endif // CONFIG_SCHED_BORE + + #ifdef CONFIG_SCHED_DEBUG + extern int sysctl_resched_latency_warn_ms; -- -2.43.0.rc2 - +2.43.0.232.ge79552d197 diff --git a/patches/cachyos/0001-cachyos-base-all.patch b/patches/cachyos/0001-cachyos-base-all.patch index d5fb21d..148a8a7 100644 --- a/patches/cachyos/0001-cachyos-base-all.patch +++ b/patches/cachyos/0001-cachyos-base-all.patch @@ -1,2069 +1,30 @@ -From 3a5d5f1ba351406e62a3e4b276c2ce8d0661d7ec Mon Sep 17 00:00:00 2001 +From 8f03bb4df21c5746b9f1c3e399faa3c932737e4f Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Wed, 29 Nov 2023 19:55:12 +0100 -Subject: [PATCH 1/7] amd-hdr - -Signed-off-by: Peter Jung ---- - drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 71 ++ - .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 34 +- - .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 100 +++ - .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 805 ++++++++++++++++-- - .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 72 ++ - .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 224 ++++- - .../amd/display/dc/dcn10/dcn10_cm_common.c | 95 ++- - .../drm/amd/display/dc/dcn30/dcn30_hwseq.c | 37 + - .../drm/amd/display/dc/dcn30/dcn30_hwseq.h | 3 + - .../drm/amd/display/dc/dcn301/dcn301_init.c | 2 +- - .../gpu/drm/amd/display/include/fixed31_32.h | 12 + - drivers/gpu/drm/arm/malidp_crtc.c | 2 +- - drivers/gpu/drm/drm_atomic.c | 1 + - drivers/gpu/drm/drm_atomic_state_helper.c | 1 + - drivers/gpu/drm/drm_property.c | 49 ++ - include/drm/drm_mode_object.h | 2 +- - include/drm/drm_plane.h | 7 + - include/drm/drm_property.h | 6 + - include/uapi/drm/drm_mode.h | 8 + - 19 files changed, 1441 insertions(+), 90 deletions(-) - -diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h -index 32fe05c810c6..84bf501b02f4 100644 ---- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h -+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h -@@ -343,6 +343,77 @@ struct amdgpu_mode_info { - int disp_priority; - const struct amdgpu_display_funcs *funcs; - const enum drm_plane_type *plane_type; -+ -+ /* Driver-private color mgmt props */ -+ -+ /* @plane_degamma_lut_property: Plane property to set a degamma LUT to -+ * convert input space before blending. -+ */ -+ struct drm_property *plane_degamma_lut_property; -+ /* @plane_degamma_lut_size_property: Plane property to define the max -+ * size of degamma LUT as supported by the driver (read-only). -+ */ -+ struct drm_property *plane_degamma_lut_size_property; -+ /** -+ * @plane_degamma_tf_property: Plane pre-defined transfer function to -+ * to go from scanout/encoded values to linear values. -+ */ -+ struct drm_property *plane_degamma_tf_property; -+ /** -+ * @plane_hdr_mult_property: -+ */ -+ struct drm_property *plane_hdr_mult_property; -+ -+ struct drm_property *plane_ctm_property; -+ /** -+ * @shaper_lut_property: Plane property to set pre-blending shaper LUT -+ * that converts color content before 3D LUT. -+ */ -+ struct drm_property *plane_shaper_lut_property; -+ /** -+ * @shaper_lut_size_property: Plane property for the size of -+ * pre-blending shaper LUT as supported by the driver (read-only). -+ */ -+ struct drm_property *plane_shaper_lut_size_property; -+ /** -+ * @plane_shaper_tf_property: Plane property to set a predefined -+ * transfer function for pre-blending shaper (before applying 3D LUT) -+ * with or without LUT. -+ */ -+ struct drm_property *plane_shaper_tf_property; -+ /** -+ * @plane_lut3d_property: Plane property for gamma correction using a -+ * 3D LUT (pre-blending). -+ */ -+ struct drm_property *plane_lut3d_property; -+ /** -+ * @plane_degamma_lut_size_property: Plane property to define the max -+ * size of 3D LUT as supported by the driver (read-only). -+ */ -+ struct drm_property *plane_lut3d_size_property; -+ /** -+ * @plane_blend_lut_property: Plane property for output gamma before -+ * blending. Userspace set a blend LUT to convert colors after 3D LUT -+ * conversion. It works as a post-3D LUT 1D LUT, with shaper LUT, they -+ * are sandwiching 3D LUT with two 1D LUT. -+ */ -+ struct drm_property *plane_blend_lut_property; -+ /** -+ * @plane_blend_lut_size_property: Plane property to define the max -+ * size of blend LUT as supported by the driver (read-only). -+ */ -+ struct drm_property *plane_blend_lut_size_property; -+ /** -+ * @plane_blend_tf_property: Plane property to set a predefined -+ * transfer function for pre-blending blend (before applying 3D LUT) -+ * with or without LUT. -+ */ -+ struct drm_property *plane_blend_tf_property; -+ /* @regamma_tf_property: Transfer function for CRTC regamma -+ * (post-blending). Possible values are defined by `enum -+ * amdgpu_transfer_function`. -+ */ -+ struct drm_property *regamma_tf_property; - }; - - #define AMDGPU_MAX_BL_LEVEL 0xFF -diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c -index f5fdb61c821d..b8c82ca5f3a1 100644 ---- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c -+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c -@@ -4022,6 +4022,11 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) - 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); -@@ -5094,7 +5099,9 @@ static int fill_dc_plane_attributes(struct amdgpu_device *adev, - * Always set input transfer function, since plane state is refreshed - * every time. - */ -- ret = amdgpu_dm_update_plane_color_mgmt(dm_crtc_state, dc_plane_state); -+ ret = amdgpu_dm_update_plane_color_mgmt(dm_crtc_state, -+ plane_state, -+ dc_plane_state); - if (ret) - return ret; - -@@ -8119,6 +8126,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, - bundle->surface_updates[planes_count].gamma = dc_plane->gamma_correction; - bundle->surface_updates[planes_count].in_transfer_func = dc_plane->in_transfer_func; - bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; -+ 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, -@@ -8330,6 +8341,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, - &acrtc_state->stream->csc_color_matrix; - bundle->stream_update.out_transfer_func = - acrtc_state->stream->out_transfer_func; -+ 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; -@@ -9518,6 +9533,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, - * when a modeset is needed, to ensure it gets reprogrammed. - */ - if (dm_new_crtc_state->base.color_mgmt_changed || -+ 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) -@@ -9585,6 +9601,10 @@ static bool should_reset_plane(struct drm_atomic_state *state, - */ - for_each_oldnew_plane_in_state(state, other, old_other_state, new_other_state, i) { - struct amdgpu_framebuffer *old_afb, *new_afb; -+ 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; -@@ -9621,6 +9641,18 @@ static bool should_reset_plane(struct drm_atomic_state *state, - old_other_state->color_encoding != new_other_state->color_encoding) - return true; - -+ /* HDR/Transfer Function changes. */ -+ if (dm_old_other_state->degamma_tf != dm_new_other_state->degamma_tf || -+ dm_old_other_state->degamma_lut != dm_new_other_state->degamma_lut || -+ dm_old_other_state->hdr_mult != dm_new_other_state->hdr_mult || -+ dm_old_other_state->ctm != dm_new_other_state->ctm || -+ dm_old_other_state->shaper_lut != dm_new_other_state->shaper_lut || -+ dm_old_other_state->shaper_tf != dm_new_other_state->shaper_tf || -+ dm_old_other_state->lut3d != dm_new_other_state->lut3d || -+ dm_old_other_state->blend_lut != dm_new_other_state->blend_lut || -+ dm_old_other_state->blend_tf != dm_new_other_state->blend_tf) -+ return true; -+ - /* Framebuffer checks fall at the end. */ - if (!old_other_state->fb || !new_other_state->fb) - continue; -diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h -index 9e4cc5eeda76..24c87f425afb 100644 ---- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h -+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h -@@ -33,6 +33,8 @@ - #include - #include "link_service_types.h" - -+#define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL) -+ - /* - * This file contains the definition for amdgpu_display_manager - * and its API for amdgpu driver's use. -@@ -716,9 +718,91 @@ static inline void amdgpu_dm_set_mst_status(uint8_t *status, - - extern const struct amdgpu_ip_block_version dm_ip_block; - -+enum amdgpu_transfer_function { -+ AMDGPU_TRANSFER_FUNCTION_DEFAULT, -+ AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_BT709_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_PQ_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_LINEAR, -+ AMDGPU_TRANSFER_FUNCTION_UNITY, -+ AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF, -+ AMDGPU_TRANSFER_FUNCTION_COUNT -+}; -+ - struct dm_plane_state { - struct drm_plane_state base; - struct dc_plane_state *dc_state; -+ -+ /* Plane color mgmt */ -+ /** -+ * @degamma_lut: -+ * -+ * 1D LUT for mapping framebuffer/plane pixel data before sampling or -+ * blending operations. It's usually applied to linearize input space. -+ * The blob (if not NULL) is an array of &struct drm_color_lut. -+ */ -+ struct drm_property_blob *degamma_lut; -+ /** -+ * @degamma_tf: -+ * -+ * Predefined transfer function to tell DC driver the input space to -+ * linearize. -+ */ -+ enum amdgpu_transfer_function degamma_tf; -+ /** -+ * @hdr_mult: -+ * -+ * Multiplier to 'gain' the plane. When PQ is decoded using the fixed -+ * func transfer function to the internal FP16 fb, 1.0 -> 80 nits (on -+ * AMD at least). When sRGB is decoded, 1.0 -> 1.0, obviously. -+ * Therefore, 1.0 multiplier = 80 nits for SDR content. So if you -+ * want, 203 nits for SDR content, pass in (203.0 / 80.0). Format is -+ * S31.32 sign-magnitude. -+ */ -+ __u64 hdr_mult; -+ /** -+ * @ctm: -+ * -+ * Color transformation matrix. See drm_crtc_enable_color_mgmt(). The -+ * blob (if not NULL) is a &struct drm_color_ctm. -+ */ -+ struct drm_property_blob *ctm; -+ /** -+ * @shaper_lut: shaper lookup table blob. The blob (if not NULL) is an -+ * array of &struct drm_color_lut. -+ */ -+ struct drm_property_blob *shaper_lut; -+ /** -+ * @shaper_tf: -+ * -+ * Predefined transfer function to delinearize color space. -+ */ -+ enum amdgpu_transfer_function shaper_tf; -+ /** -+ * @lut3d: 3D lookup table blob. The blob (if not NULL) is an array of -+ * &struct drm_color_lut. -+ */ -+ struct drm_property_blob *lut3d; -+ /** -+ * @blend_lut: blend lut lookup table blob. The blob (if not NULL) is an -+ * array of &struct drm_color_lut. -+ */ -+ struct drm_property_blob *blend_lut; -+ /** -+ * @blend_tf: -+ * -+ * Pre-defined transfer function for converting plane pixel data before -+ * applying blend LUT. -+ */ -+ enum amdgpu_transfer_function blend_tf; - }; - - struct dm_crtc_state { -@@ -743,6 +827,14 @@ struct dm_crtc_state { - struct dc_info_packet vrr_infopacket; - - int abm_level; -+ -+ /** -+ * @regamma_tf: -+ * -+ * Pre-defined transfer function for converting internal FB -> wire -+ * encoding. -+ */ -+ enum amdgpu_transfer_function regamma_tf; - }; - - #define to_dm_crtc_state(x) container_of(x, struct dm_crtc_state, base) -@@ -804,14 +896,22 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, - - void amdgpu_dm_trigger_timing_sync(struct drm_device *dev); - -+/* 3D LUT max size is 17x17x17 */ -+#define MAX_COLOR_3DLUT_ENTRIES 4913 -+#define MAX_COLOR_3DLUT_BITDEPTH 12 -+int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev, -+ struct drm_plane_state *plane_state); -+/* 1D LUT size */ - #define MAX_COLOR_LUT_ENTRIES 4096 - /* Legacy gamm LUT users such as X doesn't like large LUT sizes */ - #define MAX_COLOR_LEGACY_LUT_ENTRIES 256 - - void amdgpu_dm_init_color_mod(void); -+int amdgpu_dm_create_color_properties(struct amdgpu_device *adev); - int amdgpu_dm_verify_lut_sizes(const struct drm_crtc_state *crtc_state); - int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc); - int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, -+ struct drm_plane_state *plane_state, - struct dc_plane_state *dc_plane_state); - - void amdgpu_dm_update_connector_after_detect( -diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c -index a4cb23d059bd..0442eeaa9763 100644 ---- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c -+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c -@@ -72,6 +72,7 @@ - */ - - #define MAX_DRM_LUT_VALUE 0xFFFF -+#define SDR_WHITE_LEVEL_INIT_VALUE 80 - - /** - * amdgpu_dm_init_color_mod - Initialize the color module. -@@ -84,6 +85,213 @@ void amdgpu_dm_init_color_mod(void) - setup_x_points_distribution(); - } - -+#ifdef AMD_PRIVATE_COLOR -+/* Pre-defined Transfer Functions (TF) -+ * -+ * AMD driver supports pre-defined mathematical functions for transferring -+ * between encoded values and optical/linear space. Depending on HW color caps, -+ * ROMs and curves built by the AMD color module support these transforms. -+ * -+ * The driver-specific color implementation exposes properties for pre-blending -+ * degamma TF, shaper TF (before 3D LUT), and blend(dpp.ogam) TF and -+ * post-blending regamma (mpc.ogam) TF. However, only pre-blending degamma -+ * supports ROM curves. AMD color module uses pre-defined coefficients to build -+ * curves for the other blocks. What can be done by each color block is -+ * described by struct dpp_color_capsand struct mpc_color_caps. -+ * -+ * AMD driver-specific color API exposes the following pre-defined transfer -+ * functions: -+ * -+ * - Linear/Unity: linear/identity relationship between pixel value and -+ * luminance value; -+ * - Gamma 2.2, Gamma 2.4, Gamma 2.6: pure gamma functions; -+ * - sRGB: 2.4 gamma with small initial linear section as standardized by IEC -+ * 61966-2-1:1999; -+ * - BT.709 (BT.1886): 2.4 gamma with differences in the dark end of the scale. -+ * Used in HD-TV and standardized by ITU-R BT.1886; -+ * - PQ (Perceptual Quantizer): used for HDR display, allows luminance range -+ * capability of 0 to 10,000 nits; standardized by SMPTE ST 2084. -+ * -+ * In the driver-specific API, color block names attached to TF properties -+ * suggest the intention regarding non-linear encoding pixel's luminance -+ * values. As some newer encodings don't use gamma curve, we make encoding and -+ * decoding explicit by defining an enum list of transfer functions supported -+ * in terms of EOTF and inverse EOTF, where: -+ * -+ * - EOTF (electro-optical transfer function): is the transfer function to go -+ * from the encoded value to an optical (linear) value. De-gamma functions -+ * traditionally do this. -+ * - Inverse EOTF (simply the inverse of the EOTF): is usually intended to go -+ * from an optical/linear space (which might have been used for blending) -+ * back to the encoded values. Gamma functions traditionally do this. -+ */ -+static const char * const -+amdgpu_transfer_function_names[] = { -+ [AMDGPU_TRANSFER_FUNCTION_DEFAULT] = "Default", -+ [AMDGPU_TRANSFER_FUNCTION_LINEAR] = "Linear", -+ [AMDGPU_TRANSFER_FUNCTION_UNITY] = "Unity", -+ [AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF] = "sRGB EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_BT709_EOTF] = "BT.709 EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_PQ_EOTF] = "PQ EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF] = "Gamma 2.2 EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF] = "Gamma 2.4 EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF] = "Gamma 2.6 EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF] = "sRGB inv_EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF] = "BT.709 inv_EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF] = "PQ inv_EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF] = "Gamma 2.2 inv_EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF] = "Gamma 2.4 inv_EOTF", -+ [AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF] = "Gamma 2.6 inv_EOTF", -+}; -+ -+static const u32 amdgpu_eotf = -+ BIT(AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_BT709_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_PQ_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF); -+ -+static const u32 amdgpu_inv_eotf = -+ BIT(AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF); -+ -+static struct drm_property * -+amdgpu_create_tf_property(struct drm_device *dev, -+ const char *name, -+ u32 supported_tf) -+{ -+ u32 transfer_functions = supported_tf | -+ BIT(AMDGPU_TRANSFER_FUNCTION_DEFAULT) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_LINEAR) | -+ BIT(AMDGPU_TRANSFER_FUNCTION_UNITY); -+ struct drm_prop_enum_list enum_list[AMDGPU_TRANSFER_FUNCTION_COUNT]; -+ int i, len; -+ -+ len = 0; -+ for (i = 0; i < AMDGPU_TRANSFER_FUNCTION_COUNT; i++) { -+ if ((transfer_functions & BIT(i)) == 0) -+ continue; -+ -+ enum_list[len].type = i; -+ enum_list[len].name = amdgpu_transfer_function_names[i]; -+ len++; -+ } -+ -+ return drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, -+ name, enum_list, len); -+} -+ -+int -+amdgpu_dm_create_color_properties(struct amdgpu_device *adev) -+{ -+ struct drm_property *prop; -+ -+ prop = drm_property_create(adev_to_drm(adev), -+ DRM_MODE_PROP_BLOB, -+ "AMD_PLANE_DEGAMMA_LUT", 0); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_degamma_lut_property = prop; -+ -+ prop = drm_property_create_range(adev_to_drm(adev), -+ DRM_MODE_PROP_IMMUTABLE, -+ "AMD_PLANE_DEGAMMA_LUT_SIZE", 0, UINT_MAX); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_degamma_lut_size_property = prop; -+ -+ prop = amdgpu_create_tf_property(adev_to_drm(adev), -+ "AMD_PLANE_DEGAMMA_TF", -+ amdgpu_eotf); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_degamma_tf_property = prop; -+ -+ prop = drm_property_create_range(adev_to_drm(adev), -+ 0, "AMD_PLANE_HDR_MULT", 0, U64_MAX); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_hdr_mult_property = prop; -+ -+ prop = drm_property_create(adev_to_drm(adev), -+ DRM_MODE_PROP_BLOB, -+ "AMD_PLANE_CTM", 0); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_ctm_property = prop; -+ -+ prop = drm_property_create(adev_to_drm(adev), -+ DRM_MODE_PROP_BLOB, -+ "AMD_PLANE_SHAPER_LUT", 0); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_shaper_lut_property = prop; -+ -+ prop = drm_property_create_range(adev_to_drm(adev), -+ DRM_MODE_PROP_IMMUTABLE, -+ "AMD_PLANE_SHAPER_LUT_SIZE", 0, UINT_MAX); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_shaper_lut_size_property = prop; -+ -+ prop = amdgpu_create_tf_property(adev_to_drm(adev), -+ "AMD_PLANE_SHAPER_TF", -+ amdgpu_inv_eotf); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_shaper_tf_property = prop; -+ -+ prop = drm_property_create(adev_to_drm(adev), -+ DRM_MODE_PROP_BLOB, -+ "AMD_PLANE_LUT3D", 0); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_lut3d_property = prop; -+ -+ prop = drm_property_create_range(adev_to_drm(adev), -+ DRM_MODE_PROP_IMMUTABLE, -+ "AMD_PLANE_LUT3D_SIZE", 0, UINT_MAX); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_lut3d_size_property = prop; -+ -+ prop = drm_property_create(adev_to_drm(adev), -+ DRM_MODE_PROP_BLOB, -+ "AMD_PLANE_BLEND_LUT", 0); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_blend_lut_property = prop; -+ -+ prop = drm_property_create_range(adev_to_drm(adev), -+ DRM_MODE_PROP_IMMUTABLE, -+ "AMD_PLANE_BLEND_LUT_SIZE", 0, UINT_MAX); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_blend_lut_size_property = prop; -+ -+ prop = amdgpu_create_tf_property(adev_to_drm(adev), -+ "AMD_PLANE_BLEND_TF", -+ amdgpu_eotf); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.plane_blend_tf_property = prop; -+ -+ prop = amdgpu_create_tf_property(adev_to_drm(adev), -+ "AMD_CRTC_REGAMMA_TF", -+ amdgpu_inv_eotf); -+ if (!prop) -+ return -ENOMEM; -+ adev->mode_info.regamma_tf_property = prop; -+ -+ return 0; -+} -+#endif -+ - /** - * __extract_blob_lut - Extracts the DRM lut and lut size from a blob. - * @blob: DRM color mgmt property blob -@@ -182,7 +390,6 @@ static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, - static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, - struct fixed31_32 *matrix) - { -- int64_t val; - int i; - - /* -@@ -201,12 +408,33 @@ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, - } - - /* gamut_remap_matrix[i] = ctm[i - floor(i/4)] */ -- val = ctm->matrix[i - (i / 4)]; -- /* If negative, convert to 2's complement. */ -- if (val & (1ULL << 63)) -- val = -(val & ~(1ULL << 63)); -+ matrix[i] = dc_fixpt_from_s3132(ctm->matrix[i - (i / 4)]); -+ } -+} - -- matrix[i].value = val; -+/** -+ * __drm_ctm2_to_dc_matrix - converts a DRM CTM2 to a DC CSC float matrix -+ * @ctm: DRM color transformation matrix -+ * @matrix: DC CSC float matrix -+ * -+ * The matrix needs to be a 3x4 (12 entry) matrix. -+ */ -+static void __drm_ctm2_to_dc_matrix(const struct drm_color_ctm2 *ctm, -+ struct fixed31_32 *matrix) -+{ -+ int i; -+ -+ /* -+ * DRM gives a 3x3 matrix, but DC wants 3x4. Assuming we're operating -+ * with homogeneous coordinates, augment the matrix with 0's. -+ * -+ * The format provided is S31.32, using signed-magnitude representation. -+ * Our fixed31_32 is also S31.32, but is using 2's complement. We have -+ * to convert from signed-magnitude to 2's complement. -+ */ -+ for (i = 0; i < 12; i++) { -+ /* gamut_remap_matrix[i] = ctm[i - floor(i/4)] */ -+ matrix[i] = dc_fixpt_from_s3132(ctm->matrix[i]); - } - } - -@@ -268,16 +496,18 @@ static int __set_output_tf(struct dc_transfer_func *func, - struct calculate_buffer cal_buffer = {0}; - bool res; - -- ASSERT(lut && lut_size == MAX_COLOR_LUT_ENTRIES); -- - cal_buffer.buffer_index = -1; - -- gamma = dc_create_gamma(); -- if (!gamma) -- return -ENOMEM; -+ if (lut_size) { -+ ASSERT(lut && lut_size == MAX_COLOR_LUT_ENTRIES); - -- gamma->num_entries = lut_size; -- __drm_lut_to_dc_gamma(lut, gamma, false); -+ gamma = dc_create_gamma(); -+ if (!gamma) -+ return -ENOMEM; -+ -+ gamma->num_entries = lut_size; -+ __drm_lut_to_dc_gamma(lut, gamma, false); -+ } - - if (func->tf == TRANSFER_FUNCTION_LINEAR) { - /* -@@ -285,27 +515,63 @@ static int __set_output_tf(struct dc_transfer_func *func, - * on top of a linear input. But degamma params can be used - * instead to simulate this. - */ -- gamma->type = GAMMA_CUSTOM; -+ if (gamma) -+ gamma->type = GAMMA_CUSTOM; - res = mod_color_calculate_degamma_params(NULL, func, -- gamma, true); -+ gamma, gamma != NULL); - } else { - /* - * Assume sRGB. The actual mapping will depend on whether the - * input was legacy or not. - */ -- gamma->type = GAMMA_CS_TFM_1D; -- res = mod_color_calculate_regamma_params(func, gamma, false, -+ if (gamma) -+ gamma->type = GAMMA_CS_TFM_1D; -+ res = mod_color_calculate_regamma_params(func, gamma, gamma != NULL, - has_rom, NULL, &cal_buffer); - } - -- dc_gamma_release(&gamma); -+ if (gamma) -+ dc_gamma_release(&gamma); - - return res ? 0 : -ENOMEM; - } - -+static int amdgpu_dm_set_atomic_regamma(struct dc_stream_state *stream, -+ const struct drm_color_lut *regamma_lut, -+ uint32_t regamma_size, bool has_rom, -+ enum dc_transfer_func_predefined tf) -+{ -+ struct dc_transfer_func *out_tf = stream->out_transfer_func; -+ int ret = 0; -+ -+ if (regamma_size || tf != TRANSFER_FUNCTION_LINEAR) { -+ /* CRTC RGM goes into RGM LUT. -+ * -+ * Note: there is no implicit sRGB regamma here. We are using -+ * degamma calculation from color module to calculate the curve -+ * from a linear base. -+ */ -+ out_tf->type = TF_TYPE_DISTRIBUTED_POINTS; -+ out_tf->tf = tf; -+ out_tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; -+ -+ ret = __set_output_tf(out_tf, regamma_lut, regamma_size, has_rom); -+ } else { -+ /* -+ * No CRTC RGM means we can just put the block into bypass -+ * since we don't have any plane level adjustments using it. -+ */ -+ out_tf->type = TF_TYPE_BYPASS; -+ out_tf->tf = TRANSFER_FUNCTION_LINEAR; -+ } -+ -+ return ret; -+} -+ - /** - * __set_input_tf - calculates the input transfer function based on expected - * input space. -+ * @caps: dc color capabilities - * @func: transfer function - * @lut: lookup table that defines the color space - * @lut_size: size of respective lut. -@@ -313,27 +579,249 @@ static int __set_output_tf(struct dc_transfer_func *func, - * Returns: - * 0 in case of success. -ENOMEM if fails. - */ --static int __set_input_tf(struct dc_transfer_func *func, -+static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func, - const struct drm_color_lut *lut, uint32_t lut_size) - { - struct dc_gamma *gamma = NULL; - bool res; - -- gamma = dc_create_gamma(); -- if (!gamma) -- return -ENOMEM; -+ if (lut_size) { -+ gamma = dc_create_gamma(); -+ if (!gamma) -+ return -ENOMEM; - -- gamma->type = GAMMA_CUSTOM; -- gamma->num_entries = lut_size; -+ gamma->type = GAMMA_CUSTOM; -+ gamma->num_entries = lut_size; - -- __drm_lut_to_dc_gamma(lut, gamma, false); -+ __drm_lut_to_dc_gamma(lut, gamma, false); -+ } - -- res = mod_color_calculate_degamma_params(NULL, func, gamma, true); -- dc_gamma_release(&gamma); -+ res = mod_color_calculate_degamma_params(caps, func, gamma, gamma != NULL); -+ -+ if (gamma) -+ dc_gamma_release(&gamma); - - return res ? 0 : -ENOMEM; - } - -+static enum dc_transfer_func_predefined -+amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf) -+{ -+ switch (tf) -+ { -+ default: -+ case AMDGPU_TRANSFER_FUNCTION_DEFAULT: -+ case AMDGPU_TRANSFER_FUNCTION_LINEAR: -+ return TRANSFER_FUNCTION_LINEAR; -+ case AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF: -+ case AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF: -+ return TRANSFER_FUNCTION_SRGB; -+ case AMDGPU_TRANSFER_FUNCTION_BT709_EOTF: -+ case AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF: -+ return TRANSFER_FUNCTION_BT709; -+ case AMDGPU_TRANSFER_FUNCTION_PQ_EOTF: -+ case AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF: -+ return TRANSFER_FUNCTION_PQ; -+ case AMDGPU_TRANSFER_FUNCTION_UNITY: -+ return TRANSFER_FUNCTION_UNITY; -+ case AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF: -+ case AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF: -+ return TRANSFER_FUNCTION_GAMMA22; -+ case AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF: -+ case AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF: -+ return TRANSFER_FUNCTION_GAMMA24; -+ case AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF: -+ case AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF: -+ return TRANSFER_FUNCTION_GAMMA26; -+ } -+} -+ -+static void __to_dc_lut3d_color(struct dc_rgb *rgb, -+ const struct drm_color_lut lut, -+ int bit_precision) -+{ -+ rgb->red = drm_color_lut_extract(lut.red, bit_precision); -+ rgb->green = drm_color_lut_extract(lut.green, bit_precision); -+ rgb->blue = drm_color_lut_extract(lut.blue, bit_precision); -+} -+ -+static void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, -+ uint32_t lut3d_size, -+ struct tetrahedral_params *params, -+ bool use_tetrahedral_9, -+ int bit_depth) -+{ -+ struct dc_rgb *lut0; -+ struct dc_rgb *lut1; -+ struct dc_rgb *lut2; -+ struct dc_rgb *lut3; -+ int lut_i, i; -+ -+ -+ if (use_tetrahedral_9) { -+ lut0 = params->tetrahedral_9.lut0; -+ lut1 = params->tetrahedral_9.lut1; -+ lut2 = params->tetrahedral_9.lut2; -+ lut3 = params->tetrahedral_9.lut3; -+ } else { -+ lut0 = params->tetrahedral_17.lut0; -+ lut1 = params->tetrahedral_17.lut1; -+ lut2 = params->tetrahedral_17.lut2; -+ lut3 = params->tetrahedral_17.lut3; -+ } -+ -+ for (lut_i = 0, i = 0; i < lut3d_size - 4; lut_i++, i += 4) { -+ /* We should consider the 3dlut RGB values are distributed -+ * along four arrays lut0-3 where the first sizes 1229 and the -+ * other 1228. The bit depth supported for 3dlut channel is -+ * 12-bit, but DC also supports 10-bit. -+ * -+ * TODO: improve color pipeline API to enable the userspace set -+ * bit depth and 3D LUT size/stride, as specified by VA-API. -+ */ -+ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth); -+ __to_dc_lut3d_color(&lut1[lut_i], lut[i + 1], bit_depth); -+ __to_dc_lut3d_color(&lut2[lut_i], lut[i + 2], bit_depth); -+ __to_dc_lut3d_color(&lut3[lut_i], lut[i + 3], bit_depth); -+ } -+ /* lut0 has 1229 points (lut_size/4 + 1) */ -+ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth); -+} -+ -+/* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream -+ * @drm_lut3d: DRM CRTC (user) 3D LUT -+ * @drm_lut3d_size: size of 3D LUT -+ * @lut3d: DC 3D LUT -+ * -+ * Map DRM CRTC 3D LUT to DC 3D LUT and all necessary bits to program it -+ * on DCN MPC accordingly. -+ */ -+static void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut, -+ uint32_t drm_lut3d_size, -+ struct dc_3dlut *lut) -+{ -+ if (!drm_lut3d_size) { -+ lut->state.bits.initialized = 0; -+ } else { -+ /* Stride and bit depth are not programmable by API yet. -+ * Therefore, only supports 17x17x17 3D LUT (12-bit). -+ */ -+ lut->lut_3d.use_tetrahedral_9 = false; -+ lut->lut_3d.use_12bits = true; -+ lut->state.bits.initialized = 1; -+ __drm_3dlut_to_dc_3dlut(drm_lut, drm_lut3d_size, &lut->lut_3d, -+ lut->lut_3d.use_tetrahedral_9, -+ MAX_COLOR_3DLUT_BITDEPTH); -+ } -+} -+ -+static int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, -+ bool has_rom, -+ enum dc_transfer_func_predefined tf, -+ uint32_t shaper_size, -+ struct dc_transfer_func *func_shaper) -+{ -+ int ret = 0; -+ -+ if (shaper_size || tf != TRANSFER_FUNCTION_LINEAR) { -+ /* If DRM shaper LUT is set, we assume a linear color space -+ * (linearized by DRM degamma 1D LUT or not) -+ */ -+ func_shaper->type = TF_TYPE_DISTRIBUTED_POINTS; -+ func_shaper->tf = tf; -+ func_shaper->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; -+ -+ ret = __set_output_tf(func_shaper, shaper_lut, shaper_size, has_rom); -+ } else { -+ func_shaper->type = TF_TYPE_BYPASS; -+ func_shaper->tf = TRANSFER_FUNCTION_LINEAR; -+ } -+ -+ return ret; -+} -+ -+static int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, -+ bool has_rom, -+ enum dc_transfer_func_predefined tf, -+ uint32_t blend_size, -+ struct dc_transfer_func *func_blend) -+{ -+ int ret = 0; -+ -+ if (blend_size || tf != TRANSFER_FUNCTION_LINEAR) { -+ /* DRM plane gamma LUT or TF means we are linearizing color -+ * space before blending (similar to degamma programming). As -+ * we don't have hardcoded curve support, or we use AMD color -+ * module to fill the parameters that will be translated to HW -+ * points. -+ */ -+ func_blend->type = TF_TYPE_DISTRIBUTED_POINTS; -+ func_blend->tf = tf; -+ func_blend->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; -+ -+ ret = __set_input_tf(NULL, func_blend, blend_lut, blend_size); -+ } else { -+ func_blend->type = TF_TYPE_BYPASS; -+ func_blend->tf = TRANSFER_FUNCTION_LINEAR; -+ } -+ -+ return ret; -+} -+ -+/* amdgpu_dm_lut3d_size - get expected size according to hw color caps -+ * @adev: amdgpu device -+ * @lut_size: default size -+ * -+ * Return: -+ * lut_size if DC 3D LUT is supported, zero otherwise. -+ */ -+static uint32_t amdgpu_dm_get_lut3d_size(struct amdgpu_device *adev, -+ uint32_t lut_size) -+{ -+ return adev->dm.dc->caps.color.dpp.hw_3d_lut ? lut_size : 0; -+} -+ -+/** -+ * amdgpu_dm_verify_lut3d_size - verifies if 3D LUT is supported and if DRM 3D -+ * LUT matches the hw supported size -+ * @adev: amdgpu device -+ * @crtc_state: the DRM CRTC state -+ * -+ * Verifies if post-blending (MPC) 3D LUT is supported by the HW (DCN 3.0 or -+ * newer) and if the DRM 3D LUT matches the supported size. -+ * -+ * Returns: -+ * 0 on success. -EINVAL if lut size are invalid. -+ */ -+int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev, -+ struct drm_plane_state *plane_state) -+{ -+ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); -+ const struct drm_color_lut *shaper = NULL, *lut3d = NULL; -+ uint32_t exp_size, size; -+ -+ /* shaper LUT is only available if 3D LUT color caps*/ -+ exp_size = amdgpu_dm_get_lut3d_size(adev, MAX_COLOR_LUT_ENTRIES); -+ shaper = __extract_blob_lut(dm_plane_state->shaper_lut, &size); -+ -+ if (shaper && size != exp_size) { -+ drm_dbg(&adev->ddev, -+ "Invalid Shaper LUT size. Should be %u but got %u.\n", -+ exp_size, size); -+ } -+ -+ exp_size = amdgpu_dm_get_lut3d_size(adev, MAX_COLOR_3DLUT_ENTRIES); -+ lut3d = __extract_blob_lut(dm_plane_state->lut3d, &size); -+ -+ if (lut3d && size != exp_size) { -+ drm_dbg(&adev->ddev, "Invalid 3D LUT size. Should be %u but got %u.\n", -+ exp_size, size); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ - /** - * amdgpu_dm_verify_lut_sizes - verifies if DRM luts match the hw supported sizes - * @crtc_state: the DRM CRTC state -@@ -401,9 +889,12 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) - const struct drm_color_lut *degamma_lut, *regamma_lut; - uint32_t degamma_size, regamma_size; - bool has_regamma, has_degamma; -+ enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_LINEAR; - bool is_legacy; - int r; - -+ tf = amdgpu_tf_to_dc_tf(crtc->regamma_tf); -+ - r = amdgpu_dm_verify_lut_sizes(&crtc->base); - if (r) - return r; -@@ -440,26 +931,22 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) - stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS; - stream->out_transfer_func->tf = TRANSFER_FUNCTION_SRGB; - -+ /* Note: although we pass has_rom as parameter here, we never -+ * actually use ROM because the color module only takes the ROM -+ * path if transfer_func->type == PREDEFINED. -+ * -+ * See more in mod_color_calculate_regamma_params() -+ */ - r = __set_legacy_tf(stream->out_transfer_func, regamma_lut, - regamma_size, has_rom); - if (r) - return r; -- } else if (has_regamma) { -- /* If atomic regamma, CRTC RGM goes into RGM LUT. */ -- stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS; -- stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; -- -- r = __set_output_tf(stream->out_transfer_func, regamma_lut, -- regamma_size, has_rom); -+ } else { -+ regamma_size = has_regamma ? regamma_size : 0; -+ r = amdgpu_dm_set_atomic_regamma(stream, regamma_lut, -+ regamma_size, has_rom, tf); - if (r) - return r; -- } else { -- /* -- * No CRTC RGM means we can just put the block into bypass -- * since we don't have any plane level adjustments using it. -- */ -- stream->out_transfer_func->type = TF_TYPE_BYPASS; -- stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; - } - - /* -@@ -495,20 +982,10 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) - return 0; - } - --/** -- * amdgpu_dm_update_plane_color_mgmt: Maps DRM color management to DC plane. -- * @crtc: amdgpu_dm crtc state -- * @dc_plane_state: target DC surface -- * -- * Update the underlying dc_stream_state's input transfer function (ITF) in -- * preparation for hardware commit. The transfer function used depends on -- * the preparation done on the stream for color management. -- * -- * Returns: -- * 0 on success. -ENOMEM if mem allocation fails. -- */ --int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, -- struct dc_plane_state *dc_plane_state) -+static int -+map_crtc_degamma_to_dc_plane(struct dm_crtc_state *crtc, -+ struct dc_plane_state *dc_plane_state, -+ struct dc_color_caps *caps) - { - const struct drm_color_lut *degamma_lut; - enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_SRGB; -@@ -531,8 +1008,7 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, - °amma_size); - ASSERT(degamma_size == MAX_COLOR_LUT_ENTRIES); - -- dc_plane_state->in_transfer_func->type = -- TF_TYPE_DISTRIBUTED_POINTS; -+ dc_plane_state->in_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS; - - /* - * This case isn't fully correct, but also fairly -@@ -564,11 +1040,11 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, - dc_plane_state->in_transfer_func->tf = - TRANSFER_FUNCTION_LINEAR; - -- r = __set_input_tf(dc_plane_state->in_transfer_func, -+ r = __set_input_tf(caps, dc_plane_state->in_transfer_func, - degamma_lut, degamma_size); - if (r) - return r; -- } else if (crtc->cm_is_degamma_srgb) { -+ } else { - /* - * For legacy gamma support we need the regamma input - * in linear space. Assume that the input is sRGB. -@@ -577,14 +1053,213 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, - dc_plane_state->in_transfer_func->tf = tf; - - if (tf != TRANSFER_FUNCTION_SRGB && -- !mod_color_calculate_degamma_params(NULL, -- dc_plane_state->in_transfer_func, NULL, false)) -+ !mod_color_calculate_degamma_params(caps, -+ dc_plane_state->in_transfer_func, -+ NULL, false)) -+ return -ENOMEM; -+ } -+ -+ return 0; -+} -+ -+static int -+__set_dm_plane_degamma(struct drm_plane_state *plane_state, -+ struct dc_plane_state *dc_plane_state, -+ struct dc_color_caps *color_caps) -+{ -+ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); -+ const struct drm_color_lut *degamma_lut; -+ enum amdgpu_transfer_function tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; -+ uint32_t degamma_size; -+ bool has_degamma_lut; -+ int ret; -+ -+ degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut, -+ °amma_size); -+ -+ has_degamma_lut = degamma_lut && -+ !__is_lut_linear(degamma_lut, degamma_size); -+ -+ tf = dm_plane_state->degamma_tf; -+ -+ /* If we don't have plane degamma LUT nor TF to set on DC, we have -+ * nothing to do here, return. -+ */ -+ if (!has_degamma_lut && tf == AMDGPU_TRANSFER_FUNCTION_DEFAULT) -+ return -EINVAL; -+ -+ dc_plane_state->in_transfer_func->tf = amdgpu_tf_to_dc_tf(tf); -+ -+ if (has_degamma_lut) { -+ ASSERT(degamma_size == MAX_COLOR_LUT_ENTRIES); -+ -+ dc_plane_state->in_transfer_func->type = -+ TF_TYPE_DISTRIBUTED_POINTS; -+ -+ ret = __set_input_tf(color_caps, dc_plane_state->in_transfer_func, -+ degamma_lut, degamma_size); -+ if (ret) -+ return ret; -+ } else { -+ dc_plane_state->in_transfer_func->type = -+ TF_TYPE_PREDEFINED; -+ -+ if (!mod_color_calculate_degamma_params(color_caps, -+ dc_plane_state->in_transfer_func, NULL, false)) - return -ENOMEM; -- } else { -- /* ...Otherwise we can just bypass the DGM block. */ -- dc_plane_state->in_transfer_func->type = TF_TYPE_BYPASS; -- dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; -+ } -+ return 0; -+} -+ -+static int -+amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state, -+ struct dc_plane_state *dc_plane_state, -+ struct dc_color_caps *color_caps) -+{ -+ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); -+ enum amdgpu_transfer_function shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; -+ enum amdgpu_transfer_function blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; -+ const struct drm_color_lut *shaper_lut, *lut3d, *blend_lut; -+ uint32_t shaper_size, lut3d_size, blend_size; -+ int ret; -+ -+ /* We have nothing to do here, return */ -+ if (!plane_state->color_mgmt_changed) -+ return 0; -+ -+ dc_plane_state->hdr_mult = dc_fixpt_from_s3132(dm_plane_state->hdr_mult); -+ -+ shaper_lut = __extract_blob_lut(dm_plane_state->shaper_lut, &shaper_size); -+ shaper_size = shaper_lut != NULL ? shaper_size : 0; -+ shaper_tf = dm_plane_state->shaper_tf; -+ lut3d = __extract_blob_lut(dm_plane_state->lut3d, &lut3d_size); -+ lut3d_size = lut3d != NULL ? lut3d_size : 0; -+ -+ amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, dc_plane_state->lut3d_func); -+ ret = amdgpu_dm_atomic_shaper_lut(shaper_lut, false, -+ amdgpu_tf_to_dc_tf(shaper_tf), -+ shaper_size, -+ dc_plane_state->in_shaper_func); -+ if (ret) { -+ drm_dbg_kms(plane_state->plane->dev, -+ "setting plane %d shaper LUT failed.\n", -+ plane_state->plane->index); -+ -+ return ret; -+ } -+ -+ blend_tf = dm_plane_state->blend_tf; -+ blend_lut = __extract_blob_lut(dm_plane_state->blend_lut, &blend_size); -+ blend_size = blend_lut != NULL ? blend_size : 0; -+ -+ ret = amdgpu_dm_atomic_blend_lut(blend_lut, false, -+ amdgpu_tf_to_dc_tf(blend_tf), -+ blend_size, dc_plane_state->blend_tf); -+ if (ret) { -+ drm_dbg_kms(plane_state->plane->dev, -+ "setting plane %d gamma lut failed.\n", -+ plane_state->plane->index); -+ -+ return ret; - } - - return 0; - } -+ -+/** -+ * amdgpu_dm_update_plane_color_mgmt: Maps DRM color management to DC plane. -+ * @crtc: amdgpu_dm crtc state -+ * @plane_state: DRM plane state -+ * @dc_plane_state: target DC surface -+ * -+ * Update the underlying dc_stream_state's input transfer function (ITF) in -+ * preparation for hardware commit. The transfer function used depends on -+ * the preparation done on the stream for color management. -+ * -+ * Returns: -+ * 0 on success. -ENOMEM if mem allocation fails. -+ */ -+int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, -+ struct drm_plane_state *plane_state, -+ struct dc_plane_state *dc_plane_state) -+{ -+ struct amdgpu_device *adev = drm_to_adev(crtc->base.state->dev); -+ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); -+ struct drm_color_ctm2 *ctm = NULL; -+ struct dc_color_caps *color_caps = NULL; -+ bool has_crtc_cm_degamma; -+ int ret; -+ -+ ret = amdgpu_dm_verify_lut3d_size(adev, plane_state); -+ if (ret) { -+ drm_dbg_driver(&adev->ddev, "amdgpu_dm_verify_lut3d_size() failed\n"); -+ return ret; -+ } -+ -+ if (dc_plane_state->ctx && dc_plane_state->ctx->dc) -+ color_caps = &dc_plane_state->ctx->dc->caps.color; -+ -+ /* Initially, we can just bypass the DGM block. */ -+ dc_plane_state->in_transfer_func->type = TF_TYPE_BYPASS; -+ dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; -+ -+ /* After, we start to update values according to color props */ -+ has_crtc_cm_degamma = (crtc->cm_has_degamma || crtc->cm_is_degamma_srgb); -+ -+ ret = __set_dm_plane_degamma(plane_state, dc_plane_state, color_caps); -+ if (ret == -ENOMEM) -+ return ret; -+ -+ /* We only have one degamma block available (pre-blending) for the -+ * whole color correction pipeline, so that we can't actually perform -+ * plane and CRTC degamma at the same time. Explicitly reject atomic -+ * updates when userspace sets both plane and CRTC degamma properties. -+ */ -+ if (has_crtc_cm_degamma && ret != -EINVAL){ -+ drm_dbg_kms(crtc->base.crtc->dev, -+ "doesn't support plane and CRTC degamma at the same time\n"); -+ return -EINVAL; -+ } -+ -+ /* If we are here, it means we don't have plane degamma settings, check -+ * if we have CRTC degamma waiting for mapping to pre-blending degamma -+ * block -+ */ -+ if (has_crtc_cm_degamma) { -+ /* AMD HW doesn't have post-blending degamma caps. When DRM -+ * CRTC atomic degamma is set, we maps it to DPP degamma block -+ * (pre-blending) or, on legacy gamma, we use DPP degamma to -+ * linearize (implicit degamma) from sRGB/BT709 according to -+ * the input space. -+ */ -+ ret = map_crtc_degamma_to_dc_plane(crtc, dc_plane_state, color_caps); -+ if (ret) -+ return ret; -+ } -+ -+ /* Setup CRTC CTM. */ -+ if (dm_plane_state->ctm) { -+ ctm = (struct drm_color_ctm2 *)dm_plane_state->ctm->data; -+ -+ /* -+ * So far, if we have both plane and CRTC CTM, plane CTM takes -+ * the priority and we discard data for CRTC CTM, as -+ * implemented in dcn10_program_gamut_remap(). However, we -+ * have MPC gamut_remap_matrix from DCN3 family, therefore we -+ * can remap MPC programing of the matrix to MPC block and -+ * provide support for both DPP and MPC matrix at the same -+ * time. -+ */ -+ __drm_ctm2_to_dc_matrix(ctm, dc_plane_state->gamut_remap_matrix.matrix); -+ -+ dc_plane_state->gamut_remap_matrix.enable_remap = true; -+ dc_plane_state->input_csc_color_matrix.enable_adjustment = false; -+ } else { -+ /* Bypass CTM. */ -+ dc_plane_state->gamut_remap_matrix.enable_remap = false; -+ dc_plane_state->input_csc_color_matrix.enable_adjustment = false; -+ } -+ -+ return amdgpu_dm_plane_set_color_properties(plane_state, -+ dc_plane_state, color_caps); -+} -diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c -index 97b7a0b8a1c2..a05c210754d4 100644 ---- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c -+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c -@@ -260,6 +260,7 @@ static struct drm_crtc_state *dm_crtc_duplicate_state(struct drm_crtc *crtc) - state->freesync_config = cur->freesync_config; - state->cm_has_degamma = cur->cm_has_degamma; - state->cm_is_degamma_srgb = cur->cm_is_degamma_srgb; -+ state->regamma_tf = cur->regamma_tf; - state->crc_skip_count = cur->crc_skip_count; - state->mpo_requested = cur->mpo_requested; - /* TODO Duplicate dc_stream after objects are stream object is flattened */ -@@ -296,6 +297,70 @@ static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc) - } - #endif - -+#ifdef AMD_PRIVATE_COLOR -+/** -+ * drm_crtc_additional_color_mgmt - enable additional color properties -+ * @crtc: DRM CRTC -+ * -+ * This function lets the driver enable post-blending CRTC regamma transfer -+ * function property in addition to DRM CRTC gamma LUT. Default value means -+ * linear transfer function, which is the default CRTC gamma LUT behaviour -+ * without this property. -+ */ -+static void -+dm_crtc_additional_color_mgmt(struct drm_crtc *crtc) -+{ -+ struct amdgpu_device *adev = drm_to_adev(crtc->dev); -+ -+ if(adev->dm.dc->caps.color.mpc.ogam_ram) -+ drm_object_attach_property(&crtc->base, -+ adev->mode_info.regamma_tf_property, -+ AMDGPU_TRANSFER_FUNCTION_DEFAULT); -+} -+ -+static int -+amdgpu_dm_atomic_crtc_set_property(struct drm_crtc *crtc, -+ struct drm_crtc_state *state, -+ struct drm_property *property, -+ uint64_t val) -+{ -+ struct amdgpu_device *adev = drm_to_adev(crtc->dev); -+ struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state); -+ -+ if (property == adev->mode_info.regamma_tf_property) { -+ if (acrtc_state->regamma_tf != val) { -+ acrtc_state->regamma_tf = val; -+ acrtc_state->base.color_mgmt_changed |= 1; -+ } -+ } else { -+ drm_dbg_atomic(crtc->dev, -+ "[CRTC:%d:%s] unknown property [PROP:%d:%s]]\n", -+ crtc->base.id, crtc->name, -+ property->base.id, property->name); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int -+amdgpu_dm_atomic_crtc_get_property(struct drm_crtc *crtc, -+ const struct drm_crtc_state *state, -+ struct drm_property *property, -+ uint64_t *val) -+{ -+ struct amdgpu_device *adev = drm_to_adev(crtc->dev); -+ struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state); -+ -+ if (property == adev->mode_info.regamma_tf_property) -+ *val = acrtc_state->regamma_tf; -+ else -+ return -EINVAL; -+ -+ return 0; -+} -+#endif -+ - /* Implemented only the options currently available for the driver */ - static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { - .reset = dm_crtc_reset_state, -@@ -314,6 +379,10 @@ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { - #if defined(CONFIG_DEBUG_FS) - .late_register = amdgpu_dm_crtc_late_register, - #endif -+#ifdef AMD_PRIVATE_COLOR -+ .atomic_set_property = amdgpu_dm_atomic_crtc_set_property, -+ .atomic_get_property = amdgpu_dm_atomic_crtc_get_property, -+#endif - }; - - static void dm_crtc_helper_disable(struct drm_crtc *crtc) -@@ -489,6 +558,9 @@ int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, - - drm_mode_crtc_set_gamma_size(&acrtc->base, MAX_COLOR_LEGACY_LUT_ENTRIES); - -+#ifdef AMD_PRIVATE_COLOR -+ dm_crtc_additional_color_mgmt(&acrtc->base); -+#endif - return 0; - - fail: -diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c -index cc74dd69acf2..17719e15cbe5 100644 ---- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c -+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c -@@ -1333,8 +1333,14 @@ static void dm_drm_plane_reset(struct drm_plane *plane) - amdgpu_state = kzalloc(sizeof(*amdgpu_state), GFP_KERNEL); - WARN_ON(amdgpu_state == NULL); - -- if (amdgpu_state) -- __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base); -+ if (!amdgpu_state) -+ return; -+ -+ __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base); -+ amdgpu_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; -+ amdgpu_state->hdr_mult = AMDGPU_HDR_MULT_DEFAULT; -+ amdgpu_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; -+ amdgpu_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } - - static struct drm_plane_state * -@@ -1354,6 +1360,22 @@ dm_drm_plane_duplicate_state(struct drm_plane *plane) - dc_plane_state_retain(dm_plane_state->dc_state); - } - -+ if (dm_plane_state->degamma_lut) -+ drm_property_blob_get(dm_plane_state->degamma_lut); -+ if (dm_plane_state->ctm) -+ drm_property_blob_get(dm_plane_state->ctm); -+ if (dm_plane_state->shaper_lut) -+ drm_property_blob_get(dm_plane_state->shaper_lut); -+ if (dm_plane_state->lut3d) -+ drm_property_blob_get(dm_plane_state->lut3d); -+ if (dm_plane_state->blend_lut) -+ drm_property_blob_get(dm_plane_state->blend_lut); -+ -+ dm_plane_state->degamma_tf = old_dm_plane_state->degamma_tf; -+ dm_plane_state->hdr_mult = old_dm_plane_state->hdr_mult; -+ dm_plane_state->shaper_tf = old_dm_plane_state->shaper_tf; -+ dm_plane_state->blend_tf = old_dm_plane_state->blend_tf; -+ - return &dm_plane_state->base; - } - -@@ -1421,12 +1443,203 @@ static void dm_drm_plane_destroy_state(struct drm_plane *plane, - { - struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); - -+ if (dm_plane_state->degamma_lut) -+ drm_property_blob_put(dm_plane_state->degamma_lut); -+ if (dm_plane_state->ctm) -+ drm_property_blob_put(dm_plane_state->ctm); -+ if (dm_plane_state->lut3d) -+ drm_property_blob_put(dm_plane_state->lut3d); -+ if (dm_plane_state->shaper_lut) -+ drm_property_blob_put(dm_plane_state->shaper_lut); -+ if (dm_plane_state->blend_lut) -+ drm_property_blob_put(dm_plane_state->blend_lut); -+ - if (dm_plane_state->dc_state) - dc_plane_state_release(dm_plane_state->dc_state); - - drm_atomic_helper_plane_destroy_state(plane, state); - } - -+#ifdef AMD_PRIVATE_COLOR -+static void -+dm_atomic_plane_attach_color_mgmt_properties(struct amdgpu_display_manager *dm, -+ struct drm_plane *plane) -+{ -+ struct amdgpu_mode_info mode_info = dm->adev->mode_info; -+ struct dpp_color_caps dpp_color_caps = dm->dc->caps.color.dpp; -+ -+ /* Check HW color pipeline capabilities for DPP (pre-blending) before expose*/ -+ if (dpp_color_caps.dgam_ram || dpp_color_caps.gamma_corr) { -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_degamma_lut_property, 0); -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_degamma_lut_size_property, -+ MAX_COLOR_LUT_ENTRIES); -+ drm_object_attach_property(&plane->base, -+ dm->adev->mode_info.plane_degamma_tf_property, -+ AMDGPU_TRANSFER_FUNCTION_DEFAULT); -+ } -+ /* HDR MULT is always available */ -+ drm_object_attach_property(&plane->base, -+ dm->adev->mode_info.plane_hdr_mult_property, -+ AMDGPU_HDR_MULT_DEFAULT); -+ -+ /* Only enable plane CTM if both DPP and MPC gamut remap is available. */ -+ if (dm->dc->caps.color.mpc.gamut_remap) -+ drm_object_attach_property(&plane->base, -+ dm->adev->mode_info.plane_ctm_property, 0); -+ -+ if (dpp_color_caps.hw_3d_lut) { -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_shaper_lut_property, 0); -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_shaper_lut_size_property, -+ MAX_COLOR_LUT_ENTRIES); -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_shaper_tf_property, -+ AMDGPU_TRANSFER_FUNCTION_DEFAULT); -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_lut3d_property, 0); -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_lut3d_size_property, -+ MAX_COLOR_3DLUT_ENTRIES); -+ } -+ -+ if (dpp_color_caps.ogam_ram) { -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_blend_lut_property, 0); -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_blend_lut_size_property, -+ MAX_COLOR_LUT_ENTRIES); -+ drm_object_attach_property(&plane->base, -+ mode_info.plane_blend_tf_property, -+ AMDGPU_TRANSFER_FUNCTION_DEFAULT); -+ } -+} -+ -+static int -+dm_atomic_plane_set_property(struct drm_plane *plane, -+ struct drm_plane_state *state, -+ struct drm_property *property, -+ uint64_t val) -+{ -+ struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); -+ struct amdgpu_device *adev = drm_to_adev(plane->dev); -+ bool replaced = false; -+ int ret; -+ -+ if (property == adev->mode_info.plane_degamma_lut_property) { -+ ret = drm_property_replace_blob_from_id(plane->dev, -+ &dm_plane_state->degamma_lut, -+ val, -+ -1, sizeof(struct drm_color_lut), -+ &replaced); -+ dm_plane_state->base.color_mgmt_changed |= replaced; -+ return ret; -+ } else if (property == adev->mode_info.plane_degamma_tf_property) { -+ if (dm_plane_state->degamma_tf != val) { -+ dm_plane_state->degamma_tf = val; -+ dm_plane_state->base.color_mgmt_changed = 1; -+ } -+ } else if (property == adev->mode_info.plane_hdr_mult_property) { -+ if (dm_plane_state->hdr_mult != val) { -+ dm_plane_state->hdr_mult = val; -+ dm_plane_state->base.color_mgmt_changed = 1; -+ } -+ } else if (property == adev->mode_info.plane_ctm_property) { -+ ret = drm_property_replace_blob_from_id(plane->dev, -+ &dm_plane_state->ctm, -+ val, -+ sizeof(struct drm_color_ctm2), -1, -+ &replaced); -+ dm_plane_state->base.color_mgmt_changed |= replaced; -+ return ret; -+ } else if (property == adev->mode_info.plane_shaper_lut_property) { -+ ret = drm_property_replace_blob_from_id(plane->dev, -+ &dm_plane_state->shaper_lut, -+ val, -1, -+ sizeof(struct drm_color_lut), -+ &replaced); -+ dm_plane_state->base.color_mgmt_changed |= replaced; -+ return ret; -+ } else if (property == adev->mode_info.plane_shaper_tf_property) { -+ if (dm_plane_state->shaper_tf != val) { -+ dm_plane_state->shaper_tf = val; -+ dm_plane_state->base.color_mgmt_changed = 1; -+ } -+ } else if (property == adev->mode_info.plane_lut3d_property) { -+ ret = drm_property_replace_blob_from_id(plane->dev, -+ &dm_plane_state->lut3d, -+ val, -1, -+ sizeof(struct drm_color_lut), -+ &replaced); -+ dm_plane_state->base.color_mgmt_changed |= replaced; -+ return ret; -+ } else if (property == adev->mode_info.plane_blend_lut_property) { -+ ret = drm_property_replace_blob_from_id(plane->dev, -+ &dm_plane_state->blend_lut, -+ val, -1, -+ sizeof(struct drm_color_lut), -+ &replaced); -+ dm_plane_state->base.color_mgmt_changed |= replaced; -+ return ret; -+ } else if (property == adev->mode_info.plane_blend_tf_property) { -+ if (dm_plane_state->blend_tf != val) { -+ dm_plane_state->blend_tf = val; -+ dm_plane_state->base.color_mgmt_changed = 1; -+ } -+ } else { -+ drm_dbg_atomic(plane->dev, -+ "[PLANE:%d:%s] unknown property [PROP:%d:%s]]\n", -+ plane->base.id, plane->name, -+ property->base.id, property->name); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int -+dm_atomic_plane_get_property(struct drm_plane *plane, -+ const struct drm_plane_state *state, -+ struct drm_property *property, -+ uint64_t *val) -+{ -+ struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); -+ struct amdgpu_device *adev = drm_to_adev(plane->dev); -+ -+ if (property == adev->mode_info.plane_degamma_lut_property) { -+ *val = (dm_plane_state->degamma_lut) ? -+ dm_plane_state->degamma_lut->base.id : 0; -+ } else if (property == adev->mode_info.plane_degamma_tf_property) { -+ *val = dm_plane_state->degamma_tf; -+ } else if (property == adev->mode_info.plane_hdr_mult_property) { -+ *val = dm_plane_state->hdr_mult; -+ } else if (property == adev->mode_info.plane_ctm_property) { -+ *val = (dm_plane_state->ctm) ? -+ dm_plane_state->ctm->base.id : 0; -+ } else if (property == adev->mode_info.plane_shaper_lut_property) { -+ *val = (dm_plane_state->shaper_lut) ? -+ dm_plane_state->shaper_lut->base.id : 0; -+ } else if (property == adev->mode_info.plane_shaper_tf_property) { -+ *val = dm_plane_state->shaper_tf; -+ } else if (property == adev->mode_info.plane_lut3d_property) { -+ *val = (dm_plane_state->lut3d) ? -+ dm_plane_state->lut3d->base.id : 0; -+ } else if (property == adev->mode_info.plane_blend_lut_property) { -+ *val = (dm_plane_state->blend_lut) ? -+ dm_plane_state->blend_lut->base.id : 0; -+ } else if (property == adev->mode_info.plane_blend_tf_property) { -+ *val = dm_plane_state->blend_tf; -+ -+ } else { -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+#endif -+ - static const struct drm_plane_funcs dm_plane_funcs = { - .update_plane = drm_atomic_helper_update_plane, - .disable_plane = drm_atomic_helper_disable_plane, -@@ -1435,6 +1648,10 @@ static const struct drm_plane_funcs dm_plane_funcs = { - .atomic_duplicate_state = dm_drm_plane_duplicate_state, - .atomic_destroy_state = dm_drm_plane_destroy_state, - .format_mod_supported = dm_plane_format_mod_supported, -+#ifdef AMD_PRIVATE_COLOR -+ .atomic_set_property = dm_atomic_plane_set_property, -+ .atomic_get_property = dm_atomic_plane_get_property, -+#endif - }; - - int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, -@@ -1514,6 +1731,9 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, - - drm_plane_helper_add(plane, &dm_plane_helper_funcs); - -+#ifdef AMD_PRIVATE_COLOR -+ dm_atomic_plane_attach_color_mgmt_properties(dm, plane); -+#endif - /* Create (reset) the plane state */ - if (plane->funcs->reset) - plane->funcs->reset(plane); -diff --git a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c -index 3538973bd0c6..04b2e04b68f3 100644 ---- a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c -+++ b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c -@@ -349,20 +349,37 @@ bool cm_helper_translate_curve_to_hw_format(struct dc_context *ctx, - * segment is from 2^-10 to 2^1 - * There are less than 256 points, for optimization - */ -- seg_distr[0] = 3; -- seg_distr[1] = 4; -- seg_distr[2] = 4; -- seg_distr[3] = 4; -- seg_distr[4] = 4; -- seg_distr[5] = 4; -- seg_distr[6] = 4; -- seg_distr[7] = 4; -- seg_distr[8] = 4; -- seg_distr[9] = 4; -- seg_distr[10] = 1; -- -- region_start = -10; -- region_end = 1; -+ if (output_tf->tf == TRANSFER_FUNCTION_LINEAR) { -+ seg_distr[0] = 0; /* 2 */ -+ seg_distr[1] = 1; /* 4 */ -+ seg_distr[2] = 2; /* 4 */ -+ seg_distr[3] = 3; /* 8 */ -+ seg_distr[4] = 4; /* 16 */ -+ seg_distr[5] = 5; /* 32 */ -+ seg_distr[6] = 6; /* 64 */ -+ seg_distr[7] = 7; /* 128 */ -+ -+ region_start = -8; -+ region_end = 1; -+ } else { -+ seg_distr[0] = 3; /* 8 */ -+ seg_distr[1] = 4; /* 16 */ -+ seg_distr[2] = 4; -+ seg_distr[3] = 4; -+ seg_distr[4] = 4; -+ seg_distr[5] = 4; -+ seg_distr[6] = 4; -+ seg_distr[7] = 4; -+ seg_distr[8] = 4; -+ seg_distr[9] = 4; -+ seg_distr[10] = 1; /* 2 */ -+ /* total = 8*16 + 8 + 64 + 2 = */ -+ -+ region_start = -10; -+ region_end = 1; -+ } -+ -+ - } - - for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++) -@@ -375,16 +392,56 @@ bool cm_helper_translate_curve_to_hw_format(struct dc_context *ctx, - - j = 0; - for (k = 0; k < (region_end - region_start); k++) { -- increment = NUMBER_SW_SEGMENTS / (1 << seg_distr[k]); -+ /* -+ * We're using an ugly-ish hack here. Our HW allows for -+ * 256 segments per region but SW_SEGMENTS is 16. -+ * SW_SEGMENTS has some undocumented relationship to -+ * the number of points in the tf_pts struct, which -+ * is 512, unlike what's suggested TRANSFER_FUNC_POINTS. -+ * -+ * In order to work past this dilemma we'll scale our -+ * increment by (1 << 4) and then do the inverse (1 >> 4) -+ * when accessing the elements in tf_pts. -+ * -+ * TODO: find a better way using SW_SEGMENTS and -+ * TRANSFER_FUNC_POINTS definitions -+ */ -+ increment = (NUMBER_SW_SEGMENTS << 4) / (1 << seg_distr[k]); - start_index = (region_start + k + MAX_LOW_POINT) * - NUMBER_SW_SEGMENTS; -- for (i = start_index; i < start_index + NUMBER_SW_SEGMENTS; -+ for (i = (start_index << 4); i < (start_index << 4) + (NUMBER_SW_SEGMENTS << 4); - i += increment) { -+ struct fixed31_32 in_plus_one, in; -+ struct fixed31_32 value, red_value, green_value, blue_value; -+ uint32_t t = i & 0xf; -+ - if (j == hw_points - 1) - break; -- rgb_resulted[j].red = output_tf->tf_pts.red[i]; -- rgb_resulted[j].green = output_tf->tf_pts.green[i]; -- rgb_resulted[j].blue = output_tf->tf_pts.blue[i]; -+ -+ in_plus_one = output_tf->tf_pts.red[(i >> 4) + 1]; -+ in = output_tf->tf_pts.red[i >> 4]; -+ value = dc_fixpt_sub(in_plus_one, in); -+ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); -+ value = dc_fixpt_add(in, value); -+ red_value = value; -+ -+ in_plus_one = output_tf->tf_pts.green[(i >> 4) + 1]; -+ in = output_tf->tf_pts.green[i >> 4]; -+ value = dc_fixpt_sub(in_plus_one, in); -+ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); -+ value = dc_fixpt_add(in, value); -+ green_value = value; -+ -+ in_plus_one = output_tf->tf_pts.blue[(i >> 4) + 1]; -+ in = output_tf->tf_pts.blue[i >> 4]; -+ value = dc_fixpt_sub(in_plus_one, in); -+ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); -+ value = dc_fixpt_add(in, value); -+ blue_value = value; -+ -+ rgb_resulted[j].red = red_value; -+ rgb_resulted[j].green = green_value; -+ rgb_resulted[j].blue = blue_value; - j++; - } - } -diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c -index 255713ec29bb..fce9b33c0f88 100644 ---- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c -+++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c -@@ -186,6 +186,43 @@ bool dcn30_set_input_transfer_func(struct dc *dc, - return result; - } - -+void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx) -+{ -+ int i = 0; -+ struct dpp_grph_csc_adjustment dpp_adjust; -+ struct mpc_grph_gamut_adjustment mpc_adjust; -+ int mpcc_id = pipe_ctx->plane_res.hubp->inst; -+ struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; -+ -+ memset(&dpp_adjust, 0, sizeof(dpp_adjust)); -+ dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; -+ -+ if (pipe_ctx->plane_state && -+ pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { -+ dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; -+ for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) -+ dpp_adjust.temperature_matrix[i] = -+ pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; -+ } -+ -+ pipe_ctx->plane_res.dpp->funcs->dpp_set_gamut_remap(pipe_ctx->plane_res.dpp, -+ &dpp_adjust); -+ -+ memset(&mpc_adjust, 0, sizeof(mpc_adjust)); -+ mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; -+ -+ if (pipe_ctx->top_pipe == NULL) { -+ if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { -+ mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; -+ for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) -+ mpc_adjust.temperature_matrix[i] = -+ pipe_ctx->stream->gamut_remap_matrix.matrix[i]; -+ } -+ } -+ -+ mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust); -+} -+ - bool dcn30_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream) -diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h -index ce19c54097f8..e557e2b98618 100644 ---- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h -+++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h -@@ -58,6 +58,9 @@ bool dcn30_set_blend_lut(struct pipe_ctx *pipe_ctx, - bool dcn30_set_input_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_plane_state *plane_state); -+ -+void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx); -+ - bool dcn30_set_output_transfer_func(struct dc *dc, - struct pipe_ctx *pipe_ctx, - const struct dc_stream_state *stream); -diff --git a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c -index 61205cdbe2d5..fdbe3d42cd7b 100644 ---- a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c -+++ b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c -@@ -33,7 +33,7 @@ - #include "dcn301_init.h" - - static const struct hw_sequencer_funcs dcn301_funcs = { -- .program_gamut_remap = dcn10_program_gamut_remap, -+ .program_gamut_remap = dcn30_program_gamut_remap, - .init_hw = dcn10_init_hw, - .power_down_on_boot = dcn10_power_down_on_boot, - .apply_ctx_to_hw = dce110_apply_ctx_to_hw, -diff --git a/drivers/gpu/drm/amd/display/include/fixed31_32.h b/drivers/gpu/drm/amd/display/include/fixed31_32.h -index d4cf7ead1d87..84da1dd34efd 100644 ---- a/drivers/gpu/drm/amd/display/include/fixed31_32.h -+++ b/drivers/gpu/drm/amd/display/include/fixed31_32.h -@@ -69,6 +69,18 @@ static const struct fixed31_32 dc_fixpt_epsilon = { 1LL }; - static const struct fixed31_32 dc_fixpt_half = { 0x80000000LL }; - static const struct fixed31_32 dc_fixpt_one = { 0x100000000LL }; - -+static inline struct fixed31_32 dc_fixpt_from_s3132(__u64 x) -+{ -+ struct fixed31_32 val; -+ -+ /* If negative, convert to 2's complement. */ -+ if (x & (1ULL << 63)) -+ x = -(x & ~(1ULL << 63)); -+ -+ val.value = x; -+ return val; -+} -+ - /* - * @brief - * Initialization routines -diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c -index dc01c43f6193..d72c22dcf685 100644 ---- a/drivers/gpu/drm/arm/malidp_crtc.c -+++ b/drivers/gpu/drm/arm/malidp_crtc.c -@@ -221,7 +221,7 @@ static int malidp_crtc_atomic_check_ctm(struct drm_crtc *crtc, - - /* - * The size of the ctm is checked in -- * drm_atomic_replace_property_blob_from_id. -+ * drm_property_replace_blob_from_id. - */ - ctm = (struct drm_color_ctm *)state->ctm->data; - for (i = 0; i < ARRAY_SIZE(ctm->matrix); ++i) { -diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c -index c277b198fa3f..c3df45f90145 100644 ---- a/drivers/gpu/drm/drm_atomic.c -+++ b/drivers/gpu/drm/drm_atomic.c -@@ -733,6 +733,7 @@ static void drm_atomic_plane_print_state(struct drm_printer *p, - drm_get_color_encoding_name(state->color_encoding)); - drm_printf(p, "\tcolor-range=%s\n", - drm_get_color_range_name(state->color_range)); -+ drm_printf(p, "\tcolor_mgmt_changed=%d\n", state->color_mgmt_changed); - - if (plane->funcs->atomic_print_state) - plane->funcs->atomic_print_state(p, state); -diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c -index 784e63d70a42..25bb0859fda7 100644 ---- a/drivers/gpu/drm/drm_atomic_state_helper.c -+++ b/drivers/gpu/drm/drm_atomic_state_helper.c -@@ -338,6 +338,7 @@ void __drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane, - state->fence = NULL; - state->commit = NULL; - state->fb_damage_clips = NULL; -+ state->color_mgmt_changed = false; - } - EXPORT_SYMBOL(__drm_atomic_helper_plane_duplicate_state); - -diff --git a/drivers/gpu/drm/drm_property.c b/drivers/gpu/drm/drm_property.c -index dfec479830e4..f72ef6493340 100644 ---- a/drivers/gpu/drm/drm_property.c -+++ b/drivers/gpu/drm/drm_property.c -@@ -751,6 +751,55 @@ bool drm_property_replace_blob(struct drm_property_blob **blob, - } - EXPORT_SYMBOL(drm_property_replace_blob); - -+/** -+ * drm_property_replace_blob_from_id - replace a blob property taking a reference -+ * @dev: DRM device -+ * @blob: a pointer to the member blob to be replaced -+ * @blob_id: the id of the new blob to replace with -+ * @expected_size: expected size of the blob property -+ * @expected_elem_size: expected size of an element in the blob property -+ * @replaced: if the blob was in fact replaced -+ * -+ * Look up the new blob from id, take its reference, check expected sizes of -+ * the blob and its element and replace the old blob by the new one. Advertise -+ * if the replacement operation was successful. -+ * -+ * Return: true if the blob was in fact replaced. -EINVAL if the new blob was -+ * not found or sizes don't match. -+ */ -+int drm_property_replace_blob_from_id(struct drm_device *dev, -+ struct drm_property_blob **blob, -+ uint64_t blob_id, -+ ssize_t expected_size, -+ ssize_t expected_elem_size, -+ bool *replaced) -+{ -+ struct drm_property_blob *new_blob = NULL; -+ -+ if (blob_id != 0) { -+ new_blob = drm_property_lookup_blob(dev, blob_id); -+ if (new_blob == NULL) -+ return -EINVAL; -+ -+ if (expected_size > 0 && -+ new_blob->length != expected_size) { -+ drm_property_blob_put(new_blob); -+ return -EINVAL; -+ } -+ if (expected_elem_size > 0 && -+ new_blob->length % expected_elem_size != 0) { -+ drm_property_blob_put(new_blob); -+ return -EINVAL; -+ } -+ } -+ -+ *replaced |= drm_property_replace_blob(blob, new_blob); -+ drm_property_blob_put(new_blob); -+ -+ return 0; -+} -+EXPORT_SYMBOL(drm_property_replace_blob_from_id); -+ - int drm_mode_getblob_ioctl(struct drm_device *dev, - void *data, struct drm_file *file_priv) - { -diff --git a/include/drm/drm_mode_object.h b/include/drm/drm_mode_object.h -index 912f1e415685..08d7a7f0188f 100644 ---- a/include/drm/drm_mode_object.h -+++ b/include/drm/drm_mode_object.h -@@ -60,7 +60,7 @@ struct drm_mode_object { - void (*free_cb)(struct kref *kref); - }; - --#define DRM_OBJECT_MAX_PROPERTY 24 -+#define DRM_OBJECT_MAX_PROPERTY 64 - /** - * struct drm_object_properties - property tracking for &drm_mode_object - */ -diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h -index 79d62856defb..4f87803b3ea1 100644 ---- a/include/drm/drm_plane.h -+++ b/include/drm/drm_plane.h -@@ -237,6 +237,13 @@ struct drm_plane_state { - - /** @state: backpointer to global drm_atomic_state */ - struct drm_atomic_state *state; -+ -+ /** -+ * @color_mgmt_changed: Color management properties have changed. Used -+ * by the atomic helpers and drivers to steer the atomic commit control -+ * flow. -+ */ -+ bool color_mgmt_changed : 1; - }; - - static inline struct drm_rect -diff --git a/include/drm/drm_property.h b/include/drm/drm_property.h -index 65bc9710a470..082f29156b3e 100644 ---- a/include/drm/drm_property.h -+++ b/include/drm/drm_property.h -@@ -279,6 +279,12 @@ struct drm_property_blob *drm_property_create_blob(struct drm_device *dev, - const void *data); - struct drm_property_blob *drm_property_lookup_blob(struct drm_device *dev, - uint32_t id); -+int drm_property_replace_blob_from_id(struct drm_device *dev, -+ struct drm_property_blob **blob, -+ uint64_t blob_id, -+ ssize_t expected_size, -+ ssize_t expected_elem_size, -+ bool *replaced); - int drm_property_replace_global_blob(struct drm_device *dev, - struct drm_property_blob **replace, - size_t length, -diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h -index ea1b639bcb28..cea5653e4020 100644 ---- a/include/uapi/drm/drm_mode.h -+++ b/include/uapi/drm/drm_mode.h -@@ -846,6 +846,14 @@ struct drm_color_ctm { - __u64 matrix[9]; - }; - -+struct drm_color_ctm2 { -+ /* -+ * Conversion matrix in S31.32 sign-magnitude -+ * (not two's complement!) format. -+ */ -+ __u64 matrix[12]; -+}; -+ - struct drm_color_lut { - /* - * Values are mapped linearly to 0.0 - 1.0 range, with 0x0 == 0.0 and --- -2.43.0 - -From ae084d65240cfc24d394c25eb35d1d7bd7030ca4 Mon Sep 17 00:00:00 2001 -From: Peter Jung -Date: Fri, 8 Dec 2023 10:30:43 +0100 -Subject: [PATCH 2/7] amd-pref-core +Date: Fri, 15 Mar 2024 20:08:47 +0100 +Subject: [PATCH 1/7] amd-pstate Signed-off-by: Peter Jung --- .../admin-guide/kernel-parameters.txt | 5 + - Documentation/admin-guide/pm/amd-pstate.rst | 59 +++++- + Documentation/admin-guide/pm/amd-pstate.rst | 70 ++- arch/x86/Kconfig | 5 +- - drivers/acpi/cppc_acpi.c | 13 ++ + arch/x86/include/asm/msr-index.h | 2 + + arch/x86/kernel/acpi/cppc.c | 2 +- + drivers/acpi/cppc_acpi.c | 17 +- drivers/acpi/processor_driver.c | 6 + - drivers/cpufreq/amd-pstate.c | 175 +++++++++++++++++- - drivers/cpufreq/cpufreq.c | 13 ++ + drivers/cpufreq/acpi-cpufreq.c | 2 - + drivers/cpufreq/amd-pstate-ut.c | 2 +- + drivers/cpufreq/amd-pstate.c | 501 +++++++++++++++--- include/acpi/cppc_acpi.h | 5 + - include/linux/amd-pstate.h | 10 + - include/linux/cpufreq.h | 5 + - 10 files changed, 284 insertions(+), 12 deletions(-) + include/linux/amd-pstate.h | 32 +- + include/linux/cpufreq.h | 1 + + 13 files changed, 562 insertions(+), 88 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt -index 41644336e358..6e121fdb68f9 100644 +index 73062d47a462..a493d93e0d2c 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt -@@ -363,6 +363,11 @@ +@@ -374,6 +374,11 @@ selects a performance level in this range and appropriate to the current workload. @@ -2076,7 +37,7 @@ index 41644336e358..6e121fdb68f9 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 1cf40f69278c..0b832ff529db 100644 +index 9eb26014d34b..82fbd01da658 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 @@ -2122,7 +83,7 @@ index 1cf40f69278c..0b832ff529db 100644 +update the core ranking and set the cpu's priority. + +``amd-pstate`` Preferred Core Switch -+================================= ++===================================== +Kernel Parameters +----------------- + @@ -2139,7 +100,7 @@ index 1cf40f69278c..0b832ff529db 100644 User Space Interface in ``sysfs`` - General =========================================== -@@ -385,6 +427,19 @@ control its functionality at the system level. They are located in the +@@ -385,6 +427,30 @@ 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. @@ -2155,12 +116,23 @@ index 1cf40f69278c..0b832ff529db 100644 + + This attribute is read-only to check the state of preferred core set + by the kernel parameter. ++ ++``cpb_boost`` ++ Specifies whether core performance boost is requested to be enabled or disabled ++ If core performance boost is disabled while a core is in a boosted P-state, the ++ core automatically transitions to the highest performance non-boosted P-state. ++ AMD Core Performance Boost(CPB) is controlled by this new attribute file which ++ allow user to change all cores frequency boosting state. It supports both ++ ``active``, ``passive`` and ``guided`` mode control with below value write to it. ++ ++ "0" Disable Core Performance Boosting ++ "1" Enable Core Performance Boosting + ``cpupower`` tool support for ``amd-pstate`` =============================================== diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig -index 66bfabae8814..a2e163acf623 100644 +index 637e337c332e..de39c296ea3f 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -1054,8 +1054,9 @@ config SCHED_MC @@ -2175,11 +147,49 @@ index 66bfabae8814..a2e163acf623 100644 select CPU_FREQ default y help +diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h +index d1b5edaf6c34..bfe139eb75b6 100644 +--- a/arch/x86/include/asm/msr-index.h ++++ b/arch/x86/include/asm/msr-index.h +@@ -744,6 +744,8 @@ + #define MSR_K7_HWCR_IRPERF_EN BIT_ULL(MSR_K7_HWCR_IRPERF_EN_BIT) + #define MSR_K7_FID_VID_CTL 0xc0010041 + #define MSR_K7_FID_VID_STATUS 0xc0010042 ++#define MSR_K7_HWCR_CPB_DIS_BIT 25 ++#define MSR_K7_HWCR_CPB_DIS BIT_ULL(MSR_K7_HWCR_CPB_DIS_BIT) + + /* K6 MSRs */ + #define MSR_K6_WHCR 0xc0000082 +diff --git a/arch/x86/kernel/acpi/cppc.c b/arch/x86/kernel/acpi/cppc.c +index 8d8752b44f11..ff8f25faca3d 100644 +--- a/arch/x86/kernel/acpi/cppc.c ++++ b/arch/x86/kernel/acpi/cppc.c +@@ -20,7 +20,7 @@ bool cpc_supported_by_cpu(void) + (boot_cpu_data.x86_model >= 0x20 && boot_cpu_data.x86_model <= 0x2f))) + return true; + else if (boot_cpu_data.x86 == 0x17 && +- boot_cpu_data.x86_model >= 0x70 && boot_cpu_data.x86_model <= 0x7f) ++ boot_cpu_data.x86_model >= 0x30 && boot_cpu_data.x86_model <= 0x7f) + return true; + return boot_cpu_has(X86_FEATURE_CPPC); + } diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c -index 7ff269a78c20..ad388a0e8484 100644 +index d155a86a8614..e23a84f4a50a 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) +@@ -679,8 +679,10 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) + + if (!osc_sb_cppc2_support_acked) { + pr_debug("CPPC v2 _OSC not acked\n"); +- if (!cpc_supported_by_cpu()) ++ if (!cpc_supported_by_cpu()) { ++ pr_debug("CPPC is not supported by the CPU\n"); + return -ENODEV; ++ } + } + + /* Parse the ACPI _CPC table for this CPU. */ +@@ -1157,6 +1159,19 @@ int cppc_get_nominal_perf(int cpunum, u64 *nominal_perf) return cppc_get_perf(cpunum, NOMINAL_PERF, nominal_perf); } @@ -2200,7 +210,7 @@ index 7ff269a78c20..ad388a0e8484 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 4bd16b3f0781..29b2fb68a35d 100644 +index 4bd16b3f0781..67db60eda370 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -27,6 +27,7 @@ @@ -2216,15 +226,41 @@ index 4bd16b3f0781..29b2fb68a35d 100644 dev_name(&device->dev), event, 0); break; + case ACPI_PROCESSOR_NOTIFY_HIGEST_PERF_CHANGED: -+ cpufreq_update_highest_perf(pr->id); ++ cpufreq_update_limits(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 --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c +index 37f1cdf46d29..2fc82831bddd 100644 +--- a/drivers/cpufreq/acpi-cpufreq.c ++++ b/drivers/cpufreq/acpi-cpufreq.c +@@ -50,8 +50,6 @@ enum { + #define AMD_MSR_RANGE (0x7) + #define HYGON_MSR_RANGE (0x7) + +-#define MSR_K7_HWCR_CPB_DIS (1ULL << 25) +- + struct acpi_cpufreq_data { + unsigned int resume; + unsigned int cpu_feature; +diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +index f04ae67dda37..b3601b0e6dd3 100644 +--- a/drivers/cpufreq/amd-pstate-ut.c ++++ b/drivers/cpufreq/amd-pstate-ut.c +@@ -226,7 +226,7 @@ static void amd_pstate_ut_check_freq(u32 index) + goto skip_test; + } + +- if (cpudata->boost_supported) { ++ if (amd_pstate_global_params.cpb_boost) { + if ((policy->max == cpudata->max_freq) || + (policy->max == cpudata->nominal_freq)) + amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_PASS; diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c -index 1f6186475715..25f0fb53d320 100644 +index 1791d37fbc53..651055df1710 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -37,6 +37,7 @@ @@ -2235,23 +271,69 @@ index 1f6186475715..25f0fb53d320 100644 #include #include -@@ -49,6 +50,7 @@ - - #define AMD_PSTATE_TRANSITION_LATENCY 20000 - #define AMD_PSTATE_TRANSITION_DELAY 1000 -+#define AMD_PSTATE_PREFCORE_THRESHOLD 166 - - /* - * TODO: We need more time to fine tune processors with shared memory solution -@@ -64,6 +66,7 @@ static struct cpufreq_driver amd_pstate_driver; +@@ -64,6 +65,10 @@ 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; ++static struct quirk_entry *quirks; ++struct amd_pstate_global_params amd_pstate_global_params; ++EXPORT_SYMBOL_GPL(amd_pstate_global_params); /* * AMD Energy Preference Performance (EPP) -@@ -297,13 +300,14 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) +@@ -108,6 +113,41 @@ static unsigned int epp_values[] = { + + typedef int (*cppc_mode_transition_fn)(int); + ++static struct quirk_entry quirk_amd_7k62 = { ++ .nominal_freq = 2600, ++ .lowest_freq = 550, ++}; ++ ++static int __init dmi_matched_7k62_bios_bug(const struct dmi_system_id *dmi) ++{ ++ /** ++ * match the broken bios for family 17h processor support CPPC V2 ++ * broken BIOS lack of nominal_freq and lowest_freq capabilities ++ * definition in ACPI tables ++ */ ++ if (boot_cpu_has(X86_FEATURE_ZEN2)) { ++ quirks = dmi->driver_data; ++ pr_info("Overriding nominal and lowest frequencies for %s\n", dmi->ident); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static const struct dmi_system_id amd_pstate_quirks_table[] __initconst = { ++ { ++ .callback = dmi_matched_7k62_bios_bug, ++ .ident = "AMD EPYC 7K62", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VERSION, "5.14"), ++ DMI_MATCH(DMI_BIOS_RELEASE, "12/12/2019"), ++ }, ++ .driver_data = &quirk_amd_7k62, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(dmi, amd_pstate_quirks_table); ++ + static inline int get_mode_idx_from_str(const char *str, size_t size) + { + int i; +@@ -291,16 +331,20 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) + { + u64 cap1; + u32 highest_perf; ++ struct cppc_perf_caps cppc_perf; ++ int ret; + +- int ret = rdmsrl_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, ++ ret = rdmsrl_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, + &cap1); if (ret) return ret; @@ -2259,51 +341,267 @@ index 1f6186475715..25f0fb53d320 100644 - * 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. ++ ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); ++ if (ret) ++ return ret; ++ ++ /* Some CPUs have different highest_perf from others, it is safer ++ * to read it than to assume some erroneous value, leading to performance issues. */ -- highest_perf = amd_get_highest_perf(); -- if (highest_perf > AMD_CPPC_HIGHEST_PERF(cap1)) -+ if (cpudata->hw_prefcore) -+ highest_perf = AMD_PSTATE_PREFCORE_THRESHOLD; -+ else - highest_perf = AMD_CPPC_HIGHEST_PERF(cap1); - - WRITE_ONCE(cpudata->highest_perf, highest_perf); -@@ -311,6 +315,7 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) + highest_perf = amd_get_highest_perf(); + if (highest_perf > AMD_CPPC_HIGHEST_PERF(cap1)) +@@ -311,7 +355,11 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) WRITE_ONCE(cpudata->nominal_perf, AMD_CPPC_NOMINAL_PERF(cap1)); WRITE_ONCE(cpudata->lowest_nonlinear_perf, AMD_CPPC_LOWNONLIN_PERF(cap1)); WRITE_ONCE(cpudata->lowest_perf, AMD_CPPC_LOWEST_PERF(cap1)); + WRITE_ONCE(cpudata->prefcore_ranking, AMD_CPPC_HIGHEST_PERF(cap1)); WRITE_ONCE(cpudata->min_limit_perf, AMD_CPPC_LOWEST_PERF(cap1)); ++ WRITE_ONCE(cpudata->lowest_freq, cppc_perf.lowest_freq); ++ WRITE_ONCE(cpudata->nominal_freq, cppc_perf.nominal_freq); ++ return 0; } -@@ -324,8 +329,9 @@ static int cppc_init_perf(struct amd_cpudata *cpudata) + +@@ -319,11 +367,15 @@ static int cppc_init_perf(struct amd_cpudata *cpudata) + { + struct cppc_perf_caps cppc_perf; + u32 highest_perf; ++ int ret; + +- int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); ++ 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) -+ if (cpudata->hw_prefcore) -+ highest_perf = AMD_PSTATE_PREFCORE_THRESHOLD; -+ else ++ /* Some CPUs have different highest_perf from others, it is safer ++ * to read it than to assume some erroneous value, leading to performance issues. ++ */ + 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); -@@ -334,6 +340,7 @@ static int cppc_init_perf(struct amd_cpudata *cpudata) +@@ -334,7 +386,10 @@ static int cppc_init_perf(struct amd_cpudata *cpudata) WRITE_ONCE(cpudata->lowest_nonlinear_perf, cppc_perf.lowest_nonlinear_perf); WRITE_ONCE(cpudata->lowest_perf, cppc_perf.lowest_perf); + WRITE_ONCE(cpudata->prefcore_ranking, cppc_perf.highest_perf); WRITE_ONCE(cpudata->min_limit_perf, cppc_perf.lowest_perf); ++ WRITE_ONCE(cpudata->lowest_freq, cppc_perf.lowest_freq); ++ WRITE_ONCE(cpudata->nominal_freq, cppc_perf.nominal_freq); if (cppc_state == AMD_PSTATE_ACTIVE) -@@ -706,6 +713,106 @@ static void amd_perf_ctl_reset(unsigned int cpu) - wrmsrl_on_cpu(cpu, MSR_AMD_PERF_CTL, 0); + return 0; +@@ -430,7 +485,10 @@ static inline bool amd_pstate_sample(struct amd_cpudata *cpudata) + static void amd_pstate_update(struct amd_cpudata *cpudata, u32 min_perf, + u32 des_perf, u32 max_perf, bool fast_switch, int gov_flags) + { ++ unsigned long max_freq; ++ struct cpufreq_policy *policy = cpufreq_cpu_get(cpudata->cpu); + u64 prev = READ_ONCE(cpudata->cppc_req_cached); ++ u32 nominal_perf = READ_ONCE(cpudata->nominal_perf); + u64 value = prev; + + min_perf = clamp_t(unsigned long, min_perf, cpudata->min_limit_perf, +@@ -439,6 +497,9 @@ static void amd_pstate_update(struct amd_cpudata *cpudata, u32 min_perf, + cpudata->max_limit_perf); + des_perf = clamp_t(unsigned long, des_perf, min_perf, max_perf); + ++ max_freq = READ_ONCE(cpudata->max_limit_freq); ++ policy->cur = div_u64(des_perf * max_freq, max_perf); ++ + if ((cppc_state == AMD_PSTATE_GUIDED) && (gov_flags & CPUFREQ_GOV_DYNAMIC_SWITCHING)) { + min_perf = des_perf; + des_perf = 0; +@@ -450,6 +511,10 @@ static void amd_pstate_update(struct amd_cpudata *cpudata, u32 min_perf, + value &= ~AMD_CPPC_DES_PERF(~0L); + value |= AMD_CPPC_DES_PERF(des_perf); + ++ /* limit the max perf when core performance boost feature is disabled */ ++ if (!amd_pstate_global_params.cpb_boost) ++ max_perf = min_t(unsigned long, nominal_perf, max_perf); ++ + value &= ~AMD_CPPC_MAX_PERF(~0L); + value |= AMD_CPPC_MAX_PERF(max_perf); + +@@ -477,12 +542,19 @@ static int amd_pstate_verify(struct cpufreq_policy_data *policy) + + static int amd_pstate_update_min_max_limit(struct cpufreq_policy *policy) + { +- u32 max_limit_perf, min_limit_perf; ++ u32 max_limit_perf, min_limit_perf, lowest_perf; + struct amd_cpudata *cpudata = policy->driver_data; + + max_limit_perf = div_u64(policy->max * cpudata->highest_perf, cpudata->max_freq); + min_limit_perf = div_u64(policy->min * cpudata->highest_perf, cpudata->max_freq); + ++ lowest_perf = READ_ONCE(cpudata->lowest_perf); ++ if (min_limit_perf < lowest_perf) ++ min_limit_perf = lowest_perf; ++ ++ if (max_limit_perf < min_limit_perf) ++ max_limit_perf = min_limit_perf; ++ + WRITE_ONCE(cpudata->max_limit_perf, max_limit_perf); + WRITE_ONCE(cpudata->min_limit_perf, min_limit_perf); + WRITE_ONCE(cpudata->max_limit_freq, policy->max); +@@ -553,10 +625,9 @@ static void amd_pstate_adjust_perf(unsigned int cpu, + unsigned long capacity) + { + unsigned long max_perf, min_perf, des_perf, +- cap_perf, lowest_nonlinear_perf, max_freq; ++ cap_perf, lowest_nonlinear_perf; + struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); + struct amd_cpudata *cpudata = policy->driver_data; +- unsigned int target_freq; + + if (policy->min != cpudata->min_limit_freq || policy->max != cpudata->max_limit_freq) + amd_pstate_update_min_max_limit(policy); +@@ -564,13 +635,12 @@ static void amd_pstate_adjust_perf(unsigned int cpu, + + cap_perf = READ_ONCE(cpudata->highest_perf); + lowest_nonlinear_perf = READ_ONCE(cpudata->lowest_nonlinear_perf); +- max_freq = READ_ONCE(cpudata->max_freq); + + des_perf = cap_perf; + 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); + +@@ -582,8 +652,6 @@ static void amd_pstate_adjust_perf(unsigned int cpu, + max_perf = min_perf; + + des_perf = clamp_t(unsigned long, des_perf, min_perf, max_perf); +- target_freq = div_u64(des_perf * max_freq, max_perf); +- policy->cur = target_freq; + + amd_pstate_update(cpudata, min_perf, des_perf, max_perf, true, + policy->governor->flags); +@@ -592,30 +660,30 @@ static void amd_pstate_adjust_perf(unsigned int cpu, + + static int amd_get_min_freq(struct amd_cpudata *cpudata) + { +- struct cppc_perf_caps cppc_perf; ++ u32 lowest_freq; + +- int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); +- if (ret) +- return ret; ++ if (quirks && quirks->lowest_freq) ++ lowest_freq = quirks->lowest_freq; ++ else ++ lowest_freq = READ_ONCE(cpudata->lowest_freq); + + /* Switch to khz */ +- return cppc_perf.lowest_freq * 1000; ++ return lowest_freq * 1000; } + static int amd_get_max_freq(struct amd_cpudata *cpudata) + { +- struct cppc_perf_caps cppc_perf; + u32 max_perf, max_freq, nominal_freq, nominal_perf; + u64 boost_ratio; + +- int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); +- if (ret) +- return ret; +- +- nominal_freq = cppc_perf.nominal_freq; ++ nominal_freq = READ_ONCE(cpudata->nominal_freq); + nominal_perf = READ_ONCE(cpudata->nominal_perf); + max_perf = READ_ONCE(cpudata->highest_perf); + ++ /* when boost is off, the highest perf will be limited to nominal_perf */ ++ if (!amd_pstate_global_params.cpb_boost) ++ max_perf = nominal_perf; ++ + boost_ratio = div_u64(max_perf << SCHED_CAPACITY_SHIFT, + nominal_perf); + +@@ -627,31 +695,25 @@ static int amd_get_max_freq(struct amd_cpudata *cpudata) + + static int amd_get_nominal_freq(struct amd_cpudata *cpudata) + { +- struct cppc_perf_caps cppc_perf; ++ u32 nominal_freq; + +- int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); +- if (ret) +- return ret; ++ if (quirks && quirks->nominal_freq) ++ nominal_freq = quirks->nominal_freq; ++ else ++ nominal_freq = READ_ONCE(cpudata->nominal_freq); + +- /* Switch to khz */ +- return cppc_perf.nominal_freq * 1000; ++ return nominal_freq; + } + + static int amd_get_lowest_nonlinear_freq(struct amd_cpudata *cpudata) + { +- struct cppc_perf_caps cppc_perf; + u32 lowest_nonlinear_freq, lowest_nonlinear_perf, + nominal_freq, nominal_perf; + u64 lowest_nonlinear_ratio; + +- int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); +- if (ret) +- return ret; +- +- nominal_freq = cppc_perf.nominal_freq; ++ nominal_freq = READ_ONCE(cpudata->nominal_freq); + nominal_perf = READ_ONCE(cpudata->nominal_perf); +- +- lowest_nonlinear_perf = cppc_perf.lowest_nonlinear_perf; ++ lowest_nonlinear_perf = READ_ONCE(cpudata->lowest_nonlinear_perf); + + lowest_nonlinear_ratio = div_u64(lowest_nonlinear_perf << SCHED_CAPACITY_SHIFT, + nominal_perf); +@@ -662,48 +724,164 @@ static int amd_get_lowest_nonlinear_freq(struct amd_cpudata *cpudata) + return lowest_nonlinear_freq * 1000; + } + +-static int amd_pstate_set_boost(struct cpufreq_policy *policy, int state) ++static int amd_pstate_boost_init(struct amd_cpudata *cpudata) + { +- struct amd_cpudata *cpudata = policy->driver_data; ++ u64 boost_val; + int ret; + +- if (!cpudata->boost_supported) { +- pr_err("Boost mode is not supported by this processor or SBIOS\n"); +- return -EINVAL; ++ ret = rdmsrl_on_cpu(cpudata->cpu, MSR_K7_HWCR, &boost_val); ++ if (ret) { ++ pr_err_once("failed to read initial CPU boost state!\n"); ++ return ret; + } + +- if (state) +- policy->cpuinfo.max_freq = cpudata->max_freq; +- else +- policy->cpuinfo.max_freq = cpudata->nominal_freq; ++ amd_pstate_global_params.cpb_supported = !(boost_val & MSR_K7_HWCR_CPB_DIS); ++ amd_pstate_global_params.cpb_boost = amd_pstate_global_params.cpb_supported; + +- policy->max = policy->cpuinfo.max_freq; ++ return ret; ++} + +- ret = freq_qos_update_request(&cpudata->req[1], +- policy->cpuinfo.max_freq); +- if (ret < 0) +- return ret; ++static void amd_perf_ctl_reset(unsigned int cpu) ++{ ++ wrmsrl_on_cpu(cpu, MSR_AMD_PERF_CTL, 0); ++} + +- return 0; +/* + * Set amd-pstate preferred core enable can't be done directly from cpufreq callbacks + * due to locking, so queue the work for later. @@ -2311,9 +609,10 @@ index 1f6186475715..25f0fb53d320 100644 +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); -+ + +-static void amd_pstate_boost_init(struct amd_cpudata *cpudata) +/* + * Get the highest performance register value. + * @cpu: CPU from which to get highest performance. @@ -2322,9 +621,12 @@ index 1f6186475715..25f0fb53d320 100644 + * Return: 0 for success, -EIO otherwise. + */ +static int amd_pstate_get_highest_perf(int cpu, u32 *highest_perf) -+{ + { +- u32 highest_perf, nominal_perf; + int ret; -+ + +- highest_perf = READ_ONCE(cpudata->highest_perf); +- nominal_perf = READ_ONCE(cpudata->nominal_perf); + if (boot_cpu_has(X86_FEATURE_CPPC)) { + u64 cap1; + @@ -2350,7 +652,8 @@ index 1f6186475715..25f0fb53d320 100644 +{ + int ret, prio; + u32 highest_perf; -+ + +- if (highest_perf <= nominal_perf) + ret = amd_pstate_get_highest_perf(cpudata->cpu, &highest_perf); + if (ret) + return; @@ -2366,8 +669,10 @@ index 1f6186475715..25f0fb53d320 100644 + } + + if (!amd_pstate_prefcore) -+ return; -+ + return; + +- cpudata->boost_supported = true; +- current_pstate_driver->boost_enabled = true; + /* + * The priorities can be set regardless of whether or not + * sched_set_itmt_support(true) has been called and it is valid to @@ -2376,15 +681,19 @@ index 1f6186475715..25f0fb53d320 100644 + sched_set_itmt_core_prio(prio, cpudata->cpu); + + schedule_work(&sched_prefcore_work); -+} -+ -+static void amd_pstate_update_highest_perf(unsigned int cpu) -+{ + } + +-static void amd_perf_ctl_reset(unsigned int cpu) ++static void amd_pstate_update_limits(unsigned int cpu) + { +- wrmsrl_on_cpu(cpu, MSR_AMD_PERF_CTL, 0); + struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); + struct amd_cpudata *cpudata = policy->driver_data; + u32 prev_high = 0, cur_high = 0; + int ret; ++ bool highest_perf_changed = false; + ++ mutex_lock(&amd_pstate_driver_lock); + if ((!amd_pstate_prefcore) || (!cpudata->hw_prefcore)) + goto free_cpufreq_put; + @@ -2394,6 +703,7 @@ index 1f6186475715..25f0fb53d320 100644 + + prev_high = READ_ONCE(cpudata->prefcore_ranking); + if (prev_high != cur_high) { ++ highest_perf_changed = true; + WRITE_ONCE(cpudata->prefcore_ranking, cur_high); + + if (cur_high < CPPC_MAX_PERF) @@ -2402,12 +712,45 @@ index 1f6186475715..25f0fb53d320 100644 + +free_cpufreq_put: + cpufreq_cpu_put(policy); ++ ++ if (!highest_perf_changed) ++ cpufreq_update_policy(cpu); ++ ++ mutex_unlock(&amd_pstate_driver_lock); +} + ++/** ++ * Get pstate transition delay time from ACPI tables that firmware set ++ * instead of using hardcode value directly. ++ */ ++static u32 amd_pstate_get_transition_delay_us(unsigned int cpu) ++{ ++ u32 transition_delay_ns; ++ ++ transition_delay_ns = cppc_get_transition_latency(cpu); ++ if (transition_delay_ns == CPUFREQ_ETERNAL) ++ return AMD_PSTATE_TRANSITION_DELAY; ++ ++ return transition_delay_ns / NSEC_PER_USEC; ++} ++ ++/** ++ * Get pstate transition latency value from ACPI tables that firmware set ++ * instead of using hardcode value directly. ++ */ ++static u32 amd_pstate_get_transition_latency(unsigned int cpu) ++{ ++ u32 transition_latency; ++ ++ transition_latency = cppc_get_transition_latency(cpu); ++ if (transition_latency == CPUFREQ_ETERNAL) ++ return AMD_PSTATE_TRANSITION_LATENCY; ++ ++ return transition_latency; + } + static int amd_pstate_cpu_init(struct cpufreq_policy *policy) - { - int min_freq, max_freq, nominal_freq, lowest_nonlinear_freq, ret; -@@ -727,6 +834,8 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) +@@ -727,24 +905,30 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) cpudata->cpu = policy->cpu; @@ -2416,7 +759,48 @@ index 1f6186475715..25f0fb53d320 100644 ret = amd_pstate_init_perf(cpudata); if (ret) goto free_cpudata1; -@@ -877,6 +986,28 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, + ++ /* initialize cpu cores boot state */ ++ amd_pstate_boost_init(cpudata); ++ + min_freq = amd_get_min_freq(cpudata); +- max_freq = amd_get_max_freq(cpudata); + nominal_freq = amd_get_nominal_freq(cpudata); ++ cpudata->nominal_freq = nominal_freq; ++ max_freq = amd_get_max_freq(cpudata); + lowest_nonlinear_freq = amd_get_lowest_nonlinear_freq(cpudata); + +- if (min_freq < 0 || max_freq < 0 || min_freq > max_freq) { +- dev_err(dev, "min_freq(%d) or max_freq(%d) value is incorrect\n", +- min_freq, max_freq); ++ if (min_freq < 0 || max_freq < 0 || min_freq > max_freq || nominal_freq == 0) { ++ dev_err(dev, "min_freq(%d) or max_freq(%d) or nominal_freq(%d) is incorrect\n", ++ min_freq, max_freq, nominal_freq); + ret = -EINVAL; + goto free_cpudata1; + } + +- policy->cpuinfo.transition_latency = AMD_PSTATE_TRANSITION_LATENCY; +- policy->transition_delay_us = AMD_PSTATE_TRANSITION_DELAY; ++ policy->cpuinfo.transition_latency = amd_pstate_get_transition_latency(policy->cpu); ++ policy->transition_delay_us = amd_pstate_get_transition_delay_us(policy->cpu); + + policy->min = min_freq; + policy->max = max_freq; +@@ -777,12 +961,10 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) + cpudata->min_freq = min_freq; + cpudata->max_limit_freq = max_freq; + cpudata->min_limit_freq = min_freq; +- cpudata->nominal_freq = nominal_freq; + cpudata->lowest_nonlinear_freq = lowest_nonlinear_freq; + + policy->driver_data = cpudata; + +- amd_pstate_boost_init(cpudata); + if (!current_pstate_driver->adjust_perf) + current_pstate_driver->adjust_perf = amd_pstate_adjust_perf; + +@@ -877,6 +1059,28 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, return sysfs_emit(buf, "%u\n", perf); } @@ -2445,7 +829,7 @@ index 1f6186475715..25f0fb53d320 100644 static ssize_t show_energy_performance_available_preferences( struct cpufreq_policy *policy, char *buf) { -@@ -1074,18 +1205,29 @@ static ssize_t status_store(struct device *a, struct device_attribute *b, +@@ -1074,18 +1278,125 @@ static ssize_t status_store(struct device *a, struct device_attribute *b, return ret < 0 ? ret : count; } @@ -2454,6 +838,101 @@ index 1f6186475715..25f0fb53d320 100644 +{ + return sysfs_emit(buf, "%s\n", str_enabled_disabled(amd_pstate_prefcore)); +} ++ ++static int amd_cpu_boost_update(struct amd_cpudata *cpudata, u32 on) ++{ ++ struct cpufreq_policy *policy = cpufreq_cpu_acquire(cpudata->cpu); ++ struct cppc_perf_ctrls perf_ctrls; ++ u32 highest_perf, nominal_perf; ++ int ret; ++ ++ if (!policy) ++ return -ENODATA; ++ ++ highest_perf = READ_ONCE(cpudata->highest_perf); ++ nominal_perf = READ_ONCE(cpudata->nominal_perf); ++ ++ if (boot_cpu_has(X86_FEATURE_CPPC)) { ++ u64 value = READ_ONCE(cpudata->cppc_req_cached); ++ ++ value &= ~GENMASK_ULL(7, 0); ++ value |= on ? highest_perf : nominal_perf; ++ WRITE_ONCE(cpudata->cppc_req_cached, value); ++ ++ wrmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, value); ++ ++ } else { ++ perf_ctrls.max_perf = on ? highest_perf : nominal_perf; ++ ret = cppc_set_epp_perf(cpudata->cpu, &perf_ctrls, 1); ++ if (ret) { ++ pr_debug("failed to set energy perf value (%d)\n", ret); ++ return ret; ++ } ++ } ++ ++ if (on) ++ policy->cpuinfo.max_freq = cpudata->max_freq; ++ else ++ policy->cpuinfo.max_freq = cpudata->nominal_freq * 1000; ++ ++ policy->max = policy->cpuinfo.max_freq; ++ ++ if (cppc_state == AMD_PSTATE_PASSIVE) { ++ ret = freq_qos_update_request(&cpudata->req[1], ++ policy->cpuinfo.max_freq); ++ } ++ ++ cpufreq_cpu_release(policy); ++ ++ return ret; ++} ++ ++static ssize_t cpb_boost_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return sysfs_emit(buf, "%u\n", amd_pstate_global_params.cpb_boost); ++} ++ ++static ssize_t cpb_boost_store(struct device *dev, struct device_attribute *b, ++ const char *buf, size_t count) ++{ ++ bool new_state; ++ ssize_t ret; ++ int cpu; ++ ++ mutex_lock(&amd_pstate_driver_lock); ++ if (!amd_pstate_global_params.cpb_supported) { ++ pr_err("Boost mode is not supported by this processor or SBIOS\n"); ++ return -EINVAL; ++ } ++ ++ ret = kstrtobool(buf, &new_state); ++ if (ret) ++ return -EINVAL; ++ ++ amd_pstate_global_params.cpb_boost = !!new_state; ++ ++ for_each_online_cpu(cpu) { ++ ++ struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); ++ struct amd_cpudata *cpudata = policy->driver_data; ++ ++ if (!cpudata) { ++ pr_err("cpudata is NULL\n"); ++ ret = -ENODATA; ++ cpufreq_cpu_put(policy); ++ goto err_exit; ++ } ++ ++ amd_cpu_boost_update(cpudata, amd_pstate_global_params.cpb_boost); ++ refresh_frequency_limits(policy); ++ cpufreq_cpu_put(policy); ++ } ++ ++err_exit: ++ mutex_unlock(&amd_pstate_driver_lock); ++ return ret < 0 ? ret : count; ++} + cpufreq_freq_attr_ro(amd_pstate_max_freq); cpufreq_freq_attr_ro(amd_pstate_lowest_nonlinear_freq); @@ -2465,6 +944,7 @@ index 1f6186475715..25f0fb53d320 100644 cpufreq_freq_attr_ro(energy_performance_available_preferences); static DEVICE_ATTR_RW(status); +static DEVICE_ATTR_RO(prefcore); ++static DEVICE_ATTR_RW(cpb_boost); static struct freq_attr *amd_pstate_attr[] = { &amd_pstate_max_freq, @@ -2475,7 +955,7 @@ index 1f6186475715..25f0fb53d320 100644 NULL, }; -@@ -1093,6 +1235,8 @@ static struct freq_attr *amd_pstate_epp_attr[] = { +@@ -1093,6 +1404,8 @@ static struct freq_attr *amd_pstate_epp_attr[] = { &amd_pstate_max_freq, &amd_pstate_lowest_nonlinear_freq, &amd_pstate_highest_perf, @@ -2484,15 +964,16 @@ index 1f6186475715..25f0fb53d320 100644 &energy_performance_preference, &energy_performance_available_preferences, NULL, -@@ -1100,6 +1244,7 @@ static struct freq_attr *amd_pstate_epp_attr[] = { +@@ -1100,6 +1413,8 @@ static struct freq_attr *amd_pstate_epp_attr[] = { static struct attribute *pstate_global_attributes[] = { &dev_attr_status.attr, + &dev_attr_prefcore.attr, ++ &dev_attr_cpb_boost.attr, NULL }; -@@ -1151,6 +1296,8 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) +@@ -1151,17 +1466,23 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) cpudata->cpu = policy->cpu; cpudata->epp_policy = 0; @@ -2501,23 +982,97 @@ index 1f6186475715..25f0fb53d320 100644 ret = amd_pstate_init_perf(cpudata); if (ret) goto free_cpudata1; -@@ -1433,6 +1580,7 @@ static struct cpufreq_driver amd_pstate_driver = { + ++ /* initialize cpu cores boot state */ ++ amd_pstate_boost_init(cpudata); ++ + min_freq = amd_get_min_freq(cpudata); +- max_freq = amd_get_max_freq(cpudata); + nominal_freq = amd_get_nominal_freq(cpudata); ++ cpudata->nominal_freq = nominal_freq; ++ max_freq = amd_get_max_freq(cpudata); + lowest_nonlinear_freq = amd_get_lowest_nonlinear_freq(cpudata); +- if (min_freq < 0 || max_freq < 0 || min_freq > max_freq) { +- dev_err(dev, "min_freq(%d) or max_freq(%d) value is incorrect\n", +- min_freq, max_freq); ++ if (min_freq < 0 || max_freq < 0 || min_freq > max_freq || nominal_freq == 0) { ++ dev_err(dev, "min_freq(%d) or max_freq(%d) or nominal_freq(%d) is incorrect\n", ++ min_freq, max_freq, nominal_freq); + ret = -EINVAL; + goto free_cpudata1; + } +@@ -1174,7 +1495,6 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + /* Initial processor data capability frequencies */ + cpudata->max_freq = max_freq; + cpudata->min_freq = min_freq; +- cpudata->nominal_freq = nominal_freq; + cpudata->lowest_nonlinear_freq = lowest_nonlinear_freq; + + policy->driver_data = cpudata; +@@ -1205,7 +1525,6 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + return ret; + WRITE_ONCE(cpudata->cppc_cap1_cached, value); + } +- amd_pstate_boost_init(cpudata); + + return 0; + +@@ -1232,6 +1551,12 @@ static void amd_pstate_epp_update_limit(struct cpufreq_policy *policy) + max_limit_perf = div_u64(policy->max * cpudata->highest_perf, cpudata->max_freq); + min_limit_perf = div_u64(policy->min * cpudata->highest_perf, cpudata->max_freq); + ++ if (min_limit_perf < min_perf) ++ min_limit_perf = min_perf; ++ ++ if (max_limit_perf < min_limit_perf) ++ max_limit_perf = min_limit_perf; ++ + WRITE_ONCE(cpudata->max_limit_perf, max_limit_perf); + WRITE_ONCE(cpudata->min_limit_perf, min_limit_perf); + +@@ -1294,6 +1619,12 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy) + + amd_pstate_epp_update_limit(policy); + ++ /* ++ * policy->cur is never updated with the amd_pstate_epp driver, but it ++ * is used as a stale frequency value. So, keep it within limits. ++ */ ++ policy->cur = policy->min; ++ + return 0; + } + +@@ -1431,7 +1762,7 @@ static struct cpufreq_driver amd_pstate_driver = { + .exit = amd_pstate_cpu_exit, .suspend = amd_pstate_cpu_suspend, .resume = amd_pstate_cpu_resume, - .set_boost = amd_pstate_set_boost, -+ .update_highest_perf = amd_pstate_update_highest_perf, +- .set_boost = amd_pstate_set_boost, ++ .update_limits = amd_pstate_update_limits, .name = "amd-pstate", .attr = amd_pstate_attr, }; -@@ -1447,6 +1595,7 @@ static struct cpufreq_driver amd_pstate_epp_driver = { +@@ -1446,6 +1777,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, -+ .update_highest_perf = amd_pstate_update_highest_perf, ++ .update_limits = amd_pstate_update_limits, .name = "amd-pstate-epp", .attr = amd_pstate_epp_attr, }; -@@ -1568,7 +1717,17 @@ static int __init amd_pstate_param(char *str) +@@ -1486,6 +1818,11 @@ static int __init amd_pstate_init(void) + if (cpufreq_get_current_driver()) + return -EEXIST; + ++ quirks = NULL; ++ ++ /* check if this machine need CPPC quirks */ ++ dmi_check_system(amd_pstate_quirks_table); ++ + switch (cppc_state) { + case AMD_PSTATE_UNDEFINED: + /* Disable on the following configs by default: +@@ -1567,7 +1904,17 @@ static int __init amd_pstate_param(char *str) return amd_pstate_set_driver(mode_idx); } @@ -2535,32 +1090,8 @@ index 1f6186475715..25f0fb53d320 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 60ed89000e82..4ada787ff105 100644 ---- a/drivers/cpufreq/cpufreq.c -+++ b/drivers/cpufreq/cpufreq.c -@@ -2718,6 +2718,19 @@ void cpufreq_update_limits(unsigned int cpu) - } - 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 --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h -index 6126c977ece0..c0b69ffe7bdb 100644 +index 3a0995f8bce8..930b6afba6f4 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -139,6 +139,7 @@ struct cppc_cpudata { @@ -2571,7 +1102,7 @@ index 6126c977ece0..c0b69ffe7bdb 100644 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 @@ static inline int cppc_get_nominal_perf(int cpunum, u64 *nominal_perf) +@@ -167,6 +168,10 @@ static inline int cppc_get_nominal_perf(int cpunum, u64 *nominal_perf) { return -ENOTSUPP; } @@ -2583,7 +1114,7 @@ index 6126c977ece0..c0b69ffe7bdb 100644 { return -ENOTSUPP; diff --git a/include/linux/amd-pstate.h b/include/linux/amd-pstate.h -index 6ad02ad9c7b4..d21838835abd 100644 +index 6ad02ad9c7b4..e89cf1249715 100644 --- a/include/linux/amd-pstate.h +++ b/include/linux/amd-pstate.h @@ -39,11 +39,16 @@ struct amd_aperf_mperf { @@ -2603,17 +1134,18 @@ index 6ad02ad9c7b4..d21838835abd 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 -@@ -52,6 +57,9 @@ struct amd_aperf_mperf { +@@ -51,7 +56,9 @@ struct amd_aperf_mperf { + * @cur: Difference of Aperf/Mperf/tsc count between last and current sample * @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 +- * @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 { +@@ -70,6 +77,7 @@ struct amd_cpudata { u32 nominal_perf; u32 lowest_nonlinear_perf; u32 lowest_perf; @@ -2621,51 +1153,64 @@ index 6ad02ad9c7b4..d21838835abd 100644 u32 min_limit_perf; u32 max_limit_perf; u32 min_limit_freq; -@@ -85,6 +94,7 @@ struct amd_cpudata { +@@ -79,12 +87,13 @@ struct amd_cpudata { + u32 min_freq; + u32 nominal_freq; + u32 lowest_nonlinear_freq; ++ u32 lowest_freq; + + struct amd_aperf_mperf cur; + struct amd_aperf_mperf prev; u64 freq; - bool boost_supported; +- bool boost_supported; + bool hw_prefcore; /* EPP feature related attributes*/ s16 epp_policy; +@@ -114,4 +123,23 @@ static const char * const amd_pstate_mode_string[] = { + [AMD_PSTATE_GUIDED] = "guided", + NULL, + }; ++ ++struct quirk_entry { ++ u32 nominal_freq; ++ u32 lowest_freq; ++}; ++ ++/** ++ * struct amd_pstate_global_params - Global parameters, mostly tunable via sysfs. ++ * @cpb_boost: Whether or not to use boost CPU P-states. ++ * @cpb_supported: Whether or not CPU boost P-states are available ++ * based on the MSR_K7_HWCR bit[25] state ++ */ ++struct amd_pstate_global_params { ++ bool cpb_boost; ++ bool cpb_supported; ++}; ++ ++extern struct amd_pstate_global_params amd_pstate_global_params; ++ + #endif /* _LINUX_AMD_PSTATE_H */ diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h -index 71d186d6933a..1cc1241fb698 100644 +index afda5f24d3dd..9bebeec24abb 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h -@@ -235,6 +235,7 @@ int cpufreq_get_policy(struct cpufreq_policy *policy, unsigned int cpu); - 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); -@@ -263,6 +264,7 @@ static inline bool cpufreq_supports_freq_invariance(void) +@@ -263,6 +263,7 @@ static inline bool cpufreq_supports_freq_invariance(void) return false; } static inline void disable_cpufreq(void) { } -+static inline void cpufreq_update_highest_perf(unsigned int cpu) { } ++static inline void cpufreq_update_limits(unsigned int cpu) { } #endif #ifdef CONFIG_CPU_FREQ_STAT -@@ -380,6 +382,9 @@ struct cpufreq_driver { - /* 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); - -- -2.43.0 +2.44.0 -From 619757e1b70db8bd3163db65258ceb0c20289699 Mon Sep 17 00:00:00 2001 +From 93aefd5f98b793e9447e64dcbaa69221102e304a Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Wed, 29 Nov 2023 19:55:38 +0100 -Subject: [PATCH 3/7] bbr3 +Date: Mon, 26 Feb 2024 15:46:58 +0100 +Subject: [PATCH 2/7] bbr3 Signed-off-by: Peter Jung --- @@ -2676,21 +1221,22 @@ Signed-off-by: Peter Jung include/uapi/linux/rtnetlink.h | 4 +- include/uapi/linux/tcp.h | 1 + net/ipv4/Kconfig | 21 +- + net/ipv4/bpf_tcp_ca.c | 9 +- net/ipv4/tcp.c | 3 + - net/ipv4/tcp_bbr.c | 2231 +++++++++++++++++++++------- + net/ipv4/tcp_bbr.c | 2230 +++++++++++++++++++++------- net/ipv4/tcp_cong.c | 1 + net/ipv4/tcp_input.c | 40 +- net/ipv4/tcp_minisocks.c | 2 + net/ipv4/tcp_output.c | 48 +- net/ipv4/tcp_rate.c | 30 +- net/ipv4/tcp_timer.c | 1 + - 15 files changed, 1934 insertions(+), 551 deletions(-) + 16 files changed, 1940 insertions(+), 553 deletions(-) diff --git a/include/linux/tcp.h b/include/linux/tcp.h -index 3c5efeeb024f..a0d4afd221d8 100644 +index a1c47a6d69b0..9e63e5580dc5 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h -@@ -257,7 +257,9 @@ struct tcp_sock { +@@ -369,7 +369,9 @@ struct tcp_sock { u8 compressed_ack; u8 dup_ack_counter:2, tlp_retrans:1, /* TLP is a retransmission */ @@ -2698,14 +1244,14 @@ index 3c5efeeb024f..a0d4afd221d8 100644 + 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 */ + u8 thin_lto : 1,/* Use linear timeouts for thin streams */ + recvmsg_inq : 1,/* Indicate # of bytes in queue upon recvmsg */ + fastopen_connect:1, /* FASTOPEN_CONNECT sockopt */ diff --git a/include/net/inet_connection_sock.h b/include/net/inet_connection_sock.h -index 5d2fcc137b88..3f7d429f73e5 100644 +index 9ab4bf704e86..f681cfdb2164 100644 --- a/include/net/inet_connection_sock.h +++ b/include/net/inet_connection_sock.h -@@ -135,8 +135,8 @@ struct inet_connection_sock { +@@ -137,8 +137,8 @@ struct inet_connection_sock { u32 icsk_probes_tstamp; u32 icsk_user_timeout; @@ -2717,10 +1263,10 @@ index 5d2fcc137b88..3f7d429f73e5 100644 #define ICSK_TIME_RETRANS 1 /* Retransmit timer */ diff --git a/include/net/tcp.h b/include/net/tcp.h -index 0239e815edf7..877fc1079435 100644 +index f6eba9652d01..3998a5f145ad 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h -@@ -372,6 +372,8 @@ static inline void tcp_dec_quickack_mode(struct sock *sk) +@@ -381,6 +381,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 @@ -2729,9 +1275,9 @@ index 0239e815edf7..877fc1079435 100644 enum tcp_tw_status { TCP_TW_SUCCESS = 0, -@@ -723,6 +725,15 @@ static inline void tcp_fast_path_check(struct sock *sk) - tcp_fast_path_on(tp); - } +@@ -737,6 +739,15 @@ static inline void tcp_fast_path_check(struct sock *sk) + + u32 tcp_delack_max(const struct sock *sk); +static inline void tcp_set_ecn_low_from_dst(struct sock *sk, + const struct dst_entry *dst) @@ -2743,9 +1289,9 @@ index 0239e815edf7..877fc1079435 100644 +} + /* Compute the actual rto_min value */ - static inline u32 tcp_rto_min(struct sock *sk) + static inline u32 tcp_rto_min(const struct sock *sk) { -@@ -819,6 +830,11 @@ static inline u32 tcp_stamp_us_delta(u64 t1, u64 t0) +@@ -842,6 +853,11 @@ static inline u32 tcp_stamp_us_delta(u64 t1, u64 t0) return max_t(s64, t1 - t0, 0); } @@ -2754,10 +1300,10 @@ index 0239e815edf7..877fc1079435 100644 + return max_t(s32, t1 - t0, 0); +} + - static inline u32 tcp_skb_timestamp(const struct sk_buff *skb) + /* provide the departure time in us unit */ + static inline u64 tcp_skb_timestamp_us(const struct sk_buff *skb) { - return tcp_ns_to_ts(skb->skb_mstamp_ns); -@@ -894,9 +910,14 @@ struct tcp_skb_cb { +@@ -930,9 +946,14 @@ struct tcp_skb_cb { /* pkts S/ACKed so far upon tx of skb, incl retrans: */ __u32 delivered; /* start of send pipeline phase */ @@ -2774,7 +1320,7 @@ index 0239e815edf7..877fc1079435 100644 } tx; /* only used for outgoing skbs */ union { struct inet_skb_parm h4; -@@ -1000,6 +1021,7 @@ enum tcp_ca_event { +@@ -1036,6 +1057,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 */ @@ -2782,7 +1328,7 @@ index 0239e815edf7..877fc1079435 100644 }; /* Information about inbound ACK, passed to cong_ops->in_ack_event() */ -@@ -1022,7 +1044,11 @@ enum tcp_ca_ack_event_flags { +@@ -1058,7 +1080,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 @@ -2795,7 +1341,7 @@ index 0239e815edf7..877fc1079435 100644 union tcp_cc_info; -@@ -1042,10 +1068,13 @@ struct ack_sample { +@@ -1078,10 +1104,13 @@ struct ack_sample { */ struct rate_sample { u64 prior_mstamp; /* starting timestamp for interval */ @@ -2810,7 +1356,7 @@ index 0239e815edf7..877fc1079435 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 */ -@@ -1056,7 +1085,9 @@ struct rate_sample { +@@ -1092,7 +1121,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? */ @@ -2820,7 +1366,7 @@ index 0239e815edf7..877fc1079435 100644 }; struct tcp_congestion_ops { -@@ -1080,8 +1111,11 @@ struct tcp_congestion_ops { +@@ -1116,8 +1147,11 @@ struct tcp_congestion_ops { /* hook for packet ack accounting (optional) */ void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample); @@ -2834,7 +1380,7 @@ index 0239e815edf7..877fc1079435 100644 /* call when packets are delivered to update cwnd and pacing rate, * after all the ca_state processing. (optional) -@@ -1147,6 +1181,14 @@ static inline char *tcp_ca_get_name_by_key(u32 key, char *buffer) +@@ -1183,6 +1217,14 @@ static inline char *tcp_ca_get_name_by_key(u32 key, char *buffer) } #endif @@ -2849,7 +1395,7 @@ index 0239e815edf7..877fc1079435 100644 static inline bool tcp_ca_needs_ecn(const struct sock *sk) { const struct inet_connection_sock *icsk = inet_csk(sk); -@@ -1166,6 +1208,7 @@ static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) +@@ -1202,6 +1244,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 */ @@ -2857,7 +1403,7 @@ index 0239e815edf7..877fc1079435 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); -@@ -1178,6 +1221,21 @@ static inline bool tcp_skb_sent_after(u64 t1, u64 t2, u32 seq1, u32 seq2) +@@ -1214,6 +1257,21 @@ static inline bool tcp_skb_sent_after(u64 t1, u64 t2, u32 seq1, u32 seq2) return t1 > t2 || (t1 == t2 && after(seq1, seq2)); } @@ -2879,7 +1425,7 @@ index 0239e815edf7..877fc1079435 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. -@@ -2203,7 +2261,7 @@ struct tcp_plb_state { +@@ -2373,7 +2431,7 @@ struct tcp_plb_state { u8 consec_cong_rounds:5, /* consecutive congested rounds */ unused:3; u32 pause_until; /* jiffies32 when PLB can resume rerouting */ @@ -2923,36 +1469,39 @@ index 50655de04c9b..82f8bd8f0d16 100644 union tcp_cc_info { diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h -index 51c13cf9c5ae..de8dcba26bec 100644 +index 3b687d20c9ed..a7c30c243b54 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h -@@ -506,9 +506,11 @@ enum { - #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) +@@ -507,12 +507,14 @@ enum { + #define RTAX_FEATURE_TIMESTAMP (1 << 2) /* unused */ + #define RTAX_FEATURE_ALLFRAG (1 << 3) /* unused */ + #define RTAX_FEATURE_TCP_USEC_TS (1 << 4) ++#define RTAX_FEATURE_ECN_LOW (1 << 5) - #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) + #define RTAX_FEATURE_MASK (RTAX_FEATURE_ECN | \ + RTAX_FEATURE_SACK | \ + RTAX_FEATURE_TIMESTAMP | \ + RTAX_FEATURE_ALLFRAG | \ +- RTAX_FEATURE_TCP_USEC_TS) ++ RTAX_FEATURE_TCP_USEC_TS | \ ++ RTAX_FEATURE_ECN_LOW) struct rta_session { __u8 proto; diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h -index 879eeb0a084b..77270053a5e3 100644 +index c07e9f90c084..5c88336ced60 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h -@@ -170,6 +170,7 @@ enum tcp_fastopen_client_fail { - #define TCPI_OPT_ECN 8 /* ECN was negociated at TCP session init */ +@@ -176,6 +176,7 @@ enum tcp_fastopen_client_fail { #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 */ + #define TCPI_OPT_USEC_TS 64 /* usec timestamps */ ++#define TCPI_OPT_ECN_LOW 128 /* Low-latency ECN configured at init */ /* * Sender's congestion state indicating normal or abnormal situations diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig -index 2dfb12230f08..2e14db3bee70 100644 +index 8e94ed7c56a0..50dc9970cad2 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -668,15 +668,18 @@ config TCP_CONG_BBR @@ -2983,11 +1532,42 @@ index 2dfb12230f08..2e14db3bee70 100644 choice prompt "Default TCP congestion control" +diff --git a/net/ipv4/bpf_tcp_ca.c b/net/ipv4/bpf_tcp_ca.c +index ae8b15e6896f..beb040e80b6f 100644 +--- a/net/ipv4/bpf_tcp_ca.c ++++ b/net/ipv4/bpf_tcp_ca.c +@@ -296,11 +296,15 @@ static void bpf_tcp_ca_pkts_acked(struct sock *sk, const struct ack_sample *samp + { + } + +-static u32 bpf_tcp_ca_min_tso_segs(struct sock *sk) ++static u32 bpf_tcp_ca_tso_segs(struct sock *sk, unsigned int mss_now) + { + return 0; + } + ++static void bpf_tcp_ca_skb_marked_lost(struct sock *sk, const struct sk_buff *skb) ++{ ++} ++ + static void bpf_tcp_ca_cong_control(struct sock *sk, const struct rate_sample *rs) + { + } +@@ -330,7 +334,8 @@ static struct tcp_congestion_ops __bpf_ops_tcp_congestion_ops = { + .cwnd_event = bpf_tcp_ca_cwnd_event, + .in_ack_event = bpf_tcp_ca_in_ack_event, + .pkts_acked = bpf_tcp_ca_pkts_acked, +- .min_tso_segs = bpf_tcp_ca_min_tso_segs, ++ .tso_segs = bpf_tcp_ca_tso_segs, ++ .skb_marked_lost = bpf_tcp_ca_skb_marked_lost, + .cong_control = bpf_tcp_ca_cong_control, + .undo_cwnd = bpf_tcp_ca_undo_cwnd, + .sndbuf_expand = bpf_tcp_ca_sndbuf_expand, diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c -index 3d3a24f79573..f9c1ca064b50 100644 +index c82dc42f57c6..1bc25bc01a8d 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c -@@ -3079,6 +3079,7 @@ int tcp_disconnect(struct sock *sk, int flags) +@@ -3089,6 +3089,7 @@ int tcp_disconnect(struct sock *sk, int flags) tp->rx_opt.dsack = 0; tp->rx_opt.num_sacks = 0; tp->rcv_ooopack = 0; @@ -2995,7 +1575,7 @@ index 3d3a24f79573..f9c1ca064b50 100644 /* Clean up fastopen related fields */ -@@ -3754,6 +3755,8 @@ void tcp_get_info(struct sock *sk, struct tcp_info *info) +@@ -3815,6 +3816,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; @@ -3003,9 +1583,9 @@ index 3d3a24f79573..f9c1ca064b50 100644 + info->tcpi_options |= TCPI_OPT_ECN_LOW; if (tp->syn_data_acked) info->tcpi_options |= TCPI_OPT_SYN_DATA; - + if (tp->tcp_usec_ts) diff --git a/net/ipv4/tcp_bbr.c b/net/ipv4/tcp_bbr.c -index 146792cd26fe..f4f477a69917 100644 +index 22358032dd48..cd6bef71bf4c 100644 --- a/net/ipv4/tcp_bbr.c +++ b/net/ipv4/tcp_bbr.c @@ -1,18 +1,19 @@ @@ -3432,7 +2012,7 @@ index 146792cd26fe..f4f477a69917 100644 - 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); + rate = min_t(u64, rate, READ_ONCE(sk->sk_max_pacing_rate)); return rate; } @@ -3441,18 +2021,17 @@ index 146792cd26fe..f4f477a69917 100644 static void bbr_init_pacing_rate_from_rtt(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); -@@ -278,7 +455,8 @@ static void bbr_init_pacing_rate_from_rtt(struct sock *sk) - } +@@ -279,7 +456,7 @@ static void bbr_init_pacing_rate_from_rtt(struct sock *sk) 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)); + WRITE_ONCE(sk->sk_pacing_rate, +- bbr_bw_to_pacing_rate(sk, bw, bbr_high_gain)); ++ 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 @@ static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain) - sk->sk_pacing_rate = rate; +@@ -295,26 +472,48 @@ static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain) + WRITE_ONCE(sk->sk_pacing_rate, rate); } -/* override sysctl_tcp_min_tso_segs */ @@ -3469,7 +2048,7 @@ index 146792cd26fe..f4f477a69917 100644 + u64 bytes; + + /* Budget a TSO/GSO burst size allowance based on bw (pacing_rate). */ -+ bytes = sk->sk_pacing_rate >> sk->sk_pacing_shift; ++ bytes = READ_ONCE(sk->sk_pacing_rate) >> READ_ONCE(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. @@ -3490,7 +2069,7 @@ index 146792cd26fe..f4f477a69917 100644 +/* 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 READ_ONCE(sk->sk_pacing_rate) < (bbr_min_tso_rate >> 3) ? 1 : 2; + return bbr_tso_segs_generic(sk, mss_now, sk->sk_gso_max_size); } @@ -3504,16 +2083,16 @@ index 146792cd26fe..f4f477a69917 100644 - * driver provided sk_gso_max_size. - */ - bytes = min_t(unsigned long, -- sk->sk_pacing_rate >> READ_ONCE(sk->sk_pacing_shift), +- READ_ONCE(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); ++ 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 @@ __bpf_kfunc static void bbr_cwnd_event(struct sock *sk, enum tcp_ca_event event) +@@ -334,7 +533,9 @@ __bpf_kfunc static void bbr_cwnd_event(struct sock *sk, enum tcp_ca_event event) struct tcp_sock *tp = tcp_sk(sk); struct bbr *bbr = inet_csk_ca(sk); @@ -3524,7 +2103,7 @@ index 146792cd26fe..f4f477a69917 100644 bbr->idle_restart = 1; bbr->ack_epoch_mstamp = tp->tcp_mstamp; bbr->ack_epoch_acked = 0; -@@ -344,6 +546,16 @@ __bpf_kfunc static void bbr_cwnd_event(struct sock *sk, enum tcp_ca_event event) +@@ -345,6 +546,16 @@ __bpf_kfunc static void bbr_cwnd_event(struct sock *sk, enum tcp_ca_event event) bbr_set_pacing_rate(sk, bbr_bw(sk), BBR_UNIT); else if (bbr->mode == BBR_PROBE_RTT) bbr_check_probe_rtt_done(sk); @@ -3541,7 +2120,7 @@ index 146792cd26fe..f4f477a69917 100644 } } -@@ -366,10 +578,10 @@ static u32 bbr_bdp(struct sock *sk, u32 bw, int gain) +@@ -367,10 +578,10 @@ static u32 bbr_bdp(struct sock *sk, u32 bw, int gain) * 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 @@ -3554,7 +2133,7 @@ index 146792cd26fe..f4f477a69917 100644 w = (u64)bw * bbr->min_rtt_us; -@@ -386,23 +598,23 @@ static u32 bbr_bdp(struct sock *sk, u32 bw, int gain) +@@ -387,23 +598,23 @@ static u32 bbr_bdp(struct sock *sk, u32 bw, int gain) * - 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 @@ -3586,7 +2165,7 @@ index 146792cd26fe..f4f477a69917 100644 cwnd += 2; return cwnd; -@@ -457,10 +669,10 @@ static u32 bbr_ack_aggregation_cwnd(struct sock *sk) +@@ -458,10 +669,10 @@ static u32 bbr_ack_aggregation_cwnd(struct sock *sk) { u32 max_aggr_cwnd, aggr_cwnd = 0; @@ -3599,7 +2178,7 @@ index 146792cd26fe..f4f477a69917 100644 >> BBR_SCALE; aggr_cwnd = min(aggr_cwnd, max_aggr_cwnd); } -@@ -468,66 +680,27 @@ static u32 bbr_ack_aggregation_cwnd(struct sock *sk) +@@ -469,66 +680,27 @@ static u32 bbr_ack_aggregation_cwnd(struct sock *sk) return aggr_cwnd; } @@ -3673,7 +2252,7 @@ index 146792cd26fe..f4f477a69917 100644 target_cwnd = bbr_bdp(sk, bw, gain); /* Increment the cwnd to account for excess ACKed data that seems -@@ -536,74 +709,26 @@ static void bbr_set_cwnd(struct sock *sk, const struct rate_sample *rs, +@@ -537,74 +709,26 @@ static void bbr_set_cwnd(struct sock *sk, const struct rate_sample *rs, target_cwnd += bbr_ack_aggregation_cwnd(sk); target_cwnd = bbr_quantization_budget(sk, target_cwnd); @@ -3765,7 +2344,7 @@ index 146792cd26fe..f4f477a69917 100644 } static void bbr_reset_startup_mode(struct sock *sk) -@@ -613,191 +738,49 @@ static void bbr_reset_startup_mode(struct sock *sk) +@@ -614,191 +738,49 @@ static void bbr_reset_startup_mode(struct sock *sk) bbr->mode = BBR_STARTUP; } @@ -3982,7 +2561,7 @@ index 146792cd26fe..f4f477a69917 100644 } /* Estimates the windowed max degree of ack aggregation. -@@ -811,7 +794,7 @@ static void bbr_update_bw(struct sock *sk, const struct rate_sample *rs) +@@ -812,7 +794,7 @@ static void bbr_update_bw(struct sock *sk, const struct rate_sample *rs) * * 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 @@ -3991,7 +2570,7 @@ index 146792cd26fe..f4f477a69917 100644 */ static void bbr_update_ack_aggregation(struct sock *sk, const struct rate_sample *rs) -@@ -819,15 +802,19 @@ static void bbr_update_ack_aggregation(struct sock *sk, +@@ -820,15 +802,19 @@ static void bbr_update_ack_aggregation(struct sock *sk, u32 epoch_us, expected_acked, extra_acked; struct bbr *bbr = inet_csk_ca(sk); struct tcp_sock *tp = tcp_sk(sk); @@ -4013,7 +2592,7 @@ index 146792cd26fe..f4f477a69917 100644 bbr->extra_acked_win_rtts = 0; bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? 0 : 1; -@@ -861,49 +848,6 @@ static void bbr_update_ack_aggregation(struct sock *sk, +@@ -862,49 +848,6 @@ static void bbr_update_ack_aggregation(struct sock *sk, bbr->extra_acked[bbr->extra_acked_win_idx] = extra_acked; } @@ -4063,7 +2642,7 @@ index 146792cd26fe..f4f477a69917 100644 static void bbr_check_probe_rtt_done(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); -@@ -913,9 +857,9 @@ static void bbr_check_probe_rtt_done(struct sock *sk) +@@ -914,9 +857,9 @@ static void bbr_check_probe_rtt_done(struct sock *sk) after(tcp_jiffies32, bbr->probe_rtt_done_stamp))) return; @@ -4075,7 +2654,7 @@ index 146792cd26fe..f4f477a69917 100644 } /* The goal of PROBE_RTT mode is to have BBR flows cooperatively and -@@ -941,23 +885,35 @@ static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs) +@@ -942,23 +885,35 @@ static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs) { struct tcp_sock *tp = tcp_sk(sk); struct bbr *bbr = inet_csk_ca(sk); @@ -4120,7 +2699,7 @@ index 146792cd26fe..f4f477a69917 100644 } if (bbr->mode == BBR_PROBE_RTT) { -@@ -966,9 +922,9 @@ static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs) +@@ -967,9 +922,9 @@ static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs) (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 && @@ -4132,7 +2711,7 @@ index 146792cd26fe..f4f477a69917 100644 bbr->probe_rtt_round_done = 0; bbr->next_rtt_delivered = tp->delivered; } else if (bbr->probe_rtt_done_stamp) { -@@ -989,18 +945,20 @@ static void bbr_update_gains(struct sock *sk) +@@ -990,18 +945,20 @@ static void bbr_update_gains(struct sock *sk) switch (bbr->mode) { case BBR_STARTUP: @@ -4161,7 +2740,7 @@ index 146792cd26fe..f4f477a69917 100644 break; case BBR_PROBE_RTT: bbr->pacing_gain = BBR_UNIT; -@@ -1012,144 +970,1387 @@ static void bbr_update_gains(struct sock *sk) +@@ -1013,144 +970,1387 @@ static void bbr_update_gains(struct sock *sk) } } @@ -4185,9 +2764,9 @@ index 146792cd26fe..f4f477a69917 100644 { struct bbr *bbr = inet_csk_ca(sk); - u32 bw; - -- bbr_update_model(sk, rs); - +- bbr_update_model(sk, rs); + - 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); @@ -4374,9 +2953,9 @@ index 146792cd26fe..f4f477a69917 100644 + +/* 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); ++ struct bbr *bbr = inet_csk_ca(sk); + u32 growth_this_round, cnt; + + /* Calculate "slope": packets S/Acked per inflight_hi increment. */ @@ -4421,8 +3000,7 @@ index 146792cd26fe..f4f477a69917 100644 +{ + const struct bbr *bbr = inet_csk_ca(sk); + u32 loss_thresh, ecn_thresh; - -- bbr->full_bw = 0; /* spurious slow-down; reset full pipe detection */ ++ + if (rs->lost > 0 && rs->tx_in_flight) { + loss_thresh = (u64)rs->tx_in_flight * bbr_param(sk, loss_thresh) >> + BBR_SCALE; @@ -4560,7 +3138,7 @@ index 146792cd26fe..f4f477a69917 100644 +} + +/* How should we multiplicatively cut bw or inflight limits based on ECN? */ -+u32 bbr_ecn_cut(struct sock *sk) ++static u32 bbr_ecn_cut(struct sock *sk) +{ + struct bbr *bbr = inet_csk_ca(sk); + @@ -4722,14 +3300,15 @@ index 146792cd26fe..f4f477a69917 100644 + */ +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); + { + 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; -+ + +- bbr->full_bw = 0; /* spurious slow-down; reset full pipe detection */ + if (!rs->is_app_limited || bw >= bbr_max_bw(sk)) + bbr_take_max_bw_sample(sk, bw); + @@ -5281,7 +3860,7 @@ index 146792cd26fe..f4f477a69917 100644 + return false; +} + -+__bpf_kfunc void bbr_main(struct sock *sk, const struct rate_sample *rs) ++__bpf_kfunc static 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); @@ -5623,7 +4202,7 @@ index 146792cd26fe..f4f477a69917 100644 .get_info = bbr_get_info, .set_state = bbr_set_state, }; -@@ -1160,10 +2361,11 @@ BTF_SET8_START(tcp_bbr_check_kfunc_ids) +@@ -1161,10 +2361,11 @@ BTF_SET8_START(tcp_bbr_check_kfunc_ids) BTF_ID_FLAGS(func, bbr_init) BTF_ID_FLAGS(func, bbr_main) BTF_ID_FLAGS(func, bbr_sndbuf_expand) @@ -5636,7 +4215,7 @@ index 146792cd26fe..f4f477a69917 100644 BTF_ID_FLAGS(func, bbr_set_state) #endif #endif -@@ -1198,5 +2400,12 @@ MODULE_AUTHOR("Van Jacobson "); +@@ -1199,5 +2400,12 @@ MODULE_AUTHOR("Van Jacobson "); MODULE_AUTHOR("Neal Cardwell "); MODULE_AUTHOR("Yuchung Cheng "); MODULE_AUTHOR("Soheil Hassas Yeganeh "); @@ -5662,10 +4241,10 @@ index 1b34050a7538..66d40449b3f4 100644 icsk->icsk_ca_ops->init(sk); if (tcp_ca_needs_ecn(sk)) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c -index 1f9d1d445fb3..d64d0672b8c4 100644 +index df7b13f0e5e0..8415aa41524e 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c -@@ -371,7 +371,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) +@@ -364,7 +364,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: @@ -5674,7 +4253,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 tcp_ca_event(sk, CA_EVENT_ECN_IS_CE); if (!(tp->ecn_flags & TCP_ECN_DEMAND_CWR)) { -@@ -382,7 +382,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) +@@ -375,7 +375,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) tp->ecn_flags |= TCP_ECN_SEEN; break; default: @@ -5683,7 +4262,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 tcp_ca_event(sk, CA_EVENT_ECN_NO_CE); tp->ecn_flags |= TCP_ECN_SEEN; break; -@@ -1096,7 +1096,12 @@ static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb) +@@ -1112,7 +1112,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) { @@ -5696,7 +4275,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 } void tcp_mark_skb_lost(struct sock *sk, struct sk_buff *skb) -@@ -1477,6 +1482,17 @@ static bool tcp_shifted_skb(struct sock *sk, struct sk_buff *prev, +@@ -1493,6 +1498,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); @@ -5714,7 +4293,7 @@ index 1f9d1d445fb3..d64d0672b8c4 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 -@@ -3706,7 +3722,8 @@ static void tcp_replace_ts_recent(struct tcp_sock *tp, u32 seq) +@@ -3761,7 +3777,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 */ @@ -5724,7 +4303,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 { struct tcp_sock *tp = tcp_sk(sk); -@@ -3723,6 +3740,7 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) +@@ -3778,6 +3795,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() */ @@ -5732,7 +4311,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 tcp_init_cwnd_reduction(sk); tcp_set_ca_state(sk, TCP_CA_CWR); tcp_end_cwnd_reduction(sk); -@@ -3733,6 +3751,11 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) +@@ -3788,6 +3806,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; @@ -5744,7 +4323,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 } } -@@ -3837,6 +3860,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3896,6 +3919,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); @@ -5752,7 +4331,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 /* ts_recent update must be made after we are sure that the packet * is in window. -@@ -3911,7 +3935,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3970,7 +3994,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) @@ -5761,7 +4340,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 if (tcp_ack_is_dubious(sk, flag)) { if (!(flag & (FLAG_SND_UNA_ADVANCED | -@@ -3935,6 +3959,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3994,6 +4018,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); @@ -5769,7 +4348,7 @@ index 1f9d1d445fb3..d64d0672b8c4 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); -@@ -3954,7 +3979,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -4013,7 +4038,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) tcp_ack_probe(sk); if (tp->tlp_high_seq) @@ -5778,7 +4357,7 @@ index 1f9d1d445fb3..d64d0672b8c4 100644 return 1; old_ack: -@@ -5556,13 +5581,14 @@ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) +@@ -5664,13 +5689,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 && @@ -5796,10 +4375,10 @@ index 1f9d1d445fb3..d64d0672b8c4 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 b98d476f1594..ca5c89cc7dc8 100644 +index 9e85f2a0bddd..914a75bb0734 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c -@@ -439,6 +439,8 @@ void tcp_ca_openreq_child(struct sock *sk, const struct dst_entry *dst) +@@ -464,6 +464,8 @@ void tcp_ca_openreq_child(struct sock *sk, const struct dst_entry *dst) u32 ca_key = dst_metric(dst, RTAX_CC_ALGO); bool ca_got_dst = false; @@ -5809,7 +4388,7 @@ index b98d476f1594..ca5c89cc7dc8 100644 const struct tcp_congestion_ops *ca; diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c -index 9ccfdc825004..680a7eb6fccc 100644 +index e3167ad96567..08fe7a626be1 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -332,10 +332,9 @@ static void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) @@ -5844,7 +4423,7 @@ index 9ccfdc825004..680a7eb6fccc 100644 /* ACK or retransmitted segment: clear ECT|CE */ INET_ECN_dontxmit(sk); } -@@ -1546,7 +1549,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, +@@ -1593,7 +1596,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, { struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *buff; @@ -5853,7 +4432,7 @@ index 9ccfdc825004..680a7eb6fccc 100644 long limit; int nlen; u8 flags; -@@ -1621,6 +1624,30 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, +@@ -1668,6 +1671,30 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, if (diff) tcp_adjust_pcount(sk, skb, diff); @@ -5884,7 +4463,7 @@ index 9ccfdc825004..680a7eb6fccc 100644 } /* Link BUFF into the send queue. */ -@@ -1996,13 +2023,12 @@ static u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, +@@ -2025,13 +2052,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; @@ -5903,7 +4482,7 @@ index 9ccfdc825004..680a7eb6fccc 100644 return min_t(u32, tso_segs, sk->sk_gso_max_segs); } -@@ -2701,6 +2727,7 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, +@@ -2731,6 +2757,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); @@ -5911,7 +4490,7 @@ index 9ccfdc825004..680a7eb6fccc 100644 goto repair; /* Skip network transmission */ } -@@ -2914,6 +2941,7 @@ void tcp_send_loss_probe(struct sock *sk) +@@ -2944,6 +2971,7 @@ void tcp_send_loss_probe(struct sock *sk) if (WARN_ON(!skb || !tcp_skb_pcount(skb))) goto rearm_timer; @@ -6000,10 +4579,10 @@ index a8f6d9d06f2e..8737f2134648 100644 rs->interval_us = max(snd_us, ack_us); diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c -index 984ab4a0421e..037f54263aee 100644 +index d1ad20ce1c8c..ef74f33c7905 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c -@@ -653,6 +653,7 @@ void tcp_write_timer_handler(struct sock *sk) +@@ -678,6 +678,7 @@ void tcp_write_timer_handler(struct sock *sk) return; } @@ -6012,62 +4591,557 @@ index 984ab4a0421e..037f54263aee 100644 event = icsk->icsk_pending; -- -2.43.0 +2.44.0 -From 3fcc633f1079391c5ee4ffa122ea7f2e441c426d Mon Sep 17 00:00:00 2001 +From fb681aa9768aa30b3b17152a221868238394dd64 Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Wed, 29 Nov 2023 19:56:03 +0100 +Date: Mon, 26 Feb 2024 15:47:11 +0100 +Subject: [PATCH 3/7] block + +Signed-off-by: Peter Jung +--- + block/bfq-iosched.c | 120 ++++++++++++++++++++++++++++++++++++-------- + block/bfq-iosched.h | 16 +++++- + block/mq-deadline.c | 114 +++++++++++++++++++++++++++++++++-------- + 3 files changed, 205 insertions(+), 45 deletions(-) + +diff --git a/block/bfq-iosched.c b/block/bfq-iosched.c +index 3cce6de464a7..9bd57baa4b0b 100644 +--- a/block/bfq-iosched.c ++++ b/block/bfq-iosched.c +@@ -467,6 +467,21 @@ static struct bfq_io_cq *bfq_bic_lookup(struct request_queue *q) + return icq; + } + ++static struct bfq_io_cq *bfq_bic_try_lookup(struct request_queue *q) ++{ ++ if (!current->io_context) ++ return NULL; ++ if (spin_trylock_irq(&q->queue_lock)) { ++ struct bfq_io_cq *icq; ++ ++ icq = icq_to_bic(ioc_lookup_icq(q)); ++ spin_unlock_irq(&q->queue_lock); ++ return icq; ++ } ++ ++ return NULL; ++} ++ + /* + * Scheduler run of queue, if there are requests pending and no one in the + * driver that will restart queueing. +@@ -2454,10 +2469,21 @@ static bool bfq_bio_merge(struct request_queue *q, struct bio *bio, + * returned by bfq_bic_lookup does not go away before + * bfqd->lock is taken. + */ +- struct bfq_io_cq *bic = bfq_bic_lookup(q); ++ struct bfq_io_cq *bic = bfq_bic_try_lookup(q); + bool ret; + +- spin_lock_irq(&bfqd->lock); ++ /* ++ * bio merging is called for every bio queued, and it's very easy ++ * to run into contention because of that. If we fail getting ++ * the dd lock, just skip this merge attempt. For related IO, the ++ * plug will be the successful merging point. If we get here, we ++ * already failed doing the obvious merge. Chances of actually ++ * getting a merge off this path is a lot slimmer, so skipping an ++ * occassional lookup that will most likely not succeed anyway should ++ * not be a problem. ++ */ ++ if (!spin_trylock_irq(&bfqd->lock)) ++ return false; + + if (bic) { + /* +@@ -5148,6 +5174,10 @@ static bool bfq_has_work(struct blk_mq_hw_ctx *hctx) + { + struct bfq_data *bfqd = hctx->queue->elevator->elevator_data; + ++ if (!list_empty_careful(&bfqd->at_head) || ++ !list_empty_careful(&bfqd->at_tail)) ++ return true; ++ + /* + * Avoiding lock: a race on bfqd->queued should cause at + * most a call to dispatch for nothing +@@ -5297,15 +5327,61 @@ static inline void bfq_update_dispatch_stats(struct request_queue *q, + bool idle_timer_disabled) {} + #endif /* CONFIG_BFQ_CGROUP_DEBUG */ + ++static void bfq_insert_request(struct request_queue *q, struct request *rq, ++ blk_insert_t flags, struct list_head *free); ++ ++static void __bfq_do_insert(struct request_queue *q, blk_insert_t flags, ++ struct list_head *list, struct list_head *free) ++{ ++ while (!list_empty(list)) { ++ struct request *rq; ++ ++ rq = list_first_entry(list, struct request, queuelist); ++ list_del_init(&rq->queuelist); ++ bfq_insert_request(q, rq, flags, free); ++ } ++} ++ ++static void bfq_do_insert(struct request_queue *q, struct list_head *free) ++{ ++ struct bfq_data *bfqd = q->elevator->elevator_data; ++ LIST_HEAD(at_head); ++ LIST_HEAD(at_tail); ++ ++ spin_lock(&bfqd->insert_lock); ++ list_splice_init(&bfqd->at_head, &at_head); ++ list_splice_init(&bfqd->at_tail, &at_tail); ++ spin_unlock(&bfqd->insert_lock); ++ ++ __bfq_do_insert(q, BLK_MQ_INSERT_AT_HEAD, &at_head, free); ++ __bfq_do_insert(q, 0, &at_tail, free); ++} ++ + static struct request *bfq_dispatch_request(struct blk_mq_hw_ctx *hctx) + { +- struct bfq_data *bfqd = hctx->queue->elevator->elevator_data; ++ struct request_queue *q = hctx->queue; ++ struct bfq_data *bfqd = q->elevator->elevator_data; + struct request *rq; + struct bfq_queue *in_serv_queue; + bool waiting_rq, idle_timer_disabled = false; ++ LIST_HEAD(free); ++ ++ /* ++ * If someone else is already dispatching, skip this one. This will ++ * defer the next dispatch event to when something completes, and could ++ * potentially lower the queue depth for contended cases. ++ * ++ * See the logic in blk_mq_do_dispatch_sched(), which loops and ++ * retries if nothing is dispatched. ++ */ ++ if (test_bit(BFQ_DISPATCHING, &bfqd->run_state) || ++ test_and_set_bit_lock(BFQ_DISPATCHING, &bfqd->run_state)) ++ return NULL; + + spin_lock_irq(&bfqd->lock); + ++ bfq_do_insert(hctx->queue, &free); ++ + in_serv_queue = bfqd->in_service_queue; + waiting_rq = in_serv_queue && bfq_bfqq_wait_request(in_serv_queue); + +@@ -5315,7 +5391,9 @@ static struct request *bfq_dispatch_request(struct blk_mq_hw_ctx *hctx) + waiting_rq && !bfq_bfqq_wait_request(in_serv_queue); + } + ++ clear_bit_unlock(BFQ_DISPATCHING, &bfqd->run_state); + spin_unlock_irq(&bfqd->lock); ++ blk_mq_free_requests(&free); + bfq_update_dispatch_stats(hctx->queue, rq, + idle_timer_disabled ? in_serv_queue : NULL, + idle_timer_disabled); +@@ -6236,27 +6314,21 @@ static inline void bfq_update_insert_stats(struct request_queue *q, + + static struct bfq_queue *bfq_init_rq(struct request *rq); + +-static void bfq_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq, +- blk_insert_t flags) ++static void bfq_insert_request(struct request_queue *q, struct request *rq, ++ blk_insert_t flags, struct list_head *free) + { +- struct request_queue *q = hctx->queue; + struct bfq_data *bfqd = q->elevator->elevator_data; + struct bfq_queue *bfqq; + bool idle_timer_disabled = false; + blk_opf_t cmd_flags; +- LIST_HEAD(free); + + #ifdef CONFIG_BFQ_GROUP_IOSCHED + if (!cgroup_subsys_on_dfl(io_cgrp_subsys) && rq->bio) + bfqg_stats_update_legacy_io(q, rq); + #endif +- spin_lock_irq(&bfqd->lock); + bfqq = bfq_init_rq(rq); +- if (blk_mq_sched_try_insert_merge(q, rq, &free)) { +- spin_unlock_irq(&bfqd->lock); +- blk_mq_free_requests(&free); ++ if (blk_mq_sched_try_insert_merge(q, rq, free)) + return; +- } + + trace_block_rq_insert(rq); + +@@ -6286,8 +6358,6 @@ static void bfq_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq, + * merge). + */ + cmd_flags = rq->cmd_flags; +- spin_unlock_irq(&bfqd->lock); +- + bfq_update_insert_stats(q, bfqq, idle_timer_disabled, + cmd_flags); + } +@@ -6296,13 +6366,15 @@ static void bfq_insert_requests(struct blk_mq_hw_ctx *hctx, + struct list_head *list, + blk_insert_t flags) + { +- while (!list_empty(list)) { +- struct request *rq; ++ struct request_queue *q = hctx->queue; ++ struct bfq_data *bfqd = q->elevator->elevator_data; + +- rq = list_first_entry(list, struct request, queuelist); +- list_del_init(&rq->queuelist); +- bfq_insert_request(hctx, rq, flags); +- } ++ spin_lock_irq(&bfqd->insert_lock); ++ if (flags & BLK_MQ_INSERT_AT_HEAD) ++ list_splice_init(list, &bfqd->at_head); ++ else ++ list_splice_init(list, &bfqd->at_tail); ++ spin_unlock_irq(&bfqd->insert_lock); + } + + static void bfq_update_hw_tag(struct bfq_data *bfqd) +@@ -7211,6 +7283,12 @@ static int bfq_init_queue(struct request_queue *q, struct elevator_type *e) + q->elevator = eq; + spin_unlock_irq(&q->queue_lock); + ++ spin_lock_init(&bfqd->lock); ++ spin_lock_init(&bfqd->insert_lock); ++ ++ INIT_LIST_HEAD(&bfqd->at_head); ++ INIT_LIST_HEAD(&bfqd->at_tail); ++ + /* + * Our fallback bfqq if bfq_find_alloc_queue() runs into OOM issues. + * Grab a permanent reference to it, so that the normal code flow +@@ -7329,8 +7407,6 @@ static int bfq_init_queue(struct request_queue *q, struct elevator_type *e) + /* see comments on the definition of next field inside bfq_data */ + bfqd->actuator_load_threshold = 4; + +- spin_lock_init(&bfqd->lock); +- + /* + * The invocation of the next bfq_create_group_hierarchy + * function is the head of a chain of function calls +diff --git a/block/bfq-iosched.h b/block/bfq-iosched.h +index 467e8cfc41a2..f44f5d4ec2f4 100644 +--- a/block/bfq-iosched.h ++++ b/block/bfq-iosched.h +@@ -504,12 +504,26 @@ struct bfq_io_cq { + unsigned int requests; /* Number of requests this process has in flight */ + }; + ++enum { ++ BFQ_DISPATCHING = 0, ++}; ++ + /** + * struct bfq_data - per-device data structure. + * + * All the fields are protected by @lock. + */ + struct bfq_data { ++ struct { ++ spinlock_t lock; ++ spinlock_t insert_lock; ++ } ____cacheline_aligned_in_smp; ++ ++ unsigned long run_state; ++ ++ struct list_head at_head; ++ struct list_head at_tail; ++ + /* device request queue */ + struct request_queue *queue; + /* dispatch queue */ +@@ -795,8 +809,6 @@ struct bfq_data { + /* fallback dummy bfqq for extreme OOM conditions */ + struct bfq_queue oom_bfqq; + +- spinlock_t lock; +- + /* + * bic associated with the task issuing current bio for + * merging. This and the next field are used as a support to +diff --git a/block/mq-deadline.c b/block/mq-deadline.c +index f958e79277b8..1b0de4fc3958 100644 +--- a/block/mq-deadline.c ++++ b/block/mq-deadline.c +@@ -79,10 +79,24 @@ struct dd_per_prio { + struct io_stats_per_prio stats; + }; + ++enum { ++ DD_DISPATCHING = 0, ++}; ++ + struct deadline_data { + /* + * run time data + */ ++ struct { ++ spinlock_t lock; ++ spinlock_t insert_lock; ++ spinlock_t zone_lock; ++ } ____cacheline_aligned_in_smp; ++ ++ unsigned long run_state; ++ ++ struct list_head at_head; ++ struct list_head at_tail; + + struct dd_per_prio per_prio[DD_PRIO_COUNT]; + +@@ -100,9 +114,6 @@ struct deadline_data { + int front_merges; + u32 async_depth; + int prio_aging_expire; +- +- spinlock_t lock; +- spinlock_t zone_lock; + }; + + /* Maps an I/O priority class to a deadline scheduler priority. */ +@@ -113,6 +124,9 @@ static const enum dd_prio ioprio_class_to_prio[] = { + [IOPRIO_CLASS_IDLE] = DD_IDLE_PRIO, + }; + ++static void dd_insert_request(struct request_queue *q, struct request *rq, ++ blk_insert_t flags, struct list_head *free); ++ + static inline struct rb_root * + deadline_rb_root(struct dd_per_prio *per_prio, struct request *rq) + { +@@ -585,6 +599,33 @@ static struct request *dd_dispatch_prio_aged_requests(struct deadline_data *dd, + return NULL; + } + ++static void __dd_do_insert(struct request_queue *q, blk_insert_t flags, ++ struct list_head *list, struct list_head *free) ++{ ++ while (!list_empty(list)) { ++ struct request *rq; ++ ++ rq = list_first_entry(list, struct request, queuelist); ++ list_del_init(&rq->queuelist); ++ dd_insert_request(q, rq, flags, free); ++ } ++} ++ ++static void dd_do_insert(struct request_queue *q, struct list_head *free) ++{ ++ struct deadline_data *dd = q->elevator->elevator_data; ++ LIST_HEAD(at_head); ++ LIST_HEAD(at_tail); ++ ++ spin_lock(&dd->insert_lock); ++ list_splice_init(&dd->at_head, &at_head); ++ list_splice_init(&dd->at_tail, &at_tail); ++ spin_unlock(&dd->insert_lock); ++ ++ __dd_do_insert(q, BLK_MQ_INSERT_AT_HEAD, &at_head, free); ++ __dd_do_insert(q, 0, &at_tail, free); ++} ++ + /* + * Called from blk_mq_run_hw_queue() -> __blk_mq_sched_dispatch_requests(). + * +@@ -595,12 +636,27 @@ static struct request *dd_dispatch_prio_aged_requests(struct deadline_data *dd, + */ + static struct request *dd_dispatch_request(struct blk_mq_hw_ctx *hctx) + { +- struct deadline_data *dd = hctx->queue->elevator->elevator_data; ++ struct request_queue *q = hctx->queue; ++ struct deadline_data *dd = q->elevator->elevator_data; + const unsigned long now = jiffies; + struct request *rq; + enum dd_prio prio; ++ LIST_HEAD(free); ++ ++ /* ++ * If someone else is already dispatching, skip this one. This will ++ * defer the next dispatch event to when something completes, and could ++ * potentially lower the queue depth for contended cases. ++ * ++ * See the logic in blk_mq_do_dispatch_sched(), which loops and ++ * retries if nothing is dispatched. ++ */ ++ if (test_bit(DD_DISPATCHING, &dd->run_state) || ++ test_and_set_bit_lock(DD_DISPATCHING, &dd->run_state)) ++ return NULL; + + spin_lock(&dd->lock); ++ dd_do_insert(q, &free); + rq = dd_dispatch_prio_aged_requests(dd, now); + if (rq) + goto unlock; +@@ -616,8 +672,10 @@ static struct request *dd_dispatch_request(struct blk_mq_hw_ctx *hctx) + } + + unlock: ++ clear_bit_unlock(DD_DISPATCHING, &dd->run_state); + spin_unlock(&dd->lock); + ++ blk_mq_free_requests(&free); + return rq; + } + +@@ -706,6 +764,13 @@ static int dd_init_sched(struct request_queue *q, struct elevator_type *e) + + eq->elevator_data = dd; + ++ spin_lock_init(&dd->lock); ++ spin_lock_init(&dd->insert_lock); ++ spin_lock_init(&dd->zone_lock); ++ ++ INIT_LIST_HEAD(&dd->at_head); ++ INIT_LIST_HEAD(&dd->at_tail); ++ + for (prio = 0; prio <= DD_PRIO_MAX; prio++) { + struct dd_per_prio *per_prio = &dd->per_prio[prio]; + +@@ -722,8 +787,6 @@ static int dd_init_sched(struct request_queue *q, struct elevator_type *e) + dd->last_dir = DD_WRITE; + dd->fifo_batch = fifo_batch; + dd->prio_aging_expire = prio_aging_expire; +- spin_lock_init(&dd->lock); +- spin_lock_init(&dd->zone_lock); + + /* We dispatch from request queue wide instead of hw queue */ + blk_queue_flag_set(QUEUE_FLAG_SQ_SCHED, q); +@@ -779,7 +842,19 @@ static bool dd_bio_merge(struct request_queue *q, struct bio *bio, + struct request *free = NULL; + bool ret; + +- spin_lock(&dd->lock); ++ /* ++ * bio merging is called for every bio queued, and it's very easy ++ * to run into contention because of that. If we fail getting ++ * the dd lock, just skip this merge attempt. For related IO, the ++ * plug will be the successful merging point. If we get here, we ++ * already failed doing the obvious merge. Chances of actually ++ * getting a merge off this path is a lot slimmer, so skipping an ++ * occassional lookup that will most likely not succeed anyway should ++ * not be a problem. ++ */ ++ if (!spin_trylock(&dd->lock)) ++ return false; ++ + ret = blk_mq_sched_try_merge(q, bio, nr_segs, &free); + spin_unlock(&dd->lock); + +@@ -792,10 +867,9 @@ static bool dd_bio_merge(struct request_queue *q, struct bio *bio, + /* + * add rq to rbtree and fifo + */ +-static void dd_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq, ++static void dd_insert_request(struct request_queue *q, struct request *rq, + blk_insert_t flags, struct list_head *free) + { +- struct request_queue *q = hctx->queue; + struct deadline_data *dd = q->elevator->elevator_data; + const enum dd_data_dir data_dir = rq_data_dir(rq); + u16 ioprio = req_get_ioprio(rq); +@@ -867,19 +941,13 @@ static void dd_insert_requests(struct blk_mq_hw_ctx *hctx, + { + struct request_queue *q = hctx->queue; + struct deadline_data *dd = q->elevator->elevator_data; +- LIST_HEAD(free); + +- spin_lock(&dd->lock); +- while (!list_empty(list)) { +- struct request *rq; +- +- rq = list_first_entry(list, struct request, queuelist); +- list_del_init(&rq->queuelist); +- dd_insert_request(hctx, rq, flags, &free); +- } +- spin_unlock(&dd->lock); +- +- blk_mq_free_requests(&free); ++ spin_lock(&dd->insert_lock); ++ if (flags & BLK_MQ_INSERT_AT_HEAD) ++ list_splice_init(list, &dd->at_head); ++ else ++ list_splice_init(list, &dd->at_tail); ++ spin_unlock(&dd->insert_lock); + } + + /* Callback from inside blk_mq_rq_ctx_init(). */ +@@ -958,6 +1026,10 @@ static bool dd_has_work(struct blk_mq_hw_ctx *hctx) + struct deadline_data *dd = hctx->queue->elevator->elevator_data; + enum dd_prio prio; + ++ if (!list_empty_careful(&dd->at_head) || ++ !list_empty_careful(&dd->at_tail)) ++ return true; ++ + for (prio = 0; prio <= DD_PRIO_MAX; prio++) + if (dd_has_work_for_prio(&dd->per_prio[prio])) + return true; +-- +2.44.0 + +From 4f371ea8a1f8a47e624592a91f9e961080aec2eb Mon Sep 17 00:00:00 2001 +From: Peter Jung +Date: Mon, 26 Feb 2024 15:47:21 +0100 Subject: [PATCH 4/7] cachy Signed-off-by: Peter Jung --- - .../admin-guide/kernel-parameters.txt | 9 + - Makefile | 3 + - arch/x86/Kconfig.cpu | 427 +- - arch/x86/Makefile | 44 +- - arch/x86/include/asm/pci.h | 6 + - arch/x86/include/asm/vermagic.h | 74 + - arch/x86/pci/common.c | 7 +- - block/bfq-iosched.c | 6 + - drivers/Makefile | 15 +- - drivers/ata/ahci.c | 23 +- - drivers/cpufreq/Kconfig.x86 | 2 - - drivers/i2c/busses/Kconfig | 9 + - drivers/i2c/busses/Makefile | 1 + - drivers/i2c/busses/i2c-nct6775.c | 648 ++ - drivers/i2c/busses/i2c-piix4.c | 4 +- - drivers/md/dm-crypt.c | 5 + - drivers/pci/controller/Makefile | 6 + - drivers/pci/controller/intel-nvme-remap.c | 462 ++ - drivers/pci/quirks.c | 101 + - drivers/platform/x86/Kconfig | 24 + - drivers/platform/x86/Makefile | 4 + - drivers/platform/x86/legion-laptop.c | 5858 +++++++++++++++++ - drivers/platform/x86/steamdeck.c | 523 ++ - include/linux/mm.h | 2 +- - include/linux/pagemap.h | 2 +- - include/linux/user_namespace.h | 4 + - init/Kconfig | 26 + - kernel/Kconfig.hz | 24 + - kernel/fork.c | 14 + - kernel/sysctl.c | 12 + - kernel/user_namespace.c | 7 + - mm/Kconfig | 2 +- - mm/page-writeback.c | 8 + - mm/swap.c | 5 + - mm/vmpressure.c | 4 + - mm/vmscan.c | 8 + - 36 files changed, 8332 insertions(+), 47 deletions(-) + .../admin-guide/kernel-parameters.txt | 9 + + Makefile | 162 ++++- + arch/arm/Makefile | 56 +- + arch/x86/Kconfig.cpu | 426 +++++++++++- + arch/x86/Makefile | 19 +- + arch/x86/Makefile_32.cpu | 41 -- + arch/x86/include/asm/pci.h | 6 + + arch/x86/include/asm/vermagic.h | 74 ++ + arch/x86/pci/common.c | 7 +- + block/bfq-iosched.c | 6 + + block/elevator.c | 10 + + drivers/ata/ahci.c | 23 +- + drivers/cpufreq/Kconfig.x86 | 2 - + drivers/i2c/busses/Kconfig | 9 + + drivers/i2c/busses/Makefile | 1 + + drivers/i2c/busses/i2c-nct6775.c | 648 ++++++++++++++++++ + drivers/i2c/busses/i2c-piix4.c | 4 +- + drivers/input/evdev.c | 19 +- + drivers/md/dm-crypt.c | 5 + + drivers/pci/controller/Makefile | 6 + + drivers/pci/controller/intel-nvme-remap.c | 462 +++++++++++++ + drivers/pci/quirks.c | 101 +++ + drivers/platform/x86/Kconfig | 14 + + drivers/platform/x86/Makefile | 3 + + drivers/platform/x86/steamdeck.c | 523 ++++++++++++++ + include/linux/mm.h | 2 +- + include/linux/pagemap.h | 2 +- + include/linux/user_namespace.h | 4 + + init/Kconfig | 26 + + kernel/Kconfig.hz | 24 + + kernel/fork.c | 14 + + kernel/sched/fair.c | 13 + + kernel/sched/sched.h | 2 +- + kernel/sysctl.c | 12 + + kernel/user_namespace.c | 7 + + mm/Kconfig | 2 +- + mm/compaction.c | 4 + + mm/huge_memory.c | 4 + + mm/page-writeback.c | 8 + + mm/page_alloc.c | 27 +- + mm/swap.c | 5 + + mm/vmpressure.c | 4 + + mm/vmscan.c | 8 + + 43 files changed, 2639 insertions(+), 165 deletions(-) create mode 100644 drivers/i2c/busses/i2c-nct6775.c create mode 100644 drivers/pci/controller/intel-nvme-remap.c - create mode 100644 drivers/platform/x86/legion-laptop.c 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 6e121fdb68f9..0fba49520140 100644 +index a493d93e0d2c..8d6a2ce37f8f 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt -@@ -4292,6 +4292,15 @@ +@@ -4396,6 +4396,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. @@ -6084,10 +5158,165 @@ index 6e121fdb68f9..0fba49520140 100644 Safety option to keep boot IRQs enabled. This should never be necessary. diff --git a/Makefile b/Makefile -index ee4e504a3e78..5c6874a1bb37 100644 +index 95b320ada47c..0b7d42037c3e 100644 --- a/Makefile +++ b/Makefile -@@ -819,6 +819,9 @@ KBUILD_CFLAGS += -fno-delete-null-pointer-checks +@@ -808,9 +808,164 @@ endif # need-config + + KBUILD_CFLAGS += -fno-delete-null-pointer-checks + ++# This selects which ARM instruction set is used. ++arch-$(CONFIG_CPU_32v7M) :=-march=armv7-m ++arch-$(CONFIG_CPU_32v7) :=-march=armv7-a ++arch-$(CONFIG_CPU_32v6) :=-march=armv6 ++# Only override the compiler option if ARMv6. The ARMv6K extensions are ++# always available in ARMv7 ++ifeq ($(CONFIG_CPU_32v6),y) ++arch-$(CONFIG_CPU_32v6K) :=-march=armv6k ++endif ++arch-$(CONFIG_CPU_32v5) :=-march=armv5te ++arch-$(CONFIG_CPU_32v4T) :=-march=armv4t ++arch-$(CONFIG_CPU_32v4) :=-march=armv4 ++arch-$(CONFIG_CPU_32v3) :=-march=armv3m ++ ++# Note that GCC does not numerically define an architecture version ++# macro, but instead defines a whole series of macros which makes ++# testing for a specific architecture or later rather impossible. ++cpp-$(CONFIG_CPU_32v7M) :=-D__LINUX_ARM_ARCH__=7 ++cpp-$(CONFIG_CPU_32v7) :=-D__LINUX_ARM_ARCH__=7 ++cpp-$(CONFIG_CPU_32v6) :=-D__LINUX_ARM_ARCH__=6 ++# Only override the compiler option if ARMv6. The ARMv6K extensions are ++# always available in ARMv7 ++ifeq ($(CONFIG_CPU_32v6),y) ++cpp-$(CONFIG_CPU_32v6K) :=-D__LINUX_ARM_ARCH__=6 ++endif ++cpp-$(CONFIG_CPU_32v5) :=-D__LINUX_ARM_ARCH__=5 ++cpp-$(CONFIG_CPU_32v4T) :=-D__LINUX_ARM_ARCH__=4 ++cpp-$(CONFIG_CPU_32v4) :=-D__LINUX_ARM_ARCH__=4 ++cpp-$(CONFIG_CPU_32v3) :=-D__LINUX_ARM_ARCH__=3 ++ ++# This selects how we optimise for the processor. ++tune-$(CONFIG_CPU_ARM7TDMI) :=-mtune=arm7tdmi ++tune-$(CONFIG_CPU_ARM720T) :=-mtune=arm7tdmi ++tune-$(CONFIG_CPU_ARM740T) :=-mtune=arm7tdmi ++tune-$(CONFIG_CPU_ARM9TDMI) :=-mtune=arm9tdmi ++tune-$(CONFIG_CPU_ARM940T) :=-mtune=arm9tdmi ++tune-$(CONFIG_CPU_ARM946E) :=-mtune=arm9e ++tune-$(CONFIG_CPU_ARM920T) :=-mtune=arm9tdmi ++tune-$(CONFIG_CPU_ARM922T) :=-mtune=arm9tdmi ++tune-$(CONFIG_CPU_ARM925T) :=-mtune=arm9tdmi ++tune-$(CONFIG_CPU_ARM926T) :=-mtune=arm9tdmi ++tune-$(CONFIG_CPU_FA526) :=-mtune=arm9tdmi ++tune-$(CONFIG_CPU_SA110) :=-mtune=strongarm110 ++tune-$(CONFIG_CPU_SA1100) :=-mtune=strongarm1100 ++tune-$(CONFIG_CPU_XSCALE) :=-mtune=xscale ++tune-$(CONFIG_CPU_XSC3) :=-mtune=xscale ++tune-$(CONFIG_CPU_FEROCEON) :=-mtune=xscale ++tune-$(CONFIG_CPU_V6) :=-mtune=arm1136j-s ++tune-$(CONFIG_CPU_V6K) :=-mtune=arm1136j-s ++ ++KBUILD_CPPFLAGS +=$(cpp-y) ++KBUILD_CFLAGS +=$(arch-y) $(tune-y) ++KBUILD_AFLAGS +=$(arch-y) $(tune-y) ++ ++# This selects which x86 instruction set is used. ++cflags-$(CONFIG_M486SX) += -march=i486 ++cflags-$(CONFIG_M486) += -march=i486 ++cflags-$(CONFIG_M586) += -march=i586 ++cflags-$(CONFIG_M586TSC) += -march=i586 ++cflags-$(CONFIG_M586MMX) += -march=pentium-mmx ++cflags-$(CONFIG_M686) += -march=i686 ++cflags-$(CONFIG_MPENTIUMII) += -march=i686 $(call tune,pentium2) ++cflags-$(CONFIG_MPENTIUMIII) += -march=i686 $(call tune,pentium3) ++cflags-$(CONFIG_MPENTIUMM) += -march=i686 $(call tune,pentium3) ++cflags-$(CONFIG_MPENTIUM4) += -march=i686 $(call tune,pentium4) ++cflags-$(CONFIG_MK6) += -march=k6 ++# Please note, that patches that add -march=athlon-xp and friends are pointless. ++# They make zero difference whatsosever to performance at this time. ++cflags-$(CONFIG_MK7) += -march=athlon ++cflags-$(CONFIG_MK8) += $(call cc-option,-march=k8,-march=athlon) ++cflags-$(CONFIG_MCRUSOE) += -march=i686 $(align) ++cflags-$(CONFIG_MEFFICEON) += -march=i686 $(call tune,pentium3) $(align) ++cflags-$(CONFIG_MWINCHIPC6) += $(call cc-option,-march=winchip-c6,-march=i586) ++cflags-$(CONFIG_MWINCHIP3D) += $(call cc-option,-march=winchip2,-march=i586) ++cflags-$(CONFIG_MCYRIXIII) += $(call cc-option,-march=c3,-march=i486) $(align) ++cflags-$(CONFIG_MVIAC3_2) += $(call cc-option,-march=c3-2,-march=i686) ++cflags-$(CONFIG_MVIAC7) += -march=i686 ++cflags-$(CONFIG_MCORE2) += -march=i686 $(call tune,core2) ++cflags-$(CONFIG_MATOM) += $(call cc-option,-march=atom,$(call cc-option,-march=core2,-march=i686)) \ ++$(call cc-option,-mtune=atom,$(call cc-option,-mtune=generic)) ++ ++# AMD Elan support ++cflags-$(CONFIG_MELAN) += -march=i486 ++ ++# Geode GX1 support ++cflags-$(CONFIG_MGEODEGX1) += -march=pentium-mmx ++cflags-$(CONFIG_MGEODE_LX) += $(call cc-option,-march=geode,-march=pentium-mmx) ++# add at the end to overwrite eventual tuning options from earlier ++# cpu entries ++cflags-$(CONFIG_X86_GENERIC) += $(call tune,generic,$(call tune,i686)) ++ ++# Bug fix for binutils: this option is required in order to keep ++# binutils from generating NOPL instructions against our will. ++ifneq ($(CONFIG_X86_P6_NOP),y) ++cflags-y += $(call cc-option,-Wa$(comma)-mtune=generic32,) ++endif ++ ++# x86_64 instruction set ++cflags64-$(CONFIG_MK8) += -march=k8 ++cflags64-$(CONFIG_MPSC) += -march=nocona ++cflags64-$(CONFIG_MK8SSE3) += -march=k8-sse3 ++cflags64-$(CONFIG_MK10) += -march=amdfam10 ++cflags64-$(CONFIG_MBARCELONA) += -march=barcelona ++cflags64-$(CONFIG_MBOBCAT) += -march=btver1 ++cflags64-$(CONFIG_MJAGUAR) += -march=btver2 ++cflags64-$(CONFIG_MBULLDOZER) += -march=bdver1 ++cflags64-$(CONFIG_MPILEDRIVER) += -march=bdver2 -mno-tbm ++cflags64-$(CONFIG_MSTEAMROLLER) += -march=bdver3 -mno-tbm ++cflags64-$(CONFIG_MEXCAVATOR) += -march=bdver4 -mno-tbm ++cflags64-$(CONFIG_MZEN) += -march=znver1 ++cflags64-$(CONFIG_MZEN2) += -march=znver2 ++cflags64-$(CONFIG_MZEN3) += -march=znver3 ++cflags64-$(CONFIG_MZEN4) += -march=znver4 ++cflags64-$(CONFIG_MNATIVE_INTEL) += -march=native ++cflags64-$(CONFIG_MNATIVE_AMD) += -march=native ++cflags64-$(CONFIG_MATOM) += -march=bonnell ++cflags64-$(CONFIG_MCORE2) += -march=core2 ++cflags64-$(CONFIG_MNEHALEM) += -march=nehalem ++cflags64-$(CONFIG_MWESTMERE) += -march=westmere ++cflags64-$(CONFIG_MSILVERMONT) += -march=silvermont ++cflags64-$(CONFIG_MGOLDMONT) += -march=goldmont ++cflags64-$(CONFIG_MGOLDMONTPLUS) += -march=goldmont-plus ++cflags64-$(CONFIG_MSANDYBRIDGE) += -march=sandybridge ++cflags64-$(CONFIG_MIVYBRIDGE) += -march=ivybridge ++cflags64-$(CONFIG_MHASWELL) += -march=haswell ++cflags64-$(CONFIG_MBROADWELL) += -march=broadwell ++cflags64-$(CONFIG_MSKYLAKE) += -march=skylake ++cflags64-$(CONFIG_MSKYLAKEX) += -march=skylake-avx512 ++cflags64-$(CONFIG_MCANNONLAKE) += -march=cannonlake ++cflags64-$(CONFIG_MICELAKE) += -march=icelake-client ++cflags64-$(CONFIG_MCASCADELAKE) += -march=cascadelake ++cflags64-$(CONFIG_MCOOPERLAKE) += -march=cooperlake ++cflags64-$(CONFIG_MTIGERLAKE) += -march=tigerlake ++cflags64-$(CONFIG_MSAPPHIRERAPIDS) += -march=sapphirerapids ++cflags64-$(CONFIG_MROCKETLAKE) += -march=rocketlake ++cflags64-$(CONFIG_MALDERLAKE) += -march=alderlake ++cflags64-$(CONFIG_MRAPTORLAKE) += -march=raptorlake ++cflags64-$(CONFIG_MMETEORLAKE) += -march=meteorlake ++cflags64-$(CONFIG_MEMERALDRAPIDS) += -march=emeraldrapids ++cflags64-$(CONFIG_GENERIC_CPU2) += -march=x86-64-v2 ++cflags64-$(CONFIG_GENERIC_CPU3) += -march=x86-64-v3 ++cflags64-$(CONFIG_GENERIC_CPU4) += -march=x86-64-v4 ++cflags64-$(CONFIG_GENERIC_CPU) += -mtune=generic ++KBUILD_CFLAGS += $(cflags64-y) ++ ++rustflags64-$(CONFIG_MK8) += -Ctarget-cpu=k8 ++rustflags64-$(CONFIG_MPSC) += -Ctarget-cpu=nocona ++rustflags64-$(CONFIG_MCORE2) += -Ctarget-cpu=core2 ++rustflags64-$(CONFIG_MATOM) += -Ctarget-cpu=atom ++rustflags64-$(CONFIG_GENERIC_CPU) += -Ztune-cpu=generic ++KBUILD_RUSTFLAGS += $(rustflags64-y) ++ ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE KBUILD_CFLAGS += -O2 KBUILD_RUSTFLAGS += -Copt-level=2 @@ -6097,8 +5326,103 @@ index ee4e504a3e78..5c6874a1bb37 100644 else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE KBUILD_CFLAGS += -Os KBUILD_RUSTFLAGS += -Copt-level=s +@@ -990,15 +1145,18 @@ KBUILD_CFLAGS += $(call cc-option, -fstrict-flex-arrays=3) + KBUILD_CFLAGS-$(CONFIG_CC_NO_STRINGOP_OVERFLOW) += $(call cc-option, -Wno-stringop-overflow) + KBUILD_CFLAGS-$(CONFIG_CC_STRINGOP_OVERFLOW) += $(call cc-option, -Wstringop-overflow) + ++# disable GCC vectorization on trees ++KBUILD_CFLAGS += $(call cc-option, -fno-tree-vectorize) ++ + # disable invalid "can't wrap" optimizations for signed / pointers + KBUILD_CFLAGS += -fno-strict-overflow + + # Make sure -fstack-check isn't enabled (like gentoo apparently did) + KBUILD_CFLAGS += -fno-stack-check + +-# conserve stack if available ++# conserve stack, ivopts and modulo-sched if available + ifdef CONFIG_CC_IS_GCC +-KBUILD_CFLAGS += -fconserve-stack ++KBUILD_CFLAGS += -fconserve-stack -fivopts -fmodulo-sched + endif + + # change __FILE__ to the relative path from the srctree +diff --git a/arch/arm/Makefile b/arch/arm/Makefile +index 473280d5adce..c7596c898625 100644 +--- a/arch/arm/Makefile ++++ b/arch/arm/Makefile +@@ -59,56 +59,6 @@ endif + # + KBUILD_CFLAGS += $(call cc-option,-fno-ipa-sra) + +-# This selects which instruction set is used. +-arch-$(CONFIG_CPU_32v7M) :=-march=armv7-m +-arch-$(CONFIG_CPU_32v7) :=-march=armv7-a +-arch-$(CONFIG_CPU_32v6) :=-march=armv6 +-# Only override the compiler option if ARMv6. The ARMv6K extensions are +-# always available in ARMv7 +-ifeq ($(CONFIG_CPU_32v6),y) +-arch-$(CONFIG_CPU_32v6K) :=-march=armv6k +-endif +-arch-$(CONFIG_CPU_32v5) :=-march=armv5te +-arch-$(CONFIG_CPU_32v4T) :=-march=armv4t +-arch-$(CONFIG_CPU_32v4) :=-march=armv4 +-arch-$(CONFIG_CPU_32v3) :=-march=armv3m +- +-# Note that GCC does not numerically define an architecture version +-# macro, but instead defines a whole series of macros which makes +-# testing for a specific architecture or later rather impossible. +-cpp-$(CONFIG_CPU_32v7M) :=-D__LINUX_ARM_ARCH__=7 +-cpp-$(CONFIG_CPU_32v7) :=-D__LINUX_ARM_ARCH__=7 +-cpp-$(CONFIG_CPU_32v6) :=-D__LINUX_ARM_ARCH__=6 +-# Only override the compiler option if ARMv6. The ARMv6K extensions are +-# always available in ARMv7 +-ifeq ($(CONFIG_CPU_32v6),y) +-cpp-$(CONFIG_CPU_32v6K) :=-D__LINUX_ARM_ARCH__=6 +-endif +-cpp-$(CONFIG_CPU_32v5) :=-D__LINUX_ARM_ARCH__=5 +-cpp-$(CONFIG_CPU_32v4T) :=-D__LINUX_ARM_ARCH__=4 +-cpp-$(CONFIG_CPU_32v4) :=-D__LINUX_ARM_ARCH__=4 +-cpp-$(CONFIG_CPU_32v3) :=-D__LINUX_ARM_ARCH__=3 +- +-# This selects how we optimise for the processor. +-tune-$(CONFIG_CPU_ARM7TDMI) :=-mtune=arm7tdmi +-tune-$(CONFIG_CPU_ARM720T) :=-mtune=arm7tdmi +-tune-$(CONFIG_CPU_ARM740T) :=-mtune=arm7tdmi +-tune-$(CONFIG_CPU_ARM9TDMI) :=-mtune=arm9tdmi +-tune-$(CONFIG_CPU_ARM940T) :=-mtune=arm9tdmi +-tune-$(CONFIG_CPU_ARM946E) :=-mtune=arm9e +-tune-$(CONFIG_CPU_ARM920T) :=-mtune=arm9tdmi +-tune-$(CONFIG_CPU_ARM922T) :=-mtune=arm9tdmi +-tune-$(CONFIG_CPU_ARM925T) :=-mtune=arm9tdmi +-tune-$(CONFIG_CPU_ARM926T) :=-mtune=arm9tdmi +-tune-$(CONFIG_CPU_FA526) :=-mtune=arm9tdmi +-tune-$(CONFIG_CPU_SA110) :=-mtune=strongarm110 +-tune-$(CONFIG_CPU_SA1100) :=-mtune=strongarm1100 +-tune-$(CONFIG_CPU_XSCALE) :=-mtune=xscale +-tune-$(CONFIG_CPU_XSC3) :=-mtune=xscale +-tune-$(CONFIG_CPU_FEROCEON) :=-mtune=xscale +-tune-$(CONFIG_CPU_V6) :=-mtune=arm1136j-s +-tune-$(CONFIG_CPU_V6K) :=-mtune=arm1136j-s +- + ifeq ($(CONFIG_AEABI),y) + CFLAGS_ABI :=-mabi=aapcs-linux -mfpu=vfp + else +@@ -140,9 +90,9 @@ AFLAGS_ISA :=$(CFLAGS_ISA) + endif + + # Need -Uarm for gcc < 3.x +-KBUILD_CPPFLAGS +=$(cpp-y) +-KBUILD_CFLAGS +=$(CFLAGS_ABI) $(CFLAGS_ISA) $(arch-y) $(tune-y) $(call cc-option,-mshort-load-bytes,$(call cc-option,-malignment-traps,)) -msoft-float -Uarm +-KBUILD_AFLAGS +=$(CFLAGS_ABI) $(AFLAGS_ISA) -Wa,$(arch-y) $(tune-y) -include asm/unified.h -msoft-float ++ ++KBUILD_CFLAGS +=$(CFLAGS_ABI) $(CFLAGS_ISA) $(call cc-option,-mshort-load-bytes,$(call cc-option,-malignment-traps,)) -msoft-float -Uarm ++KBUILD_AFLAGS +=$(CFLAGS_ABI) $(AFLAGS_ISA) -Wa,-include asm/unified.h -msoft-float + + CHECKFLAGS += -D__arm__ + diff --git a/arch/x86/Kconfig.cpu b/arch/x86/Kconfig.cpu -index 00468adf180f..46cc91cb622f 100644 +index 2a7279d80460..b6a64a959e09 100644 --- a/arch/x86/Kconfig.cpu +++ b/arch/x86/Kconfig.cpu @@ -157,7 +157,7 @@ config MPENTIUM4 @@ -6560,7 +5884,7 @@ index 00468adf180f..46cc91cb622f 100644 # # P6_NOPs are a relatively minor optimization that require a family >= -@@ -356,32 +722,63 @@ config X86_USE_PPRO_CHECKSUM +@@ -356,11 +722,22 @@ config X86_USE_PPRO_CHECKSUM config X86_P6_NOP def_bool y depends on X86_64 @@ -6583,11 +5907,14 @@ index 00468adf180f..46cc91cb622f 100644 + || MTIGERLAKE || MSAPPHIRERAPIDS || MROCKETLAKE || MALDERLAKE || MRAPTORLAKE || MMETEORLAKE || MEMERALDRAPIDS \ + || MNATIVE_INTEL || MNATIVE_AMD) || X86_64 + config X86_HAVE_PAE + def_bool y +@@ -368,24 +745,43 @@ config X86_HAVE_PAE + 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 \ +- depends on X86_HAVE_PAE || M586TSC || M586MMX || MK6 || MK7 ++ depends on X86_HAVE_PAE || M586TSC || M586MMX || 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 \ @@ -6610,9 +5937,9 @@ index 00468adf180f..46cc91cb622f 100644 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 || MCORE2 || MK7 || MK8) + default "6" if X86_32 && (MPENTIUM4 || MPENTIUMM || MPENTIUMIII || MPENTIUMII || M686 \ -+ || MVIAC3_2 || MVIAC7 || MEFFICEON || MATOM || MCRUSOE || MCORE2 || MK7 || MK8 || MK8SSE3 \ ++ || MVIAC3_2 || MVIAC7 || MEFFICEON || MATOM || 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 \ @@ -6631,60 +5958,91 @@ index 00468adf180f..46cc91cb622f 100644 config IA32_FEAT_CTL def_bool y diff --git a/arch/x86/Makefile b/arch/x86/Makefile -index 5bfe5caaa444..b7717a5e10ed 100644 +index da8f3caf2781..adf396b23669 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile -@@ -151,8 +151,48 @@ else - # FIXME - should be integrated in Makefile.cpu (Makefile_32.cpu) - cflags-$(CONFIG_MK8) += -march=k8 - cflags-$(CONFIG_MPSC) += -march=nocona +@@ -67,8 +67,8 @@ export BITS + # + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53383 + # +-KBUILD_CFLAGS += -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx +-KBUILD_RUSTFLAGS += -Ctarget-feature=-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2 ++KBUILD_CFLAGS += -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -mno-avx2 -mno-avx512f ++KBUILD_RUSTFLAGS += -Ctarget-feature=-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-avx512f + + ifeq ($(CONFIG_X86_KERNEL_IBT),y) + # +@@ -149,21 +149,6 @@ else + # Use -mskip-rax-setup if supported. + KBUILD_CFLAGS += $(call cc-option,-mskip-rax-setup) + +- # 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) +- cflags-$(CONFIG_GENERIC_CPU) += -mtune=generic +- KBUILD_CFLAGS += $(cflags-y) +- +- rustflags-$(CONFIG_MK8) += -Ctarget-cpu=k8 +- rustflags-$(CONFIG_MPSC) += -Ctarget-cpu=nocona +- rustflags-$(CONFIG_MCORE2) += -Ctarget-cpu=core2 +- rustflags-$(CONFIG_MATOM) += -Ctarget-cpu=atom +- rustflags-$(CONFIG_GENERIC_CPU) += -Ztune-cpu=generic +- KBUILD_RUSTFLAGS += $(rustflags-y) +- + KBUILD_CFLAGS += -mno-red-zone + KBUILD_CFLAGS += -mcmodel=kernel + KBUILD_RUSTFLAGS += -Cno-redzone=y +diff --git a/arch/x86/Makefile_32.cpu b/arch/x86/Makefile_32.cpu +index 94834c4b5e5e..81923b4afdf8 100644 +--- a/arch/x86/Makefile_32.cpu ++++ b/arch/x86/Makefile_32.cpu +@@ -10,44 +10,3 @@ else + align := -falign-functions=0 -falign-jumps=0 -falign-loops=0 + endif +-cflags-$(CONFIG_M486SX) += -march=i486 +-cflags-$(CONFIG_M486) += -march=i486 +-cflags-$(CONFIG_M586) += -march=i586 +-cflags-$(CONFIG_M586TSC) += -march=i586 +-cflags-$(CONFIG_M586MMX) += -march=pentium-mmx +-cflags-$(CONFIG_M686) += -march=i686 +-cflags-$(CONFIG_MPENTIUMII) += -march=i686 $(call tune,pentium2) +-cflags-$(CONFIG_MPENTIUMIII) += -march=i686 $(call tune,pentium3) +-cflags-$(CONFIG_MPENTIUMM) += -march=i686 $(call tune,pentium3) +-cflags-$(CONFIG_MPENTIUM4) += -march=i686 $(call tune,pentium4) +-cflags-$(CONFIG_MK6) += -march=k6 +-# Please note, that patches that add -march=athlon-xp and friends are pointless. +-# They make zero difference whatsosever to performance at this time. +-cflags-$(CONFIG_MK7) += -march=athlon +-cflags-$(CONFIG_MK8) += $(call cc-option,-march=k8,-march=athlon) +-cflags-$(CONFIG_MCRUSOE) += -march=i686 $(align) +-cflags-$(CONFIG_MEFFICEON) += -march=i686 $(call tune,pentium3) $(align) +-cflags-$(CONFIG_MWINCHIPC6) += $(call cc-option,-march=winchip-c6,-march=i586) +-cflags-$(CONFIG_MWINCHIP3D) += $(call cc-option,-march=winchip2,-march=i586) +-cflags-$(CONFIG_MCYRIXIII) += $(call cc-option,-march=c3,-march=i486) $(align) +-cflags-$(CONFIG_MVIAC3_2) += $(call cc-option,-march=c3-2,-march=i686) +-cflags-$(CONFIG_MVIAC7) += -march=i686 +-cflags-$(CONFIG_MCORE2) += -march=i686 $(call tune,core2) +-cflags-$(CONFIG_MATOM) += $(call cc-option,-march=atom,$(call cc-option,-march=core2,-march=i686)) \ +- $(call cc-option,-mtune=atom,$(call cc-option,-mtune=generic)) +- +-# AMD Elan support +-cflags-$(CONFIG_MELAN) += -march=i486 +- +-# Geode GX1 support +-cflags-$(CONFIG_MGEODEGX1) += -march=pentium-mmx +-cflags-$(CONFIG_MGEODE_LX) += $(call cc-option,-march=geode,-march=pentium-mmx) +-# add at the end to overwrite eventual tuning options from earlier +-# cpu entries +-cflags-$(CONFIG_X86_GENERIC) += $(call tune,generic,$(call tune,i686)) +- +-# Bug fix for binutils: this option is required in order to keep +-# binutils from generating NOPL instructions against our will. +-ifneq ($(CONFIG_X86_P6_NOP),y) +-cflags-y += $(call cc-option,-Wa$(comma)-mtune=generic32,) +-endif diff --git a/arch/x86/include/asm/pci.h b/arch/x86/include/asm/pci.h index b40c462b4af3..c4e66e60d559 100644 --- a/arch/x86/include/asm/pci.h @@ -6824,18 +6182,18 @@ index ddb798603201..7c20387d8202 100644 } -#endif diff --git a/block/bfq-iosched.c b/block/bfq-iosched.c -index 3cce6de464a7..7bdaa2e3a8ee 100644 +index 9bd57baa4b0b..efe818271cf7 100644 --- a/block/bfq-iosched.c +++ b/block/bfq-iosched.c -@@ -7627,6 +7627,7 @@ MODULE_ALIAS("bfq-iosched"); +@@ -7703,6 +7703,7 @@ MODULE_ALIAS("bfq-iosched"); static int __init bfq_init(void) { int ret; -+ char msg[60] = "BFQ I/O-scheduler: BFQ-CachyOS v6.6"; ++ char msg[60] = "BFQ I/O-scheduler: BFQ-CachyOS v6.8"; #ifdef CONFIG_BFQ_GROUP_IOSCHED ret = blkcg_policy_register(&blkcg_policy_bfq); -@@ -7658,6 +7659,11 @@ static int __init bfq_init(void) +@@ -7734,6 +7735,11 @@ static int __init bfq_init(void) if (ret) goto slab_kill; @@ -6847,46 +6205,35 @@ index 3cce6de464a7..7bdaa2e3a8ee 100644 return 0; slab_kill: -diff --git a/drivers/Makefile b/drivers/Makefile -index 1bec7819a837..dcdb0ddb7b66 100644 ---- a/drivers/Makefile -+++ b/drivers/Makefile -@@ -66,15 +66,8 @@ obj-y += char/ - # iommu/ comes before gpu as gpu are using iommu controllers - obj-y += iommu/ +diff --git a/block/elevator.c b/block/elevator.c +index 5ff093cb3cf8..1c93fe91b006 100644 +--- a/block/elevator.c ++++ b/block/elevator.c +@@ -574,9 +574,19 @@ static struct elevator_type *elevator_get_default(struct request_queue *q) --# gpu/ comes after char for AGP vs DRM startup and after iommu --obj-y += gpu/ -- - obj-$(CONFIG_CONNECTOR) += connector/ + if (q->nr_hw_queues != 1 && + !blk_mq_is_shared_tags(q->tag_set->flags)) ++#if defined(CONFIG_CACHY) && defined(CONFIG_MQ_IOSCHED_KYBER) ++ return elevator_find_get(q, "kyber"); ++#elif defined(CONFIG_CACHY) ++ return elevator_find_get(q, "mq-deadline"); ++#else + return NULL; ++#endif --# 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/ -@@ -86,6 +79,14 @@ obj-y += macintosh/ - 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/ ++#if defined(CONFIG_CACHY) && defined(CONFIG_IOSCHED_BFQ) ++ return elevator_find_get(q, "bfq"); ++#else + return elevator_find_get(q, "mq-deadline"); ++#endif + } + + /* diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c -index 08745e7db820..07483490913d 100644 +index 682ff550ccfb..67f17fd94144 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) +@@ -1560,7 +1560,7 @@ static irqreturn_t ahci_thunderx_irq_handler(int irq, void *dev_instance) } #endif @@ -6895,7 +6242,7 @@ index 08745e7db820..07483490913d 100644 struct ahci_host_priv *hpriv) { int i; -@@ -1537,7 +1537,7 @@ static void ahci_remap_check(struct pci_dev *pdev, int bar, +@@ -1573,7 +1573,7 @@ static void ahci_remap_check(struct pci_dev *pdev, int bar, pci_resource_len(pdev, bar) < SZ_512K || bar != AHCI_PCI_BAR_STANDARD || !(readl(hpriv->mmio + AHCI_VSCAP) & 1)) @@ -6904,7 +6251,7 @@ index 08745e7db820..07483490913d 100644 cap = readq(hpriv->mmio + AHCI_REMAP_CAP); for (i = 0; i < AHCI_MAX_REMAP; i++) { -@@ -1552,18 +1552,11 @@ static void ahci_remap_check(struct pci_dev *pdev, int bar, +@@ -1588,18 +1588,11 @@ static void ahci_remap_check(struct pci_dev *pdev, int bar, } if (!hpriv->remapped_nvme) @@ -6927,7 +6274,7 @@ index 08745e7db820..07483490913d 100644 } static int ahci_get_irq_vector(struct ata_host *host, int port) -@@ -1783,7 +1776,9 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +@@ -1819,7 +1812,9 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) hpriv->mmio = pcim_iomap_table(pdev)[ahci_pci_bar]; /* detect remapped nvme devices */ @@ -6959,7 +6306,7 @@ index 438c9e75a04d..1bbfeca5f01e 100644 This driver adds a CPUFreq driver which utilizes a fine grain processor performance frequency control range instead of legacy diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig -index 97d27e01a6ee..6a5ca108ac71 100644 +index 28eb48dd5b32..1cf4c700b108 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -230,6 +230,15 @@ config I2C_CHT_WC @@ -6979,7 +6326,7 @@ index 97d27e01a6ee..6a5ca108ac71 100644 tristate "Nvidia nForce2, nForce3 and nForce4" depends on PCI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile -index af56fe2c75c0..76be74584719 100644 +index aa0ee8ecd6f2..020714113e9a 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_I2C_CHT_WC) += i2c-cht-wc.o @@ -7645,7 +6992,7 @@ index 000000000000..e919d1e10c51 +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 809fbd014cd6..d54b35b147ee 100644 +index 6a0392172b2f..e7dd007bf6b1 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) @@ -7662,11 +7009,75 @@ index 809fbd014cd6..d54b35b147ee 100644 /* If the SMBus is still busy, we give up */ if (timeout == MAX_TIMEOUT) { +diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c +index 51e0c4954600..35c3ad741870 100644 +--- a/drivers/input/evdev.c ++++ b/drivers/input/evdev.c +@@ -46,6 +46,7 @@ struct evdev_client { + struct fasync_struct *fasync; + struct evdev *evdev; + struct list_head node; ++ struct rcu_head rcu; + enum input_clock_type clk_type; + bool revoked; + unsigned long *evmasks[EV_CNT]; +@@ -377,13 +378,22 @@ static void evdev_attach_client(struct evdev *evdev, + spin_unlock(&evdev->client_lock); + } + ++static void evdev_reclaim_client(struct rcu_head *rp) ++{ ++ struct evdev_client *client = container_of(rp, struct evdev_client, rcu); ++ unsigned int i; ++ for (i = 0; i < EV_CNT; ++i) ++ bitmap_free(client->evmasks[i]); ++ kvfree(client); ++} ++ + static void evdev_detach_client(struct evdev *evdev, + struct evdev_client *client) + { + spin_lock(&evdev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&evdev->client_lock); +- synchronize_rcu(); ++ call_rcu(&client->rcu, evdev_reclaim_client); + } + + static int evdev_open_device(struct evdev *evdev) +@@ -436,7 +446,6 @@ static int evdev_release(struct inode *inode, struct file *file) + { + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; +- unsigned int i; + + mutex_lock(&evdev->mutex); + +@@ -448,11 +457,6 @@ static int evdev_release(struct inode *inode, struct file *file) + + evdev_detach_client(evdev, client); + +- for (i = 0; i < EV_CNT; ++i) +- bitmap_free(client->evmasks[i]); +- +- kvfree(client); +- + evdev_close_device(evdev); + + return 0; +@@ -495,7 +499,6 @@ static int evdev_open(struct inode *inode, struct file *file) + + err_free_client: + evdev_detach_client(evdev, client); +- kvfree(client); + return error; + } + diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c -index cef9353370b2..89747847dfc8 100644 +index 59445763e55a..568f85414c85 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c -@@ -3249,6 +3249,11 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) +@@ -3271,6 +3271,11 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) goto bad; } @@ -7679,7 +7090,7 @@ index cef9353370b2..89747847dfc8 100644 if (ret < 0) goto bad; diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile -index 37c8663de7fe..897d19f92ede 100644 +index f2b19e6174af..4fef4b174321 100644 --- a/drivers/pci/controller/Makefile +++ b/drivers/pci/controller/Makefile @@ -1,4 +1,10 @@ @@ -8162,10 +7573,10 @@ index 000000000000..e105e6f5cc91 +MODULE_AUTHOR("Daniel Drake "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c -index ae95d0950772..07596cfed8b7 100644 +index d797df6e5f3e..b53d515da054 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c -@@ -3722,6 +3722,106 @@ static void quirk_no_bus_reset(struct pci_dev *dev) +@@ -3732,6 +3732,106 @@ static void quirk_no_bus_reset(struct pci_dev *dev) dev->dev_flags |= PCI_DEV_FLAGS_NO_BUS_RESET; } @@ -8272,7 +7683,7 @@ index ae95d0950772..07596cfed8b7 100644 /* * Some NVIDIA GPU devices do not work with bus reset, SBR needs to be * prevented for those affected devices. -@@ -5116,6 +5216,7 @@ static const struct pci_dev_acs_enabled { +@@ -5143,6 +5243,7 @@ static const struct pci_dev_acs_enabled { { PCI_VENDOR_ID_ZHAOXIN, PCI_ANY_ID, pci_quirk_zhaoxin_pcie_ports_acs }, /* Wangxun nics */ { PCI_VENDOR_ID_WANGXUN, PCI_ANY_ID, pci_quirk_wangxun_nic_acs }, @@ -8281,27 +7692,10 @@ index ae95d0950772..07596cfed8b7 100644 }; diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 2a1070543391..522e52d788b4 100644 +index bdd302274b9a..0afc3e4c1880 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 -@@ -1099,6 +1109,20 @@ config SEL3350_PLATFORM +@@ -1127,6 +1127,20 @@ config SEL3350_PLATFORM To compile this driver as a module, choose M here: the module will be called sel3350-platform. @@ -8323,5888 +7717,16 @@ index 2a1070543391..522e52d788b4 100644 config P2SB diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index b457de5abf7d..ecfbaf2e4e86 100644 +index 1de432e8861e..59bfbd2649eb 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 -@@ -138,3 +139,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o +@@ -144,3 +144,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o # SEL obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o + +# Steam Deck +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 000000000000..7275105071d2 ---- /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); diff --git a/drivers/platform/x86/steamdeck.c b/drivers/platform/x86/steamdeck.c new file mode 100644 index 000000000000..77a6677ec19e @@ -14735,7 +8257,7 @@ index 000000000000..77a6677ec19e +MODULE_DESCRIPTION("Steam Deck ACPI platform driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mm.h b/include/linux/mm.h -index bf5d0b1b16f4..5a62f3ab1b80 100644 +index f5a97dec5169..c9fb00c56844 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) @@ -14748,10 +8270,10 @@ index bf5d0b1b16f4..5a62f3ab1b80 100644 extern int sysctl_max_map_count; diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h -index 8c9608b217b0..0bdee469ff93 100644 +index 2df35e65557d..a52bd9f4b632 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h -@@ -1278,7 +1278,7 @@ struct readahead_control { +@@ -1268,7 +1268,7 @@ struct readahead_control { ._index = i, \ } @@ -14761,10 +8283,10 @@ index 8c9608b217b0..0bdee469ff93 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 45f09bec02c4..87b20e2ee274 100644 +index 6030a8235617..60b7fe5fa74a 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, +@@ -156,6 +156,8 @@ static inline void set_userns_rlimit_max(struct user_namespace *ns, #ifdef CONFIG_USER_NS @@ -14773,7 +8295,7 @@ index 45f09bec02c4..87b20e2ee274 100644 static inline struct user_namespace *get_user_ns(struct user_namespace *ns) { if (ns) -@@ -181,6 +183,8 @@ extern bool current_in_userns(const struct user_namespace *target_ns); +@@ -189,6 +191,8 @@ extern bool current_in_userns(const struct user_namespace *target_ns); struct ns_common *ns_get_owner(struct ns_common *ns); #else @@ -14783,10 +8305,10 @@ index 45f09bec02c4..87b20e2ee274 100644 { return &init_user_ns; diff --git a/init/Kconfig b/init/Kconfig -index 6d35728b94b2..9dee4c100348 100644 +index bee58f7468c3..9ea39297f149 100644 --- a/init/Kconfig +++ b/init/Kconfig -@@ -123,6 +123,10 @@ config THREAD_INFO_IN_TASK +@@ -132,6 +132,10 @@ config THREAD_INFO_IN_TASK menu "General setup" @@ -14797,7 +8319,7 @@ index 6d35728b94b2..9dee4c100348 100644 config BROKEN bool -@@ -1226,6 +1230,22 @@ config USER_NS +@@ -1247,6 +1251,22 @@ config USER_NS If unsure, say N. @@ -14820,7 +8342,7 @@ index 6d35728b94b2..9dee4c100348 100644 config PID_NS bool "PID Namespaces" default y -@@ -1368,6 +1388,12 @@ config CC_OPTIMIZE_FOR_PERFORMANCE +@@ -1389,6 +1409,12 @@ config CC_OPTIMIZE_FOR_PERFORMANCE with the "-O2" compiler flag for best performance and most helpful compile-time warnings. @@ -14876,12 +8398,12 @@ index 38ef6d06888e..0f78364efd4f 100644 config SCHED_HRTICK diff --git a/kernel/fork.c b/kernel/fork.c -index 177ce7438db6..6ecece1407fc 100644 +index 0d944e92a43f..5449c990a91a 100644 --- a/kernel/fork.c +++ b/kernel/fork.c -@@ -100,6 +100,10 @@ - #include +@@ -102,6 +102,10 @@ #include + #include +#ifdef CONFIG_USER_NS +#include @@ -14901,7 +8423,7 @@ index 177ce7438db6..6ecece1407fc 100644 /* * Thread groups must share signals as well, and detached threads * can only be started up within the thread group. -@@ -3413,6 +3421,12 @@ int ksys_unshare(unsigned long unshare_flags) +@@ -3406,6 +3414,12 @@ int ksys_unshare(unsigned long unshare_flags) if (unshare_flags & CLONE_NEWNS) unshare_flags |= CLONE_FS; @@ -14914,8 +8436,58 @@ index 177ce7438db6..6ecece1407fc 100644 err = check_unshare_flags(unshare_flags); if (err) goto bad_unshare_out; +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +index 533547e3c90a..fc0a9de42a9d 100644 +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -73,10 +73,19 @@ unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; + * + * (default: 0.75 msec * (1 + ilog(ncpus)), units: nanoseconds) + */ ++#ifdef CONFIG_CACHY ++unsigned int sysctl_sched_base_slice = 350000ULL; ++static unsigned int normalized_sysctl_sched_base_slice = 350000ULL; ++#else + unsigned int sysctl_sched_base_slice = 750000ULL; + static unsigned int normalized_sysctl_sched_base_slice = 750000ULL; ++#endif + ++#ifdef CONFIG_CACHY ++const_debug unsigned int sysctl_sched_migration_cost = 300000UL; ++#else + const_debug unsigned int sysctl_sched_migration_cost = 500000UL; ++#endif + + int sched_thermal_decay_shift; + static int __init setup_sched_thermal_decay_shift(char *str) +@@ -127,8 +136,12 @@ int __weak arch_asym_cpu_priority(int cpu) + * + * (default: 5 msec, units: microseconds) + */ ++#ifdef CONFIG_CACHY ++static unsigned int sysctl_sched_cfs_bandwidth_slice = 3000UL; ++#else + static unsigned int sysctl_sched_cfs_bandwidth_slice = 5000UL; + #endif ++#endif + + #ifdef CONFIG_NUMA_BALANCING + /* Restrict the NUMA promotion throughput (MB/s) for each target node. */ +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +index 001fe047bd5d..ed5c758c7368 100644 +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -2542,7 +2542,7 @@ extern void deactivate_task(struct rq *rq, struct task_struct *p, int flags); + + extern void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags); + +-#ifdef CONFIG_PREEMPT_RT ++#if defined(CONFIG_PREEMPT_RT) || defined(CONFIG_CACHY) + #define SCHED_NR_MIGRATE_BREAK 8 + #else + #define SCHED_NR_MIGRATE_BREAK 32 diff --git a/kernel/sysctl.c b/kernel/sysctl.c -index 354a2d294f52..4dc780aa3bcc 100644 +index 157f7ce2942d..c92d8a4b23fb 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -95,6 +95,9 @@ EXPORT_SYMBOL_GPL(sysctl_long_vals); @@ -14945,7 +8517,7 @@ index 354a2d294f52..4dc780aa3bcc 100644 { .procname = "tainted", diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c -index 1d8e47bed3f1..fec01d016a35 100644 +index ce4d99df5f0e..8272e2e359f1 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -22,6 +22,13 @@ @@ -14959,14 +8531,14 @@ index 1d8e47bed3f1..fec01d016a35 100644 +int unprivileged_userns_clone; +#endif + - static struct kmem_cache *user_ns_cachep __read_mostly; + static struct kmem_cache *user_ns_cachep __ro_after_init; static DEFINE_MUTEX(userns_state_mutex); diff --git a/mm/Kconfig b/mm/Kconfig -index 264a2df5ecf5..0bf8853cc3a8 100644 +index ffc3a2ba3a8c..0e440573033c 100644 --- a/mm/Kconfig +++ b/mm/Kconfig -@@ -653,7 +653,7 @@ config COMPACTION +@@ -630,7 +630,7 @@ config COMPACTION config COMPACT_UNEVICTABLE_DEFAULT int depends on COMPACTION @@ -14975,8 +8547,40 @@ index 264a2df5ecf5..0bf8853cc3a8 100644 default 1 # +diff --git a/mm/compaction.c b/mm/compaction.c +index b961db601df4..91d627e8a93d 100644 +--- a/mm/compaction.c ++++ b/mm/compaction.c +@@ -1830,7 +1830,11 @@ static int sysctl_compact_unevictable_allowed __read_mostly = CONFIG_COMPACT_UNE + * aggressively the kernel should compact memory in the + * background. It takes values in the range [0, 100]. + */ ++#ifdef CONFIG_CACHY ++static unsigned int __read_mostly sysctl_compaction_proactiveness; ++#else + static unsigned int __read_mostly sysctl_compaction_proactiveness = 20; ++#endif + static int sysctl_extfrag_threshold = 500; + static int __read_mostly sysctl_compact_memory; + +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +index 94c958f7ebb5..2f9974f305ee 100644 +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -62,7 +62,11 @@ unsigned long transparent_hugepage_flags __read_mostly = + #ifdef CONFIG_TRANSPARENT_HUGEPAGE_MADVISE + (1<lock, flags); + for (i = 0; i < count; ++i) { +@@ -2134,6 +2139,18 @@ static int rmqueue_bulk(struct zone *zone, unsigned int order, + 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 +@@ -2150,7 +2167,7 @@ static int rmqueue_bulk(struct zone *zone, unsigned int order, + -(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; diff --git a/mm/swap.c b/mm/swap.c index cd8f0150ba3a..42c405a4f114 100644 --- a/mm/swap.c @@ -15025,7 +8695,7 @@ index cd8f0150ba3a..42c405a4f114 100644 +#endif } diff --git a/mm/vmpressure.c b/mm/vmpressure.c -index 22c6689d9302..bf65bd9abdf3 100644 +index bd5183dfd879..3a410f53a07c 100644 --- a/mm/vmpressure.c +++ b/mm/vmpressure.c @@ -43,7 +43,11 @@ static const unsigned long vmpressure_win = SWAP_CLUSTER_MAX * 16; @@ -15041,10 +8711,10 @@ index 22c6689d9302..bf65bd9abdf3 100644 /* diff --git a/mm/vmscan.c b/mm/vmscan.c -index 6f13394b112e..1fb69bffa109 100644 +index 4255619a1a31..5a3fbaf34158 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c -@@ -186,7 +186,11 @@ struct scan_control { +@@ -185,7 +185,11 @@ struct scan_control { /* * From 0 .. 200. Higher means more swappy. */ @@ -15054,14 +8724,14 @@ index 6f13394b112e..1fb69bffa109 100644 int vm_swappiness = 60; +#endif - LIST_HEAD(shrinker_list); - DECLARE_RWSEM(shrinker_rwsem); -@@ -4595,7 +4599,11 @@ static bool lruvec_is_reclaimable(struct lruvec *lruvec, struct scan_control *sc + #ifdef CONFIG_MEMCG + +@@ -3922,7 +3926,11 @@ static bool lruvec_is_reclaimable(struct lruvec *lruvec, struct scan_control *sc } /* to protect the working set of the last N jiffies */ +#ifdef CONFIG_CACHY -+static unsigned long lru_gen_min_ttl __read_mostly = HZ; ++static unsigned long lru_gen_min_ttl __read_mostly = 1000; +#else static unsigned long lru_gen_min_ttl __read_mostly; +#endif @@ -15069,1791 +8739,46 @@ index 6f13394b112e..1fb69bffa109 100644 static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc) { -- -2.43.0 +2.44.0 -From 30f602327964d34b5fc7155724fcaf97a77a7848 Mon Sep 17 00:00:00 2001 +From 516559b0e31629dafbe60212d041e63af1b12c1c Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Wed, 29 Nov 2023 19:56:14 +0100 +Date: Mon, 26 Feb 2024 15:47:43 +0100 Subject: [PATCH 5/7] fixes Signed-off-by: Peter Jung --- - Documentation/ABI/stable/sysfs-block | 10 + - .../testing/sysfs-class-led-trigger-blkdev | 78 ++ - Documentation/leds/index.rst | 1 + - Documentation/leds/ledtrig-blkdev.rst | 158 +++ - arch/x86/include/asm/barrier.h | 18 - - arch/x86/include/asm/processor.h | 19 + - drivers/bluetooth/btusb.c | 2 +- - drivers/hid/amd-sfh-hid/amd_sfh_client.c | 6 + - drivers/leds/trigger/Kconfig | 9 + - drivers/leds/trigger/Makefile | 1 + - drivers/leds/trigger/ledtrig-blkdev.c | 1218 +++++++++++++++++ - include/linux/pageblock-flags.h | 2 +- - kernel/padata.c | 4 +- - kernel/smp.c | 2 +- - mm/page_alloc.c | 2 +- - mm/slub.c | 17 +- - sound/pci/hda/patch_realtek.c | 1 + - 17 files changed, 1514 insertions(+), 34 deletions(-) - create mode 100644 Documentation/ABI/testing/sysfs-class-led-trigger-blkdev - create mode 100644 Documentation/leds/ledtrig-blkdev.rst - create mode 100644 drivers/leds/trigger/ledtrig-blkdev.c + arch/Kconfig | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) -diff --git a/Documentation/ABI/stable/sysfs-block b/Documentation/ABI/stable/sysfs-block -index 1fe9a553c37b..edeac5e4c83d 100644 ---- a/Documentation/ABI/stable/sysfs-block -+++ b/Documentation/ABI/stable/sysfs-block -@@ -101,6 +101,16 @@ Description: - 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 --git a/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev -new file mode 100644 -index 000000000000..28ce8c814fb7 ---- /dev/null -+++ b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev -@@ -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 --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst -index 3ade16c18328..3fd55a2cbfb5 100644 ---- a/Documentation/leds/index.rst -+++ b/Documentation/leds/index.rst -@@ -10,6 +10,7 @@ LEDs - leds-class - leds-class-flash - leds-class-multicolor -+ ledtrig-blkdev - ledtrig-oneshot - ledtrig-transient - ledtrig-usbport -diff --git a/Documentation/leds/ledtrig-blkdev.rst b/Documentation/leds/ledtrig-blkdev.rst -new file mode 100644 -index 000000000000..9ff5b99de451 ---- /dev/null -+++ b/Documentation/leds/ledtrig-blkdev.rst -@@ -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 --git a/arch/x86/include/asm/barrier.h b/arch/x86/include/asm/barrier.h -index 35389b2af88e..0216f63a366b 100644 ---- a/arch/x86/include/asm/barrier.h -+++ b/arch/x86/include/asm/barrier.h -@@ -81,22 +81,4 @@ do { \ - - #include - --/* -- * Make previous memory operations globally visible before -- * a WRMSR. -- * -- * MFENCE makes writes visible, but only affects load/store -- * instructions. WRMSR is unfortunately not a load/store -- * instruction and is unaffected by MFENCE. The LFENCE ensures -- * that the WRMSR is not reordered. -- * -- * Most WRMSRs are full serializing instructions themselves and -- * do not require this barrier. This is only required for the -- * IA32_TSC_DEADLINE and X2APIC MSRs. -- */ --static inline void weak_wrmsr_fence(void) --{ -- asm volatile("mfence; lfence" : : : "memory"); --} -- - #endif /* _ASM_X86_BARRIER_H */ -diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h -index a3669a7774ed..3e175d55488d 100644 ---- a/arch/x86/include/asm/processor.h -+++ b/arch/x86/include/asm/processor.h -@@ -734,4 +734,23 @@ bool arch_is_platform_page(u64 paddr); - - extern bool gds_ucode_mitigated(void); - -+/* -+ * Make previous memory operations globally visible before -+ * a WRMSR. -+ * -+ * MFENCE makes writes visible, but only affects load/store -+ * instructions. WRMSR is unfortunately not a load/store -+ * instruction and is unaffected by MFENCE. The LFENCE ensures -+ * that the WRMSR is not reordered. -+ * -+ * Most WRMSRs are full serializing instructions themselves and -+ * do not require this barrier. This is only required for the -+ * IA32_TSC_DEADLINE and X2APIC MSRs. -+ */ -+static inline void weak_wrmsr_fence(void) -+{ -+ if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) -+ asm volatile("mfence; lfence" : : : "memory"); -+} -+ - #endif /* _ASM_X86_PROCESSOR_H */ -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index 66080fae072f..7778883d3381 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -1032,7 +1032,7 @@ static void btusb_qca_cmd_timeout(struct hci_dev *hdev) - } - - 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; -diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c -index bdb578e0899f..0f1d6778051f 100644 ---- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c -+++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c -@@ -25,6 +25,9 @@ void amd_sfh_set_report(struct hid_device *hid, int report_id, - struct amdtp_cl_data *cli_data = hid_data->cli_data; - int i; - -+ if (!cli_data->is_any_sensor_enabled) -+ return; -+ - for (i = 0; i < cli_data->num_hid_devices; i++) { - if (cli_data->hid_sensor_hubs[i] == hid) { - cli_data->cur_hid_dev = i; -@@ -41,6 +44,9 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type) - struct request_list *req_list = &cli_data->req_list; - int i; - -+ if (!cli_data->is_any_sensor_enabled) -+ return -ENODEV; -+ - for (i = 0; i < cli_data->num_hid_devices; i++) { - if (cli_data->hid_sensor_hubs[i] == hid) { - struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL); -diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig -index 2a57328eca20..05e80cfd0ed8 100644 ---- a/drivers/leds/trigger/Kconfig -+++ b/drivers/leds/trigger/Kconfig -@@ -155,4 +155,13 @@ config LEDS_TRIGGER_TTY - - 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 --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile -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 - 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 --git a/drivers/leds/trigger/ledtrig-blkdev.c b/drivers/leds/trigger/ledtrig-blkdev.c -new file mode 100644 -index 000000000000..9e0c4b66ea27 ---- /dev/null -+++ b/drivers/leds/trigger/ledtrig-blkdev.c -@@ -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 --git a/include/linux/pageblock-flags.h b/include/linux/pageblock-flags.h -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; - #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 --git a/kernel/padata.c b/kernel/padata.c -index 179fb1518070..b6bb0c45d7b8 100644 ---- a/kernel/padata.c -+++ b/kernel/padata.c -@@ -45,7 +45,7 @@ struct padata_mt_job_state { - }; - - 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 @@ static int padata_setup_cpumasks(struct padata_instance *pinst) - 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 --git a/kernel/smp.c b/kernel/smp.c -index 695eb13a276d..83b0468808f9 100644 ---- a/kernel/smp.c -+++ b/kernel/smp.c -@@ -957,7 +957,7 @@ early_param("maxcpus", maxcpus); - - #if (NR_CPUS > 1) && !defined(CONFIG_FORCE_NR_CPUS) - /* Setup number of possible processor ids */ --unsigned int nr_cpu_ids __read_mostly = NR_CPUS; -+unsigned int nr_cpu_ids __ro_after_init = NR_CPUS; - EXPORT_SYMBOL(nr_cpu_ids); - #endif - -diff --git a/mm/page_alloc.c b/mm/page_alloc.c -index 85741403948f..313338789e46 100644 ---- a/mm/page_alloc.c -+++ b/mm/page_alloc.c -@@ -294,7 +294,7 @@ int movable_zone; - EXPORT_SYMBOL(movable_zone); - - #if MAX_NUMNODES > 1 --unsigned int nr_node_ids __read_mostly = MAX_NUMNODES; -+unsigned int nr_node_ids __ro_after_init = MAX_NUMNODES; - unsigned int nr_online_nodes __read_mostly = 1; - EXPORT_SYMBOL(nr_node_ids); - EXPORT_SYMBOL(nr_online_nodes); -diff --git a/mm/slub.c b/mm/slub.c -index f7940048138c..3cf4842d534e 100644 ---- a/mm/slub.c -+++ b/mm/slub.c -@@ -287,6 +287,7 @@ static inline bool kmem_cache_has_cpu_partial(struct kmem_cache *s) - #define OO_SHIFT 16 - #define OO_MASK ((1 << OO_SHIFT) - 1) - #define MAX_OBJS_PER_PAGE 32767 /* since slab.objects is u15 */ -+#define SLUB_PAGE_FRAC_SHIFT 12 - - /* Internal SLUB flags */ - /* Poison object */ -@@ -4140,6 +4141,7 @@ static inline int calculate_order(unsigned int size) - unsigned int min_objects; - unsigned int max_objects; - unsigned int nr_cpus; -+ unsigned int page_size_frac; - - /* - * Attempt to find best configuration for a slab. This -@@ -4168,10 +4170,13 @@ static inline int calculate_order(unsigned int size) - max_objects = order_objects(slub_max_order, size); - min_objects = min(min_objects, max_objects); - -- while (min_objects > 1) { -+ page_size_frac = ((PAGE_SIZE >> SLUB_PAGE_FRAC_SHIFT) == 1) ? 0 -+ : PAGE_SIZE >> SLUB_PAGE_FRAC_SHIFT; -+ -+ while (min_objects >= 1) { - unsigned int fraction; - -- fraction = 16; -+ fraction = 16 + page_size_frac; - while (fraction >= 4) { - order = calc_slab_order(size, min_objects, - slub_max_order, fraction); -@@ -4182,14 +4187,6 @@ static inline int calculate_order(unsigned int size) - min_objects--; - } - -- /* -- * We were unable to place multiple objects in a slab. Now -- * lets see if we can place a single object there. -- */ -- order = calc_slab_order(size, 1, slub_max_order, 1); -- if (order <= slub_max_order) -- return order; -- - /* - * Doh this slab cannot be placed using slub_max_order. - */ -diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c -index 758abe9dffd6..aa6460f49ee2 100644 ---- a/sound/pci/hda/patch_realtek.c -+++ b/sound/pci/hda/patch_realtek.c -@@ -9873,6 +9873,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { - SND_PCI_QUIRK(0x1043, 0x17f3, "ROG Ally RC71L_RC71L", ALC294_FIXUP_ASUS_ALLY), - SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), - SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), -+ 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 --git a/arch/Kconfig b/arch/Kconfig +index a5af0edd3eb8..0731bc203aa9 100644 +--- a/arch/Kconfig ++++ b/arch/Kconfig +@@ -1025,7 +1025,7 @@ config ARCH_MMAP_RND_BITS + int "Number of bits to use for ASLR of mmap base address" if EXPERT + range ARCH_MMAP_RND_BITS_MIN ARCH_MMAP_RND_BITS_MAX + default ARCH_MMAP_RND_BITS_DEFAULT if ARCH_MMAP_RND_BITS_DEFAULT +- default ARCH_MMAP_RND_BITS_MIN ++ default ARCH_MMAP_RND_BITS_MAX + depends on HAVE_ARCH_MMAP_RND_BITS + help + This value can be used to select the number of bits to use to +@@ -1059,7 +1059,7 @@ config ARCH_MMAP_RND_COMPAT_BITS + int "Number of bits to use for ASLR of mmap base address for compatible applications" if EXPERT + range ARCH_MMAP_RND_COMPAT_BITS_MIN ARCH_MMAP_RND_COMPAT_BITS_MAX + default ARCH_MMAP_RND_COMPAT_BITS_DEFAULT if ARCH_MMAP_RND_COMPAT_BITS_DEFAULT +- default ARCH_MMAP_RND_COMPAT_BITS_MIN ++ default ARCH_MMAP_RND_COMPAT_BITS_MAX + depends on HAVE_ARCH_MMAP_RND_COMPAT_BITS + help + This value can be used to select the number of bits to use to -- -2.43.0 +2.44.0 -From 202d60b35aafc317c784518f1c0aa46fb68a2dcf Mon Sep 17 00:00:00 2001 +From e01d8909a6a6d90eb2ff29871d79f4e9359638ca Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Wed, 29 Nov 2023 19:56:24 +0100 +Date: Mon, 26 Feb 2024 15:48:00 +0100 Subject: [PATCH 6/7] ksm Signed-off-by: Peter Jung @@ -16862,7 +8787,6 @@ Signed-off-by: Peter Jung arch/arm/tools/syscall.tbl | 3 + arch/arm64/include/asm/unistd.h | 2 +- arch/arm64/include/asm/unistd32.h | 6 + - arch/ia64/kernel/syscalls/syscall.tbl | 3 + arch/m68k/kernel/syscalls/syscall.tbl | 3 + arch/microblaze/kernel/syscalls/syscall.tbl | 3 + arch/mips/kernel/syscalls/syscall_n32.tbl | 3 + @@ -16880,222 +8804,211 @@ Signed-off-by: Peter Jung include/uapi/asm-generic/unistd.h | 11 +- kernel/sys.c | 147 ++++++++++++++++++++ kernel/sys_ni.c | 3 + - 22 files changed, 218 insertions(+), 2 deletions(-) + 21 files changed, 215 insertions(+), 2 deletions(-) diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl -index ad37569d0507..9f4311e21c42 100644 +index 8ff110826ce2..ad64d9b5ce62 100644 --- a/arch/alpha/kernel/syscalls/syscall.tbl +++ b/arch/alpha/kernel/syscalls/syscall.tbl -@@ -492,3 +492,6 @@ - 560 common set_mempolicy_home_node sys_ni_syscall - 561 common cachestat sys_cachestat - 562 common fchmodat2 sys_fchmodat2 -+563 common process_ksm_enable sys_process_ksm_enable -+564 common process_ksm_disable sys_process_ksm_disable -+565 common process_ksm_status sys_process_ksm_status +@@ -501,3 +501,6 @@ + 569 common lsm_get_self_attr sys_lsm_get_self_attr + 570 common lsm_set_self_attr sys_lsm_set_self_attr + 571 common lsm_list_modules sys_lsm_list_modules ++572 common process_ksm_enable sys_process_ksm_enable ++573 common process_ksm_disable sys_process_ksm_disable ++574 common process_ksm_status sys_process_ksm_status diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl -index c572d6c3dee0..8d40b3a4572e 100644 +index b6c9e01e14f5..524a0429016a 100644 --- a/arch/arm/tools/syscall.tbl +++ b/arch/arm/tools/syscall.tbl -@@ -466,3 +466,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -475,3 +475,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 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 bd77253b62e0..f33190f17ebb 100644 +index 491b2b9bd553..8bdc6380b21d 100644 --- a/arch/arm64/include/asm/unistd.h +++ b/arch/arm64/include/asm/unistd.h @@ -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 453 -+#define __NR_compat_syscalls 456 +-#define __NR_compat_syscalls 462 ++#define __NR_compat_syscalls 465 #endif #define __ARCH_WANT_SYS_CLONE diff --git a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h -index 78b68311ec81..8c8b1c7497c5 100644 +index 7118282d1c79..af409ce2d8af 100644 --- a/arch/arm64/include/asm/unistd32.h +++ b/arch/arm64/include/asm/unistd32.h -@@ -911,6 +911,12 @@ __SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node) - __SYSCALL(__NR_cachestat, sys_cachestat) - #define __NR_fchmodat2 452 - __SYSCALL(__NR_fchmodat2, sys_fchmodat2) -+#define __NR_process_ksm_enable 453 +@@ -929,6 +929,12 @@ __SYSCALL(__NR_lsm_get_self_attr, sys_lsm_get_self_attr) + __SYSCALL(__NR_lsm_set_self_attr, sys_lsm_set_self_attr) + #define __NR_lsm_list_modules 461 + __SYSCALL(__NR_lsm_list_modules, sys_lsm_list_modules) ++#define __NR_process_ksm_enable 462 +__SYSCALL(__NR_process_ksm_enable, sys_process_ksm_enable) -+#define __NR_process_ksm_disable 454 ++#define __NR_process_ksm_disable 463 +__SYSCALL(__NR_process_ksm_disable, sys_process_ksm_disable) -+#define __NR_process_ksm_status 455 ++#define __NR_process_ksm_status 464 +__SYSCALL(__NR_process_ksm_status, sys_process_ksm_status) /* * 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 83d8609aec03..2c370b5695ef 100644 ---- a/arch/ia64/kernel/syscalls/syscall.tbl -+++ b/arch/ia64/kernel/syscalls/syscall.tbl -@@ -373,3 +373,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 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 259ceb125367..346033761c74 100644 +index 7fd43fd4c9f2..3374d91d8665 100644 --- a/arch/m68k/kernel/syscalls/syscall.tbl +++ b/arch/m68k/kernel/syscalls/syscall.tbl -@@ -452,3 +452,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -461,3 +461,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 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 a3798c2637fd..3d550ff347e4 100644 +index b00ab2cabab9..f44872df0f9c 100644 --- a/arch/microblaze/kernel/syscalls/syscall.tbl +++ b/arch/microblaze/kernel/syscalls/syscall.tbl -@@ -458,3 +458,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -467,3 +467,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 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 152034b8e0a0..49bedc66cdd8 100644 +index 83cfc9eb6b88..7a5a02c29175 100644 --- a/arch/mips/kernel/syscalls/syscall_n32.tbl +++ b/arch/mips/kernel/syscalls/syscall_n32.tbl -@@ -391,3 +391,6 @@ - 450 n32 set_mempolicy_home_node sys_set_mempolicy_home_node - 451 n32 cachestat sys_cachestat - 452 n32 fchmodat2 sys_fchmodat2 -+453 n32 process_ksm_enable sys_process_ksm_enable -+454 n32 process_ksm_disable sys_process_ksm_disable -+455 n32 process_ksm_status sys_process_ksm_status +@@ -400,3 +400,6 @@ + 459 n32 lsm_get_self_attr sys_lsm_get_self_attr + 460 n32 lsm_set_self_attr sys_lsm_set_self_attr + 461 n32 lsm_list_modules sys_lsm_list_modules ++462 n32 process_ksm_enable sys_process_ksm_enable ++463 n32 process_ksm_disable sys_process_ksm_disable ++464 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 cb5e757f6621..5c17a0a34f68 100644 +index 532b855df589..bc960ce89fac 100644 --- a/arch/mips/kernel/syscalls/syscall_n64.tbl +++ b/arch/mips/kernel/syscalls/syscall_n64.tbl -@@ -367,3 +367,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 n64 cachestat sys_cachestat - 452 n64 fchmodat2 sys_fchmodat2 -+453 n64 process_ksm_enable sys_process_ksm_enable -+454 n64 process_ksm_disable sys_process_ksm_disable -+455 n64 process_ksm_status sys_process_ksm_status +@@ -376,3 +376,6 @@ + 459 n64 lsm_get_self_attr sys_lsm_get_self_attr + 460 n64 lsm_set_self_attr sys_lsm_set_self_attr + 461 n64 lsm_list_modules sys_lsm_list_modules ++462 n64 process_ksm_enable sys_process_ksm_enable ++463 n64 process_ksm_disable sys_process_ksm_disable ++464 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 1a646813afdc..95dbd2c60dd8 100644 +index f45c9530ea93..1179ed8891be 100644 --- a/arch/mips/kernel/syscalls/syscall_o32.tbl +++ b/arch/mips/kernel/syscalls/syscall_o32.tbl -@@ -440,3 +440,6 @@ - 450 o32 set_mempolicy_home_node sys_set_mempolicy_home_node - 451 o32 cachestat sys_cachestat - 452 o32 fchmodat2 sys_fchmodat2 -+453 o32 process_ksm_enable sys_process_ksm_enable -+454 o32 process_ksm_disable sys_process_ksm_disable -+455 o32 process_ksm_status sys_process_ksm_status +@@ -449,3 +449,6 @@ + 459 o32 lsm_get_self_attr sys_lsm_get_self_attr + 460 o32 lsm_set_self_attr sys_lsm_set_self_attr + 461 o32 lsm_list_modules sys_lsm_list_modules ++462 o32 process_ksm_enable sys_process_ksm_enable ++463 o32 process_ksm_disable sys_process_ksm_disable ++464 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 e97c175b56f9..9b325ef36d52 100644 +index b236a84c4e12..d3157f90ef49 100644 --- a/arch/parisc/kernel/syscalls/syscall.tbl +++ b/arch/parisc/kernel/syscalls/syscall.tbl -@@ -451,3 +451,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -460,3 +460,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 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 20e50586e8a2..b62ba0834869 100644 +index 17173b82ca21..bf7920c3de18 100644 --- a/arch/powerpc/kernel/syscalls/syscall.tbl +++ b/arch/powerpc/kernel/syscalls/syscall.tbl -@@ -539,3 +539,6 @@ - 450 nospu set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -548,3 +548,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 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 0122cc156952..70165723f772 100644 +index 095bb86339a7..25421b092cbc 100644 --- a/arch/s390/kernel/syscalls/syscall.tbl +++ b/arch/s390/kernel/syscalls/syscall.tbl -@@ -455,3 +455,6 @@ - 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 fchmodat2 sys_fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status sys_process_ksm_status +@@ -464,3 +464,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable sys_process_ksm_disable ++464 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 e90d585c4d3e..80769b880b37 100644 +index 86fe269f0220..d6c06877a69a 100644 --- a/arch/sh/kernel/syscalls/syscall.tbl +++ b/arch/sh/kernel/syscalls/syscall.tbl -@@ -455,3 +455,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -464,3 +464,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 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 4ed06c71c43f..fb3514cce0e7 100644 +index b23d59313589..b749293082d9 100644 --- a/arch/sparc/kernel/syscalls/syscall.tbl +++ b/arch/sparc/kernel/syscalls/syscall.tbl -@@ -498,3 +498,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -507,3 +507,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 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 2d0b1bd866ea..80a57f6a8981 100644 +index 5f8591ce7f25..f652771d6e94 100644 --- a/arch/x86/entry/syscalls/syscall_32.tbl +++ b/arch/x86/entry/syscalls/syscall_32.tbl -@@ -457,3 +457,6 @@ - 450 i386 set_mempolicy_home_node sys_set_mempolicy_home_node - 451 i386 cachestat sys_cachestat - 452 i386 fchmodat2 sys_fchmodat2 -+453 i386 process_ksm_enable sys_process_ksm_enable -+454 i386 process_ksm_disable sys_process_ksm_disable -+455 i386 process_ksm_status sys_process_ksm_status +@@ -466,3 +466,6 @@ + 459 i386 lsm_get_self_attr sys_lsm_get_self_attr + 460 i386 lsm_set_self_attr sys_lsm_set_self_attr + 461 i386 lsm_list_modules sys_lsm_list_modules ++462 i386 process_ksm_enable sys_process_ksm_enable ++463 i386 process_ksm_disable sys_process_ksm_disable ++464 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 1d6eee30eceb..38faca76e9a0 100644 +index 7e8d46f4147f..0e86c5b0fd39 100644 --- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl -@@ -375,6 +375,9 @@ - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 - 453 64 map_shadow_stack sys_map_shadow_stack -+454 common process_ksm_enable sys_process_ksm_enable -+455 common process_ksm_disable sys_process_ksm_disable -+456 common process_ksm_status sys_process_ksm_status +@@ -383,6 +383,9 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 common process_ksm_status sys_process_ksm_status # # 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 fc1a4f3c81d9..83f5032b2526 100644 +index dd116598fb25..28e59bfe9474 100644 --- a/arch/xtensa/kernel/syscalls/syscall.tbl +++ b/arch/xtensa/kernel/syscalls/syscall.tbl -@@ -423,3 +423,6 @@ - 450 common set_mempolicy_home_node sys_set_mempolicy_home_node - 451 common cachestat sys_cachestat - 452 common fchmodat2 sys_fchmodat2 -+453 common process_ksm_enable sys_process_ksm_enable -+454 common process_ksm_disable sys_process_ksm_disable -+455 common process_ksm_status sys_process_ksm_status +@@ -432,3 +432,6 @@ + 459 common lsm_get_self_attr sys_lsm_get_self_attr + 460 common lsm_set_self_attr sys_lsm_set_self_attr + 461 common lsm_list_modules sys_lsm_list_modules ++462 common process_ksm_enable sys_process_ksm_enable ++463 common process_ksm_disable sys_process_ksm_disable ++464 common process_ksm_status sys_process_ksm_status diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h -index 22bc6bc147f8..da013ad43df9 100644 +index 77eb9b0e7685..c30bd8068c83 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h -@@ -799,6 +799,9 @@ asmlinkage long sys_madvise(unsigned long start, size_t len, int behavior); +@@ -818,6 +818,9 @@ asmlinkage long sys_madvise(unsigned long start, size_t len, int behavior); 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); @@ -17106,33 +9019,33 @@ index 22bc6bc147f8..da013ad43df9 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 abe087c53b4b..e393422e2983 100644 +index 75f00965ab15..c46daa8bda1e 100644 --- a/include/uapi/asm-generic/unistd.h +++ b/include/uapi/asm-generic/unistd.h -@@ -823,8 +823,17 @@ __SYSCALL(__NR_cachestat, sys_cachestat) - #define __NR_fchmodat2 452 - __SYSCALL(__NR_fchmodat2, sys_fchmodat2) +@@ -842,8 +842,17 @@ __SYSCALL(__NR_lsm_set_self_attr, sys_lsm_set_self_attr) + #define __NR_lsm_list_modules 461 + __SYSCALL(__NR_lsm_list_modules, sys_lsm_list_modules) -+#define __NR_process_ksm_enable 453 ++#define __NR_process_ksm_enable 462 +__SYSCALL(__NR_process_ksm_enable, sys_process_ksm_enable) + -+#define __NR_process_ksm_disable 454 ++#define __NR_process_ksm_disable 463 +__SYSCALL(__NR_process_ksm_disable, sys_process_ksm_disable) + -+#define __NR_process_ksm_status 455 ++#define __NR_process_ksm_status 464 +__SYSCALL(__NR_process_ksm_status, sys_process_ksm_status) + #undef __NR_syscalls --#define __NR_syscalls 453 -+#define __NR_syscalls 456 +-#define __NR_syscalls 462 ++#define __NR_syscalls 465 /* * 32 bit systems traditionally used different diff --git a/kernel/sys.c b/kernel/sys.c -index 7a4ae6d5aecd..8e0080338bfc 100644 +index f8e543f1e38a..a02487c059f3 100644 --- a/kernel/sys.c +++ b/kernel/sys.c -@@ -2751,6 +2751,153 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, +@@ -2764,6 +2764,153 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, return error; } @@ -17287,10 +9200,10 @@ index 7a4ae6d5aecd..8e0080338bfc 100644 struct getcpu_cache __user *, unused) { diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c -index e137c1385c56..2d9772d11c92 100644 +index faad00cce269..c7c9eb656468 100644 --- a/kernel/sys_ni.c +++ b/kernel/sys_ni.c -@@ -184,6 +184,9 @@ COND_SYSCALL(mincore); +@@ -188,6 +188,9 @@ COND_SYSCALL(mincore); COND_SYSCALL(madvise); COND_SYSCALL(process_madvise); COND_SYSCALL(process_mrelease); @@ -17301,11 +9214,11 @@ index e137c1385c56..2d9772d11c92 100644 COND_SYSCALL(mbind); COND_SYSCALL(get_mempolicy); -- -2.43.0 +2.44.0 -From 9e8cfc68f6c3690c813f08b58de6f4b505422c98 Mon Sep 17 00:00:00 2001 +From 0634ad09765970da5be85d61cb4b8b4b38adb3c0 Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Wed, 29 Nov 2023 19:56:57 +0100 +Date: Thu, 1 Feb 2024 16:54:48 +0100 Subject: [PATCH 7/7] zstd Signed-off-by: Peter Jung @@ -17325,7 +9238,7 @@ Signed-off-by: Peter Jung lib/zstd/common/error_private.c | 12 +- lib/zstd/common/error_private.h | 3 +- lib/zstd/common/fse.h | 89 +- - lib/zstd/common/fse_decompress.c | 96 +- + lib/zstd/common/fse_decompress.c | 94 +- lib/zstd/common/huf.h | 222 +-- lib/zstd/common/mem.h | 2 +- lib/zstd/common/portability_macros.h | 26 +- @@ -17360,7 +9273,7 @@ Signed-off-by: Peter Jung lib/zstd/decompress/huf_decompress.c | 770 ++++--- lib/zstd/decompress/zstd_ddict.c | 9 +- lib/zstd/decompress/zstd_ddict.h | 3 +- - lib/zstd/decompress/zstd_decompress.c | 261 ++- + lib/zstd/decompress/zstd_decompress.c | 263 ++- lib/zstd/decompress/zstd_decompress_block.c | 283 ++- lib/zstd/decompress/zstd_decompress_block.h | 8 +- .../decompress/zstd_decompress_internal.h | 7 +- @@ -19406,7 +11319,7 @@ index 4507043b2287..c4e25a219142 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 a0d06095be83..99ce8fa54d08 100644 +index 8dcb8ca39767..99ce8fa54d08 100644 --- a/lib/zstd/common/fse_decompress.c +++ b/lib/zstd/common/fse_decompress.c @@ -1,6 +1,7 @@ @@ -19518,7 +11431,7 @@ index a0d06095be83..99ce8fa54d08 100644 FORCE_INLINE_TEMPLATE size_t FSE_decompress_usingDTable_generic( void* dst, size_t maxDstSize, -@@ -290,29 +236,9 @@ FORCE_INLINE_TEMPLATE size_t FSE_decompress_usingDTable_generic( +@@ -290,26 +236,6 @@ FORCE_INLINE_TEMPLATE size_t FSE_decompress_usingDTable_generic( return op-ostart; } @@ -19544,11 +11457,7 @@ index a0d06095be83..99ce8fa54d08 100644 - typedef struct { short ncount[FSE_MAX_SYMBOL_VALUE + 1]; -- FSE_DTable dtable[1]; /* Dynamically sized */ -+ FSE_DTable dtable[]; /* Dynamically sized */ - } FSE_DecompressWksp; - - + FSE_DTable dtable[]; /* Dynamically sized */ @@ -342,7 +268,8 @@ FORCE_INLINE_TEMPLATE size_t FSE_decompress_wksp_body( } @@ -29919,7 +21828,7 @@ index 8c1a79d666f8..de459a0dacd1 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 6b3177c94711..03dbdf39109f 100644 +index 6b3177c94711..4b3b88715f18 100644 --- a/lib/zstd/decompress/zstd_decompress.c +++ b/lib/zstd/decompress/zstd_decompress.c @@ -1,5 +1,6 @@ @@ -30020,7 +21929,7 @@ index 6b3177c94711..03dbdf39109f 100644 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 @@ static size_t readSkippableFrameSize(void const* src, size_t srcSize) +@@ -540,61 +567,62 @@ static size_t readSkippableFrameSize(void const* src, size_t srcSize) sizeU32 = MEM_readLE32((BYTE const*)src + ZSTD_FRAMEIDSIZE); RETURN_ERROR_IF((U32)(sizeU32 + ZSTD_SKIPPABLEHEADERSIZE) < sizeU32, frameParameter_unsupported, ""); @@ -30094,8 +22003,11 @@ index 6b3177c94711..03dbdf39109f 100644 + * @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 @@ unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize) +- unsigned long long totalDstSize = 0; ++ U64 totalDstSize = 0; + + while (srcSize >= ZSTD_startingInputLength(ZSTD_f_zstd1)) { + U32 const magicNumber = MEM_readLE32(src); if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { size_t const skippableSize = readSkippableFrameSize(src, srcSize); @@ -30118,7 +22030,7 @@ index 6b3177c94711..03dbdf39109f 100644 - /* check for overflow */ - if (totalDstSize + ret < totalDstSize) return ZSTD_CONTENTSIZE_ERROR; - totalDstSize += ret; -+ if (totalDstSize + fcs < totalDstSize) ++ if (U64_MAX - totalDstSize < fcs) + return ZSTD_CONTENTSIZE_ERROR; /* check for overflow */ + totalDstSize += fcs; } @@ -31138,5 +23050,4 @@ index f4ed952ed485..7d31518e9d5a 100644 EXPORT_SYMBOL(zstd_reset_dstream); -- -2.43.0 - +2.44.0 diff --git a/patches/cachyos/0002-ntsync.patch b/patches/cachyos/0002-ntsync.patch new file mode 100644 index 0000000..fa47d21 --- /dev/null +++ b/patches/cachyos/0002-ntsync.patch @@ -0,0 +1,3187 @@ +Subject: [PATCH v2 0/31] NT synchronization primitive driver +From: Elizabeth Figura +Date: Mon, 19 Feb 2024 16:38:02 -0600 +Message-Id: <20240219223833.95710-1-zfigura@codeweavers.com> +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +This patch series introduces a new char misc driver, /dev/ntsync, which is used +to implement Windows NT synchronization primitives. + + Documentation/userspace-api/index.rst | 1 + + Documentation/userspace-api/ioctl/ioctl-number.rst | 2 + + Documentation/userspace-api/ntsync.rst | 399 ++++++ + MAINTAINERS | 9 + + drivers/misc/Kconfig | 11 + + drivers/misc/Makefile | 1 + + drivers/misc/ntsync.c | 1159 ++++++++++++++++ + include/uapi/linux/ntsync.h | 62 + + tools/testing/selftests/Makefile | 1 + + tools/testing/selftests/drivers/ntsync/Makefile | 8 + + tools/testing/selftests/drivers/ntsync/config | 1 + + tools/testing/selftests/drivers/ntsync/ntsync.c | 1407 ++++++++++++++++++++ + 12 files changed, 3061 insertions(+) +diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst +index 09f61bd2ac2e..f5a72ed27def 100644 +--- a/Documentation/userspace-api/index.rst ++++ b/Documentation/userspace-api/index.rst +@@ -34,6 +34,7 @@ place where this information is gathered. + tee + isapnp + dcdbas ++ ntsync + + .. only:: subproject and html + +diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst +index 457e16f06e04..2f5c6994f042 100644 +--- a/Documentation/userspace-api/ioctl/ioctl-number.rst ++++ b/Documentation/userspace-api/ioctl/ioctl-number.rst +@@ -173,6 +173,8 @@ Code Seq# Include File Comments + 'M' 00-0F drivers/video/fsl-diu-fb.h conflict! + 'N' 00-1F drivers/usb/scanner.h + 'N' 40-7F drivers/block/nvme.c ++'N' 80-8F uapi/linux/ntsync.h NT synchronization primitives ++ + 'O' 00-06 mtd/ubi-user.h UBI + 'P' all linux/soundcard.h conflict! + 'P' 60-6F sound/sscape_ioctl.h conflict! +diff --git a/Documentation/userspace-api/ntsync.rst b/Documentation/userspace-api/ntsync.rst +new file mode 100644 +index 000000000000..202c2350d3af +--- /dev/null ++++ b/Documentation/userspace-api/ntsync.rst +@@ -0,0 +1,399 @@ ++=================================== ++NT synchronization primitive driver ++=================================== ++ ++This page documents the user-space API for the ntsync driver. ++ ++ntsync is a support driver for emulation of NT synchronization ++primitives by user-space NT emulators. It exists because implementation ++in user-space, using existing tools, cannot match Windows performance ++while offering accurate semantics. It is implemented entirely in ++software, and does not drive any hardware device. ++ ++This interface is meant as a compatibility tool only, and should not ++be used for general synchronization. Instead use generic, versatile ++interfaces such as futex(2) and poll(2). ++ ++Synchronization primitives ++========================== ++ ++The ntsync driver exposes three types of synchronization primitives: ++semaphores, mutexes, and events. ++ ++A semaphore holds a single volatile 32-bit counter, and a static 32-bit ++integer denoting the maximum value. It is considered signaled when the ++counter is nonzero. The counter is decremented by one when a wait is ++satisfied. Both the initial and maximum count are established when the ++semaphore is created. ++ ++A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit ++identifier denoting its owner. A mutex is considered signaled when its ++owner is zero (indicating that it is not owned). The recursion count is ++incremented when a wait is satisfied, and ownership is set to the given ++identifier. ++ ++A mutex also holds an internal flag denoting whether its previous owner ++has died; such a mutex is said to be abandoned. Owner death is not ++tracked automatically based on thread death, but rather must be ++communicated using ``NTSYNC_IOC_MUTEX_KILL``. An abandoned mutex is ++inherently considered unowned. ++ ++Except for the "unowned" semantics of zero, the actual value of the ++owner identifier is not interpreted by the ntsync driver at all. The ++intended use is to store a thread identifier; however, the ntsync ++driver does not actually validate that a calling thread provides ++consistent or unique identifiers. ++ ++An event holds a volatile boolean state denoting whether it is signaled ++or not. There are two types of events, auto-reset and manual-reset. An ++auto-reset event is designaled when a wait is satisfied; a manual-reset ++event is not. The event type is specified when the event is created. ++ ++Unless specified otherwise, all operations on an object are atomic and ++totally ordered with respect to other operations on the same object. ++ ++Objects are represented by files. When all file descriptors to an ++object are closed, that object is deleted. ++ ++Char device ++=========== ++ ++The ntsync driver creates a single char device /dev/ntsync. Each file ++description opened on the device represents a unique instance intended ++to back an individual NT virtual machine. Objects created by one ntsync ++instance may only be used with other objects created by the same ++instance. ++ ++ioctl reference ++=============== ++ ++All operations on the device are done through ioctls. There are four ++structures used in ioctl calls:: ++ ++ struct ntsync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++ }; ++ ++ struct ntsync_mutex_args { ++ __u32 mutex; ++ __u32 owner; ++ __u32 count; ++ }; ++ ++ struct ntsync_event_args { ++ __u32 event; ++ __u32 signaled; ++ __u32 manual; ++ }; ++ ++ struct ntsync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 alert; ++ __u32 flags; ++ __u32 pad; ++ }; ++ ++Depending on the ioctl, members of the structure may be used as input, ++output, or not at all. All ioctls return 0 on success. ++ ++The ioctls on the device file are as follows: ++ ++.. c:macro:: NTSYNC_IOC_CREATE_SEM ++ ++ Create a semaphore object. Takes a pointer to struct ++ :c:type:`ntsync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - On output, contains a file descriptor to the created semaphore. ++ * - ``count`` ++ - Initial count of the semaphore. ++ * - ``max`` ++ - Maximum count of the semaphore. ++ ++ Fails with ``EINVAL`` if ``count`` is greater than ``max``. ++ ++.. c:macro:: NTSYNC_IOC_CREATE_MUTEX ++ ++ Create a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - On output, contains a file descriptor to the created mutex. ++ * - ``count`` ++ - Initial recursion count of the mutex. ++ * - ``owner`` ++ - Initial owner of the mutex. ++ ++ If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is ++ zero and ``count`` is nonzero, the function fails with ``EINVAL``. ++ ++.. c:macro:: NTSYNC_IOC_CREATE_EVENT ++ ++ Create an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - On output, contains a file descriptor to the created event. ++ * - ``signaled`` ++ - If nonzero, the event is initially signaled, otherwise ++ nonsignaled. ++ * - ``manual`` ++ - If nonzero, the event is a manual-reset event, otherwise ++ auto-reset. ++ ++The ioctls on the individual objects are as follows: ++ ++.. c:macro:: NTSYNC_IOC_SEM_POST ++ ++ Post to a semaphore object. Takes a pointer to a 32-bit integer, ++ which on input holds the count to be added to the semaphore, and on ++ output contains its previous count. ++ ++ If adding to the semaphore's current count would raise the latter ++ past the semaphore's maximum count, the ioctl fails with ++ ``EOVERFLOW`` and the semaphore is not affected. If raising the ++ semaphore's count causes it to become signaled, eligible threads ++ waiting on this semaphore will be woken and the semaphore's count ++ decremented appropriately. ++ ++.. c:macro:: NTSYNC_IOC_MUTEX_UNLOCK ++ ++ Release a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - Ignored. ++ * - ``owner`` ++ - Specifies the owner trying to release this mutex. ++ * - ``count`` ++ - On output, contains the previous recursion count. ++ ++ If ``owner`` is zero, the ioctl fails with ``EINVAL``. If ``owner`` ++ is not the current owner of the mutex, the ioctl fails with ++ ``EPERM``. ++ ++ The mutex's count will be decremented by one. If decrementing the ++ mutex's count causes it to become zero, the mutex is marked as ++ unowned and signaled, and eligible threads waiting on it will be ++ woken as appropriate. ++ ++.. c:macro:: NTSYNC_IOC_SET_EVENT ++ ++ Signal an event object. Takes a pointer to a 32-bit integer, which on ++ output contains the previous state of the event. ++ ++ Eligible threads will be woken, and auto-reset events will be ++ designaled appropriately. ++ ++.. c:macro:: NTSYNC_IOC_RESET_EVENT ++ ++ Designal an event object. Takes a pointer to a 32-bit integer, which ++ on output contains the previous state of the event. ++ ++.. c:macro:: NTSYNC_IOC_PULSE_EVENT ++ ++ Wake threads waiting on an event object while leaving it in an ++ unsignaled state. Takes a pointer to a 32-bit integer, which on ++ output contains the previous state of the event. ++ ++ A pulse operation can be thought of as a set followed by a reset, ++ performed as a single atomic operation. If two threads are waiting on ++ an auto-reset event which is pulsed, only one will be woken. If two ++ threads are waiting a manual-reset event which is pulsed, both will ++ be woken. However, in both cases, the event will be unsignaled ++ afterwards, and a simultaneous read operation will always report the ++ event as unsignaled. ++ ++.. c:macro:: NTSYNC_IOC_READ_SEM ++ ++ Read the current state of a semaphore object. Takes a pointer to ++ struct :c:type:`ntsync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - Ignored. ++ * - ``count`` ++ - On output, contains the current count of the semaphore. ++ * - ``max`` ++ - On output, contains the maximum count of the semaphore. ++ ++.. c:macro:: NTSYNC_IOC_READ_MUTEX ++ ++ Read the current state of a mutex object. Takes a pointer to struct ++ :c:type:`ntsync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - Ignored. ++ * - ``owner`` ++ - On output, contains the current owner of the mutex, or zero ++ if the mutex is not currently owned. ++ * - ``count`` ++ - On output, contains the current recursion count of the mutex. ++ ++ If the mutex is marked as abandoned, the function fails with ++ ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to ++ zero. ++ ++.. c:macro:: NTSYNC_IOC_READ_EVENT ++ ++ Read the current state of an event object. Takes a pointer to struct ++ :c:type:`ntsync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Ignored. ++ * - ``signaled`` ++ - On output, contains the current state of the event. ++ * - ``manual`` ++ - On output, contains 1 if the event is a manual-reset event, ++ and 0 otherwise. ++ ++.. c:macro:: NTSYNC_IOC_KILL_OWNER ++ ++ Mark a mutex as unowned and abandoned if it is owned by the given ++ owner. Takes an input-only pointer to a 32-bit integer denoting the ++ owner. If the owner is zero, the ioctl fails with ``EINVAL``. If the ++ owner does not own the mutex, the function fails with ``EPERM``. ++ ++ Eligible threads waiting on the mutex will be woken as appropriate ++ (and such waits will fail with ``EOWNERDEAD``, as described below). ++ ++.. c:macro:: NTSYNC_IOC_WAIT_ANY ++ ++ Poll on any of a list of objects, atomically acquiring at most one. ++ Takes a pointer to struct :c:type:`ntsync_wait_args`, which is ++ used as follows: ++ ++ .. list-table:: ++ ++ * - ``timeout`` ++ - Absolute timeout in nanoseconds. If ``NTSYNC_WAIT_REALTIME`` ++ is set, the timeout is measured against the REALTIME clock; ++ otherwise it is measured against the MONOTONIC clock. If the ++ timeout is equal to or earlier than the current time, the ++ function returns immediately without sleeping. If ``timeout`` ++ is U64_MAX, the function will sleep until an object is ++ signaled, and will not fail with ``ETIMEDOUT``. ++ * - ``objs`` ++ - Pointer to an array of ``count`` file descriptors ++ (specified as an integer so that the structure has the same ++ size regardless of architecture). If any object is ++ invalid, the function fails with ``EINVAL``. ++ * - ``count`` ++ - Number of objects specified in the ``objs`` array. ++ If greater than ``NTSYNC_MAX_WAIT_COUNT``, the function fails ++ with ``EINVAL``. ++ * - ``owner`` ++ - Mutex owner identifier. If any object in ``objs`` is a mutex, ++ the ioctl will attempt to acquire that mutex on behalf of ++ ``owner``. If ``owner`` is zero, the ioctl fails with ++ ``EINVAL``. ++ * - ``index`` ++ - On success, contains the index (into ``objs``) of the object ++ which was signaled. If ``alert`` was signaled instead, ++ this contains ``count``. ++ * - ``alert`` ++ - Optional event object file descriptor. If nonzero, this ++ specifies an "alert" event object which, if signaled, will ++ terminate the wait. If nonzero, the identifier must point to a ++ valid event. ++ * - ``flags`` ++ - Zero or more flags. Currently the only flag is ++ ``NTSYNC_WAIT_REALTIME``, which causes the timeout to be ++ measured against the REALTIME clock instead of MONOTONIC. ++ * - ``pad`` ++ - Unused, must be set to zero. ++ ++ This function attempts to acquire one of the given objects. If unable ++ to do so, it sleeps until an object becomes signaled, subsequently ++ acquiring it, or the timeout expires. In the latter case the ioctl ++ fails with ``ETIMEDOUT``. The function only acquires one object, even ++ if multiple objects are signaled. ++ ++ A semaphore is considered to be signaled if its count is nonzero, and ++ is acquired by decrementing its count by one. A mutex is considered ++ to be signaled if it is unowned or if its owner matches the ``owner`` ++ argument, and is acquired by incrementing its recursion count by one ++ and setting its owner to the ``owner`` argument. An auto-reset event ++ is acquired by designaling it; a manual-reset event is not affected ++ by acquisition. ++ ++ Acquisition is atomic and totally ordered with respect to other ++ operations on the same object. If two wait operations (with different ++ ``owner`` identifiers) are queued on the same mutex, only one is ++ signaled. If two wait operations are queued on the same semaphore, ++ and a value of one is posted to it, only one is signaled. The order ++ in which threads are signaled is not specified. ++ ++ If an abandoned mutex is acquired, the ioctl fails with ++ ``EOWNERDEAD``. Although this is a failure return, the function may ++ otherwise be considered successful. The mutex is marked as owned by ++ the given owner (with a recursion count of 1) and as no longer ++ abandoned, and ``index`` is still set to the index of the mutex. ++ ++ The ``alert`` argument is an "extra" event which can terminate the ++ wait, independently of all other objects. If members of ``objs`` and ++ ``alert`` are both simultaneously signaled, a member of ``objs`` will ++ always be given priority and acquired first. ++ ++ It is valid to pass the same object more than once, including by ++ passing the same event in the ``objs`` array and in ``alert``. If a ++ wakeup occurs due to that object being signaled, ``index`` is set to ++ the lowest index corresponding to that object. ++ ++ The function may fail with ``EINTR`` if a signal is received. ++ ++.. c:macro:: NTSYNC_IOC_WAIT_ALL ++ ++ Poll on a list of objects, atomically acquiring all of them. Takes a ++ pointer to struct :c:type:`ntsync_wait_args`, which is used ++ identically to ``NTSYNC_IOC_WAIT_ANY``, except that ``index`` is ++ always filled with zero on success if not woken via alert. ++ ++ This function attempts to simultaneously acquire all of the given ++ objects. If unable to do so, it sleeps until all objects become ++ simultaneously signaled, subsequently acquiring them, or the timeout ++ expires. In the latter case the ioctl fails with ``ETIMEDOUT`` and no ++ objects are modified. ++ ++ Objects may become signaled and subsequently designaled (through ++ acquisition by other threads) while this thread is sleeping. Only ++ once all objects are simultaneously signaled does the ioctl acquire ++ them and return. The entire acquisition is atomic and totally ordered ++ with respect to other operations on any of the given objects. ++ ++ If an abandoned mutex is acquired, the ioctl fails with ++ ``EOWNERDEAD``. Similarly to ``NTSYNC_IOC_WAIT_ANY``, all objects are ++ nevertheless marked as acquired. Note that if multiple mutex objects ++ are specified, there is no way to know which were marked as ++ abandoned. ++ ++ As with "any" waits, the ``alert`` argument is an "extra" event which ++ can terminate the wait. Critically, however, an "all" wait will ++ succeed if all members in ``objs`` are signaled, *or* if ``alert`` is ++ signaled. In the latter case ``index`` will be set to ``count``. As ++ with "any" waits, if both conditions are filled, the former takes ++ priority, and objects in ``objs`` will be acquired. ++ ++ Unlike ``NTSYNC_IOC_WAIT_ANY``, it is not valid to pass the same ++ object more than once, nor is it valid to pass the same object in ++ ``objs`` and in ``alert``. If this is attempted, the function fails ++ with ``EINVAL``. +diff --git a/MAINTAINERS b/MAINTAINERS +index 9ed4d3868539..d83dd35d9f73 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -15595,6 +15595,15 @@ T: git https://github.com/Paragon-Software-Group/linux-ntfs3.git + F: Documentation/filesystems/ntfs3.rst + F: fs/ntfs3/ + ++NTSYNC SYNCHRONIZATION PRIMITIVE DRIVER ++M: Elizabeth Figura ++L: wine-devel@winehq.org ++S: Supported ++F: Documentation/userspace-api/ntsync.rst ++F: drivers/misc/ntsync.c ++F: include/uapi/linux/ntsync.h ++F: tools/testing/selftests/drivers/ntsync/ ++ + NUBUS SUBSYSTEM + M: Finn Thain + L: linux-m68k@lists.linux-m68k.org +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index 4fb291f0bf7c..801ed229ed7d 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -506,6 +506,17 @@ config OPEN_DICE + + If unsure, say N. + ++config NTSYNC ++ tristate "NT synchronization primitive emulation" ++ help ++ This module provides kernel support for emulation of Windows NT ++ synchronization primitives. It is not a hardware driver. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ntsync. ++ ++ If unsure, say N. ++ + config VCPU_STALL_DETECTOR + tristate "Guest vCPU stall detector" + depends on OF && HAS_IOMEM +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index ea6ea5bbbc9c..153a3f4837e8 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -59,6 +59,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic/ + obj-$(CONFIG_UACCE) += uacce/ + obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o + obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o ++obj-$(CONFIG_NTSYNC) += ntsync.o + obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o + obj-$(CONFIG_OPEN_DICE) += open-dice.o + obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ +diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c +new file mode 100644 +index 000000000000..f54c81dada3d +--- /dev/null ++++ b/drivers/misc/ntsync.c +@@ -0,0 +1,1159 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * ntsync.c - Kernel driver for NT synchronization primitives ++ * ++ * Copyright (C) 2024 Elizabeth Figura ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define NTSYNC_NAME "ntsync" ++ ++enum ntsync_type { ++ NTSYNC_TYPE_SEM, ++ NTSYNC_TYPE_MUTEX, ++ NTSYNC_TYPE_EVENT, ++}; ++ ++/* ++ * Individual synchronization primitives are represented by ++ * struct ntsync_obj, and each primitive is backed by a file. ++ * ++ * The whole namespace is represented by a struct ntsync_device also ++ * backed by a file. ++ * ++ * Both rely on struct file for reference counting. Individual ++ * ntsync_obj objects take a reference to the device when created. ++ * Wait operations take a reference to each object being waited on for ++ * the duration of the wait. ++ */ ++ ++struct ntsync_obj { ++ spinlock_t lock; ++ ++ enum ntsync_type type; ++ ++ struct file *file; ++ struct ntsync_device *dev; ++ ++ /* The following fields are protected by the object lock. */ ++ union { ++ struct { ++ __u32 count; ++ __u32 max; ++ } sem; ++ struct { ++ __u32 count; ++ __u32 owner; ++ bool ownerdead; ++ } mutex; ++ struct { ++ bool manual; ++ bool signaled; ++ } event; ++ } u; ++ ++ /* ++ * any_waiters is protected by the object lock, but all_waiters is ++ * protected by the device wait_all_lock. ++ */ ++ struct list_head any_waiters; ++ struct list_head all_waiters; ++ ++ /* ++ * Hint describing how many tasks are queued on this object in a ++ * wait-all operation. ++ * ++ * Any time we do a wake, we may need to wake "all" waiters as well as ++ * "any" waiters. In order to atomically wake "all" waiters, we must ++ * lock all of the objects, and that means grabbing the wait_all_lock ++ * below (and, due to lock ordering rules, before locking this object). ++ * However, wait-all is a rare operation, and grabbing the wait-all ++ * lock for every wake would create unnecessary contention. ++ * Therefore we first check whether all_hint is zero, and, if it is, ++ * we skip trying to wake "all" waiters. ++ * ++ * This hint isn't protected by any lock. It might change during the ++ * course of a wake, but there's no meaningful race there; it's only a ++ * hint. ++ * ++ * Since wait requests must originate from user-space threads, we're ++ * limited here by PID_MAX_LIMIT, so there's no risk of overflow. ++ */ ++ atomic_t all_hint; ++}; ++ ++struct ntsync_q_entry { ++ struct list_head node; ++ struct ntsync_q *q; ++ struct ntsync_obj *obj; ++ __u32 index; ++}; ++ ++struct ntsync_q { ++ struct task_struct *task; ++ __u32 owner; ++ ++ /* ++ * Protected via atomic_cmpxchg(). Only the thread that wins the ++ * compare-and-swap may actually change object states and wake this ++ * task. ++ */ ++ atomic_t signaled; ++ ++ bool all; ++ bool ownerdead; ++ __u32 count; ++ struct ntsync_q_entry entries[]; ++}; ++ ++struct ntsync_device { ++ /* ++ * Wait-all operations must atomically grab all objects, and be totally ++ * ordered with respect to each other and wait-any operations. ++ * If one thread is trying to acquire several objects, another thread ++ * cannot touch the object at the same time. ++ * ++ * We achieve this by grabbing multiple object locks at the same time. ++ * However, this creates a lock ordering problem. To solve that problem, ++ * wait_all_lock is taken first whenever multiple objects must be locked ++ * at the same time. ++ */ ++ spinlock_t wait_all_lock; ++ ++ struct file *file; ++}; ++ ++static bool is_signaled(struct ntsync_obj *obj, __u32 owner) ++{ ++ lockdep_assert_held(&obj->lock); ++ ++ switch (obj->type) { ++ case NTSYNC_TYPE_SEM: ++ return !!obj->u.sem.count; ++ case NTSYNC_TYPE_MUTEX: ++ if (obj->u.mutex.owner && obj->u.mutex.owner != owner) ++ return false; ++ return obj->u.mutex.count < UINT_MAX; ++ case NTSYNC_TYPE_EVENT: ++ return obj->u.event.signaled; ++ } ++ ++ WARN(1, "bad object type %#x\n", obj->type); ++ return false; ++} ++ ++/* ++ * "locked_obj" is an optional pointer to an object which is already locked and ++ * should not be locked again. This is necessary so that changing an object's ++ * state and waking it can be a single atomic operation. ++ */ ++static void try_wake_all(struct ntsync_device *dev, struct ntsync_q *q, ++ struct ntsync_obj *locked_obj) ++{ ++ __u32 count = q->count; ++ bool can_wake = true; ++ __u32 i; ++ ++ lockdep_assert_held(&dev->wait_all_lock); ++ if (locked_obj) ++ lockdep_assert_held(&locked_obj->lock); ++ ++ for (i = 0; i < count; i++) { ++ if (q->entries[i].obj != locked_obj) ++ spin_lock_nest_lock(&q->entries[i].obj->lock, &dev->wait_all_lock); ++ } ++ ++ for (i = 0; i < count; i++) { ++ if (!is_signaled(q->entries[i].obj, q->owner)) { ++ can_wake = false; ++ break; ++ } ++ } ++ ++ if (can_wake && atomic_cmpxchg(&q->signaled, -1, 0) == -1) { ++ for (i = 0; i < count; i++) { ++ struct ntsync_obj *obj = q->entries[i].obj; ++ ++ switch (obj->type) { ++ case NTSYNC_TYPE_SEM: ++ obj->u.sem.count--; ++ break; ++ case NTSYNC_TYPE_MUTEX: ++ if (obj->u.mutex.ownerdead) ++ q->ownerdead = true; ++ obj->u.mutex.ownerdead = false; ++ obj->u.mutex.count++; ++ obj->u.mutex.owner = q->owner; ++ break; ++ case NTSYNC_TYPE_EVENT: ++ if (!obj->u.event.manual) ++ obj->u.event.signaled = false; ++ break; ++ } ++ } ++ wake_up_process(q->task); ++ } ++ ++ for (i = 0; i < count; i++) { ++ if (q->entries[i].obj != locked_obj) ++ spin_unlock(&q->entries[i].obj->lock); ++ } ++} ++ ++static void try_wake_all_obj(struct ntsync_device *dev, struct ntsync_obj *obj) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&dev->wait_all_lock); ++ lockdep_assert_held(&obj->lock); ++ ++ list_for_each_entry(entry, &obj->all_waiters, node) ++ try_wake_all(dev, entry->q, obj); ++} ++ ++static void try_wake_any_sem(struct ntsync_obj *sem) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&sem->lock); ++ ++ list_for_each_entry(entry, &sem->any_waiters, node) { ++ struct ntsync_q *q = entry->q; ++ ++ if (!sem->u.sem.count) ++ break; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ sem->u.sem.count--; ++ wake_up_process(q->task); ++ } ++ } ++} ++ ++static void try_wake_any_mutex(struct ntsync_obj *mutex) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&mutex->lock); ++ ++ list_for_each_entry(entry, &mutex->any_waiters, node) { ++ struct ntsync_q *q = entry->q; ++ ++ if (mutex->u.mutex.count == UINT_MAX) ++ break; ++ if (mutex->u.mutex.owner && mutex->u.mutex.owner != q->owner) ++ continue; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ if (mutex->u.mutex.ownerdead) ++ q->ownerdead = true; ++ mutex->u.mutex.ownerdead = false; ++ mutex->u.mutex.count++; ++ mutex->u.mutex.owner = q->owner; ++ wake_up_process(q->task); ++ } ++ } ++} ++ ++static void try_wake_any_event(struct ntsync_obj *event) ++{ ++ struct ntsync_q_entry *entry; ++ ++ lockdep_assert_held(&event->lock); ++ ++ list_for_each_entry(entry, &event->any_waiters, node) { ++ struct ntsync_q *q = entry->q; ++ ++ if (!event->u.event.signaled) ++ break; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ if (!event->u.event.manual) ++ event->u.event.signaled = false; ++ wake_up_process(q->task); ++ } ++ } ++} ++ ++/* ++ * Actually change the semaphore state, returning -EOVERFLOW if it is made ++ * invalid. ++ */ ++static int post_sem_state(struct ntsync_obj *sem, __u32 count) ++{ ++ __u32 sum; ++ ++ lockdep_assert_held(&sem->lock); ++ ++ if (check_add_overflow(sem->u.sem.count, count, &sum) || ++ sum > sem->u.sem.max) ++ return -EOVERFLOW; ++ ++ sem->u.sem.count = sum; ++ return 0; ++} ++ ++static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) ++{ ++ struct ntsync_device *dev = sem->dev; ++ __u32 __user *user_args = argp; ++ __u32 prev_count; ++ __u32 args; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ if (sem->type != NTSYNC_TYPE_SEM) ++ return -EINVAL; ++ ++ if (atomic_read(&sem->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&sem->lock, &dev->wait_all_lock); ++ ++ prev_count = sem->u.sem.count; ++ ret = post_sem_state(sem, args); ++ if (!ret) { ++ try_wake_all_obj(dev, sem); ++ try_wake_any_sem(sem); ++ } ++ ++ spin_unlock(&sem->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&sem->lock); ++ ++ prev_count = sem->u.sem.count; ++ ret = post_sem_state(sem, args); ++ if (!ret) ++ try_wake_any_sem(sem); ++ ++ spin_unlock(&sem->lock); ++ } ++ ++ if (!ret && put_user(prev_count, user_args)) ++ ret = -EFAULT; ++ ++ return ret; ++} ++ ++/* ++ * Actually change the mutex state, returning -EPERM if not the owner. ++ */ ++static int unlock_mutex_state(struct ntsync_obj *mutex, ++ const struct ntsync_mutex_args *args) ++{ ++ lockdep_assert_held(&mutex->lock); ++ ++ if (mutex->u.mutex.owner != args->owner) ++ return -EPERM; ++ ++ if (!--mutex->u.mutex.count) ++ mutex->u.mutex.owner = 0; ++ return 0; ++} ++ ++static int ntsync_mutex_unlock(struct ntsync_obj *mutex, void __user *argp) ++{ ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_device *dev = mutex->dev; ++ struct ntsync_mutex_args args; ++ __u32 prev_count; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ if (!args.owner) ++ return -EINVAL; ++ ++ if (mutex->type != NTSYNC_TYPE_MUTEX) ++ return -EINVAL; ++ ++ if (atomic_read(&mutex->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&mutex->lock, &dev->wait_all_lock); ++ ++ prev_count = mutex->u.mutex.count; ++ ret = unlock_mutex_state(mutex, &args); ++ if (!ret) { ++ try_wake_all_obj(dev, mutex); ++ try_wake_any_mutex(mutex); ++ } ++ ++ spin_unlock(&mutex->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&mutex->lock); ++ ++ prev_count = mutex->u.mutex.count; ++ ret = unlock_mutex_state(mutex, &args); ++ if (!ret) ++ try_wake_any_mutex(mutex); ++ ++ spin_unlock(&mutex->lock); ++ } ++ ++ if (!ret && put_user(prev_count, &user_args->count)) ++ ret = -EFAULT; ++ ++ return ret; ++} ++ ++/* ++ * Actually change the mutex state to mark its owner as dead, ++ * returning -EPERM if not the owner. ++ */ ++static int kill_mutex_state(struct ntsync_obj *mutex, __u32 owner) ++{ ++ lockdep_assert_held(&mutex->lock); ++ ++ if (mutex->u.mutex.owner != owner) ++ return -EPERM; ++ ++ mutex->u.mutex.ownerdead = true; ++ mutex->u.mutex.owner = 0; ++ mutex->u.mutex.count = 0; ++ return 0; ++} ++ ++static int ntsync_mutex_kill(struct ntsync_obj *mutex, void __user *argp) ++{ ++ struct ntsync_device *dev = mutex->dev; ++ __u32 owner; ++ int ret; ++ ++ if (get_user(owner, (__u32 __user *)argp)) ++ return -EFAULT; ++ if (!owner) ++ return -EINVAL; ++ ++ if (mutex->type != NTSYNC_TYPE_MUTEX) ++ return -EINVAL; ++ ++ if (atomic_read(&mutex->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&mutex->lock, &dev->wait_all_lock); ++ ++ ret = kill_mutex_state(mutex, owner); ++ if (!ret) { ++ try_wake_all_obj(dev, mutex); ++ try_wake_any_mutex(mutex); ++ } ++ ++ spin_unlock(&mutex->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&mutex->lock); ++ ++ ret = kill_mutex_state(mutex, owner); ++ if (!ret) ++ try_wake_any_mutex(mutex); ++ ++ spin_unlock(&mutex->lock); ++ } ++ ++ return ret; ++} ++ ++static int ntsync_event_set(struct ntsync_obj *event, void __user *argp, bool pulse) ++{ ++ struct ntsync_device *dev = event->dev; ++ __u32 prev_state; ++ ++ if (event->type != NTSYNC_TYPE_EVENT) ++ return -EINVAL; ++ ++ if (atomic_read(&event->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock_nest_lock(&event->lock, &dev->wait_all_lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = true; ++ try_wake_all_obj(dev, event); ++ try_wake_any_event(event); ++ if (pulse) ++ event->u.event.signaled = false; ++ ++ spin_unlock(&event->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&event->lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = true; ++ try_wake_any_event(event); ++ if (pulse) ++ event->u.event.signaled = false; ++ ++ spin_unlock(&event->lock); ++ } ++ ++ if (put_user(prev_state, (__u32 __user *)argp)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int ntsync_event_reset(struct ntsync_obj *event, void __user *argp) ++{ ++ __u32 prev_state; ++ ++ if (event->type != NTSYNC_TYPE_EVENT) ++ return -EINVAL; ++ ++ spin_lock(&event->lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = false; ++ ++ spin_unlock(&event->lock); ++ ++ if (put_user(prev_state, (__u32 __user *)argp)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int ntsync_sem_read(struct ntsync_obj *sem, void __user *argp) ++{ ++ struct ntsync_sem_args __user *user_args = argp; ++ struct ntsync_sem_args args; ++ ++ if (sem->type != NTSYNC_TYPE_SEM) ++ return -EINVAL; ++ ++ args.sem = 0; ++ spin_lock(&sem->lock); ++ args.count = sem->u.sem.count; ++ args.max = sem->u.sem.max; ++ spin_unlock(&sem->lock); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return 0; ++} ++ ++static int ntsync_mutex_read(struct ntsync_obj *mutex, void __user *argp) ++{ ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_mutex_args args; ++ int ret; ++ ++ if (mutex->type != NTSYNC_TYPE_MUTEX) ++ return -EINVAL; ++ ++ args.mutex = 0; ++ spin_lock(&mutex->lock); ++ args.count = mutex->u.mutex.count; ++ args.owner = mutex->u.mutex.owner; ++ ret = mutex->u.mutex.ownerdead ? -EOWNERDEAD : 0; ++ spin_unlock(&mutex->lock); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return ret; ++} ++ ++static int ntsync_event_read(struct ntsync_obj *event, void __user *argp) ++{ ++ struct ntsync_event_args __user *user_args = argp; ++ struct ntsync_event_args args; ++ ++ if (event->type != NTSYNC_TYPE_EVENT) ++ return -EINVAL; ++ ++ args.event = 0; ++ spin_lock(&event->lock); ++ args.manual = event->u.event.manual; ++ args.signaled = event->u.event.signaled; ++ spin_unlock(&event->lock); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return 0; ++} ++ ++static int ntsync_obj_release(struct inode *inode, struct file *file) ++{ ++ struct ntsync_obj *obj = file->private_data; ++ ++ fput(obj->dev->file); ++ kfree(obj); ++ ++ return 0; ++} ++ ++static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, ++ unsigned long parm) ++{ ++ struct ntsync_obj *obj = file->private_data; ++ void __user *argp = (void __user *)parm; ++ ++ switch (cmd) { ++ case NTSYNC_IOC_SEM_POST: ++ return ntsync_sem_post(obj, argp); ++ case NTSYNC_IOC_SEM_READ: ++ return ntsync_sem_read(obj, argp); ++ case NTSYNC_IOC_MUTEX_UNLOCK: ++ return ntsync_mutex_unlock(obj, argp); ++ case NTSYNC_IOC_MUTEX_KILL: ++ return ntsync_mutex_kill(obj, argp); ++ case NTSYNC_IOC_MUTEX_READ: ++ return ntsync_mutex_read(obj, argp); ++ case NTSYNC_IOC_EVENT_SET: ++ return ntsync_event_set(obj, argp, false); ++ case NTSYNC_IOC_EVENT_RESET: ++ return ntsync_event_reset(obj, argp); ++ case NTSYNC_IOC_EVENT_PULSE: ++ return ntsync_event_set(obj, argp, true); ++ case NTSYNC_IOC_EVENT_READ: ++ return ntsync_event_read(obj, argp); ++ default: ++ return -ENOIOCTLCMD; ++ } ++} ++ ++static const struct file_operations ntsync_obj_fops = { ++ .owner = THIS_MODULE, ++ .release = ntsync_obj_release, ++ .unlocked_ioctl = ntsync_obj_ioctl, ++ .compat_ioctl = compat_ptr_ioctl, ++ .llseek = no_llseek, ++}; ++ ++static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, ++ enum ntsync_type type) ++{ ++ struct ntsync_obj *obj; ++ ++ obj = kzalloc(sizeof(*obj), GFP_KERNEL); ++ if (!obj) ++ return NULL; ++ obj->type = type; ++ obj->dev = dev; ++ get_file(dev->file); ++ spin_lock_init(&obj->lock); ++ INIT_LIST_HEAD(&obj->any_waiters); ++ INIT_LIST_HEAD(&obj->all_waiters); ++ atomic_set(&obj->all_hint, 0); ++ ++ return obj; ++} ++ ++static int ntsync_obj_get_fd(struct ntsync_obj *obj) ++{ ++ struct file *file; ++ int fd; ++ ++ fd = get_unused_fd_flags(O_CLOEXEC); ++ if (fd < 0) ++ return fd; ++ file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); ++ if (IS_ERR(file)) { ++ put_unused_fd(fd); ++ return PTR_ERR(file); ++ } ++ obj->file = file; ++ fd_install(fd, file); ++ ++ return fd; ++} ++ ++static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_sem_args __user *user_args = argp; ++ struct ntsync_sem_args args; ++ struct ntsync_obj *sem; ++ int fd; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ if (args.count > args.max) ++ return -EINVAL; ++ ++ sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); ++ if (!sem) ++ return -ENOMEM; ++ sem->u.sem.count = args.count; ++ sem->u.sem.max = args.max; ++ fd = ntsync_obj_get_fd(sem); ++ if (fd < 0) { ++ kfree(sem); ++ return fd; ++ } ++ ++ return put_user(fd, &user_args->sem); ++} ++ ++static int ntsync_create_mutex(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_mutex_args __user *user_args = argp; ++ struct ntsync_mutex_args args; ++ struct ntsync_obj *mutex; ++ int fd; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ if (!args.owner != !args.count) ++ return -EINVAL; ++ ++ mutex = ntsync_alloc_obj(dev, NTSYNC_TYPE_MUTEX); ++ if (!mutex) ++ return -ENOMEM; ++ mutex->u.mutex.count = args.count; ++ mutex->u.mutex.owner = args.owner; ++ fd = ntsync_obj_get_fd(mutex); ++ if (fd < 0) { ++ kfree(mutex); ++ return fd; ++ } ++ ++ return put_user(fd, &user_args->mutex); ++} ++ ++static int ntsync_create_event(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_event_args __user *user_args = argp; ++ struct ntsync_event_args args; ++ struct ntsync_obj *event; ++ int fd; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = ntsync_alloc_obj(dev, NTSYNC_TYPE_EVENT); ++ if (!event) ++ return -ENOMEM; ++ event->u.event.manual = args.manual; ++ event->u.event.signaled = args.signaled; ++ fd = ntsync_obj_get_fd(event); ++ if (fd < 0) { ++ kfree(event); ++ return fd; ++ } ++ ++ return put_user(fd, &user_args->event); ++} ++ ++static struct ntsync_obj *get_obj(struct ntsync_device *dev, int fd) ++{ ++ struct file *file = fget(fd); ++ struct ntsync_obj *obj; ++ ++ if (file->f_op != &ntsync_obj_fops) { ++ fput(file); ++ return NULL; ++ } ++ ++ obj = file->private_data; ++ if (obj->dev != dev) { ++ fput(file); ++ return NULL; ++ } ++ ++ return obj; ++} ++ ++static void put_obj(struct ntsync_obj *obj) ++{ ++ fput(obj->file); ++} ++ ++static int ntsync_schedule(const struct ntsync_q *q, const struct ntsync_wait_args *args) ++{ ++ ktime_t timeout = ns_to_ktime(args->timeout); ++ clockid_t clock = CLOCK_MONOTONIC; ++ ktime_t *timeout_ptr; ++ int ret = 0; ++ ++ timeout_ptr = (args->timeout == U64_MAX ? NULL : &timeout); ++ ++ if (args->flags & NTSYNC_WAIT_REALTIME) ++ clock = CLOCK_REALTIME; ++ ++ do { ++ if (signal_pending(current)) { ++ ret = -ERESTARTSYS; ++ break; ++ } ++ ++ set_current_state(TASK_INTERRUPTIBLE); ++ if (atomic_read(&q->signaled) != -1) { ++ ret = 0; ++ break; ++ } ++ ret = schedule_hrtimeout_range_clock(timeout_ptr, 0, HRTIMER_MODE_ABS, clock); ++ } while (ret < 0); ++ __set_current_state(TASK_RUNNING); ++ ++ return ret; ++} ++ ++/* ++ * Allocate and initialize the ntsync_q structure, but do not queue us yet. ++ */ ++static int setup_wait(struct ntsync_device *dev, ++ const struct ntsync_wait_args *args, bool all, ++ struct ntsync_q **ret_q) ++{ ++ int fds[NTSYNC_MAX_WAIT_COUNT + 1]; ++ const __u32 count = args->count; ++ struct ntsync_q *q; ++ __u32 total_count; ++ __u32 i, j; ++ ++ if (!args->owner) ++ return -EINVAL; ++ ++ if (args->pad || (args->flags & ~NTSYNC_WAIT_REALTIME)) ++ return -EINVAL; ++ ++ if (args->count > NTSYNC_MAX_WAIT_COUNT) ++ return -EINVAL; ++ ++ total_count = count; ++ if (args->alert) ++ total_count++; ++ ++ if (copy_from_user(fds, u64_to_user_ptr(args->objs), ++ array_size(count, sizeof(*fds)))) ++ return -EFAULT; ++ if (args->alert) ++ fds[count] = args->alert; ++ ++ q = kmalloc(struct_size(q, entries, total_count), GFP_KERNEL); ++ if (!q) ++ return -ENOMEM; ++ q->task = current; ++ q->owner = args->owner; ++ atomic_set(&q->signaled, -1); ++ q->all = all; ++ q->ownerdead = false; ++ q->count = count; ++ ++ for (i = 0; i < total_count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = get_obj(dev, fds[i]); ++ ++ if (!obj) ++ goto err; ++ ++ if (all) { ++ /* Check that the objects are all distinct. */ ++ for (j = 0; j < i; j++) { ++ if (obj == q->entries[j].obj) { ++ put_obj(obj); ++ goto err; ++ } ++ } ++ } ++ ++ entry->obj = obj; ++ entry->q = q; ++ entry->index = i; ++ } ++ ++ *ret_q = q; ++ return 0; ++ ++err: ++ for (j = 0; j < i; j++) ++ put_obj(q->entries[j].obj); ++ kfree(q); ++ return -EINVAL; ++} ++ ++static void try_wake_any_obj(struct ntsync_obj *obj) ++{ ++ switch (obj->type) { ++ case NTSYNC_TYPE_SEM: ++ try_wake_any_sem(obj); ++ break; ++ case NTSYNC_TYPE_MUTEX: ++ try_wake_any_mutex(obj); ++ break; ++ case NTSYNC_TYPE_EVENT: ++ try_wake_any_event(obj); ++ break; ++ } ++} ++ ++static int ntsync_wait_any(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_wait_args args; ++ __u32 i, total_count; ++ struct ntsync_q *q; ++ int signaled; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ ret = setup_wait(dev, &args, false, &q); ++ if (ret < 0) ++ return ret; ++ ++ total_count = args.count; ++ if (args.alert) ++ total_count++; ++ ++ /* queue ourselves */ ++ ++ for (i = 0; i < total_count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ spin_lock(&obj->lock); ++ list_add_tail(&entry->node, &obj->any_waiters); ++ spin_unlock(&obj->lock); ++ } ++ ++ /* ++ * Check if we are already signaled. ++ * ++ * Note that the API requires that normal objects are checked before ++ * the alert event. Hence we queue the alert event last, and check ++ * objects in order. ++ */ ++ ++ for (i = 0; i < total_count; i++) { ++ struct ntsync_obj *obj = q->entries[i].obj; ++ ++ if (atomic_read(&q->signaled) != -1) ++ break; ++ ++ spin_lock(&obj->lock); ++ try_wake_any_obj(obj); ++ spin_unlock(&obj->lock); ++ } ++ ++ /* sleep */ ++ ++ ret = ntsync_schedule(q, &args); ++ ++ /* and finally, unqueue */ ++ ++ for (i = 0; i < total_count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ spin_lock(&obj->lock); ++ list_del(&entry->node); ++ spin_unlock(&obj->lock); ++ ++ put_obj(obj); ++ } ++ ++ signaled = atomic_read(&q->signaled); ++ if (signaled != -1) { ++ struct ntsync_wait_args __user *user_args = argp; ++ ++ /* even if we caught a signal, we need to communicate success */ ++ ret = q->ownerdead ? -EOWNERDEAD : 0; ++ ++ if (put_user(signaled, &user_args->index)) ++ ret = -EFAULT; ++ } else if (!ret) { ++ ret = -ETIMEDOUT; ++ } ++ ++ kfree(q); ++ return ret; ++} ++ ++static int ntsync_wait_all(struct ntsync_device *dev, void __user *argp) ++{ ++ struct ntsync_wait_args args; ++ struct ntsync_q *q; ++ int signaled; ++ __u32 i; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ ret = setup_wait(dev, &args, true, &q); ++ if (ret < 0) ++ return ret; ++ ++ /* queue ourselves */ ++ ++ spin_lock(&dev->wait_all_lock); ++ ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ atomic_inc(&obj->all_hint); ++ ++ /* ++ * obj->all_waiters is protected by dev->wait_all_lock rather ++ * than obj->lock, so there is no need to acquire obj->lock ++ * here. ++ */ ++ list_add_tail(&entry->node, &obj->all_waiters); ++ } ++ if (args.alert) { ++ struct ntsync_q_entry *entry = &q->entries[args.count]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); ++ list_add_tail(&entry->node, &obj->any_waiters); ++ spin_unlock(&obj->lock); ++ } ++ ++ /* check if we are already signaled */ ++ ++ try_wake_all(dev, q, NULL); ++ ++ spin_unlock(&dev->wait_all_lock); ++ ++ /* ++ * Check if the alert event is signaled, making sure to do so only ++ * after checking if the other objects are signaled. ++ */ ++ ++ if (args.alert) { ++ struct ntsync_obj *obj = q->entries[args.count].obj; ++ ++ if (atomic_read(&q->signaled) == -1) { ++ spin_lock(&obj->lock); ++ try_wake_any_obj(obj); ++ spin_unlock(&obj->lock); ++ } ++ } ++ ++ /* sleep */ ++ ++ ret = ntsync_schedule(q, &args); ++ ++ /* and finally, unqueue */ ++ ++ spin_lock(&dev->wait_all_lock); ++ ++ for (i = 0; i < args.count; i++) { ++ struct ntsync_q_entry *entry = &q->entries[i]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ /* ++ * obj->all_waiters is protected by dev->wait_all_lock rather ++ * than obj->lock, so there is no need to acquire it here. ++ */ ++ list_del(&entry->node); ++ ++ atomic_dec(&obj->all_hint); ++ ++ put_obj(obj); ++ } ++ if (args.alert) { ++ struct ntsync_q_entry *entry = &q->entries[args.count]; ++ struct ntsync_obj *obj = entry->obj; ++ ++ spin_lock_nest_lock(&obj->lock, &dev->wait_all_lock); ++ list_del(&entry->node); ++ spin_unlock(&obj->lock); ++ ++ put_obj(obj); ++ } ++ ++ spin_unlock(&dev->wait_all_lock); ++ ++ signaled = atomic_read(&q->signaled); ++ if (signaled != -1) { ++ struct ntsync_wait_args __user *user_args = argp; ++ ++ /* even if we caught a signal, we need to communicate success */ ++ ret = q->ownerdead ? -EOWNERDEAD : 0; ++ ++ if (put_user(signaled, &user_args->index)) ++ ret = -EFAULT; ++ } else if (!ret) { ++ ret = -ETIMEDOUT; ++ } ++ ++ kfree(q); ++ return ret; ++} ++ ++static int ntsync_char_open(struct inode *inode, struct file *file) ++{ ++ struct ntsync_device *dev; ++ ++ dev = kzalloc(sizeof(*dev), GFP_KERNEL); ++ if (!dev) ++ return -ENOMEM; ++ ++ spin_lock_init(&dev->wait_all_lock); ++ ++ file->private_data = dev; ++ dev->file = file; ++ return nonseekable_open(inode, file); ++} ++ ++static int ntsync_char_release(struct inode *inode, struct file *file) ++{ ++ struct ntsync_device *dev = file->private_data; ++ ++ kfree(dev); ++ ++ return 0; ++} ++ ++static long ntsync_char_ioctl(struct file *file, unsigned int cmd, ++ unsigned long parm) ++{ ++ struct ntsync_device *dev = file->private_data; ++ void __user *argp = (void __user *)parm; ++ ++ switch (cmd) { ++ case NTSYNC_IOC_CREATE_EVENT: ++ return ntsync_create_event(dev, argp); ++ case NTSYNC_IOC_CREATE_MUTEX: ++ return ntsync_create_mutex(dev, argp); ++ case NTSYNC_IOC_CREATE_SEM: ++ return ntsync_create_sem(dev, argp); ++ case NTSYNC_IOC_WAIT_ALL: ++ return ntsync_wait_all(dev, argp); ++ case NTSYNC_IOC_WAIT_ANY: ++ return ntsync_wait_any(dev, argp); ++ default: ++ return -ENOIOCTLCMD; ++ } ++} ++ ++static const struct file_operations ntsync_fops = { ++ .owner = THIS_MODULE, ++ .open = ntsync_char_open, ++ .release = ntsync_char_release, ++ .unlocked_ioctl = ntsync_char_ioctl, ++ .compat_ioctl = compat_ptr_ioctl, ++ .llseek = no_llseek, ++}; ++ ++static struct miscdevice ntsync_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = NTSYNC_NAME, ++ .fops = &ntsync_fops, ++}; ++ ++module_misc_device(ntsync_misc); ++ ++MODULE_AUTHOR("Elizabeth Figura "); ++MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); ++MODULE_LICENSE("GPL"); +diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h +new file mode 100644 +index 000000000000..b5e835d8dba8 +--- /dev/null ++++ b/include/uapi/linux/ntsync.h +@@ -0,0 +1,62 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* ++ * Kernel support for NT synchronization primitive emulation ++ * ++ * Copyright (C) 2021-2022 Elizabeth Figura ++ */ ++ ++#ifndef __LINUX_NTSYNC_H ++#define __LINUX_NTSYNC_H ++ ++#include ++ ++struct ntsync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++}; ++ ++struct ntsync_mutex_args { ++ __u32 mutex; ++ __u32 owner; ++ __u32 count; ++}; ++ ++struct ntsync_event_args { ++ __u32 event; ++ __u32 manual; ++ __u32 signaled; ++}; ++ ++#define NTSYNC_WAIT_REALTIME 0x1 ++ ++struct ntsync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 alert; ++ __u32 flags; ++ __u32 pad; ++}; ++ ++#define NTSYNC_MAX_WAIT_COUNT 64 ++ ++#define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) ++#define NTSYNC_IOC_WAIT_ANY _IOWR('N', 0x82, struct ntsync_wait_args) ++#define NTSYNC_IOC_WAIT_ALL _IOWR('N', 0x83, struct ntsync_wait_args) ++#define NTSYNC_IOC_CREATE_MUTEX _IOWR('N', 0x84, struct ntsync_sem_args) ++#define NTSYNC_IOC_CREATE_EVENT _IOWR('N', 0x87, struct ntsync_event_args) ++ ++#define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) ++#define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('N', 0x85, struct ntsync_mutex_args) ++#define NTSYNC_IOC_MUTEX_KILL _IOW ('N', 0x86, __u32) ++#define NTSYNC_IOC_EVENT_SET _IOR ('N', 0x88, __u32) ++#define NTSYNC_IOC_EVENT_RESET _IOR ('N', 0x89, __u32) ++#define NTSYNC_IOC_EVENT_PULSE _IOR ('N', 0x8a, __u32) ++#define NTSYNC_IOC_SEM_READ _IOR ('N', 0x8b, struct ntsync_sem_args) ++#define NTSYNC_IOC_MUTEX_READ _IOR ('N', 0x8c, struct ntsync_mutex_args) ++#define NTSYNC_IOC_EVENT_READ _IOR ('N', 0x8d, struct ntsync_event_args) ++ ++#endif +diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile +index 15b6a111c3be..6c714a4e6478 100644 +--- a/tools/testing/selftests/Makefile ++++ b/tools/testing/selftests/Makefile +@@ -15,6 +15,7 @@ TARGETS += cpu-hotplug + TARGETS += damon + TARGETS += dmabuf-heaps + TARGETS += drivers/dma-buf ++TARGETS += drivers/ntsync + TARGETS += drivers/s390x/uvdevice + TARGETS += drivers/net/bonding + TARGETS += drivers/net/team +diff --git a/tools/testing/selftests/drivers/ntsync/Makefile b/tools/testing/selftests/drivers/ntsync/Makefile +new file mode 100644 +index 000000000000..a34da5ccacf0 +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/Makefile +@@ -0,0 +1,8 @@ ++# SPDX-LICENSE-IDENTIFIER: GPL-2.0-only ++TEST_GEN_PROGS := ntsync ++ ++top_srcdir =../../../../.. ++CFLAGS += -I$(top_srcdir)/usr/include ++LDLIBS += -lpthread ++ ++include ../../lib.mk +diff --git a/tools/testing/selftests/drivers/ntsync/config b/tools/testing/selftests/drivers/ntsync/config +new file mode 100644 +index 000000000000..60539c826d06 +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/config +@@ -0,0 +1 @@ ++CONFIG_WINESYNC=y +diff --git a/tools/testing/selftests/drivers/ntsync/ntsync.c b/tools/testing/selftests/drivers/ntsync/ntsync.c +new file mode 100644 +index 000000000000..5fa2c9a0768c +--- /dev/null ++++ b/tools/testing/selftests/drivers/ntsync/ntsync.c +@@ -0,0 +1,1407 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Various unit tests for the "ntsync" synchronization primitive driver. ++ * ++ * Copyright (C) 2021-2022 Elizabeth Figura ++ */ ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../../kselftest_harness.h" ++ ++static int read_sem_state(int sem, __u32 *count, __u32 *max) ++{ ++ struct ntsync_sem_args args; ++ int ret; ++ ++ memset(&args, 0xcc, sizeof(args)); ++ ret = ioctl(sem, NTSYNC_IOC_SEM_READ, &args); ++ *count = args.count; ++ *max = args.max; ++ return ret; ++} ++ ++#define check_sem_state(sem, count, max) \ ++ ({ \ ++ __u32 __count, __max; \ ++ int ret = read_sem_state((sem), &__count, &__max); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((max), __max); \ ++ }) ++ ++static int post_sem(int sem, __u32 *count) ++{ ++ return ioctl(sem, NTSYNC_IOC_SEM_POST, count); ++} ++ ++static int read_mutex_state(int mutex, __u32 *count, __u32 *owner) ++{ ++ struct ntsync_mutex_args args; ++ int ret; ++ ++ memset(&args, 0xcc, sizeof(args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &args); ++ *count = args.count; ++ *owner = args.owner; ++ return ret; ++} ++ ++#define check_mutex_state(mutex, count, owner) \ ++ ({ \ ++ __u32 __count, __owner; \ ++ int ret = read_mutex_state((mutex), &__count, &__owner); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((owner), __owner); \ ++ }) ++ ++static int unlock_mutex(int mutex, __u32 owner, __u32 *count) ++{ ++ struct ntsync_mutex_args args; ++ int ret; ++ ++ args.owner = owner; ++ args.count = 0xdeadbeef; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_UNLOCK, &args); ++ *count = args.count; ++ return ret; ++} ++ ++static int read_event_state(int event, __u32 *signaled, __u32 *manual) ++{ ++ struct ntsync_event_args args; ++ int ret; ++ ++ memset(&args, 0xcc, sizeof(args)); ++ ret = ioctl(event, NTSYNC_IOC_EVENT_READ, &args); ++ *signaled = args.signaled; ++ *manual = args.manual; ++ return ret; ++} ++ ++#define check_event_state(event, signaled, manual) \ ++ ({ \ ++ __u32 __signaled, __manual; \ ++ int ret = read_event_state((event), &__signaled, &__manual); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((signaled), __signaled); \ ++ EXPECT_EQ((manual), __manual); \ ++ }) ++ ++static int wait_objs(int fd, unsigned long request, __u32 count, ++ const int *objs, __u32 owner, int alert, __u32 *index) ++{ ++ struct ntsync_wait_args args = {0}; ++ struct timespec timeout; ++ int ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ args.timeout = timeout.tv_sec * 1000000000 + timeout.tv_nsec; ++ args.count = count; ++ args.objs = (uintptr_t)objs; ++ args.owner = owner; ++ args.index = 0xdeadbeef; ++ args.alert = alert; ++ ret = ioctl(fd, request, &args); ++ *index = args.index; ++ return ret; ++} ++ ++static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, 0, index); ++} ++ ++static int wait_all(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, 0, index); ++} ++ ++static int wait_any_alert(int fd, __u32 count, const int *objs, ++ __u32 owner, int alert, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, ++ count, objs, owner, alert, index); ++} ++ ++static int wait_all_alert(int fd, __u32 count, const int *objs, ++ __u32 owner, int alert, __u32 *index) ++{ ++ return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, ++ count, objs, owner, alert, index); ++} ++ ++TEST(semaphore_state) ++{ ++ struct ntsync_sem_args sem_args; ++ struct timespec timeout; ++ __u32 count, index; ++ int fd, ret, sem; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 3; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ sem_args.count = 2; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ sem = sem_args.sem; ++ check_sem_state(sem, 2, 2); ++ ++ count = 0; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_sem_state(sem, 2, 2); ++ ++ count = 1; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(sem, 2, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem, 1, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem, 0, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ count = 3; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(sem, 0, 2); ++ ++ count = 2; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(sem, 2, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ ++ count = 1; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(sem, 1, 2); ++ ++ count = ~0u; ++ ret = post_sem(sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(sem, 1, 2); ++ ++ close(sem); ++ ++ close(fd); ++} ++ ++TEST(mutex_state) ++{ ++ struct ntsync_mutex_args mutex_args; ++ __u32 owner, count, index; ++ struct timespec timeout; ++ int fd, ret, mutex; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 0; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 2; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 2; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ mutex = mutex_args.mutex; ++ check_mutex_state(mutex, 2, 123); ++ ++ ret = unlock_mutex(mutex, 0, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = unlock_mutex(mutex, 456, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ check_mutex_state(mutex, 2, 123); ++ ++ ret = unlock_mutex(mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(mutex, 1, 123); ++ ++ ret = unlock_mutex(mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); ++ check_mutex_state(mutex, 0, 0); ++ ++ ret = unlock_mutex(mutex, 123, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 456); ++ ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 2, 456); ++ ++ ret = unlock_mutex(mutex, 456, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(mutex, 1, 456); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ owner = 0; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ owner = 123; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ check_mutex_state(mutex, 1, 456); ++ ++ owner = 456; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); ++ ++ memset(&mutex_args, 0xcc, sizeof(mutex_args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ memset(&mutex_args, 0xcc, sizeof(mutex_args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 123); ++ ++ owner = 123; ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); ++ ++ memset(&mutex_args, 0xcc, sizeof(mutex_args)); ++ ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 123); ++ ++ close(mutex); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ mutex = mutex_args.mutex; ++ check_mutex_state(mutex, 0, 0); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(mutex, 1, 123); ++ ++ close(mutex); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = ~0u; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ mutex = mutex_args.mutex; ++ check_mutex_state(mutex, ~0u, 123); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ close(mutex); ++ ++ close(fd); ++} ++ ++TEST(manual_event_state) ++{ ++ struct ntsync_event_args event_args; ++ __u32 index, signaled; ++ int fd, event, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ event_args.manual = 1; ++ event_args.signaled = 0; ++ event_args.event = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, event_args.event); ++ event = event_args.event; ++ check_event_state(event, 0, 1); ++ ++ signaled = 0xdeadbeef; ++ ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event, 1, 1); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ check_event_state(event, 1, 1); ++ ++ ret = wait_any(fd, 1, &event, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_event_state(event, 1, 1); ++ ++ signaled = 0xdeadbeef; ++ ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ check_event_state(event, 0, 1); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event, 0, 1); ++ ++ ret = wait_any(fd, 1, &event, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ check_event_state(event, 0, 1); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event, 0, 1); ++ ++ close(event); ++ ++ close(fd); ++} ++ ++TEST(auto_event_state) ++{ ++ struct ntsync_event_args event_args; ++ __u32 index, signaled; ++ int fd, event, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ event_args.manual = 0; ++ event_args.signaled = 1; ++ event_args.event = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, event_args.event); ++ event = event_args.event; ++ ++ check_event_state(event, 1, 0); ++ ++ signaled = 0xdeadbeef; ++ ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ check_event_state(event, 1, 0); ++ ++ ret = wait_any(fd, 1, &event, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_event_state(event, 0, 0); ++ ++ signaled = 0xdeadbeef; ++ ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event, 0, 0); ++ ++ ret = wait_any(fd, 1, &event, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ check_event_state(event, 0, 0); ++ ++ ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event, 0, 0); ++ ++ close(event); ++ ++ close(fd); ++} ++ ++TEST(test_wait_any) ++{ ++ int objs[NTSYNC_MAX_WAIT_COUNT + 1], fd, ret; ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 owner, index, count, i; ++ struct timespec timeout; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 0, 0); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 0, 0); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); ++ ++ count = 1; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 2, 123); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ owner = 123; ++ ret = ioctl(mutex_args.mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(1, index); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ ++ /* test waiting on the same object twice */ ++ count = 2; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ ++ objs[0] = objs[1] = sem_args.sem; ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 1, 3); ++ ++ ret = wait_any(fd, 0, NULL, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ for (i = 0; i < NTSYNC_MAX_WAIT_COUNT + 1; ++i) ++ objs[i] = sem_args.sem; ++ ++ ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT + 1, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = wait_any(fd, -1, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ close(sem_args.sem); ++ close(mutex_args.mutex); ++ ++ close(fd); ++} ++ ++TEST(test_wait_all) ++{ ++ struct ntsync_event_args event_args = {0}; ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 owner, index, count; ++ int objs[2], fd, ret; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); ++ ++ ret = wait_all(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 2, 123); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_mutex_state(mutex_args.mutex, 2, 123); ++ ++ count = 3; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 2, 3); ++ check_mutex_state(mutex_args.mutex, 3, 123); ++ ++ owner = 123; ++ ret = ioctl(mutex_args.mutex, NTSYNC_IOC_MUTEX_KILL, &owner); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 1, 123); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = event_args.event; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(sem_args.sem, 0, 3); ++ check_event_state(event_args.event, 1, 1); ++ ++ /* test waiting on the same object twice */ ++ objs[0] = objs[1] = sem_args.sem; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ close(sem_args.sem); ++ close(mutex_args.mutex); ++ close(event_args.event); ++ ++ close(fd); ++} ++ ++struct wake_args { ++ int fd; ++ int obj; ++}; ++ ++struct wait_args { ++ int fd; ++ unsigned long request; ++ struct ntsync_wait_args *args; ++ int ret; ++ int err; ++}; ++ ++static void *wait_thread(void *arg) ++{ ++ struct wait_args *args = arg; ++ ++ args->ret = ioctl(args->fd, args->request, args->args); ++ args->err = errno; ++ return NULL; ++} ++ ++static __u64 get_abs_timeout(unsigned int ms) ++{ ++ struct timespec timeout; ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ return (timeout.tv_sec * 1000000000) + timeout.tv_nsec + (ms * 1000000); ++} ++ ++static int wait_for_thread(pthread_t thread, unsigned int ms) ++{ ++ struct timespec timeout; ++ ++ clock_gettime(CLOCK_REALTIME, &timeout); ++ timeout.tv_nsec += ms * 1000000; ++ timeout.tv_sec += (timeout.tv_nsec / 1000000000); ++ timeout.tv_nsec %= 1000000000; ++ return pthread_timedjoin_np(thread, NULL, &timeout); ++} ++ ++TEST(wake_any) ++{ ++ struct ntsync_event_args event_args = {0}; ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ __u32 count, index, signaled; ++ int objs[2], fd, ret; ++ pthread_t thread; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 1; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ /* test waking the semaphore */ ++ ++ wait_args.timeout = get_abs_timeout(1000); ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 456; ++ wait_args.index = 0xdeadbeef; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ANY; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ count = 1; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(sem_args.sem, 0, 3); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(0, wait_args.index); ++ ++ /* test waking the mutex */ ++ ++ /* first grab it again for owner 123 */ ++ ret = wait_any(fd, 1, &mutex_args.mutex, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ wait_args.timeout = get_abs_timeout(1000); ++ wait_args.owner = 456; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = unlock_mutex(mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ ret = unlock_mutex(mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, mutex_args.count); ++ check_mutex_state(mutex_args.mutex, 1, 456); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ /* test waking events */ ++ ++ event_args.manual = false; ++ event_args.signaled = false; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[1] = event_args.event; ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 0, 0); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_PULSE, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 0, 0); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ close(event_args.event); ++ ++ event_args.manual = true; ++ event_args.signaled = false; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[1] = event_args.event; ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 1, 1); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ ++ wait_args.timeout = get_abs_timeout(1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_PULSE, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ check_event_state(event_args.event, 0, 1); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ close(event_args.event); ++ ++ /* delete an object while it's being waited on */ ++ ++ wait_args.timeout = get_abs_timeout(200); ++ wait_args.owner = 123; ++ objs[1] = mutex_args.mutex; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ close(sem_args.sem); ++ close(mutex_args.mutex); ++ ++ ret = wait_for_thread(thread, 200); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(-1, thread_args.ret); ++ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ ++ close(fd); ++} ++ ++TEST(wake_all) ++{ ++ struct ntsync_event_args manual_event_args = {0}; ++ struct ntsync_event_args auto_event_args = {0}; ++ struct ntsync_mutex_args mutex_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ __u32 count, index, signaled; ++ int objs[4], fd, ret; ++ pthread_t thread; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 1; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ manual_event_args.manual = true; ++ manual_event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &manual_event_args); ++ EXPECT_EQ(0, ret); ++ ++ auto_event_args.manual = false; ++ auto_event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ objs[2] = manual_event_args.event; ++ objs[3] = auto_event_args.event; ++ ++ wait_args.timeout = get_abs_timeout(1000); ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 4; ++ wait_args.owner = 456; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ALL; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ count = 1; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ check_sem_state(sem_args.sem, 1, 3); ++ ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = unlock_mutex(mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ check_mutex_state(mutex_args.mutex, 0, 0); ++ ++ ret = ioctl(manual_event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ ++ count = 2; ++ ret = post_sem(sem_args.sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(sem_args.sem, 2, 3); ++ ++ ret = ioctl(auto_event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, signaled); ++ ++ ret = ioctl(manual_event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ ++ ret = ioctl(auto_event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, signaled); ++ ++ check_sem_state(sem_args.sem, 1, 3); ++ check_mutex_state(mutex_args.mutex, 1, 456); ++ check_event_state(manual_event_args.event, 1, 1); ++ check_event_state(auto_event_args.event, 0, 0); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ ++ /* delete an object while it's being waited on */ ++ ++ wait_args.timeout = get_abs_timeout(200); ++ wait_args.owner = 123; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ close(sem_args.sem); ++ close(mutex_args.mutex); ++ close(manual_event_args.event); ++ close(auto_event_args.event); ++ ++ ret = wait_for_thread(thread, 200); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(-1, thread_args.ret); ++ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ ++ close(fd); ++} ++ ++TEST(alert_any) ++{ ++ struct ntsync_event_args event_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ __u32 index, count, signaled; ++ struct wait_args thread_args; ++ int objs[2], fd, ret; ++ pthread_t thread; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[0] = sem_args.sem; ++ ++ sem_args.count = 1; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[1] = sem_args.sem; ++ ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ /* test wakeup via alert */ ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ ++ wait_args.timeout = get_abs_timeout(1000); ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 123; ++ wait_args.index = 0xdeadbeef; ++ wait_args.alert = event_args.event; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ANY; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(2, wait_args.index); ++ ++ close(event_args.event); ++ ++ /* test with an auto-reset event */ ++ ++ event_args.manual = false; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ count = 1; ++ ret = post_sem(objs[0], &count); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ close(event_args.event); ++ ++ close(objs[0]); ++ close(objs[1]); ++ ++ close(fd); ++} ++ ++TEST(alert_all) ++{ ++ struct ntsync_event_args event_args = {0}; ++ struct ntsync_wait_args wait_args = {0}; ++ struct ntsync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ __u32 index, count, signaled; ++ int objs[2], fd, ret; ++ pthread_t thread; ++ ++ fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[0] = sem_args.sem; ++ ++ sem_args.count = 1; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[1] = sem_args.sem; ++ ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ /* test wakeup via alert */ ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_RESET, &signaled); ++ EXPECT_EQ(0, ret); ++ ++ wait_args.timeout = get_abs_timeout(1000); ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 123; ++ wait_args.index = 0xdeadbeef; ++ wait_args.alert = event_args.event; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = NTSYNC_IOC_WAIT_ALL; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(event_args.event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(2, wait_args.index); ++ ++ close(event_args.event); ++ ++ /* test with an auto-reset event */ ++ ++ event_args.manual = false; ++ event_args.signaled = true; ++ ret = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ count = 2; ++ ret = post_sem(objs[1], &count); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ close(event_args.event); ++ ++ close(objs[0]); ++ close(objs[1]); ++ ++ close(fd); ++} ++ ++#define STRESS_LOOPS 10000 ++#define STRESS_THREADS 4 ++ ++static unsigned int stress_counter; ++static int stress_device, stress_start_event, stress_mutex; ++ ++static void *stress_thread(void *arg) ++{ ++ struct ntsync_wait_args wait_args = {0}; ++ __u32 index, count, i; ++ int ret; ++ ++ wait_args.timeout = UINT64_MAX; ++ wait_args.count = 1; ++ wait_args.objs = (uintptr_t)&stress_start_event; ++ wait_args.owner = gettid(); ++ wait_args.index = 0xdeadbeef; ++ ++ ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ ++ wait_args.objs = (uintptr_t)&stress_mutex; ++ ++ for (i = 0; i < STRESS_LOOPS; ++i) { ++ ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args); ++ ++ ++stress_counter; ++ ++ unlock_mutex(stress_mutex, wait_args.owner, &count); ++ } ++ ++ return NULL; ++} ++ ++TEST(stress_wait) ++{ ++ struct ntsync_event_args event_args; ++ struct ntsync_mutex_args mutex_args; ++ pthread_t threads[STRESS_THREADS]; ++ __u32 signaled, i; ++ int ret; ++ ++ stress_device = open("/dev/ntsync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, stress_device); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ ret = ioctl(stress_device, NTSYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ stress_mutex = mutex_args.mutex; ++ ++ event_args.manual = 1; ++ event_args.signaled = 0; ++ ret = ioctl(stress_device, NTSYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ stress_start_event = event_args.event; ++ ++ for (i = 0; i < STRESS_THREADS; ++i) ++ pthread_create(&threads[i], NULL, stress_thread, NULL); ++ ++ ret = ioctl(stress_start_event, NTSYNC_IOC_EVENT_SET, &signaled); ++ EXPECT_EQ(0, ret); ++ ++ for (i = 0; i < STRESS_THREADS; ++i) { ++ ret = pthread_join(threads[i], NULL); ++ EXPECT_EQ(0, ret); ++ } ++ ++ EXPECT_EQ(STRESS_LOOPS * STRESS_THREADS, stress_counter); ++ ++ close(stress_start_event); ++ close(stress_mutex); ++ close(stress_device); ++} ++ ++TEST_HARNESS_MAIN diff --git a/patches/cachyos/0003-nvidia.patch b/patches/cachyos/0003-nvidia.patch new file mode 100644 index 0000000..ce7fb7f --- /dev/null +++ b/patches/cachyos/0003-nvidia.patch @@ -0,0 +1,230 @@ +From d2db737a5be989688a7a5d805b7f299d0203d228 Mon Sep 17 00:00:00 2001 +From: Peter Jung +Date: Mon, 29 Jan 2024 15:09:44 +0100 +Subject: [PATCH] NVIDIA: Fixup GPL issue + +Signed-off-by: Peter Jung +--- + kernel/rcu/tree_plugin.h | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h +index 41021080ad25..72474d8ec180 100644 +--- a/kernel/rcu/tree_plugin.h ++++ b/kernel/rcu/tree_plugin.h +@@ -406,7 +406,7 @@ void __rcu_read_lock(void) + WRITE_ONCE(current->rcu_read_unlock_special.b.need_qs, true); + barrier(); /* critical section after entry code. */ + } +-EXPORT_SYMBOL_GPL(__rcu_read_lock); ++EXPORT_SYMBOL(__rcu_read_lock); + + /* + * Preemptible RCU implementation for rcu_read_unlock(). +@@ -431,7 +431,7 @@ void __rcu_read_unlock(void) + WARN_ON_ONCE(rrln < 0 || rrln > RCU_NEST_PMAX); + } + } +-EXPORT_SYMBOL_GPL(__rcu_read_unlock); ++EXPORT_SYMBOL(__rcu_read_unlock); + + /* + * Advance a ->blkd_tasks-list pointer to the next entry, instead +-- +2.43.0 + +--- a/kernel/nvidia-drm/nvidia-drm-drv.c ++++ b/kernel/nvidia-drm/nvidia-drm-drv.c +@@ -480,6 +480,22 @@ static int nv_drm_load(struct drm_device *dev, unsigned long flags) + return -ENODEV; + } + ++#if defined(NV_DRM_FBDEV_GENERIC_AVAILABLE) ++ /* ++ * If fbdev is enabled, take modeset ownership now before other DRM clients ++ * can take master (and thus NVKMS ownership). ++ */ ++ if (nv_drm_fbdev_module_param) { ++ if (!nvKms->grabOwnership(pDevice)) { ++ nvKms->freeDevice(pDevice); ++ NV_DRM_DEV_LOG_ERR(nv_dev, "Failed to grab NVKMS modeset ownership"); ++ return -EBUSY; ++ } ++ ++ nv_dev->hasFramebufferConsole = NV_TRUE; ++ } ++#endif ++ + mutex_lock(&nv_dev->lock); + + /* Set NvKmsKapiDevice */ +@@ -590,6 +606,15 @@ static void __nv_drm_unload(struct drm_device *dev) + return; + } + ++ /* Release modeset ownership if fbdev is enabled */ ++ ++#if defined(NV_DRM_FBDEV_GENERIC_AVAILABLE) ++ if (nv_dev->hasFramebufferConsole) { ++ drm_atomic_helper_shutdown(dev); ++ nvKms->releaseOwnership(nv_dev->pDevice); ++ } ++#endif ++ + cancel_delayed_work_sync(&nv_dev->hotplug_event_work); + mutex_lock(&nv_dev->lock); + +@@ -1768,14 +1793,7 @@ void nv_drm_register_drm_device(const nv_gpu_info_t *gpu_info) + } + + #if defined(NV_DRM_FBDEV_GENERIC_AVAILABLE) +- if (nv_drm_fbdev_module_param && +- drm_core_check_feature(dev, DRIVER_MODESET)) { +- +- if (!nvKms->grabOwnership(nv_dev->pDevice)) { +- NV_DRM_DEV_LOG_ERR(nv_dev, "Failed to grab NVKMS modeset ownership"); +- goto failed_grab_ownership; +- } +- ++ if (nv_dev->hasFramebufferConsole) { + if (bus_is_pci) { + struct pci_dev *pdev = to_pci_dev(device); + +@@ -1786,8 +1804,6 @@ void nv_drm_register_drm_device(const nv_gpu_info_t *gpu_info) + #endif + } + drm_fbdev_generic_setup(dev, 32); +- +- nv_dev->hasFramebufferConsole = NV_TRUE; + } + #endif /* defined(NV_DRM_FBDEV_GENERIC_AVAILABLE) */ + +@@ -1798,12 +1814,6 @@ void nv_drm_register_drm_device(const nv_gpu_info_t *gpu_info) + + return; /* Success */ + +-#if defined(NV_DRM_FBDEV_GENERIC_AVAILABLE) +-failed_grab_ownership: +- +- drm_dev_unregister(dev); +-#endif +- + failed_drm_register: + + nv_drm_dev_free(dev); +@@ -1870,12 +1880,6 @@ void nv_drm_remove_devices(void) + struct nv_drm_device *next = dev_list->next; + struct drm_device *dev = dev_list->dev; + +-#if defined(NV_DRM_FBDEV_GENERIC_AVAILABLE) +- if (dev_list->hasFramebufferConsole) { +- drm_atomic_helper_shutdown(dev); +- nvKms->releaseOwnership(dev_list->pDevice); +- } +-#endif + drm_dev_unregister(dev); + nv_drm_dev_free(dev); + +From d82eb6c87ee2e05b6bbd35f703a41e68b3adc3a7 Mon Sep 17 00:00:00 2001 +From: Aaron Plattner +Date: Tue, 26 Dec 2023 11:58:46 -0800 +Subject: [PATCH] nvidia-drm: Use a workqueue to defer calling + drm_kms_helper_hotplug_event + +--- + kernel/nvidia-drm/nvidia-drm-drv.c | 24 ++++++++++++++++++++++++ + kernel/nvidia-drm/nvidia-drm-encoder.c | 4 ++-- + kernel/nvidia-drm/nvidia-drm-priv.h | 1 + + 3 files changed, 27 insertions(+), 2 deletions(-) + +diff --git kernel/nvidia-drm/nvidia-drm-drv.c kernel/nvidia-drm/nvidia-drm-drv.c +index e0ddb6c..9f7424d 100644 +--- kernel/nvidia-drm/nvidia-drm-drv.c ++++ kernel/nvidia-drm/nvidia-drm-drv.c +@@ -74,6 +74,7 @@ + #endif + + #include ++#include + + /* + * Commit fcd70cd36b9b ("drm: Split out drm_probe_helper.h") +@@ -405,6 +406,27 @@ static int nv_drm_create_properties(struct nv_drm_device *nv_dev) + return 0; + } + ++#if defined(NV_DRM_ATOMIC_MODESET_AVAILABLE) ++/* ++ * We can't just call drm_kms_helper_hotplug_event directly because ++ * fbdev_generic may attempt to set a mode from inside the hotplug event ++ * handler. Because kapi event handling runs on nvkms_kthread_q, this blocks ++ * other event processing including the flip completion notifier expected by ++ * nv_drm_atomic_commit. ++ * ++ * Defer hotplug event handling to a work item so that nvkms_kthread_q can ++ * continue processing events while a DRM modeset is in progress. ++ */ ++static void nv_drm_handle_hotplug_event(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct nv_drm_device *nv_dev = ++ container_of(dwork, struct nv_drm_device, hotplug_event_work); ++ ++ drm_kms_helper_hotplug_event(nv_dev->dev); ++} ++#endif ++ + static int nv_drm_load(struct drm_device *dev, unsigned long flags) + { + #if defined(NV_DRM_ATOMIC_MODESET_AVAILABLE) +@@ -540,6 +562,7 @@ static int nv_drm_load(struct drm_device *dev, unsigned long flags) + + /* Enable event handling */ + ++ INIT_DELAYED_WORK(&nv_dev->hotplug_event_work, nv_drm_handle_hotplug_event); + atomic_set(&nv_dev->enable_event_handling, true); + + init_waitqueue_head(&nv_dev->flip_event_wq); +@@ -567,6 +590,7 @@ static void __nv_drm_unload(struct drm_device *dev) + return; + } + ++ cancel_delayed_work_sync(&nv_dev->hotplug_event_work); + mutex_lock(&nv_dev->lock); + + WARN_ON(nv_dev->subOwnershipGranted); +diff --git kernel/nvidia-drm/nvidia-drm-encoder.c kernel/nvidia-drm/nvidia-drm-encoder.c +index b5ef5a2..7c0c119 100644 +--- kernel/nvidia-drm/nvidia-drm-encoder.c ++++ kernel/nvidia-drm/nvidia-drm-encoder.c +@@ -300,7 +300,7 @@ void nv_drm_handle_display_change(struct nv_drm_device *nv_dev, + + nv_drm_connector_mark_connection_status_dirty(nv_encoder->nv_connector); + +- drm_kms_helper_hotplug_event(dev); ++ schedule_delayed_work(&nv_dev->hotplug_event_work, 0); + } + + void nv_drm_handle_dynamic_display_connected(struct nv_drm_device *nv_dev, +@@ -347,6 +347,6 @@ void nv_drm_handle_dynamic_display_connected(struct nv_drm_device *nv_dev, + drm_reinit_primary_mode_group(dev); + #endif + +- drm_kms_helper_hotplug_event(dev); ++ schedule_delayed_work(&nv_dev->hotplug_event_work, 0); + } + #endif +diff --git kernel/nvidia-drm/nvidia-drm-priv.h kernel/nvidia-drm/nvidia-drm-priv.h +index 253155f..c9ce727 100644 +--- kernel/nvidia-drm/nvidia-drm-priv.h ++++ kernel/nvidia-drm/nvidia-drm-priv.h +@@ -126,6 +126,7 @@ struct nv_drm_device { + NvU64 modifiers[6 /* block linear */ + 1 /* linear */ + 1 /* terminator */]; + #endif + ++ struct delayed_work hotplug_event_work; + atomic_t enable_event_handling; + + /** +-- +2.43.0 \ No newline at end of file diff --git a/patches/cachyos/0004-intel.patch b/patches/cachyos/0004-intel.patch new file mode 100644 index 0000000..87da0d3 --- /dev/null +++ b/patches/cachyos/0004-intel.patch @@ -0,0 +1,2203 @@ +From a06ef5a36a19553f48d73428311b241839d53b9c Mon Sep 17 00:00:00 2001 +From: Laio Oriel Seman +Date: Fri, 8 Mar 2024 11:30:24 -0300 +Subject: [PATCH 1/2] ITD + +--- + MAINTAINERS | 1 + + arch/x86/include/asm/cpufeatures.h | 2 + + arch/x86/include/asm/disabled-features.h | 8 +- + arch/x86/include/asm/hfi.h | 85 +++++ + arch/x86/include/asm/hreset.h | 30 ++ + arch/x86/include/asm/msr-index.h | 12 + + arch/x86/include/asm/topology.h | 15 + + arch/x86/kernel/Makefile | 2 + + arch/x86/kernel/cpu/common.c | 33 +- + arch/x86/kernel/cpu/cpuid-deps.c | 1 + + arch/x86/kernel/process_32.c | 3 + + arch/x86/kernel/process_64.c | 3 + + arch/x86/kernel/sched_ipcc.c | 93 +++++ + drivers/thermal/intel/Kconfig | 1 + + drivers/thermal/intel/intel_hfi.c | 411 ++++++++++++++++++----- + drivers/thermal/thermal_netlink.c | 62 +++- + drivers/thermal/thermal_netlink.h | 26 ++ + include/linux/sched.h | 24 +- + include/linux/sched/topology.h | 6 + + init/Kconfig | 12 + + kernel/sched/core.c | 10 +- + kernel/sched/fair.c | 318 +++++++++++++++++- + kernel/sched/sched.h | 66 ++++ + kernel/sched/topology.c | 9 + + kernel/time/timer.c | 2 +- + 25 files changed, 1127 insertions(+), 108 deletions(-) + create mode 100644 arch/x86/include/asm/hfi.h + create mode 100644 arch/x86/include/asm/hreset.h + create mode 100644 arch/x86/kernel/sched_ipcc.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 88b28f85587..9bb09b30526 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -21791,6 +21791,7 @@ L: linux-pm@vger.kernel.org + S: Supported + Q: https://patchwork.kernel.org/project/linux-pm/list/ + T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git thermal ++F: arch/x86/include/asm/hfi.h + F: Documentation/ABI/testing/sysfs-class-thermal + F: Documentation/admin-guide/thermal/ + F: Documentation/devicetree/bindings/thermal/ +diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h +index 2b62cdd8dd1..31b1cea6847 100644 +--- a/arch/x86/include/asm/cpufeatures.h ++++ b/arch/x86/include/asm/cpufeatures.h +@@ -326,6 +326,7 @@ + #define X86_FEATURE_FSRC (12*32+12) /* "" Fast short REP {CMPSB,SCASB} */ + #define X86_FEATURE_LKGS (12*32+18) /* "" Load "kernel" (userspace) GS */ + #define X86_FEATURE_AMX_FP16 (12*32+21) /* "" AMX fp16 Support */ ++#define X86_FEATURE_HRESET (12*32+22) /* Hardware history reset instruction */ + #define X86_FEATURE_AVX_IFMA (12*32+23) /* "" Support for VPMADD52[H,L]UQ */ + #define X86_FEATURE_LAM (12*32+26) /* Linear Address Masking */ + +@@ -360,6 +361,7 @@ + #define X86_FEATURE_HWP_EPP (14*32+10) /* HWP Energy Perf. Preference */ + #define X86_FEATURE_HWP_PKG_REQ (14*32+11) /* HWP Package Level Request */ + #define X86_FEATURE_HFI (14*32+19) /* Hardware Feedback Interface */ ++#define X86_FEATURE_ITD (14*32+23) /* Intel Thread Director */ + + /* AMD SVM Feature Identification, CPUID level 0x8000000a (EDX), word 15 */ + #define X86_FEATURE_NPT (15*32+ 0) /* Nested Page Table support */ +diff --git a/arch/x86/include/asm/disabled-features.h b/arch/x86/include/asm/disabled-features.h +index 702d93fdd10..f4aa34cfd20 100644 +--- a/arch/x86/include/asm/disabled-features.h ++++ b/arch/x86/include/asm/disabled-features.h +@@ -117,6 +117,12 @@ + #define DISABLE_IBT (1 << (X86_FEATURE_IBT & 31)) + #endif + ++#ifdef CONFIG_IPC_CLASSES ++# define DISABLE_ITD 0 ++#else ++# define DISABLE_ITD (1 << (X86_FEATURE_ITD & 31)) ++#endif ++ + /* + * Make sure to add features to the correct mask + */ +@@ -135,7 +141,7 @@ + DISABLE_CALL_DEPTH_TRACKING|DISABLE_USER_SHSTK) + #define DISABLED_MASK12 (DISABLE_LAM) + #define DISABLED_MASK13 0 +-#define DISABLED_MASK14 0 ++#define DISABLED_MASK14 (DISABLE_ITD) + #define DISABLED_MASK15 0 + #define DISABLED_MASK16 (DISABLE_PKU|DISABLE_OSPKE|DISABLE_LA57|DISABLE_UMIP| \ + DISABLE_ENQCMD) +diff --git a/arch/x86/include/asm/hfi.h b/arch/x86/include/asm/hfi.h +new file mode 100644 +index 00000000000..b7fda3e0e8c +--- /dev/null ++++ b/arch/x86/include/asm/hfi.h +@@ -0,0 +1,85 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef _ASM_X86_HFI_H ++#define _ASM_X86_HFI_H ++ ++/* CPUID detection and enumeration definitions for HFI */ ++ ++union hfi_capabilities { ++ struct { ++ u8 performance:1; ++ u8 energy_efficiency:1; ++ u8 __reserved:6; ++ } split; ++ u8 bits; ++}; ++ ++union cpuid6_edx { ++ struct { ++ union hfi_capabilities capabilities; ++ u32 table_pages:4; ++ u32 __reserved:4; ++ s32 index:16; ++ } split; ++ u32 full; ++}; ++ ++union cpuid6_ecx { ++ struct { ++ u32 dont_care0:8; ++ u32 nr_classes:8; ++ u32 dont_care1:16; ++ } split; ++ u32 full; ++}; ++ ++/** ++ * struct hfi_hdr - Header of the HFI table ++ * @perf_updated: Hardware updated performance capabilities ++ * @ee_updated: Hardware updated energy efficiency capabilities ++ * ++ * Properties of the data in an HFI table. There exists one header per each ++ * HFI class. ++ */ ++struct hfi_hdr { ++ u8 perf_updated; ++ u8 ee_updated; ++} __packed; ++ ++/** ++ * struct hfi_table - Representation of an HFI table ++ * @base_addr: Base address of the local copy of the HFI table ++ * @timestamp: Timestamp of the last update of the local table. ++ * Located at the base of the local table. ++ * @hdr: Base address of the header of the local table ++ * @data: Base address of the data of the local table ++ */ ++struct hfi_table { ++ union { ++ void *base_addr; ++ u64 *timestamp; ++ }; ++ void *hdr; ++ void *data; ++}; ++ ++/** ++ * struct hfi_features - Supported HFI features ++ * @nr_classes: Number of classes supported ++ * @nr_table_pages: Size of the HFI table in 4KB pages ++ * @cpu_stride: Stride size to locate the capability data of a logical ++ * processor within the table (i.e., row stride) ++ * @class_stride: Stride size to locate a class within the capability ++ * data of a logical processor or the HFI table header ++ * @hdr_size: Size of the table header ++ * ++ * Parameters and supported features that are common to all HFI instances ++ */ ++struct hfi_features { ++ unsigned int nr_classes; ++ size_t nr_table_pages; ++ unsigned int cpu_stride; ++ unsigned int class_stride; ++ unsigned int hdr_size; ++}; ++ ++#endif /* _ASM_X86_HFI_H */ +diff --git a/arch/x86/include/asm/hreset.h b/arch/x86/include/asm/hreset.h +new file mode 100644 +index 00000000000..d68ca2fb864 +--- /dev/null ++++ b/arch/x86/include/asm/hreset.h +@@ -0,0 +1,30 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef _ASM_X86_HRESET_H ++ ++/** ++ * HRESET - History reset. Available since binutils v2.36. ++ * ++ * Request the processor to reset the history of task classification on the ++ * current logical processor. The history components to be ++ * reset are specified in %eax. Only bits specified in CPUID(0x20).EBX ++ * and enabled in the IA32_HRESET_ENABLE MSR can be selected. ++ * ++ * The assembly code looks like: ++ * ++ * hreset %eax ++ * ++ * The corresponding machine code looks like: ++ * ++ * F3 0F 3A F0 ModRM Imm ++ * ++ * The value of ModRM is 0xc0 to specify %eax register addressing. ++ * The ignored immediate operand is set to 0. ++ * ++ * The instruction is documented in the Intel SDM. ++ */ ++ ++#define __ASM_HRESET ".byte 0xf3, 0xf, 0x3a, 0xf0, 0xc0, 0x0" ++ ++void reset_hardware_history(void); ++ ++#endif /* _ASM_X86_HRESET_H */ +diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h +index f1bd7b91b3c..f334c19b028 100644 +--- a/arch/x86/include/asm/msr-index.h ++++ b/arch/x86/include/asm/msr-index.h +@@ -1143,7 +1143,19 @@ + + /* Hardware Feedback Interface */ + #define MSR_IA32_HW_FEEDBACK_PTR 0x17d0 ++#define HW_FEEDBACK_PTR_VALID BIT_ULL(0) ++#define HW_FEEDBACK_PTR_RESERVED_MASK GENMASK_ULL(11, 1) ++ + #define MSR_IA32_HW_FEEDBACK_CONFIG 0x17d1 ++#define MSR_IA32_HW_FEEDBACK_THREAD_CONFIG 0x17d4 ++#define MSR_IA32_HW_FEEDBACK_CHAR 0x17d2 ++ ++/* Hardware History Reset */ ++#define MSR_IA32_HW_HRESET_ENABLE 0x17da ++ ++#define HW_FEEDBACK_CONFIG_HFI_ENABLE BIT_ULL(0) ++#define HW_FEEDBACK_CONFIG_ITD_ENABLE BIT_ULL(1) ++#define HW_FEEDBACK_THREAD_CONFIG_ENABLE BIT_ULL(0) + + /* x2APIC locked status */ + #define MSR_IA32_XAPIC_DISABLE_STATUS 0xBD +diff --git a/arch/x86/include/asm/topology.h b/arch/x86/include/asm/topology.h +index 5f87f6b9b09..29fc06efcb6 100644 +--- a/arch/x86/include/asm/topology.h ++++ b/arch/x86/include/asm/topology.h +@@ -235,4 +235,19 @@ void init_freq_invariance_cppc(void); + #define arch_init_invariance_cppc init_freq_invariance_cppc + #endif + ++#ifdef CONFIG_INTEL_HFI_THERMAL ++int intel_hfi_read_classid(u8 *classid); ++unsigned long intel_hfi_get_ipcc_score(unsigned short ipcc, int cpu); ++#else ++static inline int intel_hfi_read_classid(u8 *classid) { return -ENODEV; } ++static inline unsigned long ++intel_hfi_get_ipcc_score(unsigned short ipcc, int cpu) { return -ENODEV; } ++#endif ++ ++#ifdef CONFIG_IPC_CLASSES ++void intel_update_ipcc(struct task_struct *curr); ++#define arch_update_ipcc intel_update_ipcc ++#define arch_get_ipcc_score intel_hfi_get_ipcc_score ++#endif ++ + #endif /* _ASM_X86_TOPOLOGY_H */ +diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile +index 0000325ab98..9bc7319175d 100644 +--- a/arch/x86/kernel/Makefile ++++ b/arch/x86/kernel/Makefile +@@ -150,6 +150,8 @@ obj-$(CONFIG_X86_CET) += cet.o + + obj-$(CONFIG_X86_USER_SHADOW_STACK) += shstk.o + ++obj-$(CONFIG_IPC_CLASSES) += sched_ipcc.o ++ + ### + # 64 bit specific files + ifeq ($(CONFIG_X86_64),y) +diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c +index fbc4e60d027..99ebd403fe4 100644 +--- a/arch/x86/kernel/cpu/common.c ++++ b/arch/x86/kernel/cpu/common.c +@@ -57,6 +57,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -381,6 +382,35 @@ static __always_inline void setup_umip(struct cpuinfo_x86 *c) + cr4_clear_bits(X86_CR4_UMIP); + } + ++static u32 hardware_history_features __ro_after_init; ++ ++ ++void reset_hardware_history(void) ++{ ++ asm_inline volatile (ALTERNATIVE("", __ASM_HRESET, X86_FEATURE_HRESET) ++ : : "a" (hardware_history_features) : "memory"); ++} ++ ++EXPORT_SYMBOL(reset_hardware_history); ++ ++static __always_inline void setup_hreset(struct cpuinfo_x86 *c) ++{ ++ if (!cpu_feature_enabled(X86_FEATURE_HRESET)) ++ return; ++ ++ /* ++ * Use on all CPUs the hardware history features that the boot ++ * CPU supports. ++ */ ++ if (c == &boot_cpu_data) ++ hardware_history_features = cpuid_ebx(0x20); ++ ++ if (!hardware_history_features) ++ return; ++ ++ wrmsrl(MSR_IA32_HW_HRESET_ENABLE, hardware_history_features); ++} ++ + /* These bits should not change their value after CPU init is finished. */ + static const unsigned long cr4_pinned_mask = + X86_CR4_SMEP | X86_CR4_SMAP | X86_CR4_UMIP | +@@ -1872,10 +1902,11 @@ static void identify_cpu(struct cpuinfo_x86 *c) + /* Disable the PN if appropriate */ + squash_the_stupid_serial_number(c); + +- /* Set up SMEP/SMAP/UMIP */ ++ /* Set up SMEP/SMAP/UMIP/HRESET */ + setup_smep(c); + setup_smap(c); + setup_umip(c); ++ setup_hreset(c); + + /* Enable FSGSBASE instructions if available. */ + if (cpu_has(c, X86_FEATURE_FSGSBASE)) { +diff --git a/arch/x86/kernel/cpu/cpuid-deps.c b/arch/x86/kernel/cpu/cpuid-deps.c +index e462c1d3800..db62700cdac 100644 +--- a/arch/x86/kernel/cpu/cpuid-deps.c ++++ b/arch/x86/kernel/cpu/cpuid-deps.c +@@ -81,6 +81,7 @@ static const struct cpuid_dep cpuid_deps[] = { + { X86_FEATURE_XFD, X86_FEATURE_XSAVES }, + { X86_FEATURE_XFD, X86_FEATURE_XGETBV1 }, + { X86_FEATURE_AMX_TILE, X86_FEATURE_XFD }, ++ { X86_FEATURE_ITD, X86_FEATURE_HFI }, + { X86_FEATURE_SHSTK, X86_FEATURE_XSAVES }, + {} + }; +diff --git a/arch/x86/kernel/process_32.c b/arch/x86/kernel/process_32.c +index 708c87b88cc..7353bb119e7 100644 +--- a/arch/x86/kernel/process_32.c ++++ b/arch/x86/kernel/process_32.c +@@ -52,6 +52,7 @@ + #include + #include + #include ++#include + #include + + #include "process.h" +@@ -214,6 +215,8 @@ __switch_to(struct task_struct *prev_p, struct task_struct *next_p) + /* Load the Intel cache allocation PQR MSR. */ + resctrl_sched_in(next_p); + ++ reset_hardware_history(); ++ + return prev_p; + } + +diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c +index 33b268747bb..202a6735c09 100644 +--- a/arch/x86/kernel/process_64.c ++++ b/arch/x86/kernel/process_64.c +@@ -54,6 +54,7 @@ + #include + #include + #include ++#include + #include + #include + #ifdef CONFIG_IA32_EMULATION +@@ -661,6 +662,8 @@ __switch_to(struct task_struct *prev_p, struct task_struct *next_p) + /* Load the Intel cache allocation PQR MSR. */ + resctrl_sched_in(next_p); + ++ reset_hardware_history(); ++ + return prev_p; + } + +diff --git a/arch/x86/kernel/sched_ipcc.c b/arch/x86/kernel/sched_ipcc.c +new file mode 100644 +index 00000000000..dd73fc8be49 +--- /dev/null ++++ b/arch/x86/kernel/sched_ipcc.c +@@ -0,0 +1,93 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Intel support for scheduler IPC classes ++ * ++ * Copyright (c) 2023, Intel Corporation. ++ * ++ * Author: Ricardo Neri ++ * ++ * On hybrid processors, the architecture differences between types of CPUs ++ * lead to different number of retired instructions per cycle (IPC). IPCs may ++ * differ further by classes of instructions. ++ * ++ * The scheduler assigns an IPC class to every task with arch_update_ipcc() ++ * from data that hardware provides. Implement this interface for x86. ++ * ++ * See kernel/sched/sched.h for details. ++ */ ++ ++#include ++ ++#include ++#include ++ ++#define CLASS_DEBOUNCER_SKIPS 4 ++ ++/** ++ * debounce_and_update_class() - Process and update a task's classification ++ * ++ * @p: The task of which the classification will be updated ++ * @new_ipcc: The new IPC classification ++ * ++ * Update the classification of @p with the new value that hardware provides. ++ * Only update the classification of @p if it has been the same during ++ * CLASS_DEBOUNCER_SKIPS consecutive ticks. ++ */ ++static void debounce_and_update_class(struct task_struct *p, u8 new_ipcc) ++{ ++ u16 debounce_skip; ++ ++ /* The class of @p changed. Only restart the debounce counter. */ ++ if (p->ipcc_tmp != new_ipcc) { ++ p->ipcc_cntr = 1; ++ goto out; ++ } ++ ++ /* ++ * The class of @p did not change. Update it if it has been the same ++ * for CLASS_DEBOUNCER_SKIPS user ticks. ++ */ ++ debounce_skip = p->ipcc_cntr + 1; ++ if (debounce_skip < CLASS_DEBOUNCER_SKIPS) ++ p->ipcc_cntr++; ++ else ++ p->ipcc = new_ipcc; ++ ++out: ++ p->ipcc_tmp = new_ipcc; ++} ++ ++static bool classification_is_accurate(u8 hfi_class, bool smt_siblings_idle) ++{ ++ switch (boot_cpu_data.x86_model) { ++ case INTEL_FAM6_ALDERLAKE: ++ case INTEL_FAM6_ALDERLAKE_L: ++ case INTEL_FAM6_RAPTORLAKE: ++ case INTEL_FAM6_RAPTORLAKE_P: ++ case INTEL_FAM6_RAPTORLAKE_S: ++ if (hfi_class == 3 || hfi_class == 2 || smt_siblings_idle) ++ return true; ++ ++ return false; ++ ++ default: ++ return false; ++ } ++} ++ ++void intel_update_ipcc(struct task_struct *curr) ++{ ++ u8 hfi_class; ++ bool idle; ++ ++ if (intel_hfi_read_classid(&hfi_class)) ++ return; ++ ++ /* ++ * 0 is a valid classification for Intel Thread Director. A scheduler ++ * IPCC class of 0 means that the task is unclassified. Adjust. ++ */ ++ idle = sched_smt_siblings_idle(task_cpu(curr)); ++ if (classification_is_accurate(hfi_class, idle)) ++ debounce_and_update_class(curr, hfi_class + 1); ++} +diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig +index b43953b5539..03da183ff99 100644 +--- a/drivers/thermal/intel/Kconfig ++++ b/drivers/thermal/intel/Kconfig +@@ -109,6 +109,7 @@ config INTEL_HFI_THERMAL + depends on CPU_SUP_INTEL + depends on X86_THERMAL_VECTOR + select THERMAL_NETLINK ++ select IPC_CLASSES + help + Select this option to enable the Hardware Feedback Interface. If + selected, hardware provides guidance to the operating system on +diff --git a/drivers/thermal/intel/intel_hfi.c b/drivers/thermal/intel/intel_hfi.c +index 3b04c6ec4fc..b791906914b 100644 +--- a/drivers/thermal/intel/intel_hfi.c ++++ b/drivers/thermal/intel/intel_hfi.c +@@ -30,9 +30,12 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include ++#include + #include + #include + #include +@@ -41,6 +44,7 @@ + #include + #include + ++#include + #include + + #include "intel_hfi.h" +@@ -48,32 +52,20 @@ + + #include "../thermal_netlink.h" + +-/* Hardware Feedback Interface MSR configuration bits */ +-#define HW_FEEDBACK_PTR_VALID_BIT BIT(0) +-#define HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT BIT(0) + + /* CPUID detection and enumeration definitions for HFI */ + + #define CPUID_HFI_LEAF 6 + +-union hfi_capabilities { ++union hfi_thread_feedback_char_msr { + struct { +- u8 performance:1; +- u8 energy_efficiency:1; +- u8 __reserved:6; ++ u64 classid : 8; ++ u64 __reserved : 55; ++ u64 valid : 1; + } split; +- u8 bits; ++ u64 full; + }; + +-union cpuid6_edx { +- struct { +- union hfi_capabilities capabilities; +- u32 table_pages:4; +- u32 __reserved:4; +- s32 index:16; +- } split; +- u32 full; +-}; + + /** + * struct hfi_cpu_data - HFI capabilities per CPU +@@ -81,32 +73,17 @@ union cpuid6_edx { + * @ee_cap: Energy efficiency capability + * + * Capabilities of a logical processor in the HFI table. These capabilities are +- * unitless. ++ * unitless and specific to each HFI class. + */ + struct hfi_cpu_data { + u8 perf_cap; + u8 ee_cap; + } __packed; + +-/** +- * struct hfi_hdr - Header of the HFI table +- * @perf_updated: Hardware updated performance capabilities +- * @ee_updated: Hardware updated energy efficiency capabilities +- * +- * Properties of the data in an HFI table. +- */ +-struct hfi_hdr { +- u8 perf_updated; +- u8 ee_updated; +-} __packed; + + /** + * struct hfi_instance - Representation of an HFI instance (i.e., a table) +- * @local_table: Base of the local copy of the HFI table +- * @timestamp: Timestamp of the last update of the local table. +- * Located at the base of the local table. +- * @hdr: Base address of the header of the local table +- * @data: Base address of the data of the local table ++ * @local_table: Local copy of HFI table for this instance + * @cpus: CPUs represented in this HFI table instance + * @hw_table: Pointer to the HFI table of this instance + * @update_work: Delayed work to process HFI updates +@@ -116,12 +93,7 @@ struct hfi_hdr { + * A set of parameters to parse and navigate a specific HFI table. + */ + struct hfi_instance { +- union { +- void *local_table; +- u64 *timestamp; +- }; +- void *hdr; +- void *data; ++ struct hfi_table local_table; + cpumask_var_t cpus; + void *hw_table; + struct delayed_work update_work; +@@ -129,20 +101,6 @@ struct hfi_instance { + raw_spinlock_t event_lock; + }; + +-/** +- * struct hfi_features - Supported HFI features +- * @nr_table_pages: Size of the HFI table in 4KB pages +- * @cpu_stride: Stride size to locate the capability data of a logical +- * processor within the table (i.e., row stride) +- * @hdr_size: Size of the table header +- * +- * Parameters and supported features that are common to all HFI instances +- */ +-struct hfi_features { +- size_t nr_table_pages; +- unsigned int cpu_stride; +- unsigned int hdr_size; +-}; + + /** + * struct hfi_cpu_info - Per-CPU attributes to consume HFI data +@@ -159,6 +117,7 @@ struct hfi_cpu_info { + static DEFINE_PER_CPU(struct hfi_cpu_info, hfi_cpu_info) = { .index = -1 }; + + static int max_hfi_instances; ++static int hfi_clients_nr; + static struct hfi_instance *hfi_instances; + + static struct hfi_features hfi_features; +@@ -168,6 +127,139 @@ static struct workqueue_struct *hfi_updates_wq; + #define HFI_UPDATE_INTERVAL HZ + #define HFI_MAX_THERM_NOTIFY_COUNT 16 + ++/* ++ * A task may be unclassified if it has been recently created, spend most of ++ * its lifetime sleeping, or hardware has not provided a classification. ++ * ++ * Most tasks will be classified as scheduler's IPC class 1 (HFI class 0) ++ * eventually. Meanwhile, the scheduler will place classes of tasks with higher ++ * IPC scores on higher-performance CPUs. ++ * ++ * IPC class 1 is a reasonable choice. It matches the performance capability ++ * of the legacy, classless, HFI table. ++ */ ++#define HFI_UNCLASSIFIED_DEFAULT 1 ++ ++/* A cache of the HFI perf capabilities for lockless access. */ ++static int __percpu *hfi_ipcc_scores; ++/* Sequence counter for hfi_ipcc_scores */ ++static seqcount_t hfi_ipcc_seqcount = SEQCNT_ZERO(hfi_ipcc_seqcount); ++ ++static int alloc_hfi_ipcc_scores(void) ++{ ++ if (!cpu_feature_enabled(X86_FEATURE_ITD)) ++ return 0; ++ ++ hfi_ipcc_scores = __alloc_percpu(sizeof(*hfi_ipcc_scores) * ++ hfi_features.nr_classes, ++ sizeof(*hfi_ipcc_scores)); ++ ++ return hfi_ipcc_scores ? 0 : -ENOMEM; ++} ++ ++unsigned long intel_hfi_get_ipcc_score(unsigned short ipcc, int cpu) ++{ ++ int *scores, score; ++ unsigned long seq; ++ ++ scores = per_cpu_ptr(hfi_ipcc_scores, cpu); ++ if (!scores) ++ return -ENODEV; ++ ++ if (cpu < 0 || cpu >= nr_cpu_ids) ++ return -EINVAL; ++ ++ if (ipcc == IPC_CLASS_UNCLASSIFIED) ++ ipcc = HFI_UNCLASSIFIED_DEFAULT; ++ ++ /* ++ * Scheduler IPC classes start at 1. HFI classes start at 0. ++ * See note intel_hfi_update_ipcc(). ++ */ ++ if (ipcc >= hfi_features.nr_classes + 1) ++ return -EINVAL; ++ ++ /* ++ * The seqcount implies load-acquire semantics to order loads with ++ * lockless stores of the write side in set_hfi_ipcc_score(). It ++ * also implies a compiler barrier. ++ */ ++ do { ++ seq = read_seqcount_begin(&hfi_ipcc_seqcount); ++ /* @ipcc is never 0. */ ++ score = scores[ipcc - 1]; ++ } while (read_seqcount_retry(&hfi_ipcc_seqcount, seq)); ++ ++ return score; ++} ++ ++static void set_hfi_ipcc_scores(struct hfi_instance *hfi_instance) ++{ ++ int cpu; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_ITD)) ++ return; ++ ++ /* ++ * Serialize with writes to the HFI table. It also protects the write ++ * loop against seqcount readers running in interrupt context. ++ */ ++ raw_spin_lock_irq(&hfi_instance->table_lock); ++ /* ++ * The seqcount implies store-release semantics to order stores with ++ * lockless loads from the seqcount read side in ++ * intel_hfi_get_ipcc_score(). It also implies a compiler barrier. ++ */ ++ write_seqcount_begin(&hfi_ipcc_seqcount); ++ for_each_cpu(cpu, hfi_instance->cpus) { ++ int c, *scores; ++ s16 index; ++ ++ index = per_cpu(hfi_cpu_info, cpu).index; ++ scores = per_cpu_ptr(hfi_ipcc_scores, cpu); ++ ++ for (c = 0; c < hfi_features.nr_classes; c++) { ++ struct hfi_cpu_data *caps; ++ ++ caps = hfi_instance->local_table.data + ++ index * hfi_features.cpu_stride + ++ c * hfi_features.class_stride; ++ scores[c] = caps->perf_cap; ++ } ++ } ++ ++ write_seqcount_end(&hfi_ipcc_seqcount); ++ raw_spin_unlock_irq(&hfi_instance->table_lock); ++} ++ ++/** ++ * intel_hfi_read_classid() - Read the currrent classid ++ * @classid: Variable to which the classid will be written. ++ * ++ * Read the classification that Intel Thread Director has produced when this ++ * function is called. Thread classification must be enabled before calling ++ * this function. ++ * ++ * Return: 0 if the produced classification is valid. Error otherwise. ++ */ ++int intel_hfi_read_classid(u8 *classid) ++{ ++ union hfi_thread_feedback_char_msr msr; ++ ++ /* We should not be here if ITD is not supported. */ ++ if (!cpu_feature_enabled(X86_FEATURE_ITD)) { ++ pr_warn_once("task classification requested but not supported!"); ++ return -ENODEV; ++ } ++ ++ rdmsrl(MSR_IA32_HW_FEEDBACK_CHAR, msr.full); ++ if (!msr.split.valid) ++ return -EINVAL; ++ ++ *classid = msr.split.classid; ++ return 0; ++} ++ + static void get_hfi_caps(struct hfi_instance *hfi_instance, + struct thermal_genl_cpu_caps *cpu_caps) + { +@@ -179,7 +271,7 @@ static void get_hfi_caps(struct hfi_instance *hfi_instance, + s16 index; + + index = per_cpu(hfi_cpu_info, cpu).index; +- caps = hfi_instance->data + index * hfi_features.cpu_stride; ++ caps = hfi_instance->local_table.data + index * hfi_features.cpu_stride; + cpu_caps[i].cpu = cpu; + + /* +@@ -235,6 +327,8 @@ static void update_capabilities(struct hfi_instance *hfi_instance) + thermal_genl_cpu_capability_event(cpu_count, &cpu_caps[i]); + + kfree(cpu_caps); ++ ++ set_hfi_ipcc_scores(hfi_instance); + out: + mutex_unlock(&hfi_instance_lock); + } +@@ -296,7 +390,7 @@ void intel_hfi_process_event(__u64 pkg_therm_status_msr_val) + * where a lagging CPU entered the locked region. + */ + new_timestamp = *(u64 *)hfi_instance->hw_table; +- if (*hfi_instance->timestamp == new_timestamp) { ++ if (*hfi_instance->local_table.timestamp == new_timestamp) { + thermal_clear_package_intr_status(PACKAGE_LEVEL, PACKAGE_THERM_STATUS_HFI_UPDATED); + raw_spin_unlock(&hfi_instance->event_lock); + return; +@@ -308,7 +402,7 @@ void intel_hfi_process_event(__u64 pkg_therm_status_msr_val) + * Copy the updated table into our local copy. This includes the new + * timestamp. + */ +- memcpy(hfi_instance->local_table, hfi_instance->hw_table, ++ memcpy(hfi_instance->local_table.base_addr, hfi_instance->hw_table, + hfi_features.nr_table_pages << PAGE_SHIFT); + + /* +@@ -337,17 +431,18 @@ static void init_hfi_cpu_index(struct hfi_cpu_info *info) + } + + /* +- * The format of the HFI table depends on the number of capabilities that the +- * hardware supports. Keep a data structure to navigate the table. ++ * The format of the HFI table depends on the number of capabilities and classes ++ * that the hardware supports. Keep a data structure to navigate the table. + */ + static void init_hfi_instance(struct hfi_instance *hfi_instance) + { + /* The HFI header is below the time-stamp. */ +- hfi_instance->hdr = hfi_instance->local_table + +- sizeof(*hfi_instance->timestamp); ++ hfi_instance->local_table.hdr = hfi_instance->local_table.base_addr + ++ sizeof(*hfi_instance->local_table.timestamp); + + /* The HFI data starts below the header. */ +- hfi_instance->data = hfi_instance->hdr + hfi_features.hdr_size; ++ hfi_instance->local_table.data = hfi_instance->local_table.hdr + ++ hfi_features.hdr_size; + } + + /* Caller must hold hfi_instance_lock. */ +@@ -356,8 +451,13 @@ static void hfi_enable(void) + u64 msr_val; + + rdmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val); +- msr_val |= HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT; ++ msr_val |= HW_FEEDBACK_CONFIG_HFI_ENABLE; ++ ++ if (cpu_feature_enabled(X86_FEATURE_ITD)) ++ msr_val |= HW_FEEDBACK_CONFIG_ITD_ENABLE; ++ + wrmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val); ++ + } + + static void hfi_set_hw_table(struct hfi_instance *hfi_instance) +@@ -366,7 +466,7 @@ static void hfi_set_hw_table(struct hfi_instance *hfi_instance) + u64 msr_val; + + hw_table_pa = virt_to_phys(hfi_instance->hw_table); +- msr_val = hw_table_pa | HW_FEEDBACK_PTR_VALID_BIT; ++ msr_val = hw_table_pa | HW_FEEDBACK_PTR_VALID; + wrmsrl(MSR_IA32_HW_FEEDBACK_PTR, msr_val); + } + +@@ -377,7 +477,11 @@ static void hfi_disable(void) + int i; + + rdmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val); +- msr_val &= ~HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT; ++ msr_val &= ~HW_FEEDBACK_CONFIG_HFI_ENABLE; ++ ++ if (cpu_feature_enabled(X86_FEATURE_ITD)) ++ msr_val &= ~HW_FEEDBACK_CONFIG_ITD_ENABLE; ++ + wrmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val); + + /* +@@ -396,6 +500,30 @@ static void hfi_disable(void) + } + } + ++static void hfi_enable_itd_classification(void) ++{ ++ u64 msr_val; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_ITD)) ++ return; ++ ++ rdmsrl(MSR_IA32_HW_FEEDBACK_THREAD_CONFIG, msr_val); ++ msr_val |= HW_FEEDBACK_THREAD_CONFIG_ENABLE; ++ wrmsrl(MSR_IA32_HW_FEEDBACK_THREAD_CONFIG, msr_val); ++} ++ ++static void hfi_disable_itd_classification(void) ++{ ++ u64 msr_val; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_ITD)) ++ return; ++ ++ rdmsrl(MSR_IA32_HW_FEEDBACK_THREAD_CONFIG, msr_val); ++ msr_val &= ~HW_FEEDBACK_THREAD_CONFIG_ENABLE; ++ wrmsrl(MSR_IA32_HW_FEEDBACK_THREAD_CONFIG, msr_val); ++} ++ + /** + * intel_hfi_online() - Enable HFI on @cpu + * @cpu: CPU in which the HFI will be enabled +@@ -436,6 +564,8 @@ void intel_hfi_online(unsigned int cpu) + + init_hfi_cpu_index(info); + ++ hfi_enable_itd_classification(); ++ + /* + * Now check if the HFI instance of the package/die of @cpu has been + * initialized (by checking its header). In such case, all we have to +@@ -443,7 +573,7 @@ void intel_hfi_online(unsigned int cpu) + * if needed. + */ + mutex_lock(&hfi_instance_lock); +- if (hfi_instance->hdr) ++ if (hfi_instance->local_table.hdr) + goto enable; + + /* +@@ -463,9 +593,9 @@ void intel_hfi_online(unsigned int cpu) + * Allocate memory to keep a local copy of the table that + * hardware generates. + */ +- hfi_instance->local_table = kzalloc(hfi_features.nr_table_pages << PAGE_SHIFT, +- GFP_KERNEL); +- if (!hfi_instance->local_table) ++ hfi_instance->local_table.base_addr = kzalloc(hfi_features.nr_table_pages << PAGE_SHIFT, ++ GFP_KERNEL); ++ if (!hfi_instance->local_table.base_addr) + goto free_hw_table; + + init_hfi_instance(hfi_instance); +@@ -477,11 +607,23 @@ void intel_hfi_online(unsigned int cpu) + enable: + cpumask_set_cpu(cpu, hfi_instance->cpus); + +- /* Enable this HFI instance if this is its first online CPU. */ +- if (cpumask_weight(hfi_instance->cpus) == 1) { ++ /* ++ * Enable this HFI instance if this is its first online CPU and ++ * there are user-space clients of thermal events. ++ */ ++ if (cpumask_weight(hfi_instance->cpus) == 1 && hfi_clients_nr > 0) { + hfi_set_hw_table(hfi_instance); + hfi_enable(); + } ++ /* ++ * We have all we need to support IPC classes. Task classification is ++ * now working. ++ * ++ * All class scores are zero until after the first HFI update. That is ++ * OK. The scheduler queries these scores at every load balance. ++ */ ++ if (cpu_feature_enabled(X86_FEATURE_ITD)) ++ sched_enable_ipc_classes(); + + unlock: + mutex_unlock(&hfi_instance_lock); +@@ -516,9 +658,11 @@ void intel_hfi_offline(unsigned int cpu) + if (!hfi_instance) + return; + +- if (!hfi_instance->hdr) ++ if (!hfi_instance->local_table.hdr) + return; + ++ hfi_disable_itd_classification(); ++ + mutex_lock(&hfi_instance_lock); + cpumask_clear_cpu(cpu, hfi_instance->cpus); + +@@ -557,44 +701,133 @@ static __init int hfi_parse_features(void) + /* The number of 4KB pages required by the table */ + hfi_features.nr_table_pages = edx.split.table_pages + 1; + ++ /* ++ * Capability fields of an HFI class are grouped together. Classes are ++ * contiguous in memory. Hence, use the number of supported features to ++ * locate a specific class. ++ */ ++ hfi_features.class_stride = nr_capabilities; ++ ++ if (cpu_feature_enabled(X86_FEATURE_ITD)) { ++ union cpuid6_ecx ecx; ++ ++ ecx.full = cpuid_ecx(CPUID_HFI_LEAF); ++ hfi_features.nr_classes = ecx.split.nr_classes; ++ } else { ++ hfi_features.nr_classes = 1; ++ } ++ + /* + * The header contains change indications for each supported feature. + * The size of the table header is rounded up to be a multiple of 8 + * bytes. + */ +- hfi_features.hdr_size = DIV_ROUND_UP(nr_capabilities, 8) * 8; ++ hfi_features.hdr_size = DIV_ROUND_UP(nr_capabilities * ++ hfi_features.nr_classes, 8) * 8; + + /* + * Data of each logical processor is also rounded up to be a multiple + * of 8 bytes. + */ +- hfi_features.cpu_stride = DIV_ROUND_UP(nr_capabilities, 8) * 8; ++ hfi_features.cpu_stride = DIV_ROUND_UP(nr_capabilities * ++ hfi_features.nr_classes, 8) * 8; + + return 0; + } + +-static void hfi_do_enable(void) ++/* ++ * If concurrency is not prevented by other means, the HFI enable/disable ++ * routines must be called under hfi_instance_lock." ++ */ ++static void hfi_enable_instance(void *ptr) ++{ ++ hfi_set_hw_table(ptr); ++ hfi_enable(); ++} ++ ++static void hfi_disable_instance(void *ptr) ++{ ++ hfi_disable(); ++} ++ ++static void hfi_syscore_resume(void) + { + /* This code runs only on the boot CPU. */ + struct hfi_cpu_info *info = &per_cpu(hfi_cpu_info, 0); + struct hfi_instance *hfi_instance = info->hfi_instance; + + /* No locking needed. There is no concurrency with CPU online. */ +- hfi_set_hw_table(hfi_instance); +- hfi_enable(); ++ if (hfi_clients_nr > 0) { ++ hfi_set_hw_table(hfi_instance); ++ hfi_enable_instance(hfi_instance); ++ hfi_enable_itd_classification(); ++ } + } + +-static int hfi_do_disable(void) ++static int hfi_syscore_suspend(void) + { + /* No locking needed. There is no concurrency with CPU offline. */ ++ ++ hfi_disable_itd_classification(); ++ + hfi_disable(); + + return 0; + } + + static struct syscore_ops hfi_pm_ops = { +- .resume = hfi_do_enable, +- .suspend = hfi_do_disable, ++ .resume = hfi_syscore_resume, ++ .suspend = hfi_syscore_suspend, ++}; ++ ++static int hfi_thermal_notify(struct notifier_block *nb, unsigned long state, ++ void *_notify) ++{ ++ struct thermal_genl_notify *notify = _notify; ++ struct hfi_instance *hfi_instance; ++ smp_call_func_t func = NULL; ++ unsigned int cpu; ++ int i; ++ ++ if (notify->mcgrp != THERMAL_GENL_EVENT_GROUP) ++ return NOTIFY_DONE; ++ ++ if (state != THERMAL_NOTIFY_BIND && state != THERMAL_NOTIFY_UNBIND) ++ return NOTIFY_DONE; ++ ++ mutex_lock(&hfi_instance_lock); ++ ++ switch (state) { ++ case THERMAL_NOTIFY_BIND: ++ if (++hfi_clients_nr == 1) ++ func = hfi_enable_instance; ++ break; ++ case THERMAL_NOTIFY_UNBIND: ++ if (--hfi_clients_nr == 0) ++ func = hfi_disable_instance; ++ break; ++ } ++ ++ if (!func) ++ goto out; ++ ++ for (i = 0; i < max_hfi_instances; i++) { ++ hfi_instance = &hfi_instances[i]; ++ if (cpumask_empty(hfi_instance->cpus)) ++ continue; ++ ++ cpu = cpumask_any(hfi_instance->cpus); ++ smp_call_function_single(cpu, func, hfi_instance, true); ++ } ++ ++out: ++ mutex_unlock(&hfi_instance_lock); ++ ++ return NOTIFY_OK; ++} ++ ++static struct notifier_block hfi_thermal_nb = { ++ .notifier_call = hfi_thermal_notify, + }; + + void __init intel_hfi_init(void) +@@ -628,10 +861,28 @@ void __init intel_hfi_init(void) + if (!hfi_updates_wq) + goto err_nomem; + ++ /* ++ * Both thermal core and Intel HFI can not be build as modules. ++ * As kernel build-in drivers they are initialized before user-space ++ * starts, hence we can not miss BIND/UNBIND events when applications ++ * add/remove thermal multicast group to/from a netlink socket. ++ */ ++ if (thermal_genl_register_notifier(&hfi_thermal_nb)) ++ goto err_nl_notif; ++ + register_syscore_ops(&hfi_pm_ops); + ++ if (alloc_hfi_ipcc_scores()) ++ goto err_ipcc; ++ + return; + ++err_nl_notif: ++ destroy_workqueue(hfi_updates_wq); ++ ++err_ipcc: ++ destroy_workqueue(hfi_updates_wq); ++ + err_nomem: + for (j = 0; j < i; ++j) { + hfi_instance = &hfi_instances[j]; +diff --git a/drivers/thermal/thermal_netlink.c b/drivers/thermal/thermal_netlink.c +index 76a231a2965..bef14ce69ec 100644 +--- a/drivers/thermal/thermal_netlink.c ++++ b/drivers/thermal/thermal_netlink.c +@@ -7,17 +7,13 @@ + * Generic netlink for thermal management framework + */ + #include ++#include + #include + #include + #include + + #include "thermal_core.h" + +-enum thermal_genl_multicast_groups { +- THERMAL_GENL_SAMPLING_GROUP = 0, +- THERMAL_GENL_EVENT_GROUP = 1, +-}; +- + static const struct genl_multicast_group thermal_genl_mcgrps[] = { + [THERMAL_GENL_SAMPLING_GROUP] = { .name = THERMAL_GENL_SAMPLING_GROUP_NAME, }, + [THERMAL_GENL_EVENT_GROUP] = { .name = THERMAL_GENL_EVENT_GROUP_NAME, }, +@@ -74,11 +70,12 @@ struct param { + + typedef int (*cb_t)(struct param *); + +-static struct genl_family thermal_gnl_family; ++static struct genl_family thermal_genl_family; ++static BLOCKING_NOTIFIER_HEAD(thermal_genl_chain); + + static int thermal_group_has_listeners(enum thermal_genl_multicast_groups group) + { +- return genl_has_listeners(&thermal_gnl_family, &init_net, group); ++ return genl_has_listeners(&thermal_genl_family, &init_net, group); + } + + /************************** Sampling encoding *******************************/ +@@ -95,7 +92,7 @@ int thermal_genl_sampling_temp(int id, int temp) + if (!skb) + return -ENOMEM; + +- hdr = genlmsg_put(skb, 0, 0, &thermal_gnl_family, 0, ++ hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0, + THERMAL_GENL_SAMPLING_TEMP); + if (!hdr) + goto out_free; +@@ -108,7 +105,7 @@ int thermal_genl_sampling_temp(int id, int temp) + + genlmsg_end(skb, hdr); + +- genlmsg_multicast(&thermal_gnl_family, skb, 0, THERMAL_GENL_SAMPLING_GROUP, GFP_KERNEL); ++ genlmsg_multicast(&thermal_genl_family, skb, 0, THERMAL_GENL_SAMPLING_GROUP, GFP_KERNEL); + + return 0; + out_cancel: +@@ -282,7 +279,7 @@ static int thermal_genl_send_event(enum thermal_genl_event event, + return -ENOMEM; + p->msg = msg; + +- hdr = genlmsg_put(msg, 0, 0, &thermal_gnl_family, 0, event); ++ hdr = genlmsg_put(msg, 0, 0, &thermal_genl_family, 0, event); + if (!hdr) + goto out_free_msg; + +@@ -292,7 +289,7 @@ static int thermal_genl_send_event(enum thermal_genl_event event, + + genlmsg_end(msg, hdr); + +- genlmsg_multicast(&thermal_gnl_family, msg, 0, THERMAL_GENL_EVENT_GROUP, GFP_KERNEL); ++ genlmsg_multicast(&thermal_genl_family, msg, 0, THERMAL_GENL_EVENT_GROUP, GFP_KERNEL); + + return 0; + +@@ -593,7 +590,7 @@ static int thermal_genl_cmd_dumpit(struct sk_buff *skb, + int ret; + void *hdr; + +- hdr = genlmsg_put(skb, 0, 0, &thermal_gnl_family, 0, cmd); ++ hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0, cmd); + if (!hdr) + return -EMSGSIZE; + +@@ -625,7 +622,7 @@ static int thermal_genl_cmd_doit(struct sk_buff *skb, + return -ENOMEM; + p.msg = msg; + +- hdr = genlmsg_put_reply(msg, info, &thermal_gnl_family, 0, cmd); ++ hdr = genlmsg_put_reply(msg, info, &thermal_genl_family, 0, cmd); + if (!hdr) + goto out_free_msg; + +@@ -645,6 +642,27 @@ static int thermal_genl_cmd_doit(struct sk_buff *skb, + return ret; + } + ++static int thermal_genl_bind(int mcgrp) ++{ ++ struct thermal_genl_notify n = { .mcgrp = mcgrp }; ++ ++ if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP)) ++ return -EINVAL; ++ ++ blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_BIND, &n); ++ return 0; ++} ++ ++static void thermal_genl_unbind(int mcgrp) ++{ ++ struct thermal_genl_notify n = { .mcgrp = mcgrp }; ++ ++ if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP)) ++ return; ++ ++ blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_UNBIND, &n); ++} ++ + static const struct genl_small_ops thermal_genl_ops[] = { + { + .cmd = THERMAL_GENL_CMD_TZ_GET_ID, +@@ -673,12 +691,14 @@ static const struct genl_small_ops thermal_genl_ops[] = { + }, + }; + +-static struct genl_family thermal_gnl_family __ro_after_init = { ++static struct genl_family thermal_genl_family __ro_after_init = { + .hdrsize = 0, + .name = THERMAL_GENL_FAMILY_NAME, + .version = THERMAL_GENL_VERSION, + .maxattr = THERMAL_GENL_ATTR_MAX, + .policy = thermal_genl_policy, ++ .bind = thermal_genl_bind, ++ .unbind = thermal_genl_unbind, + .small_ops = thermal_genl_ops, + .n_small_ops = ARRAY_SIZE(thermal_genl_ops), + .resv_start_op = THERMAL_GENL_CMD_CDEV_GET + 1, +@@ -686,12 +706,22 @@ static struct genl_family thermal_gnl_family __ro_after_init = { + .n_mcgrps = ARRAY_SIZE(thermal_genl_mcgrps), + }; + ++int thermal_genl_register_notifier(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_register(&thermal_genl_chain, nb); ++} ++ ++int thermal_genl_unregister_notifier(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_unregister(&thermal_genl_chain, nb); ++} ++ + int __init thermal_netlink_init(void) + { +- return genl_register_family(&thermal_gnl_family); ++ return genl_register_family(&thermal_genl_family); + } + + void __init thermal_netlink_exit(void) + { +- genl_unregister_family(&thermal_gnl_family); ++ genl_unregister_family(&thermal_genl_family); + } +diff --git a/drivers/thermal/thermal_netlink.h b/drivers/thermal/thermal_netlink.h +index 93a927e144d..e01221e8816 100644 +--- a/drivers/thermal/thermal_netlink.h ++++ b/drivers/thermal/thermal_netlink.h +@@ -10,6 +10,19 @@ struct thermal_genl_cpu_caps { + int efficiency; + }; + ++enum thermal_genl_multicast_groups { ++ THERMAL_GENL_SAMPLING_GROUP = 0, ++ THERMAL_GENL_EVENT_GROUP = 1, ++ THERMAL_GENL_MAX_GROUP = THERMAL_GENL_EVENT_GROUP, ++}; ++ ++#define THERMAL_NOTIFY_BIND 0 ++#define THERMAL_NOTIFY_UNBIND 1 ++ ++struct thermal_genl_notify { ++ int mcgrp; ++}; ++ + struct thermal_zone_device; + struct thermal_trip; + struct thermal_cooling_device; +@@ -18,6 +31,9 @@ struct thermal_cooling_device; + #ifdef CONFIG_THERMAL_NETLINK + int __init thermal_netlink_init(void); + void __init thermal_netlink_exit(void); ++int thermal_genl_register_notifier(struct notifier_block *nb); ++int thermal_genl_unregister_notifier(struct notifier_block *nb); ++ + int thermal_notify_tz_create(const struct thermal_zone_device *tz); + int thermal_notify_tz_delete(const struct thermal_zone_device *tz); + int thermal_notify_tz_enable(const struct thermal_zone_device *tz); +@@ -48,6 +64,16 @@ static inline int thermal_notify_tz_create(const struct thermal_zone_device *tz) + return 0; + } + ++static inline int thermal_genl_register_notifier(struct notifier_block *nb) ++{ ++ return 0; ++} ++ ++static inline int thermal_genl_unregister_notifier(struct notifier_block *nb) ++{ ++ return 0; ++} ++ + static inline int thermal_notify_tz_delete(const struct thermal_zone_device *tz) + { + return 0; +diff --git a/include/linux/sched.h b/include/linux/sched.h +index ffe8f618ab8..8d458554bae 100644 +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -137,6 +137,8 @@ struct user_event_mm; + __TASK_TRACED | EXIT_DEAD | EXIT_ZOMBIE | \ + TASK_PARKED) + ++#define IPC_CLASS_UNCLASSIFIED 0 ++ + #define task_is_running(task) (READ_ONCE((task)->__state) == TASK_RUNNING) + + #define task_is_traced(task) ((READ_ONCE(task->jobctl) & JOBCTL_TRACED) != 0) +@@ -301,7 +303,7 @@ enum { + TASK_COMM_LEN = 16, + }; + +-extern void scheduler_tick(void); ++extern void scheduler_tick(bool user_tick); + + #define MAX_SCHEDULE_TIMEOUT LONG_MAX + +@@ -1547,6 +1549,24 @@ struct task_struct { + struct user_event_mm *user_event_mm; + #endif + ++#ifdef CONFIG_IPC_CLASSES ++ /* ++ * A hardware-defined classification of task that reflects but is ++ * not identical to the number of instructions per cycle. ++ */ ++ unsigned int ipcc : 9; ++ /* ++ * A candidate classification that arch-specific implementations ++ * qualify for correctness. ++ */ ++ unsigned int ipcc_tmp : 9; ++ /* ++ * Counter to filter out transient candidate classifications ++ * of a task. ++ */ ++ unsigned int ipcc_cntr : 14; ++#endif ++ + /* + * New fields for task_struct should be added above here, so that + * they are included in the randomized portion of task_struct. +@@ -2183,4 +2203,6 @@ static inline int sched_core_idle_cpu(int cpu) { return idle_cpu(cpu); } + + extern void sched_set_stop_task(int cpu, struct task_struct *stop); + ++extern bool sched_smt_siblings_idle(int cpu); ++ + #endif +diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h +index a6e04b4a21d..f32fce3fc8e 100644 +--- a/include/linux/sched/topology.h ++++ b/include/linux/sched/topology.h +@@ -292,4 +292,10 @@ static inline int task_node(const struct task_struct *p) + return cpu_to_node(task_cpu(p)); + } + ++#ifdef CONFIG_IPC_CLASSES ++extern void sched_enable_ipc_classes(void); ++#else ++static inline void sched_enable_ipc_classes(void) { } ++#endif ++ + #endif /* _LINUX_SCHED_TOPOLOGY_H */ +diff --git a/init/Kconfig b/init/Kconfig +index bee58f7468c..3447c10cbdd 100644 +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -849,6 +849,18 @@ config UCLAMP_BUCKETS_COUNT + + If in doubt, use the default value. + ++config IPC_CLASSES ++ bool "IPC classes of tasks" ++ depends on SMP ++ help ++ If selected, each task is assigned a classification value that ++ reflects the type of instructions that the task executes. This ++ classification reflects but is not equal to the number of ++ instructions retired per cycle. ++ ++ The scheduler uses the classification value to improve the placement ++ of tasks. ++ + endmenu + + # +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +index 9116bcc9034..5e07149813c 100644 +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -4515,6 +4515,11 @@ int wake_up_state(struct task_struct *p, unsigned int state) + */ + static void __sched_fork(unsigned long clone_flags, struct task_struct *p) + { ++#ifdef CONFIG_IPC_CLASSES ++ p->ipcc = IPC_CLASS_UNCLASSIFIED; ++ p->ipcc_tmp = IPC_CLASS_UNCLASSIFIED; ++ p->ipcc_cntr = 0; ++#endif + p->on_rq = 0; + + p->se.on_rq = 0; +@@ -5653,7 +5658,7 @@ static inline u64 cpu_resched_latency(struct rq *rq) { return 0; } + * This function gets called by the timer code, with HZ frequency. + * We call it with interrupts disabled. + */ +-void scheduler_tick(void) ++void scheduler_tick(bool user_tick) + { + int cpu = smp_processor_id(); + struct rq *rq = cpu_rq(cpu); +@@ -5665,6 +5670,9 @@ void scheduler_tick(void) + if (housekeeping_cpu(cpu, HK_TYPE_TICK)) + arch_scale_freq_tick(); + ++ if (sched_ipcc_enabled() && user_tick) ++ arch_update_ipcc(curr); ++ + sched_clock_tick(); + + rq_lock(rq, &rf); +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +index 533547e3c90..38e0acfefb0 100644 +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -1305,7 +1305,14 @@ update_stats_curr_start(struct cfs_rq *cfs_rq, struct sched_entity *se) + * Scheduling class queueing methods: + */ + +-static inline bool is_core_idle(int cpu) ++/** ++ * sched_smt_siblings_idle - Check whether SMT siblings of a CPU are idle ++ * @cpu: The CPU to check ++ * ++ * Returns true if all the SMT siblings of @cpu are idle or @cpu does not have ++ * SMT siblings. The idle state of @cpu is not considered. ++ */ ++bool sched_smt_siblings_idle(int cpu) + { + #ifdef CONFIG_SCHED_SMT + int sibling; +@@ -2008,7 +2015,7 @@ static inline int numa_idle_core(int idle_core, int cpu) + * Prefer cores instead of packing HT siblings + * and triggering future load balancing. + */ +- if (is_core_idle(cpu)) ++ if (sched_smt_siblings_idle(cpu)) + idle_core = cpu; + + return idle_core; +@@ -9449,6 +9456,13 @@ struct sg_lb_stats { + unsigned int nr_numa_running; + unsigned int nr_preferred_running; + #endif ++#ifdef CONFIG_IPC_CLASSES ++ unsigned long min_score; /* Min(score(rq->curr->ipcc)) */ ++ unsigned short min_ipcc; /* Class of the task with the minimum IPCC score in the rq */ ++ unsigned long sum_score; /* Sum(score(rq->curr->ipcc)) */ ++ long ipcc_score_after; /* Prospective IPCC score after load balancing */ ++ unsigned long ipcc_score_before; /* IPCC score before load balancing */ ++#endif + }; + + /* +@@ -9727,6 +9741,248 @@ group_type group_classify(unsigned int imbalance_pct, + return group_has_spare; + } + ++#ifdef CONFIG_IPC_CLASSES ++static void init_rq_ipcc_stats(struct sg_lb_stats *sgs) ++{ ++ /* All IPCC stats have been set to zero in update_sg_lb_stats(). */ ++ sgs->min_score = ULONG_MAX; ++} ++ ++static int rq_last_task_ipcc(int dst_cpu, struct rq *rq, unsigned short *ipcc) ++{ ++ struct list_head *tasks = &rq->cfs_tasks; ++ struct task_struct *p; ++ struct rq_flags rf; ++ int ret = -EINVAL; ++ ++ rq_lock_irqsave(rq, &rf); ++ if (list_empty(tasks)) ++ goto out; ++ ++ p = list_last_entry(tasks, struct task_struct, se.group_node); ++ if (p->flags & PF_EXITING || is_idle_task(p) || ++ !cpumask_test_cpu(dst_cpu, p->cpus_ptr)) ++ goto out; ++ ++ ret = 0; ++ *ipcc = p->ipcc; ++out: ++ rq_unlock(rq, &rf); ++ return ret; ++} ++ ++/* Called only if cpu_of(@rq) is not idle and has tasks running. */ ++static void update_sg_lb_ipcc_stats(int dst_cpu, struct sg_lb_stats *sgs, ++ struct rq *rq) ++{ ++ unsigned short ipcc; ++ unsigned long score; ++ ++ if (!sched_ipcc_enabled()) ++ return; ++ ++ if (rq_last_task_ipcc(dst_cpu, rq, &ipcc)) ++ return; ++ ++ score = arch_get_ipcc_score(ipcc, cpu_of(rq)); ++ ++ /* ++ * Ignore tasks with invalid scores. When finding the busiest group, we ++ * prefer those with higher sum_score. This group will not be selected. ++ */ ++ if (IS_ERR_VALUE(score)) ++ return; ++ ++ sgs->sum_score += score; ++ ++ if (score < sgs->min_score) { ++ sgs->min_score = score; ++ sgs->min_ipcc = ipcc; ++ } ++} ++ ++static void update_sg_lb_stats_scores(struct sg_lb_stats *sgs, ++ struct sched_group *sg, ++ struct lb_env *env) ++{ ++ unsigned long score_on_dst_cpu, before; ++ int busy_cpus; ++ long after; ++ ++ if (!sched_ipcc_enabled()) ++ return; ++ ++ /* ++ * IPCC scores are only useful during idle load balancing. For now, ++ * only asym_packing uses IPCC scores. ++ */ ++ if (!(env->sd->flags & SD_ASYM_PACKING) || ++ env->idle == CPU_NOT_IDLE) ++ return; ++ ++ /* ++ * IPCC scores are used to break ties only between these types of ++ * groups. ++ */ ++ if (sgs->group_type != group_fully_busy && ++ sgs->group_type != group_asym_packing) ++ return; ++ ++ busy_cpus = sgs->group_weight - sgs->idle_cpus; ++ ++ /* No busy CPUs in the group. No tasks to move. */ ++ if (!busy_cpus) ++ return; ++ ++ score_on_dst_cpu = arch_get_ipcc_score(sgs->min_ipcc, env->dst_cpu); ++ ++ /* ++ * Do not use IPC scores. sgs::ipcc_score_{after, before} will be zero ++ * and not used. ++ */ ++ if (IS_ERR_VALUE(score_on_dst_cpu)) ++ return; ++ ++ before = sgs->sum_score; ++ after = before - sgs->min_score; ++ ++ /* SMT siblings share throughput. */ ++ if (busy_cpus > 1 && sg->flags & SD_SHARE_CPUCAPACITY) { ++ before /= busy_cpus; ++ /* One sibling will become idle after load balance. */ ++ after /= busy_cpus - 1; ++ } ++ ++ sgs->ipcc_score_after = after + score_on_dst_cpu; ++ sgs->ipcc_score_before = before; ++} ++ ++/** ++ * sched_asym_ipcc_prefer - Select a sched group based on its IPCC score ++ * @a: Load balancing statistics of a sched group ++ * @b: Load balancing statistics of a second sched group ++ * ++ * Returns: true if @a has a higher IPCC score than @b after load balance. ++ * False otherwise. ++ */ ++static bool sched_asym_ipcc_prefer(struct sg_lb_stats *a, ++ struct sg_lb_stats *b) ++{ ++ if (!sched_ipcc_enabled()) ++ return false; ++ ++ /* @a increases overall throughput after load balance. */ ++ if (a->ipcc_score_after > b->ipcc_score_after) ++ return true; ++ ++ /* ++ * If @a and @b yield the same overall throughput, pick @a if ++ * its current throughput is lower than that of @b. ++ */ ++ if (a->ipcc_score_after == b->ipcc_score_after) ++ return a->ipcc_score_before < b->ipcc_score_before; ++ ++ return false; ++} ++ ++/** ++ * sched_asym_ipcc_pick - Select a sched group based on its IPCC score ++ * @a: A scheduling group ++ * @b: A second scheduling group ++ * @a_stats: Load balancing statistics of @a ++ * @b_stats: Load balancing statistics of @b ++ * ++ * Returns: true if @a has the same priority and @a has tasks with IPC classes ++ * that yield higher overall throughput after load balance. False otherwise. ++ */ ++static bool sched_asym_ipcc_pick(struct sched_group *a, ++ struct sched_group *b, ++ struct sg_lb_stats *a_stats, ++ struct sg_lb_stats *b_stats) ++{ ++ /* ++ * Only use the class-specific preference selection if both sched ++ * groups have the same priority. ++ */ ++ if (arch_asym_cpu_priority(a->asym_prefer_cpu) != ++ arch_asym_cpu_priority(b->asym_prefer_cpu)) ++ return false; ++ ++ return sched_asym_ipcc_prefer(a_stats, b_stats); ++} ++ ++/** ++ * ipcc_score_delta - Get the IPCC score delta wrt the load balance's dst_cpu ++ * @rq: A runqueue ++ * @env: Load balancing environment ++ * ++ * Returns: The IPCC score delta that the last task enqueued in @rq would get ++ * if placed in the destination CPU of @env. LONG_MIN to indicate that the ++ * delta should not be used. ++ */ ++static long ipcc_score_delta(struct rq *rq, struct lb_env *env) ++{ ++ unsigned long score_src, score_dst; ++ unsigned short ipcc; ++ ++ if (!sched_ipcc_enabled()) ++ return LONG_MIN; ++ ++ /* Only asym_packing uses IPCC scores at the moment. */ ++ if (!(env->sd->flags & SD_ASYM_PACKING)) ++ return LONG_MIN; ++ ++ if (rq_last_task_ipcc(env->dst_cpu, rq, &ipcc)) ++ return LONG_MIN; ++ ++ score_dst = arch_get_ipcc_score(ipcc, env->dst_cpu); ++ if (IS_ERR_VALUE(score_dst)) ++ return LONG_MIN; ++ ++ score_src = arch_get_ipcc_score(ipcc, cpu_of(rq)); ++ if (IS_ERR_VALUE(score_src)) ++ return LONG_MIN; ++ ++ return score_dst - score_src; ++} ++ ++#else /* CONFIG_IPC_CLASSES */ ++static void update_sg_lb_ipcc_stats(int dst_cpu, struct sg_lb_stats *sgs, ++ struct rq *rq) ++{ ++} ++ ++static void init_rq_ipcc_stats(struct sg_lb_stats *sgs) ++{ ++} ++ ++static void update_sg_lb_stats_scores(struct sg_lb_stats *sgs, ++ struct sched_group *sg, ++ struct lb_env *env) ++{ ++} ++ ++static bool sched_asym_ipcc_prefer(struct sg_lb_stats *a, ++ struct sg_lb_stats *b) ++{ ++ return false; ++} ++ ++static bool sched_asym_ipcc_pick(struct sched_group *a, ++ struct sched_group *b, ++ struct sg_lb_stats *a_stats, ++ struct sg_lb_stats *b_stats) ++{ ++ return false; ++} ++ ++static long ipcc_score_delta(struct rq *rq, struct lb_env *env) ++{ ++ return LONG_MIN; ++} ++ ++#endif /* CONFIG_IPC_CLASSES */ ++ + /** + * sched_use_asym_prio - Check whether asym_packing priority must be used + * @sd: The scheduling domain of the load balancing +@@ -9743,7 +9999,7 @@ static bool sched_use_asym_prio(struct sched_domain *sd, int cpu) + if (!sched_smt_active()) + return true; + +- return sd->flags & SD_SHARE_CPUCAPACITY || is_core_idle(cpu); ++ return sd->flags & SD_SHARE_CPUCAPACITY || sched_smt_siblings_idle(cpu); + } + + /** +@@ -9882,6 +10138,7 @@ static inline void update_sg_lb_stats(struct lb_env *env, + int i, nr_running, local_group; + + memset(sgs, 0, sizeof(*sgs)); ++ init_rq_ipcc_stats(sgs); + + local_group = group == sds->local; + +@@ -9931,6 +10188,8 @@ static inline void update_sg_lb_stats(struct lb_env *env, + if (sgs->group_misfit_task_load < load) + sgs->group_misfit_task_load = load; + } ++ ++ update_sg_lb_ipcc_stats(env->dst_cpu, sgs, rq); + } + + sgs->group_capacity = group->sgc->capacity; +@@ -9950,6 +10209,9 @@ static inline void update_sg_lb_stats(struct lb_env *env, + + sgs->group_type = group_classify(env->sd->imbalance_pct, group, sgs); + ++ if (!local_group) ++ update_sg_lb_stats_scores(sgs, group, env); ++ + /* Computing avg_load makes sense only when group is overloaded */ + if (sgs->group_type == group_overloaded) + sgs->avg_load = (sgs->group_load * SCHED_CAPACITY_SCALE) / +@@ -10021,6 +10283,16 @@ static bool update_sd_pick_busiest(struct lb_env *env, + /* Prefer to move from lowest priority CPU's work */ + if (sched_asym_prefer(sg->asym_prefer_cpu, sds->busiest->asym_prefer_cpu)) + return false; ++ ++ /* ++ * Unlike other callers of sched_asym_prefer(), here both @sg ++ * and @sds::busiest have tasks running. When they have equal ++ * priority, their IPC class scores can be used to select a ++ * better busiest. ++ */ ++ if (sched_asym_ipcc_pick(sds->busiest, sg, &sds->busiest_stat, sgs)) ++ return false; ++ + break; + + case group_misfit_task: +@@ -10061,10 +10333,21 @@ static bool update_sd_pick_busiest(struct lb_env *env, + if (sgs->avg_load == busiest->avg_load) { + /* + * SMT sched groups need more help than non-SMT groups. +- * If @sg happens to also be SMT, either choice is good. + */ +- if (sds->busiest->flags & SD_SHARE_CPUCAPACITY) +- return false; ++ if (sds->busiest->flags & SD_SHARE_CPUCAPACITY) { ++ if (!(sg->flags & SD_SHARE_CPUCAPACITY)) ++ return false; ++ ++ /* ++ * Between two SMT groups, use IPCC scores to pick the ++ * one that would improve throughput the most (only ++ * asym_packing uses IPCC scores for now). ++ */ ++ if (sched_ipcc_enabled() && ++ env->sd->flags & SD_ASYM_PACKING && ++ sched_asym_ipcc_prefer(busiest, sgs)) ++ return false; ++ } + } + + break; +@@ -10981,6 +11264,7 @@ static struct rq *find_busiest_queue(struct lb_env *env, + { + struct rq *busiest = NULL, *rq; + unsigned long busiest_util = 0, busiest_load = 0, busiest_capacity = 1; ++ long busiest_ipcc_delta = LONG_MIN; + unsigned int busiest_nr = 0; + int i; + +@@ -11097,6 +11381,26 @@ static struct rq *find_busiest_queue(struct lb_env *env, + if (busiest_nr < nr_running) { + busiest_nr = nr_running; + busiest = rq; ++ ++ /* ++ * Remember the IPCC score of the busiest ++ * runqueue. We may need it to break a tie with ++ * other queues with equal nr_running. ++ */ ++ busiest_ipcc_delta = ipcc_score_delta(busiest, env); ++ /* ++ * For ties, select @rq if doing would give its last ++ * queued task a bigger IPC boost when migrated to ++ * dst_cpu. ++ */ ++ } else if (busiest_nr == nr_running) { ++ long delta = ipcc_score_delta(rq, env); ++ ++ if (busiest_ipcc_delta < delta) { ++ busiest_ipcc_delta = delta; ++ busiest_nr = nr_running; ++ busiest = rq; ++ } + } + break; + +@@ -11228,7 +11532,7 @@ static int should_we_balance(struct lb_env *env) + * balancing cores, but remember the first idle SMT CPU for + * later consideration. Find CPU on an idle core first. + */ +- if (!(env->sd->flags & SD_SHARE_CPUCAPACITY) && !is_core_idle(cpu)) { ++ if (!(env->sd->flags & SD_SHARE_CPUCAPACITY) && !sched_smt_siblings_idle(cpu)) { + if (idle_smt == -1) + idle_smt = cpu; + /* +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +index 001fe047bd5..b741fca335b 100644 +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -2622,6 +2622,72 @@ void arch_scale_freq_tick(void) + } + #endif + ++#ifdef CONFIG_IPC_CLASSES ++DECLARE_STATIC_KEY_FALSE(sched_ipcc); ++ ++static inline bool sched_ipcc_enabled(void) ++{ ++ return static_branch_unlikely(&sched_ipcc); ++} ++ ++#ifndef arch_update_ipcc ++/** ++ * arch_update_ipcc() - Update the IPC class of the current task ++ * @curr: The current task ++ * ++ * Request that the IPC classification of @curr is updated. ++ * ++ * Returns: none ++ */ ++static __always_inline ++void arch_update_ipcc(struct task_struct *curr) ++{ ++} ++#endif ++ ++#ifndef arch_get_ipcc_score ++ ++#define SCHED_IPCC_SCORE_SCALE (1L << SCHED_FIXEDPOINT_SHIFT) ++/** ++ * arch_get_ipcc_score() - Get the IPC score of a class of task ++ * @ipcc: The IPC class ++ * @cpu: A CPU number ++ * ++ * The IPC performance scores reflects (but it is not identical to) the number ++ * of instructions retired per cycle for a given IPC class. It is a linear and ++ * abstract metric. Higher scores reflect better performance. ++ * ++ * The IPC score can be normalized with respect to the class, i, with the ++ * highest IPC score on the CPU, c, with highest performance: ++ * ++ * IPC(i, c) ++ * ------------------------------------ * SCHED_IPCC_SCORE_SCALE ++ * max(IPC(i, c) : (i, c)) ++ * ++ * Scheduling schemes that want to use the IPC score along with other ++ * normalized metrics for scheduling (e.g., CPU capacity) may need to normalize ++ * it. ++ * ++ * Other scheduling schemes (e.g., asym_packing) do not need normalization. ++ * ++ * Returns the performance score of an IPC class, @ipcc, when running on @cpu. ++ * Error when either @ipcc or @cpu are invalid. ++ */ ++static __always_inline ++unsigned long arch_get_ipcc_score(unsigned short ipcc, int cpu) ++{ ++ return SCHED_IPCC_SCORE_SCALE; ++} ++#endif ++#else /* CONFIG_IPC_CLASSES */ ++ ++#define arch_get_ipcc_score(ipcc, cpu) (-EINVAL) ++#define arch_update_ipcc(curr) ++ ++static inline bool sched_ipcc_enabled(void) { return false; } ++ ++#endif /* CONFIG_IPC_CLASSES */ ++ + #ifndef arch_scale_freq_capacity + /** + * arch_scale_freq_capacity - get the frequency scale factor of a given CPU. +diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c +index 10d1391e741..da49c3c5162 100644 +--- a/kernel/sched/topology.c ++++ b/kernel/sched/topology.c +@@ -677,6 +677,15 @@ DEFINE_PER_CPU(struct sched_domain __rcu *, sd_asym_cpucapacity); + DEFINE_STATIC_KEY_FALSE(sched_asym_cpucapacity); + DEFINE_STATIC_KEY_FALSE(sched_cluster_active); + ++#ifdef CONFIG_IPC_CLASSES ++DEFINE_STATIC_KEY_FALSE(sched_ipcc); ++ ++void sched_enable_ipc_classes(void) ++{ ++ static_branch_enable_cpuslocked(&sched_ipcc); ++} ++#endif ++ + static void update_top_cache_domain(int cpu) + { + struct sched_domain_shared *sds = NULL; +diff --git a/kernel/time/timer.c b/kernel/time/timer.c +index 352b161113c..f739cd5912d 100644 +--- a/kernel/time/timer.c ++++ b/kernel/time/timer.c +@@ -2089,7 +2089,7 @@ void update_process_times(int user_tick) + if (in_irq()) + irq_work_tick(); + #endif +- scheduler_tick(); ++ scheduler_tick(user_tick); + if (IS_ENABLED(CONFIG_POSIX_TIMERS)) + run_posix_cpu_timers(); + } +-- +2.44.0 + + +From 6ac91be34077c54e9f7459098aff5b9d183de7f8 Mon Sep 17 00:00:00 2001 +From: Stanislaw Gruszka +Date: Mon, 12 Feb 2024 17:16:13 +0100 +Subject: [PATCH 2/2] genetlink: Add per family bind/unbind callbacks + +Add genetlink family bind()/unbind() callbacks when adding/removing +multicast group to/from netlink client socket via setsockopt() or +bind() syscall. + +They can be used to track if consumers of netlink multicast messages +emerge or disappear. Thus, a client implementing callbacks, can now +send events only when there are active consumers, preventing unnecessary +work when none exist. + +Suggested-by: Jakub Kicinski +Signed-off-by: Stanislaw Gruszka +Reviewed-by: Jiri Pirko +Link: https://lore.kernel.org/r/20240212161615.161935-2-stanislaw.gruszka@linux.intel.com +Signed-off-by: Jakub Kicinski +--- + include/net/genetlink.h | 4 ++++ + net/netlink/genetlink.c | 30 ++++++++++++++++++++++++++++++ + 2 files changed, 34 insertions(+) + +diff --git a/include/net/genetlink.h b/include/net/genetlink.h +index e6146912940..ecadba836ae 100644 +--- a/include/net/genetlink.h ++++ b/include/net/genetlink.h +@@ -41,6 +41,8 @@ struct genl_info; + * do additional, common, filtering and return an error + * @post_doit: called after an operation's doit callback, it may + * undo operations done by pre_doit, for example release locks ++ * @bind: called when family multicast group is added to a netlink socket ++ * @unbind: called when family multicast group is removed from a netlink socket + * @module: pointer to the owning module (set to THIS_MODULE) + * @mcgrps: multicast groups used by this family + * @n_mcgrps: number of multicast groups +@@ -84,6 +86,8 @@ struct genl_family { + void (*post_doit)(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); ++ int (*bind)(int mcgrp); ++ void (*unbind)(int mcgrp); + const struct genl_ops * ops; + const struct genl_small_ops *small_ops; + const struct genl_split_ops *split_ops; +diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c +index 8c7af02f845..50ec599a5cf 100644 +--- a/net/netlink/genetlink.c ++++ b/net/netlink/genetlink.c +@@ -1836,6 +1836,9 @@ static int genl_bind(struct net *net, int group) + !ns_capable(net->user_ns, CAP_SYS_ADMIN)) + ret = -EPERM; + ++ if (family->bind) ++ family->bind(i); ++ + break; + } + +@@ -1843,12 +1846,39 @@ static int genl_bind(struct net *net, int group) + return ret; + } + ++static void genl_unbind(struct net *net, int group) ++{ ++ const struct genl_family *family; ++ unsigned int id; ++ ++ down_read(&cb_lock); ++ ++ idr_for_each_entry(&genl_fam_idr, family, id) { ++ int i; ++ ++ if (family->n_mcgrps == 0) ++ continue; ++ ++ i = group - family->mcgrp_offset; ++ if (i < 0 || i >= family->n_mcgrps) ++ continue; ++ ++ if (family->unbind) ++ family->unbind(i); ++ ++ break; ++ } ++ ++ up_read(&cb_lock); ++} ++ + static int __net_init genl_pernet_init(struct net *net) + { + struct netlink_kernel_cfg cfg = { + .input = genl_rcv, + .flags = NL_CFG_F_NONROOT_RECV, + .bind = genl_bind, ++ .unbind = genl_unbind, + .release = genl_release, + }; + +-- +2.44.0 + +From 68a15ef01803c252261ebb47d86dfc1f2c68ae1e Mon Sep 17 00:00:00 2001 +From: Tim Chen +Date: Fri, 6 Oct 2023 15:58:56 -0700 +Subject: [PATCH] sched/fair: Don't force smt balancing when CPU has spare + capacity + +Currently group_smt_balance is picked whenever there are more +than two tasks on a core with two SMT. However, the utilization +of those tasks may be low and do not warrant a task +migration to a CPU of lower priority. + +Adjust sched group clssification and sibling_imbalance() +to reflect this consideration. Use sibling_imbalance() to +compute imbalance in calculate_imbalance() for the group_smt_balance +case. + +Signed-off-by: Tim Chen + +--- + kernel/sched/fair.c | 23 +++++++++++------------ + 1 file changed, 11 insertions(+), 12 deletions(-) + +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +index ef7490c4b8b4..7dd7c2d2367a 100644 +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -9460,14 +9460,15 @@ group_type group_classify(unsigned int imbalance_pct, + if (sgs->group_asym_packing) + return group_asym_packing; + +- if (sgs->group_smt_balance) +- return group_smt_balance; +- + if (sgs->group_misfit_task_load) + return group_misfit_task; + +- if (!group_has_capacity(imbalance_pct, sgs)) +- return group_fully_busy; ++ if (!group_has_capacity(imbalance_pct, sgs)) { ++ if (sgs->group_smt_balance) ++ return group_smt_balance; ++ else ++ return group_fully_busy; ++ } + + return group_has_spare; + } +@@ -9573,6 +9574,11 @@ static inline long sibling_imbalance(struct lb_env *env, + if (env->idle == CPU_NOT_IDLE || !busiest->sum_nr_running) + return 0; + ++ /* Do not pull tasks off preferred group with spare capacity */ ++ if (busiest->group_type == group_has_spare && ++ sched_asym_prefer(sds->busiest->asym_prefer_cpu, env->dst_cpu)) ++ return 0; ++ + ncores_busiest = sds->busiest->cores; + ncores_local = sds->local->cores; + +@@ -10411,13 +10417,6 @@ static inline void calculate_imbalance(struct lb_env *env, struct sd_lb_stats *s + return; + } + +- if (busiest->group_type == group_smt_balance) { +- /* Reduce number of tasks sharing CPU capacity */ +- env->migration_type = migrate_task; +- env->imbalance = 1; +- return; +- } +- + if (busiest->group_type == group_imbalanced) { + /* + * In the group_imb case we cannot rely on group-wide averages +-- +2.32.0 \ No newline at end of file diff --git a/patches/nobara/OpenRGB.patch b/patches/nobara/OpenRGB.patch index 0ca0401..3ddf50e 100644 --- a/patches/nobara/OpenRGB.patch +++ b/patches/nobara/OpenRGB.patch @@ -1,24 +1,11 @@ -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 +index 2ddca08f8a76..72647850f08e 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig -@@ -229,6 +229,15 @@ config I2C_CHT_WC +@@ -217,6 +217,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 @@ -32,10 +19,10 @@ index 9cfe8fc50..efc3b0c0b 100644 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 +index 25d60889713c..3c2a9b237ac6 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile -@@ -20,6 +20,7 @@ obj-$(CONFIG_I2C_CHT_WC) += i2c-cht-wc.o +@@ -17,6 +17,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 @@ -45,7 +32,7 @@ index af56fe2c7..76be74584 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 000000000..0462f0952 +index 000000000000..0462f0952043 --- /dev/null +++ b/drivers/i2c/busses/i2c-nct6775.c @@ -0,0 +1,647 @@ @@ -697,23 +684,20 @@ index 000000000..0462f0952 +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 +index 30ded6422e7b..e25ce84c26af 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) +@@ -467,11 +467,11 @@ static int piix4_transaction(struct i2c_adapter *piix4_adapter) if (srvrworks_csb5_delay) /* Extra delay for SERVERWORKS_CSB5 */ usleep_range(2000, 2100); else - usleep_range(250, 500); + usleep_range(25, 50); - + while ((++timeout < MAX_TIMEOUT) && ((temp = inb_p(SMBHSTSTS)) & 0x01)) - usleep_range(250, 500); + usleep_range(25, 50); - + /* If the SMBus is still busy, we give up */ if (timeout == MAX_TIMEOUT) { --- -2.41.0 - diff --git a/patches/nobara/winesync.patch b/patches/nobara/winesync.patch deleted file mode 100644 index 3c72546..0000000 --- a/patches/nobara/winesync.patch +++ /dev/null @@ -1,5071 +0,0 @@ -From 153c94d81f583dfbd9e4e81eefc6a9b8e83ff06d Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 10:50:45 -0600 -Subject: [PATCH 01/34] winesync: Introduce the winesync driver and character - device. - ---- - drivers/misc/Kconfig | 11 +++++++ - drivers/misc/Makefile | 1 + - drivers/misc/winesync.c | 64 +++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 76 insertions(+) - create mode 100644 drivers/misc/winesync.c - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 94e9fb4cdd76..4f9e3d80a6e8 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -562,6 +562,17 @@ config VCPU_STALL_DETECTOR - This driver can also be built as a module. If so, the module - will be called tps6594-pfsm. - -+config WINESYNC -+ tristate "Synchronization primitives for Wine" -+ help -+ This module provides kernel support for synchronization primitives -+ used by Wine. It is not a hardware driver. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called winesync. -+ -+ If unsure, say N. -+ - source "drivers/misc/c2port/Kconfig" - source "drivers/misc/eeprom/Kconfig" - source "drivers/misc/cb710/Kconfig" -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index 2be8542616dd..d061fe45407b 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -59,6 +59,7 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ - obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o -+obj-$(CONFIG_WINESYNC) += winesync.o - obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o - obj-$(CONFIG_OPEN_DICE) += open-dice.o - obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -new file mode 100644 -index 000000000000..111f33c5676e ---- /dev/null -+++ b/drivers/misc/winesync.c -@@ -0,0 +1,64 @@ -+// SPDX-License-Identifier: GPL-2.0-only -+/* -+ * winesync.c - Kernel driver for Wine synchronization primitives -+ * -+ * Copyright (C) 2021 Zebediah Figura -+ */ -+ -+#include -+#include -+#include -+ -+#define WINESYNC_NAME "winesync" -+ -+static int winesync_char_open(struct inode *inode, struct file *file) -+{ -+ return nonseekable_open(inode, file); -+} -+ -+static int winesync_char_release(struct inode *inode, struct file *file) -+{ -+ return 0; -+} -+ -+static long winesync_char_ioctl(struct file *file, unsigned int cmd, -+ unsigned long parm) -+{ -+ switch (cmd) { -+ default: -+ return -ENOSYS; -+ } -+} -+ -+static const struct file_operations winesync_fops = { -+ .owner = THIS_MODULE, -+ .open = winesync_char_open, -+ .release = winesync_char_release, -+ .unlocked_ioctl = winesync_char_ioctl, -+ .compat_ioctl = winesync_char_ioctl, -+ .llseek = no_llseek, -+}; -+ -+static struct miscdevice winesync_misc = { -+ .minor = MISC_DYNAMIC_MINOR, -+ .name = WINESYNC_NAME, -+ .fops = &winesync_fops, -+}; -+ -+static int __init winesync_init(void) -+{ -+ return misc_register(&winesync_misc); -+} -+ -+static void __exit winesync_exit(void) -+{ -+ misc_deregister(&winesync_misc); -+} -+ -+module_init(winesync_init); -+module_exit(winesync_exit); -+ -+MODULE_AUTHOR("Zebediah Figura"); -+MODULE_DESCRIPTION("Kernel driver for Wine synchronization primitives"); -+MODULE_LICENSE("GPL"); -+MODULE_ALIAS("devname:" WINESYNC_NAME); --- -2.37.3 - -From 1f142d40cb7537bd936a68cadaf0f2a0d94abd62 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 10:57:06 -0600 -Subject: [PATCH 02/34] winesync: Reserve a minor device number and ioctl - range. - ---- - Documentation/admin-guide/devices.txt | 3 ++- - Documentation/userspace-api/ioctl/ioctl-number.rst | 2 ++ - drivers/misc/winesync.c | 3 ++- - include/linux/miscdevice.h | 1 + - 4 files changed, 7 insertions(+), 2 deletions(-) - -diff --git a/Documentation/admin-guide/devices.txt b/Documentation/admin-guide/devices.txt -index c07dc0ee860e..4e5abe508426 100644 ---- a/Documentation/admin-guide/devices.txt -+++ b/Documentation/admin-guide/devices.txt -@@ -376,8 +376,9 @@ - 240 = /dev/userio Serio driver testing device - 241 = /dev/vhost-vsock Host kernel driver for virtio vsock - 242 = /dev/rfkill Turning off radio transmissions (rfkill) -+ 243 = /dev/winesync Wine synchronization primitive device - -- 243-254 Reserved for local use -+ 244-254 Reserved for local use - 255 Reserved for MISC_DYNAMIC_MINOR - - 11 char Raw keyboard device (Linux/SPARC only) -diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst -index 3b985b19f39d..3f313fd4338c 100644 ---- a/Documentation/userspace-api/ioctl/ioctl-number.rst -+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst -@@ -375,6 +375,8 @@ Code Seq# Include File Comments - - 0xF6 all LTTng Linux Trace Toolkit Next Generation - -+0xF7 00-0F uapi/linux/winesync.h Wine synchronization primitives -+ - 0xF8 all arch/x86/include/uapi/asm/amd_hsmp.h AMD HSMP EPYC system management interface driver - - 0xFD all linux/dm-ioctl.h -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 111f33c5676e..85cb6ccaa077 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -40,7 +40,7 @@ static const struct file_operations winesync_fops = { - }; - - static struct miscdevice winesync_misc = { -- .minor = MISC_DYNAMIC_MINOR, -+ .minor = WINESYNC_MINOR, - .name = WINESYNC_NAME, - .fops = &winesync_fops, - }; -@@ -62,3 +62,4 @@ MODULE_AUTHOR("Zebediah Figura"); - MODULE_DESCRIPTION("Kernel driver for Wine synchronization primitives"); - MODULE_LICENSE("GPL"); - MODULE_ALIAS("devname:" WINESYNC_NAME); -+MODULE_ALIAS_MISCDEV(WINESYNC_MINOR); -diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h -index 0676f18093f9..350aecfcfb29 100644 ---- a/include/linux/miscdevice.h -+++ b/include/linux/miscdevice.h -@@ -71,6 +71,7 @@ - #define USERIO_MINOR 240 - #define VHOST_VSOCK_MINOR 241 - #define RFKILL_MINOR 242 -+#define WINESYNC_MINOR 243 - #define MISC_DYNAMIC_MINOR 255 - - struct device; --- -2.36.0 - -From 8ad26f39cb5442d9e17f22ed0cda8d3669bb11b5 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:15:39 -0600 -Subject: [PATCH 03/34] winesync: Introduce WINESYNC_IOC_CREATE_SEM and - WINESYNC_IOC_DELETE. - ---- - drivers/misc/winesync.c | 117 ++++++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 25 ++++++++ - 2 files changed, 142 insertions(+) - create mode 100644 include/uapi/linux/winesync.h - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 85cb6ccaa077..36e31bbe0390 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -8,23 +8,140 @@ - #include - #include - #include -+#include -+#include -+#include - - #define WINESYNC_NAME "winesync" - -+enum winesync_type { -+ WINESYNC_TYPE_SEM, -+}; -+ -+struct winesync_obj { -+ struct rcu_head rhead; -+ struct kref refcount; -+ -+ enum winesync_type type; -+ -+ union { -+ struct { -+ __u32 count; -+ __u32 max; -+ } sem; -+ } u; -+}; -+ -+struct winesync_device { -+ struct xarray objects; -+}; -+ -+static void destroy_obj(struct kref *ref) -+{ -+ struct winesync_obj *obj = container_of(ref, struct winesync_obj, refcount); -+ -+ kfree_rcu(obj, rhead); -+} -+ -+static void put_obj(struct winesync_obj *obj) -+{ -+ kref_put(&obj->refcount, destroy_obj); -+} -+ - static int winesync_char_open(struct inode *inode, struct file *file) - { -+ struct winesync_device *dev; -+ -+ dev = kzalloc(sizeof(*dev), GFP_KERNEL); -+ if (!dev) -+ return -ENOMEM; -+ -+ xa_init_flags(&dev->objects, XA_FLAGS_ALLOC); -+ -+ file->private_data = dev; - return nonseekable_open(inode, file); - } - - static int winesync_char_release(struct inode *inode, struct file *file) - { -+ struct winesync_device *dev = file->private_data; -+ struct winesync_obj *obj; -+ unsigned long id; -+ -+ xa_for_each(&dev->objects, id, obj) -+ put_obj(obj); -+ -+ xa_destroy(&dev->objects); -+ -+ kfree(dev); -+ -+ return 0; -+} -+ -+static void init_obj(struct winesync_obj *obj) -+{ -+ kref_init(&obj->refcount); -+} -+ -+static int winesync_create_sem(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_sem_args __user *user_args = argp; -+ struct winesync_sem_args args; -+ struct winesync_obj *sem; -+ __u32 id; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ if (args.count > args.max) -+ return -EINVAL; -+ -+ sem = kzalloc(sizeof(*sem), GFP_KERNEL); -+ if (!sem) -+ return -ENOMEM; -+ -+ init_obj(sem); -+ sem->type = WINESYNC_TYPE_SEM; -+ sem->u.sem.count = args.count; -+ sem->u.sem.max = args.max; -+ -+ ret = xa_alloc(&dev->objects, &id, sem, xa_limit_32b, GFP_KERNEL); -+ if (ret < 0) { -+ kfree(sem); -+ return ret; -+ } -+ -+ return put_user(id, &user_args->sem); -+} -+ -+static int winesync_delete(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_obj *obj; -+ __u32 id; -+ -+ if (get_user(id, (__u32 __user *)argp)) -+ return -EFAULT; -+ -+ obj = xa_erase(&dev->objects, id); -+ if (!obj) -+ return -EINVAL; -+ -+ put_obj(obj); - return 0; - } - - static long winesync_char_ioctl(struct file *file, unsigned int cmd, - unsigned long parm) - { -+ struct winesync_device *dev = file->private_data; -+ void __user *argp = (void __user *)parm; -+ - switch (cmd) { -+ case WINESYNC_IOC_CREATE_SEM: -+ return winesync_create_sem(dev, argp); -+ case WINESYNC_IOC_DELETE: -+ return winesync_delete(dev, argp); - default: - return -ENOSYS; - } -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -new file mode 100644 -index 000000000000..aabb491f39d2 ---- /dev/null -+++ b/include/uapi/linux/winesync.h -@@ -0,0 +1,25 @@ -+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -+/* -+ * Kernel support for Wine synchronization primitives -+ * -+ * Copyright (C) 2021 Zebediah Figura -+ */ -+ -+#ifndef __LINUX_WINESYNC_H -+#define __LINUX_WINESYNC_H -+ -+#include -+ -+struct winesync_sem_args { -+ __u32 sem; -+ __u32 count; -+ __u32 max; -+}; -+ -+#define WINESYNC_IOC_BASE 0xf7 -+ -+#define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \ -+ struct winesync_sem_args) -+#define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __u32) -+ -+#endif --- -2.36.0 - -From 144e223bfd7c5e733a9e7e50a3a8d37dbbedc0b7 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:22:42 -0600 -Subject: [PATCH 04/34] winesync: Introduce WINESYNC_IOC_PUT_SEM. - ---- - drivers/misc/winesync.c | 76 +++++++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 2 + - 2 files changed, 78 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 36e31bbe0390..84b5a5c9e0ce 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -21,9 +21,11 @@ enum winesync_type { - struct winesync_obj { - struct rcu_head rhead; - struct kref refcount; -+ spinlock_t lock; - - enum winesync_type type; - -+ /* The following fields are protected by the object lock. */ - union { - struct { - __u32 count; -@@ -36,6 +38,19 @@ struct winesync_device { - struct xarray objects; - }; - -+static struct winesync_obj *get_obj(struct winesync_device *dev, __u32 id) -+{ -+ struct winesync_obj *obj; -+ -+ rcu_read_lock(); -+ obj = xa_load(&dev->objects, id); -+ if (obj && !kref_get_unless_zero(&obj->refcount)) -+ obj = NULL; -+ rcu_read_unlock(); -+ -+ return obj; -+} -+ - static void destroy_obj(struct kref *ref) - { - struct winesync_obj *obj = container_of(ref, struct winesync_obj, refcount); -@@ -48,6 +63,18 @@ static void put_obj(struct winesync_obj *obj) - kref_put(&obj->refcount, destroy_obj); - } - -+static struct winesync_obj *get_obj_typed(struct winesync_device *dev, __u32 id, -+ enum winesync_type type) -+{ -+ struct winesync_obj *obj = get_obj(dev, id); -+ -+ if (obj && obj->type != type) { -+ put_obj(obj); -+ return NULL; -+ } -+ return obj; -+} -+ - static int winesync_char_open(struct inode *inode, struct file *file) - { - struct winesync_device *dev; -@@ -81,6 +108,7 @@ static int winesync_char_release(struct inode *inode, struct file *file) - static void init_obj(struct winesync_obj *obj) - { - kref_init(&obj->refcount); -+ spin_lock_init(&obj->lock); - } - - static int winesync_create_sem(struct winesync_device *dev, void __user *argp) -@@ -131,6 +159,52 @@ static int winesync_delete(struct winesync_device *dev, void __user *argp) - return 0; - } - -+/* -+ * Actually change the semaphore state, returning -EOVERFLOW if it is made -+ * invalid. -+ */ -+static int put_sem_state(struct winesync_obj *sem, __u32 count) -+{ -+ lockdep_assert_held(&sem->lock); -+ -+ if (sem->u.sem.count + count < sem->u.sem.count || -+ sem->u.sem.count + count > sem->u.sem.max) -+ return -EOVERFLOW; -+ -+ sem->u.sem.count += count; -+ return 0; -+} -+ -+static int winesync_put_sem(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_sem_args __user *user_args = argp; -+ struct winesync_sem_args args; -+ struct winesync_obj *sem; -+ __u32 prev_count; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ sem = get_obj_typed(dev, args.sem, WINESYNC_TYPE_SEM); -+ if (!sem) -+ return -EINVAL; -+ -+ spin_lock(&sem->lock); -+ -+ prev_count = sem->u.sem.count; -+ ret = put_sem_state(sem, args.count); -+ -+ spin_unlock(&sem->lock); -+ -+ put_obj(sem); -+ -+ if (!ret && put_user(prev_count, &user_args->count)) -+ ret = -EFAULT; -+ -+ return ret; -+} -+ - static long winesync_char_ioctl(struct file *file, unsigned int cmd, - unsigned long parm) - { -@@ -142,6 +216,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_create_sem(dev, argp); - case WINESYNC_IOC_DELETE: - return winesync_delete(dev, argp); -+ case WINESYNC_IOC_PUT_SEM: -+ return winesync_put_sem(dev, argp); - default: - return -ENOSYS; - } -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index aabb491f39d2..7681a168eb92 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -21,5 +21,7 @@ struct winesync_sem_args { - #define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \ - struct winesync_sem_args) - #define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __u32) -+#define WINESYNC_IOC_PUT_SEM _IOWR(WINESYNC_IOC_BASE, 2, \ -+ struct winesync_sem_args) - - #endif --- -2.36.0 - -From 207daf2aa77f9d197b205a88322d5359f432bc67 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:31:44 -0600 -Subject: [PATCH 05/34] winesync: Introduce WINESYNC_IOC_WAIT_ANY. - ---- - drivers/misc/winesync.c | 226 ++++++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 11 ++ - 2 files changed, 237 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 84b5a5c9e0ce..d9b5ab159520 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -23,6 +23,8 @@ struct winesync_obj { - struct kref refcount; - spinlock_t lock; - -+ struct list_head any_waiters; -+ - enum winesync_type type; - - /* The following fields are protected by the object lock. */ -@@ -34,6 +36,28 @@ struct winesync_obj { - } u; - }; - -+struct winesync_q_entry { -+ struct list_head node; -+ struct winesync_q *q; -+ struct winesync_obj *obj; -+ __u32 index; -+}; -+ -+struct winesync_q { -+ struct task_struct *task; -+ __u32 owner; -+ -+ /* -+ * Protected via atomic_cmpxchg(). Only the thread that wins the -+ * compare-and-swap may actually change object states and wake this -+ * task. -+ */ -+ atomic_t signaled; -+ -+ __u32 count; -+ struct winesync_q_entry entries[]; -+}; -+ - struct winesync_device { - struct xarray objects; - }; -@@ -109,6 +133,26 @@ static void init_obj(struct winesync_obj *obj) - { - kref_init(&obj->refcount); - spin_lock_init(&obj->lock); -+ INIT_LIST_HEAD(&obj->any_waiters); -+} -+ -+static void try_wake_any_sem(struct winesync_obj *sem) -+{ -+ struct winesync_q_entry *entry; -+ -+ lockdep_assert_held(&sem->lock); -+ -+ list_for_each_entry(entry, &sem->any_waiters, node) { -+ struct winesync_q *q = entry->q; -+ -+ if (!sem->u.sem.count) -+ break; -+ -+ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { -+ sem->u.sem.count--; -+ wake_up_process(q->task); -+ } -+ } - } - - static int winesync_create_sem(struct winesync_device *dev, void __user *argp) -@@ -194,6 +238,8 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) - - prev_count = sem->u.sem.count; - ret = put_sem_state(sem, args.count); -+ if (!ret) -+ try_wake_any_sem(sem); - - spin_unlock(&sem->lock); - -@@ -205,6 +251,184 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) - return ret; - } - -+static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) -+{ -+ int ret = 0; -+ -+ do { -+ if (signal_pending(current)) { -+ ret = -ERESTARTSYS; -+ break; -+ } -+ -+ set_current_state(TASK_INTERRUPTIBLE); -+ if (atomic_read(&q->signaled) != -1) { -+ ret = 0; -+ break; -+ } -+ ret = schedule_hrtimeout(timeout, HRTIMER_MODE_ABS); -+ } while (ret < 0); -+ __set_current_state(TASK_RUNNING); -+ -+ return ret; -+} -+ -+/* -+ * Allocate and initialize the winesync_q structure, but do not queue us yet. -+ * Also, calculate the relative timeout. -+ */ -+static int setup_wait(struct winesync_device *dev, -+ const struct winesync_wait_args *args, -+ ktime_t *ret_timeout, struct winesync_q **ret_q) -+{ -+ const __u32 count = args->count; -+ struct winesync_q *q; -+ ktime_t timeout = 0; -+ __u32 *ids; -+ __u32 i, j; -+ -+ if (!args->owner || args->pad) -+ return -EINVAL; -+ -+ if (args->timeout) { -+ struct timespec64 to; -+ -+ if (get_timespec64(&to, u64_to_user_ptr(args->timeout))) -+ return -EFAULT; -+ if (!timespec64_valid(&to)) -+ return -EINVAL; -+ -+ timeout = timespec64_to_ns(&to); -+ } -+ -+ ids = kmalloc_array(count, sizeof(*ids), GFP_KERNEL); -+ if (!ids) -+ return -ENOMEM; -+ if (copy_from_user(ids, u64_to_user_ptr(args->objs), -+ array_size(count, sizeof(*ids)))) { -+ kfree(ids); -+ return -EFAULT; -+ } -+ -+ q = kmalloc(struct_size(q, entries, count), GFP_KERNEL); -+ if (!q) { -+ kfree(ids); -+ return -ENOMEM; -+ } -+ q->task = current; -+ q->owner = args->owner; -+ atomic_set(&q->signaled, -1); -+ q->count = count; -+ -+ for (i = 0; i < count; i++) { -+ struct winesync_q_entry *entry = &q->entries[i]; -+ struct winesync_obj *obj = get_obj(dev, ids[i]); -+ -+ if (!obj) -+ goto err; -+ -+ entry->obj = obj; -+ entry->q = q; -+ entry->index = i; -+ } -+ -+ kfree(ids); -+ -+ *ret_q = q; -+ *ret_timeout = timeout; -+ return 0; -+ -+err: -+ for (j = 0; j < i; j++) -+ put_obj(q->entries[j].obj); -+ kfree(ids); -+ kfree(q); -+ return -EINVAL; -+} -+ -+static void try_wake_any_obj(struct winesync_obj *obj) -+{ -+ switch (obj->type) { -+ case WINESYNC_TYPE_SEM: -+ try_wake_any_sem(obj); -+ break; -+ } -+} -+ -+static int winesync_wait_any(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_wait_args args; -+ struct winesync_q *q; -+ ktime_t timeout; -+ int signaled; -+ __u32 i; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ ret = setup_wait(dev, &args, &timeout, &q); -+ if (ret < 0) -+ return ret; -+ -+ /* queue ourselves */ -+ -+ for (i = 0; i < args.count; i++) { -+ struct winesync_q_entry *entry = &q->entries[i]; -+ struct winesync_obj *obj = entry->obj; -+ -+ spin_lock(&obj->lock); -+ list_add_tail(&entry->node, &obj->any_waiters); -+ spin_unlock(&obj->lock); -+ } -+ -+ /* check if we are already signaled */ -+ -+ for (i = 0; i < args.count; i++) { -+ struct winesync_obj *obj = q->entries[i].obj; -+ -+ if (atomic_read(&q->signaled) != -1) -+ break; -+ -+ spin_lock(&obj->lock); -+ try_wake_any_obj(obj); -+ spin_unlock(&obj->lock); -+ } -+ -+ /* sleep */ -+ -+ ret = winesync_schedule(q, args.timeout ? &timeout : NULL); -+ -+ /* and finally, unqueue */ -+ -+ for (i = 0; i < args.count; i++) { -+ struct winesync_q_entry *entry = &q->entries[i]; -+ struct winesync_obj *obj = entry->obj; -+ -+ spin_lock(&obj->lock); -+ list_del(&entry->node); -+ spin_unlock(&obj->lock); -+ -+ put_obj(obj); -+ } -+ -+ signaled = atomic_read(&q->signaled); -+ if (signaled != -1) { -+ struct winesync_wait_args __user *user_args = argp; -+ -+ /* even if we caught a signal, we need to communicate success */ -+ ret = 0; -+ -+ if (put_user(signaled, &user_args->index)) -+ ret = -EFAULT; -+ } else if (!ret) { -+ ret = -ETIMEDOUT; -+ } -+ -+ kfree(q); -+ return ret; -+} -+ - static long winesync_char_ioctl(struct file *file, unsigned int cmd, - unsigned long parm) - { -@@ -218,6 +442,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_delete(dev, argp); - case WINESYNC_IOC_PUT_SEM: - return winesync_put_sem(dev, argp); -+ case WINESYNC_IOC_WAIT_ANY: -+ return winesync_wait_any(dev, argp); - default: - return -ENOSYS; - } -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 7681a168eb92..f57ebfbe1dd9 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -16,6 +16,15 @@ struct winesync_sem_args { - __u32 max; - }; - -+struct winesync_wait_args { -+ __u64 timeout; -+ __u64 objs; -+ __u32 count; -+ __u32 owner; -+ __u32 index; -+ __u32 pad; -+}; -+ - #define WINESYNC_IOC_BASE 0xf7 - - #define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \ -@@ -23,5 +32,7 @@ struct winesync_sem_args { - #define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __u32) - #define WINESYNC_IOC_PUT_SEM _IOWR(WINESYNC_IOC_BASE, 2, \ - struct winesync_sem_args) -+#define WINESYNC_IOC_WAIT_ANY _IOWR(WINESYNC_IOC_BASE, 3, \ -+ struct winesync_wait_args) - - #endif --- -2.36.0 - -From 3d68ffb91767194d5a1a07aa6c57849343530a15 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:36:09 -0600 -Subject: [PATCH 06/34] winesync: Introduce WINESYNC_IOC_WAIT_ALL. - ---- - drivers/misc/winesync.c | 242 ++++++++++++++++++++++++++++++++-- - include/uapi/linux/winesync.h | 2 + - 2 files changed, 236 insertions(+), 8 deletions(-) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index d9b5ab159520..2b708c5b88a6 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -23,7 +23,34 @@ struct winesync_obj { - struct kref refcount; - spinlock_t lock; - -+ /* -+ * any_waiters is protected by the object lock, but all_waiters is -+ * protected by the device wait_all_lock. -+ */ - struct list_head any_waiters; -+ struct list_head all_waiters; -+ -+ /* -+ * Hint describing how many tasks are queued on this object in a -+ * wait-all operation. -+ * -+ * Any time we do a wake, we may need to wake "all" waiters as well as -+ * "any" waiters. In order to atomically wake "all" waiters, we must -+ * lock all of the objects, and that means grabbing the wait_all_lock -+ * below (and, due to lock ordering rules, before locking this object). -+ * However, wait-all is a rare operation, and grabbing the wait-all -+ * lock for every wake would create unnecessary contention. Therefore we -+ * first check whether all_hint is zero, and, if it is, we skip trying -+ * to wake "all" waiters. -+ * -+ * This hint isn't protected by any lock. It might change during the -+ * course of a wake, but there's no meaningful race there; it's only a -+ * hint. -+ * -+ * Since wait requests must originate from user-space threads, we're -+ * limited here by PID_MAX_LIMIT, so there's no risk of saturation. -+ */ -+ atomic_t all_hint; - - enum winesync_type type; - -@@ -54,11 +81,25 @@ struct winesync_q { - */ - atomic_t signaled; - -+ bool all; - __u32 count; - struct winesync_q_entry entries[]; - }; - - struct winesync_device { -+ /* -+ * Wait-all operations must atomically grab all objects, and be totally -+ * ordered with respect to each other and wait-any operations. If one -+ * thread is trying to acquire several objects, another thread cannot -+ * touch the object at the same time. -+ * -+ * We achieve this by grabbing multiple object locks at the same time. -+ * However, this creates a lock ordering problem. To solve that problem, -+ * wait_all_lock is taken first whenever multiple objects must be locked -+ * at the same time. -+ */ -+ spinlock_t wait_all_lock; -+ - struct xarray objects; - }; - -@@ -107,6 +148,8 @@ static int winesync_char_open(struct inode *inode, struct file *file) - if (!dev) - return -ENOMEM; - -+ spin_lock_init(&dev->wait_all_lock); -+ - xa_init_flags(&dev->objects, XA_FLAGS_ALLOC); - - file->private_data = dev; -@@ -132,8 +175,82 @@ static int winesync_char_release(struct inode *inode, struct file *file) - static void init_obj(struct winesync_obj *obj) - { - kref_init(&obj->refcount); -+ atomic_set(&obj->all_hint, 0); - spin_lock_init(&obj->lock); - INIT_LIST_HEAD(&obj->any_waiters); -+ INIT_LIST_HEAD(&obj->all_waiters); -+} -+ -+static bool is_signaled(struct winesync_obj *obj, __u32 owner) -+{ -+ lockdep_assert_held(&obj->lock); -+ -+ switch (obj->type) { -+ case WINESYNC_TYPE_SEM: -+ return !!obj->u.sem.count; -+ } -+ -+ WARN(1, "bad object type %#x\n", obj->type); -+ return false; -+} -+ -+/* -+ * "locked_obj" is an optional pointer to an object which is already locked and -+ * should not be locked again. This is necessary so that changing an object's -+ * state and waking it can be a single atomic operation. -+ */ -+static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, -+ struct winesync_obj *locked_obj) -+{ -+ __u32 count = q->count; -+ bool can_wake = true; -+ __u32 i; -+ -+ lockdep_assert_held(&dev->wait_all_lock); -+ if (locked_obj) -+ lockdep_assert_held(&locked_obj->lock); -+ -+ for (i = 0; i < count; i++) { -+ if (q->entries[i].obj != locked_obj) -+ spin_lock(&q->entries[i].obj->lock); -+ } -+ -+ for (i = 0; i < count; i++) { -+ if (!is_signaled(q->entries[i].obj, q->owner)) { -+ can_wake = false; -+ break; -+ } -+ } -+ -+ if (can_wake && atomic_cmpxchg(&q->signaled, -1, 0) == -1) { -+ for (i = 0; i < count; i++) { -+ struct winesync_obj *obj = q->entries[i].obj; -+ -+ switch (obj->type) { -+ case WINESYNC_TYPE_SEM: -+ obj->u.sem.count--; -+ break; -+ } -+ } -+ wake_up_process(q->task); -+ } -+ -+ for (i = 0; i < count; i++) { -+ if (q->entries[i].obj != locked_obj) -+ spin_unlock(&q->entries[i].obj->lock); -+ } -+} -+ -+static void try_wake_all_obj(struct winesync_device *dev, -+ struct winesync_obj *obj) -+{ -+ struct winesync_q_entry *entry; -+ -+ lockdep_assert_held(&dev->wait_all_lock); -+ lockdep_assert_held(&obj->lock); -+ -+ list_for_each_entry(entry, &obj->all_waiters, node) -+ try_wake_all(dev, entry->q, obj); - } - - static void try_wake_any_sem(struct winesync_obj *sem) -@@ -234,14 +351,29 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) - if (!sem) - return -EINVAL; - -- spin_lock(&sem->lock); -+ if (atomic_read(&sem->all_hint) > 0) { -+ spin_lock(&dev->wait_all_lock); -+ spin_lock(&sem->lock); -+ -+ prev_count = sem->u.sem.count; -+ ret = put_sem_state(sem, args.count); -+ if (!ret) { -+ try_wake_all_obj(dev, sem); -+ try_wake_any_sem(sem); -+ } - -- prev_count = sem->u.sem.count; -- ret = put_sem_state(sem, args.count); -- if (!ret) -- try_wake_any_sem(sem); -+ spin_unlock(&sem->lock); -+ spin_unlock(&dev->wait_all_lock); -+ } else { -+ spin_lock(&sem->lock); - -- spin_unlock(&sem->lock); -+ prev_count = sem->u.sem.count; -+ ret = put_sem_state(sem, args.count); -+ if (!ret) -+ try_wake_any_sem(sem); -+ -+ spin_unlock(&sem->lock); -+ } - - put_obj(sem); - -@@ -278,7 +410,7 @@ static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) - * Also, calculate the relative timeout. - */ - static int setup_wait(struct winesync_device *dev, -- const struct winesync_wait_args *args, -+ const struct winesync_wait_args *args, bool all, - ktime_t *ret_timeout, struct winesync_q **ret_q) - { - const __u32 count = args->count; -@@ -318,6 +450,7 @@ static int setup_wait(struct winesync_device *dev, - q->task = current; - q->owner = args->owner; - atomic_set(&q->signaled, -1); -+ q->all = all; - q->count = count; - - for (i = 0; i < count; i++) { -@@ -327,6 +460,16 @@ static int setup_wait(struct winesync_device *dev, - if (!obj) - goto err; - -+ if (all) { -+ /* Check that the objects are all distinct. */ -+ for (j = 0; j < i; j++) { -+ if (obj == q->entries[j].obj) { -+ put_obj(obj); -+ goto err; -+ } -+ } -+ } -+ - entry->obj = obj; - entry->q = q; - entry->index = i; -@@ -367,7 +510,7 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) - if (copy_from_user(&args, argp, sizeof(args))) - return -EFAULT; - -- ret = setup_wait(dev, &args, &timeout, &q); -+ ret = setup_wait(dev, &args, false, &timeout, &q); - if (ret < 0) - return ret; - -@@ -429,6 +572,87 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) - return ret; - } - -+static int winesync_wait_all(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_wait_args args; -+ struct winesync_q *q; -+ ktime_t timeout; -+ int signaled; -+ __u32 i; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ ret = setup_wait(dev, &args, true, &timeout, &q); -+ if (ret < 0) -+ return ret; -+ -+ /* queue ourselves */ -+ -+ spin_lock(&dev->wait_all_lock); -+ -+ for (i = 0; i < args.count; i++) { -+ struct winesync_q_entry *entry = &q->entries[i]; -+ struct winesync_obj *obj = entry->obj; -+ -+ atomic_inc(&obj->all_hint); -+ -+ /* -+ * obj->all_waiters is protected by dev->wait_all_lock rather -+ * than obj->lock, so there is no need to acquire it here. -+ */ -+ list_add_tail(&entry->node, &obj->all_waiters); -+ } -+ -+ /* check if we are already signaled */ -+ -+ try_wake_all(dev, q, NULL); -+ -+ spin_unlock(&dev->wait_all_lock); -+ -+ /* sleep */ -+ -+ ret = winesync_schedule(q, args.timeout ? &timeout : NULL); -+ -+ /* and finally, unqueue */ -+ -+ spin_lock(&dev->wait_all_lock); -+ -+ for (i = 0; i < args.count; i++) { -+ struct winesync_q_entry *entry = &q->entries[i]; -+ struct winesync_obj *obj = entry->obj; -+ -+ /* -+ * obj->all_waiters is protected by dev->wait_all_lock rather -+ * than obj->lock, so there is no need to acquire it here. -+ */ -+ list_del(&entry->node); -+ -+ atomic_dec(&obj->all_hint); -+ -+ put_obj(obj); -+ } -+ -+ spin_unlock(&dev->wait_all_lock); -+ -+ signaled = atomic_read(&q->signaled); -+ if (signaled != -1) { -+ struct winesync_wait_args __user *user_args = argp; -+ -+ /* even if we caught a signal, we need to communicate success */ -+ ret = 0; -+ -+ if (put_user(signaled, &user_args->index)) -+ ret = -EFAULT; -+ } else if (!ret) { -+ ret = -ETIMEDOUT; -+ } -+ -+ kfree(q); -+ return ret; -+} -+ - static long winesync_char_ioctl(struct file *file, unsigned int cmd, - unsigned long parm) - { -@@ -442,6 +666,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_delete(dev, argp); - case WINESYNC_IOC_PUT_SEM: - return winesync_put_sem(dev, argp); -+ case WINESYNC_IOC_WAIT_ALL: -+ return winesync_wait_all(dev, argp); - case WINESYNC_IOC_WAIT_ANY: - return winesync_wait_any(dev, argp); - default: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index f57ebfbe1dd9..44025a510cb9 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -34,5 +34,7 @@ struct winesync_wait_args { - struct winesync_sem_args) - #define WINESYNC_IOC_WAIT_ANY _IOWR(WINESYNC_IOC_BASE, 3, \ - struct winesync_wait_args) -+#define WINESYNC_IOC_WAIT_ALL _IOWR(WINESYNC_IOC_BASE, 4, \ -+ struct winesync_wait_args) - - #endif --- -2.36.0 - -From 2838a60302cd26a2ab92a143749e455edebe7b7c Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:41:10 -0600 -Subject: [PATCH 07/34] winesync: Introduce WINESYNC_IOC_CREATE_MUTEX. - ---- - drivers/misc/winesync.c | 72 +++++++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 8 ++++ - 2 files changed, 80 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 2b708c5b88a6..18eb05975907 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -16,6 +16,7 @@ - - enum winesync_type { - WINESYNC_TYPE_SEM, -+ WINESYNC_TYPE_MUTEX, - }; - - struct winesync_obj { -@@ -60,6 +61,10 @@ struct winesync_obj { - __u32 count; - __u32 max; - } sem; -+ struct { -+ __u32 count; -+ __u32 owner; -+ } mutex; - } u; - }; - -@@ -188,6 +193,10 @@ static bool is_signaled(struct winesync_obj *obj, __u32 owner) - switch (obj->type) { - case WINESYNC_TYPE_SEM: - return !!obj->u.sem.count; -+ case WINESYNC_TYPE_MUTEX: -+ if (obj->u.mutex.owner && obj->u.mutex.owner != owner) -+ return false; -+ return obj->u.mutex.count < UINT_MAX; - } - - WARN(1, "bad object type %#x\n", obj->type); -@@ -230,6 +239,10 @@ static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, - case WINESYNC_TYPE_SEM: - obj->u.sem.count--; - break; -+ case WINESYNC_TYPE_MUTEX: -+ obj->u.mutex.count++; -+ obj->u.mutex.owner = q->owner; -+ break; - } - } - wake_up_process(q->task); -@@ -272,6 +285,28 @@ static void try_wake_any_sem(struct winesync_obj *sem) - } - } - -+static void try_wake_any_mutex(struct winesync_obj *mutex) -+{ -+ struct winesync_q_entry *entry; -+ -+ lockdep_assert_held(&mutex->lock); -+ -+ list_for_each_entry(entry, &mutex->any_waiters, node) { -+ struct winesync_q *q = entry->q; -+ -+ if (mutex->u.mutex.count == UINT_MAX) -+ break; -+ if (mutex->u.mutex.owner && mutex->u.mutex.owner != q->owner) -+ continue; -+ -+ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { -+ mutex->u.mutex.count++; -+ mutex->u.mutex.owner = q->owner; -+ wake_up_process(q->task); -+ } -+ } -+} -+ - static int winesync_create_sem(struct winesync_device *dev, void __user *argp) - { - struct winesync_sem_args __user *user_args = argp; -@@ -304,6 +339,38 @@ static int winesync_create_sem(struct winesync_device *dev, void __user *argp) - return put_user(id, &user_args->sem); - } - -+static int winesync_create_mutex(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_mutex_args __user *user_args = argp; -+ struct winesync_mutex_args args; -+ struct winesync_obj *mutex; -+ __u32 id; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ if (!args.owner != !args.count) -+ return -EINVAL; -+ -+ mutex = kzalloc(sizeof(*mutex), GFP_KERNEL); -+ if (!mutex) -+ return -ENOMEM; -+ -+ init_obj(mutex); -+ mutex->type = WINESYNC_TYPE_MUTEX; -+ mutex->u.mutex.count = args.count; -+ mutex->u.mutex.owner = args.owner; -+ -+ ret = xa_alloc(&dev->objects, &id, mutex, xa_limit_32b, GFP_KERNEL); -+ if (ret < 0) { -+ kfree(mutex); -+ return ret; -+ } -+ -+ return put_user(id, &user_args->mutex); -+} -+ - static int winesync_delete(struct winesync_device *dev, void __user *argp) - { - struct winesync_obj *obj; -@@ -495,6 +562,9 @@ static void try_wake_any_obj(struct winesync_obj *obj) - case WINESYNC_TYPE_SEM: - try_wake_any_sem(obj); - break; -+ case WINESYNC_TYPE_MUTEX: -+ try_wake_any_mutex(obj); -+ break; - } - } - -@@ -660,6 +730,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - void __user *argp = (void __user *)parm; - - switch (cmd) { -+ case WINESYNC_IOC_CREATE_MUTEX: -+ return winesync_create_mutex(dev, argp); - case WINESYNC_IOC_CREATE_SEM: - return winesync_create_sem(dev, argp); - case WINESYNC_IOC_DELETE: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 44025a510cb9..23606a3b1546 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -16,6 +16,12 @@ struct winesync_sem_args { - __u32 max; - }; - -+struct winesync_mutex_args { -+ __u32 mutex; -+ __u32 owner; -+ __u32 count; -+}; -+ - struct winesync_wait_args { - __u64 timeout; - __u64 objs; -@@ -36,5 +42,7 @@ struct winesync_wait_args { - struct winesync_wait_args) - #define WINESYNC_IOC_WAIT_ALL _IOWR(WINESYNC_IOC_BASE, 4, \ - struct winesync_wait_args) -+#define WINESYNC_IOC_CREATE_MUTEX _IOWR(WINESYNC_IOC_BASE, 5, \ -+ struct winesync_mutex_args) - - #endif --- -2.36.0 - -From 25b9628ad91377840cdc2b08dd53e1539ad05bdd Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:44:41 -0600 -Subject: [PATCH 08/34] winesync: Introduce WINESYNC_IOC_PUT_MUTEX. - ---- - drivers/misc/winesync.c | 67 +++++++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 2 ++ - 2 files changed, 69 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 18eb05975907..d18d08a68546 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -450,6 +450,71 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) - return ret; - } - -+/* -+ * Actually change the mutex state, returning -EPERM if not the owner. -+ */ -+static int put_mutex_state(struct winesync_obj *mutex, -+ const struct winesync_mutex_args *args) -+{ -+ lockdep_assert_held(&mutex->lock); -+ -+ if (mutex->u.mutex.owner != args->owner) -+ return -EPERM; -+ -+ if (!--mutex->u.mutex.count) -+ mutex->u.mutex.owner = 0; -+ return 0; -+} -+ -+static int winesync_put_mutex(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_mutex_args __user *user_args = argp; -+ struct winesync_mutex_args args; -+ struct winesync_obj *mutex; -+ __u32 prev_count; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ if (!args.owner) -+ return -EINVAL; -+ -+ mutex = get_obj_typed(dev, args.mutex, WINESYNC_TYPE_MUTEX); -+ if (!mutex) -+ return -EINVAL; -+ -+ if (atomic_read(&mutex->all_hint) > 0) { -+ spin_lock(&dev->wait_all_lock); -+ spin_lock(&mutex->lock); -+ -+ prev_count = mutex->u.mutex.count; -+ ret = put_mutex_state(mutex, &args); -+ if (!ret) { -+ try_wake_all_obj(dev, mutex); -+ try_wake_any_mutex(mutex); -+ } -+ -+ spin_unlock(&mutex->lock); -+ spin_unlock(&dev->wait_all_lock); -+ } else { -+ spin_lock(&mutex->lock); -+ -+ prev_count = mutex->u.mutex.count; -+ ret = put_mutex_state(mutex, &args); -+ if (!ret) -+ try_wake_any_mutex(mutex); -+ -+ spin_unlock(&mutex->lock); -+ } -+ -+ put_obj(mutex); -+ -+ if (!ret && put_user(prev_count, &user_args->count)) -+ ret = -EFAULT; -+ -+ return ret; -+} -+ - static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) - { - int ret = 0; -@@ -736,6 +801,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_create_sem(dev, argp); - case WINESYNC_IOC_DELETE: - return winesync_delete(dev, argp); -+ case WINESYNC_IOC_PUT_MUTEX: -+ return winesync_put_mutex(dev, argp); - case WINESYNC_IOC_PUT_SEM: - return winesync_put_sem(dev, argp); - case WINESYNC_IOC_WAIT_ALL: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 23606a3b1546..fde08cb8ab95 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -44,5 +44,7 @@ struct winesync_wait_args { - struct winesync_wait_args) - #define WINESYNC_IOC_CREATE_MUTEX _IOWR(WINESYNC_IOC_BASE, 5, \ - struct winesync_mutex_args) -+#define WINESYNC_IOC_PUT_MUTEX _IOWR(WINESYNC_IOC_BASE, 6, \ -+ struct winesync_mutex_args) - - #endif --- -2.36.0 - -From 97d6dc0155da6609849e6a03bcc9e7d7e0cb58f5 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:46:46 -0600 -Subject: [PATCH 09/34] winesync: Introduce WINESYNC_IOC_KILL_OWNER. - ---- - drivers/misc/winesync.c | 80 ++++++++++++++++++++++++++++++++++- - include/uapi/linux/winesync.h | 1 + - 2 files changed, 79 insertions(+), 2 deletions(-) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index d18d08a68546..891537063bb6 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -64,6 +64,7 @@ struct winesync_obj { - struct { - __u32 count; - __u32 owner; -+ bool ownerdead; - } mutex; - } u; - }; -@@ -87,6 +88,7 @@ struct winesync_q { - atomic_t signaled; - - bool all; -+ bool ownerdead; - __u32 count; - struct winesync_q_entry entries[]; - }; -@@ -240,6 +242,9 @@ static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, - obj->u.sem.count--; - break; - case WINESYNC_TYPE_MUTEX: -+ if (obj->u.mutex.ownerdead) -+ q->ownerdead = true; -+ obj->u.mutex.ownerdead = false; - obj->u.mutex.count++; - obj->u.mutex.owner = q->owner; - break; -@@ -300,6 +305,9 @@ static void try_wake_any_mutex(struct winesync_obj *mutex) - continue; - - if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { -+ if (mutex->u.mutex.ownerdead) -+ q->ownerdead = true; -+ mutex->u.mutex.ownerdead = false; - mutex->u.mutex.count++; - mutex->u.mutex.owner = q->owner; - wake_up_process(q->task); -@@ -515,6 +523,71 @@ static int winesync_put_mutex(struct winesync_device *dev, void __user *argp) - return ret; - } - -+/* -+ * Actually change the mutex state to mark its owner as dead. -+ */ -+static void put_mutex_ownerdead_state(struct winesync_obj *mutex) -+{ -+ lockdep_assert_held(&mutex->lock); -+ -+ mutex->u.mutex.ownerdead = true; -+ mutex->u.mutex.owner = 0; -+ mutex->u.mutex.count = 0; -+} -+ -+static int winesync_kill_owner(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_obj *obj; -+ unsigned long id; -+ __u32 owner; -+ -+ if (get_user(owner, (__u32 __user *)argp)) -+ return -EFAULT; -+ if (!owner) -+ return -EINVAL; -+ -+ rcu_read_lock(); -+ -+ xa_for_each(&dev->objects, id, obj) { -+ if (!kref_get_unless_zero(&obj->refcount)) -+ continue; -+ -+ if (obj->type != WINESYNC_TYPE_MUTEX) { -+ put_obj(obj); -+ continue; -+ } -+ -+ if (atomic_read(&obj->all_hint) > 0) { -+ spin_lock(&dev->wait_all_lock); -+ spin_lock(&obj->lock); -+ -+ if (obj->u.mutex.owner == owner) { -+ put_mutex_ownerdead_state(obj); -+ try_wake_all_obj(dev, obj); -+ try_wake_any_mutex(obj); -+ } -+ -+ spin_unlock(&obj->lock); -+ spin_unlock(&dev->wait_all_lock); -+ } else { -+ spin_lock(&obj->lock); -+ -+ if (obj->u.mutex.owner == owner) { -+ put_mutex_ownerdead_state(obj); -+ try_wake_any_mutex(obj); -+ } -+ -+ spin_unlock(&obj->lock); -+ } -+ -+ put_obj(obj); -+ } -+ -+ rcu_read_unlock(); -+ -+ return 0; -+} -+ - static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) - { - int ret = 0; -@@ -583,6 +656,7 @@ static int setup_wait(struct winesync_device *dev, - q->owner = args->owner; - atomic_set(&q->signaled, -1); - q->all = all; -+ q->ownerdead = false; - q->count = count; - - for (i = 0; i < count; i++) { -@@ -695,7 +769,7 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) - struct winesync_wait_args __user *user_args = argp; - - /* even if we caught a signal, we need to communicate success */ -- ret = 0; -+ ret = q->ownerdead ? -EOWNERDEAD : 0; - - if (put_user(signaled, &user_args->index)) - ret = -EFAULT; -@@ -776,7 +850,7 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) - struct winesync_wait_args __user *user_args = argp; - - /* even if we caught a signal, we need to communicate success */ -- ret = 0; -+ ret = q->ownerdead ? -EOWNERDEAD : 0; - - if (put_user(signaled, &user_args->index)) - ret = -EFAULT; -@@ -801,6 +875,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_create_sem(dev, argp); - case WINESYNC_IOC_DELETE: - return winesync_delete(dev, argp); -+ case WINESYNC_IOC_KILL_OWNER: -+ return winesync_kill_owner(dev, argp); - case WINESYNC_IOC_PUT_MUTEX: - return winesync_put_mutex(dev, argp); - case WINESYNC_IOC_PUT_SEM: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index fde08cb8ab95..f57aa76d57f5 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -46,5 +46,6 @@ struct winesync_wait_args { - struct winesync_mutex_args) - #define WINESYNC_IOC_PUT_MUTEX _IOWR(WINESYNC_IOC_BASE, 6, \ - struct winesync_mutex_args) -+#define WINESYNC_IOC_KILL_OWNER _IOW (WINESYNC_IOC_BASE, 7, __u32) - - #endif --- -2.36.0 - -From 888bb6fa10b7eb593db18a38fe696fc396ee30de Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:47:55 -0600 -Subject: [PATCH 10/34] winesync: Introduce WINESYNC_IOC_READ_SEM. - ---- - drivers/misc/winesync.c | 29 +++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 2 ++ - 2 files changed, 31 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 891537063bb6..98bedda2f8eb 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -523,6 +523,33 @@ static int winesync_put_mutex(struct winesync_device *dev, void __user *argp) - return ret; - } - -+static int winesync_read_sem(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_sem_args __user *user_args = argp; -+ struct winesync_sem_args args; -+ struct winesync_obj *sem; -+ __u32 id; -+ -+ if (get_user(id, &user_args->sem)) -+ return -EFAULT; -+ -+ sem = get_obj_typed(dev, id, WINESYNC_TYPE_SEM); -+ if (!sem) -+ return -EINVAL; -+ -+ args.sem = id; -+ spin_lock(&sem->lock); -+ args.count = sem->u.sem.count; -+ args.max = sem->u.sem.max; -+ spin_unlock(&sem->lock); -+ -+ put_obj(sem); -+ -+ if (copy_to_user(user_args, &args, sizeof(args))) -+ return -EFAULT; -+ return 0; -+} -+ - /* - * Actually change the mutex state to mark its owner as dead. - */ -@@ -881,6 +908,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_put_mutex(dev, argp); - case WINESYNC_IOC_PUT_SEM: - return winesync_put_sem(dev, argp); -+ case WINESYNC_IOC_READ_SEM: -+ return winesync_read_sem(dev, argp); - case WINESYNC_IOC_WAIT_ALL: - return winesync_wait_all(dev, argp); - case WINESYNC_IOC_WAIT_ANY: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index f57aa76d57f5..311eb810647d 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -47,5 +47,7 @@ struct winesync_wait_args { - #define WINESYNC_IOC_PUT_MUTEX _IOWR(WINESYNC_IOC_BASE, 6, \ - struct winesync_mutex_args) - #define WINESYNC_IOC_KILL_OWNER _IOW (WINESYNC_IOC_BASE, 7, __u32) -+#define WINESYNC_IOC_READ_SEM _IOWR(WINESYNC_IOC_BASE, 8, \ -+ struct winesync_sem_args) - - #endif --- -2.36.0 - -From 4f17c2ab7b9aca22fb00f7f16e0bd3cf70c44fe1 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:48:10 -0600 -Subject: [PATCH 11/34] winesync: Introduce WINESYNC_IOC_READ_MUTEX. - ---- - drivers/misc/winesync.c | 31 +++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 2 ++ - 2 files changed, 33 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 98bedda2f8eb..eae272663abe 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -550,6 +550,35 @@ static int winesync_read_sem(struct winesync_device *dev, void __user *argp) - return 0; - } - -+static int winesync_read_mutex(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_mutex_args __user *user_args = argp; -+ struct winesync_mutex_args args; -+ struct winesync_obj *mutex; -+ __u32 id; -+ int ret; -+ -+ if (get_user(id, &user_args->mutex)) -+ return -EFAULT; -+ -+ mutex = get_obj_typed(dev, id, WINESYNC_TYPE_MUTEX); -+ if (!mutex) -+ return -EINVAL; -+ -+ args.mutex = id; -+ spin_lock(&mutex->lock); -+ args.count = mutex->u.mutex.count; -+ args.owner = mutex->u.mutex.owner; -+ ret = mutex->u.mutex.ownerdead ? -EOWNERDEAD : 0; -+ spin_unlock(&mutex->lock); -+ -+ put_obj(mutex); -+ -+ if (copy_to_user(user_args, &args, sizeof(args))) -+ return -EFAULT; -+ return ret; -+} -+ - /* - * Actually change the mutex state to mark its owner as dead. - */ -@@ -908,6 +937,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_put_mutex(dev, argp); - case WINESYNC_IOC_PUT_SEM: - return winesync_put_sem(dev, argp); -+ case WINESYNC_IOC_READ_MUTEX: -+ return winesync_read_mutex(dev, argp); - case WINESYNC_IOC_READ_SEM: - return winesync_read_sem(dev, argp); - case WINESYNC_IOC_WAIT_ALL: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 311eb810647d..3371a303a927 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -49,5 +49,7 @@ struct winesync_wait_args { - #define WINESYNC_IOC_KILL_OWNER _IOW (WINESYNC_IOC_BASE, 7, __u32) - #define WINESYNC_IOC_READ_SEM _IOWR(WINESYNC_IOC_BASE, 8, \ - struct winesync_sem_args) -+#define WINESYNC_IOC_READ_MUTEX _IOWR(WINESYNC_IOC_BASE, 9, \ -+ struct winesync_mutex_args) - - #endif --- -2.36.0 - -From e897f7ec5164d6d5d3d9881756be9a538c533487 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 11:50:49 -0600 -Subject: [PATCH 12/34] docs: winesync: Add documentation for the winesync - uAPI. - ---- - Documentation/userspace-api/index.rst | 1 + - Documentation/userspace-api/winesync.rst | 324 +++++++++++++++++++++++ - 2 files changed, 325 insertions(+) - create mode 100644 Documentation/userspace-api/winesync.rst - -diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst -index a61eac0c73f8..0bf697ddcb09 100644 ---- a/Documentation/userspace-api/index.rst -+++ b/Documentation/userspace-api/index.rst -@@ -29,6 +29,7 @@ place where this information is gathered. - sysfs-platform_profile - vduse - futex2 -+ winesync - - .. only:: subproject and html - -diff --git a/Documentation/userspace-api/winesync.rst b/Documentation/userspace-api/winesync.rst -new file mode 100644 -index 000000000000..34e54be229cf ---- /dev/null -+++ b/Documentation/userspace-api/winesync.rst -@@ -0,0 +1,324 @@ -+===================================== -+Wine synchronization primitive driver -+===================================== -+ -+This page documents the user-space API for the winesync driver. -+ -+winesync is a support driver for emulation of NT synchronization -+primitives by the Wine project or other NT emulators. It exists -+because implementation in user-space, using existing tools, cannot -+simultaneously satisfy performance, correctness, and security -+constraints. It is implemented entirely in software, and does not -+drive any hardware device. -+ -+This interface is meant as a compatibility tool only, and should not -+be used for general synchronization. Instead use generic, versatile -+interfaces such as futex(2) and poll(2). -+ -+Synchronization primitives -+========================== -+ -+The winesync driver exposes two types of synchronization primitives, -+semaphores and mutexes. -+ -+A semaphore holds a single volatile 32-bit counter, and a static -+32-bit integer denoting the maximum value. It is considered signaled -+when the counter is nonzero. The counter is decremented by one when a -+wait is satisfied. Both the initial and maximum count are established -+when the semaphore is created. -+ -+A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit -+identifier denoting its owner. A mutex is considered signaled when its -+owner is zero (indicating that it is not owned). The recursion count -+is incremented when a wait is satisfied, and ownership is set to the -+given identifier. -+ -+A mutex also holds an internal flag denoting whether its previous -+owner has died; such a mutex is said to be inconsistent. Owner death -+is not tracked automatically based on thread death, but rather must be -+communicated using ``WINESYNC_IOC_KILL_OWNER``. An inconsistent mutex -+is inherently considered unowned. -+ -+Except for the "unowned" semantics of zero, the actual value of the -+owner identifier is not interpreted by the winesync driver at all. The -+intended use is to store a thread identifier; however, the winesync -+driver does not actually validate that a calling thread provides -+consistent or unique identifiers. -+ -+Unless specified otherwise, all operations on an object are atomic and -+totally ordered with respect to other operations on the same object. -+ -+Objects are represented by unsigned 32-bit integers. -+ -+Char device -+=========== -+ -+The winesync driver creates a single char device /dev/winesync. Each -+file description opened on the device represents a unique namespace. -+That is, objects created on one open file description are shared -+across all its individual descriptors, but are not shared with other -+open() calls on the same device. The same file description may be -+shared across multiple processes. -+ -+ioctl reference -+=============== -+ -+All operations on the device are done through ioctls. There are three -+structures used in ioctl calls:: -+ -+ struct winesync_sem_args { -+ __u32 sem; -+ __u32 count; -+ __u32 max; -+ }; -+ -+ struct winesync_mutex_args { -+ __u32 mutex; -+ __u32 owner; -+ __u32 count; -+ }; -+ -+ struct winesync_wait_args { -+ __u64 timeout; -+ __u64 objs; -+ __u32 count; -+ __u32 owner; -+ __u32 index; -+ __u32 pad; -+ }; -+ -+Depending on the ioctl, members of the structure may be used as input, -+output, or not at all. All ioctls return 0 on success. -+ -+The ioctls are as follows: -+ -+.. c:macro:: WINESYNC_IOC_CREATE_SEM -+ -+ Create a semaphore object. Takes a pointer to struct -+ :c:type:`winesync_sem_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``sem`` -+ - On output, contains the identifier of the created semaphore. -+ * - ``count`` -+ - Initial count of the semaphore. -+ * - ``max`` -+ - Maximum count of the semaphore. -+ -+ Fails with ``EINVAL`` if ``count`` is greater than ``max``. -+ -+.. c:macro:: WINESYNC_IOC_CREATE_MUTEX -+ -+ Create a mutex object. Takes a pointer to struct -+ :c:type:`winesync_mutex_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``mutex`` -+ - On output, contains the identifier of the created mutex. -+ * - ``count`` -+ - Initial recursion count of the mutex. -+ * - ``owner`` -+ - Initial owner of the mutex. -+ -+ If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is -+ zero and ``count`` is nonzero, the function fails with ``EINVAL``. -+ -+.. c:macro:: WINESYNC_IOC_DELETE -+ -+ Delete an object of any type. Takes an input-only pointer to a -+ 32-bit integer denoting the object to delete. -+ -+ Wait ioctls currently in progress are not interrupted, and behave as -+ if the object remains valid. -+ -+.. c:macro:: WINESYNC_IOC_PUT_SEM -+ -+ Post to a semaphore object. Takes a pointer to struct -+ :c:type:`winesync_sem_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``sem`` -+ - Semaphore object to post to. -+ * - ``count`` -+ - Count to add to the semaphore. On output, contains the -+ previous count of the semaphore. -+ * - ``max`` -+ - Not used. -+ -+ If adding ``count`` to the semaphore's current count would raise the -+ latter past the semaphore's maximum count, the ioctl fails with -+ ``EOVERFLOW`` and the semaphore is not affected. If raising the -+ semaphore's count causes it to become signaled, eligible threads -+ waiting on this semaphore will be woken and the semaphore's count -+ decremented appropriately. -+ -+.. c:macro:: WINESYNC_IOC_PUT_MUTEX -+ -+ Release a mutex object. Takes a pointer to struct -+ :c:type:`winesync_mutex_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``mutex`` -+ - Mutex object to release. -+ * - ``owner`` -+ - Mutex owner identifier. -+ * - ``count`` -+ - On output, contains the previous recursion count. -+ -+ If ``owner`` is zero, the ioctl fails with ``EINVAL``. If ``owner`` -+ is not the current owner of the mutex, the ioctl fails with -+ ``EPERM``. -+ -+ The mutex's count will be decremented by one. If decrementing the -+ mutex's count causes it to become zero, the mutex is marked as -+ unowned and signaled, and eligible threads waiting on it will be -+ woken as appropriate. -+ -+.. c:macro:: WINESYNC_IOC_READ_SEM -+ -+ Read the current state of a semaphore object. Takes a pointer to -+ struct :c:type:`winesync_sem_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``sem`` -+ - Semaphore object to read. -+ * - ``count`` -+ - On output, contains the current count of the semaphore. -+ * - ``max`` -+ - On output, contains the maximum count of the semaphore. -+ -+.. c:macro:: WINESYNC_IOC_READ_MUTEX -+ -+ Read the current state of a mutex object. Takes a pointer to struct -+ :c:type:`winesync_mutex_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``mutex`` -+ - Mutex object to read. -+ * - ``owner`` -+ - On output, contains the current owner of the mutex, or zero -+ if the mutex is not currently owned. -+ * - ``count`` -+ - On output, contains the current recursion count of the mutex. -+ -+ If the mutex is marked as inconsistent, the function fails with -+ ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to -+ zero. -+ -+.. c:macro:: WINESYNC_IOC_KILL_OWNER -+ -+ Mark any mutexes owned by the given owner as unowned and -+ inconsistent. Takes an input-only pointer to a 32-bit integer -+ denoting the owner. If the owner is zero, the ioctl fails with -+ ``EINVAL``. -+ -+ For each mutex currently owned by the given owner, eligible threads -+ waiting on said mutex will be woken as appropriate (and such waits -+ will fail with ``EOWNERDEAD``, as described below). -+ -+ The operation as a whole is not atomic; however, the modification of -+ each mutex is atomic and totally ordered with respect to other -+ operations on the same mutex. -+ -+.. c:macro:: WINESYNC_IOC_WAIT_ANY -+ -+ Poll on any of a list of objects, atomically acquiring at most one. -+ Takes a pointer to struct :c:type:`winesync_wait_args`, which is -+ used as follows: -+ -+ .. list-table:: -+ -+ * - ``timeout`` -+ - Optional pointer to a 64-bit struct :c:type:`timespec` -+ (specified as an integer so that the structure has the same -+ size regardless of architecture). The timeout is specified in -+ absolute format, as measured against the MONOTONIC clock. If -+ the timeout is equal to or earlier than the current time, the -+ function returns immediately without sleeping. If ``timeout`` -+ is zero, i.e. NULL, the function will sleep until an object -+ is signaled, and will not fail with ``ETIMEDOUT``. -+ * - ``objs`` -+ - Pointer to an array of ``count`` 32-bit object identifiers -+ (specified as an integer so that the structure has the same -+ size regardless of architecture). If any identifier is -+ invalid, the function fails with ``EINVAL``. -+ * - ``count`` -+ - Number of object identifiers specified in the ``objs`` array. -+ * - ``owner`` -+ - Mutex owner identifier. If any object in ``objs`` is a mutex, -+ the ioctl will attempt to acquire that mutex on behalf of -+ ``owner``. If ``owner`` is zero, the ioctl fails with -+ ``EINVAL``. -+ * - ``index`` -+ - On success, contains the index (into ``objs``) of the object -+ which was signaled. -+ * - ``pad`` -+ - This field is not used and must be set to zero. -+ -+ This function attempts to acquire one of the given objects. If -+ unable to do so, it sleeps until an object becomes signaled, -+ subsequently acquiring it, or the timeout expires. In the latter -+ case the ioctl fails with ``ETIMEDOUT``. The function only acquires -+ one object, even if multiple objects are signaled. -+ -+ A semaphore is considered to be signaled if its count is nonzero, -+ and is acquired by decrementing its count by one. A mutex is -+ considered to be signaled if it is unowned or if its owner matches -+ the ``owner`` argument, and is acquired by incrementing its -+ recursion count by one and setting its owner to the ``owner`` -+ argument. -+ -+ Acquisition is atomic and totally ordered with respect to other -+ operations on the same object. If two wait operations (with -+ different ``owner`` identifiers) are queued on the same mutex, only -+ one is signaled. If two wait operations are queued on the same -+ semaphore, and a value of one is posted to it, only one is signaled. -+ The order in which threads are signaled is not specified. -+ -+ If an inconsistent mutex is acquired, the ioctl fails with -+ ``EOWNERDEAD``. Although this is a failure return, the function may -+ otherwise be considered successful. The mutex is marked as owned by -+ the given owner (with a recursion count of 1) and as no longer -+ inconsistent, and ``index`` is still set to the index of the mutex. -+ -+ It is valid to pass the same object more than once. If a wakeup -+ occurs due to that object being signaled, ``index`` is set to the -+ lowest index corresponding to that object. -+ -+ The function may fail with ``EINTR`` if a signal is received. -+ -+.. c:macro:: WINESYNC_IOC_WAIT_ALL -+ -+ Poll on a list of objects, atomically acquiring all of them. Takes a -+ pointer to struct :c:type:`winesync_wait_args`, which is used -+ identically to ``WINESYNC_IOC_WAIT_ANY``, except that ``index`` is -+ always filled with zero on success. -+ -+ This function attempts to simultaneously acquire all of the given -+ objects. If unable to do so, it sleeps until all objects become -+ simultaneously signaled, subsequently acquiring them, or the timeout -+ expires. In the latter case the ioctl fails with ``ETIMEDOUT`` and -+ no objects are modified. -+ -+ Objects may become signaled and subsequently designaled (through -+ acquisition by other threads) while this thread is sleeping. Only -+ once all objects are simultaneously signaled does the ioctl acquire -+ them and return. The entire acquisition is atomic and totally -+ ordered with respect to other operations on any of the given -+ objects. -+ -+ If an inconsistent mutex is acquired, the ioctl fails with -+ ``EOWNERDEAD``. Similarly to ``WINESYNC_IOC_WAIT_ANY``, all objects -+ are nevertheless marked as acquired. Note that if multiple mutex -+ objects are specified, there is no way to know which were marked as -+ inconsistent. -+ -+ Unlike ``WINESYNC_IOC_WAIT_ANY``, it is not valid to pass the same -+ object more than once. If this is attempted, the function fails with -+ ``EINVAL``. --- -2.36.0 - -From 622699b7dd8d5390dccdd9be1159e93dee6815ac Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 12:06:23 -0600 -Subject: [PATCH 13/34] selftests: winesync: Add some tests for semaphore - state. - ---- - tools/testing/selftests/Makefile | 1 + - .../selftests/drivers/winesync/Makefile | 8 + - .../testing/selftests/drivers/winesync/config | 1 + - .../selftests/drivers/winesync/winesync.c | 153 ++++++++++++++++++ - 4 files changed, 163 insertions(+) - create mode 100644 tools/testing/selftests/drivers/winesync/Makefile - create mode 100644 tools/testing/selftests/drivers/winesync/config - create mode 100644 tools/testing/selftests/drivers/winesync/winesync.c - -diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile -index c852eb40c4f7..a366016d6254 100644 ---- a/tools/testing/selftests/Makefile -+++ b/tools/testing/selftests/Makefile -@@ -18,6 +18,7 @@ - TARGETS += drivers/s390x/uvdevice - TARGETS += drivers/net/bonding - TARGETS += drivers/net/team -+TARGETS += drivers/winesync - TARGETS += efivarfs - TARGETS += exec - TARGETS += fchmodat2 -new file mode 100644 -index 000000000000..43b39fdeea10 ---- /dev/null -+++ b/tools/testing/selftests/drivers/winesync/Makefile -@@ -0,0 +1,8 @@ -+# SPDX-LICENSE-IDENTIFIER: GPL-2.0-only -+TEST_GEN_PROGS := winesync -+ -+top_srcdir =../../../../.. -+CFLAGS += -I$(top_srcdir)/usr/include -+LDLIBS += -lpthread -+ -+include ../../lib.mk -diff --git a/tools/testing/selftests/drivers/winesync/config b/tools/testing/selftests/drivers/winesync/config -new file mode 100644 -index 000000000000..60539c826d06 ---- /dev/null -+++ b/tools/testing/selftests/drivers/winesync/config -@@ -0,0 +1 @@ -+CONFIG_WINESYNC=y -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -new file mode 100644 -index 000000000000..58ade297fef9 ---- /dev/null -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -0,0 +1,153 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Various unit tests for the "winesync" synchronization primitive driver. -+ * -+ * Copyright (C) 2021 Zebediah Figura -+ */ -+ -+#define _GNU_SOURCE -+#include -+#include -+#include -+#include -+#include -+#include -+#include "../../kselftest_harness.h" -+ -+static int read_sem_state(int fd, __u32 sem, __u32 *count, __u32 *max) -+{ -+ struct winesync_sem_args args; -+ int ret; -+ -+ args.sem = sem; -+ args.count = 0xdeadbeef; -+ args.max = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &args); -+ *count = args.count; -+ *max = args.max; -+ return ret; -+} -+ -+#define check_sem_state(fd, sem, count, max) \ -+ ({ \ -+ __u32 __count, __max; \ -+ int ret = read_sem_state((fd), (sem), &__count, &__max); \ -+ EXPECT_EQ(0, ret); \ -+ EXPECT_EQ((count), __count); \ -+ EXPECT_EQ((max), __max); \ -+ }) -+ -+static int put_sem(int fd, __u32 sem, __u32 *count) -+{ -+ struct winesync_sem_args args; -+ int ret; -+ -+ args.sem = sem; -+ args.count = *count; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &args); -+ *count = args.count; -+ return ret; -+} -+ -+static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, -+ __u32 *index) -+{ -+ struct winesync_wait_args args = {0}; -+ struct timespec timeout; -+ int ret; -+ -+ clock_gettime(CLOCK_MONOTONIC, &timeout); -+ -+ args.timeout = (uintptr_t)&timeout; -+ args.count = count; -+ args.objs = (uintptr_t)objs; -+ args.owner = owner; -+ args.index = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &args); -+ *index = args.index; -+ return ret; -+} -+ -+TEST(semaphore_state) -+{ -+ struct winesync_sem_args sem_args; -+ struct timespec timeout; -+ __u32 sem, count, index; -+ int fd, ret; -+ -+ clock_gettime(CLOCK_MONOTONIC, &timeout); -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ sem_args.count = 3; -+ sem_args.max = 2; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ sem_args.count = 2; -+ sem_args.max = 2; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ check_sem_state(fd, sem, 2, 2); -+ -+ count = 0; -+ ret = put_sem(fd, sem, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, count); -+ check_sem_state(fd, sem, 2, 2); -+ -+ count = 1; -+ ret = put_sem(fd, sem, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOVERFLOW, errno); -+ check_sem_state(fd, sem, 2, 2); -+ -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem, 1, 2); -+ -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem, 0, 2); -+ -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ count = 3; -+ ret = put_sem(fd, sem, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOVERFLOW, errno); -+ check_sem_state(fd, sem, 0, 2); -+ -+ count = 2; -+ ret = put_sem(fd, sem, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); -+ check_sem_state(fd, sem, 2, 2); -+ -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ ret = wait_any(fd, 1, &sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ -+ count = 1; -+ ret = put_sem(fd, sem, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, count); -+ check_sem_state(fd, sem, 1, 2); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ -+TEST_HARNESS_MAIN --- -2.36.0 - -From c62acefda29b36849abde8134bf2a3fe8d893520 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 12:07:04 -0600 -Subject: [PATCH 14/34] selftests: winesync: Add some tests for mutex state. - ---- - .../selftests/drivers/winesync/winesync.c | 188 ++++++++++++++++++ - 1 file changed, 188 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 58ade297fef9..801b776da5aa 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -49,6 +49,42 @@ static int put_sem(int fd, __u32 sem, __u32 *count) - return ret; - } - -+static int read_mutex_state(int fd, __u32 mutex, __u32 *count, __u32 *owner) -+{ -+ struct winesync_mutex_args args; -+ int ret; -+ -+ args.mutex = mutex; -+ args.count = 0xdeadbeef; -+ args.owner = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &args); -+ *count = args.count; -+ *owner = args.owner; -+ return ret; -+} -+ -+#define check_mutex_state(fd, mutex, count, owner) \ -+ ({ \ -+ __u32 __count, __owner; \ -+ int ret = read_mutex_state((fd), (mutex), &__count, &__owner); \ -+ EXPECT_EQ(0, ret); \ -+ EXPECT_EQ((count), __count); \ -+ EXPECT_EQ((owner), __owner); \ -+ }) -+ -+static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) -+{ -+ struct winesync_mutex_args args; -+ int ret; -+ -+ args.mutex = mutex; -+ args.owner = owner; -+ args.count = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &args); -+ *count = args.count; -+ return ret; -+} -+ - static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, - __u32 *index) - { -@@ -150,4 +186,156 @@ TEST(semaphore_state) - close(fd); - } - -+TEST(mutex_state) -+{ -+ struct winesync_mutex_args mutex_args; -+ __u32 mutex, owner, count, index; -+ struct timespec timeout; -+ int fd, ret; -+ -+ clock_gettime(CLOCK_MONOTONIC, &timeout); -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ mutex_args.owner = 123; -+ mutex_args.count = 0; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ mutex_args.owner = 0; -+ mutex_args.count = 2; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ mutex_args.owner = 123; -+ mutex_args.count = 2; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ mutex = mutex_args.mutex; -+ check_mutex_state(fd, mutex, 2, 123); -+ -+ ret = put_mutex(fd, mutex, 0, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = put_mutex(fd, mutex, 456, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); -+ check_mutex_state(fd, mutex, 2, 123); -+ -+ ret = put_mutex(fd, mutex, 123, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, count); -+ check_mutex_state(fd, mutex, 1, 123); -+ -+ ret = put_mutex(fd, mutex, 123, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, count); -+ check_mutex_state(fd, mutex, 0, 0); -+ -+ ret = put_mutex(fd, mutex, 123, &count); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EPERM, errno); -+ -+ ret = wait_any(fd, 1, &mutex, 456, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_mutex_state(fd, mutex, 1, 456); -+ -+ ret = wait_any(fd, 1, &mutex, 456, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_mutex_state(fd, mutex, 2, 456); -+ -+ ret = put_mutex(fd, mutex, 456, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, count); -+ check_mutex_state(fd, mutex, 1, 456); -+ -+ ret = wait_any(fd, 1, &mutex, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ owner = 0; -+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ owner = 123; -+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); -+ EXPECT_EQ(0, ret); -+ check_mutex_state(fd, mutex, 1, 456); -+ -+ owner = 456; -+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); -+ EXPECT_EQ(0, ret); -+ -+ mutex_args.count = 0xdeadbeef; -+ mutex_args.owner = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(0, mutex_args.count); -+ EXPECT_EQ(0, mutex_args.owner); -+ -+ mutex_args.count = 0xdeadbeef; -+ mutex_args.owner = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(0, mutex_args.count); -+ EXPECT_EQ(0, mutex_args.owner); -+ -+ ret = wait_any(fd, 1, &mutex, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(0, index); -+ check_mutex_state(fd, mutex, 1, 123); -+ -+ owner = 123; -+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); -+ EXPECT_EQ(0, ret); -+ -+ mutex_args.count = 0xdeadbeef; -+ mutex_args.owner = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(0, mutex_args.count); -+ EXPECT_EQ(0, mutex_args.owner); -+ -+ ret = wait_any(fd, 1, &mutex, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(0, index); -+ check_mutex_state(fd, mutex, 1, 123); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex); -+ EXPECT_EQ(0, ret); -+ -+ mutex_args.owner = 0; -+ mutex_args.count = 0; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ mutex = mutex_args.mutex; -+ check_mutex_state(fd, mutex, 0, 0); -+ -+ ret = wait_any(fd, 1, &mutex, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_mutex_state(fd, mutex, 1, 123); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ - TEST_HARNESS_MAIN --- -2.36.0 - -From 540cefcfe255d0b4c7208ae57a43fe0f16ce2531 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 12:07:45 -0600 -Subject: [PATCH 15/34] selftests: winesync: Add some tests for - WINESYNC_IOC_WAIT_ANY. - ---- - .../selftests/drivers/winesync/winesync.c | 107 ++++++++++++++++++ - 1 file changed, 107 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 801b776da5aa..5903061d38b6 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -338,4 +338,111 @@ TEST(mutex_state) - close(fd); - } - -+TEST(test_wait_any) -+{ -+ struct winesync_mutex_args mutex_args = {0}; -+ struct winesync_wait_args wait_args = {0}; -+ struct winesync_sem_args sem_args = {0}; -+ __u32 objs[2], owner, index; -+ struct timespec timeout; -+ int fd, ret; -+ -+ clock_gettime(CLOCK_MONOTONIC, &timeout); -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ sem_args.count = 2; -+ sem_args.max = 3; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ -+ mutex_args.owner = 0; -+ mutex_args.count = 0; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ -+ objs[0] = sem_args.sem; -+ objs[1] = mutex_args.mutex; -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem_args.sem, 1, 3); -+ check_mutex_state(fd, mutex_args.mutex, 0, 0); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ check_mutex_state(fd, mutex_args.mutex, 0, 0); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, index); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ check_mutex_state(fd, mutex_args.mutex, 1, 123); -+ -+ sem_args.count = 1; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, sem_args.count); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ check_mutex_state(fd, mutex_args.mutex, 1, 123); -+ -+ ret = wait_any(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, index); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ check_mutex_state(fd, mutex_args.mutex, 2, 123); -+ -+ ret = wait_any(fd, 2, objs, 456, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ owner = 123; -+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_any(fd, 2, objs, 456, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ EXPECT_EQ(1, index); -+ -+ ret = wait_any(fd, 2, objs, 456, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, index); -+ -+ /* test waiting on the same object twice */ -+ sem_args.count = 2; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, sem_args.count); -+ -+ objs[0] = objs[1] = sem_args.sem; -+ ret = wait_any(fd, 2, objs, 456, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, wait_args.index); -+ check_sem_state(fd, sem_args.sem, 1, 3); -+ -+ ret = wait_any(fd, 0, NULL, 456, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); -+ EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ - TEST_HARNESS_MAIN --- -2.36.0 - -From 17f55215ea56e925369e2eec7eaead604a273e34 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 12:08:25 -0600 -Subject: [PATCH 16/34] selftests: winesync: Add some tests for - WINESYNC_IOC_WAIT_ALL. - ---- - .../selftests/drivers/winesync/winesync.c | 104 +++++++++++++++++- - 1 file changed, 101 insertions(+), 3 deletions(-) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 5903061d38b6..0718219f54bf 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -85,8 +85,8 @@ static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) - return ret; - } - --static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, -- __u32 *index) -+static int wait_objs(int fd, unsigned long request, __u32 count, -+ const __u32 *objs, __u32 owner, __u32 *index) - { - struct winesync_wait_args args = {0}; - struct timespec timeout; -@@ -99,11 +99,23 @@ static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, - args.objs = (uintptr_t)objs; - args.owner = owner; - args.index = 0xdeadbeef; -- ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &args); -+ ret = ioctl(fd, request, &args); - *index = args.index; - return ret; - } - -+static int wait_any(int fd, __u32 count, const __u32 *objs, -+ __u32 owner, __u32 *index) -+{ -+ return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, count, objs, owner, index); -+} -+ -+static int wait_all(int fd, __u32 count, const __u32 *objs, -+ __u32 owner, __u32 *index) -+{ -+ return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, count, objs, owner, index); -+} -+ - TEST(semaphore_state) - { - struct winesync_sem_args sem_args; -@@ -445,4 +457,90 @@ TEST(test_wait_any) - close(fd); - } - -+TEST(test_wait_all) -+{ -+ struct winesync_mutex_args mutex_args = {0}; -+ struct winesync_sem_args sem_args = {0}; -+ __u32 objs[2], owner, index; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ sem_args.count = 2; -+ sem_args.max = 3; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ -+ mutex_args.owner = 0; -+ mutex_args.count = 0; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ -+ objs[0] = sem_args.sem; -+ objs[1] = mutex_args.mutex; -+ -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem_args.sem, 1, 3); -+ check_mutex_state(fd, mutex_args.mutex, 1, 123); -+ -+ ret = wait_all(fd, 2, objs, 456, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ check_sem_state(fd, sem_args.sem, 1, 3); -+ check_mutex_state(fd, mutex_args.mutex, 1, 123); -+ -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ check_mutex_state(fd, mutex_args.mutex, 2, 123); -+ -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ check_mutex_state(fd, mutex_args.mutex, 2, 123); -+ -+ sem_args.count = 3; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, sem_args.count); -+ -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem_args.sem, 2, 3); -+ check_mutex_state(fd, mutex_args.mutex, 3, 123); -+ -+ owner = 123; -+ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EOWNERDEAD, errno); -+ check_sem_state(fd, sem_args.sem, 1, 3); -+ check_mutex_state(fd, mutex_args.mutex, 1, 123); -+ -+ /* test waiting on the same object twice */ -+ objs[0] = objs[1] = sem_args.sem; -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); -+ EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ - TEST_HARNESS_MAIN --- -2.36.0 - -From 6d07a2265d06d3f0af6fe2d9874762fb2e922488 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 12:08:54 -0600 -Subject: [PATCH 17/34] selftests: winesync: Add some tests for invalid object - handling. - ---- - .../selftests/drivers/winesync/winesync.c | 93 +++++++++++++++++++ - 1 file changed, 93 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 0718219f54bf..8a9fb496f5e0 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -543,4 +543,97 @@ TEST(test_wait_all) - close(fd); - } - -+TEST(invalid_objects) -+{ -+ struct winesync_mutex_args mutex_args = {0}; -+ struct winesync_wait_args wait_args = {0}; -+ struct winesync_sem_args sem_args = {0}; -+ __u32 objs[2] = {0}; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ wait_args.objs = (uintptr_t)objs; -+ wait_args.count = 1; -+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[0]); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ sem_args.max = 1; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ -+ mutex_args.mutex = sem_args.sem; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ objs[0] = sem_args.sem; -+ objs[1] = sem_args.sem + 1; -+ wait_args.count = 2; -+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ objs[0] = sem_args.sem + 1; -+ objs[1] = sem_args.sem; -+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); -+ EXPECT_EQ(0, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ -+ sem_args.sem = mutex_args.mutex; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ - TEST_HARNESS_MAIN --- -2.36.0 - -From fafaf63d58b1f8ae3644ec5850c170bce6f6b5d2 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 12:09:32 -0600 -Subject: [PATCH 18/34] selftests: winesync: Add some tests for wakeup - signaling with WINESYNC_IOC_WAIT_ANY. - ---- - .../selftests/drivers/winesync/winesync.c | 154 ++++++++++++++++++ - 1 file changed, 154 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 8a9fb496f5e0..04855df00894 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -636,4 +636,158 @@ TEST(invalid_objects) - close(fd); - } - -+struct wake_args -+{ -+ int fd; -+ __u32 obj; -+}; -+ -+struct wait_args -+{ -+ int fd; -+ unsigned long request; -+ struct winesync_wait_args *args; -+ int ret; -+ int err; -+}; -+ -+static void *wait_thread(void *arg) -+{ -+ struct wait_args *args = arg; -+ -+ args->ret = ioctl(args->fd, args->request, args->args); -+ args->err = errno; -+ return NULL; -+} -+ -+static void get_abs_timeout(struct timespec *timeout, clockid_t clock, -+ unsigned int ms) -+{ -+ clock_gettime(clock, timeout); -+ timeout->tv_nsec += ms * 1000000; -+ timeout->tv_sec += (timeout->tv_nsec / 1000000000); -+ timeout->tv_nsec %= 1000000000; -+} -+ -+static int wait_for_thread(pthread_t thread, unsigned int ms) -+{ -+ struct timespec timeout; -+ get_abs_timeout(&timeout, CLOCK_REALTIME, ms); -+ return pthread_timedjoin_np(thread, NULL, &timeout); -+} -+ -+TEST(wake_any) -+{ -+ struct winesync_mutex_args mutex_args = {0}; -+ struct winesync_wait_args wait_args = {0}; -+ struct winesync_sem_args sem_args = {0}; -+ struct wait_args thread_args; -+ __u32 objs[2], count, index; -+ struct timespec timeout; -+ pthread_t thread; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ sem_args.count = 0; -+ sem_args.max = 3; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ -+ mutex_args.owner = 123; -+ mutex_args.count = 1; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ -+ objs[0] = sem_args.sem; -+ objs[1] = mutex_args.mutex; -+ -+ /* test waking the semaphore */ -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ wait_args.timeout = (uintptr_t)&timeout; -+ wait_args.objs = (uintptr_t)objs; -+ wait_args.count = 2; -+ wait_args.owner = 456; -+ wait_args.index = 0xdeadbeef; -+ thread_args.fd = fd; -+ thread_args.args = &wait_args; -+ thread_args.request = WINESYNC_IOC_WAIT_ANY; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ sem_args.count = 1; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, sem_args.count); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(0, wait_args.index); -+ -+ /* test waking the mutex */ -+ -+ /* first grab it again for owner 123 */ -+ ret = wait_any(fd, 1, &mutex_args.mutex, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ wait_args.owner = 456; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = put_mutex(fd, mutex_args.mutex, 123, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, count); -+ -+ ret = pthread_tryjoin_np(thread, NULL); -+ EXPECT_EQ(EBUSY, ret); -+ -+ ret = put_mutex(fd, mutex_args.mutex, 123, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, mutex_args.count); -+ check_mutex_state(fd, mutex_args.mutex, 1, 456); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); -+ -+ /* delete an object while it's being waited on */ -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); -+ wait_args.owner = 123; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); -+ EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 200); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(-1, thread_args.ret); -+ EXPECT_EQ(ETIMEDOUT, thread_args.err); -+ -+ close(fd); -+} -+ - TEST_HARNESS_MAIN --- -2.36.0 - -From c1916abd720dc30c3dc1972fd9a4d69844e8ffbd Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Fri, 5 Mar 2021 12:09:36 -0600 -Subject: [PATCH 19/34] selftests: winesync: Add some tests for wakeup - signaling with WINESYNC_IOC_WAIT_ALL. - ---- - .../selftests/drivers/winesync/winesync.c | 102 ++++++++++++++++++ - 1 file changed, 102 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 04855df00894..ad6d0f9a2a35 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -790,4 +790,106 @@ TEST(wake_any) - close(fd); - } - -+TEST(wake_all) -+{ -+ struct winesync_mutex_args mutex_args = {0}; -+ struct winesync_wait_args wait_args = {0}; -+ struct winesync_sem_args sem_args = {0}; -+ struct wait_args thread_args; -+ __u32 objs[2], count, index; -+ struct timespec timeout; -+ pthread_t thread; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ sem_args.count = 0; -+ sem_args.max = 3; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ -+ mutex_args.owner = 123; -+ mutex_args.count = 1; -+ mutex_args.mutex = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, mutex_args.mutex); -+ -+ objs[0] = sem_args.sem; -+ objs[1] = mutex_args.mutex; -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ wait_args.timeout = (uintptr_t)&timeout; -+ wait_args.objs = (uintptr_t)objs; -+ wait_args.count = 2; -+ wait_args.owner = 456; -+ thread_args.fd = fd; -+ thread_args.args = &wait_args; -+ thread_args.request = WINESYNC_IOC_WAIT_ALL; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ sem_args.count = 1; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, sem_args.count); -+ -+ ret = pthread_tryjoin_np(thread, NULL); -+ EXPECT_EQ(EBUSY, ret); -+ -+ check_sem_state(fd, sem_args.sem, 1, 3); -+ -+ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ -+ ret = put_mutex(fd, mutex_args.mutex, 123, &count); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, count); -+ -+ ret = pthread_tryjoin_np(thread, NULL); -+ EXPECT_EQ(EBUSY, ret); -+ -+ check_mutex_state(fd, mutex_args.mutex, 0, 0); -+ -+ sem_args.count = 2; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, sem_args.count); -+ check_sem_state(fd, sem_args.sem, 1, 3); -+ check_mutex_state(fd, mutex_args.mutex, 1, 456); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ -+ /* delete an object while it's being waited on */ -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); -+ wait_args.owner = 123; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); -+ EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 200); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(-1, thread_args.ret); -+ EXPECT_EQ(ETIMEDOUT, thread_args.err); -+ -+ close(fd); -+} -+ - TEST_HARNESS_MAIN --- -2.36.0 - -From 4e6e34339182f13972e7b906c0bd0dde74eda3d7 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 18:21:03 -0600 -Subject: [PATCH 21/34] winesync: Introduce WINESYNC_IOC_CREATE_EVENT. - ---- - drivers/misc/winesync.c | 65 +++++++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 8 +++++ - 2 files changed, 73 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index eae272663abe..eaba41510784 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -17,6 +17,7 @@ - enum winesync_type { - WINESYNC_TYPE_SEM, - WINESYNC_TYPE_MUTEX, -+ WINESYNC_TYPE_EVENT, - }; - - struct winesync_obj { -@@ -66,6 +67,10 @@ struct winesync_obj { - __u32 owner; - bool ownerdead; - } mutex; -+ struct { -+ bool manual; -+ bool signaled; -+ } event; - } u; - }; - -@@ -199,6 +204,8 @@ static bool is_signaled(struct winesync_obj *obj, __u32 owner) - if (obj->u.mutex.owner && obj->u.mutex.owner != owner) - return false; - return obj->u.mutex.count < UINT_MAX; -+ case WINESYNC_TYPE_EVENT: -+ return obj->u.event.signaled; - } - - WARN(1, "bad object type %#x\n", obj->type); -@@ -248,6 +255,10 @@ static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, - obj->u.mutex.count++; - obj->u.mutex.owner = q->owner; - break; -+ case WINESYNC_TYPE_EVENT: -+ if (!obj->u.event.manual) -+ obj->u.event.signaled = false; -+ break; - } - } - wake_up_process(q->task); -@@ -315,6 +326,26 @@ static void try_wake_any_mutex(struct winesync_obj *mutex) - } - } - -+static void try_wake_any_event(struct winesync_obj *event) -+{ -+ struct winesync_q_entry *entry; -+ -+ lockdep_assert_held(&event->lock); -+ -+ list_for_each_entry(entry, &event->any_waiters, node) { -+ struct winesync_q *q = entry->q; -+ -+ if (!event->u.event.signaled) -+ break; -+ -+ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { -+ if (!event->u.event.manual) -+ event->u.event.signaled = false; -+ wake_up_process(q->task); -+ } -+ } -+} -+ - static int winesync_create_sem(struct winesync_device *dev, void __user *argp) - { - struct winesync_sem_args __user *user_args = argp; -@@ -379,6 +410,35 @@ static int winesync_create_mutex(struct winesync_device *dev, void __user *argp) - return put_user(id, &user_args->mutex); - } - -+static int winesync_create_event(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_event_args __user *user_args = argp; -+ struct winesync_event_args args; -+ struct winesync_obj *event; -+ __u32 id; -+ int ret; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ event = kzalloc(sizeof(*event), GFP_KERNEL); -+ if (!event) -+ return -ENOMEM; -+ -+ init_obj(event); -+ event->type = WINESYNC_TYPE_EVENT; -+ event->u.event.manual = args.manual; -+ event->u.event.signaled = args.signaled; -+ -+ ret = xa_alloc(&dev->objects, &id, event, xa_limit_32b, GFP_KERNEL); -+ if (ret < 0) { -+ kfree(event); -+ return ret; -+ } -+ -+ return put_user(id, &user_args->event); -+} -+ - static int winesync_delete(struct winesync_device *dev, void __user *argp) - { - struct winesync_obj *obj; -@@ -760,6 +820,9 @@ static void try_wake_any_obj(struct winesync_obj *obj) - case WINESYNC_TYPE_MUTEX: - try_wake_any_mutex(obj); - break; -+ case WINESYNC_TYPE_EVENT: -+ try_wake_any_event(obj); -+ break; - } - } - -@@ -925,6 +988,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - void __user *argp = (void __user *)parm; - - switch (cmd) { -+ case WINESYNC_IOC_CREATE_EVENT: -+ return winesync_create_event(dev, argp); - case WINESYNC_IOC_CREATE_MUTEX: - return winesync_create_mutex(dev, argp); - case WINESYNC_IOC_CREATE_SEM: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 3371a303a927..3999407534e0 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -22,6 +22,12 @@ struct winesync_mutex_args { - __u32 count; - }; - -+struct winesync_event_args { -+ __u32 event; -+ __u32 manual; -+ __u32 signaled; -+}; -+ - struct winesync_wait_args { - __u64 timeout; - __u64 objs; -@@ -51,5 +57,7 @@ struct winesync_wait_args { - struct winesync_sem_args) - #define WINESYNC_IOC_READ_MUTEX _IOWR(WINESYNC_IOC_BASE, 9, \ - struct winesync_mutex_args) -+#define WINESYNC_IOC_CREATE_EVENT _IOWR(WINESYNC_IOC_BASE, 10, \ -+ struct winesync_event_args) - - #endif --- -2.36.0 - -From 92a843a6d77099e638d5513fb4093e42ba84a3a3 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 18:43:30 -0600 -Subject: [PATCH 22/34] winesync: Introduce WINESYNC_IOC_SET_EVENT. - ---- - drivers/misc/winesync.c | 45 +++++++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 2 ++ - 2 files changed, 47 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index eaba41510784..658ad7b80c29 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -704,6 +704,49 @@ static int winesync_kill_owner(struct winesync_device *dev, void __user *argp) - return 0; - } - -+static int winesync_set_event(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_event_args __user *user_args = argp; -+ struct winesync_event_args args; -+ struct winesync_obj *event; -+ bool prev_state; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ event = get_obj_typed(dev, args.event, WINESYNC_TYPE_EVENT); -+ if (!event) -+ return -EINVAL; -+ -+ if (atomic_read(&event->all_hint) > 0) { -+ spin_lock(&dev->wait_all_lock); -+ spin_lock(&event->lock); -+ -+ prev_state = event->u.event.signaled; -+ event->u.event.signaled = true; -+ try_wake_all_obj(dev, event); -+ try_wake_any_event(event); -+ -+ spin_unlock(&event->lock); -+ spin_unlock(&dev->wait_all_lock); -+ } else { -+ spin_lock(&event->lock); -+ -+ prev_state = event->u.event.signaled; -+ event->u.event.signaled = true; -+ try_wake_any_event(event); -+ -+ spin_unlock(&event->lock); -+ } -+ -+ put_obj(event); -+ -+ if (put_user(prev_state, &user_args->signaled)) -+ return -EFAULT; -+ -+ return 0; -+} -+ - static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) - { - int ret = 0; -@@ -1006,6 +1049,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_read_mutex(dev, argp); - case WINESYNC_IOC_READ_SEM: - return winesync_read_sem(dev, argp); -+ case WINESYNC_IOC_SET_EVENT: -+ return winesync_set_event(dev, argp); - case WINESYNC_IOC_WAIT_ALL: - return winesync_wait_all(dev, argp); - case WINESYNC_IOC_WAIT_ANY: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 3999407534e0..34cd65d879a8 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -59,5 +59,7 @@ struct winesync_wait_args { - struct winesync_mutex_args) - #define WINESYNC_IOC_CREATE_EVENT _IOWR(WINESYNC_IOC_BASE, 10, \ - struct winesync_event_args) -+#define WINESYNC_IOC_SET_EVENT _IOWR(WINESYNC_IOC_BASE, 11, \ -+ struct winesync_event_args) - - #endif --- -2.36.0 - -From 7abe646cd9c913b78156186e3a2d98715a0f3513 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 19:00:25 -0600 -Subject: [PATCH 23/34] winesync: Introduce WINESYNC_IOC_RESET_EVENT. - ---- - drivers/misc/winesync.c | 31 +++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 2 ++ - 2 files changed, 33 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 658ad7b80c29..a93f173127f4 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -747,6 +747,35 @@ static int winesync_set_event(struct winesync_device *dev, void __user *argp) - return 0; - } - -+static int winesync_reset_event(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_event_args __user *user_args = argp; -+ struct winesync_event_args args; -+ struct winesync_obj *event; -+ bool prev_state; -+ -+ if (copy_from_user(&args, argp, sizeof(args))) -+ return -EFAULT; -+ -+ event = get_obj_typed(dev, args.event, WINESYNC_TYPE_EVENT); -+ if (!event) -+ return -EINVAL; -+ -+ spin_lock(&event->lock); -+ -+ prev_state = event->u.event.signaled; -+ event->u.event.signaled = false; -+ -+ spin_unlock(&event->lock); -+ -+ put_obj(event); -+ -+ if (put_user(prev_state, &user_args->signaled)) -+ return -EFAULT; -+ -+ return 0; -+} -+ - static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) - { - int ret = 0; -@@ -1049,6 +1078,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_read_mutex(dev, argp); - case WINESYNC_IOC_READ_SEM: - return winesync_read_sem(dev, argp); -+ case WINESYNC_IOC_RESET_EVENT: -+ return winesync_reset_event(dev, argp); - case WINESYNC_IOC_SET_EVENT: - return winesync_set_event(dev, argp); - case WINESYNC_IOC_WAIT_ALL: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 34cd65d879a8..e71271fc44ba 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -61,5 +61,7 @@ struct winesync_wait_args { - struct winesync_event_args) - #define WINESYNC_IOC_SET_EVENT _IOWR(WINESYNC_IOC_BASE, 11, \ - struct winesync_event_args) -+#define WINESYNC_IOC_RESET_EVENT _IOWR(WINESYNC_IOC_BASE, 12, \ -+ struct winesync_event_args) - - #endif --- -2.36.0 - -From 3ea6a631230c7b17d345e2249f5f72ad24c46a79 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 19:10:12 -0600 -Subject: [PATCH 24/34] winesync: Introduce WINESYNC_IOC_PULSE_EVENT. - ---- - drivers/misc/winesync.c | 11 +++++++++-- - include/uapi/linux/winesync.h | 2 ++ - 2 files changed, 11 insertions(+), 2 deletions(-) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index a93f173127f4..27d5baa457df 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -704,7 +704,8 @@ static int winesync_kill_owner(struct winesync_device *dev, void __user *argp) - return 0; - } - --static int winesync_set_event(struct winesync_device *dev, void __user *argp) -+static int winesync_set_event(struct winesync_device *dev, void __user *argp, -+ bool pulse) - { - struct winesync_event_args __user *user_args = argp; - struct winesync_event_args args; -@@ -726,6 +727,8 @@ static int winesync_set_event(struct winesync_device *dev, void __user *argp) - event->u.event.signaled = true; - try_wake_all_obj(dev, event); - try_wake_any_event(event); -+ if (pulse) -+ event->u.event.signaled = false; - - spin_unlock(&event->lock); - spin_unlock(&dev->wait_all_lock); -@@ -735,6 +738,8 @@ static int winesync_set_event(struct winesync_device *dev, void __user *argp) - prev_state = event->u.event.signaled; - event->u.event.signaled = true; - try_wake_any_event(event); -+ if (pulse) -+ event->u.event.signaled = false; - - spin_unlock(&event->lock); - } -@@ -1070,6 +1075,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_delete(dev, argp); - case WINESYNC_IOC_KILL_OWNER: - return winesync_kill_owner(dev, argp); -+ case WINESYNC_IOC_PULSE_EVENT: -+ return winesync_set_event(dev, argp, true); - case WINESYNC_IOC_PUT_MUTEX: - return winesync_put_mutex(dev, argp); - case WINESYNC_IOC_PUT_SEM: -@@ -1081,7 +1088,7 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - case WINESYNC_IOC_RESET_EVENT: - return winesync_reset_event(dev, argp); - case WINESYNC_IOC_SET_EVENT: -- return winesync_set_event(dev, argp); -+ return winesync_set_event(dev, argp, false); - case WINESYNC_IOC_WAIT_ALL: - return winesync_wait_all(dev, argp); - case WINESYNC_IOC_WAIT_ANY: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index e71271fc44ba..7c09d0e9733c 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -63,5 +63,7 @@ struct winesync_wait_args { - struct winesync_event_args) - #define WINESYNC_IOC_RESET_EVENT _IOWR(WINESYNC_IOC_BASE, 12, \ - struct winesync_event_args) -+#define WINESYNC_IOC_PULSE_EVENT _IOWR(WINESYNC_IOC_BASE, 13, \ -+ struct winesync_event_args) - - #endif --- -2.36.0 - -From 0fb972bb73385f9140f81a5f976b95ba750b73dd Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 19:14:00 -0600 -Subject: [PATCH 25/34] winesync: Introduce WINESYNC_IOC_READ_EVENT. - ---- - drivers/misc/winesync.c | 30 ++++++++++++++++++++++++++++++ - include/uapi/linux/winesync.h | 2 ++ - 2 files changed, 32 insertions(+) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 27d5baa457df..0f8a8a94eef8 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -639,6 +639,33 @@ static int winesync_read_mutex(struct winesync_device *dev, void __user *argp) - return ret; - } - -+static int winesync_read_event(struct winesync_device *dev, void __user *argp) -+{ -+ struct winesync_event_args __user *user_args = argp; -+ struct winesync_event_args args; -+ struct winesync_obj *event; -+ __u32 id; -+ -+ if (get_user(id, &user_args->event)) -+ return -EFAULT; -+ -+ event = get_obj_typed(dev, id, WINESYNC_TYPE_EVENT); -+ if (!event) -+ return -EINVAL; -+ -+ args.event = id; -+ spin_lock(&event->lock); -+ args.manual = event->u.event.manual; -+ args.signaled = event->u.event.signaled; -+ spin_unlock(&event->lock); -+ -+ put_obj(event); -+ -+ if (copy_to_user(user_args, &args, sizeof(args))) -+ return -EFAULT; -+ return 0; -+} -+ - /* - * Actually change the mutex state to mark its owner as dead. - */ -@@ -1081,6 +1109,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, - return winesync_put_mutex(dev, argp); - case WINESYNC_IOC_PUT_SEM: - return winesync_put_sem(dev, argp); -+ case WINESYNC_IOC_READ_EVENT: -+ return winesync_read_event(dev, argp); - case WINESYNC_IOC_READ_MUTEX: - return winesync_read_mutex(dev, argp); - case WINESYNC_IOC_READ_SEM: -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index 7c09d0e9733c..fb3788339ffe 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -65,5 +65,7 @@ struct winesync_wait_args { - struct winesync_event_args) - #define WINESYNC_IOC_PULSE_EVENT _IOWR(WINESYNC_IOC_BASE, 13, \ - struct winesync_event_args) -+#define WINESYNC_IOC_READ_EVENT _IOWR(WINESYNC_IOC_BASE, 14, \ -+ struct winesync_event_args) - - #endif --- -2.36.0 - -From ae7648556c522595d288bc169bde503140a59db0 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 19:34:47 -0600 -Subject: [PATCH 26/34] selftests: winesync: Add some tests for manual-reset - event state. - ---- - .../selftests/drivers/winesync/winesync.c | 92 +++++++++++++++++++ - 1 file changed, 92 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index ad6d0f9a2a35..7e99f09b113b 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -85,6 +85,30 @@ static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) - return ret; - } - -+static int read_event_state(int fd, __u32 event, __u32 *signaled, __u32 *manual) -+{ -+ struct winesync_event_args args; -+ int ret; -+ -+ args.event = event; -+ args.signaled = 0xdeadbeef; -+ args.manual = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_READ_EVENT, &args); -+ *signaled = args.signaled; -+ *manual = args.manual; -+ return ret; -+} -+ -+#define check_event_state(fd, event, signaled, manual) \ -+ ({ \ -+ __u32 __signaled, __manual; \ -+ int ret = read_event_state((fd), (event), \ -+ &__signaled, &__manual); \ -+ EXPECT_EQ(0, ret); \ -+ EXPECT_EQ((signaled), __signaled); \ -+ EXPECT_EQ((manual), __manual); \ -+ }) -+ - static int wait_objs(int fd, unsigned long request, __u32 count, - const __u32 *objs, __u32 owner, __u32 *index) - { -@@ -350,6 +374,74 @@ TEST(mutex_state) - close(fd); - } - -+TEST(manual_event_state) -+{ -+ struct winesync_event_args event_args; -+ __u32 index; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ event_args.manual = 1; -+ event_args.signaled = 0; -+ event_args.event = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, event_args.event); -+ check_event_state(fd, event_args.event, 0, 1); -+ -+ event_args.signaled = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 1, 1); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, event_args.signaled); -+ check_event_state(fd, event_args.event, 1, 1); -+ -+ ret = wait_any(fd, 1, &event_args.event, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_event_state(fd, event_args.event, 1, 1); -+ -+ event_args.signaled = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 1); -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 1); -+ -+ ret = wait_any(fd, 1, &event_args.event, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 1); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 1); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ - TEST(test_wait_any) - { - struct winesync_mutex_args mutex_args = {0}; --- -2.36.0 - -From 5eeeb415ccc7e046fc71f20345bf8be20edfc1c4 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 19:45:39 -0600 -Subject: [PATCH 27/34] selftests: winesync: Add some tests for auto-reset - event state. - ---- - .../selftests/drivers/winesync/winesync.c | 59 +++++++++++++++++++ - 1 file changed, 59 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 7e99f09b113b..3a9ac69308af 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -442,6 +442,65 @@ TEST(manual_event_state) - close(fd); - } - -+TEST(auto_event_state) -+{ -+ struct winesync_event_args event_args; -+ __u32 index; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ event_args.manual = 0; -+ event_args.signaled = 1; -+ event_args.event = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, event_args.event); -+ -+ check_event_state(fd, event_args.event, 1, 0); -+ -+ event_args.signaled = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, event_args.signaled); -+ check_event_state(fd, event_args.event, 1, 0); -+ -+ ret = wait_any(fd, 1, &event_args.event, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_event_state(fd, event_args.event, 0, 0); -+ -+ event_args.signaled = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 0); -+ -+ ret = wait_any(fd, 1, &event_args.event, 123, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 0); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 0); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ - TEST(test_wait_any) - { - struct winesync_mutex_args mutex_args = {0}; --- -2.36.0 - -From 6857a39cd264169494908abf8564ac7161773203 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 21:00:50 -0600 -Subject: [PATCH 28/34] selftests: winesync: Add some tests for wakeup - signaling with events. - ---- - .../selftests/drivers/winesync/winesync.c | 152 +++++++++++++++++- - 1 file changed, 150 insertions(+), 2 deletions(-) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 3a9ac69308af..2ccc51510230 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -610,6 +610,7 @@ TEST(test_wait_any) - - TEST(test_wait_all) - { -+ struct winesync_event_args event_args = {0}; - struct winesync_mutex_args mutex_args = {0}; - struct winesync_sem_args sem_args = {0}; - __u32 objs[2], owner, index; -@@ -632,6 +633,11 @@ TEST(test_wait_all) - EXPECT_EQ(0, ret); - EXPECT_NE(0xdeadbeef, mutex_args.mutex); - -+ event_args.manual = true; -+ event_args.signaled = true; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ - objs[0] = sem_args.sem; - objs[1] = mutex_args.mutex; - -@@ -680,6 +686,14 @@ TEST(test_wait_all) - check_sem_state(fd, sem_args.sem, 1, 3); - check_mutex_state(fd, mutex_args.mutex, 1, 123); - -+ objs[0] = sem_args.sem; -+ objs[1] = event_args.event; -+ ret = wait_all(fd, 2, objs, 123, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ check_sem_state(fd, sem_args.sem, 0, 3); -+ check_event_state(fd, event_args.event, 1, 1); -+ - /* test waiting on the same object twice */ - objs[0] = objs[1] = sem_args.sem; - ret = wait_all(fd, 2, objs, 123, &index); -@@ -690,6 +704,8 @@ TEST(test_wait_all) - EXPECT_EQ(0, ret); - ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); - EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); - - close(fd); - } -@@ -829,6 +845,7 @@ static int wait_for_thread(pthread_t thread, unsigned int ms) - - TEST(wake_any) - { -+ struct winesync_event_args event_args = {0}; - struct winesync_mutex_args mutex_args = {0}; - struct winesync_wait_args wait_args = {0}; - struct winesync_sem_args sem_args = {0}; -@@ -918,10 +935,103 @@ TEST(wake_any) - EXPECT_EQ(0, thread_args.ret); - EXPECT_EQ(1, wait_args.index); - -+ /* test waking events */ -+ -+ event_args.manual = false; -+ event_args.signaled = false; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ objs[1] = event_args.event; -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 0); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 0); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ -+ event_args.manual = true; -+ event_args.signaled = false; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ objs[1] = event_args.event; -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 1, 1); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, event_args.signaled); -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, event_args.signaled); -+ check_event_state(fd, event_args.event, 0, 1); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(1, wait_args.index); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ - /* delete an object while it's being waited on */ - - get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); - wait_args.owner = 123; -+ objs[1] = mutex_args.mutex; - ret = pthread_create(&thread, NULL, wait_thread, &thread_args); - EXPECT_EQ(0, ret); - -@@ -943,11 +1053,13 @@ TEST(wake_any) - - TEST(wake_all) - { -+ struct winesync_event_args manual_event_args = {0}; -+ struct winesync_event_args auto_event_args = {0}; - struct winesync_mutex_args mutex_args = {0}; - struct winesync_wait_args wait_args = {0}; - struct winesync_sem_args sem_args = {0}; - struct wait_args thread_args; -- __u32 objs[2], count, index; -+ __u32 objs[4], count, index; - struct timespec timeout; - pthread_t thread; - int fd, ret; -@@ -969,13 +1081,25 @@ TEST(wake_all) - EXPECT_EQ(0, ret); - EXPECT_NE(0xdeadbeef, mutex_args.mutex); - -+ manual_event_args.manual = true; -+ manual_event_args.signaled = true; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &manual_event_args); -+ EXPECT_EQ(0, ret); -+ -+ auto_event_args.manual = false; -+ auto_event_args.signaled = true; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &auto_event_args); -+ EXPECT_EQ(0, ret); -+ - objs[0] = sem_args.sem; - objs[1] = mutex_args.mutex; -+ objs[2] = manual_event_args.event; -+ objs[3] = auto_event_args.event; - - get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); - wait_args.timeout = (uintptr_t)&timeout; - wait_args.objs = (uintptr_t)objs; -- wait_args.count = 2; -+ wait_args.count = 4; - wait_args.owner = 456; - thread_args.fd = fd; - thread_args.args = &wait_args; -@@ -1009,12 +1133,32 @@ TEST(wake_all) - - check_mutex_state(fd, mutex_args.mutex, 0, 0); - -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &manual_event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, manual_event_args.signaled); -+ - sem_args.count = 2; - ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); - EXPECT_EQ(0, ret); - EXPECT_EQ(0, sem_args.count); -+ check_sem_state(fd, sem_args.sem, 2, 3); -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &auto_event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, auto_event_args.signaled); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &manual_event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, manual_event_args.signaled); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &auto_event_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, auto_event_args.signaled); -+ - check_sem_state(fd, sem_args.sem, 1, 3); - check_mutex_state(fd, mutex_args.mutex, 1, 456); -+ check_event_state(fd, manual_event_args.event, 1, 1); -+ check_event_state(fd, auto_event_args.event, 0, 0); - - ret = wait_for_thread(thread, 100); - EXPECT_EQ(0, ret); -@@ -1034,6 +1178,10 @@ TEST(wake_all) - EXPECT_EQ(0, ret); - ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); - EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &manual_event_args.event); -+ EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &auto_event_args.event); -+ EXPECT_EQ(0, ret); - - ret = wait_for_thread(thread, 200); - EXPECT_EQ(0, ret); --- -2.36.0 - -From 8d2d3a310b90252903cc10e84e2bb1a06d7e8fac Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 21:06:22 -0600 -Subject: [PATCH 29/34] selftests: winesync: Add some tests for invalid object - handling with events. - ---- - .../selftests/drivers/winesync/winesync.c | 34 +++++++++++++++++++ - 1 file changed, 34 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index 2ccc51510230..f2e18836c733 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -712,6 +712,7 @@ TEST(test_wait_all) - - TEST(invalid_objects) - { -+ struct winesync_event_args event_args = {0}; - struct winesync_mutex_args mutex_args = {0}; - struct winesync_wait_args wait_args = {0}; - struct winesync_sem_args sem_args = {0}; -@@ -737,6 +738,22 @@ TEST(invalid_objects) - EXPECT_EQ(-1, ret); - EXPECT_EQ(EINVAL, errno); - -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_READ_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ - wait_args.objs = (uintptr_t)objs; - wait_args.count = 1; - ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); -@@ -763,6 +780,23 @@ TEST(invalid_objects) - EXPECT_EQ(-1, ret); - EXPECT_EQ(EINVAL, errno); - -+ event_args.event = sem_args.sem; -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_READ_EVENT, &event_args); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(EINVAL, errno); -+ - objs[0] = sem_args.sem; - objs[1] = sem_args.sem + 1; - wait_args.count = 2; --- -2.36.0 - -From 25270ec5877bcf2aa81fc4dd8326a4ee5af6e541 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 19 Jan 2022 22:01:46 -0600 -Subject: [PATCH 30/34] docs: winesync: Document event APIs. - ---- - Documentation/userspace-api/winesync.rst | 104 ++++++++++++++++++++++- - 1 file changed, 101 insertions(+), 3 deletions(-) - -diff --git a/Documentation/userspace-api/winesync.rst b/Documentation/userspace-api/winesync.rst -index 34e54be229cf..ffa2f8fbc7e3 100644 ---- a/Documentation/userspace-api/winesync.rst -+++ b/Documentation/userspace-api/winesync.rst -@@ -18,8 +18,8 @@ interfaces such as futex(2) and poll(2). - Synchronization primitives - ========================== - --The winesync driver exposes two types of synchronization primitives, --semaphores and mutexes. -+The winesync driver exposes three types of synchronization primitives: -+semaphores, mutexes, and events. - - A semaphore holds a single volatile 32-bit counter, and a static - 32-bit integer denoting the maximum value. It is considered signaled -@@ -45,6 +45,12 @@ intended use is to store a thread identifier; however, the winesync - driver does not actually validate that a calling thread provides - consistent or unique identifiers. - -+An event holds a volatile boolean state denoting whether it is -+signaled or not. There are two types of events, auto-reset and -+manual-reset. An auto-reset event is designaled when a wait is -+satisfied; a manual-reset event is not. The event type is specified -+when the event is created. -+ - Unless specified otherwise, all operations on an object are atomic and - totally ordered with respect to other operations on the same object. - -@@ -78,6 +84,12 @@ structures used in ioctl calls:: - __u32 count; - }; - -+ struct winesync_event_args { -+ __u32 event; -+ __u32 signaled; -+ __u32 manual; -+ }; -+ - struct winesync_wait_args { - __u64 timeout; - __u64 objs; -@@ -125,6 +137,22 @@ The ioctls are as follows: - If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is - zero and ``count`` is nonzero, the function fails with ``EINVAL``. - -+.. c:macro:: WINESYNC_IOC_CREATE_EVENT -+ -+ Create an event object. Takes a pointer to struct -+ :c:type:`winesync_event_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``event`` -+ - On output, contains the identifier of the created event. -+ * - ``signaled`` -+ - If nonzero, the event is initially signaled, otherwise -+ nonsignaled. -+ * - ``manual`` -+ - If nonzero, the event is a manual-reset event, otherwise -+ auto-reset. -+ - .. c:macro:: WINESYNC_IOC_DELETE - - Delete an object of any type. Takes an input-only pointer to a -@@ -178,6 +206,60 @@ The ioctls are as follows: - unowned and signaled, and eligible threads waiting on it will be - woken as appropriate. - -+.. c:macro:: WINESYNC_IOC_SET_EVENT -+ -+ Signal an event object. Takes a pointer to struct -+ :c:type:`winesync_event_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``event`` -+ - Event object to set. -+ * - ``signaled`` -+ - On output, contains the previous state of the event. -+ * - ``manual`` -+ - Unused. -+ -+ Eligible threads will be woken, and auto-reset events will be -+ designaled appropriately. -+ -+.. c:macro:: WINESYNC_IOC_RESET_EVENT -+ -+ Designal an event object. Takes a pointer to struct -+ :c:type:`winesync_event_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``event`` -+ - Event object to reset. -+ * - ``signaled`` -+ - On output, contains the previous state of the event. -+ * - ``manual`` -+ - Unused. -+ -+.. c:macro:: WINESYNC_IOC_PULSE_EVENT -+ -+ Wake threads waiting on an event object without leaving it in a -+ signaled state. Takes a pointer to struct -+ :c:type:`winesync_event_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``event`` -+ - Event object to pulse. -+ * - ``signaled`` -+ - On output, contains the previous state of the event. -+ * - ``manual`` -+ - Unused. -+ -+ A pulse operation can be thought of as a set followed by a reset, -+ performed as a single atomic operation. If two threads are waiting -+ on an auto-reset event which is pulsed, only one will be woken. If -+ two threads are waiting a manual-reset event which is pulsed, both -+ will be woken. However, in both cases, the event will be unsignaled -+ afterwards, and a simultaneous read operation will always report the -+ event as unsignaled. -+ - .. c:macro:: WINESYNC_IOC_READ_SEM - - Read the current state of a semaphore object. Takes a pointer to -@@ -211,6 +293,21 @@ The ioctls are as follows: - ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to - zero. - -+.. c:macro:: WINESYNC_IOC_READ_EVENT -+ -+ Read the current state of an event object. Takes a pointer to struct -+ :c:type:`winesync_event_args`, which is used as follows: -+ -+ .. list-table:: -+ -+ * - ``event`` -+ - Event object. -+ * - ``signaled`` -+ - On output, contains the current state of the event. -+ * - ``manual`` -+ - On output, contains 1 if the event is a manual-reset event, -+ and 0 otherwise. -+ - .. c:macro:: WINESYNC_IOC_KILL_OWNER - - Mark any mutexes owned by the given owner as unowned and -@@ -272,7 +369,8 @@ The ioctls are as follows: - considered to be signaled if it is unowned or if its owner matches - the ``owner`` argument, and is acquired by incrementing its - recursion count by one and setting its owner to the ``owner`` -- argument. -+ argument. An auto-reset event is acquired by designaling it; a -+ manual-reset event is not affected by acquisition. - - Acquisition is atomic and totally ordered with respect to other - operations on the same object. If two wait operations (with --- -2.36.0 - -From 80f5b4dfd947592ff89cb54a07ce9d1087c608d0 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 13 Apr 2022 20:02:39 -0500 -Subject: [PATCH 31/34] winesync: Introduce alertable waits. - ---- - drivers/misc/winesync.c | 68 ++++++++++++++++++++++++++++++----- - include/uapi/linux/winesync.h | 2 +- - 2 files changed, 60 insertions(+), 10 deletions(-) - -diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c -index 4fbf231a7909..7a28f58dbbf2 100644 ---- a/drivers/misc/winesync.c -+++ b/drivers/misc/winesync.c -@@ -841,10 +841,11 @@ static int setup_wait(struct winesync_device *dev, - const __u32 count = args->count; - struct winesync_q *q; - ktime_t timeout = 0; -+ __u32 total_count; - __u32 *ids; - __u32 i, j; - -- if (!args->owner || args->pad) -+ if (!args->owner) - return -EINVAL; - - if (args->timeout) { -@@ -858,7 +859,11 @@ static int setup_wait(struct winesync_device *dev, - timeout = timespec64_to_ns(&to); - } - -- ids = kmalloc_array(count, sizeof(*ids), GFP_KERNEL); -+ total_count = count; -+ if (args->alert) -+ total_count++; -+ -+ ids = kmalloc_array(total_count, sizeof(*ids), GFP_KERNEL); - if (!ids) - return -ENOMEM; - if (copy_from_user(ids, u64_to_user_ptr(args->objs), -@@ -866,8 +871,10 @@ static int setup_wait(struct winesync_device *dev, - kfree(ids); - return -EFAULT; - } -+ if (args->alert) -+ ids[count] = args->alert; - -- q = kmalloc(struct_size(q, entries, count), GFP_KERNEL); -+ q = kmalloc(struct_size(q, entries, total_count), GFP_KERNEL); - if (!q) { - kfree(ids); - return -ENOMEM; -@@ -879,7 +886,7 @@ static int setup_wait(struct winesync_device *dev, - q->ownerdead = false; - q->count = count; - -- for (i = 0; i < count; i++) { -+ for (i = 0; i < total_count; i++) { - struct winesync_q_entry *entry = &q->entries[i]; - struct winesync_obj *obj = get_obj(dev, ids[i]); - -@@ -934,9 +941,9 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) - { - struct winesync_wait_args args; - struct winesync_q *q; -+ __u32 i, total_count; - ktime_t timeout; - int signaled; -- __u32 i; - int ret; - - if (copy_from_user(&args, argp, sizeof(args))) -@@ -946,9 +953,13 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) - if (ret < 0) - return ret; - -+ total_count = args.count; -+ if (args.alert) -+ total_count++; -+ - /* queue ourselves */ - -- for (i = 0; i < args.count; i++) { -+ for (i = 0; i < total_count; i++) { - struct winesync_q_entry *entry = &q->entries[i]; - struct winesync_obj *obj = entry->obj; - -@@ -957,9 +968,15 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) - spin_unlock(&obj->lock); - } - -- /* check if we are already signaled */ -+ /* -+ * Check if we are already signaled. -+ * -+ * Note that the API requires that normal objects are checked before -+ * the alert event. Hence we queue the alert event last, and check -+ * objects in order. -+ */ - -- for (i = 0; i < args.count; i++) { -+ for (i = 0; i < total_count; i++) { - struct winesync_obj *obj = q->entries[i].obj; - - if (atomic_read(&q->signaled) != -1) -@@ -976,7 +993,7 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) - - /* and finally, unqueue */ - -- for (i = 0; i < args.count; i++) { -+ for (i = 0; i < total_count; i++) { - struct winesync_q_entry *entry = &q->entries[i]; - struct winesync_obj *obj = entry->obj; - -@@ -1036,6 +1053,14 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) - */ - list_add_tail(&entry->node, &obj->all_waiters); - } -+ if (args.alert) { -+ struct winesync_q_entry *entry = &q->entries[args.count]; -+ struct winesync_obj *obj = entry->obj; -+ -+ spin_lock(&obj->lock); -+ list_add_tail(&entry->node, &obj->any_waiters); -+ spin_unlock(&obj->lock); -+ } - - /* check if we are already signaled */ - -@@ -1043,6 +1068,21 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) - - spin_unlock(&dev->wait_all_lock); - -+ /* -+ * Check if the alert event is signaled, making sure to do so only -+ * after checking if the other objects are signaled. -+ */ -+ -+ if (args.alert) { -+ struct winesync_obj *obj = q->entries[args.count].obj; -+ -+ if (atomic_read(&q->signaled) == -1) { -+ spin_lock(&obj->lock); -+ try_wake_any_obj(obj); -+ spin_unlock(&obj->lock); -+ } -+ } -+ - /* sleep */ - - ret = winesync_schedule(q, args.timeout ? &timeout : NULL); -@@ -1065,6 +1105,16 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) - - put_obj(obj); - } -+ if (args.alert) { -+ struct winesync_q_entry *entry = &q->entries[args.count]; -+ struct winesync_obj *obj = entry->obj; -+ -+ spin_lock(&obj->lock); -+ list_del(&entry->node); -+ spin_unlock(&obj->lock); -+ -+ put_obj(obj); -+ } - - spin_unlock(&dev->wait_all_lock); - -diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h -index fb3788339ffe..5b4e369f7469 100644 ---- a/include/uapi/linux/winesync.h -+++ b/include/uapi/linux/winesync.h -@@ -34,7 +34,7 @@ struct winesync_wait_args { - __u32 count; - __u32 owner; - __u32 index; -- __u32 pad; -+ __u32 alert; - }; - - #define WINESYNC_IOC_BASE 0xf7 --- -2.37.3 - -From 127efad71a0702a68890097b114b3467c234259f Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 20 Apr 2022 18:08:37 -0500 -Subject: [PATCH 32/34] selftests: winesync: Add tests for alertable waits. - ---- - .../selftests/drivers/winesync/winesync.c | 191 +++++++++++++++++- - 1 file changed, 188 insertions(+), 3 deletions(-) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index f2e18836c733..a87e3c48709b 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -110,7 +110,7 @@ static int read_event_state(int fd, __u32 event, __u32 *signaled, __u32 *manual) - }) - - static int wait_objs(int fd, unsigned long request, __u32 count, -- const __u32 *objs, __u32 owner, __u32 *index) -+ const __u32 *objs, __u32 owner, __u32 alert, __u32 *index) - { - struct winesync_wait_args args = {0}; - struct timespec timeout; -@@ -123,6 +123,7 @@ static int wait_objs(int fd, unsigned long request, __u32 count, - args.objs = (uintptr_t)objs; - args.owner = owner; - args.index = 0xdeadbeef; -+ args.alert = alert; - ret = ioctl(fd, request, &args); - *index = args.index; - return ret; -@@ -131,13 +132,29 @@ static int wait_objs(int fd, unsigned long request, __u32 count, - static int wait_any(int fd, __u32 count, const __u32 *objs, - __u32 owner, __u32 *index) - { -- return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, count, objs, owner, index); -+ return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, -+ count, objs, owner, 0, index); - } - - static int wait_all(int fd, __u32 count, const __u32 *objs, - __u32 owner, __u32 *index) - { -- return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, count, objs, owner, index); -+ return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, -+ count, objs, owner, 0, index); -+} -+ -+static int wait_any_alert(int fd, __u32 count, const __u32 *objs, -+ __u32 owner, __u32 alert, __u32 *index) -+{ -+ return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, -+ count, objs, owner, alert, index); -+} -+ -+static int wait_all_alert(int fd, __u32 count, const __u32 *objs, -+ __u32 owner, __u32 alert, __u32 *index) -+{ -+ return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, -+ count, objs, owner, alert, index); - } - - TEST(semaphore_state) -@@ -1225,4 +1242,172 @@ TEST(wake_all) - close(fd); - } - -+TEST(alert_any) -+{ -+ struct winesync_event_args event_args = {0}; -+ struct winesync_sem_args sem_args = {0}; -+ __u32 objs[2], index; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ sem_args.count = 0; -+ sem_args.max = 2; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ objs[0] = sem_args.sem; -+ -+ sem_args.count = 1; -+ sem_args.max = 2; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ objs[1] = sem_args.sem; -+ -+ event_args.manual = true; -+ event_args.signaled = true; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(1, index); -+ -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ -+ /* test with an auto-reset event */ -+ -+ event_args.manual = false; -+ event_args.signaled = true; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ sem_args.sem = objs[0]; -+ sem_args.count = 1; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); -+ -+ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[0]); -+ EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[1]); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ -+TEST(alert_all) -+{ -+ struct winesync_event_args event_args = {0}; -+ struct winesync_sem_args sem_args = {0}; -+ __u32 objs[2], index; -+ int fd, ret; -+ -+ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -+ ASSERT_LE(0, fd); -+ -+ sem_args.count = 2; -+ sem_args.max = 2; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ objs[0] = sem_args.sem; -+ -+ sem_args.count = 1; -+ sem_args.max = 2; -+ sem_args.sem = 0xdeadbeef; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ EXPECT_NE(0xdeadbeef, sem_args.sem); -+ objs[1] = sem_args.sem; -+ -+ event_args.manual = true; -+ event_args.signaled = true; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ -+ /* test with an auto-reset event */ -+ -+ event_args.manual = false; -+ event_args.signaled = true; -+ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ sem_args.sem = objs[1]; -+ sem_args.count = 2; -+ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, index); -+ -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(2, index); -+ -+ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); -+ EXPECT_EQ(-1, ret); -+ EXPECT_EQ(ETIMEDOUT, errno); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); -+ EXPECT_EQ(0, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[0]); -+ EXPECT_EQ(0, ret); -+ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[1]); -+ EXPECT_EQ(0, ret); -+ -+ close(fd); -+} -+ - TEST_HARNESS_MAIN --- -2.36.0 - -From e5ec8276fae40b6a2cdab3cb728160705c0f40ab Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 20 Apr 2022 18:24:43 -0500 -Subject: [PATCH 33/34] serftests: winesync: Add some tests for wakeup - signaling via alerts. - ---- - .../selftests/drivers/winesync/winesync.c | 66 +++++++++++++++++++ - 1 file changed, 66 insertions(+) - -diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c -index a87e3c48709b..169e922484b0 100644 ---- a/tools/testing/selftests/drivers/winesync/winesync.c -+++ b/tools/testing/selftests/drivers/winesync/winesync.c -@@ -1245,8 +1245,12 @@ TEST(wake_all) - TEST(alert_any) - { - struct winesync_event_args event_args = {0}; -+ struct winesync_wait_args wait_args = {0}; - struct winesync_sem_args sem_args = {0}; -+ struct wait_args thread_args; -+ struct timespec timeout; - __u32 objs[2], index; -+ pthread_t thread; - int fd, ret; - - fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -@@ -1295,6 +1299,35 @@ TEST(alert_any) - EXPECT_EQ(0, ret); - EXPECT_EQ(2, index); - -+ /* test wakeup via alert */ -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ wait_args.timeout = (uintptr_t)&timeout; -+ wait_args.objs = (uintptr_t)objs; -+ wait_args.count = 2; -+ wait_args.owner = 123; -+ wait_args.index = 0xdeadbeef; -+ wait_args.alert = event_args.event; -+ thread_args.fd = fd; -+ thread_args.args = &wait_args; -+ thread_args.request = WINESYNC_IOC_WAIT_ANY; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(2, wait_args.index); -+ - ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); - EXPECT_EQ(0, ret); - -@@ -1336,8 +1369,12 @@ TEST(alert_any) - TEST(alert_all) - { - struct winesync_event_args event_args = {0}; -+ struct winesync_wait_args wait_args = {0}; - struct winesync_sem_args sem_args = {0}; -+ struct wait_args thread_args; -+ struct timespec timeout; - __u32 objs[2], index; -+ pthread_t thread; - int fd, ret; - - fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); -@@ -1372,6 +1409,35 @@ TEST(alert_all) - EXPECT_EQ(0, ret); - EXPECT_EQ(2, index); - -+ /* test wakeup via alert */ -+ -+ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); -+ wait_args.timeout = (uintptr_t)&timeout; -+ wait_args.objs = (uintptr_t)objs; -+ wait_args.count = 2; -+ wait_args.owner = 123; -+ wait_args.index = 0xdeadbeef; -+ wait_args.alert = event_args.event; -+ thread_args.fd = fd; -+ thread_args.args = &wait_args; -+ thread_args.request = WINESYNC_IOC_WAIT_ALL; -+ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(ETIMEDOUT, ret); -+ -+ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); -+ EXPECT_EQ(0, ret); -+ -+ ret = wait_for_thread(thread, 100); -+ EXPECT_EQ(0, ret); -+ EXPECT_EQ(0, thread_args.ret); -+ EXPECT_EQ(2, wait_args.index); -+ - ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); - EXPECT_EQ(0, ret); - --- -2.36.0 - -From 50ed00eef095c7799949b2523a5c21210b374f86 Mon Sep 17 00:00:00 2001 -From: Zebediah Figura -Date: Wed, 20 Apr 2022 18:58:17 -0500 -Subject: [PATCH 34/34] docs: winesync: Document alertable waits. - ---- - Documentation/userspace-api/winesync.rst | 40 ++++++++++++++++++------ - 1 file changed, 31 insertions(+), 9 deletions(-) - -diff --git a/Documentation/userspace-api/winesync.rst b/Documentation/userspace-api/winesync.rst -index ffa2f8fbc7e3..f0110d2744c7 100644 ---- a/Documentation/userspace-api/winesync.rst -+++ b/Documentation/userspace-api/winesync.rst -@@ -354,9 +354,13 @@ The ioctls are as follows: - ``EINVAL``. - * - ``index`` - - On success, contains the index (into ``objs``) of the object -- which was signaled. -- * - ``pad`` -- - This field is not used and must be set to zero. -+ which was signaled. If ``alert`` was signaled instead, -+ this contains ``count``. -+ * - ``alert`` -+ - Optional event object identifier. If nonzero, this specifies -+ an "alert" event object which, if signaled, will terminate -+ the wait. If nonzero, the identifier must point to a valid -+ event. - - This function attempts to acquire one of the given objects. If - unable to do so, it sleeps until an object becomes signaled, -@@ -385,9 +389,19 @@ The ioctls are as follows: - the given owner (with a recursion count of 1) and as no longer - inconsistent, and ``index`` is still set to the index of the mutex. - -- It is valid to pass the same object more than once. If a wakeup -- occurs due to that object being signaled, ``index`` is set to the -- lowest index corresponding to that object. -+ The ``alert`` argument is an "extra" event which can terminate the -+ wait, independently of all other objects. If members of ``objs`` and -+ ``alert`` are both simultaneously signaled, a member of ``objs`` -+ will always be given priority and acquired first. Aside from this, -+ for "any" waits, there is no difference between passing an event as -+ this parameter, and passing it as an additional object at the end of -+ the ``objs`` array. For "all" waits, there is an additional -+ difference, as described below. -+ -+ It is valid to pass the same object more than once, including by -+ passing the same event in the ``objs`` array and in ``alert``. If a -+ wakeup occurs due to that object being signaled, ``index`` is set to -+ the lowest index corresponding to that object. - - The function may fail with ``EINTR`` if a signal is received. - -@@ -396,7 +410,7 @@ The ioctls are as follows: - Poll on a list of objects, atomically acquiring all of them. Takes a - pointer to struct :c:type:`winesync_wait_args`, which is used - identically to ``WINESYNC_IOC_WAIT_ANY``, except that ``index`` is -- always filled with zero on success. -+ always filled with zero on success if not woken via alert. - - This function attempts to simultaneously acquire all of the given - objects. If unable to do so, it sleeps until all objects become -@@ -417,6 +431,14 @@ The ioctls are as follows: - objects are specified, there is no way to know which were marked as - inconsistent. - -+ As with "any" waits, the ``alert`` argument is an "extra" event -+ which can terminate the wait. Critically, however, an "all" wait -+ will succeed if all members in ``objs`` are signaled, *or* if -+ ``alert`` is signaled. In the latter case ``index`` will be set to -+ ``count``. As with "any" waits, if both conditions are filled, the -+ former takes priority, and objects in ``objs`` will be acquired. -+ - Unlike ``WINESYNC_IOC_WAIT_ANY``, it is not valid to pass the same -- object more than once. If this is attempted, the function fails with -- ``EINVAL``. -+ object more than once, nor is it valid to pass the same object in -+ ``objs`` and in ``alert`` If this is attempted, the function fails -+ with ``EINVAL``. --- -2.36.0 - diff --git a/patches/series b/patches/series index 53037fa..50989b1 100644 --- a/patches/series +++ b/patches/series @@ -1,26 +1,13 @@ cachyos/0001-cachyos-base-all.patch cachyos/0001-bore-cachy.patch -asuslinux/ROG-ALLY-NCT6775-PLATFORM.patch -asuslinux/asus-linux.patch -asuslinux/rog-ally-audio-fix.patch -asuslinux/rog-ally-bmc150.patch -asuslinux/v2-0001-platform-x86-asus-wmi-disable-USB0-hub-on-ROG-All.patch +cachyos/0002-ntsync.patch +cachyos/0003-nvidia.patch +cachyos/0004-intel.patch nobara/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch nobara/0001-Set-amdgpu.ppfeaturemask-0xffffffff-as-default.patch nobara/0001-acpi-proc-idle-skip-dummy-wait.patch nobara/0001-add-acpi_call.patch -nobara/0001-amd-hdr.patch -nobara/0001-drm-i915-quirks-disable-async-flipping-on-specific-d.patch -nobara/0001-hid-asus-nero-patches-rogue.patch -nobara/0002-drm-i915-add-kernel-parameter-to-disable-async-page-.patch nobara/OpenRGB.patch nobara/amdgpu-si-cik-default.patch -nobara/lenovo-legion-laptop.patch -nobara/linux-surface.patch -nobara/mt76:-mt7921:-Disable-powersave-features-by-default.patch -nobara/set-ps4-bt-poll-rate-1000hz.patch -nobara/steam-deck.patch -nobara/uinput.patch -nobara/winesync.patc