Hi,
On Thu, 6 Aug 2020 at 22:47, Takashi Sakamoto o-takashi@sakamocchi.jp wrote:
Hi,
As long as I know, neither tools in alsa-utils/alsa-tools nor pulseaudio use the lock mechanism. In short, you are the first person to address to the issue. Thanks for your patience since the first post in 2015.
As for the compare-and-swap part, it's just a plus. Not that "double-looping" for *each* channel doesn't work. It just again seems silly and primitive (and was once confusing to me).
I prepare a rough kernel patch abount the compare-and-swap idea for our discussion. The compare-and-swap is done under lock acquisition of 'struct snd_card.controls_rwsem', therefore many types of operations to control element (e.g. read as well) get affects. This idea works well at first when alsa-lib supports corresponding API and userspace applications uses it. Therefore we need more work than changing just in userspace.
But in my opinion, if things can be solved just in userspace, it should be done with no change in kernel space.
I didn't know much about programming or so back then (even by now I know only a little) when I first noticed the problem, so I just avoided using amixer / my volume wheel / parts of pulse and resorted to alsamixer. For some reason the race didn't *appear to* exist with onboard or USB audio but only my Xonar STX (maybe because volume adjustments take a bit more time with it), so for a long time I thought it's some delicate bug in the oxygen/xonar driver that I failed to identify. Only until very recently it gets back to my head and becomes clear to me that it's a general design flaw in ALSA.
Just to confirm, does SNDRV_CTL_IOCTL_ELEM_LOCK currently prevent get()/read? Or is it just a write lock as I thought? If that's the case, and if the compare-and-swap approach doesn't involve a lot of changes (in all the kernel drivers, for example), I'd say we better start moving to something neat than using something which is less so and wasn't really ever adopted anyway.
======== 8< --------
From 54832d11b9056da2883d6edfdccaab76d8b08a5c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Thu, 6 Aug 2020 19:34:55 +0900 Subject: [PATCH] ALSA: control: add new ioctl request for compare_and_swap operation to element value
This is a rough implementation as a solution for an issue below. This is not tested yet. The aim of this patch is for further discussion.
Typical userspace applications decide write value to control element according to value read preliminarily. In the case, if multiple applications begin a pair of read and write operations simultaneously, the result is not deterministic without any lock mechanism. Although ALSA control core has lock/unlock mechanism to a control element for the case, the mechanism is not so popular. The mechanism neither not used by tools in alsa-utils, alsa-tools, nor PulseAudio, at least.
This commit is an attempt to solve the case by introducing new ioctl request. The request is a part of 'compare and swap' mechanism. The applications should pass ioctl argument with a pair of old and new value of the control element. ALSA control core read current value and compare it to the old value under acquisition of lock. If they are the same, the new value is going to be written at last.
Reported-by: Tom Yan tom.ty89@gmail.com Reference: https://lore.kernel.org/alsa-devel/CAGnHSEkV9cpWoQKP1mT7RyqyTvGrZu045k=3W45J... Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
include/uapi/sound/asound.h | 6 ++++ sound/core/control.c | 56 +++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+)
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 535a7229e1d9..ff8d5416458d 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -1074,6 +1074,11 @@ struct snd_ctl_tlv { unsigned int tlv[0]; /* first TLV */ };
+struct snd_ctl_elem_compare_and_swap {
struct snd_ctl_elem_value old;
struct snd_ctl_elem_value new;
+};
#define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) @@ -1089,6 +1094,7 @@ struct snd_ctl_tlv { #define SNDRV_CTL_IOCTL_TLV_READ _IOWR('U', 0x1a, struct snd_ctl_tlv) #define SNDRV_CTL_IOCTL_TLV_WRITE _IOWR('U', 0x1b, struct snd_ctl_tlv) #define SNDRV_CTL_IOCTL_TLV_COMMAND _IOWR('U', 0x1c, struct snd_ctl_tlv) +#define SNDRV_CTL_IOCTL_ELEM_COPARE_AND_SWAP _IOWR('U', 0x1d, struct snd_ctl_elem_compare_and_swap) #define SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE _IOWR('U', 0x20, int) #define SNDRV_CTL_IOCTL_HWDEP_INFO _IOR('U', 0x21, struct snd_hwdep_info) #define SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE _IOR('U', 0x30, int) diff --git a/sound/core/control.c b/sound/core/control.c index aa0c0cf182af..0ac1f7c489be 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1684,6 +1684,60 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, return -ENXIO; }
+static int snd_ctl_elem_compare_and_swap(struct snd_ctl_file *ctl_file,
struct snd_ctl_elem_compare_and_swap *cas)
+{
struct snd_card *card = ctl_file->card;
// TODO: too much use on kernel stack...
struct snd_ctl_elem_value curr;
struct snd_ctl_elem_info info;
unsigned int unit_size;
int err;
info.id = cas->old.id;
err = snd_ctl_elem_info(ctl_file, &info);
if (err < 0)
return err;
if (info.type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || info.type > SNDRV_CTL_ELEM_TYPE_INTEGER64)
return -ENXIO;
unit_size = value_sizes[info.type];
curr.id = cas->old.id;
err = snd_ctl_elem_read(card, &curr);
if (err < 0)
return err;
// Compare.
if (memcmp(&cas->old.value, &curr.value, unit_size * info.count) != 0)
return -EAGAIN;
// Swap.
return snd_ctl_elem_write(card, ctl_file, &cas->new);
+}
+static int snd_ctl_elem_compare_and_swap_user(struct snd_ctl_file *ctl_file,
struct snd_ctl_elem_compare_and_swap __user *argp)
+{
struct snd_card *card = ctl_file->card;
struct snd_ctl_elem_compare_and_swap *cas;
int err;
cas = memdup_user(argp, sizeof(*cas));
if (IS_ERR(cas))
return PTR_ERR(cas);
err = snd_power_wait(card, SNDRV_CTL_POWER_D0);
if (err < 0)
goto end;
down_read(&card->controls_rwsem);
err = snd_ctl_elem_compare_and_swap(ctl_file, cas);
up_read(&card->controls_rwsem);
+end:
kfree(cas);
return err;
+}
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct snd_ctl_file *ctl; @@ -1737,6 +1791,8 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD); up_write(&ctl->card->controls_rwsem); return err;
case SNDRV_CTL_IOCTL_ELEM_COPARE_AND_SWAP:
return snd_ctl_elem_compare_and_swap_user(ctl, argp); case SNDRV_CTL_IOCTL_POWER: return -ENOPROTOOPT; case SNDRV_CTL_IOCTL_POWER_STATE:
-- 2.25.1
======== 8< --------
Thanks
Takashi Sakamoto