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/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 04ef66298943..d5a5322082f1 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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); } @@ -701,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 } @@ -799,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 */ } @@ -895,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 }