From 0b5fb2ded0f518f3e23effcbf3153ff8b267eb13 Mon Sep 17 00:00:00 2001
From: NotWadeGrimridge <wade@6942020.xyz>
Date: Wed, 10 Dec 2025 20:54:49 +0530
Subject: [PATCH 2/2] ALSA: hda: Keep IRQ core out of deep C-states
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Track the CPU serving the HDA IRQ and apply a per-CPU PM QoS
constraint while streams are running so it stays out of deep C-states.
Done automatically when the CPU governor is performance, through
CPUFREQ_UPDATE_POLICY.

On my i7-8550U CPU, the exit latency for C3 is 70 µs, so 69 µs was
chosen.

Signed-off-by: NotWadeGrimridge <wade@6942020.xyz>
---
 sound/hda/common/controller.c     | 129 ++++++++++++++++++++++++++++++
 sound/hda/common/hda_controller.h |   7 ++
 sound/hda/controllers/intel.c     |  45 +++++++++++
 3 files changed, 181 insertions(+)

diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c
index 84387ed76..5ab3dd0f8 100644
--- a/sound/hda/common/controller.c
+++ b/sound/hda/common/controller.c
@@ -16,6 +16,10 @@
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
 #include <linux/slab.h>
+#include <linux/pm_qos.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu.h>
+#include <linux/irq.h>
 
 #ifdef CONFIG_X86
 /* for art-tsc conversion */
@@ -36,6 +40,126 @@
 #define dsp_unlock(dev)		snd_hdac_dsp_unlock(azx_stream(dev))
 #define dsp_is_locked(dev)	snd_hdac_stream_is_locked(azx_stream(dev))
 
+#define AZX_CPU_LATENCY_QOS_US	69
+
+static void azx_cpu_latency_qos_add(struct azx *chip, int cpu)
+{
+	struct device *cpu_dev;
+	int ret;
+
+	if (dev_pm_qos_request_active(&chip->cpu_latency_req))
+		return;
+
+	cpu_dev = get_cpu_device(cpu);
+	if (!cpu_dev)
+		return;
+
+	ret = dev_pm_qos_add_request(cpu_dev, &chip->cpu_latency_req,
+				     DEV_PM_QOS_RESUME_LATENCY,
+				     AZX_CPU_LATENCY_QOS_US);
+	if (ret < 0) {
+		memset(&chip->cpu_latency_req, 0,
+		       sizeof(chip->cpu_latency_req));
+		return;
+	}
+}
+
+static bool azx_policy_is_performance(const struct cpufreq_policy *policy)
+{
+	if (!policy)
+		return true;
+
+	if (policy->policy == CPUFREQ_POLICY_PERFORMANCE)
+		return true;
+
+	return policy->governor &&
+		!strcmp(policy->governor->name, "performance");
+}
+
+static int azx_cpu_latency_irq_cpu(struct azx *chip)
+{
+	struct hdac_bus *bus = azx_bus(chip);
+	const struct cpumask *mask;
+	int cpu;
+
+	if (bus->irq < 0)
+		return -1;
+
+	mask = irq_get_effective_affinity_mask(bus->irq);
+	if (!mask)
+		return -1;
+
+	cpu = cpumask_first(mask);
+	if (cpu >= nr_cpu_ids)
+		return -1;
+
+	return cpu;
+}
+
+static void azx_cpu_latency_qos_remove(struct azx *chip)
+{
+	if (dev_pm_qos_request_active(&chip->cpu_latency_req))
+		dev_pm_qos_remove_request(&chip->cpu_latency_req);
+}
+
+static void azx_cpu_latency_qos_apply(struct azx *chip,
+				      struct cpufreq_policy *policy)
+{
+	struct cpufreq_policy *held = NULL;
+	int cpu;
+
+	if (!atomic_read(&chip->cpu_latency_refcount))
+		goto disable;
+
+	cpu = azx_cpu_latency_irq_cpu(chip);
+	if (cpu < 0)
+		goto disable;
+
+	if (policy) {
+		if (!cpumask_test_cpu(cpu, policy->cpus))
+			goto out;
+	} else {
+		held = cpufreq_cpu_get(cpu);
+		policy = held;
+	}
+
+	if (!azx_policy_is_performance(policy))
+		goto disable;
+
+	azx_cpu_latency_qos_add(chip, cpu);
+	goto out;
+
+disable:
+	azx_cpu_latency_qos_remove(chip);
+out:
+	if (held)
+		cpufreq_cpu_put(held);
+}
+
+static void azx_cpu_latency_qos_get(struct azx *chip)
+{
+	atomic_inc(&chip->cpu_latency_refcount);
+	azx_cpu_latency_qos_apply(chip, NULL);
+}
+
+static void azx_cpu_latency_qos_put(struct azx *chip)
+{
+	if (atomic_dec_if_positive(&chip->cpu_latency_refcount) < 0)
+		return;
+
+	azx_cpu_latency_qos_apply(chip, NULL);
+}
+
+void azx_update_cpu_latency_qos_policy(struct azx *chip,
+				       struct cpufreq_policy *policy)
+{
+	if (!policy)
+		return;
+
+	azx_cpu_latency_qos_apply(chip, policy);
+}
+EXPORT_SYMBOL_GPL(azx_update_cpu_latency_qos_policy);
+
 /* assign a stream for the PCM */
 static inline struct azx_dev *
 azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream)
@@ -244,6 +368,11 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 		return -EINVAL;
 	}
 
+	if (start)
+		azx_cpu_latency_qos_get(chip);
+	else
+		azx_cpu_latency_qos_put(chip);
+
 	snd_pcm_group_for_each_entry(s, substream) {
 		if (s->pcm->card != substream->pcm->card)
 			continue;
diff --git a/sound/hda/common/hda_controller.h b/sound/hda/common/hda_controller.h
index c2d010986..18edc0d46 100644
--- a/sound/hda/common/hda_controller.h
+++ b/sound/hda/common/hda_controller.h
@@ -8,6 +8,8 @@
 
 #include <linux/timecounter.h>
 #include <linux/interrupt.h>
+#include <linux/pm_qos.h>
+#include <linux/atomic.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/initval.h>
@@ -70,6 +72,7 @@ struct azx_dev {
 #define stream_to_azx_dev(s)	container_of(s, struct azx_dev, core)
 
 struct azx;
+struct cpufreq_policy;
 
 /* Functions to read/write to hda registers. */
 struct hda_controller_ops {
@@ -150,6 +153,8 @@ struct azx {
 #ifdef CONFIG_SND_HDA_DSP_LOADER
 	struct azx_dev saved_azx_dev;
 #endif
+	struct dev_pm_qos_request cpu_latency_req;
+	atomic_t cpu_latency_refcount;
 };
 
 #define azx_bus(chip)	(&(chip)->bus.core)
@@ -188,6 +193,8 @@ static inline struct azx_dev *get_azx_dev(struct snd_pcm_substream *substream)
 unsigned int azx_get_position(struct azx *chip, struct azx_dev *azx_dev);
 unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev);
 unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev);
+void azx_update_cpu_latency_qos_policy(struct azx *chip,
+				       struct cpufreq_policy *policy);
 
 /* Stream control. */
 void azx_stop_all_streams(struct azx *chip);
diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c
index 9e37586e3..b3eb83ff7 100644
--- a/sound/hda/controllers/intel.c
+++ b/sound/hda/controllers/intel.c
@@ -38,6 +38,7 @@
 #include <linux/acpi.h>
 #include <linux/pgtable.h>
 #include <linux/dmi.h>
+#include <linux/cpufreq.h>
 
 #ifdef CONFIG_X86
 /* for snoop control */
@@ -49,6 +50,7 @@
 #include <sound/hdaudio.h>
 #include <sound/hda_i915.h>
 #include <sound/intel-dsp-config.h>
+#include <linux/pm_qos.h>
 #include <linux/vgaarb.h>
 #include <linux/vga_switcheroo.h>
 #include <linux/apple-gmux.h>
@@ -907,6 +909,29 @@ static void __azx_shutdown_chip(struct azx *chip, bool skip_link_reset)
 static DEFINE_MUTEX(card_list_lock);
 static LIST_HEAD(card_list);
 
+static int azx_cpufreq_policy_callback(struct notifier_block *nb,
+				       unsigned long val, void *data)
+{
+	struct cpufreq_policy *policy = data;
+	struct hda_intel *hda;
+
+	if (val != CPUFREQ_UPDATE_POLICY)
+		return NOTIFY_DONE;
+
+	mutex_lock(&card_list_lock);
+	list_for_each_entry(hda, &card_list, list)
+		azx_update_cpu_latency_qos_policy(&hda->chip, policy);
+	mutex_unlock(&card_list_lock);
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block azx_cpufreq_notifier = {
+	.notifier_call = azx_cpufreq_policy_callback,
+};
+
+static bool azx_cpufreq_registered;
+
 static void azx_shutdown_chip(struct azx *chip)
 {
 	__azx_shutdown_chip(chip, false);
@@ -915,16 +940,34 @@ static void azx_shutdown_chip(struct azx *chip)
 static void azx_add_card_list(struct azx *chip)
 {
 	struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+
 	mutex_lock(&card_list_lock);
 	list_add(&hda->list, &card_list);
+	if (!azx_cpufreq_registered) {
+		int ret;
+
+		ret = cpufreq_register_notifier(&azx_cpufreq_notifier,
+						CPUFREQ_POLICY_NOTIFIER);
+		if (!ret)
+			azx_cpufreq_registered = true;
+		else
+			pr_warn("hda-intel: failed to register cpufreq notifier: %d\n",
+				ret);
+	}
 	mutex_unlock(&card_list_lock);
 }
 
 static void azx_del_card_list(struct azx *chip)
 {
 	struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+
 	mutex_lock(&card_list_lock);
 	list_del_init(&hda->list);
+	if (azx_cpufreq_registered && list_empty(&card_list)) {
+		cpufreq_unregister_notifier(&azx_cpufreq_notifier,
+					    CPUFREQ_POLICY_NOTIFIER);
+		azx_cpufreq_registered = false;
+	}
 	mutex_unlock(&card_list_lock);
 }
 
@@ -1343,6 +1386,8 @@ static void azx_free(struct azx *chip)
 		pm_runtime_forbid(&pci->dev);
 		pm_runtime_dont_use_autosuspend(&pci->dev);
 	}
+	if (dev_pm_qos_request_active(&chip->cpu_latency_req))
+		dev_pm_qos_remove_request(&chip->cpu_latency_req);
 
 	chip->running = 0;
 
-- 
2.52.0

