summaryrefslogtreecommitdiffstats
path: root/sound/core/control.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2021-05-23 11:09:16 +0200
committerTakashi Iwai <tiwai@suse.de>2021-05-25 08:48:28 +0200
commite94fdbd7b25d87e64688bb109e2c550217a4c879 (patch)
tree03bb5dcb64d6a3db2f3c9a5c25b125b0806a281b /sound/core/control.c
parentALSA: core: Use READ_ONCE() / WRITE_ONCE() for power state change (diff)
downloadlinux-e94fdbd7b25d87e64688bb109e2c550217a4c879.tar.xz
linux-e94fdbd7b25d87e64688bb109e2c550217a4c879.zip
ALSA: control: Track in-flight control read/write/tlv accesses
Although the power state check is performed in various places (e.g. at the entrance of quite a few ioctls), there can be still some pending tasks that already went into the ioctl handler or other ops, and those may access the hardware even after the power state check. For example, kcontrol access ioctl paths that call info/get/put callbacks may update the hardware registers. If a system wants to assure the free from such hw access (like the case of PCI rescan feature we're going to implement in future), this situation must be avoided, and we have to sync such in-flight tasks finishing beforehand. For that purpose, this patch introduces a few new things in core code: - A refcount, power_ref, and a wait queue, power_ref_sleep, to the card object - A few new helpers, snd_power_ref(), snd_power_unref(), snd_power_ref_and_wait(), and snd_power_sync_ref() In the code paths that call kctl info/read/write/tlv ops, we check the power state with the newly introduced snd_power_ref_and_wait(). This function also takes the card.power_ref refcount for tracking this in-flight task. Once after the access finishes, snd_power_unref() is called to released the refcount in return. So the driver can sync via snd_power_sync_ref() assuring that all in-flight tasks have been finished. As of this patch, snd_power_sync_ref() is called only at snd_card_disconnect(), but it'll be used in other places in future. Note that atomic_t is used for power_ref intentionally instead of refcount_t. It's because of the design of refcount_t type; refcount_t cannot be zero-based, and it cannot do dec_and_test() call for multiple times, hence it's not suitable for our purpose. Also, this patch changes snd_power_wait() to accept only SNDRV_CTL_POWER_D0, which is the only value that makes sense. In later patch, the snd_power_wait() calls will be cleaned up. Reviewed-by: Jaroslav Kysela <perex@perex.cz> Link: https://lore.kernel.org/r/20210523090920.15345-3-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/core/control.c')
-rw-r--r--sound/core/control.c23
1 files changed, 18 insertions, 5 deletions
diff --git a/sound/core/control.c b/sound/core/control.c
index 498e3701514a..638da34605ba 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -995,7 +995,10 @@ static int __snd_ctl_elem_info(struct snd_card *card,
#ifdef CONFIG_SND_DEBUG
info->access = 0;
#endif
- result = kctl->info(kctl, info);
+ result = snd_power_ref_and_wait(card);
+ if (!result)
+ result = kctl->info(kctl, info);
+ snd_power_unref(card);
if (result >= 0) {
snd_BUG_ON(info->access);
index_offset = snd_ctl_get_ioff(kctl, &info->id);
@@ -1088,7 +1091,10 @@ static int snd_ctl_elem_read(struct snd_card *card,
if (!snd_ctl_skip_validation(&info))
fill_remaining_elem_value(control, &info, pattern);
- ret = kctl->get(kctl, control);
+ ret = snd_power_ref_and_wait(card);
+ if (!ret)
+ ret = kctl->get(kctl, control);
+ snd_power_unref(card);
if (ret < 0)
return ret;
if (!snd_ctl_skip_validation(&info) &&
@@ -1154,7 +1160,10 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
}
snd_ctl_build_ioff(&control->id, kctl, index_offset);
- result = kctl->put(kctl, control);
+ result = snd_power_ref_and_wait(card);
+ if (!result)
+ result = kctl->put(kctl, control);
+ snd_power_unref(card);
if (result < 0) {
up_write(&card->controls_rwsem);
return result;
@@ -1669,7 +1678,7 @@ static int call_tlv_handler(struct snd_ctl_file *file, int op_flag,
{SNDRV_CTL_TLV_OP_CMD, SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND},
};
struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)];
- int i;
+ int i, ret;
/* Check support of the request for this element. */
for (i = 0; i < ARRAY_SIZE(pairs); ++i) {
@@ -1687,7 +1696,11 @@ static int call_tlv_handler(struct snd_ctl_file *file, int op_flag,
vd->owner != NULL && vd->owner != file)
return -EPERM;
- return kctl->tlv.c(kctl, op_flag, size, buf);
+ ret = snd_power_ref_and_wait(file->card);
+ if (!ret)
+ ret = kctl->tlv.c(kctl, op_flag, size, buf);
+ snd_power_unref(file->card);
+ return ret;
}
static int read_tlv_buf(struct snd_kcontrol *kctl, struct snd_ctl_elem_id *id,