diff --git a/patches/0001-cachyos-base-all.patch b/patches/0001-cachyos-base-all.patch index 3ed5a82..1134836 100644 --- a/patches/0001-cachyos-base-all.patch +++ b/patches/0001-cachyos-base-all.patch @@ -1,20 +1,584 @@ -From e18c7382054960494788390ac3364676e797658f Mon Sep 17 00:00:00 2001 +From 1138281a324639ba9ab730e82ab976c2ac80b24c Mon Sep 17 00:00:00 2001 From: Eric Naim -Date: Mon, 3 Feb 2025 11:04:37 +0800 -Subject: [PATCH 1/6] amd-pstate +Date: Mon, 10 Feb 2025 09:48:51 +0800 +Subject: [PATCH 1/9] amd-pstate Signed-off-by: Eric Naim --- - kernel/sched/fair.c | 21 +++++++++++++++++++-- - kernel/sched/sched.h | 1 - - kernel/sched/topology.c | 15 +-------------- - 3 files changed, 20 insertions(+), 17 deletions(-) + drivers/cpufreq/amd-pstate-trace.h | 46 ++++---- + drivers/cpufreq/amd-pstate.c | 162 +++++++++++++---------------- + drivers/cpufreq/amd-pstate.h | 18 ++-- + include/linux/cpufreq.h | 3 + + kernel/sched/fair.c | 21 +++- + kernel/sched/sched.h | 1 - + kernel/sched/topology.c | 15 +-- + 7 files changed, 130 insertions(+), 136 deletions(-) +diff --git a/drivers/cpufreq/amd-pstate-trace.h b/drivers/cpufreq/amd-pstate-trace.h +index 8d692415d905..f457d4af2c62 100644 +--- a/drivers/cpufreq/amd-pstate-trace.h ++++ b/drivers/cpufreq/amd-pstate-trace.h +@@ -24,9 +24,9 @@ + + TRACE_EVENT(amd_pstate_perf, + +- TP_PROTO(unsigned long min_perf, +- unsigned long target_perf, +- unsigned long capacity, ++ TP_PROTO(u8 min_perf, ++ u8 target_perf, ++ u8 capacity, + u64 freq, + u64 mperf, + u64 aperf, +@@ -47,9 +47,9 @@ TRACE_EVENT(amd_pstate_perf, + ), + + TP_STRUCT__entry( +- __field(unsigned long, min_perf) +- __field(unsigned long, target_perf) +- __field(unsigned long, capacity) ++ __field(u8, min_perf) ++ __field(u8, target_perf) ++ __field(u8, capacity) + __field(unsigned long long, freq) + __field(unsigned long long, mperf) + __field(unsigned long long, aperf) +@@ -70,10 +70,10 @@ TRACE_EVENT(amd_pstate_perf, + __entry->fast_switch = fast_switch; + ), + +- TP_printk("amd_min_perf=%lu amd_des_perf=%lu amd_max_perf=%lu freq=%llu mperf=%llu aperf=%llu tsc=%llu cpu_id=%u fast_switch=%s", +- (unsigned long)__entry->min_perf, +- (unsigned long)__entry->target_perf, +- (unsigned long)__entry->capacity, ++ TP_printk("amd_min_perf=%hhu amd_des_perf=%hhu amd_max_perf=%hhu freq=%llu mperf=%llu aperf=%llu tsc=%llu cpu_id=%u fast_switch=%s", ++ (u8)__entry->min_perf, ++ (u8)__entry->target_perf, ++ (u8)__entry->capacity, + (unsigned long long)__entry->freq, + (unsigned long long)__entry->mperf, + (unsigned long long)__entry->aperf, +@@ -86,10 +86,10 @@ TRACE_EVENT(amd_pstate_perf, + TRACE_EVENT(amd_pstate_epp_perf, + + TP_PROTO(unsigned int cpu_id, +- unsigned int highest_perf, +- unsigned int epp, +- unsigned int min_perf, +- unsigned int max_perf, ++ u8 highest_perf, ++ u8 epp, ++ u8 min_perf, ++ u8 max_perf, + bool boost + ), + +@@ -102,10 +102,10 @@ TRACE_EVENT(amd_pstate_epp_perf, + + TP_STRUCT__entry( + __field(unsigned int, cpu_id) +- __field(unsigned int, highest_perf) +- __field(unsigned int, epp) +- __field(unsigned int, min_perf) +- __field(unsigned int, max_perf) ++ __field(u8, highest_perf) ++ __field(u8, epp) ++ __field(u8, min_perf) ++ __field(u8, max_perf) + __field(bool, boost) + ), + +@@ -118,12 +118,12 @@ TRACE_EVENT(amd_pstate_epp_perf, + __entry->boost = boost; + ), + +- TP_printk("cpu%u: [%u<->%u]/%u, epp=%u, boost=%u", ++ TP_printk("cpu%u: [%hhu<->%hhu]/%hhu, epp=%hhu, boost=%u", + (unsigned int)__entry->cpu_id, +- (unsigned int)__entry->min_perf, +- (unsigned int)__entry->max_perf, +- (unsigned int)__entry->highest_perf, +- (unsigned int)__entry->epp, ++ (u8)__entry->min_perf, ++ (u8)__entry->max_perf, ++ (u8)__entry->highest_perf, ++ (u8)__entry->epp, + (bool)__entry->boost + ) + ); +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +index 313550fa62d4..08ae48076812 100644 +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -142,6 +142,20 @@ static struct quirk_entry quirk_amd_7k62 = { + .lowest_freq = 550, + }; + ++static inline u8 freq_to_perf(struct amd_cpudata *cpudata, unsigned int freq_val) ++{ ++ u8 perf_val = DIV_ROUND_UP_ULL((u64)freq_val * cpudata->nominal_perf, ++ cpudata->nominal_freq); ++ ++ return clamp_t(u8, perf_val, cpudata->lowest_perf, cpudata->highest_perf); ++} ++ ++static inline u32 perf_to_freq(struct amd_cpudata *cpudata, u8 perf_val) ++{ ++ return DIV_ROUND_UP_ULL((u64)cpudata->nominal_freq * perf_val, ++ cpudata->nominal_perf); ++} ++ + static int __init dmi_matched_7k62_bios_bug(const struct dmi_system_id *dmi) + { + /** +@@ -186,7 +200,7 @@ static inline int get_mode_idx_from_str(const char *str, size_t size) + static DEFINE_MUTEX(amd_pstate_limits_lock); + static DEFINE_MUTEX(amd_pstate_driver_lock); + +-static s16 msr_get_epp(struct amd_cpudata *cpudata) ++static u8 msr_get_epp(struct amd_cpudata *cpudata) + { + u64 value; + int ret; +@@ -207,7 +221,7 @@ static inline s16 amd_pstate_get_epp(struct amd_cpudata *cpudata) + return static_call(amd_pstate_get_epp)(cpudata); + } + +-static s16 shmem_get_epp(struct amd_cpudata *cpudata) ++static u8 shmem_get_epp(struct amd_cpudata *cpudata) + { + u64 epp; + int ret; +@@ -218,11 +232,11 @@ static s16 shmem_get_epp(struct amd_cpudata *cpudata) + return ret; + } + +- return (s16)(epp & 0xff); ++ return FIELD_GET(AMD_CPPC_EPP_PERF_MASK, epp); + } + +-static int msr_update_perf(struct amd_cpudata *cpudata, u32 min_perf, +- u32 des_perf, u32 max_perf, u32 epp, bool fast_switch) ++static int msr_update_perf(struct amd_cpudata *cpudata, u8 min_perf, ++ u8 des_perf, u8 max_perf, u8 epp, bool fast_switch) + { + u64 value, prev; + +@@ -257,15 +271,15 @@ static int msr_update_perf(struct amd_cpudata *cpudata, u32 min_perf, + DEFINE_STATIC_CALL(amd_pstate_update_perf, msr_update_perf); + + static inline int amd_pstate_update_perf(struct amd_cpudata *cpudata, +- u32 min_perf, u32 des_perf, +- u32 max_perf, u32 epp, ++ u8 min_perf, u8 des_perf, ++ u8 max_perf, u8 epp, + bool fast_switch) + { + return static_call(amd_pstate_update_perf)(cpudata, min_perf, des_perf, + max_perf, epp, fast_switch); + } + +-static int msr_set_epp(struct amd_cpudata *cpudata, u32 epp) ++static int msr_set_epp(struct amd_cpudata *cpudata, u8 epp) + { + u64 value, prev; + int ret; +@@ -292,12 +306,12 @@ static int msr_set_epp(struct amd_cpudata *cpudata, u32 epp) + + DEFINE_STATIC_CALL(amd_pstate_set_epp, msr_set_epp); + +-static inline int amd_pstate_set_epp(struct amd_cpudata *cpudata, u32 epp) ++static inline int amd_pstate_set_epp(struct amd_cpudata *cpudata, u8 epp) + { + return static_call(amd_pstate_set_epp)(cpudata, epp); + } + +-static int shmem_set_epp(struct amd_cpudata *cpudata, u32 epp) ++static int shmem_set_epp(struct amd_cpudata *cpudata, u8 epp) + { + int ret; + struct cppc_perf_ctrls perf_ctrls; +@@ -320,7 +334,7 @@ static int amd_pstate_set_energy_pref_index(struct cpufreq_policy *policy, + int pref_index) + { + struct amd_cpudata *cpudata = policy->driver_data; +- int epp; ++ u8 epp; + + if (!pref_index) + epp = cpudata->epp_default; +@@ -479,8 +493,8 @@ static inline int amd_pstate_init_perf(struct amd_cpudata *cpudata) + return static_call(amd_pstate_init_perf)(cpudata); + } + +-static int shmem_update_perf(struct amd_cpudata *cpudata, u32 min_perf, +- u32 des_perf, u32 max_perf, u32 epp, bool fast_switch) ++static int shmem_update_perf(struct amd_cpudata *cpudata, u8 min_perf, ++ u8 des_perf, u8 max_perf, u8 epp, bool fast_switch) + { + struct cppc_perf_ctrls perf_ctrls; + +@@ -531,17 +545,18 @@ static inline bool amd_pstate_sample(struct amd_cpudata *cpudata) + return true; + } + +-static void amd_pstate_update(struct amd_cpudata *cpudata, u32 min_perf, +- u32 des_perf, u32 max_perf, bool fast_switch, int gov_flags) ++static void amd_pstate_update(struct amd_cpudata *cpudata, u8 min_perf, ++ u8 des_perf, u8 max_perf, bool fast_switch, int gov_flags) + { +- unsigned long max_freq; +- struct cpufreq_policy *policy = cpufreq_cpu_get(cpudata->cpu); +- u32 nominal_perf = READ_ONCE(cpudata->nominal_perf); ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); ++ u8 nominal_perf = READ_ONCE(cpudata->nominal_perf); ++ ++ if (!policy) ++ return; + +- des_perf = clamp_t(unsigned long, des_perf, min_perf, max_perf); ++ des_perf = clamp_t(u8, des_perf, min_perf, max_perf); + +- max_freq = READ_ONCE(cpudata->max_limit_freq); +- policy->cur = div_u64(des_perf * max_freq, max_perf); ++ policy->cur = perf_to_freq(cpudata, des_perf); + + if ((cppc_state == AMD_PSTATE_GUIDED) && (gov_flags & CPUFREQ_GOV_DYNAMIC_SWITCHING)) { + min_perf = des_perf; +@@ -550,7 +565,7 @@ static void amd_pstate_update(struct amd_cpudata *cpudata, u32 min_perf, + + /* limit the max perf when core performance boost feature is disabled */ + if (!cpudata->boost_supported) +- max_perf = min_t(unsigned long, nominal_perf, max_perf); ++ max_perf = min_t(u8, nominal_perf, max_perf); + + if (trace_amd_pstate_perf_enabled() && amd_pstate_sample(cpudata)) { + trace_amd_pstate_perf(min_perf, des_perf, max_perf, cpudata->freq, +@@ -559,8 +574,6 @@ static void amd_pstate_update(struct amd_cpudata *cpudata, u32 min_perf, + } + + amd_pstate_update_perf(cpudata, min_perf, des_perf, max_perf, 0, fast_switch); +- +- cpufreq_cpu_put(policy); + } + + static int amd_pstate_verify(struct cpufreq_policy_data *policy_data) +@@ -572,7 +585,8 @@ static int amd_pstate_verify(struct cpufreq_policy_data *policy_data) + * amd-pstate qos_requests. + */ + if (policy_data->min == FREQ_QOS_MIN_DEFAULT_VALUE) { +- struct cpufreq_policy *policy = cpufreq_cpu_get(policy_data->cpu); ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = ++ cpufreq_cpu_get(policy_data->cpu); + struct amd_cpudata *cpudata; + + if (!policy) +@@ -580,7 +594,6 @@ static int amd_pstate_verify(struct cpufreq_policy_data *policy_data) + + cpudata = policy->driver_data; + policy_data->min = cpudata->lowest_nonlinear_freq; +- cpufreq_cpu_put(policy); + } + + cpufreq_verify_within_cpu_limits(policy_data); +@@ -591,13 +604,11 @@ static int amd_pstate_verify(struct cpufreq_policy_data *policy_data) + + static int amd_pstate_update_min_max_limit(struct cpufreq_policy *policy) + { +- u32 max_limit_perf, min_limit_perf, max_perf, max_freq; ++ u8 max_limit_perf, min_limit_perf; + struct amd_cpudata *cpudata = policy->driver_data; + +- max_perf = READ_ONCE(cpudata->highest_perf); +- max_freq = READ_ONCE(cpudata->max_freq); +- max_limit_perf = div_u64(policy->max * max_perf, max_freq); +- min_limit_perf = div_u64(policy->min * max_perf, max_freq); ++ max_limit_perf = freq_to_perf(cpudata, policy->max); ++ min_limit_perf = freq_to_perf(cpudata, policy->min); + + if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) + min_limit_perf = min(cpudata->nominal_perf, max_limit_perf); +@@ -615,23 +626,15 @@ static int amd_pstate_update_freq(struct cpufreq_policy *policy, + { + struct cpufreq_freqs freqs; + struct amd_cpudata *cpudata = policy->driver_data; +- unsigned long max_perf, min_perf, des_perf, cap_perf; +- +- if (!cpudata->max_freq) +- return -ENODEV; ++ u8 des_perf; + + if (policy->min != cpudata->min_limit_freq || policy->max != cpudata->max_limit_freq) + amd_pstate_update_min_max_limit(policy); + +- cap_perf = READ_ONCE(cpudata->highest_perf); +- min_perf = READ_ONCE(cpudata->lowest_perf); +- max_perf = cap_perf; +- + freqs.old = policy->cur; + freqs.new = target_freq; + +- des_perf = DIV_ROUND_CLOSEST(target_freq * cap_perf, +- cpudata->max_freq); ++ des_perf = freq_to_perf(cpudata, target_freq); + + WARN_ON(fast_switch && !policy->fast_switch_enabled); + /* +@@ -642,8 +645,9 @@ static int amd_pstate_update_freq(struct cpufreq_policy *policy, + if (!fast_switch) + cpufreq_freq_transition_begin(policy, &freqs); + +- amd_pstate_update(cpudata, min_perf, des_perf, +- max_perf, fast_switch, policy->governor->flags); ++ amd_pstate_update(cpudata, cpudata->min_limit_perf, des_perf, ++ cpudata->max_limit_perf, fast_switch, ++ policy->governor->flags); + + if (!fast_switch) + cpufreq_freq_transition_end(policy, &freqs, false); +@@ -671,9 +675,8 @@ static void amd_pstate_adjust_perf(unsigned int cpu, + unsigned long target_perf, + unsigned long capacity) + { +- unsigned long max_perf, min_perf, des_perf, +- cap_perf, lowest_nonlinear_perf; +- struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); ++ u8 max_perf, min_perf, des_perf, cap_perf, min_limit_perf; ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpu); + struct amd_cpudata *cpudata; + + if (!policy) +@@ -684,30 +687,27 @@ static void amd_pstate_adjust_perf(unsigned int cpu, + if (policy->min != cpudata->min_limit_freq || policy->max != cpudata->max_limit_freq) + amd_pstate_update_min_max_limit(policy); + +- + cap_perf = READ_ONCE(cpudata->highest_perf); +- lowest_nonlinear_perf = READ_ONCE(cpudata->lowest_nonlinear_perf); ++ min_limit_perf = READ_ONCE(cpudata->min_limit_perf); + + des_perf = cap_perf; + if (target_perf < capacity) + des_perf = DIV_ROUND_UP(cap_perf * target_perf, capacity); + +- min_perf = READ_ONCE(cpudata->lowest_perf); + if (_min_perf < capacity) + min_perf = DIV_ROUND_UP(cap_perf * _min_perf, capacity); ++ else ++ min_perf = cap_perf; + +- if (min_perf < lowest_nonlinear_perf) +- min_perf = lowest_nonlinear_perf; ++ if (min_perf < min_limit_perf) ++ min_perf = min_limit_perf; + + max_perf = cpudata->max_limit_perf; + if (max_perf < min_perf) + max_perf = min_perf; + +- des_perf = clamp_t(unsigned long, des_perf, min_perf, max_perf); +- + amd_pstate_update(cpudata, min_perf, des_perf, max_perf, true, + policy->governor->flags); +- cpufreq_cpu_put(policy); + } + + static int amd_pstate_cpu_boost_update(struct cpufreq_policy *policy, bool on) +@@ -821,28 +821,21 @@ static void amd_pstate_init_prefcore(struct amd_cpudata *cpudata) + + static void amd_pstate_update_limits(unsigned int cpu) + { +- struct cpufreq_policy *policy = NULL; ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpu); + struct amd_cpudata *cpudata; + u32 prev_high = 0, cur_high = 0; +- int ret; + bool highest_perf_changed = false; + + if (!amd_pstate_prefcore) + return; + +- policy = cpufreq_cpu_get(cpu); + if (!policy) + return; + +- cpudata = policy->driver_data; +- +- guard(mutex)(&amd_pstate_driver_lock); +- +- ret = amd_get_highest_perf(cpu, &cur_high); +- if (ret) { +- cpufreq_cpu_put(policy); ++ if (amd_get_highest_perf(cpu, &cur_high)) + return; +- } ++ ++ cpudata = policy->driver_data; + + prev_high = READ_ONCE(cpudata->prefcore_ranking); + highest_perf_changed = (prev_high != cur_high); +@@ -852,11 +845,6 @@ static void amd_pstate_update_limits(unsigned int cpu) + if (cur_high < CPPC_MAX_PERF) + sched_set_itmt_core_prio((int)cur_high, cpu); + } +- cpufreq_cpu_put(policy); +- +- if (!highest_perf_changed) +- cpufreq_update_policy(cpu); +- + } + + /* +@@ -908,8 +896,7 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) + { + int ret; + u32 min_freq, max_freq; +- u32 highest_perf, nominal_perf, nominal_freq; +- u32 lowest_nonlinear_perf, lowest_nonlinear_freq; ++ u32 nominal_freq, lowest_nonlinear_freq; + struct cppc_perf_caps cppc_perf; + + ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); +@@ -926,16 +913,17 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) + else + nominal_freq = cppc_perf.nominal_freq; + +- highest_perf = READ_ONCE(cpudata->highest_perf); +- nominal_perf = READ_ONCE(cpudata->nominal_perf); +- max_freq = div_u64((u64)highest_perf * nominal_freq, nominal_perf); ++ min_freq *= 1000; ++ nominal_freq *= 1000; ++ ++ WRITE_ONCE(cpudata->nominal_freq, nominal_freq); ++ WRITE_ONCE(cpudata->min_freq, min_freq); ++ ++ max_freq = perf_to_freq(cpudata, cpudata->highest_perf); ++ lowest_nonlinear_freq = perf_to_freq(cpudata, cpudata->lowest_nonlinear_perf); + +- lowest_nonlinear_perf = READ_ONCE(cpudata->lowest_nonlinear_perf); +- lowest_nonlinear_freq = div_u64((u64)nominal_freq * lowest_nonlinear_perf, nominal_perf); +- WRITE_ONCE(cpudata->min_freq, min_freq * 1000); +- WRITE_ONCE(cpudata->lowest_nonlinear_freq, lowest_nonlinear_freq * 1000); +- WRITE_ONCE(cpudata->nominal_freq, nominal_freq * 1000); +- WRITE_ONCE(cpudata->max_freq, max_freq * 1000); ++ WRITE_ONCE(cpudata->lowest_nonlinear_freq, lowest_nonlinear_freq); ++ WRITE_ONCE(cpudata->max_freq, max_freq); + + /** + * Below values need to be initialized correctly, otherwise driver will fail to load +@@ -1116,7 +1104,7 @@ static ssize_t show_amd_pstate_lowest_nonlinear_freq(struct cpufreq_policy *poli + static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, + char *buf) + { +- u32 perf; ++ u8 perf; + struct amd_cpudata *cpudata = policy->driver_data; + + perf = READ_ONCE(cpudata->highest_perf); +@@ -1127,7 +1115,7 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, + static ssize_t show_amd_pstate_prefcore_ranking(struct cpufreq_policy *policy, + char *buf) + { +- u32 perf; ++ u8 perf; + struct amd_cpudata *cpudata = policy->driver_data; + + perf = READ_ONCE(cpudata->prefcore_ranking); +@@ -1190,7 +1178,7 @@ static ssize_t show_energy_performance_preference( + struct cpufreq_policy *policy, char *buf) + { + struct amd_cpudata *cpudata = policy->driver_data; +- int preference; ++ u8 preference; + + switch (cpudata->epp_cached) { + case AMD_CPPC_EPP_PERFORMANCE: +@@ -1552,7 +1540,7 @@ static void amd_pstate_epp_cpu_exit(struct cpufreq_policy *policy) + static int amd_pstate_epp_update_limit(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; +- u32 epp; ++ u8 epp; + + amd_pstate_update_min_max_limit(policy); + +@@ -1601,7 +1589,7 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy) + static int amd_pstate_epp_reenable(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; +- u64 max_perf; ++ u8 max_perf; + int ret; + + ret = amd_pstate_cppc_enable(true); +@@ -1638,7 +1626,7 @@ static int amd_pstate_epp_cpu_online(struct cpufreq_policy *policy) + static int amd_pstate_epp_cpu_offline(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; +- int min_perf; ++ u8 min_perf; + + if (cpudata->suspended) + return 0; +diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h +index 9747e3be6cee..19d405c6d805 100644 +--- a/drivers/cpufreq/amd-pstate.h ++++ b/drivers/cpufreq/amd-pstate.h +@@ -70,13 +70,13 @@ struct amd_cpudata { + struct freq_qos_request req[2]; + u64 cppc_req_cached; + +- u32 highest_perf; +- u32 nominal_perf; +- u32 lowest_nonlinear_perf; +- u32 lowest_perf; +- u32 prefcore_ranking; +- u32 min_limit_perf; +- u32 max_limit_perf; ++ u8 highest_perf; ++ u8 nominal_perf; ++ u8 lowest_nonlinear_perf; ++ u8 lowest_perf; ++ u8 prefcore_ranking; ++ u8 min_limit_perf; ++ u8 max_limit_perf; + u32 min_limit_freq; + u32 max_limit_freq; + +@@ -93,11 +93,11 @@ struct amd_cpudata { + bool hw_prefcore; + + /* EPP feature related attributes*/ +- s16 epp_cached; ++ u8 epp_cached; + u32 policy; + u64 cppc_cap1_cached; + bool suspended; +- s16 epp_default; ++ u8 epp_default; + }; + + /* +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +index 7fe0981a7e46..dde5212d256c 100644 +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -210,6 +210,9 @@ static inline struct cpufreq_policy *cpufreq_cpu_get(unsigned int cpu) + static inline void cpufreq_cpu_put(struct cpufreq_policy *policy) { } + #endif + ++/* Scope based cleanup macro for cpufreq_policy kobject reference counting */ ++DEFINE_FREE(put_cpufreq_policy, struct cpufreq_policy *, if (_T) cpufreq_cpu_put(_T)) ++ + static inline bool policy_is_inactive(struct cpufreq_policy *policy) + { + return cpumask_empty(policy->cpus); diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c -index ce2e94ccad0c..f9464644186c 100644 +index 1c0ef435a7aa..abebb1185010 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c -@@ -9887,6 +9887,8 @@ struct sg_lb_stats { +@@ -9906,6 +9906,8 @@ struct sg_lb_stats { unsigned int group_weight; enum group_type group_type; unsigned int group_asym_packing; /* Tasks should be moved to preferred CPU */ @@ -23,7 +587,7 @@ index ce2e94ccad0c..f9464644186c 100644 unsigned int group_smt_balance; /* Task on busy SMT be moved */ unsigned long group_misfit_task_load; /* A CPU has a task too big for its capacity */ #ifdef CONFIG_NUMA_BALANCING -@@ -10216,7 +10218,7 @@ sched_group_asym(struct lb_env *env, struct sg_lb_stats *sgs, struct sched_group +@@ -10235,7 +10237,7 @@ sched_group_asym(struct lb_env *env, struct sg_lb_stats *sgs, struct sched_group (sgs->group_weight - sgs->idle_cpus != 1)) return false; @@ -32,7 +596,7 @@ index ce2e94ccad0c..f9464644186c 100644 } /* One group has more than one SMT CPU while the other group does not */ -@@ -10297,6 +10299,17 @@ sched_reduced_capacity(struct rq *rq, struct sched_domain *sd) +@@ -10316,6 +10318,17 @@ sched_reduced_capacity(struct rq *rq, struct sched_domain *sd) return check_cpu_capacity(rq, sd); } @@ -50,7 +614,7 @@ index ce2e94ccad0c..f9464644186c 100644 /** * update_sg_lb_stats - Update sched_group's statistics for load balancing. * @env: The load balancing environment. -@@ -10319,6 +10332,7 @@ static inline void update_sg_lb_stats(struct lb_env *env, +@@ -10338,6 +10351,7 @@ static inline void update_sg_lb_stats(struct lb_env *env, memset(sgs, 0, sizeof(*sgs)); local_group = group == sds->local; @@ -58,7 +622,7 @@ index ce2e94ccad0c..f9464644186c 100644 for_each_cpu_and(i, sched_group_span(group), env->cpus) { struct rq *rq = cpu_rq(i); -@@ -10332,6 +10346,9 @@ static inline void update_sg_lb_stats(struct lb_env *env, +@@ -10351,6 +10365,9 @@ static inline void update_sg_lb_stats(struct lb_env *env, nr_running = rq->nr_running; sgs->sum_nr_running += nr_running; @@ -68,7 +632,7 @@ index ce2e94ccad0c..f9464644186c 100644 if (cpu_overutilized(i)) *sg_overutilized = 1; -@@ -10453,7 +10470,7 @@ static bool update_sd_pick_busiest(struct lb_env *env, +@@ -10472,7 +10489,7 @@ static bool update_sd_pick_busiest(struct lb_env *env, case group_asym_packing: /* Prefer to move from lowest priority CPU's work */ @@ -125,10 +689,10 @@ index c49aea8c1025..b61a261ee45b 100644 -- 2.48.1 -From 2796f641fb0fceaca8fabced62e297859b4cbfc4 Mon Sep 17 00:00:00 2001 +From 22e026f1d3db4c42b3187ce5db2bc9d79f11c131 Mon Sep 17 00:00:00 2001 From: Eric Naim -Date: Mon, 3 Feb 2025 12:01:08 +0800 -Subject: [PATCH 2/6] amd-tlb-broadcast +Date: Mon, 10 Feb 2025 09:48:58 +0800 +Subject: [PATCH 2/9] amd-tlb-broadcast Signed-off-by: Eric Naim --- @@ -136,22 +700,21 @@ Signed-off-by: Eric Naim arch/x86/Kconfig.cpu | 5 + arch/x86/hyperv/mmu.c | 1 - arch/x86/include/asm/cpufeatures.h | 1 + - arch/x86/include/asm/invlpgb.h | 107 ++++++ + arch/x86/include/asm/invlpgb.h | 106 +++++ arch/x86/include/asm/mmu.h | 6 + arch/x86/include/asm/mmu_context.h | 14 + arch/x86/include/asm/msr-index.h | 2 + arch/x86/include/asm/paravirt.h | 5 - arch/x86/include/asm/paravirt_types.h | 2 - - arch/x86/include/asm/tlbbatch.h | 1 + - arch/x86/include/asm/tlbflush.h | 93 ++++- + arch/x86/include/asm/tlbflush.h | 98 ++++- arch/x86/kernel/cpu/amd.c | 12 + arch/x86/kernel/kvm.c | 1 - arch/x86/kernel/paravirt.c | 16 - arch/x86/mm/pgtable.c | 27 +- - arch/x86/mm/tlb.c | 481 +++++++++++++++++++++++-- + arch/x86/mm/tlb.c | 518 +++++++++++++++++++++++-- arch/x86/xen/mmu_pv.c | 1 - tools/arch/x86/include/asm/msr-index.h | 2 + - 19 files changed, 685 insertions(+), 94 deletions(-) + 18 files changed, 723 insertions(+), 96 deletions(-) create mode 100644 arch/x86/include/asm/invlpgb.h diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig @@ -214,10 +777,10 @@ index 508c0dad116b..b5c66b7465ba 100644 #define X86_FEATURE_AMD_IBPB (13*32+12) /* Indirect Branch Prediction Barrier */ diff --git a/arch/x86/include/asm/invlpgb.h b/arch/x86/include/asm/invlpgb.h new file mode 100644 -index 000000000000..5fba41671a6d +index 000000000000..9df559974f78 --- /dev/null +++ b/arch/x86/include/asm/invlpgb.h -@@ -0,0 +1,107 @@ +@@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_INVLPGB +#define _ASM_X86_INVLPGB @@ -251,9 +814,8 @@ index 000000000000..5fba41671a6d +} + +/* Wait for INVLPGB originated by this CPU to complete. */ -+static inline void tlbsync(void) ++static inline void __tlbsync(void) +{ -+ cant_migrate(); + /* TLBSYNC: supported in binutils >= 0.36. */ + asm volatile(".byte 0x0f, 0x01, 0xff" ::: "memory"); +} @@ -281,16 +843,16 @@ index 000000000000..5fba41671a6d + unsigned long addr) +{ + __invlpgb(0, pcid, addr, 0, 0, INVLPGB_PCID | INVLPGB_VA); -+ tlbsync(); ++ __tlbsync(); +} + -+static inline void invlpgb_flush_user_nr_nosync(unsigned long pcid, -+ unsigned long addr, -+ u16 nr, -+ bool pmd_stride, -+ bool freed_tables) ++static inline void __invlpgb_flush_user_nr_nosync(unsigned long pcid, ++ unsigned long addr, ++ u16 nr, ++ bool pmd_stride, ++ bool freed_tables) +{ -+ unsigned long flags = INVLPGB_PCID | INVLPGB_VA; ++ u8 flags = INVLPGB_PCID | INVLPGB_VA; + + if (!freed_tables) + flags |= INVLPGB_FINAL_ONLY; @@ -299,7 +861,7 @@ index 000000000000..5fba41671a6d +} + +/* Flush all mappings for a given PCID, not including globals. */ -+static inline void invlpgb_flush_single_pcid_nosync(unsigned long pcid) ++static inline void __invlpgb_flush_single_pcid_nosync(unsigned long pcid) +{ + __invlpgb(0, pcid, 0, 0, 0, INVLPGB_PCID); +} @@ -308,11 +870,11 @@ index 000000000000..5fba41671a6d +static inline void invlpgb_flush_all(void) +{ + __invlpgb(0, 0, 0, 0, 0, INVLPGB_INCLUDE_GLOBAL); -+ tlbsync(); ++ __tlbsync(); +} + +/* Flush addr, including globals, for all PCIDs. */ -+static inline void invlpgb_flush_addr_nosync(unsigned long addr, u16 nr) ++static inline void __invlpgb_flush_addr_nosync(unsigned long addr, u16 nr) +{ + __invlpgb(0, 0, addr, nr - 1, 0, INVLPGB_INCLUDE_GLOBAL); +} @@ -321,7 +883,7 @@ index 000000000000..5fba41671a6d +static inline void invlpgb_flush_all_nonglobals(void) +{ + __invlpgb(0, 0, 0, 0, 0, 0); -+ tlbsync(); ++ __tlbsync(); +} + +#endif /* _ASM_X86_INVLPGB */ @@ -430,20 +992,8 @@ index fea56b04f436..e26633c00455 100644 /* Hook for intercepting the destruction of an mm_struct. */ void (*exit_mmap)(struct mm_struct *mm); void (*notify_page_enc_status_changed)(unsigned long pfn, int npages, bool enc); -diff --git a/arch/x86/include/asm/tlbbatch.h b/arch/x86/include/asm/tlbbatch.h -index 1ad56eb3e8a8..f9a17edf63ad 100644 ---- a/arch/x86/include/asm/tlbbatch.h -+++ b/arch/x86/include/asm/tlbbatch.h -@@ -10,6 +10,7 @@ struct arch_tlbflush_unmap_batch { - * the PFNs being flushed.. - */ - struct cpumask cpumask; -+ bool used_invlpgb; - }; - - #endif /* _ARCH_X86_TLBBATCH_H */ diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h -index 02fc2aa06e9e..f8aaa4bcb4d8 100644 +index 02fc2aa06e9e..bf167e215e8e 100644 --- a/arch/x86/include/asm/tlbflush.h +++ b/arch/x86/include/asm/tlbflush.h @@ -6,10 +6,12 @@ @@ -459,7 +1009,15 @@ index 02fc2aa06e9e..f8aaa4bcb4d8 100644 #include #include #include -@@ -183,6 +185,13 @@ static inline void cr4_init_shadow(void) +@@ -104,6 +106,7 @@ struct tlb_state { + * need to be invalidated. + */ + bool invalidate_other; ++ bool need_tlbsync; + + #ifdef CONFIG_ADDRESS_MASKING + /* +@@ -183,6 +186,13 @@ static inline void cr4_init_shadow(void) extern unsigned long mmu_cr4_features; extern u32 *trampoline_cr4_features; @@ -473,7 +1031,7 @@ index 02fc2aa06e9e..f8aaa4bcb4d8 100644 extern void initialize_tlbstate_and_flush(void); /* -@@ -231,6 +240,78 @@ void flush_tlb_one_kernel(unsigned long addr); +@@ -231,6 +241,82 @@ void flush_tlb_one_kernel(unsigned long addr); void flush_tlb_multi(const struct cpumask *cpumask, const struct flush_tlb_info *info); @@ -491,12 +1049,12 @@ index 02fc2aa06e9e..f8aaa4bcb4d8 100644 + return !is_dyn_asid(asid); +} + -+static inline bool in_asid_transition(const struct flush_tlb_info *info) ++static inline bool in_asid_transition(struct mm_struct *mm) +{ + if (!cpu_feature_enabled(X86_FEATURE_INVLPGB)) + return false; + -+ return info->mm && READ_ONCE(info->mm->context.asid_transition); ++ return mm && READ_ONCE(mm->context.asid_transition); +} + +static inline u16 mm_global_asid(struct mm_struct *mm) @@ -524,7 +1082,7 @@ index 02fc2aa06e9e..f8aaa4bcb4d8 100644 + return false; +} + -+static inline bool in_asid_transition(const struct flush_tlb_info *info) ++static inline bool in_asid_transition(struct mm_struct *mm) +{ + return false; +} @@ -547,12 +1105,16 @@ index 02fc2aa06e9e..f8aaa4bcb4d8 100644 +static inline void consider_global_asid(struct mm_struct *mm) +{ +} ++ ++static inline void tlbsync(void) ++{ ++} +#endif + #ifdef CONFIG_PARAVIRT #include #endif -@@ -278,21 +359,15 @@ static inline u64 inc_mm_tlb_gen(struct mm_struct *mm) +@@ -278,21 +364,15 @@ static inline u64 inc_mm_tlb_gen(struct mm_struct *mm) return atomic64_inc_return(&mm->context.tlb_gen); } @@ -661,7 +1223,7 @@ index 1ccaa3397a67..2aa251d0b308 100644 .mmu.exit_mmap = paravirt_nop, .mmu.notify_page_enc_status_changed = paravirt_nop, diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c -index 1fef5ad32d5a..8d1d228b096c 100644 +index 1fef5ad32d5a..b1c1f72c1fd1 100644 --- a/arch/x86/mm/pgtable.c +++ b/arch/x86/mm/pgtable.c @@ -18,25 +18,6 @@ EXPORT_SYMBOL(physical_mask); @@ -695,7 +1257,7 @@ index 1fef5ad32d5a..8d1d228b096c 100644 { paravirt_release_pte(page_to_pfn(pte)); - paravirt_tlb_remove_table(tlb, page_ptdesc(pte)); -+ tlb_remove_table(tlb, pte); ++ tlb_remove_table(tlb, page_ptdesc(pte)); } #if CONFIG_PGTABLE_LEVELS > 2 @@ -712,7 +1274,7 @@ index 1fef5ad32d5a..8d1d228b096c 100644 { paravirt_release_pud(__pa(pud) >> PAGE_SHIFT); - paravirt_tlb_remove_table(tlb, virt_to_ptdesc(pud)); -+ tlb_remove_table(tlb, virt_to_page(pud)); ++ tlb_remove_table(tlb, virt_to_ptdesc(pud)); } #if CONFIG_PGTABLE_LEVELS > 4 @@ -720,12 +1282,12 @@ index 1fef5ad32d5a..8d1d228b096c 100644 { paravirt_release_p4d(__pa(p4d) >> PAGE_SHIFT); - paravirt_tlb_remove_table(tlb, virt_to_ptdesc(p4d)); -+ tlb_remove_table(tlb, virt_to_page(p4d)); ++ tlb_remove_table(tlb, virt_to_ptdesc(p4d)); } #endif /* CONFIG_PGTABLE_LEVELS > 4 */ #endif /* CONFIG_PGTABLE_LEVELS > 3 */ diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c -index 6cf881a942bb..682da8d0d1c9 100644 +index 6cf881a942bb..b9aa5ab1b1af 100644 --- a/arch/x86/mm/tlb.c +++ b/arch/x86/mm/tlb.c @@ -74,13 +74,15 @@ @@ -768,7 +1330,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 if (this_cpu_read(cpu_tlbstate.invalidate_other)) clear_asid_other(); -@@ -251,6 +267,272 @@ static void choose_new_asid(struct mm_struct *next, u64 next_tlb_gen, +@@ -251,6 +267,304 @@ static void choose_new_asid(struct mm_struct *next, u64 next_tlb_gen, *need_flush = true; } @@ -993,6 +1555,38 @@ index 6cf881a942bb..682da8d0d1c9 100644 + WRITE_ONCE(mm->context.asid_transition, false); +} + ++static inline void tlbsync(void) ++{ ++ if (!this_cpu_read(cpu_tlbstate.need_tlbsync)) ++ return; ++ __tlbsync(); ++ this_cpu_write(cpu_tlbstate.need_tlbsync, false); ++} ++ ++static inline void invlpgb_flush_user_nr_nosync(unsigned long pcid, ++ unsigned long addr, ++ u16 nr, bool pmd_stride, ++ bool freed_tables) ++{ ++ __invlpgb_flush_user_nr_nosync(pcid, addr, nr, pmd_stride, freed_tables); ++ if (!this_cpu_read(cpu_tlbstate.need_tlbsync)) ++ this_cpu_write(cpu_tlbstate.need_tlbsync, true); ++} ++ ++static inline void invlpgb_flush_single_pcid_nosync(unsigned long pcid) ++{ ++ __invlpgb_flush_single_pcid_nosync(pcid); ++ if (!this_cpu_read(cpu_tlbstate.need_tlbsync)) ++ this_cpu_write(cpu_tlbstate.need_tlbsync, true); ++} ++ ++static inline void invlpgb_flush_addr_nosync(unsigned long addr, u16 nr) ++{ ++ __invlpgb_flush_addr_nosync(addr, nr); ++ if (!this_cpu_read(cpu_tlbstate.need_tlbsync)) ++ this_cpu_write(cpu_tlbstate.need_tlbsync, true); ++} ++ +static void broadcast_tlb_flush(struct flush_tlb_info *info) +{ + bool pmd = info->stride_shift == PMD_SHIFT; @@ -1041,7 +1635,16 @@ index 6cf881a942bb..682da8d0d1c9 100644 /* * Given an ASID, flush the corresponding user ASID. We can delay this * until the next time we switch to it. -@@ -556,8 +838,9 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, +@@ -512,6 +826,8 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, + if (IS_ENABLED(CONFIG_PROVE_LOCKING)) + WARN_ON_ONCE(!irqs_disabled()); + ++ tlbsync(); ++ + /* + * Verify that CR3 is what we think it is. This will catch + * hypothetical buggy code that directly switches to swapper_pg_dir +@@ -556,8 +872,9 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, */ if (prev == next) { /* Not actually switching mm's */ @@ -1053,7 +1656,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 /* * If this races with another thread that enables lam, 'new_lam' -@@ -573,6 +856,23 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, +@@ -573,6 +890,23 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, !cpumask_test_cpu(cpu, mm_cpumask(next)))) cpumask_set_cpu(cpu, mm_cpumask(next)); @@ -1077,7 +1680,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 /* * If the CPU is not in lazy TLB mode, we are just switching * from one thread in a process to another thread in the same -@@ -606,6 +906,13 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, +@@ -606,6 +940,13 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, */ cond_mitigation(tsk); @@ -1091,7 +1694,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 /* * Leave this CPU in prev's mm_cpumask. Atomic writes to * mm_cpumask can be expensive under contention. The CPU -@@ -620,14 +927,12 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, +@@ -620,14 +961,12 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, next_tlb_gen = atomic64_read(&next->context.tlb_gen); choose_new_asid(next, next_tlb_gen, &new_asid, &need_flush); @@ -1108,7 +1711,16 @@ index 6cf881a942bb..682da8d0d1c9 100644 this_cpu_write(cpu_tlbstate.ctxs[new_asid].ctx_id, next->context.ctx_id); this_cpu_write(cpu_tlbstate.ctxs[new_asid].tlb_gen, next_tlb_gen); load_new_mm_cr3(next->pgd, new_asid, new_lam, true); -@@ -746,7 +1051,7 @@ static void flush_tlb_func(void *info) +@@ -668,6 +1007,8 @@ void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next, + */ + void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk) + { ++ tlbsync(); ++ + if (this_cpu_read(cpu_tlbstate.loaded_mm) == &init_mm) + return; + +@@ -746,7 +1087,7 @@ static void flush_tlb_func(void *info) const struct flush_tlb_info *f = info; struct mm_struct *loaded_mm = this_cpu_read(cpu_tlbstate.loaded_mm); u32 loaded_mm_asid = this_cpu_read(cpu_tlbstate.loaded_mm_asid); @@ -1117,7 +1729,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 bool local = smp_processor_id() == f->initiating_cpu; unsigned long nr_invalidate = 0; u64 mm_tlb_gen; -@@ -769,6 +1074,16 @@ static void flush_tlb_func(void *info) +@@ -769,6 +1110,16 @@ static void flush_tlb_func(void *info) if (unlikely(loaded_mm == &init_mm)) return; @@ -1134,7 +1746,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 VM_WARN_ON(this_cpu_read(cpu_tlbstate.ctxs[loaded_mm_asid].ctx_id) != loaded_mm->context.ctx_id); -@@ -786,6 +1101,8 @@ static void flush_tlb_func(void *info) +@@ -786,6 +1137,8 @@ static void flush_tlb_func(void *info) return; } @@ -1143,19 +1755,39 @@ index 6cf881a942bb..682da8d0d1c9 100644 if (unlikely(f->new_tlb_gen != TLB_GENERATION_INVALID && f->new_tlb_gen <= local_tlb_gen)) { /* -@@ -953,7 +1270,7 @@ STATIC_NOPV void native_flush_tlb_multi(const struct cpumask *cpumask, +@@ -953,7 +1306,7 @@ STATIC_NOPV void native_flush_tlb_multi(const struct cpumask *cpumask, * up on the new contents of what used to be page tables, while * doing a speculative memory access. */ - if (info->freed_tables) -+ if (info->freed_tables || in_asid_transition(info)) ++ if (info->freed_tables || in_asid_transition(info->mm)) on_each_cpu_mask(cpumask, flush_tlb_func, (void *)info, true); else on_each_cpu_cond_mask(should_flush_tlb, flush_tlb_func, -@@ -1009,6 +1326,15 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, +@@ -1000,8 +1353,13 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, + BUG_ON(this_cpu_inc_return(flush_tlb_info_idx) != 1); + #endif + +- info->start = start; +- info->end = end; ++ /* ++ * Round the start and end addresses to the page size specified ++ * by the stride shift. This ensures partial pages at the end of ++ * a range get fully invalidated. ++ */ ++ info->start = round_down(start, 1 << stride_shift); ++ info->end = round_up(end, 1 << stride_shift); + info->mm = mm; + info->stride_shift = stride_shift; + info->freed_tables = freed_tables; +@@ -1009,6 +1367,19 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, info->initiating_cpu = smp_processor_id(); info->trim_cpumask = 0; ++ WARN_ONCE(start != info->start || end != info->end, ++ "TLB flush not stride %x aligned. Start %lx, end %lx\n", ++ 1 << stride_shift, start, end); ++ + /* + * If the number of flushes is so large that a full flush + * would be faster, do a full flush. @@ -1168,7 +1800,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 return info; } -@@ -1026,17 +1352,8 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, +@@ -1026,17 +1397,8 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, bool freed_tables) { struct flush_tlb_info *info; @@ -1187,7 +1819,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 /* This is also a barrier that synchronizes with switch_mm(). */ new_tlb_gen = inc_mm_tlb_gen(mm); -@@ -1049,9 +1366,12 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, +@@ -1049,9 +1411,12 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, * a local TLB flush is needed. Optimize this use-case by calling * flush_tlb_func_local() directly in this case. */ @@ -1201,7 +1833,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 } else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) { lockdep_assert_irqs_enabled(); local_irq_disable(); -@@ -1065,6 +1385,19 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, +@@ -1065,6 +1430,19 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, } @@ -1221,7 +1853,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 static void do_flush_tlb_all(void *info) { count_vm_tlb_event(NR_TLB_REMOTE_FLUSH_RECEIVED); -@@ -1073,10 +1406,36 @@ static void do_flush_tlb_all(void *info) +@@ -1073,10 +1451,36 @@ static void do_flush_tlb_all(void *info) void flush_tlb_all(void) { @@ -1258,7 +1890,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 static void do_kernel_range_flush(void *info) { struct flush_tlb_info *f = info; -@@ -1089,22 +1448,21 @@ static void do_kernel_range_flush(void *info) +@@ -1089,22 +1493,21 @@ static void do_kernel_range_flush(void *info) void flush_tlb_kernel_range(unsigned long start, unsigned long end) { @@ -1269,15 +1901,15 @@ index 6cf881a942bb..682da8d0d1c9 100644 - } else { - struct flush_tlb_info *info; + struct flush_tlb_info *info; -+ -+ guard(preempt)(); - preempt_disable(); - info = get_flush_tlb_info(NULL, start, end, 0, false, - TLB_GENERATION_INVALID); ++ guard(preempt)(); + + info = get_flush_tlb_info(NULL, start, end, PAGE_SHIFT, false, + TLB_GENERATION_INVALID); - ++ + if (broadcast_kernel_range_flush(info)) + ; /* Fall through. */ + else if (info->end == TLB_FLUSH_ALL) @@ -1292,7 +1924,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 } /* -@@ -1276,7 +1634,7 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) +@@ -1276,7 +1679,7 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) int cpu = get_cpu(); @@ -1301,7 +1933,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 TLB_GENERATION_INVALID); /* * flush_tlb_multi() is not optimized for the common case in which only -@@ -1292,12 +1650,65 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) +@@ -1292,12 +1695,53 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) local_irq_enable(); } @@ -1310,11 +1942,8 @@ index 6cf881a942bb..682da8d0d1c9 100644 + * The cpumask above contains only CPUs that were running tasks + * not using broadcast TLB flushing. + */ -+ if (cpu_feature_enabled(X86_FEATURE_INVLPGB) && batch->used_invlpgb) { ++ if (cpu_feature_enabled(X86_FEATURE_INVLPGB)) + tlbsync(); -+ migrate_enable(); -+ batch->used_invlpgb = false; -+ } + cpumask_clear(&batch->cpumask); @@ -1329,15 +1958,6 @@ index 6cf881a942bb..682da8d0d1c9 100644 + u16 asid = mm_global_asid(mm); + + if (asid) { -+ /* -+ * Queue up an asynchronous invalidation. The corresponding -+ * TLBSYNC is done in arch_tlbbatch_flush(), and must be done -+ * on the same CPU. -+ */ -+ if (!batch->used_invlpgb) { -+ batch->used_invlpgb = true; -+ migrate_disable(); -+ } + invlpgb_flush_user_nr_nosync(kern_pcid(asid), uaddr, 1, false, false); + /* Do any CPUs supporting INVLPGB need PTI? */ + if (static_cpu_has(X86_FEATURE_PTI)) @@ -1352,7 +1972,7 @@ index 6cf881a942bb..682da8d0d1c9 100644 + * TLB invalidation, and send IPIs. The IPIs will help + * stragglers transition to the broadcast ASID. + */ -+ if (READ_ONCE(mm->context.asid_transition)) ++ if (in_asid_transition(mm)) + asid = 0; + } + @@ -1402,10 +2022,6331 @@ index 3ae84c3b8e6d..dc1c1057f26e 100644 -- 2.48.1 -From bb9024da7b3d8a2203bf5c444c461a951519025f Mon Sep 17 00:00:00 2001 +From bb53b6f3a6bce80dfd97efc1bc956f8591063d50 Mon Sep 17 00:00:00 2001 From: Eric Naim -Date: Mon, 3 Feb 2025 11:04:53 +0800 -Subject: [PATCH 3/6] bbr3 +Date: Mon, 10 Feb 2025 09:49:33 +0800 +Subject: [PATCH 3/9] asus + +Signed-off-by: Eric Naim +--- + .../ABI/testing/sysfs-platform-asus-wmi | 17 + + .../display/dc/dml2/dml2_translation_helper.c | 2 +- + drivers/hid/Kconfig | 9 + + drivers/hid/Makefile | 1 + + drivers/hid/hid-asus-ally.c | 2256 +++++++++++++++++ + drivers/hid/hid-asus-ally.h | 398 +++ + drivers/hid/hid-asus.c | 21 +- + drivers/hid/hid-ids.h | 1 + + drivers/platform/x86/Kconfig | 21 + + drivers/platform/x86/Makefile | 1 + + drivers/platform/x86/asus-armoury.c | 1153 +++++++++ + drivers/platform/x86/asus-armoury.h | 1231 +++++++++ + drivers/platform/x86/asus-wmi.c | 381 ++- + drivers/platform/x86/asus-wmi.h | 33 + + include/linux/platform_data/x86/asus-wmi.h | 24 + + 15 files changed, 5436 insertions(+), 113 deletions(-) + create mode 100644 drivers/hid/hid-asus-ally.c + create mode 100644 drivers/hid/hid-asus-ally.h + create mode 100644 drivers/platform/x86/asus-armoury.c + create mode 100644 drivers/platform/x86/asus-armoury.h + +diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi +index 28144371a0f1..765d50b0d9df 100644 +--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi ++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi +@@ -63,6 +63,7 @@ Date: Aug 2022 + KernelVersion: 6.1 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Switch the GPU hardware MUX mode. Laptops with this feature can + can be toggled to boot with only the dGPU (discrete mode) or in + standard Optimus/Hybrid mode. On switch a reboot is required: +@@ -75,6 +76,7 @@ Date: Aug 2022 + KernelVersion: 5.17 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Disable discrete GPU: + * 0 - Enable dGPU, + * 1 - Disable dGPU +@@ -84,6 +86,7 @@ Date: Aug 2022 + KernelVersion: 5.17 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Enable the external GPU paired with ROG X-Flow laptops. + Toggling this setting will also trigger ACPI to disable the dGPU: + +@@ -95,6 +98,7 @@ Date: Aug 2022 + KernelVersion: 5.17 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Enable an LCD response-time boost to reduce or remove ghosting: + * 0 - Disable, + * 1 - Enable +@@ -104,6 +108,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Get the current charging mode being used: + * 1 - Barrel connected charger, + * 2 - USB-C charging +@@ -114,6 +119,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Show if the egpu (XG Mobile) is correctly connected: + * 0 - False, + * 1 - True +@@ -123,6 +129,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Change the mini-LED mode: + * 0 - Single-zone, + * 1 - Multi-zone +@@ -133,6 +140,7 @@ Date: Apr 2024 + KernelVersion: 6.10 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + List the available mini-led modes. + + What: /sys/devices/platform//ppt_pl1_spl +@@ -140,6 +148,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD. + Shown on Intel+Nvidia or AMD+Nvidia based systems: + +@@ -150,6 +159,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT, + on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems: + +@@ -160,6 +170,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only: + * min=5, max=250 + +@@ -168,6 +179,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set the APU SPPT limit. Shown on full AMD systems only: + * min=5, max=130 + +@@ -176,6 +188,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set the platform SPPT limit. Shown on full AMD systems only: + * min=5, max=130 + +@@ -184,6 +197,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set the dynamic boost limit of the Nvidia dGPU: + * min=5, max=25 + +@@ -192,6 +206,7 @@ Date: Jun 2023 + KernelVersion: 6.5 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set the target temperature limit of the Nvidia dGPU: + * min=75, max=87 + +@@ -200,6 +215,7 @@ Date: Apr 2024 + KernelVersion: 6.10 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set if the BIOS POST sound is played on boot. + * 0 - False, + * 1 - True +@@ -209,6 +225,7 @@ Date: Apr 2024 + KernelVersion: 6.10 + Contact: "Luke Jones" + Description: ++ DEPRECATED, WILL BE REMOVED SOON + Set if the MCU can go in to low-power mode on system sleep + * 0 - False, + * 1 - True +diff --git a/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c +index b8a34abaf519..ac4692d1b54f 100644 +--- a/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c ++++ b/drivers/gpu/drm/amd/display/dc/dml2/dml2_translation_helper.c +@@ -892,7 +892,7 @@ static void populate_dummy_dml_surface_cfg(struct dml_surface_cfg_st *out, unsig + out->SurfaceWidthC[location] = in->timing.h_addressable; + out->SurfaceHeightC[location] = in->timing.v_addressable; + out->PitchY[location] = ((out->SurfaceWidthY[location] + 127) / 128) * 128; +- out->PitchC[location] = 0; ++ out->PitchC[location] = 1; + out->DCCEnable[location] = false; + out->DCCMetaPitchY[location] = 0; + out->DCCMetaPitchC[location] = 0; +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index b53eb569bd49..ced5e52d27a5 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -164,6 +164,15 @@ config HID_ASUS + - GL553V series + - GL753V series + ++config HID_ASUS_ALLY ++ tristate "Asus Ally gamepad configuration support" ++ depends on USB_HID ++ depends on LEDS_CLASS ++ depends on LEDS_CLASS_MULTICOLOR ++ select POWER_SUPPLY ++ help ++ Support for configuring the Asus ROG Ally gamepad using attributes. ++ + config HID_AUREAL + tristate "Aureal" + help +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 482b096eea28..7aba738d9355 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -31,6 +31,7 @@ obj-$(CONFIG_HID_APPLE) += hid-apple.o + obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o + obj-$(CONFIG_HID_CREATIVE_SB0540) += hid-creative-sb0540.o + obj-$(CONFIG_HID_ASUS) += hid-asus.o ++obj-$(CONFIG_HID_ASUS_ALLY) += hid-asus-ally.o + obj-$(CONFIG_HID_AUREAL) += hid-aureal.o + obj-$(CONFIG_HID_BELKIN) += hid-belkin.o + obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o +diff --git a/drivers/hid/hid-asus-ally.c b/drivers/hid/hid-asus-ally.c +new file mode 100644 +index 000000000000..c10121ebcbb8 +--- /dev/null ++++ b/drivers/hid/hid-asus-ally.c +@@ -0,0 +1,2256 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * HID driver for Asus ROG laptops and Ally ++ * ++ * Copyright (c) 2023 Luke Jones ++ */ ++ ++#include "linux/compiler_attributes.h" ++#include "linux/device.h" ++#include ++#include ++#include "linux/pm.h" ++#include "linux/printk.h" ++#include "linux/slab.h" ++#include ++#include ++#include ++#include ++#include ++ ++#include "hid-ids.h" ++#include "hid-asus-ally.h" ++ ++#define DEBUG ++ ++#define READY_MAX_TRIES 3 ++#define FEATURE_REPORT_ID 0x0d ++#define FEATURE_ROG_ALLY_REPORT_ID 0x5a ++#define FEATURE_ROG_ALLY_CODE_PAGE 0xD1 ++#define FEATURE_ROG_ALLY_REPORT_SIZE 64 ++#define ALLY_X_INPUT_REPORT_USB 0x0B ++#define ALLY_X_INPUT_REPORT_USB_SIZE 16 ++ ++#define ROG_ALLY_REPORT_SIZE 64 ++#define ROG_ALLY_X_MIN_MCU 313 ++#define ROG_ALLY_MIN_MCU 319 ++ ++#define ROG_ALLY_CFG_INTF_IN 0x83 ++#define ROG_ALLY_CFG_INTF_OUT 0x04 ++#define ROG_ALLY_X_INTF_IN 0x87 ++ ++#define FEATURE_KBD_LED_REPORT_ID1 0x5d ++#define FEATURE_KBD_LED_REPORT_ID2 0x5e ++ ++#define BTN_DATA_LEN 11; ++#define BTN_CODE_BYTES_LEN 8 ++ ++static const u8 EC_INIT_STRING[] = { 0x5A, 'A', 'S', 'U', 'S', ' ', 'T', 'e','c', 'h', '.', 'I', 'n', 'c', '.', '\0' }; ++static const u8 EC_MODE_LED_APPLY[] = { 0x5A, 0xB4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; ++static const u8 EC_MODE_LED_SET[] = { 0x5A, 0xB5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; ++static const u8 FORCE_FEEDBACK_OFF[] = { 0x0D, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; ++ ++static const struct hid_device_id rog_ally_devices[] = { ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X) }, ++ {} ++}; ++ ++struct btn_code_map { ++ u64 code; ++ const char *name; ++}; ++ ++static const struct btn_code_map ally_btn_codes[] = { ++ { 0, "NONE" }, ++ /* Gamepad button codes */ ++ { BTN_PAD_A, "PAD_A" }, ++ { BTN_PAD_B, "PAD_B" }, ++ { BTN_PAD_X, "PAD_X" }, ++ { BTN_PAD_Y, "PAD_Y" }, ++ { BTN_PAD_LB, "PAD_LB" }, ++ { BTN_PAD_RB, "PAD_RB" }, ++ { BTN_PAD_LS, "PAD_LS" }, ++ { BTN_PAD_RS, "PAD_RS" }, ++ { BTN_PAD_DPAD_UP, "PAD_DPAD_UP" }, ++ { BTN_PAD_DPAD_DOWN, "PAD_DPAD_DOWN" }, ++ { BTN_PAD_DPAD_LEFT, "PAD_DPAD_LEFT" }, ++ { BTN_PAD_DPAD_RIGHT, "PAD_DPAD_RIGHT" }, ++ { BTN_PAD_VIEW, "PAD_VIEW" }, ++ { BTN_PAD_MENU, "PAD_MENU" }, ++ { BTN_PAD_XBOX, "PAD_XBOX" }, ++ ++ /* Triggers mapped to keyboard codes */ ++ { BTN_KB_M2, "KB_M2" }, ++ { BTN_KB_M1, "KB_M1" }, ++ { BTN_KB_ESC, "KB_ESC" }, ++ { BTN_KB_F1, "KB_F1" }, ++ { BTN_KB_F2, "KB_F2" }, ++ { BTN_KB_F3, "KB_F3" }, ++ { BTN_KB_F4, "KB_F4" }, ++ { BTN_KB_F5, "KB_F5" }, ++ { BTN_KB_F6, "KB_F6" }, ++ { BTN_KB_F7, "KB_F7" }, ++ { BTN_KB_F8, "KB_F8" }, ++ { BTN_KB_F9, "KB_F9" }, ++ { BTN_KB_F10, "KB_F10" }, ++ { BTN_KB_F11, "KB_F11" }, ++ { BTN_KB_F12, "KB_F12" }, ++ { BTN_KB_F14, "KB_F14" }, ++ { BTN_KB_F15, "KB_F15" }, ++ { BTN_KB_BACKTICK, "KB_BACKTICK" }, ++ { BTN_KB_1, "KB_1" }, ++ { BTN_KB_2, "KB_2" }, ++ { BTN_KB_3, "KB_3" }, ++ { BTN_KB_4, "KB_4" }, ++ { BTN_KB_5, "KB_5" }, ++ { BTN_KB_6, "KB_6" }, ++ { BTN_KB_7, "KB_7" }, ++ { BTN_KB_8, "KB_8" }, ++ { BTN_KB_9, "KB_9" }, ++ { BTN_KB_0, "KB_0" }, ++ { BTN_KB_HYPHEN, "KB_HYPHEN" }, ++ { BTN_KB_EQUALS, "KB_EQUALS" }, ++ { BTN_KB_BACKSPACE, "KB_BACKSPACE" }, ++ { BTN_KB_TAB, "KB_TAB" }, ++ { BTN_KB_Q, "KB_Q" }, ++ { BTN_KB_W, "KB_W" }, ++ { BTN_KB_E, "KB_E" }, ++ { BTN_KB_R, "KB_R" }, ++ { BTN_KB_T, "KB_T" }, ++ { BTN_KB_Y, "KB_Y" }, ++ { BTN_KB_U, "KB_U" }, ++ { BTN_KB_O, "KB_O" }, ++ { BTN_KB_P, "KB_P" }, ++ { BTN_KB_LBRACKET, "KB_LBRACKET" }, ++ { BTN_KB_RBRACKET, "KB_RBRACKET" }, ++ { BTN_KB_BACKSLASH, "KB_BACKSLASH" }, ++ { BTN_KB_CAPS, "KB_CAPS" }, ++ { BTN_KB_A, "KB_A" }, ++ { BTN_KB_S, "KB_S" }, ++ { BTN_KB_D, "KB_D" }, ++ { BTN_KB_F, "KB_F" }, ++ { BTN_KB_G, "KB_G" }, ++ { BTN_KB_H, "KB_H" }, ++ { BTN_KB_J, "KB_J" }, ++ { BTN_KB_K, "KB_K" }, ++ { BTN_KB_L, "KB_L" }, ++ { BTN_KB_SEMI, "KB_SEMI" }, ++ { BTN_KB_QUOTE, "KB_QUOTE" }, ++ { BTN_KB_RET, "KB_RET" }, ++ { BTN_KB_LSHIFT, "KB_LSHIFT" }, ++ { BTN_KB_Z, "KB_Z" }, ++ { BTN_KB_X, "KB_X" }, ++ { BTN_KB_C, "KB_C" }, ++ { BTN_KB_V, "KB_V" }, ++ { BTN_KB_B, "KB_B" }, ++ { BTN_KB_N, "KB_N" }, ++ { BTN_KB_M, "KB_M" }, ++ { BTN_KB_COMMA, "KB_COMMA" }, ++ { BTN_KB_PERIOD, "KB_PERIOD" }, ++ { BTN_KB_RSHIFT, "KB_RSHIFT" }, ++ { BTN_KB_LCTL, "KB_LCTL" }, ++ { BTN_KB_META, "KB_META" }, ++ { BTN_KB_LALT, "KB_LALT" }, ++ { BTN_KB_SPACE, "KB_SPACE" }, ++ { BTN_KB_RALT, "KB_RALT" }, ++ { BTN_KB_MENU, "KB_MENU" }, ++ { BTN_KB_RCTL, "KB_RCTL" }, ++ { BTN_KB_PRNTSCN, "KB_PRNTSCN" }, ++ { BTN_KB_SCRLCK, "KB_SCRLCK" }, ++ { BTN_KB_PAUSE, "KB_PAUSE" }, ++ { BTN_KB_INS, "KB_INS" }, ++ { BTN_KB_HOME, "KB_HOME" }, ++ { BTN_KB_PGUP, "KB_PGUP" }, ++ { BTN_KB_DEL, "KB_DEL" }, ++ { BTN_KB_END, "KB_END" }, ++ { BTN_KB_PGDWN, "KB_PGDWN" }, ++ { BTN_KB_UP_ARROW, "KB_UP_ARROW" }, ++ { BTN_KB_DOWN_ARROW, "KB_DOWN_ARROW" }, ++ { BTN_KB_LEFT_ARROW, "KB_LEFT_ARROW" }, ++ { BTN_KB_RIGHT_ARROW, "KB_RIGHT_ARROW" }, ++ ++ /* Numpad mappings */ ++ { BTN_NUMPAD_LOCK, "NUMPAD_LOCK" }, ++ { BTN_NUMPAD_FWDSLASH, "NUMPAD_FWDSLASH" }, ++ { BTN_NUMPAD_ASTERISK, "NUMPAD_ASTERISK" }, ++ { BTN_NUMPAD_HYPHEN, "NUMPAD_HYPHEN" }, ++ { BTN_NUMPAD_0, "NUMPAD_0" }, ++ { BTN_NUMPAD_1, "NUMPAD_1" }, ++ { BTN_NUMPAD_2, "NUMPAD_2" }, ++ { BTN_NUMPAD_3, "NUMPAD_3" }, ++ { BTN_NUMPAD_4, "NUMPAD_4" }, ++ { BTN_NUMPAD_5, "NUMPAD_5" }, ++ { BTN_NUMPAD_6, "NUMPAD_6" }, ++ { BTN_NUMPAD_7, "NUMPAD_7" }, ++ { BTN_NUMPAD_8, "NUMPAD_8" }, ++ { BTN_NUMPAD_9, "NUMPAD_9" }, ++ { BTN_NUMPAD_PLUS, "NUMPAD_PLUS" }, ++ { BTN_NUMPAD_ENTER, "NUMPAD_ENTER" }, ++ { BTN_NUMPAD_PERIOD, "NUMPAD_PERIOD" }, ++ ++ /* Mouse mappings */ ++ { BTN_MOUSE_LCLICK, "MOUSE_LCLICK" }, ++ { BTN_MOUSE_RCLICK, "MOUSE_RCLICK" }, ++ { BTN_MOUSE_MCLICK, "MOUSE_MCLICK" }, ++ { BTN_MOUSE_WHEEL_UP, "MOUSE_WHEEL_UP" }, ++ { BTN_MOUSE_WHEEL_DOWN, "MOUSE_WHEEL_DOWN" }, ++ ++ /* Media mappings */ ++ { BTN_MEDIA_SCREENSHOT, "MEDIA_SCREENSHOT" }, ++ { BTN_MEDIA_SHOW_KEYBOARD, "MEDIA_SHOW_KEYBOARD" }, ++ { BTN_MEDIA_SHOW_DESKTOP, "MEDIA_SHOW_DESKTOP" }, ++ { BTN_MEDIA_START_RECORDING, "MEDIA_START_RECORDING" }, ++ { BTN_MEDIA_MIC_OFF, "MEDIA_MIC_OFF" }, ++ { BTN_MEDIA_VOL_DOWN, "MEDIA_VOL_DOWN" }, ++ { BTN_MEDIA_VOL_UP, "MEDIA_VOL_UP" }, ++}; ++static const size_t keymap_len = ARRAY_SIZE(ally_btn_codes); ++ ++/* byte_array must be >= 8 in length */ ++static void btn_code_to_byte_array(u64 keycode, u8 *byte_array) ++{ ++ /* Convert the u64 to bytes[8] */ ++ for (int i = 0; i < 8; ++i) { ++ byte_array[i] = (keycode >> (56 - 8 * i)) & 0xFF; ++ } ++} ++ ++static u64 name_to_btn(const char *name) ++{ ++ int len = strcspn(name, "\n"); ++ for (size_t i = 0; i < keymap_len; ++i) { ++ if (strncmp(ally_btn_codes[i].name, name, len) == 0) { ++ return ally_btn_codes[i].code; ++ } ++ } ++ return -EINVAL; ++} ++ ++static const char* btn_to_name(u64 key) ++{ ++ for (size_t i = 0; i < keymap_len; ++i) { ++ if (ally_btn_codes[i].code == key) { ++ return ally_btn_codes[i].name; ++ } ++ } ++ return NULL; ++} ++ ++struct btn_data { ++ u64 button; ++ u64 macro; ++ bool turbo; ++}; ++ ++struct btn_mapping { ++ struct btn_data btn_a; ++ struct btn_data btn_b; ++ struct btn_data btn_x; ++ struct btn_data btn_y; ++ struct btn_data btn_lb; ++ struct btn_data btn_rb; ++ struct btn_data btn_ls; ++ struct btn_data btn_rs; ++ struct btn_data btn_lt; ++ struct btn_data btn_rt; ++ struct btn_data dpad_up; ++ struct btn_data dpad_down; ++ struct btn_data dpad_left; ++ struct btn_data dpad_right; ++ struct btn_data btn_view; ++ struct btn_data btn_menu; ++ struct btn_data btn_m1; ++ struct btn_data btn_m2; ++}; ++ ++struct deadzone { ++ u8 inner; ++ u8 outer; ++}; ++ ++struct response_curve { ++ uint8_t move_pct_1; ++ uint8_t response_pct_1; ++ uint8_t move_pct_2; ++ uint8_t response_pct_2; ++ uint8_t move_pct_3; ++ uint8_t response_pct_3; ++ uint8_t move_pct_4; ++ uint8_t response_pct_4; ++} __packed; ++ ++struct js_axis_calibrations { ++ uint16_t left_y_stable; ++ uint16_t left_y_min; ++ uint16_t left_y_max; ++ uint16_t left_x_stable; ++ uint16_t left_x_min; ++ uint16_t left_x_max; ++ uint16_t right_y_stable; ++ uint16_t right_y_min; ++ uint16_t right_y_max; ++ uint16_t right_x_stable; ++ uint16_t right_x_min; ++ uint16_t right_x_max; ++} __packed; ++ ++struct tr_axis_calibrations { ++ uint16_t left_stable; ++ uint16_t left_max; ++ uint16_t right_stable; ++ uint16_t right_max; ++} __packed; ++ ++/* ROG Ally has many settings related to the gamepad, all using the same n-key endpoint */ ++struct ally_gamepad_cfg { ++ struct hid_device *hdev; ++ struct input_dev *input; ++ ++ enum xpad_mode mode; ++ /* ++ * index: [mode] ++ */ ++ struct btn_mapping key_mapping[xpad_mode_mouse]; ++ /* ++ * index: left, right ++ * max: 64 ++ */ ++ u8 vibration_intensity[2]; ++ ++ /* deadzones */ ++ struct deadzone ls_dz; // left stick ++ struct deadzone rs_dz; // right stick ++ struct deadzone lt_dz; // left trigger ++ struct deadzone rt_dz; // right trigger ++ /* anti-deadzones */ ++ u8 ls_adz; // left stick ++ u8 rs_adz; // right stick ++ /* joystick response curves */ ++ struct response_curve ls_rc; ++ struct response_curve rs_rc; ++ ++ struct js_axis_calibrations js_cal; ++ struct tr_axis_calibrations tr_cal; ++}; ++ ++/* The hatswitch outputs integers, we use them to index this X|Y pair */ ++static const int hat_values[][2] = { ++ { 0, 0 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, ++ { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, ++}; ++ ++/* rumble packet structure */ ++struct ff_data { ++ u8 enable; ++ u8 magnitude_left; ++ u8 magnitude_right; ++ u8 magnitude_strong; ++ u8 magnitude_weak; ++ u8 pulse_sustain_10ms; ++ u8 pulse_release_10ms; ++ u8 loop_count; ++} __packed; ++ ++struct ff_report { ++ u8 report_id; ++ struct ff_data ff; ++} __packed; ++ ++struct ally_x_input_report { ++ uint16_t x, y; ++ uint16_t rx, ry; ++ uint16_t z, rz; ++ uint8_t buttons[4]; ++} __packed; ++ ++struct ally_x_device { ++ struct input_dev *input; ++ struct hid_device *hdev; ++ spinlock_t lock; ++ ++ struct ff_report *ff_packet; ++ struct work_struct output_worker; ++ bool output_worker_initialized; ++ /* Prevent multiple queued event due to the enforced delay in worker */ ++ bool update_qam_btn; ++ /* Set if the QAM and AC buttons emit Xbox and Xbox+A */ ++ bool qam_btns_steam_mode; ++ bool update_ff; ++}; ++ ++struct ally_rgb_dev { ++ struct hid_device *hdev; ++ struct led_classdev_mc led_rgb_dev; ++ struct work_struct work; ++ bool output_worker_initialized; ++ spinlock_t lock; ++ ++ bool removed; ++ bool update_rgb; ++ uint8_t red[4]; ++ uint8_t green[4]; ++ uint8_t blue[4]; ++}; ++ ++struct ally_rgb_data { ++ uint8_t brightness; ++ uint8_t red[4]; ++ uint8_t green[4]; ++ uint8_t blue[4]; ++ bool initialized; ++}; ++ ++static struct ally_drvdata { ++ struct hid_device *hdev; ++ struct ally_x_device *ally_x; ++ struct ally_gamepad_cfg *gamepad_cfg; ++ struct ally_rgb_dev *led_rgb_dev; ++ struct ally_rgb_data led_rgb_data; ++} drvdata; ++ ++static void reverse_bytes_in_pairs(u8 *buf, size_t size) { ++ uint16_t *word_ptr; ++ size_t i; ++ ++ for (i = 0; i < size; i += 2) { ++ if (i + 1 < size) { ++ word_ptr = (uint16_t *)&buf[i]; ++ *word_ptr = cpu_to_be16(*word_ptr); ++ } ++ } ++} ++ ++/** ++ * asus_dev_set_report - send set report request to device. ++ * ++ * @hdev: hid device ++ * @buf: in/out data to transfer ++ * @len: length of buf ++ * ++ * Return: count of data transferred, negative if error ++ * ++ * Same behavior as hid_hw_raw_request. Note that the input buffer is duplicated. ++ */ ++static int asus_dev_set_report(struct hid_device *hdev, const u8 *buf, size_t len) ++{ ++ unsigned char *dmabuf; ++ int ret; ++ ++ dmabuf = kmemdup(buf, len, GFP_KERNEL); ++ if (!dmabuf) ++ return -ENOMEM; ++ ++ ret = hid_hw_raw_request(hdev, buf[0], dmabuf, len, HID_FEATURE_REPORT, ++ HID_REQ_SET_REPORT); ++ kfree(dmabuf); ++ ++ return ret; ++} ++ ++static int asus_dev_get_report(struct hid_device *hdev, u8 *out_buf, size_t out_buf_size) ++{ ++ return hid_hw_raw_request(hdev, FEATURE_REPORT_ID, out_buf, out_buf_size, ++ HID_FEATURE_REPORT, HID_REQ_GET_REPORT); ++} ++ ++static u8 get_endpoint_address(struct hid_device *hdev) ++{ ++ struct usb_interface *intf; ++ struct usb_host_endpoint *ep; ++ ++ intf = to_usb_interface(hdev->dev.parent); ++ ++ if (intf) { ++ ep = intf->cur_altsetting->endpoint; ++ if (ep) { ++ return ep->desc.bEndpointAddress; ++ } ++ } ++ ++ return -ENODEV; ++} ++ ++/**************************************************************************************************/ ++/* ROG Ally gamepad configuration */ ++/**************************************************************************************************/ ++ ++/* This should be called before any attempts to set device functions */ ++static int ally_gamepad_check_ready(struct hid_device *hdev) ++{ ++ int ret, count; ++ u8 *hidbuf; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ ret = 0; ++ for (count = 0; count < READY_MAX_TRIES; count++) { ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ hidbuf[2] = xpad_cmd_check_ready; ++ hidbuf[3] = 01; ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ hid_dbg(hdev, "ROG Ally check failed set report: %d\n", ret); ++ ++ hidbuf[0] = hidbuf[1] = hidbuf[2] = hidbuf[3] = 0; ++ ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ hid_dbg(hdev, "ROG Ally check failed get report: %d\n", ret); ++ ++ ret = hidbuf[2] == xpad_cmd_check_ready; ++ if (ret) ++ break; ++ usleep_range( ++ 1000, ++ 2000); /* don't spam the entire loop in less than USB response time */ ++ } ++ ++ if (count == READY_MAX_TRIES) ++ hid_warn(hdev, "ROG Ally never responded with a ready\n"); ++ ++ kfree(hidbuf); ++ return ret; ++} ++ ++/* VIBRATION INTENSITY ****************************************************************************/ ++static ssize_t gamepad_vibration_intensity_index_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return sysfs_emit(buf, "left right\n"); ++} ++ ++ALLY_DEVICE_ATTR_RO(gamepad_vibration_intensity_index, vibration_intensity_index); ++ ++static ssize_t _gamepad_apply_intensity(struct hid_device *hdev, ++ struct ally_gamepad_cfg *ally_cfg) ++{ ++ u8 *hidbuf; ++ int ret; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ hidbuf[2] = xpad_cmd_set_vibe_intensity; ++ hidbuf[3] = xpad_cmd_len_vibe_intensity; ++ hidbuf[4] = ally_cfg->vibration_intensity[0]; ++ hidbuf[5] = ally_cfg->vibration_intensity[1]; ++ ++ ret = ally_gamepad_check_ready(hdev); ++ if (ret < 0) ++ goto report_fail; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ goto report_fail; ++ ++report_fail: ++ kfree(hidbuf); ++ return ret; ++} ++ ++static ssize_t gamepad_vibration_intensity_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ ++ if (!drvdata.gamepad_cfg) ++ return -ENODEV; ++ ++ return sysfs_emit( ++ buf, "%d %d\n", ++ ally_cfg->vibration_intensity[0], ++ ally_cfg->vibration_intensity[1]); ++} ++ ++static ssize_t gamepad_vibration_intensity_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct hid_device *hdev = to_hid_device(dev); ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ u32 left, right; ++ int ret; ++ ++ if (!drvdata.gamepad_cfg) ++ return -ENODEV; ++ ++ if (sscanf(buf, "%d %d", &left, &right) != 2) ++ return -EINVAL; ++ ++ if (left > 64 || right > 64) ++ return -EINVAL; ++ ++ ally_cfg->vibration_intensity[0] = left; ++ ally_cfg->vibration_intensity[1] = right; ++ ++ ret = _gamepad_apply_intensity(hdev, ally_cfg); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++ALLY_DEVICE_ATTR_RW(gamepad_vibration_intensity, vibration_intensity); ++ ++/* ANALOGUE DEADZONES *****************************************************************************/ ++static ssize_t _gamepad_apply_deadzones(struct hid_device *hdev, ++ struct ally_gamepad_cfg *ally_cfg) ++{ ++ u8 *hidbuf; ++ int ret; ++ ++ ret = ally_gamepad_check_ready(hdev); ++ if (ret < 0) ++ return ret; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ hidbuf[2] = xpad_cmd_set_js_dz; ++ hidbuf[3] = xpad_cmd_len_deadzone; ++ hidbuf[4] = ally_cfg->ls_dz.inner; ++ hidbuf[5] = ally_cfg->ls_dz.outer; ++ hidbuf[6] = ally_cfg->rs_dz.inner; ++ hidbuf[7] = ally_cfg->rs_dz.outer; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ goto end; ++ ++ hidbuf[2] = xpad_cmd_set_tr_dz; ++ hidbuf[4] = ally_cfg->lt_dz.inner; ++ hidbuf[5] = ally_cfg->lt_dz.outer; ++ hidbuf[6] = ally_cfg->rt_dz.inner; ++ hidbuf[7] = ally_cfg->rt_dz.outer; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ goto end; ++ ++end: ++ kfree(hidbuf); ++ return ret; ++} ++ ++static void _gamepad_set_deadzones_default(struct ally_gamepad_cfg *ally_cfg) ++{ ++ ally_cfg->ls_dz.inner = 0x00; ++ ally_cfg->ls_dz.outer = 0x64; ++ ally_cfg->rs_dz.inner = 0x00; ++ ally_cfg->rs_dz.outer = 0x64; ++ ally_cfg->lt_dz.inner = 0x00; ++ ally_cfg->lt_dz.outer = 0x64; ++ ally_cfg->rt_dz.inner = 0x00; ++ ally_cfg->rt_dz.outer = 0x64; ++} ++ ++static ssize_t axis_xyz_deadzone_index_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "inner outer\n"); ++} ++ ++ALLY_DEVICE_ATTR_RO(axis_xyz_deadzone_index, deadzone_index); ++ ++ALLY_DEADZONES(axis_xy_left, ls_dz); ++ALLY_DEADZONES(axis_xy_right, rs_dz); ++ALLY_DEADZONES(axis_z_left, lt_dz); ++ALLY_DEADZONES(axis_z_right, rt_dz); ++ ++/* ANTI-DEADZONES *********************************************************************************/ ++static ssize_t _gamepad_apply_js_ADZ(struct hid_device *hdev, ++ struct ally_gamepad_cfg *ally_cfg) ++{ ++ u8 *hidbuf; ++ int ret; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ hidbuf[2] = xpad_cmd_set_adz; ++ hidbuf[3] = xpad_cmd_len_adz; ++ hidbuf[4] = ally_cfg->ls_adz; ++ hidbuf[5] = ally_cfg->rs_adz; ++ ++ ret = ally_gamepad_check_ready(hdev); ++ if (ret < 0) ++ goto report_fail; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ goto report_fail; ++ ++report_fail: ++ kfree(hidbuf); ++ return ret; ++} ++ ++static void _gamepad_set_anti_deadzones_default(struct ally_gamepad_cfg *ally_cfg) ++{ ++ ally_cfg->ls_adz = 0x00; ++ ally_cfg->rs_adz = 0x00; ++} ++ ++static ssize_t _gamepad_js_ADZ_store(struct device *dev, const char *buf, u8 *adz) ++{ ++ int ret, val; ++ ++ ret = kstrtoint(buf, 0, &val); ++ if (ret) ++ return ret; ++ ++ if (val < 0 || val > 32) ++ return -EINVAL; ++ ++ *adz = val; ++ ++ return ret; ++} ++ ++static ssize_t axis_xy_left_anti_deadzone_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ ++ return sysfs_emit(buf, "%d\n", ally_cfg->ls_adz); ++} ++ ++static ssize_t axis_xy_left_anti_deadzone_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ int ret; ++ ++ ret = _gamepad_js_ADZ_store(dev, buf, &ally_cfg->ls_adz); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ALLY_DEVICE_ATTR_RW(axis_xy_left_anti_deadzone, anti_deadzone); ++ ++static ssize_t axis_xy_right_anti_deadzone_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ ++ return sysfs_emit(buf, "%d\n", ally_cfg->rs_adz); ++} ++ ++static ssize_t axis_xy_right_anti_deadzone_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ int ret; ++ ++ ret = _gamepad_js_ADZ_store(dev, buf, &ally_cfg->rs_adz); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ALLY_DEVICE_ATTR_RW(axis_xy_right_anti_deadzone, anti_deadzone); ++ ++/* JS RESPONSE CURVES *****************************************************************************/ ++static void _gamepad_set_js_response_curves_default(struct ally_gamepad_cfg *ally_cfg) ++{ ++ struct response_curve *js1_rc = &ally_cfg->ls_rc; ++ struct response_curve *js2_rc = &ally_cfg->rs_rc; ++ js1_rc->move_pct_1 = js2_rc->move_pct_1 = 0x16; // 25% ++ js1_rc->move_pct_2 = js2_rc->move_pct_2 = 0x32; // 50% ++ js1_rc->move_pct_3 = js2_rc->move_pct_3 = 0x48; // 75% ++ js1_rc->move_pct_4 = js2_rc->move_pct_4 = 0x64; // 100% ++ js1_rc->response_pct_1 = js2_rc->response_pct_1 = 0x16; ++ js1_rc->response_pct_2 = js2_rc->response_pct_2 = 0x32; ++ js1_rc->response_pct_3 = js2_rc->response_pct_3 = 0x48; ++ js1_rc->response_pct_4 = js2_rc->response_pct_4 = 0x64; ++} ++ ++static ssize_t _gamepad_apply_response_curves(struct hid_device *hdev, ++ struct ally_gamepad_cfg *ally_cfg) ++{ ++ u8 *hidbuf; ++ int ret; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ memcpy(&hidbuf[2], &ally_cfg->ls_rc, sizeof(ally_cfg->ls_rc)); ++ ++ ret = ally_gamepad_check_ready(hdev); ++ if (ret < 0) ++ goto report_fail; ++ ++ hidbuf[4] = 0x02; ++ memcpy(&hidbuf[5], &ally_cfg->rs_rc, sizeof(ally_cfg->rs_rc)); ++ ++ ret = ally_gamepad_check_ready(hdev); ++ if (ret < 0) ++ goto report_fail; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ goto report_fail; ++ ++report_fail: ++ kfree(hidbuf); ++ return ret; ++} ++ ++ALLY_JS_RC_POINT(axis_xy_left, move, 1); ++ALLY_JS_RC_POINT(axis_xy_left, move, 2); ++ALLY_JS_RC_POINT(axis_xy_left, move, 3); ++ALLY_JS_RC_POINT(axis_xy_left, move, 4); ++ALLY_JS_RC_POINT(axis_xy_left, response, 1); ++ALLY_JS_RC_POINT(axis_xy_left, response, 2); ++ALLY_JS_RC_POINT(axis_xy_left, response, 3); ++ALLY_JS_RC_POINT(axis_xy_left, response, 4); ++ ++ALLY_JS_RC_POINT(axis_xy_right, move, 1); ++ALLY_JS_RC_POINT(axis_xy_right, move, 2); ++ALLY_JS_RC_POINT(axis_xy_right, move, 3); ++ALLY_JS_RC_POINT(axis_xy_right, move, 4); ++ALLY_JS_RC_POINT(axis_xy_right, response, 1); ++ALLY_JS_RC_POINT(axis_xy_right, response, 2); ++ALLY_JS_RC_POINT(axis_xy_right, response, 3); ++ALLY_JS_RC_POINT(axis_xy_right, response, 4); ++ ++/* CALIBRATIONS ***********************************************************************************/ ++static int gamepad_get_calibration(struct hid_device *hdev) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ u8 *hidbuf; ++ int ret, i; ++ ++ if (!drvdata.gamepad_cfg) ++ return -ENODEV; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ for (i = 0; i < 2; i++) { ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = 0xD0; ++ hidbuf[2] = 0x03; ++ hidbuf[3] = i + 1; // 0x01 JS, 0x02 TR ++ hidbuf[4] = 0x20; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) { ++ hid_warn(hdev, "ROG Ally check failed set report: %d\n", ret); ++ goto cleanup; ++ } ++ ++ memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE); ++ ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0 || hidbuf[5] != 1) { ++ hid_warn(hdev, "ROG Ally check failed get report: %d\n", ret); ++ goto cleanup; ++ } ++ ++ if (i == 0) { ++ /* Joystick calibration */ ++ reverse_bytes_in_pairs(&hidbuf[6], sizeof(struct js_axis_calibrations)); ++ ally_cfg->js_cal = *(struct js_axis_calibrations *)&hidbuf[6]; ++ print_hex_dump(KERN_INFO, "HID Buffer JS: ", DUMP_PREFIX_OFFSET, 16, 1, hidbuf, 32, true); ++ struct js_axis_calibrations *cal = &drvdata.gamepad_cfg->js_cal; ++ pr_err("LS_CAL: X: %d, Min: %d, Max: %d", cal->left_x_stable, cal->left_x_min, cal->left_x_max); ++ pr_err("LS_CAL: Y: %d, Min: %d, Max: %d", cal->left_y_stable, cal->left_y_min, cal->left_y_max); ++ pr_err("RS_CAL: X: %d, Min: %d, Max: %d", cal->right_x_stable, cal->right_x_min, cal->right_x_max); ++ pr_err("RS_CAL: Y: %d, Min: %d, Max: %d", cal->right_y_stable, cal->right_y_min, cal->right_y_max); ++ } else { ++ /* Trigger calibration */ ++ reverse_bytes_in_pairs(&hidbuf[6], sizeof(struct tr_axis_calibrations)); ++ ally_cfg->tr_cal = *(struct tr_axis_calibrations *)&hidbuf[6]; ++ print_hex_dump(KERN_INFO, "HID Buffer TR: ", DUMP_PREFIX_OFFSET, 16, 1, hidbuf, 32, true); ++ } ++ } ++ ++cleanup: ++ kfree(hidbuf); ++ return ret; ++} ++ ++static struct attribute *axis_xy_left_attrs[] = { ++ &dev_attr_axis_xy_left_anti_deadzone.attr, ++ &dev_attr_axis_xy_left_deadzone.attr, ++ &dev_attr_axis_xyz_deadzone_index.attr, ++ &dev_attr_axis_xy_left_move_1.attr, ++ &dev_attr_axis_xy_left_move_2.attr, ++ &dev_attr_axis_xy_left_move_3.attr, ++ &dev_attr_axis_xy_left_move_4.attr, ++ &dev_attr_axis_xy_left_response_1.attr, ++ &dev_attr_axis_xy_left_response_2.attr, ++ &dev_attr_axis_xy_left_response_3.attr, ++ &dev_attr_axis_xy_left_response_4.attr, ++ NULL ++}; ++static const struct attribute_group axis_xy_left_attr_group = { ++ .name = "axis_xy_left", ++ .attrs = axis_xy_left_attrs, ++}; ++ ++static struct attribute *axis_xy_right_attrs[] = { ++ &dev_attr_axis_xy_right_anti_deadzone.attr, ++ &dev_attr_axis_xy_right_deadzone.attr, ++ &dev_attr_axis_xyz_deadzone_index.attr, ++ &dev_attr_axis_xy_right_move_1.attr, ++ &dev_attr_axis_xy_right_move_2.attr, ++ &dev_attr_axis_xy_right_move_3.attr, ++ &dev_attr_axis_xy_right_move_4.attr, ++ &dev_attr_axis_xy_right_response_1.attr, ++ &dev_attr_axis_xy_right_response_2.attr, ++ &dev_attr_axis_xy_right_response_3.attr, ++ &dev_attr_axis_xy_right_response_4.attr, ++ NULL ++}; ++static const struct attribute_group axis_xy_right_attr_group = { ++ .name = "axis_xy_right", ++ .attrs = axis_xy_right_attrs, ++}; ++ ++static struct attribute *axis_z_left_attrs[] = { ++ &dev_attr_axis_z_left_deadzone.attr, ++ &dev_attr_axis_xyz_deadzone_index.attr, ++ NULL, ++}; ++static const struct attribute_group axis_z_left_attr_group = { ++ .name = "axis_z_left", ++ .attrs = axis_z_left_attrs, ++}; ++ ++static struct attribute *axis_z_right_attrs[] = { ++ &dev_attr_axis_z_right_deadzone.attr, ++ &dev_attr_axis_xyz_deadzone_index.attr, ++ NULL, ++}; ++static const struct attribute_group axis_z_right_attr_group = { ++ .name = "axis_z_right", ++ .attrs = axis_z_right_attrs, ++}; ++ ++/* A HID packet conatins mappings for two buttons: btn1, btn1_macro, btn2, btn2_macro */ ++static void _btn_pair_to_hid_pkt(struct ally_gamepad_cfg *ally_cfg, ++ enum btn_pair_index pair, ++ struct btn_data *btn1, struct btn_data *btn2, ++ u8 *out, int out_len) ++{ ++ int start = 5; ++ ++ out[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ out[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ out[2] = xpad_cmd_set_mapping; ++ out[3] = pair; ++ out[4] = xpad_cmd_len_mapping; ++ ++ btn_code_to_byte_array(btn1->button, &out[start]); ++ start += BTN_DATA_LEN; ++ btn_code_to_byte_array(btn1->macro, &out[start]); ++ start += BTN_DATA_LEN; ++ btn_code_to_byte_array(btn2->button, &out[start]); ++ start += BTN_DATA_LEN; ++ btn_code_to_byte_array(btn2->macro, &out[start]); ++ //print_hex_dump(KERN_DEBUG, "byte_array: ", DUMP_PREFIX_OFFSET, 64, 1, out, 64, false); ++} ++ ++/* Apply the mapping pair to the device */ ++static int _gamepad_apply_btn_pair(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg, ++ enum btn_pair_index btn_pair) ++{ ++ u8 mode = ally_cfg->mode - 1; ++ struct btn_data *btn1, *btn2; ++ u8 *hidbuf; ++ int ret; ++ ++ ret = ally_gamepad_check_ready(hdev); ++ if (ret < 0) ++ return ret; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ switch (btn_pair) { ++ case btn_pair_dpad_u_d: ++ btn1 = &ally_cfg->key_mapping[mode].dpad_up; ++ btn2 = &ally_cfg->key_mapping[mode].dpad_down; ++ break; ++ case btn_pair_dpad_l_r: ++ btn1 = &ally_cfg->key_mapping[mode].dpad_left; ++ btn2 = &ally_cfg->key_mapping[mode].dpad_right; ++ break; ++ case btn_pair_ls_rs: ++ btn1 = &ally_cfg->key_mapping[mode].btn_ls; ++ btn2 = &ally_cfg->key_mapping[mode].btn_rs; ++ break; ++ case btn_pair_lb_rb: ++ btn1 = &ally_cfg->key_mapping[mode].btn_lb; ++ btn2 = &ally_cfg->key_mapping[mode].btn_rb; ++ break; ++ case btn_pair_lt_rt: ++ btn1 = &ally_cfg->key_mapping[mode].btn_lt; ++ btn2 = &ally_cfg->key_mapping[mode].btn_rt; ++ break; ++ case btn_pair_a_b: ++ btn1 = &ally_cfg->key_mapping[mode].btn_a; ++ btn2 = &ally_cfg->key_mapping[mode].btn_b; ++ break; ++ case btn_pair_x_y: ++ btn1 = &ally_cfg->key_mapping[mode].btn_x; ++ btn2 = &ally_cfg->key_mapping[mode].btn_y; ++ break; ++ case btn_pair_view_menu: ++ btn1 = &ally_cfg->key_mapping[mode].btn_view; ++ btn2 = &ally_cfg->key_mapping[mode].btn_menu; ++ break; ++ case btn_pair_m1_m2: ++ btn1 = &ally_cfg->key_mapping[mode].btn_m1; ++ btn2 = &ally_cfg->key_mapping[mode].btn_m2; ++ break; ++ default: ++ break; ++ } ++ ++ _btn_pair_to_hid_pkt(ally_cfg, btn_pair, btn1, btn2, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ ++ kfree(hidbuf); ++ ++ return ret; ++} ++ ++static int _gamepad_apply_turbo(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg) ++{ ++ struct btn_mapping *map = &ally_cfg->key_mapping[ally_cfg->mode - 1]; ++ u8 *hidbuf; ++ int ret; ++ ++ /* set turbo */ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ hidbuf[2] = xpad_cmd_set_turbo; ++ hidbuf[3] = xpad_cmd_len_turbo; ++ ++ hidbuf[4] = map->dpad_up.turbo; ++ hidbuf[6] = map->dpad_down.turbo; ++ hidbuf[8] = map->dpad_left.turbo; ++ hidbuf[10] = map->dpad_right.turbo; ++ ++ hidbuf[12] = map->btn_ls.turbo; ++ hidbuf[14] = map->btn_rs.turbo; ++ hidbuf[16] = map->btn_lb.turbo; ++ hidbuf[18] = map->btn_rb.turbo; ++ ++ hidbuf[20] = map->btn_a.turbo; ++ hidbuf[22] = map->btn_b.turbo; ++ hidbuf[24] = map->btn_x.turbo; ++ hidbuf[26] = map->btn_y.turbo; ++ ++ hidbuf[28] = map->btn_lt.turbo; ++ hidbuf[30] = map->btn_rt.turbo; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ ++ kfree(hidbuf); ++ ++ return ret; ++} ++ ++static ssize_t _gamepad_apply_all(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg) ++{ ++ int ret; ++ ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_dpad_u_d); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_dpad_l_r); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_ls_rs); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_lb_rb); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_a_b); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_x_y); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_view_menu); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_m1_m2); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_lt_rt); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_turbo(hdev, ally_cfg); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_deadzones(hdev, ally_cfg); ++ if (ret < 0) ++ return ret; ++ ret = _gamepad_apply_js_ADZ(hdev, ally_cfg); ++ if (ret < 0) ++ return ret; ++ ret =_gamepad_apply_response_curves(hdev, ally_cfg); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static ssize_t gamepad_apply_all_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ struct hid_device *hdev = to_hid_device(dev); ++ int ret; ++ ++ if (!drvdata.gamepad_cfg) ++ return -ENODEV; ++ ++ ret = _gamepad_apply_all(hdev, ally_cfg); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ALLY_DEVICE_ATTR_WO(gamepad_apply_all, apply_all); ++ ++/* button map attributes, regular and macro*/ ++ALLY_BTN_MAPPING(m1, btn_m1); ++ALLY_BTN_MAPPING(m2, btn_m2); ++ALLY_BTN_MAPPING(view, btn_view); ++ALLY_BTN_MAPPING(menu, btn_menu); ++ALLY_TURBO_BTN_MAPPING(a, btn_a); ++ALLY_TURBO_BTN_MAPPING(b, btn_b); ++ALLY_TURBO_BTN_MAPPING(x, btn_x); ++ALLY_TURBO_BTN_MAPPING(y, btn_y); ++ALLY_TURBO_BTN_MAPPING(lb, btn_lb); ++ALLY_TURBO_BTN_MAPPING(rb, btn_rb); ++ALLY_TURBO_BTN_MAPPING(ls, btn_ls); ++ALLY_TURBO_BTN_MAPPING(rs, btn_rs); ++ALLY_TURBO_BTN_MAPPING(lt, btn_lt); ++ALLY_TURBO_BTN_MAPPING(rt, btn_rt); ++ALLY_TURBO_BTN_MAPPING(dpad_u, dpad_up); ++ALLY_TURBO_BTN_MAPPING(dpad_d, dpad_down); ++ALLY_TURBO_BTN_MAPPING(dpad_l, dpad_left); ++ALLY_TURBO_BTN_MAPPING(dpad_r, dpad_right); ++ ++static void _gamepad_set_xpad_default(struct ally_gamepad_cfg *ally_cfg) ++{ ++ struct btn_mapping *map = &ally_cfg->key_mapping[ally_cfg->mode - 1]; ++ map->btn_m1.button = BTN_KB_M1; ++ map->btn_m2.button = BTN_KB_M2; ++ map->btn_a.button = BTN_PAD_A; ++ map->btn_b.button = BTN_PAD_B; ++ map->btn_x.button = BTN_PAD_X; ++ map->btn_y.button = BTN_PAD_Y; ++ map->btn_lb.button = BTN_PAD_LB; ++ map->btn_rb.button = BTN_PAD_RB; ++ map->btn_lt.button = BTN_PAD_LT; ++ map->btn_rt.button = BTN_PAD_RT; ++ map->btn_ls.button = BTN_PAD_LS; ++ map->btn_rs.button = BTN_PAD_RS; ++ map->dpad_up.button = BTN_PAD_DPAD_UP; ++ map->dpad_down.button = BTN_PAD_DPAD_DOWN; ++ map->dpad_left.button = BTN_PAD_DPAD_LEFT; ++ map->dpad_right.button = BTN_PAD_DPAD_RIGHT; ++ map->btn_view.button = BTN_PAD_VIEW; ++ map->btn_menu.button = BTN_PAD_MENU; ++} ++ ++static ssize_t btn_mapping_reset_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ ++ if (!drvdata.gamepad_cfg) ++ return -ENODEV; ++ ++ switch (ally_cfg->mode) { ++ case xpad_mode_game: ++ _gamepad_set_xpad_default(ally_cfg); ++ break; ++ default: ++ _gamepad_set_xpad_default(ally_cfg); ++ break; ++ } ++ ++ return count; ++} ++ALLY_DEVICE_ATTR_WO(btn_mapping_reset, reset_btn_mapping); ++ ++/* GAMEPAD MODE */ ++static ssize_t _gamepad_set_mode(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg, ++ int val) ++{ ++ u8 *hidbuf; ++ int ret; ++ ++ hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!hidbuf) ++ return -ENOMEM; ++ ++ hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; ++ hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; ++ hidbuf[2] = xpad_cmd_set_mode; ++ hidbuf[3] = xpad_cmd_len_mode; ++ hidbuf[4] = val; ++ ++ ret = ally_gamepad_check_ready(hdev); ++ if (ret < 0) ++ goto report_fail; ++ ++ ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ goto report_fail; ++ ++ ret = _gamepad_apply_all(hdev, ally_cfg); ++ if (ret < 0) ++ goto report_fail; ++ ++report_fail: ++ kfree(hidbuf); ++ return ret; ++} ++ ++static ssize_t gamepad_mode_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ ++ if (!drvdata.gamepad_cfg) ++ return -ENODEV; ++ ++ return sysfs_emit(buf, "%d\n", ally_cfg->mode); ++} ++ ++static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct hid_device *hdev = to_hid_device(dev); ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; ++ int ret, val; ++ ++ if (!drvdata.gamepad_cfg) ++ return -ENODEV; ++ ++ ret = kstrtoint(buf, 0, &val); ++ if (ret) ++ return ret; ++ ++ if (val < xpad_mode_game || val > xpad_mode_mouse) ++ return -EINVAL; ++ ++ ally_cfg->mode = val; ++ ++ ret = _gamepad_set_mode(hdev, ally_cfg, val); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++DEVICE_ATTR_RW(gamepad_mode); ++ ++/* ROOT LEVEL ATTRS *******************************************************************************/ ++static struct attribute *gamepad_device_attrs[] = { ++ &dev_attr_btn_mapping_reset.attr, ++ &dev_attr_gamepad_mode.attr, ++ &dev_attr_gamepad_apply_all.attr, ++ &dev_attr_gamepad_vibration_intensity.attr, ++ &dev_attr_gamepad_vibration_intensity_index.attr, ++ NULL ++}; ++ ++static const struct attribute_group ally_controller_attr_group = { ++ .attrs = gamepad_device_attrs, ++}; ++ ++static const struct attribute_group *gamepad_device_attr_groups[] = { ++ &ally_controller_attr_group, ++ &axis_xy_left_attr_group, ++ &axis_xy_right_attr_group, ++ &axis_z_left_attr_group, ++ &axis_z_right_attr_group, ++ &btn_mapping_m1_attr_group, ++ &btn_mapping_m2_attr_group, ++ &btn_mapping_a_attr_group, ++ &btn_mapping_b_attr_group, ++ &btn_mapping_x_attr_group, ++ &btn_mapping_y_attr_group, ++ &btn_mapping_lb_attr_group, ++ &btn_mapping_rb_attr_group, ++ &btn_mapping_ls_attr_group, ++ &btn_mapping_rs_attr_group, ++ &btn_mapping_lt_attr_group, ++ &btn_mapping_rt_attr_group, ++ &btn_mapping_dpad_u_attr_group, ++ &btn_mapping_dpad_d_attr_group, ++ &btn_mapping_dpad_l_attr_group, ++ &btn_mapping_dpad_r_attr_group, ++ &btn_mapping_view_attr_group, ++ &btn_mapping_menu_attr_group, ++ NULL, ++}; ++ ++static struct ally_gamepad_cfg *ally_gamepad_cfg_create(struct hid_device *hdev) ++{ ++ struct ally_gamepad_cfg *ally_cfg; ++ struct input_dev *input_dev; ++ int err; ++ ++ ally_cfg = devm_kzalloc(&hdev->dev, sizeof(*ally_cfg), GFP_KERNEL); ++ if (!ally_cfg) ++ return ERR_PTR(-ENOMEM); ++ ally_cfg->hdev = hdev; ++ // Allocate memory for each mode's `btn_mapping` ++ ally_cfg->mode = xpad_mode_game; ++ ++ input_dev = devm_input_allocate_device(&hdev->dev); ++ if (!input_dev) { ++ err = -ENOMEM; ++ goto free_ally_cfg; ++ } ++ ++ input_dev->id.bustype = hdev->bus; ++ input_dev->id.vendor = hdev->vendor; ++ input_dev->id.product = hdev->product; ++ input_dev->id.version = hdev->version; ++ input_dev->uniq = hdev->uniq; ++ input_dev->name = "ASUS ROG Ally Config"; ++ input_set_capability(input_dev, EV_KEY, KEY_PROG1); ++ input_set_capability(input_dev, EV_KEY, KEY_F16); ++ input_set_capability(input_dev, EV_KEY, KEY_F17); ++ input_set_capability(input_dev, EV_KEY, KEY_F18); ++ input_set_drvdata(input_dev, hdev); ++ ++ err = input_register_device(input_dev); ++ if (err) ++ goto free_input_dev; ++ ally_cfg->input = input_dev; ++ ++ /* ignore all errors for this as they are related to USB HID I/O */ ++ _gamepad_set_xpad_default(ally_cfg); ++ ally_cfg->key_mapping[ally_cfg->mode - 1].btn_m1.button = BTN_KB_M1; ++ ally_cfg->key_mapping[ally_cfg->mode - 1].btn_m2.button = BTN_KB_M2; ++ _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_m1_m2); ++ gamepad_get_calibration(hdev); ++ ++ ally_cfg->vibration_intensity[0] = 0x64; ++ ally_cfg->vibration_intensity[1] = 0x64; ++ _gamepad_set_deadzones_default(ally_cfg); ++ _gamepad_set_anti_deadzones_default(ally_cfg); ++ _gamepad_set_js_response_curves_default(ally_cfg); ++ ++ drvdata.gamepad_cfg = ally_cfg; // Must asign before attr group setup ++ if (sysfs_create_groups(&hdev->dev.kobj, gamepad_device_attr_groups)) { ++ err = -ENODEV; ++ goto unregister_input_dev; ++ } ++ ++ return ally_cfg; ++ ++unregister_input_dev: ++ input_unregister_device(input_dev); ++ ally_cfg->input = NULL; // Prevent double free when kfree(ally_cfg) happens ++ ++free_input_dev: ++ devm_kfree(&hdev->dev, input_dev); ++ ++free_ally_cfg: ++ devm_kfree(&hdev->dev, ally_cfg); ++ return ERR_PTR(err); ++} ++ ++static void ally_cfg_remove(struct hid_device *hdev) ++{ ++ // __gamepad_set_mode(hdev, drvdata.gamepad_cfg, xpad_mode_mouse); ++ sysfs_remove_groups(&hdev->dev.kobj, gamepad_device_attr_groups); ++} ++ ++/**************************************************************************************************/ ++/* ROG Ally gamepad i/o and force-feedback */ ++/**************************************************************************************************/ ++static int ally_x_raw_event(struct ally_x_device *ally_x, struct hid_report *report, u8 *data, ++ int size) ++{ ++ struct ally_x_input_report *in_report; ++ unsigned long flags; ++ u8 byte; ++ ++ if (data[0] == 0x0B) { ++ in_report = (struct ally_x_input_report *)&data[1]; ++ ++ input_report_abs(ally_x->input, ABS_X, in_report->x); ++ input_report_abs(ally_x->input, ABS_Y, in_report->y); ++ input_report_abs(ally_x->input, ABS_RX, in_report->rx); ++ input_report_abs(ally_x->input, ABS_RY, in_report->ry); ++ input_report_abs(ally_x->input, ABS_Z, in_report->z); ++ input_report_abs(ally_x->input, ABS_RZ, in_report->rz); ++ ++ byte = in_report->buttons[0]; ++ input_report_key(ally_x->input, BTN_A, byte & BIT(0)); ++ input_report_key(ally_x->input, BTN_B, byte & BIT(1)); ++ input_report_key(ally_x->input, BTN_X, byte & BIT(2)); ++ input_report_key(ally_x->input, BTN_Y, byte & BIT(3)); ++ input_report_key(ally_x->input, BTN_TL, byte & BIT(4)); ++ input_report_key(ally_x->input, BTN_TR, byte & BIT(5)); ++ input_report_key(ally_x->input, BTN_SELECT, byte & BIT(6)); ++ input_report_key(ally_x->input, BTN_START, byte & BIT(7)); ++ ++ byte = in_report->buttons[1]; ++ input_report_key(ally_x->input, BTN_THUMBL, byte & BIT(0)); ++ input_report_key(ally_x->input, BTN_THUMBR, byte & BIT(1)); ++ input_report_key(ally_x->input, BTN_MODE, byte & BIT(2)); ++ ++ byte = in_report->buttons[2]; ++ input_report_abs(ally_x->input, ABS_HAT0X, hat_values[byte][0]); ++ input_report_abs(ally_x->input, ABS_HAT0Y, hat_values[byte][1]); ++ } ++ /* ++ * The MCU used on Ally provides many devices: gamepad, keyboord, mouse, other. ++ * The AC and QAM buttons route through another interface making it difficult to ++ * use the events unless we grab those and use them here. Only works for Ally X. ++ */ ++ else if (data[0] == 0x5A) { ++ if (ally_x->qam_btns_steam_mode) { ++ spin_lock_irqsave(&ally_x->lock, flags); ++ if (data[1] == 0x38 && !ally_x->update_qam_btn) { ++ ally_x->update_qam_btn = true; ++ if (ally_x->output_worker_initialized) ++ schedule_work(&ally_x->output_worker); ++ } ++ spin_unlock_irqrestore(&ally_x->lock, flags); ++ /* Left/XBox button. Long press does ctrl+alt+del which we can't catch */ ++ input_report_key(ally_x->input, BTN_MODE, data[1] == 0xA6); ++ } else { ++ input_report_key(ally_x->input, KEY_F16, data[1] == 0xA6); ++ input_report_key(ally_x->input, KEY_PROG1, data[1] == 0x38); ++ } ++ /* QAM long press */ ++ input_report_key(ally_x->input, KEY_F17, data[1] == 0xA7); ++ /* QAM long press released */ ++ input_report_key(ally_x->input, KEY_F18, data[1] == 0xA8); ++ } ++ ++ input_sync(ally_x->input); ++ ++ return 0; ++} ++ ++static struct input_dev *ally_x_alloc_input_dev(struct hid_device *hdev, ++ const char *name_suffix) ++{ ++ struct input_dev *input_dev; ++ ++ input_dev = devm_input_allocate_device(&hdev->dev); ++ if (!input_dev) ++ return ERR_PTR(-ENOMEM); ++ ++ input_dev->id.bustype = hdev->bus; ++ input_dev->id.vendor = hdev->vendor; ++ input_dev->id.product = hdev->product; ++ input_dev->id.version = hdev->version; ++ input_dev->uniq = hdev->uniq; ++ input_dev->name = "ASUS ROG Ally X Gamepad"; ++ ++ input_set_drvdata(input_dev, hdev); ++ ++ return input_dev; ++} ++ ++static int ally_x_play_effect(struct input_dev *idev, void *data, struct ff_effect *effect) ++{ ++ struct ally_x_device *ally_x = drvdata.ally_x; ++ unsigned long flags; ++ ++ if (effect->type != FF_RUMBLE) ++ return 0; ++ ++ spin_lock_irqsave(&ally_x->lock, flags); ++ ally_x->ff_packet->ff.magnitude_strong = effect->u.rumble.strong_magnitude / 512; ++ ally_x->ff_packet->ff.magnitude_weak = effect->u.rumble.weak_magnitude / 512; ++ ally_x->update_ff = true; ++ spin_unlock_irqrestore(&ally_x->lock, flags); ++ ++ if (ally_x->output_worker_initialized) ++ schedule_work(&ally_x->output_worker); ++ ++ return 0; ++} ++ ++static void ally_x_work(struct work_struct *work) ++{ ++ struct ally_x_device *ally_x = container_of(work, struct ally_x_device, output_worker); ++ struct ff_report *ff_report = NULL; ++ bool update_qam = false; ++ bool update_ff = false; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ally_x->lock, flags); ++ update_ff = ally_x->update_ff; ++ if (ally_x->update_ff) { ++ ff_report = kmemdup(ally_x->ff_packet, sizeof(*ally_x->ff_packet), GFP_KERNEL); ++ ally_x->update_ff = false; ++ } ++ update_qam = ally_x->update_qam_btn; ++ spin_unlock_irqrestore(&ally_x->lock, flags); ++ ++ if (update_ff && ff_report) { ++ ff_report->ff.magnitude_left = ff_report->ff.magnitude_strong; ++ ff_report->ff.magnitude_right = ff_report->ff.magnitude_weak; ++ asus_dev_set_report(ally_x->hdev, (u8 *)ff_report, sizeof(*ff_report)); ++ } ++ kfree(ff_report); ++ ++ if (update_qam) { ++ /* ++ * The sleeps here are required to allow steam to register the button combo. ++ */ ++ usleep_range(1000, 2000); ++ input_report_key(ally_x->input, BTN_MODE, 1); ++ input_sync(ally_x->input); ++ ++ msleep(80); ++ input_report_key(ally_x->input, BTN_A, 1); ++ input_sync(ally_x->input); ++ ++ msleep(80); ++ input_report_key(ally_x->input, BTN_A, 0); ++ input_sync(ally_x->input); ++ ++ msleep(80); ++ input_report_key(ally_x->input, BTN_MODE, 0); ++ input_sync(ally_x->input); ++ ++ spin_lock_irqsave(&ally_x->lock, flags); ++ ally_x->update_qam_btn = false; ++ spin_unlock_irqrestore(&ally_x->lock, flags); ++ } ++} ++ ++static struct input_dev *ally_x_setup_input(struct hid_device *hdev) ++{ ++ int ret, abs_min = 0, js_abs_max = 65535, tr_abs_max = 1023; ++ struct input_dev *input; ++ ++ input = ally_x_alloc_input_dev(hdev, NULL); ++ if (IS_ERR(input)) ++ return ERR_CAST(input); ++ ++ input_set_abs_params(input, ABS_X, abs_min, js_abs_max, 0, 0); ++ input_set_abs_params(input, ABS_Y, abs_min, js_abs_max, 0, 0); ++ input_set_abs_params(input, ABS_RX, abs_min, js_abs_max, 0, 0); ++ input_set_abs_params(input, ABS_RY, abs_min, js_abs_max, 0, 0); ++ input_set_abs_params(input, ABS_Z, abs_min, tr_abs_max, 0, 0); ++ input_set_abs_params(input, ABS_RZ, abs_min, tr_abs_max, 0, 0); ++ input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0); ++ input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0); ++ input_set_capability(input, EV_KEY, BTN_A); ++ input_set_capability(input, EV_KEY, BTN_B); ++ input_set_capability(input, EV_KEY, BTN_X); ++ input_set_capability(input, EV_KEY, BTN_Y); ++ input_set_capability(input, EV_KEY, BTN_TL); ++ input_set_capability(input, EV_KEY, BTN_TR); ++ input_set_capability(input, EV_KEY, BTN_SELECT); ++ input_set_capability(input, EV_KEY, BTN_START); ++ input_set_capability(input, EV_KEY, BTN_MODE); ++ input_set_capability(input, EV_KEY, BTN_THUMBL); ++ input_set_capability(input, EV_KEY, BTN_THUMBR); ++ ++ input_set_capability(input, EV_KEY, KEY_PROG1); ++ input_set_capability(input, EV_KEY, KEY_F16); ++ input_set_capability(input, EV_KEY, KEY_F17); ++ input_set_capability(input, EV_KEY, KEY_F18); ++ ++ input_set_capability(input, EV_FF, FF_RUMBLE); ++ input_ff_create_memless(input, NULL, ally_x_play_effect); ++ ++ ret = input_register_device(input); ++ if (ret) ++ return ERR_PTR(ret); ++ ++ return input; ++} ++ ++static ssize_t ally_x_qam_mode_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct ally_x_device *ally_x = drvdata.ally_x; ++ ++ return sysfs_emit(buf, "%d\n", ally_x->qam_btns_steam_mode); ++} ++ ++static ssize_t ally_x_qam_mode_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct ally_x_device *ally_x = drvdata.ally_x; ++ bool val; ++ int ret; ++ ++ ret = kstrtobool(buf, &val); ++ if (ret < 0) ++ return ret; ++ ++ ally_x->qam_btns_steam_mode = val; ++ ++ return count; ++} ++ALLY_DEVICE_ATTR_RW(ally_x_qam_mode, qam_mode); ++ ++static struct ally_x_device *ally_x_create(struct hid_device *hdev) ++{ ++ uint8_t max_output_report_size; ++ struct ally_x_device *ally_x; ++ struct ff_report *report; ++ int ret; ++ ++ ally_x = devm_kzalloc(&hdev->dev, sizeof(*ally_x), GFP_KERNEL); ++ if (!ally_x) ++ return ERR_PTR(-ENOMEM); ++ ++ ally_x->hdev = hdev; ++ INIT_WORK(&ally_x->output_worker, ally_x_work); ++ spin_lock_init(&ally_x->lock); ++ ally_x->output_worker_initialized = true; ++ ally_x->qam_btns_steam_mode = ++ true; /* Always default to steam mode, it can be changed by userspace attr */ ++ ++ max_output_report_size = sizeof(struct ally_x_input_report); ++ report = devm_kzalloc(&hdev->dev, sizeof(*report), GFP_KERNEL); ++ if (!report) { ++ ret = -ENOMEM; ++ goto free_ally_x; ++ } ++ ++ /* None of these bytes will change for the FF command for now */ ++ report->report_id = 0x0D; ++ report->ff.enable = 0x0F; /* Enable all by default */ ++ report->ff.pulse_sustain_10ms = 0xFF; /* Duration */ ++ report->ff.pulse_release_10ms = 0x00; /* Start Delay */ ++ report->ff.loop_count = 0xEB; /* Loop Count */ ++ ally_x->ff_packet = report; ++ ++ ally_x->input = ally_x_setup_input(hdev); ++ if (IS_ERR(ally_x->input)) { ++ ret = PTR_ERR(ally_x->input); ++ goto free_ff_packet; ++ } ++ ++ if (sysfs_create_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr)) { ++ ret = -ENODEV; ++ goto unregister_input; ++ } ++ ++ ally_x->update_ff = true; ++ if (ally_x->output_worker_initialized) ++ schedule_work(&ally_x->output_worker); ++ ++ hid_info(hdev, "Registered Ally X controller using %s\n", ++ dev_name(&ally_x->input->dev)); ++ return ally_x; ++ ++unregister_input: ++ input_unregister_device(ally_x->input); ++free_ff_packet: ++ kfree(ally_x->ff_packet); ++free_ally_x: ++ kfree(ally_x); ++ return ERR_PTR(ret); ++} ++ ++static void ally_x_remove(struct hid_device *hdev) ++{ ++ struct ally_x_device *ally_x = drvdata.ally_x; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ally_x->lock, flags); ++ ally_x->output_worker_initialized = false; ++ spin_unlock_irqrestore(&ally_x->lock, flags); ++ cancel_work_sync(&ally_x->output_worker); ++ sysfs_remove_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr); ++} ++ ++/**************************************************************************************************/ ++/* ROG Ally LED control */ ++/**************************************************************************************************/ ++static void ally_rgb_schedule_work(struct ally_rgb_dev *led) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&led->lock, flags); ++ if (!led->removed) ++ schedule_work(&led->work); ++ spin_unlock_irqrestore(&led->lock, flags); ++} ++ ++/* ++ * The RGB still has the basic 0-3 level brightness. Since the multicolour ++ * brightness is being used in place, set this to max ++ */ ++static int ally_rgb_set_bright_base_max(struct hid_device *hdev) ++{ ++ u8 buf[] = { FEATURE_KBD_LED_REPORT_ID1, 0xba, 0xc5, 0xc4, 0x02 }; ++ ++ return asus_dev_set_report(hdev, buf, sizeof(buf)); ++} ++ ++static void ally_rgb_do_work(struct work_struct *work) ++{ ++ struct ally_rgb_dev *led = container_of(work, struct ally_rgb_dev, work); ++ int ret; ++ unsigned long flags; ++ ++ u8 buf[16] = { [0] = FEATURE_ROG_ALLY_REPORT_ID, ++ [1] = FEATURE_ROG_ALLY_CODE_PAGE, ++ [2] = xpad_cmd_set_leds, ++ [3] = xpad_cmd_len_leds }; ++ ++ spin_lock_irqsave(&led->lock, flags); ++ if (!led->update_rgb) { ++ spin_unlock_irqrestore(&led->lock, flags); ++ return; ++ } ++ ++ for (int i = 0; i < 4; i++) { ++ buf[5 + i * 3] = drvdata.led_rgb_dev->green[i]; ++ buf[6 + i * 3] = drvdata.led_rgb_dev->blue[i]; ++ buf[4 + i * 3] = drvdata.led_rgb_dev->red[i]; ++ } ++ led->update_rgb = false; ++ ++ spin_unlock_irqrestore(&led->lock, flags); ++ ++ ret = asus_dev_set_report(led->hdev, buf, sizeof(buf)); ++ if (ret < 0) ++ hid_err(led->hdev, "Ally failed to set gamepad backlight: %d\n", ret); ++} ++ ++static void ally_rgb_set(struct led_classdev *cdev, enum led_brightness brightness) ++{ ++ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); ++ struct ally_rgb_dev *led = container_of(mc_cdev, struct ally_rgb_dev, led_rgb_dev); ++ int intensity, bright; ++ unsigned long flags; ++ ++ led_mc_calc_color_components(mc_cdev, brightness); ++ spin_lock_irqsave(&led->lock, flags); ++ led->update_rgb = true; ++ bright = mc_cdev->led_cdev.brightness; ++ for (int i = 0; i < 4; i++) { ++ intensity = mc_cdev->subled_info[i].intensity; ++ drvdata.led_rgb_dev->red[i] = (((intensity >> 16) & 0xFF) * bright) / 255; ++ drvdata.led_rgb_dev->green[i] = (((intensity >> 8) & 0xFF) * bright) / 255; ++ drvdata.led_rgb_dev->blue[i] = ((intensity & 0xFF) * bright) / 255; ++ } ++ spin_unlock_irqrestore(&led->lock, flags); ++ drvdata.led_rgb_data.initialized = true; ++ ++ ally_rgb_schedule_work(led); ++} ++ ++static int ally_rgb_set_static_from_multi(struct hid_device *hdev) ++{ ++ u8 buf[17] = {FEATURE_KBD_LED_REPORT_ID1, 0xb3}; ++ int ret; ++ ++ /* ++ * Set single zone single colour based on the first LED of EC software mode. ++ * buf[2] = zone, buf[3] = mode ++ */ ++ buf[4] = drvdata.led_rgb_data.red[0]; ++ buf[5] = drvdata.led_rgb_data.green[0]; ++ buf[6] = drvdata.led_rgb_data.blue[0]; ++ ++ ret = asus_dev_set_report(hdev, buf, sizeof(buf)); ++ if (ret < 0) ++ return ret; ++ ++ ret = asus_dev_set_report(hdev, EC_MODE_LED_APPLY, sizeof(EC_MODE_LED_APPLY)); ++ if (ret < 0) ++ return ret; ++ ++ return asus_dev_set_report(hdev, EC_MODE_LED_SET, sizeof(EC_MODE_LED_SET)); ++} ++ ++/* ++ * Store the RGB values for restoring on resume, and set the static mode to the first LED colour ++*/ ++static void ally_rgb_store_settings(void) ++{ ++ int arr_size = sizeof(drvdata.led_rgb_data.red); ++ ++ struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev; ++ ++ drvdata.led_rgb_data.brightness = led_rgb->led_rgb_dev.led_cdev.brightness; ++ ++ memcpy(drvdata.led_rgb_data.red, led_rgb->red, arr_size); ++ memcpy(drvdata.led_rgb_data.green, led_rgb->green, arr_size); ++ memcpy(drvdata.led_rgb_data.blue, led_rgb->blue, arr_size); ++ ++ ally_rgb_set_static_from_multi(led_rgb->hdev); ++} ++ ++static void ally_rgb_restore_settings(struct ally_rgb_dev *led_rgb, struct led_classdev *led_cdev, ++ struct mc_subled *mc_led_info) ++{ ++ int arr_size = sizeof(drvdata.led_rgb_data.red); ++ ++ memcpy(led_rgb->red, drvdata.led_rgb_data.red, arr_size); ++ memcpy(led_rgb->green, drvdata.led_rgb_data.green, arr_size); ++ memcpy(led_rgb->blue, drvdata.led_rgb_data.blue, arr_size); ++ for (int i = 0; i < 4; i++) { ++ mc_led_info[i].intensity = (drvdata.led_rgb_data.red[i] << 16) | ++ (drvdata.led_rgb_data.green[i] << 8) | ++ drvdata.led_rgb_data.blue[i]; ++ } ++ led_cdev->brightness = drvdata.led_rgb_data.brightness; ++} ++ ++/* Set LEDs. Call after any setup. */ ++static void ally_rgb_resume(void) ++{ ++ struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev; ++ struct led_classdev *led_cdev; ++ struct mc_subled *mc_led_info; ++ ++ if (!led_rgb) ++ return; ++ ++ led_cdev = &led_rgb->led_rgb_dev.led_cdev; ++ mc_led_info = led_rgb->led_rgb_dev.subled_info; ++ ++ if (drvdata.led_rgb_data.initialized) { ++ ally_rgb_restore_settings(led_rgb, led_cdev, mc_led_info); ++ led_rgb->update_rgb = true; ++ ally_rgb_schedule_work(led_rgb); ++ ally_rgb_set_bright_base_max(led_rgb->hdev); ++ } ++} ++ ++static int ally_rgb_register(struct hid_device *hdev, struct ally_rgb_dev *led_rgb) ++{ ++ struct mc_subled *mc_led_info; ++ struct led_classdev *led_cdev; ++ ++ mc_led_info = ++ devm_kmalloc_array(&hdev->dev, 12, sizeof(*mc_led_info), GFP_KERNEL | __GFP_ZERO); ++ if (!mc_led_info) ++ return -ENOMEM; ++ ++ mc_led_info[0].color_index = LED_COLOR_ID_RGB; ++ mc_led_info[1].color_index = LED_COLOR_ID_RGB; ++ mc_led_info[2].color_index = LED_COLOR_ID_RGB; ++ mc_led_info[3].color_index = LED_COLOR_ID_RGB; ++ ++ led_rgb->led_rgb_dev.subled_info = mc_led_info; ++ led_rgb->led_rgb_dev.num_colors = 4; ++ ++ led_cdev = &led_rgb->led_rgb_dev.led_cdev; ++ led_cdev->brightness = 128; ++ led_cdev->name = "ally:rgb:joystick_rings"; ++ led_cdev->max_brightness = 255; ++ led_cdev->brightness_set = ally_rgb_set; ++ ++ if (drvdata.led_rgb_data.initialized) { ++ ally_rgb_restore_settings(led_rgb, led_cdev, mc_led_info); ++ } ++ ++ return devm_led_classdev_multicolor_register(&hdev->dev, &led_rgb->led_rgb_dev); ++} ++ ++static struct ally_rgb_dev *ally_rgb_create(struct hid_device *hdev) ++{ ++ struct ally_rgb_dev *led_rgb; ++ int ret; ++ ++ led_rgb = devm_kzalloc(&hdev->dev, sizeof(struct ally_rgb_dev), GFP_KERNEL); ++ if (!led_rgb) ++ return ERR_PTR(-ENOMEM); ++ ++ ret = ally_rgb_register(hdev, led_rgb); ++ if (ret < 0) { ++ cancel_work_sync(&led_rgb->work); ++ devm_kfree(&hdev->dev, led_rgb); ++ return ERR_PTR(ret); ++ } ++ ++ led_rgb->hdev = hdev; ++ led_rgb->removed = false; ++ ++ INIT_WORK(&led_rgb->work, ally_rgb_do_work); ++ led_rgb->output_worker_initialized = true; ++ spin_lock_init(&led_rgb->lock); ++ ++ ally_rgb_set_bright_base_max(hdev); ++ ++ /* Not marked as initialized unless ally_rgb_set() is called */ ++ if (drvdata.led_rgb_data.initialized) { ++ msleep(1500); ++ led_rgb->update_rgb = true; ++ ally_rgb_schedule_work(led_rgb); ++ } ++ ++ return led_rgb; ++} ++ ++static void ally_rgb_remove(struct hid_device *hdev) ++{ ++ struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev; ++ unsigned long flags; ++ int ep; ++ ++ ep = get_endpoint_address(hdev); ++ if (ep != ROG_ALLY_CFG_INTF_IN) ++ return; ++ ++ if (!drvdata.led_rgb_dev || led_rgb->removed) ++ return; ++ ++ spin_lock_irqsave(&led_rgb->lock, flags); ++ led_rgb->removed = true; ++ led_rgb->output_worker_initialized = false; ++ spin_unlock_irqrestore(&led_rgb->lock, flags); ++ cancel_work_sync(&led_rgb->work); ++ devm_led_classdev_multicolor_unregister(&hdev->dev, &led_rgb->led_rgb_dev); ++ ++ hid_info(hdev, "Removed Ally RGB interface"); ++} ++ ++/**************************************************************************************************/ ++/* ROG Ally driver init */ ++/**************************************************************************************************/ ++ ++static int ally_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, ++ int size) ++{ ++ struct ally_gamepad_cfg *cfg = drvdata.gamepad_cfg; ++ struct ally_x_device *ally_x = drvdata.ally_x; ++ ++ if (ally_x) { ++ if ((hdev->bus == BUS_USB && report->id == ALLY_X_INPUT_REPORT_USB && ++ size == ALLY_X_INPUT_REPORT_USB_SIZE) || ++ (data[0] == 0x5A)) { ++ ally_x_raw_event(ally_x, report, data, size); ++ } else { ++ return -1; ++ } ++ } ++ ++ if (cfg && !ally_x) { ++ input_report_key(cfg->input, KEY_PROG1, data[1] == 0x38); ++ input_report_key(cfg->input, KEY_F16, data[1] == 0xA6); ++ input_report_key(cfg->input, KEY_F17, data[1] == 0xA7); ++ input_report_key(cfg->input, KEY_F18, data[1] == 0xA8); ++ input_sync(cfg->input); ++ } ++ ++ return 0; ++} ++ ++/* ++ * We don't care about any other part of the string except the version section. ++ * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01 ++ */ ++static int mcu_parse_version_string(const u8 *response, size_t response_size) ++{ ++ int dot_count = 0; ++ size_t i; ++ ++ // Look for the second '.' to identify the start of the version ++ for (i = 0; i < response_size; i++) { ++ if (response[i] == '.') { ++ dot_count++; ++ if (dot_count == 2) { ++ int version = ++ simple_strtol((const char *)&response[i + 1], NULL, 10); ++ return (version >= 0) ? version : -EINVAL; ++ } ++ } ++ } ++ ++ return -EINVAL; ++} ++ ++static int mcu_request_version(struct hid_device *hdev) ++{ ++ const u8 request[] = { 0x5a, 0x05, 0x03, 0x31, 0x00, 0x20 }; ++ u8 *response; ++ int ret; ++ ++ response = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); ++ if (!response) ++ return -ENOMEM; ++ ++ ret = asus_dev_set_report(hdev, request, sizeof(request)); ++ if (ret < 0) ++ goto out; ++ ++ ret = asus_dev_get_report(hdev, response, FEATURE_ROG_ALLY_REPORT_SIZE); ++ if (ret < 0) ++ goto out; ++ ++ ret = mcu_parse_version_string(response, FEATURE_ROG_ALLY_REPORT_SIZE); ++out: ++ if (ret < 0) ++ hid_err(hdev, "Failed to get MCU version: %d\n", ret); ++ kfree(response); ++ return ret; ++} ++ ++static void mcu_maybe_warn_version(struct hid_device *hdev, int idProduct) ++{ ++ int min_version, version; ++ struct asus_wmi *asus; ++ struct device *dev; ++ ++ min_version = ROG_ALLY_X_MIN_MCU; ++ version = mcu_request_version(hdev); ++ if (version) { ++ switch (idProduct) { ++ case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY: ++ min_version = ROG_ALLY_MIN_MCU; ++ break; ++ case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X: ++ min_version = ROG_ALLY_X_MIN_MCU; ++ break; ++ } ++ } ++ ++ hid_info(hdev, "Ally device MCU version: %d\n", version); ++ if (version < min_version) { ++ hid_warn(hdev, ++ "The MCU version must be %d or greater\n" ++ "Please update your MCU with official ASUS firmware release\n", ++ min_version); ++ /* Get the asus platform device */ ++ dev = bus_find_device_by_name(&platform_bus_type, NULL, "asus-nb-wmi"); ++ if (dev) { ++ asus = dev_get_drvdata(dev); ++ /* Do not show the powersave attribute if MCU version too low */ ++ if (asus) ++ asus->mcu_powersave_available = false; ++ put_device(dev); ++ } ++ } ++} ++ ++static int ally_hid_init(struct hid_device *hdev) ++{ ++ int ret; ++ ++ ret = asus_dev_set_report(hdev, EC_INIT_STRING, sizeof(EC_INIT_STRING)); ++ if (ret < 0) { ++ hid_err(hdev, "Ally failed to send init command: %d\n", ret); ++ return ret; ++ } ++ ++ ret = asus_dev_set_report(hdev, FORCE_FEEDBACK_OFF, sizeof(FORCE_FEEDBACK_OFF)); ++ if (ret < 0) ++ hid_err(hdev, "Ally failed to send init command: %d\n", ret); ++ ++ return ret; ++} ++ ++static int ally_hid_probe(struct hid_device *hdev, const struct hid_device_id *_id) ++{ ++ struct usb_interface *intf = to_usb_interface(hdev->dev.parent); ++ struct usb_device *udev = interface_to_usbdev(intf); ++ u16 idProduct = le16_to_cpu(udev->descriptor.idProduct); ++ int ret, ep; ++ ++ ep = get_endpoint_address(hdev); ++ if (ep < 0) ++ return ep; ++ ++ if (ep != ROG_ALLY_CFG_INTF_IN && ++ ep != ROG_ALLY_X_INTF_IN) ++ return -ENODEV; ++ ++ ret = hid_parse(hdev); ++ if (ret) { ++ hid_err(hdev, "Parse failed\n"); ++ return ret; ++ } ++ ++ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); ++ if (ret) { ++ hid_err(hdev, "Failed to start HID device\n"); ++ return ret; ++ } ++ ++ ret = hid_hw_open(hdev); ++ if (ret) { ++ hid_err(hdev, "Failed to open HID device\n"); ++ goto err_stop; ++ } ++ ++ /* Initialize MCU even before alloc */ ++ ret = ally_hid_init(hdev); ++ if (ret < 0) ++ return ret; ++ ++ drvdata.hdev = hdev; ++ hid_set_drvdata(hdev, &drvdata); ++ ++ /* This should almost always exist */ ++ if (ep == ROG_ALLY_CFG_INTF_IN) { ++ mcu_maybe_warn_version(hdev, idProduct); ++ ++ drvdata.led_rgb_dev = ally_rgb_create(hdev); ++ if (IS_ERR(drvdata.led_rgb_dev)) ++ hid_err(hdev, "Failed to create Ally gamepad LEDs.\n"); ++ else ++ hid_info(hdev, "Created Ally RGB LED controls.\n"); ++ ++ drvdata.gamepad_cfg = ally_gamepad_cfg_create(hdev); ++ if (IS_ERR(drvdata.gamepad_cfg)) ++ hid_err(hdev, "Failed to create Ally gamepad attributes.\n"); ++ else ++ hid_info(hdev, "Created Ally gamepad attributes.\n"); ++ ++ if (IS_ERR(drvdata.led_rgb_dev) && IS_ERR(drvdata.gamepad_cfg)) ++ goto err_close; ++ } ++ ++ /* May or may not exist */ ++ if (ep == ROG_ALLY_X_INTF_IN) { ++ drvdata.ally_x = ally_x_create(hdev); ++ if (IS_ERR(drvdata.ally_x)) { ++ hid_err(hdev, "Failed to create Ally X gamepad.\n"); ++ drvdata.ally_x = NULL; ++ goto err_close; ++ } ++ hid_info(hdev, "Created Ally X controller.\n"); ++ ++ // Not required since we send this inputs ep through the gamepad input dev ++ if (drvdata.gamepad_cfg && drvdata.gamepad_cfg->input) { ++ input_unregister_device(drvdata.gamepad_cfg->input); ++ hid_info(hdev, "Ally X removed unrequired input dev.\n"); ++ } ++ } ++ ++ return 0; ++ ++err_close: ++ hid_hw_close(hdev); ++err_stop: ++ hid_hw_stop(hdev); ++ return ret; ++} ++ ++static void ally_hid_remove(struct hid_device *hdev) ++{ ++ if (drvdata.led_rgb_dev) ++ ally_rgb_remove(hdev); ++ ++ if (drvdata.ally_x) ++ ally_x_remove(hdev); ++ ++ if (drvdata.gamepad_cfg) ++ ally_cfg_remove(hdev); ++ ++ hid_hw_close(hdev); ++ hid_hw_stop(hdev); ++} ++ ++static int ally_hid_resume(struct hid_device *hdev) ++{ ++ ally_rgb_resume(); ++ ++ return 0; ++} ++ ++static int ally_hid_reset_resume(struct hid_device *hdev) ++{ ++ int ep = get_endpoint_address(hdev); ++ if (ep != ROG_ALLY_CFG_INTF_IN) ++ return 0; ++ ++ ally_hid_init(hdev); ++ ally_rgb_resume(); ++ ++ return 0; ++} ++ ++static int ally_pm_thaw(struct device *dev) ++{ ++ struct hid_device *hdev = to_hid_device(dev); ++ ++ return ally_hid_reset_resume(hdev); ++} ++ ++static int ally_pm_suspend(struct device *dev) ++{ ++ if (drvdata.led_rgb_dev) { ++ ally_rgb_store_settings(); ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops ally_pm_ops = { ++ .thaw = ally_pm_thaw, ++ .suspend = ally_pm_suspend, ++ .poweroff = ally_pm_suspend, ++}; ++ ++MODULE_DEVICE_TABLE(hid, rog_ally_devices); ++ ++static struct hid_driver ++ rog_ally_cfg = { .name = "asus_rog_ally", ++ .id_table = rog_ally_devices, ++ .probe = ally_hid_probe, ++ .remove = ally_hid_remove, ++ .raw_event = ally_raw_event, ++ /* HID is the better place for resume functions, not pm_ops */ ++ .resume = ally_hid_resume, ++ .reset_resume = ally_hid_reset_resume, ++ .driver = { ++ .pm = &ally_pm_ops, ++ } }; ++ ++static int __init rog_ally_init(void) ++{ ++ return hid_register_driver(&rog_ally_cfg); ++} ++ ++static void __exit rog_ally_exit(void) ++{ ++ hid_unregister_driver(&rog_ally_cfg); ++} ++ ++module_init(rog_ally_init); ++module_exit(rog_ally_exit); ++ ++MODULE_AUTHOR("Luke D. Jones"); ++MODULE_DESCRIPTION("HID Driver for ASUS ROG Ally gamepad configuration."); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/hid-asus-ally.h b/drivers/hid/hid-asus-ally.h +new file mode 100644 +index 000000000000..c83817589082 +--- /dev/null ++++ b/drivers/hid/hid-asus-ally.h +@@ -0,0 +1,398 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later ++ * ++ * HID driver for Asus ROG laptops and Ally ++ * ++ * Copyright (c) 2023 Luke Jones ++ */ ++ ++#include ++#include ++ ++/* ++ * the xpad_mode is used inside the mode setting packet and is used ++ * for indexing (xpad_mode - 1) ++ */ ++enum xpad_mode { ++ xpad_mode_game = 0x01, ++ xpad_mode_wasd = 0x02, ++ xpad_mode_mouse = 0x03, ++}; ++ ++/* the xpad_cmd determines which feature is set or queried */ ++enum xpad_cmd { ++ xpad_cmd_set_mode = 0x01, ++ xpad_cmd_set_mapping = 0x02, ++ xpad_cmd_set_js_dz = 0x04, /* deadzones */ ++ xpad_cmd_set_tr_dz = 0x05, /* deadzones */ ++ xpad_cmd_set_vibe_intensity = 0x06, ++ xpad_cmd_set_leds = 0x08, ++ xpad_cmd_check_ready = 0x0A, ++ xpad_cmd_set_turbo = 0x0F, ++ xpad_cmd_set_response_curve = 0x13, ++ xpad_cmd_set_adz = 0x18, ++}; ++ ++/* the xpad_cmd determines which feature is set or queried */ ++enum xpad_cmd_len { ++ xpad_cmd_len_mode = 0x01, ++ xpad_cmd_len_mapping = 0x2c, ++ xpad_cmd_len_deadzone = 0x04, ++ xpad_cmd_len_vibe_intensity = 0x02, ++ xpad_cmd_len_leds = 0x0C, ++ xpad_cmd_len_turbo = 0x20, ++ xpad_cmd_len_response_curve = 0x09, ++ xpad_cmd_len_adz = 0x02, ++}; ++ ++/* Values correspond to the actual HID byte value required */ ++enum btn_pair_index { ++ btn_pair_dpad_u_d = 0x01, ++ btn_pair_dpad_l_r = 0x02, ++ btn_pair_ls_rs = 0x03, ++ btn_pair_lb_rb = 0x04, ++ btn_pair_a_b = 0x05, ++ btn_pair_x_y = 0x06, ++ btn_pair_view_menu = 0x07, ++ btn_pair_m1_m2 = 0x08, ++ btn_pair_lt_rt = 0x09, ++}; ++ ++#define BTN_PAD_A 0x0101000000000000 ++#define BTN_PAD_B 0x0102000000000000 ++#define BTN_PAD_X 0x0103000000000000 ++#define BTN_PAD_Y 0x0104000000000000 ++#define BTN_PAD_LB 0x0105000000000000 ++#define BTN_PAD_RB 0x0106000000000000 ++#define BTN_PAD_LS 0x0107000000000000 ++#define BTN_PAD_RS 0x0108000000000000 ++#define BTN_PAD_DPAD_UP 0x0109000000000000 ++#define BTN_PAD_DPAD_DOWN 0x010A000000000000 ++#define BTN_PAD_DPAD_LEFT 0x010B000000000000 ++#define BTN_PAD_DPAD_RIGHT 0x010C000000000000 ++#define BTN_PAD_LT 0x010D000000000000 ++#define BTN_PAD_RT 0x010E000000000000 ++#define BTN_PAD_VIEW 0x0111000000000000 ++#define BTN_PAD_MENU 0x0112000000000000 ++#define BTN_PAD_XBOX 0x0113000000000000 ++ ++#define BTN_KB_M2 0x02008E0000000000 ++#define BTN_KB_M1 0x02008F0000000000 ++#define BTN_KB_ESC 0x0200760000000000 ++#define BTN_KB_F1 0x0200500000000000 ++#define BTN_KB_F2 0x0200600000000000 ++#define BTN_KB_F3 0x0200400000000000 ++#define BTN_KB_F4 0x02000C0000000000 ++#define BTN_KB_F5 0x0200030000000000 ++#define BTN_KB_F6 0x02000B0000000000 ++#define BTN_KB_F7 0x0200800000000000 ++#define BTN_KB_F8 0x02000A0000000000 ++#define BTN_KB_F9 0x0200010000000000 ++#define BTN_KB_F10 0x0200090000000000 ++#define BTN_KB_F11 0x0200780000000000 ++#define BTN_KB_F12 0x0200070000000000 ++#define BTN_KB_F14 0x0200180000000000 ++#define BTN_KB_F15 0x0200100000000000 ++#define BTN_KB_BACKTICK 0x02000E0000000000 ++#define BTN_KB_1 0x0200160000000000 ++#define BTN_KB_2 0x02001E0000000000 ++#define BTN_KB_3 0x0200260000000000 ++#define BTN_KB_4 0x0200250000000000 ++#define BTN_KB_5 0x02002E0000000000 ++#define BTN_KB_6 0x0200360000000000 ++#define BTN_KB_7 0x02003D0000000000 ++#define BTN_KB_8 0x02003E0000000000 ++#define BTN_KB_9 0x0200460000000000 ++#define BTN_KB_0 0x0200450000000000 ++#define BTN_KB_HYPHEN 0x02004E0000000000 ++#define BTN_KB_EQUALS 0x0200550000000000 ++#define BTN_KB_BACKSPACE 0x0200660000000000 ++#define BTN_KB_TAB 0x02000D0000000000 ++#define BTN_KB_Q 0x0200150000000000 ++#define BTN_KB_W 0x02001D0000000000 ++#define BTN_KB_E 0x0200240000000000 ++#define BTN_KB_R 0x02002D0000000000 ++#define BTN_KB_T 0x02002C0000000000 ++#define BTN_KB_Y 0x0200350000000000 ++#define BTN_KB_U 0x02003C0000000000 ++#define BTN_KB_O 0x0200440000000000 ++#define BTN_KB_P 0x02004D0000000000 ++#define BTN_KB_LBRACKET 0x0200540000000000 ++#define BTN_KB_RBRACKET 0x02005B0000000000 ++#define BTN_KB_BACKSLASH 0x02005D0000000000 ++#define BTN_KB_CAPS 0x0200580000000000 ++#define BTN_KB_A 0x02001C0000000000 ++#define BTN_KB_S 0x02001B0000000000 ++#define BTN_KB_D 0x0200230000000000 ++#define BTN_KB_F 0x02002B0000000000 ++#define BTN_KB_G 0x0200340000000000 ++#define BTN_KB_H 0x0200330000000000 ++#define BTN_KB_J 0x02003B0000000000 ++#define BTN_KB_K 0x0200420000000000 ++#define BTN_KB_L 0x02004B0000000000 ++#define BTN_KB_SEMI 0x02004C0000000000 ++#define BTN_KB_QUOTE 0x0200520000000000 ++#define BTN_KB_RET 0x02005A0000000000 ++#define BTN_KB_LSHIFT 0x0200880000000000 ++#define BTN_KB_Z 0x02001A0000000000 ++#define BTN_KB_X 0x0200220000000000 ++#define BTN_KB_C 0x0200210000000000 ++#define BTN_KB_V 0x02002A0000000000 ++#define BTN_KB_B 0x0200320000000000 ++#define BTN_KB_N 0x0200310000000000 ++#define BTN_KB_M 0x02003A0000000000 ++#define BTN_KB_COMMA 0x0200410000000000 ++#define BTN_KB_PERIOD 0x0200490000000000 ++#define BTN_KB_RSHIFT 0x0200890000000000 ++#define BTN_KB_LCTL 0x02008C0000000000 ++#define BTN_KB_META 0x0200820000000000 ++#define BTN_KB_LALT 0x02008A0000000000 ++#define BTN_KB_SPACE 0x0200290000000000 ++#define BTN_KB_RALT 0x02008B0000000000 ++#define BTN_KB_MENU 0x0200840000000000 ++#define BTN_KB_RCTL 0x02008D0000000000 ++#define BTN_KB_PRNTSCN 0x0200C30000000000 ++#define BTN_KB_SCRLCK 0x02007E0000000000 ++#define BTN_KB_PAUSE 0x0200910000000000 ++#define BTN_KB_INS 0x0200C20000000000 ++#define BTN_KB_HOME 0x0200940000000000 ++#define BTN_KB_PGUP 0x0200960000000000 ++#define BTN_KB_DEL 0x0200C00000000000 ++#define BTN_KB_END 0x0200950000000000 ++#define BTN_KB_PGDWN 0x0200970000000000 ++#define BTN_KB_UP_ARROW 0x0200980000000000 ++#define BTN_KB_DOWN_ARROW 0x0200990000000000 ++#define BTN_KB_LEFT_ARROW 0x0200910000000000 ++#define BTN_KB_RIGHT_ARROW 0x02009B0000000000 ++ ++#define BTN_NUMPAD_LOCK 0x0200770000000000 ++#define BTN_NUMPAD_FWDSLASH 0x0200900000000000 ++#define BTN_NUMPAD_ASTERISK 0x02007C0000000000 ++#define BTN_NUMPAD_HYPHEN 0x02007B0000000000 ++#define BTN_NUMPAD_0 0x0200700000000000 ++#define BTN_NUMPAD_1 0x0200690000000000 ++#define BTN_NUMPAD_2 0x0200720000000000 ++#define BTN_NUMPAD_3 0x02007A0000000000 ++#define BTN_NUMPAD_4 0x02006B0000000000 ++#define BTN_NUMPAD_5 0x0200730000000000 ++#define BTN_NUMPAD_6 0x0200740000000000 ++#define BTN_NUMPAD_7 0x02006C0000000000 ++#define BTN_NUMPAD_8 0x0200750000000000 ++#define BTN_NUMPAD_9 0x02007D0000000000 ++#define BTN_NUMPAD_PLUS 0x0200790000000000 ++#define BTN_NUMPAD_ENTER 0x0200810000000000 ++#define BTN_NUMPAD_PERIOD 0x0200710000000000 ++ ++#define BTN_MOUSE_LCLICK 0x0300000001000000 ++#define BTN_MOUSE_RCLICK 0x0300000002000000 ++#define BTN_MOUSE_MCLICK 0x0300000003000000 ++#define BTN_MOUSE_WHEEL_UP 0x0300000004000000 ++#define BTN_MOUSE_WHEEL_DOWN 0x0300000005000000 ++ ++#define BTN_MEDIA_SCREENSHOT 0x0500001600000000 ++#define BTN_MEDIA_SHOW_KEYBOARD 0x0500001900000000 ++#define BTN_MEDIA_SHOW_DESKTOP 0x0500001C00000000 ++#define BTN_MEDIA_START_RECORDING 0x0500001E00000000 ++#define BTN_MEDIA_MIC_OFF 0x0500000100000000 ++#define BTN_MEDIA_VOL_DOWN 0x0500000200000000 ++#define BTN_MEDIA_VOL_UP 0x0500000300000000 ++ ++#define ALLY_DEVICE_ATTR_WO(_name, _sysfs_name) \ ++ struct device_attribute dev_attr_##_name = \ ++ __ATTR(_sysfs_name, 0200, NULL, _name##_store) ++ ++/* required so we can have nested attributes with same name but different functions */ ++#define ALLY_DEVICE_ATTR_RW(_name, _sysfs_name) \ ++ struct device_attribute dev_attr_##_name = \ ++ __ATTR(_sysfs_name, 0644, _name##_show, _name##_store) ++ ++#define ALLY_DEVICE_ATTR_RO(_name, _sysfs_name) \ ++ struct device_attribute dev_attr_##_name = \ ++ __ATTR(_sysfs_name, 0444, _name##_show, NULL) ++ ++/* button specific macros */ ++#define ALLY_BTN_SHOW(_fname, _btn_name, _secondary) \ ++ static ssize_t _fname##_show(struct device *dev, \ ++ struct device_attribute *attr, char *buf) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ struct btn_data *btn; \ ++ const char* name; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ ++ name = btn_to_name(_secondary ? btn->macro : btn->button); \ ++ return sysfs_emit(buf, "%s\n", name); \ ++ } ++ ++#define ALLY_BTN_STORE(_fname, _btn_name, _secondary) \ ++ static ssize_t _fname##_store(struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ struct btn_data *btn; \ ++ u64 code; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ ++ code = name_to_btn(buf); \ ++ if (_secondary) \ ++ btn->macro = code; \ ++ else \ ++ btn->button = code; \ ++ return count; \ ++ } ++ ++#define ALLY_TURBO_SHOW(_fname, _btn_name) \ ++ static ssize_t _fname##_show(struct device *dev, \ ++ struct device_attribute *attr, char *buf) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ struct btn_data *btn; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ ++ return sysfs_emit(buf, "%d\n", btn->turbo); \ ++ } ++ ++#define ALLY_TURBO_STORE(_fname, _btn_name) \ ++ static ssize_t _fname##_store(struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ struct btn_data *btn; \ ++ bool turbo; \ ++ int ret; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ ++ ret = kstrtobool(buf, &turbo); \ ++ if (ret) \ ++ return ret; \ ++ btn->turbo = turbo; \ ++ return count; \ ++ } ++ ++#define ALLY_DEADZONE_SHOW(_fname, _axis_name) \ ++ static ssize_t _fname##_show(struct device *dev, \ ++ struct device_attribute *attr, char *buf) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ struct deadzone *dz; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ dz = &ally_cfg->_axis_name; \ ++ return sysfs_emit(buf, "%d %d\n", dz->inner, dz->outer); \ ++ } ++ ++#define ALLY_DEADZONE_STORE(_fname, _axis_name) \ ++ static ssize_t _fname##_store(struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ struct hid_device *hdev = to_hid_device(dev); \ ++ u32 inner, outer; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ if (sscanf(buf, "%d %d", &inner, &outer) != 2) \ ++ return -EINVAL; \ ++ if (inner > 64 || outer > 64 || inner > outer) \ ++ return -EINVAL; \ ++ ally_cfg->_axis_name.inner = inner; \ ++ ally_cfg->_axis_name.outer = outer; \ ++ _gamepad_apply_deadzones(hdev, ally_cfg); \ ++ return count; \ ++ } ++ ++#define ALLY_DEADZONES(_fname, _mname) \ ++ ALLY_DEADZONE_SHOW(_fname##_deadzone, _mname); \ ++ ALLY_DEADZONE_STORE(_fname##_deadzone, _mname); \ ++ ALLY_DEVICE_ATTR_RW(_fname##_deadzone, deadzone) ++ ++/* response curve macros */ ++#define ALLY_RESP_CURVE_SHOW(_fname, _mname) \ ++static ssize_t _fname##_show(struct device *dev, \ ++ struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ return sysfs_emit(buf, "%d\n", ally_cfg->ls_rc._mname); \ ++ } ++ ++#define ALLY_RESP_CURVE_STORE(_fname, _mname) \ ++static ssize_t _fname##_store(struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ ++ int ret, val; \ ++ if (!drvdata.gamepad_cfg) \ ++ return -ENODEV; \ ++ ret = kstrtoint(buf, 0, &val); \ ++ if (ret) \ ++ return ret; \ ++ if (val < 0 || val > 100) \ ++ return -EINVAL; \ ++ ally_cfg->ls_rc._mname = val; \ ++ return count; \ ++ } ++ ++/* _point_n must start at 1 */ ++#define ALLY_JS_RC_POINT(_fname, _mname, _num) \ ++ ALLY_RESP_CURVE_SHOW(_fname##_##_mname##_##_num, _mname##_pct_##_num); \ ++ ALLY_RESP_CURVE_STORE(_fname##_##_mname##_##_num, _mname##_pct_##_num); \ ++ ALLY_DEVICE_ATTR_RW(_fname##_##_mname##_##_num, curve_##_mname##_pct_##_num) ++ ++#define ALLY_BTN_ATTRS_GROUP(_name, _fname) \ ++ static struct attribute *_fname##_attrs[] = { \ ++ &dev_attr_##_fname.attr, \ ++ &dev_attr_##_fname##_macro.attr, \ ++ }; \ ++ static const struct attribute_group _fname##_attr_group = { \ ++ .name = __stringify(_name), \ ++ .attrs = _fname##_attrs, \ ++ } ++ ++#define _ALLY_BTN_REMAP(_fname, _btn_name) \ ++ ALLY_BTN_SHOW(btn_mapping_##_fname##_remap, _btn_name, false); \ ++ ALLY_BTN_STORE(btn_mapping_##_fname##_remap, _btn_name, false); \ ++ ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_remap, remap); ++ ++#define _ALLY_BTN_MACRO(_fname, _btn_name) \ ++ ALLY_BTN_SHOW(btn_mapping_##_fname##_macro, _btn_name, true); \ ++ ALLY_BTN_STORE(btn_mapping_##_fname##_macro, _btn_name, true); \ ++ ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_macro, macro_remap); ++ ++#define ALLY_BTN_MAPPING(_fname, _btn_name) \ ++ _ALLY_BTN_REMAP(_fname, _btn_name) \ ++ _ALLY_BTN_MACRO(_fname, _btn_name) \ ++ static struct attribute *_fname##_attrs[] = { \ ++ &dev_attr_btn_mapping_##_fname##_remap.attr, \ ++ &dev_attr_btn_mapping_##_fname##_macro.attr, \ ++ NULL, \ ++ }; \ ++ static const struct attribute_group btn_mapping_##_fname##_attr_group = { \ ++ .name = __stringify(btn_##_fname), \ ++ .attrs = _fname##_attrs, \ ++ } ++ ++#define ALLY_TURBO_BTN_MAPPING(_fname, _btn_name) \ ++ _ALLY_BTN_REMAP(_fname, _btn_name) \ ++ _ALLY_BTN_MACRO(_fname, _btn_name) \ ++ ALLY_TURBO_SHOW(btn_mapping_##_fname##_turbo, _btn_name); \ ++ ALLY_TURBO_STORE(btn_mapping_##_fname##_turbo, _btn_name); \ ++ ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_turbo, turbo); \ ++ static struct attribute *_fname##_turbo_attrs[] = { \ ++ &dev_attr_btn_mapping_##_fname##_remap.attr, \ ++ &dev_attr_btn_mapping_##_fname##_macro.attr, \ ++ &dev_attr_btn_mapping_##_fname##_turbo.attr, \ ++ NULL, \ ++ }; \ ++ static const struct attribute_group btn_mapping_##_fname##_attr_group = { \ ++ .name = __stringify(btn_##_fname), \ ++ .attrs = _fname##_turbo_attrs, \ ++ } +diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c +index 46e3e42f9eb5..fb29486478f7 100644 +--- a/drivers/hid/hid-asus.c ++++ b/drivers/hid/hid-asus.c +@@ -52,6 +52,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); + #define FEATURE_KBD_LED_REPORT_ID1 0x5d + #define FEATURE_KBD_LED_REPORT_ID2 0x5e + ++#define ROG_ALLY_CFG_INTF_IN 0x83 ++#define ROG_ALLY_CFG_INTF_OUT 0x04 ++#define ROG_ALLY_X_INTF_IN 0x87 ++ + #define SUPPORT_KBD_BACKLIGHT BIT(0) + + #define MAX_TOUCH_MAJOR 8 +@@ -84,6 +88,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); + #define QUIRK_MEDION_E1239T BIT(10) + #define QUIRK_ROG_NKEY_KEYBOARD BIT(11) + #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) ++#define QUIRK_ROG_ALLY_XPAD BIT(13) + + #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ + QUIRK_NO_INIT_REPORTS | \ +@@ -1029,6 +1034,17 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + + drvdata->quirks = id->driver_data; + ++ /* Ignore these endpoints as they are used by hid-asus-ally */ ++ if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { ++ struct usb_interface *intf = to_usb_interface(hdev->dev.parent); ++ struct usb_host_endpoint *ep = intf->cur_altsetting->endpoint; ++ ++ if (ep->desc.bEndpointAddress == ROG_ALLY_X_INTF_IN || ++ ep->desc.bEndpointAddress == ROG_ALLY_CFG_INTF_IN || ++ ep->desc.bEndpointAddress == ROG_ALLY_CFG_INTF_OUT) ++ return -ENODEV; ++ } ++ + /* + * T90CHI's keyboard dock returns same ID values as T100CHI's dock. + * Thus, identify T90CHI dock with product name string. +@@ -1280,10 +1296,10 @@ static const struct hid_device_id asus_devices[] = { + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY), +- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, ++ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD}, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X), +- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, ++ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD), + QUIRK_ROG_CLAYMORE_II_KEYBOARD }, +@@ -1327,4 +1343,5 @@ static struct hid_driver asus_driver = { + }; + module_hid_driver(asus_driver); + ++MODULE_IMPORT_NS("ASUS_WMI"); + MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +index c448de53bf91..6ee1790f2212 100644 +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -210,6 +210,7 @@ + #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 + #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30 + #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6 ++#define USB_DEVICE_ID_ASUSTEK_ROG_RAIKIRI_PAD 0x1abb + #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe + #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c + #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 0258dd879d64..b160173a530e 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -267,6 +267,18 @@ config ASUS_WIRELESS + If you choose to compile this driver as a module the module will be + called asus-wireless. + ++config ASUS_ARMOURY ++ tristate "ASUS Armoury driver" ++ depends on ASUS_WMI ++ select FW_ATTR_CLASS ++ help ++ Say Y here if you have a WMI aware Asus machine and would like to use the ++ firmware_attributes API to control various settings typically exposed in ++ the ASUS Armoury Crate application available on Windows. ++ ++ To compile this driver as a module, choose M here: the module will ++ be called asus-armoury. ++ + config ASUS_WMI + tristate "ASUS WMI Driver" + depends on ACPI_WMI +@@ -289,6 +301,15 @@ config ASUS_WMI + To compile this driver as a module, choose M here: the module will + be called asus-wmi. + ++config ASUS_WMI_DEPRECATED_ATTRS ++ bool "BIOS option support in WMI platform (DEPRECATED)" ++ depends on ASUS_WMI ++ default y ++ help ++ Say Y to expose the configurable BIOS options through the asus-wmi ++ driver. This can be used with or without the asus-armoury driver which ++ has the same attributes, but more, and better features. ++ + config ASUS_NB_WMI + tristate "Asus Notebook WMI Driver" + depends on ASUS_WMI +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index e1b142947067..fe3e7e7dede8 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -32,6 +32,7 @@ obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o + # ASUS + obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o + obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o ++obj-$(CONFIG_ASUS_ARMOURY) += asus-armoury.o + obj-$(CONFIG_ASUS_WMI) += asus-wmi.o + obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o + obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o +diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c +new file mode 100644 +index 000000000000..7f968469a9e9 +--- /dev/null ++++ b/drivers/platform/x86/asus-armoury.c +@@ -0,0 +1,1153 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Asus Armoury (WMI) attributes driver. This driver uses the fw_attributes ++ * class to expose the various WMI functions that many gaming and some ++ * non-gaming ASUS laptops have available. ++ * These typically don't fit anywhere else in the sysfs such as under LED class, ++ * hwmon or other, and are set in Windows using the ASUS Armoury Crate tool. ++ * ++ * Copyright(C) 2024 Luke Jones ++ */ ++ ++#include "asus-wmi.h" ++#include "linux/cleanup.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "asus-armoury.h" ++#include "firmware_attributes_class.h" ++ ++#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" ++ ++#define ASUS_MINI_LED_MODE_MASK 0x03 ++/* Standard modes for devices with only on/off */ ++#define ASUS_MINI_LED_OFF 0x00 ++#define ASUS_MINI_LED_ON 0x01 ++/* Like "on" but the effect is more vibrant or brighter */ ++#define ASUS_MINI_LED_STRONG_MODE 0x02 ++/* New modes for devices with 3 mini-led mode types */ ++#define ASUS_MINI_LED_2024_WEAK 0x00 ++#define ASUS_MINI_LED_2024_STRONG 0x01 ++#define ASUS_MINI_LED_2024_OFF 0x02 ++ ++#define ASUS_POWER_CORE_MASK GENMASK(15, 8) ++#define ASUS_PERF_CORE_MASK GENMASK(7, 0) ++ ++enum cpu_core_type { ++ CPU_CORE_PERF = 0, ++ CPU_CORE_POWER, ++}; ++ ++enum cpu_core_value { ++ CPU_CORE_DEFAULT = 0, ++ CPU_CORE_MIN, ++ CPU_CORE_MAX, ++ CPU_CORE_CURRENT, ++}; ++ ++#define CPU_PERF_CORE_COUNT_MIN 4 ++#define CPU_POWR_CORE_COUNT_MIN 0 ++ ++static struct asus_wmi_armoury_interface wmi_armoury_interface; ++ ++static struct asus_armoury_priv asus_armoury = { ++ .mutex = __MUTEX_INITIALIZER(asus_armoury.mutex) ++}; ++ ++struct fw_attrs_group { ++ bool pending_reboot; ++}; ++ ++static struct fw_attrs_group fw_attrs = { ++ .pending_reboot = false, ++}; ++ ++struct asus_attr_group { ++ const struct attribute_group *attr_group; ++ u32 wmi_devid; ++}; ++static const struct asus_attr_group armoury_attr_groups[]; ++ ++static bool asus_wmi_is_present(u32 dev_id) ++{ ++ u32 retval; ++ int status; ++ ++ status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, &retval); ++ pr_debug("%s called (0x%08x), retval: 0x%08x\n", __func__, dev_id, retval); ++ ++ return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT); ++} ++ ++static void asus_set_reboot_and_signal_event(void) ++{ ++ fw_attrs.pending_reboot = true; ++ kobject_uevent(&asus_armoury.fw_attr_dev->kobj, KOBJ_CHANGE); ++} ++ ++static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) ++{ ++ return sysfs_emit(buf, "%d\n", fw_attrs.pending_reboot); ++} ++ ++static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); ++ ++static bool asus_bios_requires_reboot(struct kobj_attribute *attr) ++{ ++ return !strcmp(attr->attr.name, "gpu_mux_mode") || ++ !strcmp(attr->attr.name, "cores_performance") || ++ !strcmp(attr->attr.name, "cores_efficiency") || ++ !strcmp(attr->attr.name, "panel_hd_mode"); ++} ++ ++static int armoury_wmi_set_devstate(struct kobj_attribute *attr, u32 value, u32 wmi_dev) ++{ ++ u32 result; ++ int err; ++ ++ guard(mutex)(&asus_armoury.mutex); ++ err = asus_wmi_set_devstate(wmi_dev, value, &result); ++ if (err) { ++ pr_err("Failed to set %s: %d\n", attr->attr.name, err); ++ return err; ++ } ++ /* ++ * !1 is usually considered a fail by ASUS, but some WMI methods do use > 1 ++ * to return a status code or similar. ++ */ ++ if (result < 1) { ++ pr_err("Failed to set %s: (result): 0x%x\n", attr->attr.name, result); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++/** ++ * attr_int_store() - Send an int to wmi method, checks if within min/max exclusive. ++ * @kobj: Pointer to the driver object. ++ * @attr: Pointer to the attribute calling this function. ++ * @buf: The buffer to read from, this is parsed to `int` type. ++ * @count: Required by sysfs attribute macros, pass in from the callee attr. ++ * @min: Minimum accepted value. Below this returns -EINVAL. ++ * @max: Maximum accepted value. Above this returns -EINVAL. ++ * @store_value: Pointer to where the parsed value should be stored. ++ * @wmi_dev: The WMI function ID to use. ++ * ++ * This function is intended to be generic so it can be called from any "_store" ++ * attribute which works only with integers. The integer to be sent to the WMI method ++ * is range checked and an error returned if out of range. ++ * ++ * If the value is valid and WMI is success, then the sysfs attribute is notified ++ * and if asus_bios_requires_reboot() is true then reboot attribute is also notified. ++ * ++ * Returns: Either count, or an error. ++ */ ++static ssize_t attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, ++ size_t count, u32 min, u32 max, u32 *store_value, u32 wmi_dev) ++{ ++ u32 value; ++ int err; ++ ++ err = kstrtouint(buf, 10, &value); ++ if (err) ++ return err; ++ ++ if (value < min || value > max) ++ return -EINVAL; ++ ++ err = armoury_wmi_set_devstate(attr, value, wmi_dev); ++ if (err) ++ return err; ++ ++ if (store_value != NULL) ++ *store_value = value; ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ ++ if (asus_bios_requires_reboot(attr)) ++ asus_set_reboot_and_signal_event(); ++ ++ return count; ++} ++ ++ssize_t attr_uint_cache(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, ++ size_t count, u32 min, u32 max, u32 *store_value) ++{ ++ u32 value; ++ int err; ++ ++ err = kstrtouint(buf, 10, &value); ++ if (err) ++ return err; ++ ++ if (value < min || value > max) ++ return -EINVAL; ++ ++ if (store_value != NULL) ++ *store_value = value; ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ ++ return count; ++} ++ ++/* Mini-LED mode **************************************************************/ ++static ssize_t mini_led_mode_current_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ u32 value; ++ int err; ++ ++ err = asus_wmi_get_devstate_dsts(asus_armoury.mini_led_dev_id, &value); ++ if (err) ++ return err; ++ ++ value &= ASUS_MINI_LED_MODE_MASK; ++ ++ /* ++ * Remap the mode values to match previous generation mini-LED. The last gen ++ * WMI 0 == off, while on this version WMI 2 == off (flipped). ++ */ ++ if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) { ++ switch (value) { ++ case ASUS_MINI_LED_2024_WEAK: ++ value = ASUS_MINI_LED_ON; ++ break; ++ case ASUS_MINI_LED_2024_STRONG: ++ value = ASUS_MINI_LED_STRONG_MODE; ++ break; ++ case ASUS_MINI_LED_2024_OFF: ++ value = ASUS_MINI_LED_OFF; ++ break; ++ } ++ } ++ ++ return sysfs_emit(buf, "%u\n", value); ++} ++ ++static ssize_t mini_led_mode_current_value_store(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ u32 mode; ++ int err; ++ ++ err = kstrtou32(buf, 10, &mode); ++ if (err) ++ return err; ++ ++ if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE && ++ mode > ASUS_MINI_LED_ON) ++ return -EINVAL; ++ if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2 && ++ mode > ASUS_MINI_LED_STRONG_MODE) ++ return -EINVAL; ++ ++ /* ++ * Remap the mode values so expected behaviour is the same as the last ++ * generation of mini-LED with 0 == off, 1 == on. ++ */ ++ if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) { ++ switch (mode) { ++ case ASUS_MINI_LED_OFF: ++ mode = ASUS_MINI_LED_2024_OFF; ++ break; ++ case ASUS_MINI_LED_ON: ++ mode = ASUS_MINI_LED_2024_WEAK; ++ break; ++ case ASUS_MINI_LED_STRONG_MODE: ++ mode = ASUS_MINI_LED_2024_STRONG; ++ break; ++ } ++ } ++ ++ err = armoury_wmi_set_devstate(attr, mode, asus_armoury.mini_led_dev_id); ++ if (err) ++ return err; ++ ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ ++ return count; ++} ++ ++static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ switch (asus_armoury.mini_led_dev_id) { ++ case ASUS_WMI_DEVID_MINI_LED_MODE: ++ return sysfs_emit(buf, "0;1\n"); ++ case ASUS_WMI_DEVID_MINI_LED_MODE2: ++ return sysfs_emit(buf, "0;1;2\n"); ++ } ++ ++ return sysfs_emit(buf, "0\n"); ++} ++ ++ATTR_GROUP_ENUM_CUSTOM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode"); ++ ++static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj, ++ struct kobj_attribute *attr, const char *buf, ++ size_t count) ++{ ++ int result, err; ++ u32 optimus; ++ ++ err = kstrtou32(buf, 10, &optimus); ++ if (err) ++ return err; ++ ++ if (optimus > 1) ++ return -EINVAL; ++ ++ if (asus_wmi_is_present(ASUS_WMI_DEVID_DGPU)) { ++ err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_DGPU, &result); ++ if (err) ++ return err; ++ if (result && !optimus) { ++ err = -ENODEV; ++ pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %02X %02X %d\n", ++ result, optimus, err); ++ return err; ++ } ++ } ++ ++ if (asus_wmi_is_present(ASUS_WMI_DEVID_EGPU)) { ++ err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_EGPU, &result); ++ if (err) ++ return err; ++ if (result && !optimus) { ++ err = -ENODEV; ++ pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", ++ err); ++ return err; ++ } ++ } ++ ++ err = armoury_wmi_set_devstate(attr, optimus, asus_armoury.gpu_mux_dev_id); ++ if (err) ++ return err; ++ ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ asus_set_reboot_and_signal_event(); ++ ++ return count; ++} ++WMI_SHOW_INT(gpu_mux_mode_current_value, "%d\n", asus_armoury.gpu_mux_dev_id); ++ATTR_GROUP_BOOL_CUSTOM(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode"); ++ ++/* ++ * A user may be required to store the value twice, typical store first, then ++ * rescan PCI bus to activate power, then store a second time to save correctly. ++ */ ++static ssize_t dgpu_disable_current_value_store(struct kobject *kobj, ++ struct kobj_attribute *attr, const char *buf, ++ size_t count) ++{ ++ int result, err; ++ u32 disable; ++ ++ err = kstrtou32(buf, 10, &disable); ++ if (err) ++ return err; ++ ++ if (disable > 1) ++ return -EINVAL; ++ ++ if (asus_armoury.gpu_mux_dev_id) { ++ err = asus_wmi_get_devstate_dsts(asus_armoury.gpu_mux_dev_id, &result); ++ if (err) ++ return err; ++ if (!result && disable) { ++ err = -ENODEV; ++ pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err); ++ return err; ++ } ++ // TODO: handle a > 1 result, shouold do a PCI rescan and run again ++ } ++ ++ err = armoury_wmi_set_devstate(attr, disable, ASUS_WMI_DEVID_DGPU); ++ if (err) ++ return err; ++ ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ ++ return count; ++} ++WMI_SHOW_INT(dgpu_disable_current_value, "%d\n", ASUS_WMI_DEVID_DGPU); ++ATTR_GROUP_BOOL_CUSTOM(dgpu_disable, "dgpu_disable", "Disable the dGPU"); ++ ++/* The ACPI call to enable the eGPU also disables the internal dGPU */ ++static ssize_t egpu_enable_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 enable; ++ ++ err = kstrtou32(buf, 10, &enable); ++ if (err) ++ return err; ++ ++ if (enable > 1) ++ return -EINVAL; ++ ++ err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_EGPU_CONNECTED, &result); ++ if (err) { ++ pr_warn("Failed to get eGPU connection status: %d\n", err); ++ return err; ++ } ++ ++ if (asus_armoury.gpu_mux_dev_id) { ++ err = asus_wmi_get_devstate_dsts(asus_armoury.gpu_mux_dev_id, &result); ++ if (err) { ++ pr_warn("Failed to get GPU MUX status: %d\n", result); ++ return result; ++ } ++ if (!result && enable) { ++ err = -ENODEV; ++ pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err); ++ return err; ++ } ++ } ++ ++ err = armoury_wmi_set_devstate(attr, enable, ASUS_WMI_DEVID_EGPU); ++ if (err) ++ return err; ++ ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ ++ return count; ++} ++WMI_SHOW_INT(egpu_enable_current_value, "%d\n", ASUS_WMI_DEVID_EGPU); ++ATTR_GROUP_BOOL_CUSTOM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)"); ++ ++/* Device memory available to APU */ ++ ++static ssize_t apu_mem_current_value_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ int err; ++ u32 mem; ++ ++ err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_APU_MEM, &mem); ++ if (err) ++ return err; ++ ++ switch (mem) { ++ case 0x100: ++ mem = 0; ++ break; ++ case 0x102: ++ mem = 1; ++ break; ++ case 0x103: ++ mem = 2; ++ break; ++ case 0x104: ++ mem = 3; ++ break; ++ case 0x105: ++ mem = 4; ++ break; ++ case 0x106: ++ /* This is out of order and looks wrong but is correct */ ++ mem = 8; ++ break; ++ case 0x107: ++ mem = 5; ++ break; ++ case 0x108: ++ mem = 6; ++ break; ++ case 0x109: ++ mem = 7; ++ break; ++ default: ++ mem = 4; ++ break; ++ } ++ ++ return sysfs_emit(buf, "%u\n", mem); ++} ++ ++static ssize_t apu_mem_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int result, err; ++ u32 requested, mem; ++ ++ result = kstrtou32(buf, 10, &requested); ++ if (result) ++ return result; ++ ++ switch (requested) { ++ case 0: ++ mem = 0x000; ++ break; ++ case 1: ++ mem = 0x102; ++ break; ++ case 2: ++ mem = 0x103; ++ break; ++ case 3: ++ mem = 0x104; ++ break; ++ case 4: ++ mem = 0x105; ++ break; ++ case 5: ++ mem = 0x107; ++ break; ++ case 6: ++ mem = 0x108; ++ break; ++ case 7: ++ mem = 0x109; ++ break; ++ case 8: ++ /* This is out of order and looks wrong but is correct */ ++ mem = 0x106; ++ break; ++ default: ++ return -EIO; ++ } ++ ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_APU_MEM, mem, &result); ++ if (err) { ++ pr_warn("Failed to set apu_mem: %d\n", err); ++ return err; ++ } ++ ++ pr_info("APU memory changed to %uGB, reboot required\n", requested); ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ ++ asus_set_reboot_and_signal_event(); ++ ++ return count; ++} ++ ++static ssize_t apu_mem_possible_values_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "0;1;2;3;4;5;6;7;8\n"); ++} ++ATTR_GROUP_ENUM_CUSTOM(apu_mem, "apu_mem", "Set available system RAM (in GB) for the APU to use"); ++ ++static int init_max_cpu_cores(void) ++{ ++ u32 cores; ++ int err; ++ ++ err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES_MAX, &cores); ++ if (err) ++ return err; ++ ++ cores &= ~ASUS_WMI_DSTS_PRESENCE_BIT; ++ asus_armoury.cpu_cores->max_power_cores = FIELD_GET(ASUS_POWER_CORE_MASK, cores); ++ asus_armoury.cpu_cores->max_perf_cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores); ++ ++ err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES, &cores); ++ if (err) { ++ pr_err("Could not get CPU core count: error %d", err); ++ return err; ++ } ++ ++ asus_armoury.cpu_cores->cur_perf_cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores); ++ asus_armoury.cpu_cores->cur_power_cores = FIELD_GET(ASUS_POWER_CORE_MASK, cores); ++ ++ asus_armoury.cpu_cores->min_perf_cores = CPU_PERF_CORE_COUNT_MIN; ++ asus_armoury.cpu_cores->min_power_cores = CPU_POWR_CORE_COUNT_MIN; ++ ++ return 0; ++} ++ ++static ssize_t cores_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf, ++ enum cpu_core_type core_type, enum cpu_core_value core_value) ++{ ++ u32 cores; ++ ++ switch (core_value) { ++ case CPU_CORE_DEFAULT: ++ case CPU_CORE_MAX: ++ if (core_type == CPU_CORE_PERF) ++ return sysfs_emit(buf, "%d\n", ++ asus_armoury.cpu_cores->max_perf_cores); ++ else ++ return sysfs_emit(buf, "%d\n", ++ asus_armoury.cpu_cores->max_power_cores); ++ case CPU_CORE_MIN: ++ if (core_type == CPU_CORE_PERF) ++ return sysfs_emit(buf, "%d\n", ++ asus_armoury.cpu_cores->min_perf_cores); ++ else ++ return sysfs_emit(buf, "%d\n", ++ asus_armoury.cpu_cores->min_power_cores); ++ default: ++ break; ++ } ++ ++ if (core_type == CPU_CORE_PERF) ++ cores = asus_armoury.cpu_cores->cur_perf_cores; ++ else ++ cores = asus_armoury.cpu_cores->cur_power_cores; ++ ++ return sysfs_emit(buf, "%d\n", cores); ++} ++ ++static ssize_t cores_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, ++ const char *buf, enum cpu_core_type core_type) ++{ ++ u32 new_cores, perf_cores, power_cores, out_val, min, max; ++ int result, err; ++ ++ result = kstrtou32(buf, 10, &new_cores); ++ if (result) ++ return result; ++ ++ if (core_type == CPU_CORE_PERF) { ++ perf_cores = new_cores; ++ power_cores = out_val = asus_armoury.cpu_cores->cur_power_cores; ++ min = asus_armoury.cpu_cores->min_perf_cores; ++ max = asus_armoury.cpu_cores->max_perf_cores; ++ } else { ++ perf_cores = asus_armoury.cpu_cores->cur_perf_cores; ++ power_cores = out_val = new_cores; ++ min = asus_armoury.cpu_cores->min_power_cores; ++ max = asus_armoury.cpu_cores->max_power_cores; ++ } ++ ++ if (new_cores < min || new_cores > max) ++ return -EINVAL; ++ ++ out_val = 0; ++ out_val |= FIELD_PREP(ASUS_PERF_CORE_MASK, perf_cores); ++ out_val |= FIELD_PREP(ASUS_POWER_CORE_MASK, power_cores); ++ ++ mutex_lock(&asus_armoury.mutex); ++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_CORES, out_val, &result); ++ mutex_unlock(&asus_armoury.mutex); ++ ++ if (err) { ++ pr_warn("Failed to set CPU core count: %d\n", err); ++ return err; ++ } ++ ++ if (result > 1) { ++ pr_warn("Failed to set CPU core count (result): 0x%x\n", result); ++ return -EIO; ++ } ++ ++ pr_info("CPU core count changed, reboot required\n"); ++ sysfs_notify(kobj, NULL, attr->attr.name); ++ asus_set_reboot_and_signal_event(); ++ ++ return 0; ++} ++ ++static ssize_t cores_performance_min_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_MIN); ++} ++ ++static ssize_t cores_performance_max_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_MAX); ++} ++ ++static ssize_t cores_performance_default_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_DEFAULT); ++} ++ ++static ssize_t cores_performance_current_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_CURRENT); ++} ++ ++static ssize_t cores_performance_current_value_store(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int err; ++ ++ err = cores_current_value_store(kobj, attr, buf, CPU_CORE_PERF); ++ if (err) ++ return err; ++ ++ return count; ++} ++ATTR_GROUP_CORES_RW(cores_performance, "cores_performance", ++ "Set the max available performance cores"); ++ ++static ssize_t cores_efficiency_min_value_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_MIN); ++} ++ ++static ssize_t cores_efficiency_max_value_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_MAX); ++} ++ ++static ssize_t cores_efficiency_default_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_DEFAULT); ++} ++ ++static ssize_t cores_efficiency_current_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, char *buf) ++{ ++ return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_CURRENT); ++} ++ ++static ssize_t cores_efficiency_current_value_store(struct kobject *kobj, ++ struct kobj_attribute *attr, const char *buf, ++ size_t count) ++{ ++ int err; ++ ++ err = cores_current_value_store(kobj, attr, buf, CPU_CORE_POWER); ++ if (err) ++ return err; ++ ++ return count; ++} ++ATTR_GROUP_CORES_RW(cores_efficiency, "cores_efficiency", ++ "Set the max available efficiency cores"); ++ ++static ssize_t ppt_enabled_current_value_show(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ char *buf) ++{ ++ bool enabled = false; ++ ++ if (wmi_armoury_interface.wmi_driver) ++ enabled = asus_wmi_get_fan_curves_enabled(0); ++ ++ return sysfs_emit(buf, "%d\n", enabled); ++} ++ ++static ssize_t ppt_enabled_current_value_store(struct kobject *kobj, ++ struct kobj_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct asus_wmi *asus = wmi_armoury_interface.wmi_driver; ++ const char *name; ++ u32 wmi_dev, set; ++ bool value; ++ int err, i; ++ ++ if (!asus) { ++ pr_debug("%s: wmi_driver is NULL\n", __func__); ++ return -ENODEV; ++ } ++ ++ err = kstrtobool(buf, &value); ++ if (err) ++ return err; ++ ++ /* Set enabled state */ ++ err = asus_wmi_set_fan_curves_enabled(asus, value); ++ if (err) ++ return err; ++ ++ for (i = 0; i < 21; i++) { ++ name = armoury_attr_groups[i].attr_group->name; ++ wmi_dev = armoury_attr_groups[i].wmi_devid; ++ ++ if (!asus_wmi_is_present(wmi_dev)) ++ continue; ++ ++ if (!strcmp(name, "ppt_pl1_spl")) ++ set = asus_armoury.rog_tunables->ppt_pl1_spl; ++ if (!strcmp(name, "ppt_pl2_sppt")) ++ set = asus_armoury.rog_tunables->ppt_pl2_sppt; ++ if (!strcmp(name, "ppt_pl3_fppt")) ++ set = asus_armoury.rog_tunables->ppt_pl3_fppt; ++ if (!strcmp(name, "ppt_apu_sppt")) ++ set = asus_armoury.rog_tunables->ppt_apu_sppt; ++ if (!strcmp(name, "ppt_apu_sppt")) ++ set = asus_armoury.rog_tunables->ppt_apu_sppt; ++ if (!strcmp(name, "nv_dynamic_boost")) ++ set = asus_armoury.rog_tunables->nv_dynamic_boost; ++ if (!strcmp(name, "nv_temp_target")) ++ set = asus_armoury.rog_tunables->nv_temp_target; ++ // if (!strcmp(name, "nv_base_tgp")) ++ // set = asus_armoury.rog_tunables->nv_base_tgp; ++ // if (!strcmp(name, "dgpu_tgp")) ++ // set = asus_armoury.rog_tunables->dgpu_tgp; ++ err = armoury_wmi_set_devstate(attr, value, wmi_dev); ++ if (err) ++ return err; ++ } ++ ++ notify_fan_curves_changed(); ++ return count; ++} ++ ++ATTR_GROUP_BOOL_CUSTOM(ppt_enabled, "ppt_enabled", ++ "Enable PPT tuning and custom fan curves"); ++ ++/* Simple attribute creation */ ++ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, "ppt_pl1_spl", ASUS_WMI_DEVID_PPT_PL1_SPL, ++ "Set the CPU slow package limit"); ++ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, "ppt_pl2_sppt", ASUS_WMI_DEVID_PPT_PL2_SPPT, ++ "Set the CPU fast package limit"); ++ATTR_GROUP_ROG_TUNABLE(ppt_pl3_fppt, "ppt_pl3_fppt", ASUS_WMI_DEVID_PPT_FPPT, ++ "Set the CPU fastest package limit"); ++ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, "ppt_apu_sppt", ASUS_WMI_DEVID_PPT_APU_SPPT, ++ "Set the APU package limit"); ++ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, "ppt_platform_sppt", ASUS_WMI_DEVID_PPT_PLAT_SPPT, ++ "Set the platform package limit"); ++ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, "nv_dynamic_boost", ASUS_WMI_DEVID_NV_DYN_BOOST, ++ "Set the Nvidia dynamic boost limit"); ++ATTR_GROUP_ROG_TUNABLE(nv_temp_target, "nv_temp_target", ASUS_WMI_DEVID_NV_THERM_TARGET, ++ "Set the Nvidia max thermal limit"); ++ATTR_GROUP_ROG_TUNABLE(nv_tgp, "dgpu_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP, ++ "Set the additional TGP on top of the base TGP"); ++ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, "nv_base_tgp", ASUS_WMI_DEVID_DGPU_BASE_TGP, ++ "Read the base TGP value"); ++ ++ ++ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2", ++ "Show the current mode of charging"); ++ ++ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND, ++ "Set the boot POST sound"); ++ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE, ++ "Set MCU powersaving mode"); ++ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD, ++ "Set the panel refresh overdrive"); ++ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD, ++ "Set the panel HD mode to UHD<0> or FHD<1>"); ++ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED, ++ "Show the eGPU connection status"); ++ ++/* If an attribute does not require any special case handling add it here */ ++static const struct asus_attr_group armoury_attr_groups[] = { ++ { &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED }, ++ { &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU }, ++ { &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU }, ++ { &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM }, ++ { &cores_efficiency_attr_group, ASUS_WMI_DEVID_CORES_MAX }, ++ { &cores_performance_attr_group, ASUS_WMI_DEVID_CORES_MAX }, ++ ++ { &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL }, ++ { &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT }, ++ { &ppt_pl3_fppt_attr_group, ASUS_WMI_DEVID_PPT_FPPT }, ++ { &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT }, ++ { &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT }, ++ { &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST }, ++ { &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET }, ++ { &nv_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP }, ++ { &nv_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP }, ++ ++ { &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE }, ++ { &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND }, ++ { &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE }, ++ { &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD }, ++ { &panel_hd_mode_attr_group, ASUS_WMI_DEVID_PANEL_HD }, ++ ++ { &ppt_enabled_attr_group, ASUS_WMI_DEVID_CPU_FAN_CTRL }, ++}; ++ ++static int asus_fw_attr_add(void) ++{ ++ const struct power_limits *limits; ++ bool should_create; ++ const char *name; ++ int err, i; ++ ++ asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), ++ NULL, "%s", DRIVER_NAME); ++ if (IS_ERR(asus_armoury.fw_attr_dev)) { ++ err = PTR_ERR(asus_armoury.fw_attr_dev); ++ goto fail_class_get; ++ } ++ ++ asus_armoury.fw_attr_kset = kset_create_and_add("attributes", NULL, ++ &asus_armoury.fw_attr_dev->kobj); ++ if (!asus_armoury.fw_attr_kset) { ++ err = -ENOMEM; ++ goto err_destroy_classdev; ++ } ++ ++ err = sysfs_create_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); ++ if (err) { ++ pr_err("Failed to create sysfs level attributes\n"); ++ goto err_destroy_kset; ++ } ++ ++ asus_armoury.mini_led_dev_id = 0; ++ if (asus_wmi_is_present(ASUS_WMI_DEVID_MINI_LED_MODE)) { ++ asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; ++ } else if (asus_wmi_is_present(ASUS_WMI_DEVID_MINI_LED_MODE2)) { ++ asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2; ++ } ++ ++ if (asus_armoury.mini_led_dev_id) { ++ err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group); ++ if (err) { ++ pr_err("Failed to create sysfs-group for mini_led\n"); ++ goto err_remove_file; ++ } ++ } ++ ++ asus_armoury.gpu_mux_dev_id = 0; ++ if (asus_wmi_is_present(ASUS_WMI_DEVID_GPU_MUX)) { ++ asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX; ++ } else if (asus_wmi_is_present(ASUS_WMI_DEVID_GPU_MUX_VIVO)) { ++ asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO; ++ } ++ ++ if (asus_armoury.gpu_mux_dev_id) { ++ err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); ++ if (err) { ++ pr_err("Failed to create sysfs-group for gpu_mux\n"); ++ goto err_remove_mini_led_group; ++ } ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { ++ name = armoury_attr_groups[i].attr_group->name; ++ should_create = true; ++ ++ if (!asus_wmi_is_present(armoury_attr_groups[i].wmi_devid)) ++ continue; ++ ++ /* ++ * Check ROG tunables against initialized limits, don't create attributes for any ++ * that might have a supported WMI method but no associated data. ++ */ ++ if (!strcmp(name, "ppt_pl1_spl") || !strcmp(name, "ppt_pl2_sppt") || ++ !strcmp(name, "ppt_pl3_fppt") || !strcmp(name, "ppt_apu_sppt") || ++ !strcmp(name, "ppt_platform_sppt") || !strcmp(name, "nv_dynamic_boost") || ++ !strcmp(name, "nv_temp_target") || !strcmp(name, "nv_base_tgp") || ++ !strcmp(name, "dgpu_tgp") || !strcmp(name, "ppt_enabled")) ++ { ++ should_create = false; ++ if (asus_armoury.rog_tunables && asus_armoury.rog_tunables->tuning_limits && ++ asus_armoury.rog_tunables->tuning_limits->ac_data) { ++ /* Must have AC table, and a max value for each attribute */ ++ limits = asus_armoury.rog_tunables->tuning_limits->ac_data; ++ should_create = limits && ( ++ (!strcmp(name, "ppt_pl1_spl") && limits->ppt_pl1_spl_max) || ++ (!strcmp(name, "ppt_pl2_sppt") && limits->ppt_pl2_sppt_max) || ++ (!strcmp(name, "ppt_pl3_fppt") && limits->ppt_pl3_fppt_max) || ++ (!strcmp(name, "ppt_apu_sppt") && limits->ppt_apu_sppt_max) || ++ (!strcmp(name, "ppt_platform_sppt") && limits->ppt_platform_sppt_max) || ++ (!strcmp(name, "nv_dynamic_boost") && limits->nv_dynamic_boost_max) || ++ (!strcmp(name, "nv_temp_target") && limits->nv_temp_target_max) || ++ (!strcmp(name, "nv_base_tgp") && limits->nv_tgp_max) || ++ (!strcmp(name, "dgpu_tgp") && limits->nv_tgp_max) || ++ (!strcmp(name, "ppt_enabled") && ++ asus_armoury.rog_tunables->tuning_limits->requires_fan_curve)); ++ ++ /* Log error so users can report upstream */ ++ if (!should_create) ++ pr_err("Missing max value on %s for tunable: %s\n", ++ dmi_get_system_info(DMI_BOARD_NAME), name); ++ } ++ } ++ ++ if (should_create) { ++ err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, ++ armoury_attr_groups[i].attr_group); ++ if (err) { ++ pr_err("Failed to create sysfs-group for %s\n", ++ armoury_attr_groups[i].attr_group->name); ++ goto err_remove_groups; ++ } ++ } ++} ++ ++ return 0; ++ ++err_remove_groups: ++ while (--i >= 0) { ++ if (asus_wmi_is_present(armoury_attr_groups[i].wmi_devid)) ++ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, armoury_attr_groups[i].attr_group); ++ } ++ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); ++err_remove_mini_led_group: ++ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group); ++err_remove_file: ++ sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); ++err_destroy_kset: ++ kset_unregister(asus_armoury.fw_attr_kset); ++err_destroy_classdev: ++fail_class_get: ++ device_destroy(&firmware_attributes_class, MKDEV(0, 0)); ++ return err; ++} ++ ++/* Init / exit ****************************************************************/ ++ ++/* Set up the min/max and defaults for ROG tunables */ ++static bool init_rog_tunables(struct rog_tunables *rog) ++{ ++ const struct dmi_system_id *dmi_id; ++ const struct power_data *power_data; ++ const struct power_limits *limits; ++ ++ /* Match the system against the power_limits table */ ++ dmi_id = dmi_first_match(power_limits); ++ if (!dmi_id) { ++ pr_warn("No matching power limits found for this system\n"); ++ // rog->tuning_limits = &default_power_data; ++ rog->tuning_limits = NULL; ++ return false; ++ } ++ ++ /* Get the power data for this system */ ++ power_data = dmi_id->driver_data; ++ if (!power_data) { ++ pr_info("No power data available for this system\n"); ++ return false; ++ } ++ ++ /* Store the power limits for later use */ ++ rog->tuning_limits = power_data; ++ ++ if (power_supply_is_system_supplied()) { ++ limits = power_data->ac_data; ++ if (!limits) { ++ pr_warn("No AC power limits available\n"); ++ return false; ++ } ++ } else { ++ limits = power_data->dc_data; ++ if (!limits && !power_data->ac_data) { ++ pr_err("No power limits available\n"); ++ return false; ++ } ++ } ++ ++ /* Set initial values */ ++ rog->ppt_pl1_spl = limits->ppt_pl1_spl_def ? ++ limits->ppt_pl1_spl_def : ++ limits->ppt_pl1_spl_max; ++ ++ rog->ppt_pl2_sppt = limits->ppt_pl2_sppt_def ? ++ limits->ppt_pl2_sppt_def : ++ limits->ppt_pl2_sppt_max; ++ ++ rog->ppt_pl3_fppt = limits->ppt_pl3_fppt_def ? ++ limits->ppt_pl3_fppt_def : ++ limits->ppt_pl3_fppt_max; ++ ++ rog->ppt_apu_sppt = limits->ppt_apu_sppt_def ? ++ limits->ppt_apu_sppt_def : ++ limits->ppt_apu_sppt_max; ++ ++ rog->ppt_platform_sppt = limits->ppt_platform_sppt_def ? ++ limits->ppt_platform_sppt_def : ++ limits->ppt_platform_sppt_max; ++ ++ rog->nv_dynamic_boost = limits->nv_dynamic_boost_max; ++ rog->nv_temp_target = limits->nv_temp_target_max; ++ rog->nv_tgp = limits->nv_tgp_max; ++ ++ pr_debug("Power limits initialized for %s (%s power)\n", ++ dmi_id->matches[0].substr, ++ power_supply_is_system_supplied() ? "AC" : "DC"); ++ ++ return true; ++} ++ ++static int __init asus_fw_init(void) ++{ ++ char *wmi_uid; ++ bool tuning; ++ int err; ++ ++ wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); ++ if (!wmi_uid) ++ return -ENODEV; ++ ++ /* ++ * if equal to "ASUSWMI" then it's DCTS that can't be used for this ++ * driver, DSTS is required. ++ */ ++ if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) ++ return -ENODEV; ++ ++ ++ if (asus_wmi_is_present(ASUS_WMI_DEVID_CORES_MAX)) { ++ asus_armoury.cpu_cores = kzalloc(sizeof(struct cpu_cores), GFP_KERNEL); ++ if (!asus_armoury.cpu_cores) ++ return -ENOMEM; ++ ++ err = init_max_cpu_cores(); ++ if (err) { ++ kfree(asus_armoury.cpu_cores); ++ pr_err("Could not initialise CPU core control %d\n", err); ++ return err; ++ } ++ } ++ ++ asus_armoury.rog_tunables = kzalloc(sizeof(struct rog_tunables), GFP_KERNEL); ++ if (!asus_armoury.rog_tunables) ++ return -ENOMEM; ++ /* Init logs warn/error and the driver should still be usable if init fails */ ++ tuning = init_rog_tunables(asus_armoury.rog_tunables); ++ if (!tuning) { ++ kfree(asus_armoury.rog_tunables); ++ pr_err("Could not initialise PPT tunable control %d\n", err); ++ } ++ ++ /* Must always be last step to ensure data is available */ ++ err = asus_fw_attr_add(); ++ if (err) ++ return err; ++ ++ if (tuning) { ++ wmi_armoury_interface.armoury_fw_attr_dev = asus_armoury.fw_attr_dev; ++ wmi_armoury_interface.ppt_enabled_attr = &attr_ppt_enabled_current_value; ++ err = asus_wmi_register_armoury_interface(&wmi_armoury_interface); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static void __exit asus_fw_exit(void) ++{ ++ mutex_lock(&asus_armoury.mutex); ++ ++ sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); ++ kset_unregister(asus_armoury.fw_attr_kset); ++ device_destroy(&firmware_attributes_class, MKDEV(0, 0)); ++ ++ asus_wmi_unregister_armoury_interface(&wmi_armoury_interface); ++ ++ mutex_unlock(&asus_armoury.mutex); ++} ++ ++module_init(asus_fw_init); ++module_exit(asus_fw_exit); ++ ++MODULE_IMPORT_NS("ASUS_WMI"); ++MODULE_AUTHOR("Luke Jones "); ++MODULE_DESCRIPTION("ASUS BIOS Configuration Driver"); ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("wmi:" ASUS_NB_WMI_EVENT_GUID); +diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h +new file mode 100644 +index 000000000000..a44265788103 +--- /dev/null ++++ b/drivers/platform/x86/asus-armoury.h +@@ -0,0 +1,1231 @@ ++/* SPDX-License-Identifier: GPL-2.0 ++ * ++ * Definitions for kernel modules using asus-armoury driver ++ * ++ * Copyright (c) 2024 Luke Jones ++ */ ++ ++#ifndef _ASUS_ARMOURY_H_ ++#define _ASUS_ARMOURY_H_ ++ ++#include ++#include ++#include ++ ++#define DRIVER_NAME "asus-armoury" ++ ++/* Tunables provided by ASUS for gaming laptops */ ++struct cpu_cores { ++ u32 cur_perf_cores; ++ u32 min_perf_cores; ++ u32 max_perf_cores; ++ u32 cur_power_cores; ++ u32 min_power_cores; ++ u32 max_power_cores; ++}; ++ ++struct rog_tunables { ++ const struct power_data *tuning_limits; ++ u32 ppt_pl1_spl; // cpu ++ u32 ppt_pl2_sppt; // cpu ++ u32 ppt_pl3_fppt; // cpu ++ u32 ppt_apu_sppt; // plat ++ u32 ppt_platform_sppt; // plat ++ ++ u32 nv_dynamic_boost; ++ u32 nv_temp_target; ++ u32 nv_tgp; ++}; ++ ++struct asus_armoury_priv { ++ struct device *fw_attr_dev; ++ struct kset *fw_attr_kset; ++ ++ struct cpu_cores *cpu_cores; ++ struct rog_tunables *rog_tunables; ++ u32 mini_led_dev_id; ++ u32 gpu_mux_dev_id; ++ ++ struct mutex mutex; ++}; ++ ++static ssize_t attr_uint_cache(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, ++ size_t count, u32 min, u32 max, u32 *store_value); ++ ++static ssize_t attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr, ++ const char *buf, size_t count, u32 min, u32 max, ++ u32 *store_value, u32 wmi_dev); ++ ++static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "integer\n"); ++} ++ ++static ssize_t enum_type_show(struct kobject *kobj, struct kobj_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "enumeration\n"); ++} ++ ++#define __ASUS_ATTR_RO(_func, _name) \ ++ { \ ++ .attr = { .name = __stringify(_name), .mode = 0444 }, \ ++ .show = _func##_##_name##_show, \ ++ } ++ ++#define __ASUS_ATTR_RO_AS(_name, _show) \ ++ { \ ++ .attr = { .name = __stringify(_name), .mode = 0444 }, \ ++ .show = _show, \ ++ } ++ ++#define __ASUS_ATTR_RW(_func, _name) \ ++ __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) ++ ++#define __WMI_STORE_INT(_attr, _min, _max, _wmi) \ ++ static ssize_t _attr##_store(struct kobject *kobj, \ ++ struct kobj_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ return attr_uint_store(kobj, attr, buf, count, _min, _max, \ ++ NULL, _wmi); \ ++ } ++ ++#define WMI_SHOW_INT(_attr, _fmt, _wmi) \ ++ static ssize_t _attr##_show(struct kobject *kobj, \ ++ struct kobj_attribute *attr, char *buf) \ ++ { \ ++ u32 result; \ ++ int err; \ ++ \ ++ err = asus_wmi_get_devstate_dsts(_wmi, &result); \ ++ if (err) \ ++ return err; \ ++ return sysfs_emit(buf, _fmt, \ ++ result & ~ASUS_WMI_DSTS_PRESENCE_BIT); \ ++ } ++ ++/* Create functions and attributes for use in other macros or on their own */ ++ ++#define __ATTR_CURRENT_INT_RO(_attr, _wmi) \ ++ WMI_SHOW_INT(_attr##_current_value, "%d\n", _wmi); \ ++ static struct kobj_attribute attr_##_attr##_current_value = \ ++ __ASUS_ATTR_RO(_attr, current_value) ++ ++#define __ATTR_CURRENT_INT_RW(_attr, _minv, _maxv, _wmi) \ ++ __WMI_STORE_INT(_attr##_current_value, _minv, _maxv, _wmi); \ ++ WMI_SHOW_INT(_attr##_current_value, "%d\n", _wmi); \ ++ static struct kobj_attribute attr_##_attr##_current_value = \ ++ __ASUS_ATTR_RW(_attr, current_value) ++ ++/* Shows a formatted static variable */ ++#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ ++ static ssize_t _attrname##_##_prop##_show( \ ++ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ ++ { \ ++ return sysfs_emit(buf, _fmt, _val); \ ++ } \ ++ static struct kobj_attribute attr_##_attrname##_##_prop = \ ++ __ASUS_ATTR_RO(_attrname, _prop) ++ ++/* Requires current_value_show */ ++#define __ATTR_GROUP_INT_VALUE_ONLY(_attrname, _fsname, _dispname) \ ++ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ ++ static struct kobj_attribute attr_##_attrname##_type = \ ++ __ASUS_ATTR_RO_AS(type, int_type_show); \ ++ static struct attribute *_attrname##_attrs[] = { \ ++ &attr_##_attrname##_current_value.attr, \ ++ &attr_##_attrname##_display_name.attr, \ ++ &attr_##_attrname##_type.attr, NULL \ ++ }; \ ++ static const struct attribute_group _attrname##_attr_group = { \ ++ .name = _fsname, .attrs = _attrname##_attrs \ ++ } ++ ++/* Boolean style enumeration, base macro. Requires adding show/store */ ++#define __ATTR_GROUP_ENUM(_attrname, _fsname, _possible, _dispname) \ ++ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ ++ __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \ ++ static struct kobj_attribute attr_##_attrname##_type = \ ++ __ASUS_ATTR_RO_AS(type, enum_type_show); \ ++ static struct attribute *_attrname##_attrs[] = { \ ++ &attr_##_attrname##_current_value.attr, \ ++ &attr_##_attrname##_display_name.attr, \ ++ &attr_##_attrname##_possible_values.attr, \ ++ &attr_##_attrname##_type.attr, \ ++ NULL \ ++ }; \ ++ static const struct attribute_group _attrname##_attr_group = { \ ++ .name = _fsname, .attrs = _attrname##_attrs \ ++ } ++ ++#define ATTR_GROUP_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \ ++ __ATTR_CURRENT_INT_RO(_attrname, _wmi); \ ++ __ATTR_GROUP_INT_VALUE_ONLY(_attrname, _fsname, _dispname) ++ ++#define ATTR_GROUP_BOOL_RO(_attrname, _fsname, _wmi, _dispname) \ ++ __ATTR_CURRENT_INT_RO(_attrname, _wmi); \ ++ __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname) ++ ++#define ATTR_GROUP_BOOL_RW(_attrname, _fsname, _wmi, _dispname) \ ++ __ATTR_CURRENT_INT_RW(_attrname, 0, 1, _wmi); \ ++ __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname) ++ ++/* ++ * Requires _current_value_show(), _current_value_show() ++ */ ++#define ATTR_GROUP_BOOL_CUSTOM(_attrname, _fsname, _dispname) \ ++ static struct kobj_attribute attr_##_attrname##_current_value = \ ++ __ASUS_ATTR_RW(_attrname, current_value); \ ++ __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname) ++ ++#define ATTR_GROUP_ENUM_INT_RO(_attrname, _fsname, _wmi, _possible, _dispname) \ ++ __ATTR_CURRENT_INT_RO(_attrname, _wmi); \ ++ __ATTR_GROUP_ENUM(_attrname, _fsname, _possible, _dispname) ++ ++/* ++ * Requires _current_value_show(), _current_value_show() ++ * and _possible_values_show() ++ */ ++#define ATTR_GROUP_ENUM_CUSTOM(_attrname, _fsname, _dispname) \ ++ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ ++ static struct kobj_attribute attr_##_attrname##_current_value = \ ++ __ASUS_ATTR_RW(_attrname, current_value); \ ++ static struct kobj_attribute attr_##_attrname##_possible_values = \ ++ __ASUS_ATTR_RO(_attrname, possible_values); \ ++ static struct kobj_attribute attr_##_attrname##_type = \ ++ __ASUS_ATTR_RO_AS(type, enum_type_show); \ ++ static struct attribute *_attrname##_attrs[] = { \ ++ &attr_##_attrname##_current_value.attr, \ ++ &attr_##_attrname##_display_name.attr, \ ++ &attr_##_attrname##_possible_values.attr, \ ++ &attr_##_attrname##_type.attr, \ ++ NULL \ ++ }; \ ++ static const struct attribute_group _attrname##_attr_group = { \ ++ .name = _fsname, .attrs = _attrname##_attrs \ ++ } ++ ++/* CPU core attributes need a little different in setup */ ++#define ATTR_GROUP_CORES_RW(_attrname, _fsname, _dispname) \ ++ __ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", 1); \ ++ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ ++ static struct kobj_attribute attr_##_attrname##_current_value = \ ++ __ASUS_ATTR_RW(_attrname, current_value); \ ++ static struct kobj_attribute attr_##_attrname##_default_value = \ ++ __ASUS_ATTR_RO(_attrname, default_value); \ ++ static struct kobj_attribute attr_##_attrname##_min_value = \ ++ __ASUS_ATTR_RO(_attrname, min_value); \ ++ static struct kobj_attribute attr_##_attrname##_max_value = \ ++ __ASUS_ATTR_RO(_attrname, max_value); \ ++ static struct kobj_attribute attr_##_attrname##_type = \ ++ __ASUS_ATTR_RO_AS(type, int_type_show); \ ++ static struct attribute *_attrname##_attrs[] = { \ ++ &attr_##_attrname##_current_value.attr, \ ++ &attr_##_attrname##_default_value.attr, \ ++ &attr_##_attrname##_min_value.attr, \ ++ &attr_##_attrname##_max_value.attr, \ ++ &attr_##_attrname##_scalar_increment.attr, \ ++ &attr_##_attrname##_display_name.attr, \ ++ &attr_##_attrname##_type.attr, \ ++ NULL \ ++ }; \ ++ static const struct attribute_group _attrname##_attr_group = { \ ++ .name = _fsname, .attrs = _attrname##_attrs \ ++ } ++ ++/* ++ * ROG PPT attributes need a little different in setup as they ++ * require rog_tunables members. ++ */ ++ ++ #define __ROG_TUNABLE_SHOW(_prop, _attrname, _val) \ ++ static ssize_t _attrname##_##_prop##_show( \ ++ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ ++ { \ ++ const struct power_limits *limits; \ ++ limits = power_supply_is_system_supplied() ? \ ++ asus_armoury.rog_tunables->tuning_limits->ac_data : \ ++ asus_armoury.rog_tunables->tuning_limits->dc_data; \ ++ if (!limits) \ ++ return -ENODEV; \ ++ return sysfs_emit(buf, "%d\n", limits->_val); \ ++ } \ ++ static struct kobj_attribute attr_##_attrname##_##_prop = \ ++ __ASUS_ATTR_RO(_attrname, _prop) ++ ++#define __ROG_TUNABLE_SHOW_DEFAULT(_attrname) \ ++ static ssize_t _attrname##_default_value_show( \ ++ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ ++ { \ ++ const struct power_limits *limits; \ ++ limits = power_supply_is_system_supplied() ? \ ++ asus_armoury.rog_tunables->tuning_limits->ac_data : \ ++ asus_armoury.rog_tunables->tuning_limits->dc_data; \ ++ if (!limits) \ ++ return -ENODEV; \ ++ return sysfs_emit(buf, "%d\n", \ ++ limits->_attrname##_def ? \ ++ limits->_attrname##_def : \ ++ limits->_attrname##_max); \ ++ } \ ++ static struct kobj_attribute attr_##_attrname##_default_value = \ ++ __ASUS_ATTR_RO(_attrname, default_value) ++ ++#define __ROG_TUNABLE_RW(_attr, _wmi) \ ++ static ssize_t _attr##_current_value_store( \ ++ struct kobject *kobj, struct kobj_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ const struct power_limits *limits; \ ++ int err; \ ++ limits = power_supply_is_system_supplied() ? \ ++ asus_armoury.rog_tunables->tuning_limits->ac_data : \ ++ asus_armoury.rog_tunables->tuning_limits->dc_data; \ ++ if (!limits) \ ++ return -ENODEV; \ ++ if (!asus_wmi_get_fan_curves_enabled(0)) { \ ++ err = attr_uint_cache(kobj, attr, buf, count, \ ++ limits->_attr##_min, \ ++ limits->_attr##_max, \ ++ &asus_armoury.rog_tunables->_attr); \ ++ if (err) \ ++ return err; \ ++ return count; \ ++ } \ ++ return attr_uint_store(kobj, attr, buf, count, \ ++ limits->_attr##_min, \ ++ limits->_attr##_max, \ ++ &asus_armoury.rog_tunables->_attr, \ ++ _wmi); \ ++ } \ ++ static ssize_t _attr##_current_value_show( \ ++ struct kobject *kobj, struct kobj_attribute *attr, \ ++ char *buf) \ ++ { \ ++ return sysfs_emit(buf, "%u\n", \ ++ asus_armoury.rog_tunables->_attr); \ ++ } \ ++ static struct kobj_attribute attr_##_attr##_current_value = \ ++ __ASUS_ATTR_RW(_attr, current_value) ++ ++#define ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _dispname) \ ++ __ROG_TUNABLE_RW(_attrname, _wmi); \ ++ __ROG_TUNABLE_SHOW_DEFAULT(_attrname); \ ++ __ROG_TUNABLE_SHOW(min_value, _attrname, _attrname##_min); \ ++ __ROG_TUNABLE_SHOW(max_value, _attrname, _attrname##_max); \ ++ __ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", 1); \ ++ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ ++ static struct kobj_attribute attr_##_attrname##_type = \ ++ __ASUS_ATTR_RO_AS(type, int_type_show); \ ++ static struct attribute *_attrname##_attrs[] = { \ ++ &attr_##_attrname##_current_value.attr, \ ++ &attr_##_attrname##_default_value.attr, \ ++ &attr_##_attrname##_min_value.attr, \ ++ &attr_##_attrname##_max_value.attr, \ ++ &attr_##_attrname##_scalar_increment.attr, \ ++ &attr_##_attrname##_display_name.attr, \ ++ &attr_##_attrname##_type.attr, \ ++ NULL \ ++ }; \ ++ static const struct attribute_group _attrname##_attr_group = { \ ++ .name = _fsname, .attrs = _attrname##_attrs \ ++ } ++ ++/* Default is always the maximum value unless *_def is specified */ ++struct power_limits { ++ u32 ppt_pl1_spl_min; ++ u32 ppt_pl1_spl_def; ++ u32 ppt_pl1_spl_max; ++ u32 ppt_pl2_sppt_min; ++ u32 ppt_pl2_sppt_def; ++ u32 ppt_pl2_sppt_max; ++ u32 ppt_pl3_fppt_min; ++ u32 ppt_pl3_fppt_def; ++ u32 ppt_pl3_fppt_max; ++ u32 ppt_apu_sppt_min; ++ u32 ppt_apu_sppt_def; ++ u32 ppt_apu_sppt_max; ++ u32 ppt_platform_sppt_min; ++ u32 ppt_platform_sppt_def; ++ u32 ppt_platform_sppt_max; ++ /* Nvidia GPU specific, default is always max */ ++ u32 nv_dynamic_boost_def; // unused. exists for macro ++ u32 nv_dynamic_boost_min; ++ u32 nv_dynamic_boost_max; ++ u32 nv_temp_target_def; // unused. exists for macro ++ u32 nv_temp_target_min; ++ u32 nv_temp_target_max; ++ u32 nv_tgp_def; // unused. exists for macro ++ u32 nv_tgp_min; ++ u32 nv_tgp_max; ++}; ++ ++struct power_data { ++ const struct power_limits *ac_data; ++ const struct power_limits *dc_data; ++ bool requires_fan_curve; ++}; ++ ++/* ++ * For each avilable attribute there must be a min and a max. ++ * _def is not required and will be assumed to be default == max if missing. ++ */ ++static const struct dmi_system_id power_limits[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA401W"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 55, ++ .nv_tgp_max = 75, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 30, ++ .ppt_pl2_sppt_min = 31, ++ .ppt_pl2_sppt_max = 44, ++ .ppt_pl3_fppt_min = 45, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA507R"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80 ++ }, ++ .dc_data = NULL ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA507X"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 20, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 55, ++ .nv_tgp_max = 85, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_max = 65, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA507Z"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 105, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 15, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 55, ++ .nv_tgp_max = 85, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 45, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 60, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA607P"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 30, ++ .ppt_pl1_spl_def = 100, ++ .ppt_pl1_spl_max = 135, ++ .ppt_pl2_sppt_min = 30, ++ .ppt_pl2_sppt_def = 115, ++ .ppt_pl2_sppt_max = 135, ++ .ppt_pl3_fppt_min = 30, ++ .ppt_pl3_fppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 55, ++ .nv_tgp_max = 115, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_def = 60, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 25, ++ .ppt_pl3_fppt_max = 80, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 15, ++ .ppt_apu_sppt_max = 80, ++ .ppt_platform_sppt_min = 30, ++ .ppt_platform_sppt_max = 120 ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 25, ++ .ppt_apu_sppt_max = 35, ++ .ppt_platform_sppt_min = 45, ++ .ppt_platform_sppt_max = 100 ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA617NT"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 15, ++ .ppt_apu_sppt_max = 80, ++ .ppt_platform_sppt_min = 30, ++ .ppt_platform_sppt_max = 115 ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 15, ++ .ppt_apu_sppt_max = 45, ++ .ppt_platform_sppt_min = 30, ++ .ppt_platform_sppt_max = 50 ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA617XS"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 15, ++ .ppt_apu_sppt_max = 80, ++ .ppt_platform_sppt_min = 30, ++ .ppt_platform_sppt_max = 120, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 25, ++ .ppt_apu_sppt_max = 35, ++ .ppt_platform_sppt_min = 45, ++ .ppt_platform_sppt_max = 100, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FX507Z"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 15, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 45, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 60, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GA401Q"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 15, ++ .ppt_pl2_sppt_max = 80, ++ }, ++ .dc_data = NULL ++ }, ++ }, ++ { ++ .matches = { ++ // This model is full AMD. No Nvidia dGPU. ++ DMI_MATCH(DMI_BOARD_NAME, "GA402R"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 15, ++ .ppt_apu_sppt_max = 80, ++ .ppt_platform_sppt_min = 30, ++ .ppt_platform_sppt_max = 115, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_apu_sppt_min = 25, ++ .ppt_apu_sppt_def = 30, ++ .ppt_apu_sppt_max = 45, ++ .ppt_platform_sppt_min = 40, ++ .ppt_platform_sppt_max = 60, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GA402X"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 35, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_def = 65, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 35, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GA403U"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80 ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 35, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 65 ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GA503R"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 35, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 65, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 20, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 25, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_max = 60, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 65 ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GA605W"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 20, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 55, ++ .nv_tgp_max = 85, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl2_sppt_min = 31, ++ .ppt_pl2_sppt_max = 44, ++ .ppt_pl3_fppt_min = 45, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GU603Z"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 60, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 135, ++ /* Only allowed in AC mode */ ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 20, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 40, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 40, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GU604V"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 65, ++ .ppt_pl1_spl_max = 120, ++ .ppt_pl2_sppt_min = 65, ++ .ppt_pl2_sppt_max = 150, ++ /* Only allowed in AC mode */ ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 40, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 40, ++ .ppt_pl2_sppt_max = 60, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GU605M"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 20, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl2_sppt_min = 38, ++ .ppt_pl2_sppt_max = 53, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GV301R"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 45, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 54, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 35, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GV601R"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 35, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_max = 100, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_def = 80, ++ .ppt_pl3_fppt_max = 125, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 28, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_def = 40, ++ .ppt_pl2_sppt_max = 60, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_def = 80, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GV601V"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_def = 100, ++ .ppt_pl1_spl_max = 110, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 20, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 40, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 40, ++ .ppt_pl2_sppt_max = 60, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "GX650P"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 110, ++ .ppt_pl1_spl_max = 130, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 125, ++ .ppt_pl2_sppt_max = 130, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_def = 125, ++ .ppt_pl3_fppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_def = 25, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_def = 35, ++ .ppt_pl2_sppt_max = 65, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_def = 42, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G513I"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ /* Yes this laptop is very limited */ ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 15, ++ .ppt_pl2_sppt_max = 80, ++ }, ++ .dc_data = NULL, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G513QM"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ /* Yes this laptop is very limited */ ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 100, ++ .ppt_pl2_sppt_min = 15, ++ .ppt_pl2_sppt_max = 190, ++ }, ++ .dc_data = NULL, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G513R"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl2_sppt_min = 54, ++ .ppt_pl2_sppt_max = 100, ++ .ppt_pl3_fppt_min = 54, ++ .ppt_pl3_fppt_max = 125, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 50, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 50, ++ .ppt_pl3_fppt_min = 28, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G614J"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 140, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G634J"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 140, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G733C"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 170, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 35, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G814J"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 140, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 140, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G834J"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 140, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "H7606W"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 20, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 55, ++ .nv_tgp_max = 85, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl2_sppt_min = 31, ++ .ppt_pl2_sppt_max = 44, ++ .ppt_pl3_fppt_min = 45, ++ .ppt_pl3_fppt_max = 65, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "RC71"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 7, ++ .ppt_pl1_spl_max = 30, ++ .ppt_pl2_sppt_min = 15, ++ .ppt_pl2_sppt_max = 43, ++ .ppt_pl3_fppt_min = 15, ++ .ppt_pl3_fppt_max = 53 ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 7, ++ .ppt_pl1_spl_def = 15, ++ .ppt_pl1_spl_max = 25, ++ .ppt_pl2_sppt_min = 15, ++ .ppt_pl2_sppt_def = 20, ++ .ppt_pl2_sppt_max = 30, ++ .ppt_pl3_fppt_min = 15, ++ .ppt_pl3_fppt_def = 25, ++ .ppt_pl3_fppt_max = 35 ++ } ++ }, ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "RC72"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 7, ++ .ppt_pl1_spl_max = 30, ++ .ppt_pl2_sppt_min = 15, ++ .ppt_pl2_sppt_max = 43, ++ .ppt_pl3_fppt_min = 15, ++ .ppt_pl3_fppt_max = 53 ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 7, ++ .ppt_pl1_spl_def = 17, ++ .ppt_pl1_spl_max = 25, ++ .ppt_pl2_sppt_min = 15, ++ .ppt_pl2_sppt_def = 24, ++ .ppt_pl2_sppt_max = 30, ++ .ppt_pl3_fppt_min = 15, ++ .ppt_pl3_fppt_def = 30, ++ .ppt_pl3_fppt_max = 35 ++ } ++ }, ++ }, ++ {} ++}; ++ ++#endif /* _ASUS_ARMOURY_H_ */ +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 38ef778e8c19..6bcc8974d525 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -55,8 +55,6 @@ module_param(fnlock_default, bool, 0444); + #define to_asus_wmi_driver(pdrv) \ + (container_of((pdrv), struct asus_wmi_driver, platform_driver)) + +-#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" +- + #define NOTIFY_BRNUP_MIN 0x11 + #define NOTIFY_BRNUP_MAX 0x1f + #define NOTIFY_BRNDOWN_MIN 0x20 +@@ -105,8 +103,6 @@ module_param(fnlock_default, bool, 0444); + #define USB_INTEL_XUSB2PR 0xD0 + #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31 + +-#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" +- + #define WMI_EVENT_MASK 0xFFFF + + #define FAN_CURVE_POINTS 8 +@@ -142,11 +138,6 @@ module_param(fnlock_default, bool, 0444); + #define ASUS_MINI_LED_2024_STRONG 0x01 + #define ASUS_MINI_LED_2024_OFF 0x02 + +-/* Controls the power state of the USB0 hub on ROG Ally which input is on */ +-#define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE" +-/* 300ms so far seems to produce a reliable result on AC and battery */ +-#define ASUS_USB0_PWR_EC0_CSEE_WAIT 1500 +- + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; + + static int throttle_thermal_policy_write(struct asus_wmi *); +@@ -238,6 +229,8 @@ struct fan_curve_data { + u8 percents[FAN_CURVE_POINTS]; + }; + ++static struct asus_wmi_armoury_interface wmi_armoury_interface; ++ + struct asus_wmi { + int dsts_id; + int spec; +@@ -274,9 +267,6 @@ struct asus_wmi { + u32 tablet_switch_dev_id; + bool tablet_switch_inverted; + +- /* The ROG Ally device requires the MCU USB device be disconnected before suspend */ +- bool ally_mcu_usb_switch; +- + enum fan_type fan_type; + enum fan_type gpu_fan_type; + enum fan_type mid_fan_type; +@@ -335,6 +325,15 @@ struct asus_wmi { + struct asus_wmi_driver *driver; + }; + ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) ++static void asus_wmi_show_deprecated(void) ++{ ++ pr_notice_once("Accessing attributes through /sys/bus/platform/asus_wmi " ++ "is deprecated and will be removed in a future release. Please " ++ "switch over to /sys/class/firmware_attributes.\n"); ++} ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ ++ + /* WMI ************************************************************************/ + + static int asus_wmi_evaluate_method3(u32 method_id, +@@ -385,7 +384,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) + { + return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval); + } +-EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method); ++EXPORT_SYMBOL_NS_GPL(asus_wmi_evaluate_method, "ASUS_WMI"); + + static int asus_wmi_evaluate_method5(u32 method_id, + u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval) +@@ -406,10 +405,10 @@ static int asus_wmi_evaluate_method5(u32 method_id, + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, + &input, &output); + +- pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", ++ pr_warn("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", + __func__, method_id, arg0, arg1, arg2, arg3, arg4); + if (ACPI_FAILURE(status)) { +- pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n", ++ pr_warn("%s, (0x%08x), arg 0x%08x failed: %d\n", + __func__, method_id, arg0, -EIO); + return -EIO; + } +@@ -418,14 +417,14 @@ static int asus_wmi_evaluate_method5(u32 method_id, + if (obj && obj->type == ACPI_TYPE_INTEGER) + tmp = (u32) obj->integer.value; + +- pr_debug("Result: %x\n", tmp); ++ pr_warn("Result: %x\n", tmp); + if (retval) + *retval = tmp; + + kfree(obj); + + if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) { +- pr_debug("%s, (0x%08x), arg 0x%08x failed: %d\n", ++ pr_warn("%s, (0x%08x), arg 0x%08x failed: %d\n", + __func__, method_id, arg0, -ENODEV); + return -ENODEV; + } +@@ -549,12 +548,50 @@ static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval) + return 0; + } + +-static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, +- u32 *retval) ++/** ++ * asus_wmi_get_devstate_dsts() - Get the WMI function state. ++ * @dev_id: The WMI method ID to call. ++ * @retval: A pointer to where to store the value returned from WMI. ++ * ++ * On success the return value is 0, and the retval is a valid value returned ++ * by the successful WMI function call otherwise an error is returned if the ++ * call failed, or if the WMI method ID is unsupported. ++ */ ++int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval) ++{ ++ int err; ++ ++ err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, retval); ++ if (err) ++ return err; ++ ++ if (*retval == ASUS_WMI_UNSUPPORTED_METHOD) ++ return -ENODEV; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_get_devstate_dsts, "ASUS_WMI"); ++ ++/** ++ * asus_wmi_set_devstate() - Set the WMI function state. ++ * @dev_id: The WMI function to call. ++ * @ctrl_param: The argument to be used for this WMI function. ++ * @retval: A pointer to where to store the value returned from WMI. ++ * ++ * The returned WMI function state if not checked here for error as ++ * asus_wmi_set_devstate() is not called unless first paired with a call to ++ * asus_wmi_get_devstate_dsts() to check that the WMI function is supported. ++ * ++ * On success the return value is 0, and the retval is a valid value returned ++ * by the successful WMI function call. An error value is returned only if the ++ * WMI function failed. ++ */ ++int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval) + { + return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id, + ctrl_param, retval); + } ++EXPORT_SYMBOL_NS_GPL(asus_wmi_set_devstate, "ASUS_WMI"); + + /* Helper for special devices with magic return codes */ + static int asus_wmi_get_devstate_bits(struct asus_wmi *asus, +@@ -687,6 +724,7 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus) + } + + /* Charging mode, 1=Barrel, 2=USB ******************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t charge_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -697,12 +735,16 @@ static ssize_t charge_mode_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", value & 0xff); + } + + static DEVICE_ATTR_RO(charge_mode); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* dGPU ********************************************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t dgpu_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -713,6 +755,8 @@ static ssize_t dgpu_disable_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", result); + } + +@@ -766,8 +810,10 @@ static ssize_t dgpu_disable_store(struct device *dev, + return count; + } + static DEVICE_ATTR_RW(dgpu_disable); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* eGPU ********************************************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t egpu_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -778,6 +824,8 @@ static ssize_t egpu_enable_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", result); + } + +@@ -834,8 +882,10 @@ static ssize_t egpu_enable_store(struct device *dev, + return count; + } + static DEVICE_ATTR_RW(egpu_enable); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* Is eGPU connected? *********************************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t egpu_connected_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -846,12 +896,16 @@ static ssize_t egpu_connected_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", result); + } + + static DEVICE_ATTR_RO(egpu_connected); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* gpu mux switch *************************************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t gpu_mux_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -862,6 +916,8 @@ static ssize_t gpu_mux_mode_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", result); + } + +@@ -920,6 +976,7 @@ static ssize_t gpu_mux_mode_store(struct device *dev, + return count; + } + static DEVICE_ATTR_RW(gpu_mux_mode); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* TUF Laptop Keyboard RGB Modes **********************************************/ + static ssize_t kbd_rgb_mode_store(struct device *dev, +@@ -1043,6 +1100,7 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = { + }; + + /* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t ppt_pl2_sppt_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +@@ -1081,6 +1139,8 @@ static ssize_t ppt_pl2_sppt_show(struct device *dev, + { + struct asus_wmi *asus = dev_get_drvdata(dev); + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%u\n", asus->ppt_pl2_sppt); + } + static DEVICE_ATTR_RW(ppt_pl2_sppt); +@@ -1123,6 +1183,8 @@ static ssize_t ppt_pl1_spl_show(struct device *dev, + { + struct asus_wmi *asus = dev_get_drvdata(dev); + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%u\n", asus->ppt_pl1_spl); + } + static DEVICE_ATTR_RW(ppt_pl1_spl); +@@ -1166,6 +1228,8 @@ static ssize_t ppt_fppt_show(struct device *dev, + { + struct asus_wmi *asus = dev_get_drvdata(dev); + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%u\n", asus->ppt_fppt); + } + static DEVICE_ATTR_RW(ppt_fppt); +@@ -1209,6 +1273,8 @@ static ssize_t ppt_apu_sppt_show(struct device *dev, + { + struct asus_wmi *asus = dev_get_drvdata(dev); + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%u\n", asus->ppt_apu_sppt); + } + static DEVICE_ATTR_RW(ppt_apu_sppt); +@@ -1252,6 +1318,8 @@ static ssize_t ppt_platform_sppt_show(struct device *dev, + { + struct asus_wmi *asus = dev_get_drvdata(dev); + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%u\n", asus->ppt_platform_sppt); + } + static DEVICE_ATTR_RW(ppt_platform_sppt); +@@ -1295,6 +1363,8 @@ static ssize_t nv_dynamic_boost_show(struct device *dev, + { + struct asus_wmi *asus = dev_get_drvdata(dev); + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%u\n", asus->nv_dynamic_boost); + } + static DEVICE_ATTR_RW(nv_dynamic_boost); +@@ -1338,11 +1408,15 @@ static ssize_t nv_temp_target_show(struct device *dev, + { + struct asus_wmi *asus = dev_get_drvdata(dev); + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%u\n", asus->nv_temp_target); + } + static DEVICE_ATTR_RW(nv_temp_target); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* Ally MCU Powersave ********************************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t mcu_powersave_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -1353,6 +1427,8 @@ static ssize_t mcu_powersave_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", result); + } + +@@ -1388,6 +1464,7 @@ static ssize_t mcu_powersave_store(struct device *dev, + return count; + } + static DEVICE_ATTR_RW(mcu_powersave); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* Battery ********************************************************************/ + +@@ -2261,6 +2338,7 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus) + } + + /* Panel Overdrive ************************************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t panel_od_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -2271,6 +2349,8 @@ static ssize_t panel_od_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", result); + } + +@@ -2307,9 +2387,10 @@ static ssize_t panel_od_store(struct device *dev, + return count; + } + static DEVICE_ATTR_RW(panel_od); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* Bootup sound ***************************************************************/ +- ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t boot_sound_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -2320,6 +2401,8 @@ static ssize_t boot_sound_show(struct device *dev, + if (result < 0) + return result; + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", result); + } + +@@ -2355,8 +2438,10 @@ static ssize_t boot_sound_store(struct device *dev, + return count; + } + static DEVICE_ATTR_RW(boot_sound); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* Mini-LED mode **************************************************************/ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t mini_led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -2387,6 +2472,8 @@ static ssize_t mini_led_mode_show(struct device *dev, + } + } + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "%d\n", value); + } + +@@ -2457,10 +2544,13 @@ static ssize_t available_mini_led_mode_show(struct device *dev, + return sysfs_emit(buf, "0 1 2\n"); + } + ++ asus_wmi_show_deprecated(); ++ + return sysfs_emit(buf, "0\n"); + } + + static DEVICE_ATTR_RO(available_mini_led_mode); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* Quirks *********************************************************************/ + +@@ -2982,6 +3072,72 @@ static struct attribute *hwmon_attributes[] = { + NULL + }; + ++int asus_wmi_register_armoury_interface(struct asus_wmi_armoury_interface *armoury_interface) ++{ ++ if (!armoury_interface->armoury_fw_attr_dev) ++ return -EINVAL; ++ ++ if (!armoury_interface->ppt_enabled_attr) ++ return -EINVAL; ++ ++ if (!wmi_armoury_interface.wmi_driver) ++ return -EINVAL; ++ ++ if (!wmi_armoury_interface.fan_curves_enabled_attr) ++ return -EINVAL; ++ ++ wmi_armoury_interface.armoury_fw_attr_dev = armoury_interface->armoury_fw_attr_dev; ++ wmi_armoury_interface.ppt_enabled_attr = armoury_interface->ppt_enabled_attr; ++ armoury_interface->wmi_driver = wmi_armoury_interface.wmi_driver; ++ armoury_interface->fan_curves_enabled_attr = wmi_armoury_interface.fan_curves_enabled_attr; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_register_armoury_interface, "ASUS_WMI"); ++ ++void asus_wmi_unregister_armoury_interface(struct asus_wmi_armoury_interface *armoury_interface) ++{ ++ wmi_armoury_interface.armoury_fw_attr_dev = NULL; ++ wmi_armoury_interface.ppt_enabled_attr = NULL; ++ armoury_interface->wmi_driver = NULL; ++ armoury_interface->fan_curves_enabled_attr = NULL; ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_unregister_armoury_interface, "ASUS_WMI"); ++ ++bool asus_wmi_get_fan_curves_enabled(uint fan) ++{ ++ if (!wmi_armoury_interface.wmi_driver) ++ pr_debug("%s: wmi_driver is NULL\n", __func__); ++ ++ if (!wmi_armoury_interface.wmi_driver) ++ return false; ++ ++ return wmi_armoury_interface.wmi_driver->custom_fan_curves[fan].enabled; ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_get_fan_curves_enabled, "ASUS_WMI"); ++ ++/* Notification helpers */ ++void notify_fan_curves_changed(void) ++{ ++ struct device *dev; ++ if (!interface_is_ready(&wmi_armoury_interface)) ++ return; ++ ++ /* Notify both attributes */ ++ if (wmi_armoury_interface.fan_curves_enabled_attr) { ++ dev = &wmi_armoury_interface.wmi_driver->platform_device->dev; ++ sysfs_notify(&dev->kobj, NULL, ++ wmi_armoury_interface.fan_curves_enabled_attr->attr.name); ++ } ++ ++ if (wmi_armoury_interface.ppt_enabled_attr) { ++ dev = wmi_armoury_interface.armoury_fw_attr_dev; ++ sysfs_notify(&dev->kobj, NULL, ++ wmi_armoury_interface.ppt_enabled_attr->attr.name); ++ } ++} ++EXPORT_SYMBOL_NS_GPL(notify_fan_curves_changed, "ASUS_WMI"); ++ + static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) + { +@@ -3367,6 +3523,52 @@ static ssize_t fan_curve_store(struct device *dev, + return count; + } + ++int asus_wmi_set_fan_curves_enabled(struct asus_wmi *asus, bool enabled) ++{ ++ int err; ++ ++ /* Set enabled state for all available fan curves */ ++ if (asus->cpu_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = enabled; ++ if (asus->gpu_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = enabled; ++ if (asus->mid_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = enabled; ++ ++ if (enabled) { ++ if (asus->cpu_fan_curve_available) { ++ err = fan_curve_write(asus, &asus->custom_fan_curves[FAN_CURVE_DEV_CPU]); ++ if (err) ++ return err; ++ } ++ if (asus->gpu_fan_curve_available) { ++ err = fan_curve_write(asus, &asus->custom_fan_curves[FAN_CURVE_DEV_GPU]); ++ if (err) ++ return err; ++ } ++ if (asus->mid_fan_curve_available) { ++ /* ++ * Some laptops error with ASUS_WMI_UNSUPPORTED_METHOD ++ * even though it works due to bugged bios acpi ++ */ ++ fan_curve_write(asus, &asus->custom_fan_curves[FAN_CURVE_DEV_MID]); ++ } ++ } else { ++ if (asus->throttle_thermal_policy_dev) { ++ err = throttle_thermal_policy_write(asus); ++ if (err) ++ return err; ++ } else { ++ err = asus_fan_set_auto(asus); ++ if (err) ++ return err; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_set_fan_curves_enabled, "ASUS_WMI"); ++ + static ssize_t fan_curve_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -3398,10 +3600,10 @@ static ssize_t fan_curve_enable_store(struct device *dev, + + switch (value) { + case 1: +- data->enabled = true; ++ err = asus_wmi_set_fan_curves_enabled(asus, true); + break; + case 2: +- data->enabled = false; ++ err = asus_wmi_set_fan_curves_enabled(asus, false); + break; + /* + * Auto + reset the fan curve data to defaults. Make it an explicit +@@ -3411,40 +3613,17 @@ static ssize_t fan_curve_enable_store(struct device *dev, + err = fan_curve_get_factory_default(asus, data->device_id); + if (err) + return err; +- data->enabled = false; ++ err = asus_wmi_set_fan_curves_enabled(asus, false); + break; + default: + return -EINVAL; + } + +- if (data->enabled) { +- err = fan_curve_write(asus, data); +- if (err) +- return err; +- } else { +- /* +- * For machines with throttle this is the only way to reset fans +- * to default mode of operation (does not erase curve data). +- */ +- if (asus->throttle_thermal_policy_dev) { +- err = throttle_thermal_policy_write(asus); +- if (err) +- return err; +- /* Similar is true for laptops with this fan */ +- } else if (asus->fan_type == FAN_TYPE_SPEC83) { +- err = asus_fan_set_auto(asus); +- if (err) +- return err; +- } else { +- /* Safeguard against fautly ACPI tables */ +- err = fan_curve_get_factory_default(asus, data->device_id); +- if (err) +- return err; +- err = fan_curve_write(asus, data); +- if (err) +- return err; +- } +- } ++ if (err) ++ return err; ++ ++ notify_fan_curves_changed(); ++ + return count; + } + +@@ -3690,6 +3869,9 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) + return PTR_ERR(hwmon); + } + ++ wmi_armoury_interface.wmi_driver = asus; ++ wmi_armoury_interface.fan_curves_enabled_attr = &dev_attr_pwm1_enable; ++ + return 0; + } + +@@ -3748,6 +3930,7 @@ static int throttle_thermal_policy_set_default(struct asus_wmi *asus) + return throttle_thermal_policy_write(asus); + } + ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + static ssize_t throttle_thermal_policy_show(struct device *dev, + struct device_attribute *attr, char *buf) + { +@@ -3791,6 +3974,7 @@ static ssize_t throttle_thermal_policy_store(struct device *dev, + * Throttle thermal policy: 0 - default, 1 - overboost, 2 - silent + */ + static DEVICE_ATTR_RW(throttle_thermal_policy); ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + /* Platform profile ***********************************************************/ + static int asus_wmi_platform_profile_get(struct device *dev, +@@ -3810,7 +3994,7 @@ static int asus_wmi_platform_profile_get(struct device *dev, + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case ASUS_THROTTLE_THERMAL_POLICY_SILENT: +- *profile = PLATFORM_PROFILE_QUIET; ++ *profile = PLATFORM_PROFILE_LOW_POWER; + break; + default: + return -EINVAL; +@@ -3834,7 +4018,7 @@ static int asus_wmi_platform_profile_set(struct device *dev, + case PLATFORM_PROFILE_BALANCED: + tp = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT; + break; +- case PLATFORM_PROFILE_QUIET: ++ case PLATFORM_PROFILE_LOW_POWER: + tp = ASUS_THROTTLE_THERMAL_POLICY_SILENT; + break; + default: +@@ -3847,7 +4031,7 @@ static int asus_wmi_platform_profile_set(struct device *dev, + + static int asus_wmi_platform_profile_probe(void *drvdata, unsigned long *choices) + { +- set_bit(PLATFORM_PROFILE_QUIET, choices); ++ set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + +@@ -4392,27 +4576,29 @@ static struct attribute *platform_attributes[] = { + &dev_attr_camera.attr, + &dev_attr_cardr.attr, + &dev_attr_touchpad.attr, +- &dev_attr_charge_mode.attr, +- &dev_attr_egpu_enable.attr, +- &dev_attr_egpu_connected.attr, +- &dev_attr_dgpu_disable.attr, +- &dev_attr_gpu_mux_mode.attr, + &dev_attr_lid_resume.attr, + &dev_attr_als_enable.attr, + &dev_attr_fan_boost_mode.attr, +- &dev_attr_throttle_thermal_policy.attr, +- &dev_attr_ppt_pl2_sppt.attr, +- &dev_attr_ppt_pl1_spl.attr, +- &dev_attr_ppt_fppt.attr, +- &dev_attr_ppt_apu_sppt.attr, +- &dev_attr_ppt_platform_sppt.attr, +- &dev_attr_nv_dynamic_boost.attr, +- &dev_attr_nv_temp_target.attr, +- &dev_attr_mcu_powersave.attr, +- &dev_attr_boot_sound.attr, +- &dev_attr_panel_od.attr, +- &dev_attr_mini_led_mode.attr, +- &dev_attr_available_mini_led_mode.attr, ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) ++ &dev_attr_charge_mode.attr, ++ &dev_attr_egpu_enable.attr, ++ &dev_attr_egpu_connected.attr, ++ &dev_attr_dgpu_disable.attr, ++ &dev_attr_gpu_mux_mode.attr, ++ &dev_attr_ppt_pl2_sppt.attr, ++ &dev_attr_ppt_pl1_spl.attr, ++ &dev_attr_ppt_fppt.attr, ++ &dev_attr_ppt_apu_sppt.attr, ++ &dev_attr_ppt_platform_sppt.attr, ++ &dev_attr_nv_dynamic_boost.attr, ++ &dev_attr_nv_temp_target.attr, ++ &dev_attr_mcu_powersave.attr, ++ &dev_attr_boot_sound.attr, ++ &dev_attr_panel_od.attr, ++ &dev_attr_mini_led_mode.attr, ++ &dev_attr_available_mini_led_mode.attr, ++ &dev_attr_throttle_thermal_policy.attr, ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + NULL + }; + +@@ -4434,7 +4620,11 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, + devid = ASUS_WMI_DEVID_LID_RESUME; + else if (attr == &dev_attr_als_enable.attr) + devid = ASUS_WMI_DEVID_ALS_ENABLE; +- else if (attr == &dev_attr_charge_mode.attr) ++ else if (attr == &dev_attr_fan_boost_mode.attr) ++ ok = asus->fan_boost_mode_available; ++ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) ++ if (attr == &dev_attr_charge_mode.attr) + devid = ASUS_WMI_DEVID_CHARGE_MODE; + else if (attr == &dev_attr_egpu_enable.attr) + ok = asus->egpu_enable_available; +@@ -4472,6 +4662,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, + ok = asus->mini_led_dev_id != 0; + else if (attr == &dev_attr_available_mini_led_mode.attr) + ok = asus->mini_led_dev_id != 0; ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + if (devid != -1) { + ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); +@@ -4712,6 +4903,7 @@ static int asus_wmi_add(struct platform_device *pdev) + goto fail_platform; + + /* ensure defaults for tunables */ ++#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) + asus->ppt_pl2_sppt = 5; + asus->ppt_pl1_spl = 5; + asus->ppt_apu_sppt = 5; +@@ -4723,8 +4915,6 @@ static int asus_wmi_add(struct platform_device *pdev) + asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); + asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); + asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); +- asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) +- && dmi_check_system(asus_ally_mcu_quirk); + + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) + asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; +@@ -4735,17 +4925,18 @@ static int asus_wmi_add(struct platform_device *pdev) + asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX; + else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX_VIVO)) + asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX_VIVO; +- +- if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE)) +- asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE; +- else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2)) +- asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2; ++#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ + + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY)) + asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY; + else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO)) + asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO; + ++ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE)) ++ asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE; ++ else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2)) ++ asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2; ++ + err = fan_boost_mode_check_present(asus); + if (err) + goto fail_fan_boost_mode; +@@ -4910,34 +5101,6 @@ static int asus_hotk_resume(struct device *device) + return 0; + } + +-static int asus_hotk_resume_early(struct device *device) +-{ +- struct asus_wmi *asus = dev_get_drvdata(device); +- +- if (asus->ally_mcu_usb_switch) { +- /* sleep required to prevent USB0 being yanked then reappearing rapidly */ +- if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8))) +- dev_err(device, "ROG Ally MCU failed to connect USB dev\n"); +- else +- msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); +- } +- return 0; +-} +- +-static int asus_hotk_prepare(struct device *device) +-{ +- struct asus_wmi *asus = dev_get_drvdata(device); +- +- if (asus->ally_mcu_usb_switch) { +- /* sleep required to ensure USB0 is disabled before sleep continues */ +- if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7))) +- dev_err(device, "ROG Ally MCU failed to disconnect USB dev\n"); +- else +- msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); +- } +- return 0; +-} +- + static int asus_hotk_restore(struct device *device) + { + struct asus_wmi *asus = dev_get_drvdata(device); +@@ -4982,8 +5145,6 @@ static const struct dev_pm_ops asus_pm_ops = { + .thaw = asus_hotk_thaw, + .restore = asus_hotk_restore, + .resume = asus_hotk_resume, +- .resume_early = asus_hotk_resume_early, +- .prepare = asus_hotk_prepare, + }; + + /* Registration ***************************************************************/ +diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h +index 018dfde4025e..e7e566acbca6 100644 +--- a/drivers/platform/x86/asus-wmi.h ++++ b/drivers/platform/x86/asus-wmi.h +@@ -85,4 +85,37 @@ struct asus_wmi_driver { + int asus_wmi_register_driver(struct asus_wmi_driver *driver); + void asus_wmi_unregister_driver(struct asus_wmi_driver *driver); + ++ ++/* asus_armoury and asus_wmi need a copy each, with members filled on register */ ++struct asus_wmi_armoury_interface { ++ /* Driver instances */ ++ struct asus_wmi *wmi_driver; ++ /* Required so asus_wmi can notify the related attribute */ ++ struct device *armoury_fw_attr_dev; ++ ++ /* Attribute references for cross-notification */ ++ struct device_attribute *fan_curves_enabled_attr; /* From asus_wmi */ ++ struct kobj_attribute *ppt_enabled_attr; /* From asus_armoury */ ++}; ++ ++int asus_wmi_register_armoury_interface(struct asus_wmi_armoury_interface *armoury_interface); ++void asus_wmi_unregister_armoury_interface(struct asus_wmi_armoury_interface *armoury_interface); ++ ++/* Helper to check if interface is ready */ ++static inline bool interface_is_ready(struct asus_wmi_armoury_interface *wmi_armoury_interface) ++{ ++ if (!wmi_armoury_interface->wmi_driver) ++ pr_debug("%s: wmi_driver is NULL\n", __func__); ++ if (!wmi_armoury_interface->armoury_fw_attr_dev) ++ pr_debug("%s: armoury_fw_attr_dev is NULL\n", __func__); ++ return wmi_armoury_interface->wmi_driver && ++ wmi_armoury_interface->armoury_fw_attr_dev; ++} ++ ++void notify_fan_curves_changed(void); ++ ++bool asus_wmi_get_fan_curves_enabled(uint fan); ++ ++int asus_wmi_set_fan_curves_enabled(struct asus_wmi *asus, bool enabled); ++ + #endif /* !_ASUS_WMI_H_ */ +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index 783e2a336861..761bb228bcfb 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -6,6 +6,9 @@ + #include + #include + ++#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" ++#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" ++ + /* WMI Methods */ + #define ASUS_WMI_METHODID_SPEC 0x43455053 /* BIOS SPECification */ + #define ASUS_WMI_METHODID_SFBD 0x44424653 /* Set First Boot Device */ +@@ -73,6 +76,7 @@ + #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO 0x00110019 + + /* Misc */ ++#define ASUS_WMI_DEVID_PANEL_HD 0x0005001C + #define ASUS_WMI_DEVID_PANEL_OD 0x00050019 + #define ASUS_WMI_DEVID_CAMERA 0x00060013 + #define ASUS_WMI_DEVID_LID_FLIP 0x00060062 +@@ -133,6 +137,16 @@ + /* dgpu on/off */ + #define ASUS_WMI_DEVID_DGPU 0x00090020 + ++/* Intel E-core and P-core configuration in a format 0x0[E]0[P] */ ++#define ASUS_WMI_DEVID_CORES 0x001200D2 ++ /* Maximum Intel E-core and P-core availability */ ++#define ASUS_WMI_DEVID_CORES_MAX 0x001200D3 ++ ++#define ASUS_WMI_DEVID_APU_MEM 0x000600C1 ++ ++#define ASUS_WMI_DEVID_DGPU_BASE_TGP 0x00120099 ++#define ASUS_WMI_DEVID_DGPU_SET_TGP 0x00120098 ++ + /* gpu mux switch, 0 = dGPU, 1 = Optimus */ + #define ASUS_WMI_DEVID_GPU_MUX 0x00090016 + #define ASUS_WMI_DEVID_GPU_MUX_VIVO 0x00090026 +@@ -158,8 +172,18 @@ + #define ASUS_WMI_DSTS_LIGHTBAR_MASK 0x0000000F + + #if IS_REACHABLE(CONFIG_ASUS_WMI) ++int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); ++int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); + int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); + #else ++static inline int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval) ++{ ++ return -ENODEV; ++} ++static inline int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval) ++{ ++ return -ENODEV; ++} + static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, + u32 *retval) + { +-- +2.48.1 + +From b6629e2e27b6bc7d6006500dde841c45d1d178de Mon Sep 17 00:00:00 2001 +From: Eric Naim +Date: Mon, 10 Feb 2025 09:49:45 +0800 +Subject: [PATCH 4/9] bbr3 Signed-off-by: Eric Naim --- @@ -4788,10 +11729,10 @@ index b412ed88ccd9..d70f8b742b21 100644 -- 2.48.1 -From 84eaf13f0d53531674f646cefcbb29a597edb48a Mon Sep 17 00:00:00 2001 +From 37e983bd3a048828771d1a9379c93b619cdda084 Mon Sep 17 00:00:00 2001 From: Eric Naim -Date: Mon, 3 Feb 2025 12:01:15 +0800 -Subject: [PATCH 4/6] cachy +Date: Mon, 10 Feb 2025 09:49:54 +0800 +Subject: [PATCH 5/9] cachy Signed-off-by: Eric Naim --- @@ -4904,7 +11845,7 @@ index fb8752b42ec8..753f36fd9361 100644 Safety option to keep boot IRQs enabled. This should never be necessary. diff --git a/Makefile b/Makefile -index 9e0d63d9d94b..aeb4211d6b64 100644 +index 89628e354ca7..3d2aa0195c29 100644 --- a/Makefile +++ b/Makefile @@ -861,11 +861,19 @@ KBUILD_CFLAGS += -fno-delete-null-pointer-checks @@ -5884,10 +12825,10 @@ index d100bb7a137c..4a9580fb0628 100644 if (amdgpu_emu_mode != 1) amdgpu_atombios_fini(adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c -index 817116e53d44..2b4a4eed2ae4 100644 +index dce9323fb410..794cf0c53e53 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c -@@ -137,6 +137,7 @@ enum AMDGPU_DEBUG_MASK { +@@ -138,6 +138,7 @@ enum AMDGPU_DEBUG_MASK { }; unsigned int amdgpu_vram_limit = UINT_MAX; @@ -5895,7 +12836,7 @@ index 817116e53d44..2b4a4eed2ae4 100644 int amdgpu_vis_vram_limit; int amdgpu_gart_size = -1; /* auto */ int amdgpu_gtt_size = -1; /* auto */ -@@ -255,6 +256,15 @@ struct amdgpu_watchdog_timer amdgpu_watchdog_timer = { +@@ -256,6 +257,15 @@ struct amdgpu_watchdog_timer amdgpu_watchdog_timer = { .period = 0x0, /* default to 0x0 (timeout disable) */ }; @@ -12246,7 +19187,7 @@ index 2ddb827e3bea..464049c4af3f 100644 return state; diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c -index f9464644186c..457fb08efc66 100644 +index abebb1185010..d70d8c0056ff 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -76,10 +76,19 @@ unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; @@ -12543,20 +19484,2493 @@ index e4decfb270fa..38bff2d8a740 100644 -- 2.48.1 -From e4e4fc74df918a945d88390ed011a47640bdc9c7 Mon Sep 17 00:00:00 2001 +From 43f08216a28bacd58ccb54d9a61e8164fc55b5be Mon Sep 17 00:00:00 2001 From: Eric Naim -Date: Mon, 3 Feb 2025 11:05:11 +0800 -Subject: [PATCH 5/6] fixes +Date: Mon, 10 Feb 2025 09:50:03 +0800 +Subject: [PATCH 6/9] crypto Signed-off-by: Eric Naim --- - arch/Kconfig | 4 +-- - arch/x86/boot/compressed/Makefile | 1 + - drivers/gpu/drm/drm_edid.c | 47 +++++++++++++++++++++++++++++-- - drivers/hid/hid-ids.h | 1 + - kernel/sched/ext.c | 7 +++-- - scripts/package/PKGBUILD | 5 ++++ - 6 files changed, 57 insertions(+), 8 deletions(-) + MAINTAINERS | 1 + + arch/x86/Kconfig | 2 +- + arch/x86/crypto/aesni-intel_glue.c | 22 +- + arch/x86/include/asm/cpufeatures.h | 1 + + arch/x86/kernel/cpu/intel.c | 22 ++ + arch/x86/lib/Makefile | 2 +- + arch/x86/lib/crc-pclmul-consts.h | 99 +++++ + arch/x86/lib/crc-pclmul-template.S | 584 ++++++++++++++++++++++++++++ + arch/x86/lib/crc-pclmul-template.h | 81 ++++ + arch/x86/lib/crc-t10dif-glue.c | 23 +- + arch/x86/lib/crc16-msb-pclmul.S | 6 + + arch/x86/lib/crc32-glue.c | 37 +- + arch/x86/lib/crc32-pclmul.S | 219 +---------- + arch/x86/lib/crct10dif-pcl-asm_64.S | 332 ---------------- + drivers/nvme/host/Kconfig | 3 +- + drivers/nvme/host/tcp.c | 122 ++---- + drivers/nvme/target/tcp.c | 90 ++--- + include/linux/skbuff.h | 7 +- + net/core/datagram.c | 46 +-- + scripts/gen-crc-consts.py | 239 ++++++++++++ + 20 files changed, 1133 insertions(+), 805 deletions(-) + create mode 100644 arch/x86/lib/crc-pclmul-consts.h + create mode 100644 arch/x86/lib/crc-pclmul-template.S + create mode 100644 arch/x86/lib/crc-pclmul-template.h + create mode 100644 arch/x86/lib/crc16-msb-pclmul.S + delete mode 100644 arch/x86/lib/crct10dif-pcl-asm_64.S + create mode 100755 scripts/gen-crc-consts.py + +diff --git a/MAINTAINERS b/MAINTAINERS +index 25c86f47353d..f4def1739559 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -6132,6 +6132,7 @@ F: Documentation/staging/crc* + F: arch/*/lib/crc* + F: include/linux/crc* + F: lib/crc* ++F: scripts/gen-crc-consts.py + + CREATIVE SB0540 + M: Bastien Nocera +diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig +index 17197d395976..55d374733baa 100644 +--- a/arch/x86/Kconfig ++++ b/arch/x86/Kconfig +@@ -77,7 +77,7 @@ config X86 + select ARCH_HAS_CPU_FINALIZE_INIT + select ARCH_HAS_CPU_PASID if IOMMU_SVA + select ARCH_HAS_CRC32 +- select ARCH_HAS_CRC_T10DIF if X86_64 ++ select ARCH_HAS_CRC_T10DIF + select ARCH_HAS_CURRENT_STACK_POINTER + select ARCH_HAS_DEBUG_VIRTUAL + select ARCH_HAS_DEBUG_VM_PGTABLE if !X86_PAE +diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c +index 11e95fc62636..3e9ab5cdade4 100644 +--- a/arch/x86/crypto/aesni-intel_glue.c ++++ b/arch/x86/crypto/aesni-intel_glue.c +@@ -1536,26 +1536,6 @@ DEFINE_GCM_ALGS(vaes_avx10_512, FLAG_AVX10_512, + AES_GCM_KEY_AVX10_SIZE, 800); + #endif /* CONFIG_AS_VAES && CONFIG_AS_VPCLMULQDQ */ + +-/* +- * This is a list of CPU models that are known to suffer from downclocking when +- * zmm registers (512-bit vectors) are used. On these CPUs, the AES mode +- * implementations with zmm registers won't be used by default. Implementations +- * with ymm registers (256-bit vectors) will be used by default instead. +- */ +-static const struct x86_cpu_id zmm_exclusion_list[] = { +- X86_MATCH_VFM(INTEL_SKYLAKE_X, 0), +- X86_MATCH_VFM(INTEL_ICELAKE_X, 0), +- X86_MATCH_VFM(INTEL_ICELAKE_D, 0), +- X86_MATCH_VFM(INTEL_ICELAKE, 0), +- X86_MATCH_VFM(INTEL_ICELAKE_L, 0), +- X86_MATCH_VFM(INTEL_ICELAKE_NNPI, 0), +- X86_MATCH_VFM(INTEL_TIGERLAKE_L, 0), +- X86_MATCH_VFM(INTEL_TIGERLAKE, 0), +- /* Allow Rocket Lake and later, and Sapphire Rapids and later. */ +- /* Also allow AMD CPUs (starting with Zen 4, the first with AVX-512). */ +- {}, +-}; +- + static int __init register_avx_algs(void) + { + int err; +@@ -1600,7 +1580,7 @@ static int __init register_avx_algs(void) + if (err) + return err; + +- if (x86_match_cpu(zmm_exclusion_list)) { ++ if (boot_cpu_has(X86_FEATURE_PREFER_YMM)) { + int i; + + aes_xts_alg_vaes_avx10_512.base.cra_priority = 1; +diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h +index b5c66b7465ba..e9f5338de65b 100644 +--- a/arch/x86/include/asm/cpufeatures.h ++++ b/arch/x86/include/asm/cpufeatures.h +@@ -484,6 +484,7 @@ + #define X86_FEATURE_AMD_FAST_CPPC (21*32 + 5) /* Fast CPPC */ + #define X86_FEATURE_AMD_HETEROGENEOUS_CORES (21*32 + 6) /* Heterogeneous Core Topology */ + #define X86_FEATURE_AMD_WORKLOAD_CLASS (21*32 + 7) /* Workload Classification */ ++#define X86_FEATURE_PREFER_YMM (21*32 + 8) /* Avoid ZMM registers due to downclocking */ + + /* + * BUG word(s) +diff --git a/arch/x86/kernel/cpu/intel.c b/arch/x86/kernel/cpu/intel.c +index 3dce22f00dc3..c3005c4ec46a 100644 +--- a/arch/x86/kernel/cpu/intel.c ++++ b/arch/x86/kernel/cpu/intel.c +@@ -521,6 +521,25 @@ static void init_intel_misc_features(struct cpuinfo_x86 *c) + wrmsrl(MSR_MISC_FEATURES_ENABLES, msr); + } + ++/* ++ * This is a list of Intel CPUs that are known to suffer from downclocking when ++ * ZMM registers (512-bit vectors) are used. On these CPUs, when the kernel ++ * executes SIMD-optimized code such as cryptography functions or CRCs, it ++ * should prefer 256-bit (YMM) code to 512-bit (ZMM) code. ++ */ ++static const struct x86_cpu_id zmm_exclusion_list[] = { ++ X86_MATCH_VFM(INTEL_SKYLAKE_X, 0), ++ X86_MATCH_VFM(INTEL_ICELAKE_X, 0), ++ X86_MATCH_VFM(INTEL_ICELAKE_D, 0), ++ X86_MATCH_VFM(INTEL_ICELAKE, 0), ++ X86_MATCH_VFM(INTEL_ICELAKE_L, 0), ++ X86_MATCH_VFM(INTEL_ICELAKE_NNPI, 0), ++ X86_MATCH_VFM(INTEL_TIGERLAKE_L, 0), ++ X86_MATCH_VFM(INTEL_TIGERLAKE, 0), ++ /* Allow Rocket Lake and later, and Sapphire Rapids and later. */ ++ {}, ++}; ++ + static void init_intel(struct cpuinfo_x86 *c) + { + early_init_intel(c); +@@ -601,6 +620,9 @@ static void init_intel(struct cpuinfo_x86 *c) + } + #endif + ++ if (x86_match_cpu(zmm_exclusion_list)) ++ set_cpu_cap(c, X86_FEATURE_PREFER_YMM); ++ + /* Work around errata */ + srat_detect_node(c); + +diff --git a/arch/x86/lib/Makefile b/arch/x86/lib/Makefile +index 8a59c61624c2..08496e221a7d 100644 +--- a/arch/x86/lib/Makefile ++++ b/arch/x86/lib/Makefile +@@ -43,7 +43,7 @@ crc32-x86-y := crc32-glue.o crc32-pclmul.o + crc32-x86-$(CONFIG_64BIT) += crc32c-3way.o + + obj-$(CONFIG_CRC_T10DIF_ARCH) += crc-t10dif-x86.o +-crc-t10dif-x86-y := crc-t10dif-glue.o crct10dif-pcl-asm_64.o ++crc-t10dif-x86-y := crc-t10dif-glue.o crc16-msb-pclmul.o + + obj-y += msr.o msr-reg.o msr-reg-export.o hweight.o + obj-y += iomem.o +diff --git a/arch/x86/lib/crc-pclmul-consts.h b/arch/x86/lib/crc-pclmul-consts.h +new file mode 100644 +index 000000000000..089954988f97 +--- /dev/null ++++ b/arch/x86/lib/crc-pclmul-consts.h +@@ -0,0 +1,99 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * CRC constants generated by: ++ * ++ * ./scripts/gen-crc-consts.py x86_pclmul crc16_msb_0x8bb7,crc32_lsb_0xedb88320 ++ * ++ * Do not edit manually. ++ */ ++ ++/* ++ * CRC folding constants generated for most-significant-bit-first CRC-16 using ++ * G(x) = x^16 + x^15 + x^11 + x^9 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + x^0 ++ */ ++static const struct { ++ u8 bswap_mask[16]; ++ u64 fold_across_2048_bits_consts[2]; ++ u64 fold_across_1024_bits_consts[2]; ++ u64 fold_across_512_bits_consts[2]; ++ u64 fold_across_256_bits_consts[2]; ++ u64 fold_across_128_bits_consts[2]; ++ u8 shuf_table[48]; ++ u64 barrett_reduction_consts[2]; ++} crc16_msb_0x8bb7_consts ____cacheline_aligned __maybe_unused = { ++ .bswap_mask = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, ++ .fold_across_2048_bits_consts = { ++ 0xdccf000000000000, /* LO64_TERMS: (x^2000 mod G) * x^48 */ ++ 0x4b0b000000000000, /* HI64_TERMS: (x^2064 mod G) * x^48 */ ++ }, ++ .fold_across_1024_bits_consts = { ++ 0x9d9d000000000000, /* LO64_TERMS: (x^976 mod G) * x^48 */ ++ 0x7cf5000000000000, /* HI64_TERMS: (x^1040 mod G) * x^48 */ ++ }, ++ .fold_across_512_bits_consts = { ++ 0x044c000000000000, /* LO64_TERMS: (x^464 mod G) * x^48 */ ++ 0xe658000000000000, /* HI64_TERMS: (x^528 mod G) * x^48 */ ++ }, ++ .fold_across_256_bits_consts = { ++ 0x6ee3000000000000, /* LO64_TERMS: (x^208 mod G) * x^48 */ ++ 0xe7b5000000000000, /* HI64_TERMS: (x^272 mod G) * x^48 */ ++ }, ++ .fold_across_128_bits_consts = { ++ 0x2d56000000000000, /* LO64_TERMS: (x^80 mod G) * x^48 */ ++ 0x06df000000000000, /* HI64_TERMS: (x^144 mod G) * x^48 */ ++ }, ++ .shuf_table = { ++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ }, ++ .barrett_reduction_consts = { ++ 0x8bb7000000000000, /* LO64_TERMS: (G - x^16) * x^48 */ ++ 0xf65a57f81d33a48a, /* HI64_TERMS: (floor(x^79 / G) * x) - x^64 */ ++ }, ++}; ++ ++/* ++ * CRC folding constants generated for least-significant-bit-first CRC-32 using ++ * G(x) = x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + ++ * x^5 + x^4 + x^2 + x^1 + x^0 ++ */ ++static const struct { ++ u64 fold_across_2048_bits_consts[2]; ++ u64 fold_across_1024_bits_consts[2]; ++ u64 fold_across_512_bits_consts[2]; ++ u64 fold_across_256_bits_consts[2]; ++ u64 fold_across_128_bits_consts[2]; ++ u8 shuf_table[48]; ++ u64 barrett_reduction_consts[2]; ++} crc32_lsb_0xedb88320_consts ____cacheline_aligned __maybe_unused = { ++ .fold_across_2048_bits_consts = { ++ 0x00000000ce3371cb, /* HI64_TERMS: (x^2079 mod G) * x^32 */ ++ 0x00000000e95c1271, /* LO64_TERMS: (x^2015 mod G) * x^32 */ ++ }, ++ .fold_across_1024_bits_consts = { ++ 0x0000000033fff533, /* HI64_TERMS: (x^1055 mod G) * x^32 */ ++ 0x00000000910eeec1, /* LO64_TERMS: (x^991 mod G) * x^32 */ ++ }, ++ .fold_across_512_bits_consts = { ++ 0x000000008f352d95, /* HI64_TERMS: (x^543 mod G) * x^32 */ ++ 0x000000001d9513d7, /* LO64_TERMS: (x^479 mod G) * x^32 */ ++ }, ++ .fold_across_256_bits_consts = { ++ 0x00000000f1da05aa, /* HI64_TERMS: (x^287 mod G) * x^32 */ ++ 0x0000000081256527, /* LO64_TERMS: (x^223 mod G) * x^32 */ ++ }, ++ .fold_across_128_bits_consts = { ++ 0x00000000ae689191, /* HI64_TERMS: (x^159 mod G) * x^32 */ ++ 0x00000000ccaa009e, /* LO64_TERMS: (x^95 mod G) * x^32 */ ++ }, ++ .shuf_table = { ++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ++ }, ++ .barrett_reduction_consts = { ++ 0xb4e5b025f7011641, /* HI64_TERMS: floor(x^95 / G) */ ++ 0x00000001db710640, /* LO64_TERMS: (G - x^32) * x^31 */ ++ }, ++}; +diff --git a/arch/x86/lib/crc-pclmul-template.S b/arch/x86/lib/crc-pclmul-template.S +new file mode 100644 +index 000000000000..defe9c6045cb +--- /dev/null ++++ b/arch/x86/lib/crc-pclmul-template.S +@@ -0,0 +1,584 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++// ++// Template to generate [V]PCLMULQDQ-based CRC functions for x86 ++// ++// Copyright 2025 Google LLC ++// ++// Author: Eric Biggers ++ ++#include ++ ++// Offsets within the generated constants table ++.set OFFSETOF_BSWAP_MASK, -5*16 // msb-first CRCs only ++.set OFFSETOF_FOLD_ACROSS_2048BIT_CONSTS, -4*16 // must precede next ++.set OFFSETOF_FOLD_ACROSS_1024BIT_CONSTS, -3*16 // must precede next ++.set OFFSETOF_FOLD_ACROSS_512BIT_CONSTS, -2*16 // must precede next ++.set OFFSETOF_FOLD_ACROSS_256BIT_CONSTS, -1*16 // must precede next ++.set OFFSETOF_FOLD_ACROSS_128BIT_CONSTS, 0*16 // must be 0 ++.set OFFSETOF_SHUF_TABLE, 1*16 ++.set OFFSETOF_BARRETT_REDUCTION_CONSTS, 4*16 ++ ++// Emit a VEX (or EVEX) coded instruction if allowed, or emulate it using the ++// corresponding non-VEX instruction plus any needed moves. The supported ++// instruction formats are: ++// ++// - Two-arg [src, dst], where the non-VEX form is the same. ++// - Three-arg [src1, src2, dst] where the non-VEX form is ++// [src1, src2_and_dst]. If src2 != dst, then src1 must != dst too. ++// ++// \insn gives the instruction without a "v" prefix and including any immediate ++// argument if needed to make the instruction follow one of the above formats. ++// If \unaligned_mem_tmp is given, then the emitted non-VEX code moves \arg1 to ++// it first; this is needed when \arg1 is an unaligned mem operand. ++.macro _cond_vex insn:req, arg1:req, arg2:req, arg3, unaligned_mem_tmp ++.if AVX_LEVEL == 0 ++ // VEX not allowed. Emulate it. ++ .ifnb \arg3 // Three-arg [src1, src2, dst] ++ .ifc "\arg2", "\arg3" // src2 == dst? ++ .ifnb \unaligned_mem_tmp ++ movdqu \arg1, \unaligned_mem_tmp ++ \insn \unaligned_mem_tmp, \arg3 ++ .else ++ \insn \arg1, \arg3 ++ .endif ++ .else // src2 != dst ++ .ifc "\arg1", "\arg3" ++ .error "Can't have src1 == dst when src2 != dst" ++ .endif ++ .ifnb \unaligned_mem_tmp ++ movdqu \arg1, \unaligned_mem_tmp ++ movdqa \arg2, \arg3 ++ \insn \unaligned_mem_tmp, \arg3 ++ .else ++ movdqa \arg2, \arg3 ++ \insn \arg1, \arg3 ++ .endif ++ .endif ++ .else // Two-arg [src, dst] ++ .ifnb \unaligned_mem_tmp ++ movdqu \arg1, \unaligned_mem_tmp ++ \insn \unaligned_mem_tmp, \arg2 ++ .else ++ \insn \arg1, \arg2 ++ .endif ++ .endif ++.else ++ // VEX is allowed. Emit the desired instruction directly. ++ .ifnb \arg3 ++ v\insn \arg1, \arg2, \arg3 ++ .else ++ v\insn \arg1, \arg2 ++ .endif ++.endif ++.endm ++ ++// Broadcast an aligned 128-bit mem operand to all 128-bit lanes of a vector ++// register of length VL. ++.macro _vbroadcast src, dst ++.if VL == 16 ++ _cond_vex movdqa, \src, \dst ++.elseif VL == 32 ++ vbroadcasti128 \src, \dst ++.else ++ vbroadcasti32x4 \src, \dst ++.endif ++.endm ++ ++// Load \vl bytes from the unaligned mem operand \src into \dst, and if the CRC ++// is msb-first use \bswap_mask to reflect the bytes within each 128-bit lane. ++.macro _load_data vl, src, bswap_mask, dst ++.if \vl < 64 ++ _cond_vex movdqu, "\src", \dst ++.else ++ vmovdqu8 \src, \dst ++.endif ++.if !LSB_CRC ++ _cond_vex pshufb, \bswap_mask, \dst, \dst ++.endif ++.endm ++ ++.macro _prepare_v0 vl, v0, v1, bswap_mask ++.if LSB_CRC ++ .if \vl < 64 ++ _cond_vex pxor, (BUF), \v0, \v0, unaligned_mem_tmp=\v1 ++ .else ++ vpxorq (BUF), \v0, \v0 ++ .endif ++.else ++ _load_data \vl, (BUF), \bswap_mask, \v1 ++ .if \vl < 64 ++ _cond_vex pxor, \v1, \v0, \v0 ++ .else ++ vpxorq \v1, \v0, \v0 ++ .endif ++.endif ++.endm ++ ++// The x^0..x^63 terms, i.e. poly128 mod x^64, i.e. the physically low qword for ++// msb-first order or the physically high qword for lsb-first order. ++#define LO64_TERMS 0 ++ ++// The x^64..x^127 terms, i.e. floor(poly128 / x^64), i.e. the physically high ++// qword for msb-first order or the physically low qword for lsb-first order. ++#define HI64_TERMS 1 ++ ++// Multiply the given \src1_terms of each 128-bit lane of \src1 by the given ++// \src2_terms of each 128-bit lane of \src2, and write the result(s) to \dst. ++.macro _pclmulqdq src1, src1_terms, src2, src2_terms, dst ++ _cond_vex "pclmulqdq $((\src1_terms ^ LSB_CRC) << 4) ^ (\src2_terms ^ LSB_CRC),", \ ++ \src1, \src2, \dst ++.endm ++ ++// Fold \acc into \data and store the result back into \acc. \data can be an ++// unaligned mem operand if using VEX is allowed and the CRC is lsb-first so no ++// byte-reflection is needed; otherwise it must be a vector register. \consts ++// is a vector register containing the needed fold constants, and \tmp is a ++// temporary vector register. All arguments must be the same length. ++.macro _fold_vec acc, data, consts, tmp ++ _pclmulqdq \consts, HI64_TERMS, \acc, HI64_TERMS, \tmp ++ _pclmulqdq \consts, LO64_TERMS, \acc, LO64_TERMS, \acc ++.if AVX_LEVEL < 10 ++ _cond_vex pxor, \data, \tmp, \tmp ++ _cond_vex pxor, \tmp, \acc, \acc ++.else ++ vpternlogq $0x96, \data, \tmp, \acc ++.endif ++.endm ++ ++// Fold \acc into \data and store the result back into \acc. \data is an ++// unaligned mem operand, \consts is a vector register containing the needed ++// fold constants, \bswap_mask is a vector register containing the ++// byte-reflection table if the CRC is msb-first, and \tmp1 and \tmp2 are ++// temporary vector registers. All arguments must have length \vl. ++.macro _fold_vec_mem vl, acc, data, consts, bswap_mask, tmp1, tmp2 ++.if AVX_LEVEL == 0 || !LSB_CRC ++ _load_data \vl, \data, \bswap_mask, \tmp1 ++ _fold_vec \acc, \tmp1, \consts, \tmp2 ++.else ++ _fold_vec \acc, \data, \consts, \tmp1 ++.endif ++.endm ++ ++// Load the constants for folding across 2**i vectors of length VL at a time ++// into all 128-bit lanes of the vector register CONSTS. ++.macro _load_vec_folding_consts i ++ _vbroadcast OFFSETOF_FOLD_ACROSS_128BIT_CONSTS+(4-LOG2_VL-\i)*16(CONSTS_PTR), \ ++ CONSTS ++.endm ++ ++// Given vector registers \v0 and \v1 of length \vl, fold \v0 into \v1 and store ++// the result back into \v0. If the remaining length mod \vl is nonzero, also ++// fold \vl bytes from (BUF). For both operations the fold distance is \vl. ++// \consts must be a register of length \vl containing the fold constants. ++.macro _fold_vec_final vl, v0, v1, consts, bswap_mask, tmp1, tmp2 ++ _fold_vec \v0, \v1, \consts, \tmp1 ++ test $\vl, LEN8 ++ jz .Lfold_vec_final_done\@ ++ _fold_vec_mem \vl, \v0, (BUF), \consts, \bswap_mask, \tmp1, \tmp2 ++ add $\vl, BUF ++.Lfold_vec_final_done\@: ++.endm ++ ++// This macro generates the body of a CRC function with the following prototype: ++// ++// crc_t crc_func(crc_t crc, const u8 *buf, size_t len, const void *consts); ++// ++// |crc| is the initial CRC, and crc_t is a data type wide enough to hold it. ++// |buf| is the data to checksum. |len| is the data length in bytes, which must ++// be at least 16. |consts| is a pointer to the fold_across_128_bits_consts ++// field of the constants table that was generated for the chosen CRC variant. ++// ++// Moving onto the macro parameters, \n is the number of bits in the CRC, e.g. ++// 32 for a CRC-32. Currently the supported values are 8, 16, 32, and 64. If ++// the file is compiled in i386 mode, then the maximum supported value is 32. ++// ++// \lsb_crc is 1 if the CRC processes the least significant bit of each byte ++// first, i.e. maps bit0 to x^7, bit1 to x^6, ..., bit7 to x^0. \lsb_crc is 0 ++// if the CRC processes the most significant bit of each byte first, i.e. maps ++// bit0 to x^0, bit1 to x^1, bit7 to x^7. ++// ++// \vl is the maximum length of vector register to use in bytes: 16, 32, or 64. ++// ++// \avx_level is the level of AVX support to use: 0 for SSE only, 2 for AVX2, or ++// 10 for AVX10 or AVX512. ++// ++// If \vl == 16 && \avx_level == 0, the generated code requires: ++// PCLMULQDQ && SSE4.1. (Note: all known CPUs with PCLMULQDQ also have SSE4.1.) ++// ++// If \vl == 32 && \avx_level == 2, the generated code requires: ++// VPCLMULQDQ && AVX2. ++// ++// If \vl == 32 && \avx_level == 10, the generated code requires: ++// VPCLMULQDQ && (AVX10/256 || (AVX512BW && AVX512VL)) ++// ++// If \vl == 64 && \avx_level == 10, the generated code requires: ++// VPCLMULQDQ && (AVX10/512 || (AVX512BW && AVX512VL)) ++// ++// Other \vl and \avx_level combinations are either not supported or not useful. ++.macro _crc_pclmul n, lsb_crc, vl, avx_level ++ .set LSB_CRC, \lsb_crc ++ .set VL, \vl ++ .set AVX_LEVEL, \avx_level ++ ++ // Define aliases for the xmm, ymm, or zmm registers according to VL. ++.irp i, 0,1,2,3,4,5,6,7 ++ .if VL == 16 ++ .set V\i, %xmm\i ++ .set LOG2_VL, 4 ++ .elseif VL == 32 ++ .set V\i, %ymm\i ++ .set LOG2_VL, 5 ++ .elseif VL == 64 ++ .set V\i, %zmm\i ++ .set LOG2_VL, 6 ++ .else ++ .error "Unsupported vector length" ++ .endif ++.endr ++ // Define aliases for the function parameters. ++ // Note: when crc_t is shorter than u32, zero-extension to 32 bits is ++ // guaranteed by the ABI. Zero-extension to 64 bits is *not* guaranteed ++ // when crc_t is shorter than u64. ++#ifdef __x86_64__ ++.if \n <= 32 ++ .set CRC, %edi ++.else ++ .set CRC, %rdi ++.endif ++ .set BUF, %rsi ++ .set LEN, %rdx ++ .set LEN32, %edx ++ .set LEN8, %dl ++ .set CONSTS_PTR, %rcx ++#else ++ // 32-bit support, assuming -mregparm=3 and not including support for ++ // CRC-64 (which would use both eax and edx to pass the crc parameter). ++ .set CRC, %eax ++ .set BUF, %edx ++ .set LEN, %ecx ++ .set LEN32, %ecx ++ .set LEN8, %cl ++ .set CONSTS_PTR, %ebx // Passed on stack ++#endif ++ ++ // Define aliases for some local variables. V0-V5 are used without ++ // aliases (for accumulators, data, temporary values, etc). Staying ++ // within the first 8 vector registers keeps the code 32-bit SSE ++ // compatible and reduces the size of 64-bit SSE code slightly. ++ .set BSWAP_MASK, V6 ++ .set BSWAP_MASK_YMM, %ymm6 ++ .set BSWAP_MASK_XMM, %xmm6 ++ .set CONSTS, V7 ++ .set CONSTS_YMM, %ymm7 ++ .set CONSTS_XMM, %xmm7 ++ ++#ifdef __i386__ ++ push CONSTS_PTR ++ mov 8(%esp), CONSTS_PTR ++#endif ++ ++ // Create a 128-bit vector that contains the initial CRC in the end ++ // representing the high-order polynomial coefficients, and the rest 0. ++.if \n <= 32 ++ _cond_vex movd, CRC, %xmm0 ++.else ++ _cond_vex movq, CRC, %xmm0 ++.endif ++.if !LSB_CRC ++ _cond_vex pslldq, $(128-\n)/8, %xmm0, %xmm0 ++ _vbroadcast OFFSETOF_BSWAP_MASK(CONSTS_PTR), BSWAP_MASK ++.endif ++ ++ // Load the first vector of data and XOR the initial CRC into the ++ // appropriate end of the first 128-bit lane of data. If LEN < VL, then ++ // use a short vector and jump to the end to do the final reduction. ++ // (LEN >= 16 is guaranteed here but not necessarily LEN >= VL.) ++.if VL >= 32 ++ cmp $VL, LEN ++ jae 2f ++ .if VL == 64 ++ cmp $32, LEN32 ++ jb 1f ++ _prepare_v0 32, %ymm0, %ymm1, BSWAP_MASK_YMM ++ add $32, BUF ++ jmp .Lreduce_256bits_to_128bits\@ ++1: ++ .endif ++ _prepare_v0 16, %xmm0, %xmm1, BSWAP_MASK_XMM ++ add $16, BUF ++ vmovdqa OFFSETOF_FOLD_ACROSS_128BIT_CONSTS(CONSTS_PTR), CONSTS_XMM ++ jmp .Lcheck_for_partial_block\@ ++2: ++.endif ++ _prepare_v0 VL, V0, V1, BSWAP_MASK ++ ++ // Handle VL <= LEN < 4*VL. ++ cmp $4*VL-1, LEN ++ ja .Lfold_4vecs_prepare\@ ++ add $VL, BUF ++ // If VL <= LEN < 2*VL, then jump to the code at the end that handles ++ // the reduction from 1 vector. If VL==16 then ++ // fold_across_128bit_consts must be loaded first, as the final ++ // reduction depends on it and it won't be loaded anywhere else. ++ cmp $2*VL-1, LEN32 ++.if VL == 16 ++ _cond_vex movdqa, OFFSETOF_FOLD_ACROSS_128BIT_CONSTS(CONSTS_PTR), CONSTS_XMM ++.endif ++ jbe .Lreduce_1vec_to_128bits\@ ++ // Otherwise 2*VL <= LEN < 4*VL. Load one more vector and jump to the ++ // code at the end that handles the reduction from 2 vectors. ++ _load_data VL, (BUF), BSWAP_MASK, V1 ++ add $VL, BUF ++ jmp .Lreduce_2vecs_to_1\@ ++ ++.Lfold_4vecs_prepare\@: ++ // Load 3 more vectors of data. ++ _load_data VL, 1*VL(BUF), BSWAP_MASK, V1 ++ _load_data VL, 2*VL(BUF), BSWAP_MASK, V2 ++ _load_data VL, 3*VL(BUF), BSWAP_MASK, V3 ++ sub $-4*VL, BUF // Shorter than 'add 4*VL' when VL=32 ++ add $-4*VL, LEN // Shorter than 'sub 4*VL' when VL=32 ++ ++ // While >= 4 vectors of data remain, fold the 4 vectors V0-V3 into the ++ // next 4 vectors of data and write the result back to V0-V3. ++ cmp $4*VL-1, LEN // Shorter than 'cmp 4*VL' when VL=32 ++ jbe .Lreduce_4vecs_to_2\@ ++ _load_vec_folding_consts 2 ++.Lfold_4vecs_loop\@: ++ _fold_vec_mem VL, V0, 0*VL(BUF), CONSTS, BSWAP_MASK, V4, V5 ++ _fold_vec_mem VL, V1, 1*VL(BUF), CONSTS, BSWAP_MASK, V4, V5 ++ _fold_vec_mem VL, V2, 2*VL(BUF), CONSTS, BSWAP_MASK, V4, V5 ++ _fold_vec_mem VL, V3, 3*VL(BUF), CONSTS, BSWAP_MASK, V4, V5 ++ sub $-4*VL, BUF ++ add $-4*VL, LEN ++ cmp $4*VL-1, LEN ++ ja .Lfold_4vecs_loop\@ ++ ++ // Fold V0,V1 into V2,V3 and write the result back to V0,V1. ++ // Then fold two vectors of data, if at least that much remains. ++.Lreduce_4vecs_to_2\@: ++ _load_vec_folding_consts 1 ++ _fold_vec V0, V2, CONSTS, V4 ++ _fold_vec V1, V3, CONSTS, V4 ++ test $2*VL, LEN8 ++ jz .Lreduce_2vecs_to_1\@ ++ _fold_vec_mem VL, V0, 0*VL(BUF), CONSTS, BSWAP_MASK, V4, V5 ++ _fold_vec_mem VL, V1, 1*VL(BUF), CONSTS, BSWAP_MASK, V4, V5 ++ sub $-2*VL, BUF ++ ++ // Fold V0 into V1 and write the result back to V0. ++ // Then fold one vector of data, if at least that much remains. ++.Lreduce_2vecs_to_1\@: ++ _load_vec_folding_consts 0 ++ _fold_vec_final VL, V0, V1, CONSTS, BSWAP_MASK, V4, V5 ++ ++.Lreduce_1vec_to_128bits\@: ++ // Reduce V0 to 128 bits xmm0. ++.if VL == 64 ++ // zmm0 => ymm0 ++ vbroadcasti128 OFFSETOF_FOLD_ACROSS_256BIT_CONSTS(CONSTS_PTR), CONSTS_YMM ++ vextracti64x4 $1, %zmm0, %ymm1 ++ _fold_vec_final 32, %ymm0, %ymm1, CONSTS_YMM, BSWAP_MASK_YMM, %ymm4, %ymm5 ++.endif ++.if VL >= 32 ++.Lreduce_256bits_to_128bits\@: ++ // ymm0 => xmm0 ++ vmovdqa OFFSETOF_FOLD_ACROSS_128BIT_CONSTS(CONSTS_PTR), CONSTS_XMM ++ vextracti128 $1, %ymm0, %xmm1 ++ _fold_vec_final 16, %xmm0, %xmm1, CONSTS_XMM, BSWAP_MASK_XMM, %xmm4, %xmm5 ++.endif ++ ++.Lcheck_for_partial_block\@: ++ and $15, LEN32 ++ jz .Lpartial_block_done\@ ++ ++ // 1 <= LEN <= 15 data bytes remain. The polynomial is now ++ // A*(x^(8*LEN)) + B, where A = %xmm0 and B is the polynomial of the ++ // remaining LEN bytes. To reduce this to 128 bits without needing fold ++ // constants for each possible LEN, rearrange this expression into ++ // C1*(x^128) + C2, where C1 = floor(A / x^(128 - 8*LEN)) and ++ // C2 = A*x^(8*LEN) + B mod x^128. Then fold C1 into C2, which is just ++ // another fold across 128 bits. ++ ++.if !LSB_CRC || AVX_LEVEL == 0 ++ // Load the last 16 data bytes. ++ _load_data 16, "-16(BUF,LEN)", BSWAP_MASK_XMM, %xmm1 ++.endif // Else will use vpblendvb mem operand later. ++.if !LSB_CRC ++ neg LEN // Needed for indexing shuf_table ++.endif ++ ++ // tmp = A*x^(8*LEN) mod x^128 ++ // lsb: pshufb by [LEN, LEN+1, ..., 15, -1, -1, ..., -1] ++ // i.e. right-shift by LEN bytes. ++ // msb: pshufb by [-1, -1, ..., -1, 0, 1, ..., 15-LEN] ++ // i.e. left-shift by LEN bytes. ++ _cond_vex movdqu, "OFFSETOF_SHUF_TABLE+16(CONSTS_PTR,LEN)", %xmm3 ++ _cond_vex pshufb, %xmm3, %xmm0, %xmm2 ++ ++ // C1 = floor(A / x^(128 - 8*LEN)) ++ // lsb: pshufb by [-1, -1, ..., -1, 0, 1, ..., LEN-1] ++ // i.e. left-shift by 16-LEN bytes. ++ // msb: pshufb by [16-LEN, 16-LEN+1, ..., 15, -1, -1, ..., -1] ++ // i.e. right-shift by 16-LEN bytes. ++ _cond_vex pshufb, "OFFSETOF_SHUF_TABLE+32*!LSB_CRC(CONSTS_PTR,LEN)", \ ++ %xmm0, %xmm0, unaligned_mem_tmp=%xmm4 ++ ++ // C2 = tmp + B. This is just a blend of tmp with the last 16 data ++ // bytes (reflected if msb-first). The blend mask is the shuffle table ++ // that was used to create tmp. 0 selects tmp, and 1 last16databytes. ++.if AVX_LEVEL == 0 ++ movdqa %xmm0, %xmm4 ++ movdqa %xmm3, %xmm0 ++ pblendvb %xmm1, %xmm2 // uses %xmm0 as implicit operand ++ movdqa %xmm4, %xmm0 ++.elseif LSB_CRC ++ vpblendvb %xmm3, -16(BUF,LEN), %xmm2, %xmm2 ++.else ++ vpblendvb %xmm3, %xmm1, %xmm2, %xmm2 ++.endif ++ ++ // Fold C1 into C2 and store the 128-bit result in xmm0. ++ _fold_vec %xmm0, %xmm2, CONSTS_XMM, %xmm4 ++ ++.Lpartial_block_done\@: ++ // Compute the CRC as %xmm0 * x^n mod G. Here %xmm0 means the 128-bit ++ // polynomial stored in %xmm0 (using either lsb-first or msb-first bit ++ // order according to LSB_CRC), and G is the CRC's generator polynomial. ++ ++ // First, multiply %xmm0 by x^n and reduce the result to 64+n bits: ++ // ++ // t0 := (x^(64+n) mod G) * floor(%xmm0 / x^64) + ++ // x^n * (%xmm0 mod x^64) ++ // ++ // Store t0 * x^(64-n) in %xmm0. I.e., actually do: ++ // ++ // %xmm0 := ((x^(64+n) mod G) * x^(64-n)) * floor(%xmm0 / x^64) + ++ // x^64 * (%xmm0 mod x^64) ++ // ++ // The extra unreduced factor of x^(64-n) makes floor(t0 / x^n) aligned ++ // to the HI64_TERMS of %xmm0 so that the next pclmulqdq can easily ++ // select it. The 64-bit constant (x^(64+n) mod G) * x^(64-n) in the ++ // msb-first case, or (x^(63+n) mod G) * x^(64-n) in the lsb-first case ++ // (considering the extra factor of x that gets implicitly introduced by ++ // each pclmulqdq when using lsb-first order), is identical to the ++ // constant that was used earlier for folding the LO64_TERMS across 128 ++ // bits. Thus it's already available in LO64_TERMS of CONSTS_XMM. ++ _pclmulqdq CONSTS_XMM, LO64_TERMS, %xmm0, HI64_TERMS, %xmm1 ++.if LSB_CRC ++ _cond_vex psrldq, $8, %xmm0, %xmm0 // x^64 * (%xmm0 mod x^64) ++.else ++ _cond_vex pslldq, $8, %xmm0, %xmm0 // x^64 * (%xmm0 mod x^64) ++.endif ++ _cond_vex pxor, %xmm1, %xmm0, %xmm0 ++ // The HI64_TERMS of %xmm0 now contain floor(t0 / x^n). ++ // The LO64_TERMS of %xmm0 now contain (t0 mod x^n) * x^(64-n). ++ ++ // First step of Barrett reduction: Compute floor(t0 / G). This is the ++ // polynomial by which G needs to be multiplied to cancel out the x^n ++ // and higher terms of t0, i.e. to reduce t0 mod G. First do: ++ // ++ // t1 := floor(x^(63+n) / G) * x * floor(t0 / x^n) ++ // ++ // Then the desired value floor(t0 / G) is floor(t1 / x^64). The 63 in ++ // x^(63+n) is the maximum degree of floor(t0 / x^n) and thus the lowest ++ // value that makes enough precision be carried through the calculation. ++ // ++ // The '* x' makes it so the result is floor(t1 / x^64) rather than ++ // floor(t1 / x^63), making it qword-aligned in HI64_TERMS so that it ++ // can be extracted much more easily in the next step. In the lsb-first ++ // case the '* x' happens implicitly. In the msb-first case it must be ++ // done explicitly; floor(x^(63+n) / G) * x is a 65-bit constant, so the ++ // constant passed to pclmulqdq is (floor(x^(63+n) / G) * x) - x^64, and ++ // the multiplication by the x^64 term is handled using a pxor. The ++ // pxor causes the low 64 terms of t1 to be wrong, but they are unused. ++ _cond_vex movdqa, OFFSETOF_BARRETT_REDUCTION_CONSTS(CONSTS_PTR), CONSTS_XMM ++ _pclmulqdq CONSTS_XMM, HI64_TERMS, %xmm0, HI64_TERMS, %xmm1 ++.if !LSB_CRC ++ _cond_vex pxor, %xmm0, %xmm1, %xmm1 ++.endif ++ // The HI64_TERMS of %xmm1 now contain floor(t1 / x^64) = floor(t0 / G). ++ ++ // Second step of Barrett reduction: Cancel out the x^n and higher terms ++ // of t0 by subtracting the needed multiple of G. This gives the CRC: ++ // ++ // crc := t0 - (G * floor(t0 / G)) ++ // ++ // But %xmm0 contains t0 * x^(64-n), so it's more convenient to do: ++ // ++ // crc := ((t0 * x^(64-n)) - ((G * x^(64-n)) * floor(t0 / G))) / x^(64-n) ++ // ++ // Furthermore, since the resulting CRC is n-bit, if mod x^n is ++ // explicitly applied to it then the x^n term of G makes no difference ++ // in the result and can be omitted. This helps keep the constant ++ // multiplier in 64 bits in most cases. This gives the following: ++ // ++ // %xmm0 := %xmm0 - (((G - x^n) * x^(64-n)) * floor(t0 / G)) ++ // crc := (%xmm0 / x^(64-n)) mod x^n ++ // ++ // In the lsb-first case, each pclmulqdq implicitly introduces ++ // an extra factor of x, so in that case the constant that needs to be ++ // passed to pclmulqdq is actually '(G - x^n) * x^(63-n)' when n <= 63. ++ // For lsb-first CRCs where n=64, the extra factor of x cannot be as ++ // easily avoided. In that case, instead pass '(G - x^n - x^0) / x' to ++ // pclmulqdq and handle the x^0 term (i.e. 1) separately. (All CRC ++ // polynomials have nonzero x^n and x^0 terms.) It works out as: the ++ // CRC has be XORed with the physically low qword of %xmm1, representing ++ // floor(t0 / G). The most efficient way to do that is to move it to ++ // the physically high qword and use a ternlog to combine the two XORs. ++.if LSB_CRC && \n == 64 ++ _cond_vex punpcklqdq, %xmm1, %xmm2, %xmm2 ++ _pclmulqdq CONSTS_XMM, LO64_TERMS, %xmm1, HI64_TERMS, %xmm1 ++ .if AVX_LEVEL < 10 ++ _cond_vex pxor, %xmm2, %xmm0, %xmm0 ++ _cond_vex pxor, %xmm1, %xmm0, %xmm0 ++ .else ++ vpternlogq $0x96, %xmm2, %xmm1, %xmm0 ++ .endif ++ _cond_vex "pextrq $1,", %xmm0, %rax // (%xmm0 / x^0) mod x^64 ++.else ++ _pclmulqdq CONSTS_XMM, LO64_TERMS, %xmm1, HI64_TERMS, %xmm1 ++ _cond_vex pxor, %xmm1, %xmm0, %xmm0 ++ .if \n == 8 ++ _cond_vex "pextrb $7 + LSB_CRC,", %xmm0, %eax // (%xmm0 / x^56) mod x^8 ++ .elseif \n == 16 ++ _cond_vex "pextrw $3 + LSB_CRC,", %xmm0, %eax // (%xmm0 / x^48) mod x^16 ++ .elseif \n == 32 ++ _cond_vex "pextrd $1 + LSB_CRC,", %xmm0, %eax // (%xmm0 / x^32) mod x^32 ++ .else // \n == 64 && !LSB_CRC ++ _cond_vex movq, %xmm0, %rax // (%xmm0 / x^0) mod x^64 ++ .endif ++.endif ++ ++.if VL > 16 ++ vzeroupper // Needed when ymm or zmm registers may have been used. ++.endif ++#ifdef __i386__ ++ pop CONSTS_PTR ++#endif ++ RET ++.endm ++ ++#ifdef CONFIG_AS_VPCLMULQDQ ++#define DEFINE_CRC_PCLMUL_FUNCS(prefix, bits, lsb) \ ++SYM_FUNC_START(prefix##_pclmul_sse); \ ++ _crc_pclmul n=bits, lsb_crc=lsb, vl=16, avx_level=0; \ ++SYM_FUNC_END(prefix##_pclmul_sse); \ ++ \ ++SYM_FUNC_START(prefix##_vpclmul_avx2); \ ++ _crc_pclmul n=bits, lsb_crc=lsb, vl=32, avx_level=2; \ ++SYM_FUNC_END(prefix##_vpclmul_avx2); \ ++ \ ++SYM_FUNC_START(prefix##_vpclmul_avx10_256); \ ++ _crc_pclmul n=bits, lsb_crc=lsb, vl=32, avx_level=10; \ ++SYM_FUNC_END(prefix##_vpclmul_avx10_256); \ ++ \ ++SYM_FUNC_START(prefix##_vpclmul_avx10_512); \ ++ _crc_pclmul n=bits, lsb_crc=lsb, vl=64, avx_level=10; \ ++SYM_FUNC_END(prefix##_vpclmul_avx10_512); ++#else ++#define DEFINE_CRC_PCLMUL_FUNCS(prefix, bits, lsb) \ ++SYM_FUNC_START(prefix##_pclmul_sse); \ ++ _crc_pclmul n=bits, lsb_crc=lsb, vl=16, avx_level=0; \ ++SYM_FUNC_END(prefix##_pclmul_sse); ++#endif // !CONFIG_AS_VPCLMULQDQ +diff --git a/arch/x86/lib/crc-pclmul-template.h b/arch/x86/lib/crc-pclmul-template.h +new file mode 100644 +index 000000000000..7b89f0edbc17 +--- /dev/null ++++ b/arch/x86/lib/crc-pclmul-template.h +@@ -0,0 +1,81 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Macros for accessing the [V]PCLMULQDQ-based CRC functions that are ++ * instantiated by crc-pclmul-template.S ++ * ++ * Copyright 2025 Google LLC ++ * ++ * Author: Eric Biggers ++ */ ++#ifndef _CRC_PCLMUL_TEMPLATE_H ++#define _CRC_PCLMUL_TEMPLATE_H ++ ++#include ++#include ++#include ++#include ++#include "crc-pclmul-consts.h" ++ ++#define DECLARE_CRC_PCLMUL_FUNCS(prefix, crc_t) \ ++crc_t prefix##_pclmul_sse(crc_t crc, const u8 *p, size_t len, \ ++ const void *consts_ptr); \ ++crc_t prefix##_vpclmul_avx2(crc_t crc, const u8 *p, size_t len, \ ++ const void *consts_ptr); \ ++crc_t prefix##_vpclmul_avx10_256(crc_t crc, const u8 *p, size_t len, \ ++ const void *consts_ptr); \ ++crc_t prefix##_vpclmul_avx10_512(crc_t crc, const u8 *p, size_t len, \ ++ const void *consts_ptr); \ ++DEFINE_STATIC_CALL(prefix##_pclmul, prefix##_pclmul_sse) ++ ++#define INIT_CRC_PCLMUL(prefix) \ ++do { \ ++ if (IS_ENABLED(CONFIG_AS_VPCLMULQDQ) && \ ++ boot_cpu_has(X86_FEATURE_VPCLMULQDQ) && \ ++ boot_cpu_has(X86_FEATURE_AVX2) && \ ++ cpu_has_xfeatures(XFEATURE_MASK_YMM, NULL)) { \ ++ if (boot_cpu_has(X86_FEATURE_AVX512BW) && \ ++ boot_cpu_has(X86_FEATURE_AVX512VL) && \ ++ cpu_has_xfeatures(XFEATURE_MASK_AVX512, NULL)) { \ ++ if (boot_cpu_has(X86_FEATURE_PREFER_YMM)) \ ++ static_call_update(prefix##_pclmul, \ ++ prefix##_vpclmul_avx10_256); \ ++ else \ ++ static_call_update(prefix##_pclmul, \ ++ prefix##_vpclmul_avx10_512); \ ++ } else { \ ++ static_call_update(prefix##_pclmul, \ ++ prefix##_vpclmul_avx2); \ ++ } \ ++ } \ ++} while (0) ++ ++/* ++ * Call a [V]PCLMULQDQ optimized CRC function if the data length is at least 16 ++ * bytes, the CPU has PCLMULQDQ support, and the current context may use SIMD. ++ * ++ * 16 bytes is the minimum length supported by the [V]PCLMULQDQ functions. ++ * There is overhead associated with kernel_fpu_begin() and kernel_fpu_end(), ++ * varying by CPU and factors such as which parts of the "FPU" state userspace ++ * has touched, which could result in a larger cutoff being better. Indeed, a ++ * larger cutoff is usually better for a *single* message. However, the ++ * overhead of the FPU section gets amortized if multiple FPU sections get ++ * executed before returning to userspace, since the XSAVE and XRSTOR occur only ++ * once. Considering that and the fact that the [V]PCLMULQDQ code is lighter on ++ * the dcache than the table-based code is, a 16-byte cutoff seems to work well. ++ */ ++#define CRC_PCLMUL(crc, p, len, prefix, consts, have_pclmulqdq) \ ++do { \ ++ if ((len) >= 16 && static_branch_likely(&(have_pclmulqdq)) && \ ++ crypto_simd_usable()) { \ ++ const void *consts_ptr; \ ++ \ ++ consts_ptr = (consts).fold_across_128_bits_consts; \ ++ kernel_fpu_begin(); \ ++ crc = static_call(prefix##_pclmul)((crc), (p), (len), \ ++ consts_ptr); \ ++ kernel_fpu_end(); \ ++ return crc; \ ++ } \ ++} while (0) ++ ++#endif /* _CRC_PCLMUL_TEMPLATE_H */ +diff --git a/arch/x86/lib/crc-t10dif-glue.c b/arch/x86/lib/crc-t10dif-glue.c +index 13f07ddc9122..6b09374b8355 100644 +--- a/arch/x86/lib/crc-t10dif-glue.c ++++ b/arch/x86/lib/crc-t10dif-glue.c +@@ -1,37 +1,32 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + /* +- * CRC-T10DIF using PCLMULQDQ instructions ++ * CRC-T10DIF using [V]PCLMULQDQ instructions + * + * Copyright 2024 Google LLC + */ + +-#include +-#include +-#include + #include + #include ++#include "crc-pclmul-template.h" + + static DEFINE_STATIC_KEY_FALSE(have_pclmulqdq); + +-asmlinkage u16 crc_t10dif_pcl(u16 init_crc, const u8 *buf, size_t len); ++DECLARE_CRC_PCLMUL_FUNCS(crc16_msb, u16); + + u16 crc_t10dif_arch(u16 crc, const u8 *p, size_t len) + { +- if (len >= 16 && +- static_key_enabled(&have_pclmulqdq) && crypto_simd_usable()) { +- kernel_fpu_begin(); +- crc = crc_t10dif_pcl(crc, p, len); +- kernel_fpu_end(); +- return crc; +- } ++ CRC_PCLMUL(crc, p, len, crc16_msb, crc16_msb_0x8bb7_consts, ++ have_pclmulqdq); + return crc_t10dif_generic(crc, p, len); + } + EXPORT_SYMBOL(crc_t10dif_arch); + + static int __init crc_t10dif_x86_init(void) + { +- if (boot_cpu_has(X86_FEATURE_PCLMULQDQ)) ++ if (boot_cpu_has(X86_FEATURE_PCLMULQDQ)) { + static_branch_enable(&have_pclmulqdq); ++ INIT_CRC_PCLMUL(crc16_msb); ++ } + return 0; + } + arch_initcall(crc_t10dif_x86_init); +@@ -47,5 +42,5 @@ bool crc_t10dif_is_optimized(void) + } + EXPORT_SYMBOL(crc_t10dif_is_optimized); + +-MODULE_DESCRIPTION("CRC-T10DIF using PCLMULQDQ instructions"); ++MODULE_DESCRIPTION("CRC-T10DIF using [V]PCLMULQDQ instructions"); + MODULE_LICENSE("GPL"); +diff --git a/arch/x86/lib/crc16-msb-pclmul.S b/arch/x86/lib/crc16-msb-pclmul.S +new file mode 100644 +index 000000000000..e9fe248093a8 +--- /dev/null ++++ b/arch/x86/lib/crc16-msb-pclmul.S +@@ -0,0 +1,6 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++// Copyright 2025 Google LLC ++ ++#include "crc-pclmul-template.S" ++ ++DEFINE_CRC_PCLMUL_FUNCS(crc16_msb, /* bits= */ 16, /* lsb= */ 0) +diff --git a/arch/x86/lib/crc32-glue.c b/arch/x86/lib/crc32-glue.c +index 2dd18a886ded..1a579a4fcded 100644 +--- a/arch/x86/lib/crc32-glue.c ++++ b/arch/x86/lib/crc32-glue.c +@@ -7,43 +7,20 @@ + * Copyright 2024 Google LLC + */ + +-#include +-#include +-#include + #include +-#include + #include +- +-/* minimum size of buffer for crc32_pclmul_le_16 */ +-#define CRC32_PCLMUL_MIN_LEN 64 ++#include "crc-pclmul-template.h" + + static DEFINE_STATIC_KEY_FALSE(have_crc32); + static DEFINE_STATIC_KEY_FALSE(have_pclmulqdq); + +-u32 crc32_pclmul_le_16(u32 crc, const u8 *buffer, size_t len); ++DECLARE_CRC_PCLMUL_FUNCS(crc32_lsb, u32); + + u32 crc32_le_arch(u32 crc, const u8 *p, size_t len) + { +- if (len >= CRC32_PCLMUL_MIN_LEN + 15 && +- static_branch_likely(&have_pclmulqdq) && crypto_simd_usable()) { +- size_t n = -(uintptr_t)p & 15; +- +- /* align p to 16-byte boundary */ +- if (n) { +- crc = crc32_le_base(crc, p, n); +- p += n; +- len -= n; +- } +- n = round_down(len, 16); +- kernel_fpu_begin(); +- crc = crc32_pclmul_le_16(crc, p, n); +- kernel_fpu_end(); +- p += n; +- len -= n; +- } +- if (len) +- crc = crc32_le_base(crc, p, len); +- return crc; ++ CRC_PCLMUL(crc, p, len, crc32_lsb, crc32_lsb_0xedb88320_consts, ++ have_pclmulqdq); ++ return crc32_le_base(crc, p, len); + } + EXPORT_SYMBOL(crc32_le_arch); + +@@ -97,8 +74,10 @@ static int __init crc32_x86_init(void) + { + if (boot_cpu_has(X86_FEATURE_XMM4_2)) + static_branch_enable(&have_crc32); +- if (boot_cpu_has(X86_FEATURE_PCLMULQDQ)) ++ if (boot_cpu_has(X86_FEATURE_PCLMULQDQ)) { + static_branch_enable(&have_pclmulqdq); ++ INIT_CRC_PCLMUL(crc32_lsb); ++ } + return 0; + } + arch_initcall(crc32_x86_init); +diff --git a/arch/x86/lib/crc32-pclmul.S b/arch/x86/lib/crc32-pclmul.S +index f9637789cac1..f20f40fb0172 100644 +--- a/arch/x86/lib/crc32-pclmul.S ++++ b/arch/x86/lib/crc32-pclmul.S +@@ -1,217 +1,6 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- * Copyright 2012 Xyratex Technology Limited +- * +- * Using hardware provided PCLMULQDQ instruction to accelerate the CRC32 +- * calculation. +- * CRC32 polynomial:0x04c11db7(BE)/0xEDB88320(LE) +- * PCLMULQDQ is a new instruction in Intel SSE4.2, the reference can be found +- * at: +- * http://www.intel.com/products/processor/manuals/ +- * Intel(R) 64 and IA-32 Architectures Software Developer's Manual +- * Volume 2B: Instruction Set Reference, N-Z +- * +- * Authors: Gregory Prestas +- * Alexander Boyko +- */ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++// Copyright 2025 Google LLC + +-#include ++#include "crc-pclmul-template.S" + +- +-.section .rodata +-.align 16 +-/* +- * [x4*128+32 mod P(x) << 32)]' << 1 = 0x154442bd4 +- * #define CONSTANT_R1 0x154442bd4LL +- * +- * [(x4*128-32 mod P(x) << 32)]' << 1 = 0x1c6e41596 +- * #define CONSTANT_R2 0x1c6e41596LL +- */ +-.Lconstant_R2R1: +- .octa 0x00000001c6e415960000000154442bd4 +-/* +- * [(x128+32 mod P(x) << 32)]' << 1 = 0x1751997d0 +- * #define CONSTANT_R3 0x1751997d0LL +- * +- * [(x128-32 mod P(x) << 32)]' << 1 = 0x0ccaa009e +- * #define CONSTANT_R4 0x0ccaa009eLL +- */ +-.Lconstant_R4R3: +- .octa 0x00000000ccaa009e00000001751997d0 +-/* +- * [(x64 mod P(x) << 32)]' << 1 = 0x163cd6124 +- * #define CONSTANT_R5 0x163cd6124LL +- */ +-.Lconstant_R5: +- .octa 0x00000000000000000000000163cd6124 +-.Lconstant_mask32: +- .octa 0x000000000000000000000000FFFFFFFF +-/* +- * #define CRCPOLY_TRUE_LE_FULL 0x1DB710641LL +- * +- * Barrett Reduction constant (u64`) = u` = (x**64 / P(x))` = 0x1F7011641LL +- * #define CONSTANT_RU 0x1F7011641LL +- */ +-.Lconstant_RUpoly: +- .octa 0x00000001F701164100000001DB710641 +- +-#define CONSTANT %xmm0 +- +-#ifdef __x86_64__ +-#define CRC %edi +-#define BUF %rsi +-#define LEN %rdx +-#else +-#define CRC %eax +-#define BUF %edx +-#define LEN %ecx +-#endif +- +- +- +-.text +-/** +- * Calculate crc32 +- * CRC - initial crc32 +- * BUF - buffer (16 bytes aligned) +- * LEN - sizeof buffer (16 bytes aligned), LEN should be greater than 63 +- * return %eax crc32 +- * u32 crc32_pclmul_le_16(u32 crc, const u8 *buffer, size_t len); +- */ +- +-SYM_FUNC_START(crc32_pclmul_le_16) /* buffer and buffer size are 16 bytes aligned */ +- movdqa (BUF), %xmm1 +- movdqa 0x10(BUF), %xmm2 +- movdqa 0x20(BUF), %xmm3 +- movdqa 0x30(BUF), %xmm4 +- movd CRC, CONSTANT +- pxor CONSTANT, %xmm1 +- sub $0x40, LEN +- add $0x40, BUF +- cmp $0x40, LEN +- jb .Lless_64 +- +-#ifdef __x86_64__ +- movdqa .Lconstant_R2R1(%rip), CONSTANT +-#else +- movdqa .Lconstant_R2R1, CONSTANT +-#endif +- +-.Lloop_64:/* 64 bytes Full cache line folding */ +- prefetchnta 0x40(BUF) +- movdqa %xmm1, %xmm5 +- movdqa %xmm2, %xmm6 +- movdqa %xmm3, %xmm7 +-#ifdef __x86_64__ +- movdqa %xmm4, %xmm8 +-#endif +- pclmulqdq $0x00, CONSTANT, %xmm1 +- pclmulqdq $0x00, CONSTANT, %xmm2 +- pclmulqdq $0x00, CONSTANT, %xmm3 +-#ifdef __x86_64__ +- pclmulqdq $0x00, CONSTANT, %xmm4 +-#endif +- pclmulqdq $0x11, CONSTANT, %xmm5 +- pclmulqdq $0x11, CONSTANT, %xmm6 +- pclmulqdq $0x11, CONSTANT, %xmm7 +-#ifdef __x86_64__ +- pclmulqdq $0x11, CONSTANT, %xmm8 +-#endif +- pxor %xmm5, %xmm1 +- pxor %xmm6, %xmm2 +- pxor %xmm7, %xmm3 +-#ifdef __x86_64__ +- pxor %xmm8, %xmm4 +-#else +- /* xmm8 unsupported for x32 */ +- movdqa %xmm4, %xmm5 +- pclmulqdq $0x00, CONSTANT, %xmm4 +- pclmulqdq $0x11, CONSTANT, %xmm5 +- pxor %xmm5, %xmm4 +-#endif +- +- pxor (BUF), %xmm1 +- pxor 0x10(BUF), %xmm2 +- pxor 0x20(BUF), %xmm3 +- pxor 0x30(BUF), %xmm4 +- +- sub $0x40, LEN +- add $0x40, BUF +- cmp $0x40, LEN +- jge .Lloop_64 +-.Lless_64:/* Folding cache line into 128bit */ +-#ifdef __x86_64__ +- movdqa .Lconstant_R4R3(%rip), CONSTANT +-#else +- movdqa .Lconstant_R4R3, CONSTANT +-#endif +- prefetchnta (BUF) +- +- movdqa %xmm1, %xmm5 +- pclmulqdq $0x00, CONSTANT, %xmm1 +- pclmulqdq $0x11, CONSTANT, %xmm5 +- pxor %xmm5, %xmm1 +- pxor %xmm2, %xmm1 +- +- movdqa %xmm1, %xmm5 +- pclmulqdq $0x00, CONSTANT, %xmm1 +- pclmulqdq $0x11, CONSTANT, %xmm5 +- pxor %xmm5, %xmm1 +- pxor %xmm3, %xmm1 +- +- movdqa %xmm1, %xmm5 +- pclmulqdq $0x00, CONSTANT, %xmm1 +- pclmulqdq $0x11, CONSTANT, %xmm5 +- pxor %xmm5, %xmm1 +- pxor %xmm4, %xmm1 +- +- cmp $0x10, LEN +- jb .Lfold_64 +-.Lloop_16:/* Folding rest buffer into 128bit */ +- movdqa %xmm1, %xmm5 +- pclmulqdq $0x00, CONSTANT, %xmm1 +- pclmulqdq $0x11, CONSTANT, %xmm5 +- pxor %xmm5, %xmm1 +- pxor (BUF), %xmm1 +- sub $0x10, LEN +- add $0x10, BUF +- cmp $0x10, LEN +- jge .Lloop_16 +- +-.Lfold_64: +- /* perform the last 64 bit fold, also adds 32 zeroes +- * to the input stream */ +- pclmulqdq $0x01, %xmm1, CONSTANT /* R4 * xmm1.low */ +- psrldq $0x08, %xmm1 +- pxor CONSTANT, %xmm1 +- +- /* final 32-bit fold */ +- movdqa %xmm1, %xmm2 +-#ifdef __x86_64__ +- movdqa .Lconstant_R5(%rip), CONSTANT +- movdqa .Lconstant_mask32(%rip), %xmm3 +-#else +- movdqa .Lconstant_R5, CONSTANT +- movdqa .Lconstant_mask32, %xmm3 +-#endif +- psrldq $0x04, %xmm2 +- pand %xmm3, %xmm1 +- pclmulqdq $0x00, CONSTANT, %xmm1 +- pxor %xmm2, %xmm1 +- +- /* Finish up with the bit-reversed barrett reduction 64 ==> 32 bits */ +-#ifdef __x86_64__ +- movdqa .Lconstant_RUpoly(%rip), CONSTANT +-#else +- movdqa .Lconstant_RUpoly, CONSTANT +-#endif +- movdqa %xmm1, %xmm2 +- pand %xmm3, %xmm1 +- pclmulqdq $0x10, CONSTANT, %xmm1 +- pand %xmm3, %xmm1 +- pclmulqdq $0x00, CONSTANT, %xmm1 +- pxor %xmm2, %xmm1 +- pextrd $0x01, %xmm1, %eax +- +- RET +-SYM_FUNC_END(crc32_pclmul_le_16) ++DEFINE_CRC_PCLMUL_FUNCS(crc32_lsb, /* bits= */ 32, /* lsb= */ 1) +diff --git a/arch/x86/lib/crct10dif-pcl-asm_64.S b/arch/x86/lib/crct10dif-pcl-asm_64.S +deleted file mode 100644 +index 5286db5b8165..000000000000 +--- a/arch/x86/lib/crct10dif-pcl-asm_64.S ++++ /dev/null +@@ -1,332 +0,0 @@ +-######################################################################## +-# Implement fast CRC-T10DIF computation with SSE and PCLMULQDQ instructions +-# +-# Copyright (c) 2013, Intel Corporation +-# +-# Authors: +-# Erdinc Ozturk +-# Vinodh Gopal +-# James Guilford +-# Tim Chen +-# +-# This software is available to you under a choice of one of two +-# licenses. You may choose to be licensed under the terms of the GNU +-# General Public License (GPL) Version 2, available from the file +-# COPYING in the main directory of this source tree, or the +-# OpenIB.org BSD license below: +-# +-# Redistribution and use in source and binary forms, with or without +-# modification, are permitted provided that the following conditions are +-# met: +-# +-# * Redistributions of source code must retain the above copyright +-# notice, this list of conditions and the following disclaimer. +-# +-# * Redistributions in binary form must reproduce the above copyright +-# notice, this list of conditions and the following disclaimer in the +-# documentation and/or other materials provided with the +-# distribution. +-# +-# * Neither the name of the Intel Corporation nor the names of its +-# contributors may be used to endorse or promote products derived from +-# this software without specific prior written permission. +-# +-# +-# THIS SOFTWARE IS PROVIDED BY INTEL CORPORATION ""AS IS"" AND ANY +-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL CORPORATION OR +-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +-# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-# +-# Reference paper titled "Fast CRC Computation for Generic +-# Polynomials Using PCLMULQDQ Instruction" +-# URL: http://www.intel.com/content/dam/www/public/us/en/documents +-# /white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf +-# +- +-#include +- +-.text +- +-#define init_crc %edi +-#define buf %rsi +-#define len %rdx +- +-#define FOLD_CONSTS %xmm10 +-#define BSWAP_MASK %xmm11 +- +-# Fold reg1, reg2 into the next 32 data bytes, storing the result back into +-# reg1, reg2. +-.macro fold_32_bytes offset, reg1, reg2 +- movdqu \offset(buf), %xmm9 +- movdqu \offset+16(buf), %xmm12 +- pshufb BSWAP_MASK, %xmm9 +- pshufb BSWAP_MASK, %xmm12 +- movdqa \reg1, %xmm8 +- movdqa \reg2, %xmm13 +- pclmulqdq $0x00, FOLD_CONSTS, \reg1 +- pclmulqdq $0x11, FOLD_CONSTS, %xmm8 +- pclmulqdq $0x00, FOLD_CONSTS, \reg2 +- pclmulqdq $0x11, FOLD_CONSTS, %xmm13 +- pxor %xmm9 , \reg1 +- xorps %xmm8 , \reg1 +- pxor %xmm12, \reg2 +- xorps %xmm13, \reg2 +-.endm +- +-# Fold src_reg into dst_reg. +-.macro fold_16_bytes src_reg, dst_reg +- movdqa \src_reg, %xmm8 +- pclmulqdq $0x11, FOLD_CONSTS, \src_reg +- pclmulqdq $0x00, FOLD_CONSTS, %xmm8 +- pxor %xmm8, \dst_reg +- xorps \src_reg, \dst_reg +-.endm +- +-# +-# u16 crc_t10dif_pcl(u16 init_crc, const *u8 buf, size_t len); +-# +-# Assumes len >= 16. +-# +-SYM_FUNC_START(crc_t10dif_pcl) +- +- movdqa .Lbswap_mask(%rip), BSWAP_MASK +- +- # For sizes less than 256 bytes, we can't fold 128 bytes at a time. +- cmp $256, len +- jl .Lless_than_256_bytes +- +- # Load the first 128 data bytes. Byte swapping is necessary to make the +- # bit order match the polynomial coefficient order. +- movdqu 16*0(buf), %xmm0 +- movdqu 16*1(buf), %xmm1 +- movdqu 16*2(buf), %xmm2 +- movdqu 16*3(buf), %xmm3 +- movdqu 16*4(buf), %xmm4 +- movdqu 16*5(buf), %xmm5 +- movdqu 16*6(buf), %xmm6 +- movdqu 16*7(buf), %xmm7 +- add $128, buf +- pshufb BSWAP_MASK, %xmm0 +- pshufb BSWAP_MASK, %xmm1 +- pshufb BSWAP_MASK, %xmm2 +- pshufb BSWAP_MASK, %xmm3 +- pshufb BSWAP_MASK, %xmm4 +- pshufb BSWAP_MASK, %xmm5 +- pshufb BSWAP_MASK, %xmm6 +- pshufb BSWAP_MASK, %xmm7 +- +- # XOR the first 16 data *bits* with the initial CRC value. +- pxor %xmm8, %xmm8 +- pinsrw $7, init_crc, %xmm8 +- pxor %xmm8, %xmm0 +- +- movdqa .Lfold_across_128_bytes_consts(%rip), FOLD_CONSTS +- +- # Subtract 128 for the 128 data bytes just consumed. Subtract another +- # 128 to simplify the termination condition of the following loop. +- sub $256, len +- +- # While >= 128 data bytes remain (not counting xmm0-7), fold the 128 +- # bytes xmm0-7 into them, storing the result back into xmm0-7. +-.Lfold_128_bytes_loop: +- fold_32_bytes 0, %xmm0, %xmm1 +- fold_32_bytes 32, %xmm2, %xmm3 +- fold_32_bytes 64, %xmm4, %xmm5 +- fold_32_bytes 96, %xmm6, %xmm7 +- add $128, buf +- sub $128, len +- jge .Lfold_128_bytes_loop +- +- # Now fold the 112 bytes in xmm0-xmm6 into the 16 bytes in xmm7. +- +- # Fold across 64 bytes. +- movdqa .Lfold_across_64_bytes_consts(%rip), FOLD_CONSTS +- fold_16_bytes %xmm0, %xmm4 +- fold_16_bytes %xmm1, %xmm5 +- fold_16_bytes %xmm2, %xmm6 +- fold_16_bytes %xmm3, %xmm7 +- # Fold across 32 bytes. +- movdqa .Lfold_across_32_bytes_consts(%rip), FOLD_CONSTS +- fold_16_bytes %xmm4, %xmm6 +- fold_16_bytes %xmm5, %xmm7 +- # Fold across 16 bytes. +- movdqa .Lfold_across_16_bytes_consts(%rip), FOLD_CONSTS +- fold_16_bytes %xmm6, %xmm7 +- +- # Add 128 to get the correct number of data bytes remaining in 0...127 +- # (not counting xmm7), following the previous extra subtraction by 128. +- # Then subtract 16 to simplify the termination condition of the +- # following loop. +- add $128-16, len +- +- # While >= 16 data bytes remain (not counting xmm7), fold the 16 bytes +- # xmm7 into them, storing the result back into xmm7. +- jl .Lfold_16_bytes_loop_done +-.Lfold_16_bytes_loop: +- movdqa %xmm7, %xmm8 +- pclmulqdq $0x11, FOLD_CONSTS, %xmm7 +- pclmulqdq $0x00, FOLD_CONSTS, %xmm8 +- pxor %xmm8, %xmm7 +- movdqu (buf), %xmm0 +- pshufb BSWAP_MASK, %xmm0 +- pxor %xmm0 , %xmm7 +- add $16, buf +- sub $16, len +- jge .Lfold_16_bytes_loop +- +-.Lfold_16_bytes_loop_done: +- # Add 16 to get the correct number of data bytes remaining in 0...15 +- # (not counting xmm7), following the previous extra subtraction by 16. +- add $16, len +- je .Lreduce_final_16_bytes +- +-.Lhandle_partial_segment: +- # Reduce the last '16 + len' bytes where 1 <= len <= 15 and the first 16 +- # bytes are in xmm7 and the rest are the remaining data in 'buf'. To do +- # this without needing a fold constant for each possible 'len', redivide +- # the bytes into a first chunk of 'len' bytes and a second chunk of 16 +- # bytes, then fold the first chunk into the second. +- +- movdqa %xmm7, %xmm2 +- +- # xmm1 = last 16 original data bytes +- movdqu -16(buf, len), %xmm1 +- pshufb BSWAP_MASK, %xmm1 +- +- # xmm2 = high order part of second chunk: xmm7 left-shifted by 'len' bytes. +- lea .Lbyteshift_table+16(%rip), %rax +- sub len, %rax +- movdqu (%rax), %xmm0 +- pshufb %xmm0, %xmm2 +- +- # xmm7 = first chunk: xmm7 right-shifted by '16-len' bytes. +- pxor .Lmask1(%rip), %xmm0 +- pshufb %xmm0, %xmm7 +- +- # xmm1 = second chunk: 'len' bytes from xmm1 (low-order bytes), +- # then '16-len' bytes from xmm2 (high-order bytes). +- pblendvb %xmm2, %xmm1 #xmm0 is implicit +- +- # Fold the first chunk into the second chunk, storing the result in xmm7. +- movdqa %xmm7, %xmm8 +- pclmulqdq $0x11, FOLD_CONSTS, %xmm7 +- pclmulqdq $0x00, FOLD_CONSTS, %xmm8 +- pxor %xmm8, %xmm7 +- pxor %xmm1, %xmm7 +- +-.Lreduce_final_16_bytes: +- # Reduce the 128-bit value M(x), stored in xmm7, to the final 16-bit CRC +- +- # Load 'x^48 * (x^48 mod G(x))' and 'x^48 * (x^80 mod G(x))'. +- movdqa .Lfinal_fold_consts(%rip), FOLD_CONSTS +- +- # Fold the high 64 bits into the low 64 bits, while also multiplying by +- # x^64. This produces a 128-bit value congruent to x^64 * M(x) and +- # whose low 48 bits are 0. +- movdqa %xmm7, %xmm0 +- pclmulqdq $0x11, FOLD_CONSTS, %xmm7 # high bits * x^48 * (x^80 mod G(x)) +- pslldq $8, %xmm0 +- pxor %xmm0, %xmm7 # + low bits * x^64 +- +- # Fold the high 32 bits into the low 96 bits. This produces a 96-bit +- # value congruent to x^64 * M(x) and whose low 48 bits are 0. +- movdqa %xmm7, %xmm0 +- pand .Lmask2(%rip), %xmm0 # zero high 32 bits +- psrldq $12, %xmm7 # extract high 32 bits +- pclmulqdq $0x00, FOLD_CONSTS, %xmm7 # high 32 bits * x^48 * (x^48 mod G(x)) +- pxor %xmm0, %xmm7 # + low bits +- +- # Load G(x) and floor(x^48 / G(x)). +- movdqa .Lbarrett_reduction_consts(%rip), FOLD_CONSTS +- +- # Use Barrett reduction to compute the final CRC value. +- movdqa %xmm7, %xmm0 +- pclmulqdq $0x11, FOLD_CONSTS, %xmm7 # high 32 bits * floor(x^48 / G(x)) +- psrlq $32, %xmm7 # /= x^32 +- pclmulqdq $0x00, FOLD_CONSTS, %xmm7 # *= G(x) +- psrlq $48, %xmm0 +- pxor %xmm7, %xmm0 # + low 16 nonzero bits +- # Final CRC value (x^16 * M(x)) mod G(x) is in low 16 bits of xmm0. +- +- pextrw $0, %xmm0, %eax +- RET +- +-.align 16 +-.Lless_than_256_bytes: +- # Checksumming a buffer of length 16...255 bytes +- +- # Load the first 16 data bytes. +- movdqu (buf), %xmm7 +- pshufb BSWAP_MASK, %xmm7 +- add $16, buf +- +- # XOR the first 16 data *bits* with the initial CRC value. +- pxor %xmm0, %xmm0 +- pinsrw $7, init_crc, %xmm0 +- pxor %xmm0, %xmm7 +- +- movdqa .Lfold_across_16_bytes_consts(%rip), FOLD_CONSTS +- cmp $16, len +- je .Lreduce_final_16_bytes # len == 16 +- sub $32, len +- jge .Lfold_16_bytes_loop # 32 <= len <= 255 +- add $16, len +- jmp .Lhandle_partial_segment # 17 <= len <= 31 +-SYM_FUNC_END(crc_t10dif_pcl) +- +-.section .rodata, "a", @progbits +-.align 16 +- +-# Fold constants precomputed from the polynomial 0x18bb7 +-# G(x) = x^16 + x^15 + x^11 + x^9 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + x^0 +-.Lfold_across_128_bytes_consts: +- .quad 0x0000000000006123 # x^(8*128) mod G(x) +- .quad 0x0000000000002295 # x^(8*128+64) mod G(x) +-.Lfold_across_64_bytes_consts: +- .quad 0x0000000000001069 # x^(4*128) mod G(x) +- .quad 0x000000000000dd31 # x^(4*128+64) mod G(x) +-.Lfold_across_32_bytes_consts: +- .quad 0x000000000000857d # x^(2*128) mod G(x) +- .quad 0x0000000000007acc # x^(2*128+64) mod G(x) +-.Lfold_across_16_bytes_consts: +- .quad 0x000000000000a010 # x^(1*128) mod G(x) +- .quad 0x0000000000001faa # x^(1*128+64) mod G(x) +-.Lfinal_fold_consts: +- .quad 0x1368000000000000 # x^48 * (x^48 mod G(x)) +- .quad 0x2d56000000000000 # x^48 * (x^80 mod G(x)) +-.Lbarrett_reduction_consts: +- .quad 0x0000000000018bb7 # G(x) +- .quad 0x00000001f65a57f8 # floor(x^48 / G(x)) +- +-.section .rodata.cst16.mask1, "aM", @progbits, 16 +-.align 16 +-.Lmask1: +- .octa 0x80808080808080808080808080808080 +- +-.section .rodata.cst16.mask2, "aM", @progbits, 16 +-.align 16 +-.Lmask2: +- .octa 0x00000000FFFFFFFFFFFFFFFFFFFFFFFF +- +-.section .rodata.cst16.bswap_mask, "aM", @progbits, 16 +-.align 16 +-.Lbswap_mask: +- .octa 0x000102030405060708090A0B0C0D0E0F +- +-.section .rodata.cst32.byteshift_table, "aM", @progbits, 32 +-.align 16 +-# For 1 <= len <= 15, the 16-byte vector beginning at &byteshift_table[16 - len] +-# is the index vector to shift left by 'len' bytes, and is also {0x80, ..., +-# 0x80} XOR the index vector to shift right by '16 - len' bytes. +-.Lbyteshift_table: +- .byte 0x0, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87 +- .byte 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f +- .byte 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 +- .byte 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe , 0x0 +diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig +index 486afe598184..0e1a554c596d 100644 +--- a/drivers/nvme/host/Kconfig ++++ b/drivers/nvme/host/Kconfig +@@ -80,8 +80,7 @@ config NVME_TCP + depends on INET + depends on BLOCK + select NVME_FABRICS +- select CRYPTO +- select CRYPTO_CRC32C ++ select CRC32 + help + This provides support for the NVMe over Fabrics protocol using + the TCP transport. This allows you to use remote block devices +diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c +index 841238f38fdd..5668df68810f 100644 +--- a/drivers/nvme/host/tcp.c ++++ b/drivers/nvme/host/tcp.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -17,7 +18,6 @@ + #include + #include + #include +-#include + #include + #include + +@@ -169,8 +169,8 @@ struct nvme_tcp_queue { + bool hdr_digest; + bool data_digest; + bool tls_enabled; +- struct ahash_request *rcv_hash; +- struct ahash_request *snd_hash; ++ u32 rcv_crc; ++ u32 snd_crc; + __le32 exp_ddgst; + __le32 recv_ddgst; + struct completion tls_complete; +@@ -444,32 +444,29 @@ nvme_tcp_fetch_request(struct nvme_tcp_queue *queue) + return req; + } + +-static inline void nvme_tcp_ddgst_final(struct ahash_request *hash, +- __le32 *dgst) ++static inline void nvme_tcp_ddgst_init(u32 *crcp) + { +- ahash_request_set_crypt(hash, NULL, (u8 *)dgst, 0); +- crypto_ahash_final(hash); ++ *crcp = ~0; + } + +-static inline void nvme_tcp_ddgst_update(struct ahash_request *hash, ++static inline void nvme_tcp_ddgst_update(u32 *crcp, + struct page *page, off_t off, size_t len) + { +- struct scatterlist sg; ++ const void *virt = kmap_local_page(page + (off >> PAGE_SHIFT)); + +- sg_init_table(&sg, 1); +- sg_set_page(&sg, page, len, off); +- ahash_request_set_crypt(hash, &sg, NULL, len); +- crypto_ahash_update(hash); ++ *crcp = crc32c(*crcp, virt + (off & ~PAGE_MASK), len); ++ ++ kunmap_local(virt); + } + +-static inline void nvme_tcp_hdgst(struct ahash_request *hash, +- void *pdu, size_t len) ++static inline void nvme_tcp_ddgst_final(u32 *crcp, __le32 *dgst) + { +- struct scatterlist sg; ++ *dgst = cpu_to_le32(~*crcp); ++} + +- sg_init_one(&sg, pdu, len); +- ahash_request_set_crypt(hash, &sg, pdu + len, len); +- crypto_ahash_digest(hash); ++static inline void nvme_tcp_hdgst(void *pdu, size_t len) ++{ ++ put_unaligned_le32(~crc32c(~0, pdu, len), pdu + len); + } + + static int nvme_tcp_verify_hdgst(struct nvme_tcp_queue *queue, +@@ -487,7 +484,7 @@ static int nvme_tcp_verify_hdgst(struct nvme_tcp_queue *queue, + } + + recv_digest = *(__le32 *)(pdu + hdr->hlen); +- nvme_tcp_hdgst(queue->rcv_hash, pdu, pdu_len); ++ nvme_tcp_hdgst(pdu, pdu_len); + exp_digest = *(__le32 *)(pdu + hdr->hlen); + if (recv_digest != exp_digest) { + dev_err(queue->ctrl->ctrl.device, +@@ -514,7 +511,7 @@ static int nvme_tcp_check_ddgst(struct nvme_tcp_queue *queue, void *pdu) + nvme_tcp_queue_id(queue)); + return -EPROTO; + } +- crypto_ahash_init(queue->rcv_hash); ++ nvme_tcp_ddgst_init(&queue->rcv_crc); + + return 0; + } +@@ -821,6 +818,17 @@ static inline void nvme_tcp_end_request(struct request *rq, u16 status) + nvme_complete_rq(rq); + } + ++static size_t crc_and_copy_to_iter(const void *addr, size_t bytes, void *crcp_, ++ struct iov_iter *i) ++{ ++ u32 *crcp = crcp_; ++ size_t copied; ++ ++ copied = copy_to_iter(addr, bytes, i); ++ *crcp = crc32c(*crcp, addr, copied); ++ return copied; ++} ++ + static int nvme_tcp_recv_data(struct nvme_tcp_queue *queue, struct sk_buff *skb, + unsigned int *offset, size_t *len) + { +@@ -858,8 +866,10 @@ static int nvme_tcp_recv_data(struct nvme_tcp_queue *queue, struct sk_buff *skb, + iov_iter_count(&req->iter)); + + if (queue->data_digest) +- ret = skb_copy_and_hash_datagram_iter(skb, *offset, +- &req->iter, recv_len, queue->rcv_hash); ++ ret = __skb_datagram_iter(skb, *offset, &req->iter, ++ recv_len, true, ++ crc_and_copy_to_iter, ++ &queue->rcv_crc); + else + ret = skb_copy_datagram_iter(skb, *offset, + &req->iter, recv_len); +@@ -877,7 +887,8 @@ static int nvme_tcp_recv_data(struct nvme_tcp_queue *queue, struct sk_buff *skb, + + if (!queue->data_remaining) { + if (queue->data_digest) { +- nvme_tcp_ddgst_final(queue->rcv_hash, &queue->exp_ddgst); ++ nvme_tcp_ddgst_final(&queue->rcv_crc, ++ &queue->exp_ddgst); + queue->ddgst_remaining = NVME_TCP_DIGEST_LENGTH; + } else { + if (pdu->hdr.flags & NVME_TCP_F_DATA_SUCCESS) { +@@ -1079,7 +1090,7 @@ static int nvme_tcp_try_send_data(struct nvme_tcp_request *req) + return ret; + + if (queue->data_digest) +- nvme_tcp_ddgst_update(queue->snd_hash, page, ++ nvme_tcp_ddgst_update(&queue->snd_crc, page, + offset, ret); + + /* +@@ -1093,7 +1104,7 @@ static int nvme_tcp_try_send_data(struct nvme_tcp_request *req) + /* fully successful last send in current PDU */ + if (last && ret == len) { + if (queue->data_digest) { +- nvme_tcp_ddgst_final(queue->snd_hash, ++ nvme_tcp_ddgst_final(&queue->snd_crc, + &req->ddgst); + req->state = NVME_TCP_SEND_DDGST; + req->offset = 0; +@@ -1126,7 +1137,7 @@ static int nvme_tcp_try_send_cmd_pdu(struct nvme_tcp_request *req) + msg.msg_flags |= MSG_EOR; + + if (queue->hdr_digest && !req->offset) +- nvme_tcp_hdgst(queue->snd_hash, pdu, sizeof(*pdu)); ++ nvme_tcp_hdgst(pdu, sizeof(*pdu)); + + bvec_set_virt(&bvec, (void *)pdu + req->offset, len); + iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, &bvec, 1, len); +@@ -1139,7 +1150,7 @@ static int nvme_tcp_try_send_cmd_pdu(struct nvme_tcp_request *req) + if (inline_data) { + req->state = NVME_TCP_SEND_DATA; + if (queue->data_digest) +- crypto_ahash_init(queue->snd_hash); ++ nvme_tcp_ddgst_init(&queue->snd_crc); + } else { + nvme_tcp_done_send_req(queue); + } +@@ -1161,7 +1172,7 @@ static int nvme_tcp_try_send_data_pdu(struct nvme_tcp_request *req) + int ret; + + if (queue->hdr_digest && !req->offset) +- nvme_tcp_hdgst(queue->snd_hash, pdu, sizeof(*pdu)); ++ nvme_tcp_hdgst(pdu, sizeof(*pdu)); + + if (!req->h2cdata_left) + msg.msg_flags |= MSG_SPLICE_PAGES; +@@ -1176,7 +1187,7 @@ static int nvme_tcp_try_send_data_pdu(struct nvme_tcp_request *req) + if (!len) { + req->state = NVME_TCP_SEND_DATA; + if (queue->data_digest) +- crypto_ahash_init(queue->snd_hash); ++ nvme_tcp_ddgst_init(&queue->snd_crc); + return 1; + } + req->offset += ret; +@@ -1316,41 +1327,6 @@ static void nvme_tcp_io_work(struct work_struct *w) + queue_work_on(queue->io_cpu, nvme_tcp_wq, &queue->io_work); + } + +-static void nvme_tcp_free_crypto(struct nvme_tcp_queue *queue) +-{ +- struct crypto_ahash *tfm = crypto_ahash_reqtfm(queue->rcv_hash); +- +- ahash_request_free(queue->rcv_hash); +- ahash_request_free(queue->snd_hash); +- crypto_free_ahash(tfm); +-} +- +-static int nvme_tcp_alloc_crypto(struct nvme_tcp_queue *queue) +-{ +- struct crypto_ahash *tfm; +- +- tfm = crypto_alloc_ahash("crc32c", 0, CRYPTO_ALG_ASYNC); +- if (IS_ERR(tfm)) +- return PTR_ERR(tfm); +- +- queue->snd_hash = ahash_request_alloc(tfm, GFP_KERNEL); +- if (!queue->snd_hash) +- goto free_tfm; +- ahash_request_set_callback(queue->snd_hash, 0, NULL, NULL); +- +- queue->rcv_hash = ahash_request_alloc(tfm, GFP_KERNEL); +- if (!queue->rcv_hash) +- goto free_snd_hash; +- ahash_request_set_callback(queue->rcv_hash, 0, NULL, NULL); +- +- return 0; +-free_snd_hash: +- ahash_request_free(queue->snd_hash); +-free_tfm: +- crypto_free_ahash(tfm); +- return -ENOMEM; +-} +- + static void nvme_tcp_free_async_req(struct nvme_tcp_ctrl *ctrl) + { + struct nvme_tcp_request *async = &ctrl->async_req; +@@ -1383,9 +1359,6 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) + if (!test_and_clear_bit(NVME_TCP_Q_ALLOCATED, &queue->flags)) + return; + +- if (queue->hdr_digest || queue->data_digest) +- nvme_tcp_free_crypto(queue); +- + page_frag_cache_drain(&queue->pf_cache); + + noreclaim_flag = memalloc_noreclaim_save(); +@@ -1793,21 +1766,13 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, + + queue->hdr_digest = nctrl->opts->hdr_digest; + queue->data_digest = nctrl->opts->data_digest; +- if (queue->hdr_digest || queue->data_digest) { +- ret = nvme_tcp_alloc_crypto(queue); +- if (ret) { +- dev_err(nctrl->device, +- "failed to allocate queue %d crypto\n", qid); +- goto err_sock; +- } +- } + + rcv_pdu_size = sizeof(struct nvme_tcp_rsp_pdu) + + nvme_tcp_hdgst_len(queue); + queue->pdu = kmalloc(rcv_pdu_size, GFP_KERNEL); + if (!queue->pdu) { + ret = -ENOMEM; +- goto err_crypto; ++ goto err_sock; + } + + dev_dbg(nctrl->device, "connecting queue %d\n", +@@ -1840,9 +1805,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, + kernel_sock_shutdown(queue->sock, SHUT_RDWR); + err_rcv_pdu: + kfree(queue->pdu); +-err_crypto: +- if (queue->hdr_digest || queue->data_digest) +- nvme_tcp_free_crypto(queue); + err_sock: + /* ->sock will be released by fput() */ + fput(queue->sock->file); +diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c +index 7c51c2a8c109..abe50533fd80 100644 +--- a/drivers/nvme/target/tcp.c ++++ b/drivers/nvme/target/tcp.c +@@ -7,6 +7,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -18,7 +19,6 @@ + #include + #include + #include +-#include + #include + + #include "nvmet.h" +@@ -173,8 +173,8 @@ struct nvmet_tcp_queue { + /* digest state */ + bool hdr_digest; + bool data_digest; +- struct ahash_request *snd_hash; +- struct ahash_request *rcv_hash; ++ u32 snd_crc; ++ u32 rcv_crc; + + /* TLS state */ + key_serial_t tls_pskid; +@@ -295,14 +295,9 @@ static inline u8 nvmet_tcp_ddgst_len(struct nvmet_tcp_queue *queue) + return queue->data_digest ? NVME_TCP_DIGEST_LENGTH : 0; + } + +-static inline void nvmet_tcp_hdgst(struct ahash_request *hash, +- void *pdu, size_t len) ++static inline void nvmet_tcp_hdgst(void *pdu, size_t len) + { +- struct scatterlist sg; +- +- sg_init_one(&sg, pdu, len); +- ahash_request_set_crypt(hash, &sg, pdu + len, len); +- crypto_ahash_digest(hash); ++ put_unaligned_le32(~crc32c(~0, pdu, len), pdu + len); + } + + static int nvmet_tcp_verify_hdgst(struct nvmet_tcp_queue *queue, +@@ -319,7 +314,7 @@ static int nvmet_tcp_verify_hdgst(struct nvmet_tcp_queue *queue, + } + + recv_digest = *(__le32 *)(pdu + hdr->hlen); +- nvmet_tcp_hdgst(queue->rcv_hash, pdu, len); ++ nvmet_tcp_hdgst(pdu, len); + exp_digest = *(__le32 *)(pdu + hdr->hlen); + if (recv_digest != exp_digest) { + pr_err("queue %d: header digest error: recv %#x expected %#x\n", +@@ -442,12 +437,20 @@ static int nvmet_tcp_map_data(struct nvmet_tcp_cmd *cmd) + return NVME_SC_INTERNAL; + } + +-static void nvmet_tcp_calc_ddgst(struct ahash_request *hash, +- struct nvmet_tcp_cmd *cmd) ++static void nvmet_tcp_calc_ddgst(struct nvmet_tcp_cmd *cmd) + { +- ahash_request_set_crypt(hash, cmd->req.sg, +- (void *)&cmd->exp_ddgst, cmd->req.transfer_len); +- crypto_ahash_digest(hash); ++ size_t total_len = cmd->req.transfer_len; ++ struct scatterlist *sg = cmd->req.sg; ++ u32 crc = ~0; ++ ++ while (total_len) { ++ size_t len = min_t(size_t, total_len, sg->length); ++ ++ crc = crc32c(crc, sg_virt(sg), len); ++ total_len -= len; ++ sg = sg_next(sg); ++ } ++ cmd->exp_ddgst = cpu_to_le32(~crc); + } + + static void nvmet_setup_c2h_data_pdu(struct nvmet_tcp_cmd *cmd) +@@ -474,19 +477,18 @@ static void nvmet_setup_c2h_data_pdu(struct nvmet_tcp_cmd *cmd) + + if (queue->data_digest) { + pdu->hdr.flags |= NVME_TCP_F_DDGST; +- nvmet_tcp_calc_ddgst(queue->snd_hash, cmd); ++ nvmet_tcp_calc_ddgst(cmd); + } + + if (cmd->queue->hdr_digest) { + pdu->hdr.flags |= NVME_TCP_F_HDGST; +- nvmet_tcp_hdgst(queue->snd_hash, pdu, sizeof(*pdu)); ++ nvmet_tcp_hdgst(pdu, sizeof(*pdu)); + } + } + + static void nvmet_setup_r2t_pdu(struct nvmet_tcp_cmd *cmd) + { + struct nvme_tcp_r2t_pdu *pdu = cmd->r2t_pdu; +- struct nvmet_tcp_queue *queue = cmd->queue; + u8 hdgst = nvmet_tcp_hdgst_len(cmd->queue); + + cmd->offset = 0; +@@ -504,14 +506,13 @@ static void nvmet_setup_r2t_pdu(struct nvmet_tcp_cmd *cmd) + pdu->r2t_offset = cpu_to_le32(cmd->rbytes_done); + if (cmd->queue->hdr_digest) { + pdu->hdr.flags |= NVME_TCP_F_HDGST; +- nvmet_tcp_hdgst(queue->snd_hash, pdu, sizeof(*pdu)); ++ nvmet_tcp_hdgst(pdu, sizeof(*pdu)); + } + } + + static void nvmet_setup_response_pdu(struct nvmet_tcp_cmd *cmd) + { + struct nvme_tcp_rsp_pdu *pdu = cmd->rsp_pdu; +- struct nvmet_tcp_queue *queue = cmd->queue; + u8 hdgst = nvmet_tcp_hdgst_len(cmd->queue); + + cmd->offset = 0; +@@ -524,7 +525,7 @@ static void nvmet_setup_response_pdu(struct nvmet_tcp_cmd *cmd) + pdu->hdr.plen = cpu_to_le32(pdu->hdr.hlen + hdgst); + if (cmd->queue->hdr_digest) { + pdu->hdr.flags |= NVME_TCP_F_HDGST; +- nvmet_tcp_hdgst(queue->snd_hash, pdu, sizeof(*pdu)); ++ nvmet_tcp_hdgst(pdu, sizeof(*pdu)); + } + } + +@@ -851,42 +852,6 @@ static void nvmet_prepare_receive_pdu(struct nvmet_tcp_queue *queue) + queue->rcv_state = NVMET_TCP_RECV_PDU; + } + +-static void nvmet_tcp_free_crypto(struct nvmet_tcp_queue *queue) +-{ +- struct crypto_ahash *tfm = crypto_ahash_reqtfm(queue->rcv_hash); +- +- ahash_request_free(queue->rcv_hash); +- ahash_request_free(queue->snd_hash); +- crypto_free_ahash(tfm); +-} +- +-static int nvmet_tcp_alloc_crypto(struct nvmet_tcp_queue *queue) +-{ +- struct crypto_ahash *tfm; +- +- tfm = crypto_alloc_ahash("crc32c", 0, CRYPTO_ALG_ASYNC); +- if (IS_ERR(tfm)) +- return PTR_ERR(tfm); +- +- queue->snd_hash = ahash_request_alloc(tfm, GFP_KERNEL); +- if (!queue->snd_hash) +- goto free_tfm; +- ahash_request_set_callback(queue->snd_hash, 0, NULL, NULL); +- +- queue->rcv_hash = ahash_request_alloc(tfm, GFP_KERNEL); +- if (!queue->rcv_hash) +- goto free_snd_hash; +- ahash_request_set_callback(queue->rcv_hash, 0, NULL, NULL); +- +- return 0; +-free_snd_hash: +- ahash_request_free(queue->snd_hash); +-free_tfm: +- crypto_free_ahash(tfm); +- return -ENOMEM; +-} +- +- + static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue) + { + struct nvme_tcp_icreq_pdu *icreq = &queue->pdu.icreq; +@@ -915,11 +880,6 @@ static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue) + + queue->hdr_digest = !!(icreq->digest & NVME_TCP_HDR_DIGEST_ENABLE); + queue->data_digest = !!(icreq->digest & NVME_TCP_DATA_DIGEST_ENABLE); +- if (queue->hdr_digest || queue->data_digest) { +- ret = nvmet_tcp_alloc_crypto(queue); +- if (ret) +- return ret; +- } + + memset(icresp, 0, sizeof(*icresp)); + icresp->hdr.type = nvme_tcp_icresp; +@@ -1240,7 +1200,7 @@ static void nvmet_tcp_prep_recv_ddgst(struct nvmet_tcp_cmd *cmd) + { + struct nvmet_tcp_queue *queue = cmd->queue; + +- nvmet_tcp_calc_ddgst(queue->rcv_hash, cmd); ++ nvmet_tcp_calc_ddgst(cmd); + queue->offset = 0; + queue->left = NVME_TCP_DIGEST_LENGTH; + queue->rcv_state = NVMET_TCP_RECV_DDGST; +@@ -1609,8 +1569,6 @@ static void nvmet_tcp_release_queue_work(struct work_struct *w) + /* ->sock will be released by fput() */ + fput(queue->sock->file); + nvmet_tcp_free_cmds(queue); +- if (queue->hdr_digest || queue->data_digest) +- nvmet_tcp_free_crypto(queue); + ida_free(&nvmet_tcp_queue_ida, queue->idx); + page_frag_cache_drain(&queue->pf_cache); + kfree(queue); +diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h +index bb2b751d274a..98804d51986c 100644 +--- a/include/linux/skbuff.h ++++ b/include/linux/skbuff.h +@@ -4145,9 +4145,10 @@ static inline int skb_copy_datagram_msg(const struct sk_buff *from, int offset, + } + int skb_copy_and_csum_datagram_msg(struct sk_buff *skb, int hlen, + struct msghdr *msg); +-int skb_copy_and_hash_datagram_iter(const struct sk_buff *skb, int offset, +- struct iov_iter *to, int len, +- struct ahash_request *hash); ++int __skb_datagram_iter(const struct sk_buff *skb, int offset, ++ struct iov_iter *to, int len, bool fault_short, ++ size_t (*cb)(const void *, size_t, void *, ++ struct iov_iter *), void *data); + int skb_copy_datagram_from_iter(struct sk_buff *skb, int offset, + struct iov_iter *from, int len); + int zerocopy_sg_from_iter(struct sk_buff *skb, struct iov_iter *frm); +diff --git a/net/core/datagram.c b/net/core/datagram.c +index f0693707aece..19304c7ce7a3 100644 +--- a/net/core/datagram.c ++++ b/net/core/datagram.c +@@ -61,7 +61,6 @@ + #include + #include + #include +-#include + + /* + * Is a socket 'connection oriented' ? +@@ -385,10 +384,10 @@ INDIRECT_CALLABLE_DECLARE(static size_t simple_copy_to_iter(const void *addr, + void *data __always_unused, + struct iov_iter *i)); + +-static int __skb_datagram_iter(const struct sk_buff *skb, int offset, +- struct iov_iter *to, int len, bool fault_short, +- size_t (*cb)(const void *, size_t, void *, +- struct iov_iter *), void *data) ++int __skb_datagram_iter(const struct sk_buff *skb, int offset, ++ struct iov_iter *to, int len, bool fault_short, ++ size_t (*cb)(const void *, size_t, void *, ++ struct iov_iter *), void *data) + { + int start = skb_headlen(skb); + int i, copy = start - offset, start_off = offset, n; +@@ -481,42 +480,7 @@ static int __skb_datagram_iter(const struct sk_buff *skb, int offset, + + return 0; + } +- +-static size_t hash_and_copy_to_iter(const void *addr, size_t bytes, void *hashp, +- struct iov_iter *i) +-{ +-#ifdef CONFIG_CRYPTO_HASH +- struct ahash_request *hash = hashp; +- struct scatterlist sg; +- size_t copied; +- +- copied = copy_to_iter(addr, bytes, i); +- sg_init_one(&sg, addr, copied); +- ahash_request_set_crypt(hash, &sg, NULL, copied); +- crypto_ahash_update(hash); +- return copied; +-#else +- return 0; +-#endif +-} +- +-/** +- * skb_copy_and_hash_datagram_iter - Copy datagram to an iovec iterator +- * and update a hash. +- * @skb: buffer to copy +- * @offset: offset in the buffer to start copying from +- * @to: iovec iterator to copy to +- * @len: amount of data to copy from buffer to iovec +- * @hash: hash request to update +- */ +-int skb_copy_and_hash_datagram_iter(const struct sk_buff *skb, int offset, +- struct iov_iter *to, int len, +- struct ahash_request *hash) +-{ +- return __skb_datagram_iter(skb, offset, to, len, true, +- hash_and_copy_to_iter, hash); +-} +-EXPORT_SYMBOL(skb_copy_and_hash_datagram_iter); ++EXPORT_SYMBOL_GPL(__skb_datagram_iter); + + static size_t simple_copy_to_iter(const void *addr, size_t bytes, + void *data __always_unused, struct iov_iter *i) +diff --git a/scripts/gen-crc-consts.py b/scripts/gen-crc-consts.py +new file mode 100755 +index 000000000000..608714ba451e +--- /dev/null ++++ b/scripts/gen-crc-consts.py +@@ -0,0 +1,239 @@ ++#!/usr/bin/env python3 ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Script that generates constants for computing the given CRC variant(s). ++# ++# Copyright 2025 Google LLC ++# ++# Author: Eric Biggers ++ ++import sys ++ ++# XOR (add) an iterable of polynomials. ++def xor(iterable): ++ res = 0 ++ for val in iterable: ++ res ^= val ++ return res ++ ++# Multiply two polynomials. ++def clmul(a, b): ++ return xor(a << i for i in range(b.bit_length()) if (b & (1 << i)) != 0) ++ ++# Polynomial division floor(a / b). ++def div(a, b): ++ q = 0 ++ while a.bit_length() >= b.bit_length(): ++ q ^= 1 << (a.bit_length() - b.bit_length()) ++ a ^= b << (a.bit_length() - b.bit_length()) ++ return q ++ ++# Reduce the polynomial 'a' modulo the polynomial 'b'. ++def reduce(a, b): ++ return a ^ clmul(div(a, b), b) ++ ++# Pretty-print a polynomial. ++def pprint_poly(prefix, poly): ++ terms = [f'x^{i}' for i in reversed(range(poly.bit_length())) ++ if (poly & (1 << i)) != 0] ++ j = 0 ++ while j < len(terms): ++ s = prefix + terms[j] + (' +' if j < len(terms) - 1 else '') ++ j += 1 ++ while j < len(terms) and len(s) < 73: ++ s += ' ' + terms[j] + (' +' if j < len(terms) - 1 else '') ++ j += 1 ++ print(s) ++ prefix = ' * ' + (' ' * (len(prefix) - 3)) ++ ++# Reverse the bits of a polynomial. ++def bitreverse(poly, num_bits): ++ assert poly.bit_length() <= num_bits ++ return xor(1 << (num_bits - 1 - i) for i in range(num_bits) ++ if (poly & (1 << i)) != 0) ++ ++# Format a polynomial as hex. Bit-reflect it if the CRC is lsb-first. ++def fmt_poly(variant, poly, num_bits): ++ if variant.lsb: ++ poly = bitreverse(poly, num_bits) ++ return f'0x{poly:0{2*num_bits//8}x}' ++ ++# Print a pair of 64-bit polynomial multipliers. They are always passed in the ++# order [HI64_TERMS, LO64_TERMS] but will be printed in the appropriate order. ++def print_mult_pair(variant, mults): ++ mults = list(mults if variant.lsb else reversed(mults)) ++ terms = ["HI64_TERMS", "LO64_TERMS"] if variant.lsb else ["LO64_TERMS", "HI64_TERMS"] ++ for i in range(2): ++ print(f'\t\t{fmt_poly(variant, mults[i]["val"], 64)},\t/* {terms[i]}: {mults[i]["desc"]} */') ++ ++# Print a comment describing constants generated for the given CRC variant. ++def print_header(variant, what): ++ print('/*') ++ s = f'{"least" if variant.lsb else "most"}-significant-bit-first CRC-{variant.bits}' ++ print(f' * {what} generated for {s} using') ++ pprint_poly(' * G(x) = ', variant.G) ++ print(' */') ++ ++class CrcVariant: ++ def __init__(self, bits, generator_poly, bit_order): ++ self.bits = bits ++ if bit_order not in ['lsb', 'msb']: ++ raise ValueError('Invalid value for bit_order') ++ self.lsb = bit_order == 'lsb' ++ self.name = f'crc{bits}_{bit_order}_0x{generator_poly:0{(2*bits+7)//8}x}' ++ if self.lsb: ++ generator_poly = bitreverse(generator_poly, bits) ++ self.G = generator_poly ^ (1 << bits) ++ ++# Generate tables for CRC computation using the "slice-by-N" method. ++# N=1 corresponds to the traditional byte-at-a-time table. ++def gen_slicebyN_tables(variants, n): ++ for v in variants: ++ print('') ++ print_header(v, f'Slice-by-{n} CRC table') ++ print(f'static const u{v.bits} __maybe_unused {v.name}_table[{256*n}] = {{') ++ s = '' ++ for i in range(256 * n): ++ # The i'th table entry is the CRC of the message consisting of byte ++ # i % 256 followed by i // 256 zero bytes. ++ poly = (bitreverse(i % 256, 8) if v.lsb else (i % 256)) << (v.bits + 8*(i//256)) ++ next_entry = fmt_poly(v, reduce(poly, v.G), v.bits) + ',' ++ if len(s + next_entry) > 71: ++ print(f'\t{s}') ++ s = '' ++ s += (' ' if s else '') + next_entry ++ if s: ++ print(f'\t{s}') ++ print('};') ++ ++# Generate constants for carryless multiplication based CRC computation. ++def gen_x86_pclmul_consts(variants): ++ # These are the distances, in bits, to generate folding constants for. ++ FOLD_DISTANCES = [2048, 1024, 512, 256, 128] ++ ++ for v in variants: ++ (G, n, lsb) = (v.G, v.bits, v.lsb) ++ print('') ++ print_header(v, 'CRC folding constants') ++ print('static const struct {') ++ if not lsb: ++ print('\tu8 bswap_mask[16];') ++ for i in FOLD_DISTANCES: ++ print(f'\tu64 fold_across_{i}_bits_consts[2];') ++ print('\tu8 shuf_table[48];') ++ print('\tu64 barrett_reduction_consts[2];') ++ print(f'}} {v.name}_consts ____cacheline_aligned __maybe_unused = {{') ++ ++ # Byte-reflection mask, needed for msb-first CRCs ++ if not lsb: ++ print('\t.bswap_mask = {' + ', '.join(str(i) for i in reversed(range(16))) + '},') ++ ++ # Fold constants for all distances down to 128 bits ++ for i in FOLD_DISTANCES: ++ print(f'\t.fold_across_{i}_bits_consts = {{') ++ # Given 64x64 => 128 bit carryless multiplication instructions, two ++ # 64-bit fold constants are needed per "fold distance" i: one for ++ # HI64_TERMS that is basically x^(i+64) mod G and one for LO64_TERMS ++ # that is basically x^i mod G. The exact values however undergo a ++ # couple adjustments, described below. ++ mults = [] ++ for j in [64, 0]: ++ pow_of_x = i + j ++ if lsb: ++ # Each 64x64 => 128 bit carryless multiplication instruction ++ # actually generates a 127-bit product in physical bits 0 ++ # through 126, which in the lsb-first case represent the ++ # coefficients of x^1 through x^127, not x^0 through x^126. ++ # Thus in the lsb-first case, each such instruction ++ # implicitly adds an extra factor of x. The below removes a ++ # factor of x from each constant to compensate for this. ++ # For n < 64 the x could be removed from either the reduced ++ # part or unreduced part, but for n == 64 the reduced part ++ # is the only option; we just always use the reduced part. ++ pow_of_x -= 1 ++ # Make a factor of 64-n be applied unreduced rather than ++ # reduced, to cause the product to use only the x^n and above ++ # terms and always be zero in the x^0 through x^(n-1) terms. ++ # Usually this makes no difference as it does not affect the ++ # product's congruence class mod G and the constant remains ++ # 64-bit, but part of the final reduction from 128 bits does ++ # rely on this property when it reuses one of the constants. ++ pow_of_x -= 64 - n ++ mults.append({ 'val': reduce(1 << pow_of_x, G) << (64 - n), ++ 'desc': f'(x^{pow_of_x} mod G) * x^{64-n}' }) ++ print_mult_pair(v, mults) ++ print('\t},') ++ ++ # Shuffle table for handling 1..15 bytes at end ++ print('\t.shuf_table = {') ++ print('\t\t' + (16*'-1, ').rstrip()) ++ print('\t\t' + ''.join(f'{i:2}, ' for i in range(16)).rstrip()) ++ print('\t\t' + (16*'-1, ').rstrip()) ++ print('\t},') ++ ++ # Barrett reduction constants for reducing 128 bits to the final CRC ++ print('\t.barrett_reduction_consts = {') ++ mults = [] ++ ++ val = div(1 << (63+n), G) ++ desc = f'floor(x^{63+n} / G)' ++ if not lsb: ++ val = (val << 1) - (1 << 64) ++ desc = f'({desc} * x) - x^64' ++ mults.append({ 'val': val, 'desc': desc }) ++ ++ val = G - (1 << n) ++ desc = f'G - x^{n}' ++ if lsb and n == 64: ++ assert (val & 1) != 0 ++ val >>= 1 ++ desc = f'({desc} - x^0) / x' ++ else: ++ pow_of_x = 64 - n - (1 if lsb else 0) ++ val <<= pow_of_x ++ desc = f'({desc}) * x^{pow_of_x}' ++ mults.append({ 'val': val, 'desc': desc }) ++ ++ print_mult_pair(v, mults) ++ print('\t},') ++ ++ print('};') ++ ++def parse_crc_variants(vars_string): ++ variants = [] ++ for var_string in vars_string.split(','): ++ bits, bit_order, generator_poly = var_string.split('_') ++ assert bits.startswith('crc') ++ bits = int(bits.removeprefix('crc')) ++ assert generator_poly.startswith('0x') ++ generator_poly = generator_poly.removeprefix('0x') ++ assert len(generator_poly) % 2 == 0 ++ generator_poly = int(generator_poly, 16) ++ variants.append(CrcVariant(bits, generator_poly, bit_order)) ++ return variants ++ ++if len(sys.argv) != 3: ++ sys.stderr.write(f'Usage: {sys.argv[0]} CONSTS_TYPE[,CONSTS_TYPE]... CRC_VARIANT[,CRC_VARIANT]...\n') ++ sys.stderr.write(' CONSTS_TYPE can be sliceby[1-8] or x86_pclmul\n') ++ sys.stderr.write(' CRC_VARIANT is crc${num_bits}_${bit_order}_${generator_poly_as_hex}\n') ++ sys.stderr.write(' E.g. crc16_msb_0x8bb7 or crc32_lsb_0xedb88320\n') ++ sys.stderr.write(' Polynomial must use the given bit_order and exclude x^{num_bits}\n') ++ sys.exit(1) ++ ++print('/* SPDX-License-Identifier: GPL-2.0-or-later */') ++print('/*') ++print(' * CRC constants generated by:') ++print(' *') ++print(f' *\t{sys.argv[0]} {" ".join(sys.argv[1:])}') ++print(' *') ++print(' * Do not edit manually.') ++print(' */') ++consts_types = sys.argv[1].split(',') ++variants = parse_crc_variants(sys.argv[2]) ++for consts_type in consts_types: ++ if consts_type.startswith('sliceby'): ++ gen_slicebyN_tables(variants, int(consts_type.removeprefix('sliceby'))) ++ elif consts_type == 'x86_pclmul': ++ gen_x86_pclmul_consts(variants) ++ else: ++ raise ValueError(f'Unknown consts_type: {consts_type}') +-- +2.48.1 + +From a75ceb8efcdcc1085525ab31747a2b4849b6d0e4 Mon Sep 17 00:00:00 2001 +From: Eric Naim +Date: Mon, 10 Feb 2025 09:50:13 +0800 +Subject: [PATCH 7/9] fixes + +Signed-off-by: Eric Naim +--- + arch/Kconfig | 4 ++-- + drivers/gpu/drm/drm_edid.c | 47 +++++++++++++++++++++++++++++++++++--- + kernel/sched/ext.c | 7 +++--- + scripts/package/PKGBUILD | 5 ++++ + tools/objtool/check.c | 2 +- + 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index b8a4ff365582..9b087f9bb413 100644 @@ -12580,18 +21994,6 @@ index b8a4ff365582..9b087f9bb413 100644 depends on HAVE_ARCH_MMAP_RND_COMPAT_BITS help This value can be used to select the number of bits to use to -diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile -index f2051644de94..606c74f27459 100644 ---- a/arch/x86/boot/compressed/Makefile -+++ b/arch/x86/boot/compressed/Makefile -@@ -25,6 +25,7 @@ targets := vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 vmlinux.bin.lzma \ - # avoid errors with '-march=i386', and future flags may depend on the target to - # be valid. - KBUILD_CFLAGS := -m$(BITS) -O2 $(CLANG_FLAGS) -+KBUILD_CFLAGS += -std=gnu11 - KBUILD_CFLAGS += -fno-strict-aliasing -fPIE - KBUILD_CFLAGS += -Wundef - KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 13bc4c290b17..9b741e6262bc 100644 --- a/drivers/gpu/drm/drm_edid.c @@ -12685,18 +22087,6 @@ index 13bc4c290b17..9b741e6262bc 100644 if (!newmode) continue; -diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h -index c448de53bf91..6ee1790f2212 100644 ---- a/drivers/hid/hid-ids.h -+++ b/drivers/hid/hid-ids.h -@@ -210,6 +210,7 @@ - #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 - #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30 - #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6 -+#define USB_DEVICE_ID_ASUSTEK_ROG_RAIKIRI_PAD 0x1abb - #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe - #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c - #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 8857c0709bdd..e764c2f367f0 100644 --- a/kernel/sched/ext.c @@ -12731,13 +22121,645 @@ index 0cf3a55b05e1..a27d4344a4e8 100644 echo "Installing System.map and config..." mkdir -p "${builddir}" cp System.map "${builddir}/System.map" +diff --git a/tools/objtool/check.c b/tools/objtool/check.c +index 753dbc4f8198..096c4eeaab57 100644 +--- a/tools/objtool/check.c ++++ b/tools/objtool/check.c +@@ -2264,7 +2264,7 @@ static int read_annotate(struct objtool_file *file, + + if (sec->sh.sh_entsize != 8) { + static bool warned = false; +- if (!warned) { ++ if (!warned && opts.verbose) { + WARN("%s: dodgy linker, sh_entsize != 8", sec->name); + warned = true; + } -- 2.48.1 -From e31198eb6f1e2bc27e1bd80f6ed0633fc6b0a9fd Mon Sep 17 00:00:00 2001 +From 9ff3ff1b55e44c71811787bd933721a2472bdb65 Mon Sep 17 00:00:00 2001 From: Eric Naim -Date: Mon, 3 Feb 2025 11:05:39 +0800 -Subject: [PATCH 6/6] zstd +Date: Mon, 10 Feb 2025 09:50:19 +0800 +Subject: [PATCH 8/9] mm + +Signed-off-by: Eric Naim +--- + include/linux/pid.h | 7 +- + kernel/exit.c | 36 +++++++---- + kernel/pid.c | 82 ++++++++++++----------- + kernel/sys.c | 14 ++-- + mm/madvise.c | 154 ++++++++++++++++++++++++++++++-------------- + 5 files changed, 189 insertions(+), 104 deletions(-) + +diff --git a/include/linux/pid.h b/include/linux/pid.h +index 98837a1ff0f3..311ecebd7d56 100644 +--- a/include/linux/pid.h ++++ b/include/linux/pid.h +@@ -101,9 +101,9 @@ extern struct pid *get_task_pid(struct task_struct *task, enum pid_type type); + * these helpers must be called with the tasklist_lock write-held. + */ + extern void attach_pid(struct task_struct *task, enum pid_type); +-extern void detach_pid(struct task_struct *task, enum pid_type); +-extern void change_pid(struct task_struct *task, enum pid_type, +- struct pid *pid); ++void detach_pid(struct pid **pids, struct task_struct *task, enum pid_type); ++void change_pid(struct pid **pids, struct task_struct *task, enum pid_type, ++ struct pid *pid); + extern void exchange_tids(struct task_struct *task, struct task_struct *old); + extern void transfer_pid(struct task_struct *old, struct task_struct *new, + enum pid_type); +@@ -129,6 +129,7 @@ extern struct pid *find_ge_pid(int nr, struct pid_namespace *); + extern struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + size_t set_tid_size); + extern void free_pid(struct pid *pid); ++void free_pids(struct pid **pids); + extern void disable_pid_allocation(struct pid_namespace *ns); + + /* +diff --git a/kernel/exit.c b/kernel/exit.c +index 3485e5fc499e..0d6df671c8a8 100644 +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -122,14 +122,22 @@ static __init int kernel_exit_sysfs_init(void) + late_initcall(kernel_exit_sysfs_init); + #endif + +-static void __unhash_process(struct task_struct *p, bool group_dead) ++/* ++ * For things release_task() would like to do *after* tasklist_lock is released. ++ */ ++struct release_task_post { ++ struct pid *pids[PIDTYPE_MAX]; ++}; ++ ++static void __unhash_process(struct release_task_post *post, struct task_struct *p, ++ bool group_dead) + { + nr_threads--; +- detach_pid(p, PIDTYPE_PID); ++ detach_pid(post->pids, p, PIDTYPE_PID); + if (group_dead) { +- detach_pid(p, PIDTYPE_TGID); +- detach_pid(p, PIDTYPE_PGID); +- detach_pid(p, PIDTYPE_SID); ++ detach_pid(post->pids, p, PIDTYPE_TGID); ++ detach_pid(post->pids, p, PIDTYPE_PGID); ++ detach_pid(post->pids, p, PIDTYPE_SID); + + list_del_rcu(&p->tasks); + list_del_init(&p->sibling); +@@ -141,7 +149,7 @@ static void __unhash_process(struct task_struct *p, bool group_dead) + /* + * This function expects the tasklist_lock write-locked. + */ +-static void __exit_signal(struct task_struct *tsk) ++static void __exit_signal(struct release_task_post *post, struct task_struct *tsk) + { + struct signal_struct *sig = tsk->signal; + bool group_dead = thread_group_leader(tsk); +@@ -174,9 +182,6 @@ static void __exit_signal(struct task_struct *tsk) + sig->curr_target = next_thread(tsk); + } + +- add_device_randomness((const void*) &tsk->se.sum_exec_runtime, +- sizeof(unsigned long long)); +- + /* + * Accumulate here the counters for all threads as they die. We could + * skip the group leader because it is the last user of signal_struct, +@@ -197,7 +202,7 @@ static void __exit_signal(struct task_struct *tsk) + task_io_accounting_add(&sig->ioac, &tsk->ioac); + sig->sum_sched_runtime += tsk->se.sum_exec_runtime; + sig->nr_threads--; +- __unhash_process(tsk, group_dead); ++ __unhash_process(post, tsk, group_dead); + write_sequnlock(&sig->stats_lock); + + /* +@@ -239,10 +244,13 @@ void __weak release_thread(struct task_struct *dead_task) + + void release_task(struct task_struct *p) + { ++ struct release_task_post post; + struct task_struct *leader; + struct pid *thread_pid; + int zap_leader; + repeat: ++ memset(&post, 0, sizeof(post)); ++ + /* don't need to get the RCU readlock here - the process is dead and + * can't be modifying its own credentials. But shut RCU-lockdep up */ + rcu_read_lock(); +@@ -251,10 +259,11 @@ void release_task(struct task_struct *p) + + cgroup_release(p); + ++ thread_pid = get_pid(p->thread_pid); ++ + write_lock_irq(&tasklist_lock); + ptrace_release_task(p); +- thread_pid = get_pid(p->thread_pid); +- __exit_signal(p); ++ __exit_signal(&post, p); + + /* + * If we are the last non-leader member of the thread +@@ -278,6 +287,9 @@ void release_task(struct task_struct *p) + write_unlock_irq(&tasklist_lock); + proc_flush_pid(thread_pid); + put_pid(thread_pid); ++ add_device_randomness(&p->se.sum_exec_runtime, ++ sizeof(p->se.sum_exec_runtime)); ++ free_pids(post.pids); + release_thread(p); + put_task_struct_rcu_user(p); + +diff --git a/kernel/pid.c b/kernel/pid.c +index 924084713be8..900193de4232 100644 +--- a/kernel/pid.c ++++ b/kernel/pid.c +@@ -88,20 +88,6 @@ struct pid_namespace init_pid_ns = { + }; + EXPORT_SYMBOL_GPL(init_pid_ns); + +-/* +- * Note: disable interrupts while the pidmap_lock is held as an +- * interrupt might come in and do read_lock(&tasklist_lock). +- * +- * If we don't disable interrupts there is a nasty deadlock between +- * detach_pid()->free_pid() and another cpu that does +- * spin_lock(&pidmap_lock) followed by an interrupt routine that does +- * read_lock(&tasklist_lock); +- * +- * After we clean up the tasklist_lock and know there are no +- * irq handlers that take it we can leave the interrupts enabled. +- * For now it is easier to be safe than to prove it can't happen. +- */ +- + static __cacheline_aligned_in_smp DEFINE_SPINLOCK(pidmap_lock); + seqcount_spinlock_t pidmap_lock_seq = SEQCNT_SPINLOCK_ZERO(pidmap_lock_seq, &pidmap_lock); + +@@ -128,11 +114,11 @@ static void delayed_put_pid(struct rcu_head *rhp) + + void free_pid(struct pid *pid) + { +- /* We can be called with write_lock_irq(&tasklist_lock) held */ + int i; +- unsigned long flags; + +- spin_lock_irqsave(&pidmap_lock, flags); ++ lockdep_assert_not_held(&tasklist_lock); ++ ++ spin_lock(&pidmap_lock); + for (i = 0; i <= pid->level; i++) { + struct upid *upid = pid->numbers + i; + struct pid_namespace *ns = upid->ns; +@@ -155,11 +141,23 @@ void free_pid(struct pid *pid) + idr_remove(&ns->idr, upid->nr); + } + pidfs_remove_pid(pid); +- spin_unlock_irqrestore(&pidmap_lock, flags); ++ spin_unlock(&pidmap_lock); + + call_rcu(&pid->rcu, delayed_put_pid); + } + ++void free_pids(struct pid **pids) ++{ ++ int tmp; ++ ++ /* ++ * This can batch pidmap_lock. ++ */ ++ for (tmp = PIDTYPE_MAX; --tmp >= 0; ) ++ if (pids[tmp]) ++ free_pid(pids[tmp]); ++} ++ + struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + size_t set_tid_size) + { +@@ -211,7 +209,7 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + } + + idr_preload(GFP_KERNEL); +- spin_lock_irq(&pidmap_lock); ++ spin_lock(&pidmap_lock); + + if (tid) { + nr = idr_alloc(&tmp->idr, NULL, tid, +@@ -238,7 +236,7 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min, + pid_max, GFP_ATOMIC); + } +- spin_unlock_irq(&pidmap_lock); ++ spin_unlock(&pidmap_lock); + idr_preload_end(); + + if (nr < 0) { +@@ -272,7 +270,7 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + + upid = pid->numbers + ns->level; + idr_preload(GFP_KERNEL); +- spin_lock_irq(&pidmap_lock); ++ spin_lock(&pidmap_lock); + if (!(ns->pid_allocated & PIDNS_ADDING)) + goto out_unlock; + pidfs_add_pid(pid); +@@ -281,18 +279,18 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + idr_replace(&upid->ns->idr, pid, upid->nr); + upid->ns->pid_allocated++; + } +- spin_unlock_irq(&pidmap_lock); ++ spin_unlock(&pidmap_lock); + idr_preload_end(); + + return pid; + + out_unlock: +- spin_unlock_irq(&pidmap_lock); ++ spin_unlock(&pidmap_lock); + idr_preload_end(); + put_pid_ns(ns); + + out_free: +- spin_lock_irq(&pidmap_lock); ++ spin_lock(&pidmap_lock); + while (++i <= ns->level) { + upid = pid->numbers + i; + idr_remove(&upid->ns->idr, upid->nr); +@@ -302,7 +300,7 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + if (ns->pid_allocated == PIDNS_ADDING) + idr_set_cursor(&ns->idr, 0); + +- spin_unlock_irq(&pidmap_lock); ++ spin_unlock(&pidmap_lock); + + kmem_cache_free(ns->pid_cachep, pid); + return ERR_PTR(retval); +@@ -310,9 +308,9 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, + + void disable_pid_allocation(struct pid_namespace *ns) + { +- spin_lock_irq(&pidmap_lock); ++ spin_lock(&pidmap_lock); + ns->pid_allocated &= ~PIDNS_ADDING; +- spin_unlock_irq(&pidmap_lock); ++ spin_unlock(&pidmap_lock); + } + + struct pid *find_pid_ns(int nr, struct pid_namespace *ns) +@@ -339,17 +337,23 @@ static struct pid **task_pid_ptr(struct task_struct *task, enum pid_type type) + */ + void attach_pid(struct task_struct *task, enum pid_type type) + { +- struct pid *pid = *task_pid_ptr(task, type); ++ struct pid *pid; ++ ++ lockdep_assert_held_write(&tasklist_lock); ++ ++ pid = *task_pid_ptr(task, type); + hlist_add_head_rcu(&task->pid_links[type], &pid->tasks[type]); + } + +-static void __change_pid(struct task_struct *task, enum pid_type type, +- struct pid *new) ++static void __change_pid(struct pid **pids, struct task_struct *task, ++ enum pid_type type, struct pid *new) + { +- struct pid **pid_ptr = task_pid_ptr(task, type); +- struct pid *pid; ++ struct pid **pid_ptr, *pid; + int tmp; + ++ lockdep_assert_held_write(&tasklist_lock); ++ ++ pid_ptr = task_pid_ptr(task, type); + pid = *pid_ptr; + + hlist_del_rcu(&task->pid_links[type]); +@@ -364,18 +368,19 @@ static void __change_pid(struct task_struct *task, enum pid_type type, + if (pid_has_task(pid, tmp)) + return; + +- free_pid(pid); ++ WARN_ON(pids[type]); ++ pids[type] = pid; + } + +-void detach_pid(struct task_struct *task, enum pid_type type) ++void detach_pid(struct pid **pids, struct task_struct *task, enum pid_type type) + { +- __change_pid(task, type, NULL); ++ __change_pid(pids, task, type, NULL); + } + +-void change_pid(struct task_struct *task, enum pid_type type, ++void change_pid(struct pid **pids, struct task_struct *task, enum pid_type type, + struct pid *pid) + { +- __change_pid(task, type, pid); ++ __change_pid(pids, task, type, pid); + attach_pid(task, type); + } + +@@ -386,6 +391,8 @@ void exchange_tids(struct task_struct *left, struct task_struct *right) + struct hlist_head *head1 = &pid1->tasks[PIDTYPE_PID]; + struct hlist_head *head2 = &pid2->tasks[PIDTYPE_PID]; + ++ lockdep_assert_held_write(&tasklist_lock); ++ + /* Swap the single entry tid lists */ + hlists_swap_heads_rcu(head1, head2); + +@@ -403,6 +410,7 @@ void transfer_pid(struct task_struct *old, struct task_struct *new, + enum pid_type type) + { + WARN_ON_ONCE(type == PIDTYPE_PID); ++ lockdep_assert_held_write(&tasklist_lock); + hlist_replace_rcu(&old->pid_links[type], &new->pid_links[type]); + } + +diff --git a/kernel/sys.c b/kernel/sys.c +index cb366ff8703a..4efca8a97d62 100644 +--- a/kernel/sys.c ++++ b/kernel/sys.c +@@ -1085,6 +1085,7 @@ SYSCALL_DEFINE2(setpgid, pid_t, pid, pid_t, pgid) + { + struct task_struct *p; + struct task_struct *group_leader = current->group_leader; ++ struct pid *pids[PIDTYPE_MAX] = { 0 }; + struct pid *pgrp; + int err; + +@@ -1142,13 +1143,14 @@ SYSCALL_DEFINE2(setpgid, pid_t, pid, pid_t, pgid) + goto out; + + if (task_pgrp(p) != pgrp) +- change_pid(p, PIDTYPE_PGID, pgrp); ++ change_pid(pids, p, PIDTYPE_PGID, pgrp); + + err = 0; + out: + /* All paths lead to here, thus we are safe. -DaveM */ + write_unlock_irq(&tasklist_lock); + rcu_read_unlock(); ++ free_pids(pids); + return err; + } + +@@ -1222,21 +1224,22 @@ SYSCALL_DEFINE1(getsid, pid_t, pid) + return retval; + } + +-static void set_special_pids(struct pid *pid) ++static void set_special_pids(struct pid **pids, struct pid *pid) + { + struct task_struct *curr = current->group_leader; + + if (task_session(curr) != pid) +- change_pid(curr, PIDTYPE_SID, pid); ++ change_pid(pids, curr, PIDTYPE_SID, pid); + + if (task_pgrp(curr) != pid) +- change_pid(curr, PIDTYPE_PGID, pid); ++ change_pid(pids, curr, PIDTYPE_PGID, pid); + } + + int ksys_setsid(void) + { + struct task_struct *group_leader = current->group_leader; + struct pid *sid = task_pid(group_leader); ++ struct pid *pids[PIDTYPE_MAX] = { 0 }; + pid_t session = pid_vnr(sid); + int err = -EPERM; + +@@ -1252,13 +1255,14 @@ int ksys_setsid(void) + goto out; + + group_leader->signal->leader = 1; +- set_special_pids(sid); ++ set_special_pids(pids, sid); + + proc_clear_tty(group_leader); + + err = session; + out: + write_unlock_irq(&tasklist_lock); ++ free_pids(pids); + if (err > 0) { + proc_sid_connector(group_leader); + sched_autogroup_create_attach(group_leader); +diff --git a/mm/madvise.c b/mm/madvise.c +index 49f3a75046f6..6eb7cf1cccce 100644 +--- a/mm/madvise.c ++++ b/mm/madvise.c +@@ -1565,6 +1565,83 @@ int madvise_set_anon_name(struct mm_struct *mm, unsigned long start, + madvise_vma_anon_name); + } + #endif /* CONFIG_ANON_VMA_NAME */ ++ ++static int madvise_lock(struct mm_struct *mm, int behavior) ++{ ++ ++#ifdef CONFIG_MEMORY_FAILURE ++ if (behavior == MADV_HWPOISON || behavior == MADV_SOFT_OFFLINE) ++ return 0; ++#endif ++ ++ if (madvise_need_mmap_write(behavior)) { ++ if (mmap_write_lock_killable(mm)) ++ return -EINTR; ++ } else { ++ mmap_read_lock(mm); ++ } ++ return 0; ++ ++} ++ ++static void madvise_unlock(struct mm_struct *mm, int behavior) ++{ ++ if (madvise_need_mmap_write(behavior)) ++ mmap_write_unlock(mm); ++ else ++ mmap_read_unlock(mm); ++} ++ ++static bool is_valid_madvise(unsigned long start, size_t len_in, int behavior) ++{ ++ size_t len; ++ ++ if (!madvise_behavior_valid(behavior)) ++ return false; ++ ++ if (!PAGE_ALIGNED(start)) ++ return false; ++ len = PAGE_ALIGN(len_in); ++ ++ /* Check to see whether len was rounded up from small -ve to zero */ ++ if (len_in && !len) ++ return false; ++ ++ if (start + len < start) ++ return false; ++ ++ return true; ++} ++ ++static int madvise_do_behavior(struct mm_struct *mm, ++ unsigned long start, size_t len_in, size_t len, int behavior) ++{ ++ struct blk_plug plug; ++ unsigned long end; ++ int error; ++ ++#ifdef CONFIG_MEMORY_FAILURE ++ if (behavior == MADV_HWPOISON || behavior == MADV_SOFT_OFFLINE) ++ return madvise_inject_error(behavior, start, start + len_in); ++#endif ++ start = untagged_addr_remote(mm, start); ++ end = start + len; ++ ++ blk_start_plug(&plug); ++ switch (behavior) { ++ case MADV_POPULATE_READ: ++ case MADV_POPULATE_WRITE: ++ error = madvise_populate(mm, start, end, behavior); ++ break; ++ default: ++ error = madvise_walk_vmas(mm, start, end, behavior, ++ madvise_vma_behavior); ++ break; ++ } ++ blk_finish_plug(&plug); ++ return error; ++} ++ + /* + * The madvise(2) system call. + * +@@ -1641,61 +1718,22 @@ int do_madvise(struct mm_struct *mm, unsigned long start, size_t len_in, int beh + { + unsigned long end; + int error; +- int write; + size_t len; +- struct blk_plug plug; + +- if (!madvise_behavior_valid(behavior)) ++ if (!is_valid_madvise(start, len_in, behavior)) + return -EINVAL; + +- if (!PAGE_ALIGNED(start)) +- return -EINVAL; + len = PAGE_ALIGN(len_in); +- +- /* Check to see whether len was rounded up from small -ve to zero */ +- if (len_in && !len) +- return -EINVAL; +- + end = start + len; +- if (end < start) +- return -EINVAL; + + if (end == start) + return 0; + +-#ifdef CONFIG_MEMORY_FAILURE +- if (behavior == MADV_HWPOISON || behavior == MADV_SOFT_OFFLINE) +- return madvise_inject_error(behavior, start, start + len_in); +-#endif +- +- write = madvise_need_mmap_write(behavior); +- if (write) { +- if (mmap_write_lock_killable(mm)) +- return -EINTR; +- } else { +- mmap_read_lock(mm); +- } +- +- start = untagged_addr_remote(mm, start); +- end = start + len; +- +- blk_start_plug(&plug); +- switch (behavior) { +- case MADV_POPULATE_READ: +- case MADV_POPULATE_WRITE: +- error = madvise_populate(mm, start, end, behavior); +- break; +- default: +- error = madvise_walk_vmas(mm, start, end, behavior, +- madvise_vma_behavior); +- break; +- } +- blk_finish_plug(&plug); +- +- if (write) +- mmap_write_unlock(mm); +- else +- mmap_read_unlock(mm); ++ error = madvise_lock(mm, behavior); ++ if (error) ++ return error; ++ error = madvise_do_behavior(mm, start, len_in, len, behavior); ++ madvise_unlock(mm, behavior); + + return error; + } +@@ -1714,9 +1752,26 @@ static ssize_t vector_madvise(struct mm_struct *mm, struct iov_iter *iter, + + total_len = iov_iter_count(iter); + ++ ret = madvise_lock(mm, behavior); ++ if (ret) ++ return ret; ++ + while (iov_iter_count(iter)) { +- ret = do_madvise(mm, (unsigned long)iter_iov_addr(iter), +- iter_iov_len(iter), behavior); ++ unsigned long start = (unsigned long)iter_iov_addr(iter); ++ size_t len_in = iter_iov_len(iter); ++ size_t len; ++ ++ if (!is_valid_madvise(start, len_in, behavior)) { ++ ret = -EINVAL; ++ break; ++ } ++ ++ len = PAGE_ALIGN(len_in); ++ if (start + len == start) ++ ret = 0; ++ else ++ ret = madvise_do_behavior(mm, start, len_in, len, ++ behavior); + /* + * An madvise operation is attempting to restart the syscall, + * but we cannot proceed as it would not be correct to repeat +@@ -1732,12 +1787,17 @@ static ssize_t vector_madvise(struct mm_struct *mm, struct iov_iter *iter, + ret = -EINTR; + break; + } ++ ++ /* Drop and reacquire lock to unwind race. */ ++ madvise_unlock(mm, behavior); ++ madvise_lock(mm, behavior); + continue; + } + if (ret < 0) + break; + iov_iter_advance(iter, iter_iov_len(iter)); + } ++ madvise_unlock(mm, behavior); + + ret = (total_len - iov_iter_count(iter)) ? : ret; + +-- +2.48.1 + +From aa5f9f2f21fefea3cb480c4a319b211031cd51f6 Mon Sep 17 00:00:00 2001 +From: Eric Naim +Date: Mon, 10 Feb 2025 09:50:37 +0800 +Subject: [PATCH 9/9] zstd Signed-off-by: Eric Naim ---