summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-30 10:59:32 +0000
committerMark Brown <broonie@kernel.org>2026-10-30 10:59:32 +0000
commitb50d35a6a0dc2cc35fc0eb7cdf1ec6467f54e48b (patch)
tree31da4c94324331f6414f9c09d117796287e7252f
parent84c657e31b3119e82b9734a7ab08c7766c7fdd5b (diff)
parenta4311e9447bbb219044db38897f95be112f46a8a (diff)
downloadlinux-next-b50d35a6a0dc2cc35fc0eb7cdf1ec6467f54e48b.tar.gz
linux-next-b50d35a6a0dc2cc35fc0eb7cdf1ec6467f54e48b.zip
Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git
-rw-r--r--include/sound/info.h2
-rw-r--r--include/sound/ump.h5
-rw-r--r--sound/core/info.c23
-rw-r--r--sound/core/pcm.c56
-rw-r--r--sound/core/seq/seq_ump_client.c5
-rw-r--r--sound/core/ump.c63
-rw-r--r--sound/drivers/opl3/opl3_midi.c15
-rw-r--r--sound/drivers/opl3/opl3_oss.c1
-rw-r--r--sound/drivers/opl3/opl3_seq.c13
-rw-r--r--sound/drivers/opl3/opl3_voice.h1
-rw-r--r--sound/drivers/pcmtest.c24
-rw-r--r--sound/hda/codecs/realtek/alc269.c12
-rw-r--r--sound/hda/common/codec.c6
-rw-r--r--sound/hda/common/sysfs.c4
-rw-r--r--sound/hda/controllers/intel.c2
-rw-r--r--sound/pci/ac97/ac97_codec.c29
-rw-r--r--sound/pci/emu10k1/emufx.c18
-rw-r--r--sound/sh/aica.c2
-rw-r--r--sound/usb/endpoint.c6
-rw-r--r--sound/usb/misc/ua101.c29
-rw-r--r--sound/usb/pcm.c20
-rw-r--r--sound/usb/quirks-table.h65
-rw-r--r--sound/usb/quirks.c6
23 files changed, 333 insertions, 74 deletions
diff --git a/include/sound/info.h b/include/sound/info.h
index adbc506860d6..f216291e5c41 100644
--- a/include/sound/info.h
+++ b/include/sound/info.h
@@ -117,6 +117,7 @@ struct snd_info_entry *snd_info_create_module_entry(struct module *module,
struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
const char *name,
struct snd_info_entry *parent);
+void snd_info_disconnect_entry(struct snd_info_entry *entry);
void snd_info_free_entry(struct snd_info_entry *entry);
int snd_info_card_create(struct snd_card *card);
@@ -164,6 +165,7 @@ static inline int snd_info_get_line(struct snd_info_buffer *buffer, char *line,
static inline char *snd_info_get_str(char *dest, char *src, int len) { return NULL; }
static inline struct snd_info_entry *snd_info_create_module_entry(struct module *module, const char *name, struct snd_info_entry *parent) { return NULL; }
static inline struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card, const char *name, struct snd_info_entry *parent) { return NULL; }
+static inline void snd_info_disconnect_entry(struct snd_info_entry *entry) { }
static inline void snd_info_free_entry(struct snd_info_entry *entry) { ; }
static inline int snd_info_card_create(struct snd_card *card) { return 0; }
diff --git a/include/sound/ump.h b/include/sound/ump.h
index 73f97f88e2ed..387143783a9b 100644
--- a/include/sound/ump.h
+++ b/include/sound/ump.h
@@ -66,6 +66,7 @@ struct snd_ump_endpoint {
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
struct snd_seq_device *seq_dev;
+ /* Published after seq_client; calls are covered by UMP core SRCU. */
const struct snd_seq_ump_ops *seq_ops;
void *seq_client;
#endif
@@ -126,6 +127,10 @@ int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val);
int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol);
void snd_ump_update_group_attrs(struct snd_ump_endpoint *ump);
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+void snd_ump_detach_seq(struct snd_ump_endpoint *ump);
+#endif
+
/*
* Some definitions for UMP
*/
diff --git a/sound/core/info.c b/sound/core/info.c
index c4d57d0809ad..023b4736aa6d 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -738,6 +738,23 @@ static void snd_info_clear_entries(struct snd_info_entry *entry)
}
/**
+ * snd_info_disconnect_entry - remove proc nodes and drain their callbacks
+ * @entry: root of the info entry subtree
+ *
+ * Retains the info entries for snd_info_free_entry(). Callers must serialize
+ * registration and destruction, and must not hold locks needed by callbacks.
+ */
+void snd_info_disconnect_entry(struct snd_info_entry *entry)
+{
+ if (entry && entry->p) {
+ proc_remove(entry->p);
+ guard(mutex)(&info_mutex);
+ snd_info_clear_entries(entry);
+ }
+}
+EXPORT_SYMBOL(snd_info_disconnect_entry);
+
+/**
* snd_info_free_entry - release the info entry
* @entry: the info entry
*
@@ -749,11 +766,7 @@ void snd_info_free_entry(struct snd_info_entry * entry)
if (!entry)
return;
- if (entry->p) {
- proc_remove(entry->p);
- guard(mutex)(&info_mutex);
- snd_info_clear_entries(entry);
- }
+ snd_info_disconnect_entry(entry);
/* free all children at first */
list_for_each_entry_safe(p, n, &entry->children, list)
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 1e4e0ecb3dbd..064df4cd3b50 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -520,6 +520,11 @@ static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr)
return 0;
}
+static void snd_pcm_stream_proc_disconnect(struct snd_pcm_str *pstr)
+{
+ snd_info_disconnect_entry(pstr->proc_root);
+}
+
static int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr)
{
snd_info_free_entry(pstr->proc_root);
@@ -580,6 +585,7 @@ static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream)
#else /* !CONFIG_SND_VERBOSE_PROCFS */
static inline int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { return 0; }
+static inline void snd_pcm_stream_proc_disconnect(struct snd_pcm_str *pstr) { }
static inline int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) { return 0; }
static inline int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) { return 0; }
#endif /* CONFIG_SND_VERBOSE_PROCFS */
@@ -1094,33 +1100,37 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
int cidx;
guard(mutex)(&register_mutex);
- guard(mutex)(&pcm->open_mutex);
- wake_up(&pcm->open_wait);
- list_del_init(&pcm->list);
-
- for_each_pcm_substream(pcm, cidx, substream) {
- snd_pcm_stream_lock_irq(substream);
- if (substream->runtime) {
- if (snd_pcm_running(substream))
- snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
- /* to be sure, set the state unconditionally */
- __snd_pcm_set_state(substream->runtime,
- SNDRV_PCM_STATE_DISCONNECTED);
- wake_up(&substream->runtime->sleep);
- wake_up(&substream->runtime->tsleep);
+ scoped_guard(mutex, &pcm->open_mutex) {
+ wake_up(&pcm->open_wait);
+ list_del_init(&pcm->list);
+
+ for_each_pcm_substream(pcm, cidx, substream) {
+ snd_pcm_stream_lock_irq(substream);
+ if (substream->runtime) {
+ if (snd_pcm_running(substream))
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
+ /* to be sure, set the state unconditionally */
+ __snd_pcm_set_state(substream->runtime,
+ SNDRV_PCM_STATE_DISCONNECTED);
+ wake_up(&substream->runtime->sleep);
+ wake_up(&substream->runtime->tsleep);
+ }
+ snd_pcm_stream_unlock_irq(substream);
}
- snd_pcm_stream_unlock_irq(substream);
- }
- for_each_pcm_substream(pcm, cidx, substream)
- snd_pcm_sync_stop(substream, false);
+ for_each_pcm_substream(pcm, cidx, substream)
+ snd_pcm_sync_stop(substream, false);
- pcm_call_notify(pcm, n_disconnect);
- for (cidx = 0; cidx < 2; cidx++) {
- if (pcm->streams[cidx].dev)
- snd_unregister_device(pcm->streams[cidx].dev);
- free_chmap(&pcm->streams[cidx]);
+ pcm_call_notify(pcm, n_disconnect);
+ for (cidx = 0; cidx < 2; cidx++) {
+ if (pcm->streams[cidx].dev)
+ snd_unregister_device(pcm->streams[cidx].dev);
+ free_chmap(&pcm->streams[cidx]);
+ }
}
+ /* Proc rundown can run release callbacks which take open_mutex. */
+ for (cidx = 0; cidx < 2; cidx++)
+ snd_pcm_stream_proc_disconnect(&pcm->streams[cidx]);
return 0;
}
diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c
index 4c1e81db376f..ca9dcec19883 100644
--- a/sound/core/seq/seq_ump_client.c
+++ b/sound/core/seq/seq_ump_client.c
@@ -353,12 +353,12 @@ static int create_ump_endpoint_port(struct seq_ump_client *client)
/* release the client resources */
static void seq_ump_client_free(struct seq_ump_client *client)
{
+ snd_ump_detach_seq(client->ump);
cancel_work_sync(&client->group_notify_work);
if (client->seq_client >= 0)
snd_seq_delete_kernel_client(client->seq_client);
- client->ump->seq_ops = NULL;
client->ump->seq_client = NULL;
kfree(client);
@@ -523,7 +523,8 @@ static int snd_seq_ump_probe(struct snd_seq_device *dev)
snd_seq_kernel_client_put(cptr);
ump->seq_client = client;
- ump->seq_ops = &seq_ump_ops;
+ /* Publish the initialized client before callbacks can observe seq_ops. */
+ smp_store_release(&ump->seq_ops, &seq_ump_ops);
return 0;
error:
diff --git a/sound/core/ump.c b/sound/core/ump.c
index f6482a9bfe61..d5a5322082f1 100644
--- a/sound/core/ump.c
+++ b/sound/core/ump.c
@@ -8,6 +8,7 @@
#include <linux/module.h>
#include <linux/export.h>
#include <linux/mm.h>
+#include <linux/srcu.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/ump.h>
@@ -18,6 +19,31 @@
#define ump_info(ump, fmt, args...) dev_info((ump)->core.dev, fmt, ##args)
#define ump_dbg(ump, fmt, args...) dev_dbg((ump)->core.dev, fmt, ##args)
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+DEFINE_STATIC_SRCU(ump_seq_srcu);
+
+static const struct snd_seq_ump_ops *seq_ops_load(struct snd_ump_endpoint *ump)
+{
+ /* Pair with publication after seq_client has been initialized. */
+ return smp_load_acquire(&ump->seq_ops);
+}
+
+/**
+ * snd_ump_detach_seq - stop callbacks to a sequencer binding
+ * @ump: UMP endpoint
+ *
+ * Withdraw the binding and wait for all callbacks already in progress.
+ * The caller can then drain binding work and free the sequencer client.
+ * Must not be called from one of the binding callbacks.
+ */
+void snd_ump_detach_seq(struct snd_ump_endpoint *ump)
+{
+ WRITE_ONCE(ump->seq_ops, NULL);
+ synchronize_srcu(&ump_seq_srcu);
+}
+EXPORT_SYMBOL_GPL(snd_ump_detach_seq);
+#endif
+
static int snd_ump_dev_register(struct snd_rawmidi *rmidi);
static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi);
static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
@@ -348,8 +374,13 @@ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
continue;
ump_handle_stream_msg(ump, ump->input_buf, n);
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
- if (ump->seq_ops)
- ump->seq_ops->input_receive(ump, ump->input_buf, n);
+ scoped_guard(srcu, &ump_seq_srcu) {
+ const struct snd_seq_ump_ops *ops;
+
+ ops = seq_ops_load(ump);
+ if (ops)
+ ops->input_receive(ump, ump->input_buf, n);
+ }
#endif
process_legacy_input(ump, ump->input_buf, n);
}
@@ -559,6 +590,7 @@ static void snd_ump_proc_read(struct snd_info_entry *entry,
/* update dir_bits and active flag for all groups in the client */
void snd_ump_update_group_attrs(struct snd_ump_endpoint *ump)
{
+ static const char separator[] = ", ";
struct snd_ump_block *fb;
struct snd_ump_group *group;
int i;
@@ -598,7 +630,8 @@ void snd_ump_update_group_attrs(struct snd_ump_endpoint *ump)
if (!*fb->info.name)
continue;
if (*group->name)
- strlcat(group->name, ", ", sizeof(group->name));
+ safe_append_string(group->name, sizeof(group->name),
+ separator, sizeof(separator));
safe_append_string(group->name, sizeof(group->name),
fb->info.name, sizeof(fb->info.name));
}
@@ -699,8 +732,12 @@ static void choose_default_protocol(struct snd_ump_endpoint *ump)
static void seq_notify_ep_change(struct snd_ump_endpoint *ump)
{
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
- if (ump->parsed && ump->seq_ops && ump->seq_ops->notify_ep_change)
- ump->seq_ops->notify_ep_change(ump);
+ const struct snd_seq_ump_ops *ops;
+
+ guard(srcu)(&ump_seq_srcu);
+ ops = seq_ops_load(ump);
+ if (ump->parsed && ops && ops->notify_ep_change)
+ ops->notify_ep_change(ump);
#endif
}
@@ -797,8 +834,12 @@ static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump,
static void seq_notify_protocol(struct snd_ump_endpoint *ump)
{
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
- if (ump->seq_ops && ump->seq_ops->switch_protocol)
- ump->seq_ops->switch_protocol(ump);
+ const struct snd_seq_ump_ops *ops;
+
+ guard(srcu)(&ump_seq_srcu);
+ ops = seq_ops_load(ump);
+ if (ops && ops->switch_protocol)
+ ops->switch_protocol(ump);
#endif /* CONFIG_SND_SEQUENCER */
}
@@ -893,8 +934,12 @@ static void seq_notify_fb_change(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb)
{
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
- if (ump->seq_ops && ump->seq_ops->notify_fb_change)
- ump->seq_ops->notify_fb_change(ump, fb);
+ const struct snd_seq_ump_ops *ops;
+
+ guard(srcu)(&ump_seq_srcu);
+ ops = seq_ops_load(ump);
+ if (ops && ops->notify_fb_change)
+ ops->notify_fb_change(ump, fb);
#endif
}
diff --git a/sound/drivers/opl3/opl3_midi.c b/sound/drivers/opl3/opl3_midi.c
index 6d3c5b5a35ff..de611214cdac 100644
--- a/sound/drivers/opl3/opl3_midi.c
+++ b/sound/drivers/opl3/opl3_midi.c
@@ -632,6 +632,21 @@ static void snd_opl3_kill_voice(struct snd_opl3 *opl3, int voice)
}
+void snd_opl3_clear_voices(struct snd_opl3 *opl3)
+{
+ int i;
+
+ guard(spinlock_irqsave)(&opl3->voice_lock);
+ /* A 4-op primary clears its higher-numbered partner's state. */
+ for (i = MAX_OPL3_VOICES - 1; i >= 0; i--) {
+ if (opl3->voices[i].state > 0)
+ snd_opl3_kill_voice(opl3, i);
+ opl3->voices[i].chan = NULL;
+ opl3->voices[i].note_off_check = 0;
+ opl3->voices[i].state = SNDRV_OPL3_ST_OFF;
+ }
+}
+
/*
* Release a note in response to a midi note off.
*/
diff --git a/sound/drivers/opl3/opl3_oss.c b/sound/drivers/opl3/opl3_oss.c
index 6d39b2b77b80..1f01d27955fe 100644
--- a/sound/drivers/opl3/opl3_oss.c
+++ b/sound/drivers/opl3/opl3_oss.c
@@ -41,6 +41,7 @@ static void snd_opl3_oss_free_port(void *private_data)
{
struct snd_opl3 *opl3 = private_data;
+ snd_opl3_clear_voices(opl3);
snd_midi_channel_free_set(opl3->oss_chset);
}
diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c
index b77d8e8f12fb..1004a7df1b8c 100644
--- a/sound/drivers/opl3/opl3_seq.c
+++ b/sound/drivers/opl3/opl3_seq.c
@@ -152,6 +152,7 @@ static void snd_opl3_synth_free_port(void *private_data)
{
struct snd_opl3 *opl3 = private_data;
+ snd_opl3_clear_voices(opl3);
snd_midi_channel_free_set(opl3->chset);
}
@@ -214,6 +215,11 @@ static int snd_opl3_seq_probe(struct snd_seq_device *dev)
spin_lock_init(&opl3->voice_lock);
+ /* Initialize the timer before an attached port can receive events. */
+ timer_setup(&opl3->tlist, snd_opl3_timer_func, 0);
+ spin_lock_init(&opl3->sys_timer_lock);
+ opl3->sys_timer_status = 0;
+
opl3->seq_client = -1;
/* allocate new client */
@@ -232,11 +238,6 @@ static int snd_opl3_seq_probe(struct snd_seq_device *dev)
return err;
}
- /* setup system timer */
- timer_setup(&opl3->tlist, snd_opl3_timer_func, 0);
- spin_lock_init(&opl3->sys_timer_lock);
- opl3->sys_timer_status = 0;
-
#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
snd_opl3_init_seq_oss(opl3, name);
#endif
@@ -258,6 +259,8 @@ static void snd_opl3_seq_remove(struct snd_seq_device *dev)
snd_seq_delete_kernel_client(opl3->seq_client);
opl3->seq_client = -1;
}
+ /* Stop the shared timer after all event callbacks have been drained. */
+ timer_shutdown_sync(&opl3->tlist);
}
static struct snd_seq_driver opl3_seq_driver = {
diff --git a/sound/drivers/opl3/opl3_voice.h b/sound/drivers/opl3/opl3_voice.h
index be9ccca2d952..aa81512d23c9 100644
--- a/sound/drivers/opl3/opl3_voice.h
+++ b/sound/drivers/opl3/opl3_voice.h
@@ -13,6 +13,7 @@ int snd_opl3_synth_use_inc(struct snd_opl3 * opl3);
void snd_opl3_synth_use_dec(struct snd_opl3 * opl3);
int snd_opl3_synth_setup(struct snd_opl3 * opl3);
void snd_opl3_synth_cleanup(struct snd_opl3 * opl3);
+void snd_opl3_clear_voices(struct snd_opl3 *opl3);
/* Prototypes for opl3_midi.c */
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c
index d1b0dff56cd9..66bee8a2ce34 100644
--- a/sound/drivers/pcmtest.c
+++ b/sound/drivers/pcmtest.c
@@ -72,13 +72,32 @@ static u8 playback_capture_test;
static u8 ioctl_reset_test;
static struct dentry *driver_debug_dir;
+static int fill_mode_set(const char *val, const struct kernel_param *kp)
+{
+ short mode;
+ int err;
+
+ err = kstrtos16(val, 0, &mode);
+ if (err)
+ return err;
+ if (mode != FILL_MODE_RAND && mode != FILL_MODE_PAT)
+ return -EINVAL;
+ fill_mode = mode;
+ return 0;
+}
+
+static const struct kernel_param_ops fill_mode_ops = {
+ .set = fill_mode_set,
+ .get = param_get_short,
+};
+
module_param(index, int, 0444);
MODULE_PARM_DESC(index, "Index value for pcmtest soundcard");
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for pcmtest soundcard");
module_param(enable, bool, 0444);
MODULE_PARM_DESC(enable, "Enable pcmtest soundcard.");
-module_param(fill_mode, short, 0600);
+module_param_cb(fill_mode, &fill_mode_ops, &fill_mode, 0600);
MODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)");
module_param(inject_delay, int, 0600);
MODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)");
@@ -693,6 +712,9 @@ static int __init mod_init(void)
{
int err = 0;
+ if (!enable)
+ return -ENODEV;
+
buf_allocated = setup_patt_bufs();
if (!buf_allocated)
return -ENOMEM;
diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c
index 23aa175faedd..23329d5887a5 100644
--- a/sound/hda/codecs/realtek/alc269.c
+++ b/sound/hda/codecs/realtek/alc269.c
@@ -4492,6 +4492,7 @@ enum {
ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT,
ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT,
ALC245_FIXUP_HP_X360_MUTE_LEDS,
+ ALC245_FIXUP_HP_PAVILION_14_EW1XXX_LEDS,
ALC287_FIXUP_THINKPAD_I2S_SPK,
ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD,
ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI,
@@ -7113,6 +7114,12 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC245_FIXUP_HP_GPIO_LED
},
+ [ALC245_FIXUP_HP_PAVILION_14_EW1XXX_LEDS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc245_fixup_hp_mute_led_v2_coefbit,
+ .chained = true,
+ .chain_id = ALC245_FIXUP_HP_GPIO_LED
+ },
[ALC287_FIXUP_THINKPAD_I2S_SPK] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc287_fixup_bind_dacs,
@@ -7869,6 +7876,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8a4f, "HP Victus 15-fa0xxx (MB 8A4F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT),
SND_PCI_QUIRK(0x103c, 0x8a50, "HP Victus 15-fa0xxx (MB 8A50)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT),
SND_PCI_QUIRK(0x103c, 0x8a6b, "HP Pavilion All-in-One Desktop 27-ca1xxx", ALC274_FIXUP_HP_VERBS),
+ SND_PCI_QUIRK(0x103c, 0x89e9, "HP Pavilion All-in-One Desktop 27-ca1xxx", ALC274_FIXUP_HP_VERBS),
SND_PCI_QUIRK(0x103c, 0x8a6e, "HP EDNA 360", ALC287_FIXUP_CS35L41_I2C_4),
SND_PCI_QUIRK(0x103c, 0x8a74, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8a75, "HP ProBook 450 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED),
@@ -7951,6 +7959,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT),
SND_PCI_QUIRK(0x103c, 0x8c2f, "HP Victus 15-fb2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT),
SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT),
+ SND_PCI_QUIRK(0x103c, 0x8c31, "HP Pavilion Plus Laptop 14-ew1xxx", ALC245_FIXUP_HP_PAVILION_14_EW1XXX_LEDS),
SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT),
SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED),
@@ -8060,6 +8069,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO),
+ HDA_CODEC_QUIRK(0x103c, 0x84ee, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK),
SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED),
SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2),
@@ -8214,6 +8224,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x1df3, "ASUS UM5606WA", ALC294_FIXUP_BASS_SPEAKER_15),
SND_PCI_QUIRK(0x1043, 0x1264, "ASUS UM5606KA", ALC294_FIXUP_BASS_SPEAKER_15),
SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2),
+ SND_PCI_QUIRK(0x1043, 0x1e03, "ASUS ExpertBook B3402FVA", ALC256_FIXUP_ASUS_HPE),
SND_PCI_QUIRK(0x1043, 0x1e10, "ASUS VivoBook X507UAR", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x1e3e, "ASUS VivoBook M515DA/X515DAP", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502),
@@ -8226,6 +8237,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x1e8e, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA401),
SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE),
SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X),
+ SND_PCI_QUIRK(0x1043, 0x1ec1, "ASUS X509JB", ALC2XX_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1),
SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401),
diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c
index 31e5fad78b0f..e367cc1c6e78 100644
--- a/sound/hda/common/codec.c
+++ b/sound/hda/common/codec.c
@@ -1807,6 +1807,12 @@ int snd_hda_codec_reset(struct hda_codec *codec)
{
struct hda_bus *bus = codec->bus;
+ /* ASoC: driver unbind frees the card, incl. the hwdep issuing the reset */
+ if (bus->core.ext_ops) {
+ codec_err(codec, "Codec reset is not supported with ASoC\n");
+ return -EOPNOTSUPP;
+ }
+
if (snd_hda_lock_devices(bus) < 0)
return -EBUSY;
diff --git a/sound/hda/common/sysfs.c b/sound/hda/common/sysfs.c
index bedf10b30885..bf65223fde10 100644
--- a/sound/hda/common/sysfs.c
+++ b/sound/hda/common/sysfs.c
@@ -117,6 +117,8 @@ static int clear_codec(struct hda_codec *codec)
int err;
err = snd_hda_codec_reset(codec);
+ if (err == -EOPNOTSUPP)
+ return err;
if (err < 0) {
codec_err(codec, "The codec is being used, can't free.\n");
return err;
@@ -132,6 +134,8 @@ static int reconfig_codec(struct hda_codec *codec)
CLASS(snd_hda_power, pm)(codec);
codec_info(codec, "hda-codec: reconfiguring\n");
err = snd_hda_codec_reset(codec);
+ if (err == -EOPNOTSUPP)
+ return err;
if (err < 0) {
codec_err(codec,
"The codec is being used, can't reconfigure.\n");
diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c
index 6b80b59b8743..29679fe7f401 100644
--- a/sound/hda/controllers/intel.c
+++ b/sound/hda/controllers/intel.c
@@ -1668,6 +1668,7 @@ static const struct snd_pci_quirk msi_deny_list[] = {
SND_PCI_QUIRK(0x1179, 0xfb44, "Toshiba Satellite C870", 0), /* AMD Hudson */
SND_PCI_QUIRK(0x1849, 0x0888, "ASRock", 0), /* Athlon64 X2 + nvidia */
SND_PCI_QUIRK(0xa0a0, 0x0575, "Aopen MZ915-M", 0), /* ICH6 */
+ SND_PCI_QUIRK(0x1d94, 0xc950, "Hygon H34M51A", 1), /* INTx broken */
{}
};
@@ -2329,6 +2330,7 @@ static const struct snd_pci_quirk power_save_denylist[] = {
SND_PCI_QUIRK(0x17aa, 0x5079, "Lenovo Thinkpad E15", 0),
/* https://bugzilla.kernel.org/show_bug.cgi?id=220694 */
SND_PCI_QUIRK(0x103c, 0x8a6b, "HP 89E9", 0),
+ SND_PCI_QUIRK(0x103c, 0x89e9, "HP 89E9", 0),
{}
};
diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c
index 207ce083593f..f0ebe6d45836 100644
--- a/sound/pci/ac97/ac97_codec.c
+++ b/sound/pci/ac97/ac97_codec.c
@@ -1832,36 +1832,39 @@ void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name,
size_t maxlen, int modem)
{
const struct ac97_codec_id *pid;
+ size_t len;
- sprintf(name, "0x%x %c%c%c", id,
- printable(id >> 24),
- printable(id >> 16),
- printable(id >> 8));
pid = look_for_codec_id(snd_ac97_codec_id_vendors, id);
- if (! pid)
+ if (!pid) {
+ snprintf(name, maxlen, "0x%x %c%c%c", id,
+ printable(id >> 24),
+ printable(id >> 16),
+ printable(id >> 8));
return;
+ }
strscpy(name, pid->name, maxlen);
if (ac97 && pid->patch) {
if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
- (! modem && ! (pid->flags & AC97_MODEM_PATCH)))
+ (!modem && !(pid->flags & AC97_MODEM_PATCH)))
pid->patch(ac97);
- }
+ }
+ len = strnlen(name, maxlen);
pid = look_for_codec_id(snd_ac97_codec_ids, id);
if (pid) {
- strlcat(name, " ", maxlen);
- strlcat(name, pid->name, maxlen);
if (pid->mask != 0xffffffff)
- sprintf(name + strlen(name), " rev %u", id & ~pid->mask);
+ snprintf(name + len, maxlen - len, " %s rev %u",
+ pid->name, id & ~pid->mask);
+ else
+ snprintf(name + len, maxlen - len, " %s", pid->name);
if (ac97 && pid->patch) {
if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
- (! modem && ! (pid->flags & AC97_MODEM_PATCH)))
+ (!modem && !(pid->flags & AC97_MODEM_PATCH)))
pid->patch(ac97);
}
} else {
- int l = strlen(name);
- snprintf(name + l, maxlen - l, " id %x", id & 0xff);
+ snprintf(name + len, maxlen - len, " id %x", id & 0xff);
}
}
diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c
index a33817aba17f..d49866d9961e 100644
--- a/sound/pci/emu10k1/emufx.c
+++ b/sound/pci/emu10k1/emufx.c
@@ -2423,12 +2423,12 @@ static int snd_emu10k1_fx8010_open(struct snd_hwdep * hw, struct file *file)
return 0;
}
-static void copy_string(char *dst, const char *src, const char *null, int idx)
+static void copy_string(char *dst, size_t size, const char *src, const char *null, int idx)
{
if (src == NULL)
- sprintf(dst, "%s %02X", null, idx);
+ snprintf(dst, size, "%s %02X", null, idx);
else
- strcpy(dst, src);
+ strscpy(dst, src, size);
}
static void snd_emu10k1_fx8010_info(struct snd_emu10k1 *emu,
@@ -2446,12 +2446,16 @@ static void snd_emu10k1_fx8010_info(struct snd_emu10k1 *emu,
extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask;
extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask;
for (res = 0; res < 16; res++, fxbus++, extin++, extout++) {
- copy_string(info->fxbus_names[res], *fxbus, "FXBUS", res);
- copy_string(info->extin_names[res], extin_mask & (1 << res) ? *extin : NULL, "Unused", res);
- copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
+ copy_string(info->fxbus_names[res], sizeof(info->fxbus_names[res]),
+ *fxbus, "FXBUS", res);
+ copy_string(info->extin_names[res], sizeof(info->extin_names[res]),
+ extin_mask & (1 << res) ? *extin : NULL, "Unused", res);
+ copy_string(info->extout_names[res], sizeof(info->extout_names[res]),
+ extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
}
for (res = 16; res < 32; res++, extout++)
- copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
+ copy_string(info->extout_names[res], sizeof(info->extout_names[res]),
+ extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
info->gpr_controls = emu->fx8010.gpr_count;
}
diff --git a/sound/sh/aica.c b/sound/sh/aica.c
index 078fc7fc08f9..04f80c8c89c0 100644
--- a/sound/sh/aica.c
+++ b/sound/sh/aica.c
@@ -94,7 +94,7 @@ static void spu_memset(u32 toi, u32 what, int length)
local_irq_save(flags);
writel(what, toi + SPU_MEMORY_BASE);
local_irq_restore(flags);
- toi++;
+ toi += sizeof(what);
}
}
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index 9c844c55ad3f..8dd0b3525e4b 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -1642,9 +1642,9 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
err = -ENODEV;
if (err < 0) {
if (!atomic_read(&ep->chip->shutdown))
- usb_audio_err(ep->chip,
- "cannot submit urb %d, error %d: %s\n",
- i, err, usb_error_string(err));
+ usb_audio_err_ratelimited(ep->chip,
+ "cannot submit urb %d, error %d: %s\n",
+ i, err, usb_error_string(err));
goto __error;
}
set_bit(i, &ep->active_mask);
diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c
index 58d8a3cdc8b9..bd8e0ed0ba5d 100644
--- a/sound/usb/misc/ua101.c
+++ b/sound/usb/misc/ua101.c
@@ -285,13 +285,29 @@ static void playback_work(struct work_struct *work)
/* fill packet with data or silence */
urb->iso_frame_desc[0].length =
frames * ua->playback.frame_bytes;
- if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states))
+ if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) {
+ /*
+ * The packet size comes from the capture stream,
+ * i.e. from the device, while the ring buffer can
+ * be as small as two tiny periods. The single wrap
+ * in copy_playback_data() is only valid for
+ * frames <= buffer_size, so copy at most one
+ * buffer and send silence for the rest.
+ */
+ unsigned int copy_frames = min(frames,
+ ua->playback.substream->runtime->buffer_size);
+
do_period_elapsed |= copy_playback_data(&ua->playback,
urb,
- frames);
- else
+ copy_frames);
+ memset(urb->transfer_buffer +
+ copy_frames * ua->playback.frame_bytes, 0,
+ (frames - copy_frames) *
+ ua->playback.frame_bytes);
+ } else {
memset(urb->transfer_buffer, 0,
urb->iso_frame_desc[0].length);
+ }
/* and off you go ... */
err = usb_submit_urb(urb, GFP_ATOMIC);
@@ -320,6 +336,13 @@ static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb,
runtime = stream->substream->runtime;
frame_bytes = stream->frame_bytes;
+ /*
+ * A packet can carry up to max_packet_bytes, the device's
+ * wMaxPacketSize, which may exceed the whole ring buffer; keep what
+ * fits so the single wrap below stays valid.
+ */
+ if (unlikely(frames > runtime->buffer_size))
+ frames = runtime->buffer_size;
dest = runtime->dma_area + stream->buffer_pos * frame_bytes;
if (stream->buffer_pos + frames <= runtime->buffer_size) {
memcpy(dest, urb->transfer_buffer, frames * frame_bytes);
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index 682b6c1fe76b..c2f7b08607ac 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -1343,6 +1343,18 @@ static void retire_capture_urb(struct snd_usb_substream *subs,
bytes -= adj;
subs->stream_offset_adj -= adj;
}
+ /*
+ * A packet can be as large as the endpoint's maxpacksize, which
+ * the device chooses, while the ring buffer can be as small as
+ * two 64-byte periods. The hwptr_done wrap below handles one
+ * wrap only, so never take more than one buffer from a packet.
+ */
+ if (unlikely(bytes > subs->buffer_bytes)) {
+ dev_warn_ratelimited(&subs->dev->dev,
+ "%u-byte packet exceeds %u-byte buffer, truncating\n",
+ bytes, subs->buffer_bytes);
+ bytes = subs->buffer_bytes;
+ }
frames = bytes / stride;
if (!subs->txfr_quirk)
bytes = frames * stride;
@@ -1553,8 +1565,14 @@ static int prepare_playback_urb(struct snd_usb_substream *subs,
for (i = 0; i < ctx->packets; i++) {
counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail);
+ /*
+ * Bound by the ring buffer as well as by the URB:
+ * copy_to_urb() wraps hwptr_done once, so one URB must
+ * never carry more than the whole buffer.
+ */
if (counts < 0 ||
- (frames + counts) * stride > ctx->buffer_size)
+ (frames + counts) * stride > ctx->buffer_size ||
+ (frames + counts) * stride > subs->buffer_bytes)
break;
/* set up descriptor */
urb->iso_frame_desc[i].offset = frames * stride;
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index 6343a50fb273..4793fd1f85fe 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -2360,6 +2360,69 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
{
+ /*
+ * Novation UltraNova: vendor-specific class descriptors, fixed
+ * audio and MIDI layouts. Only the audio and MIDI interfaces
+ * (0 and 1) are grabbed; the vendor control interfaces (2/3)
+ * are left unbound for userspace access.
+ */
+ USB_DEVICE_INTERFACE_NUMBER(0x1235, 0x0011, 0),
+ QUIRK_DRIVER_INFO {
+ .vendor_name = "Novation",
+ .product_name = "UltraNova",
+ QUIRK_DATA_COMPOSITE {
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ /* playback */
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC |
+ USB_ENDPOINT_SYNC_SYNC,
+ .maxpacksize = 392,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .nr_rates = 2,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000
+ }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ /* capture */
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 2,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x82,
+ .ep_idx = 1,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC,
+ .maxpacksize = 294,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .nr_rates = 2,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000
+ }
+ }
+ },
+ { QUIRK_DATA_RAW_BYTES(1) },
+ QUIRK_COMPOSITE_END
+ }
+ }
+},
+{
USB_DEVICE(0x1235, 0x001e),
QUIRK_DRIVER_INFO {
/* .vendor_name = "Novation", */
@@ -3005,6 +3068,7 @@ YAMAHA_DEVICE(0x7010, "UB99"),
.rates = SNDRV_PCM_RATE_96000,
.ep_attr = USB_ENDPOINT_XFER_ISOC |
USB_ENDPOINT_SYNC_ASYNC,
+ .datainterval = 1,
.rate_min = 96000,
.rate_max = 96000,
.nr_rates = 1,
@@ -3027,6 +3091,7 @@ YAMAHA_DEVICE(0x7010, "UB99"),
.rates = SNDRV_PCM_RATE_96000,
.ep_attr = USB_ENDPOINT_XFER_ISOC |
USB_ENDPOINT_SYNC_ASYNC,
+ .datainterval = 1,
.rate_min = 96000,
.rate_max = 96000,
.nr_rates = 1,
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 43eea724779a..4ec1f2a0b359 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -1794,7 +1794,7 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev,
/* the quirk seems breaking on some Behringer devices */
switch (id) {
case USB_ID(0x1397, 0x0508): /* UMC204HD */
- case USB_ID(0x1397, 0x0509): /* UMC204HD */
+ case USB_ID(0x1397, 0x0509): /* UMC404HD */
return 0;
}
return snd_usb_cm1a_boot_quirk(dev);
@@ -2496,6 +2496,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
DEVICE_FLG(0x0124, 0x0c21, /* Generic USB Headphone */
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
+ DEVICE_FLG(0x0124, 0x0c36, /* RYDZ Technology USB Headphone */
+ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
DEVICE_FLG(0x03f0, 0x0c56, /* HP Elite x3 Lap Dock */
QUIRK_FLAG_FIXED_RATE),
DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */
@@ -2659,6 +2661,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
DEVICE_FLG(0x0d8c, 0x0102, /* C-Media CM6206 */
QUIRK_FLAG_GET_SAMPLE_RATE),
+ DEVICE_FLG(0x0db0, 0xa073, /* MSI MAG X570S Tomahawk/Torpedo Max (ALC4080) */
+ QUIRK_FLAG_SKIP_IFACE_SETUP),
DEVICE_FLG(0x0e0b, 0xfa01, /* Feaulle Rainbow */
QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */