Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ALSA: ak411x: Fix stall in work callback

When ak4114 work calls its callback and the callback invokes
ak4114_reinit(), it stalls due to flush_delayed_work(). For avoiding
this, control the reentrance by introducing a refcount. Also
flush_delayed_work() is replaced with cancel_delayed_work_sync().

The exactly same bug is present in ak4113.c and fixed as well.

Reported-by: Pavel Hofman <pavel.hofman@ivitera.com>
Acked-by: Jaroslav Kysela <perex@perex.cz>
Tested-by: Pavel Hofman <pavel.hofman@ivitera.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

+18 -21
+1 -1
include/sound/ak4113.h
··· 286 286 ak4113_write_t *write; 287 287 ak4113_read_t *read; 288 288 void *private_data; 289 - unsigned int init:1; 289 + atomic_t wq_processing; 290 290 spinlock_t lock; 291 291 unsigned char regmap[AK4113_WRITABLE_REGS]; 292 292 struct snd_kcontrol *kctls[AK4113_CONTROLS];
+1 -1
include/sound/ak4114.h
··· 168 168 ak4114_write_t * write; 169 169 ak4114_read_t * read; 170 170 void * private_data; 171 - unsigned int init: 1; 171 + atomic_t wq_processing; 172 172 spinlock_t lock; 173 173 unsigned char regmap[6]; 174 174 unsigned char txcsb[5];
+8 -9
sound/i2c/other/ak4113.c
··· 56 56 57 57 static void snd_ak4113_free(struct ak4113 *chip) 58 58 { 59 - chip->init = 1; /* don't schedule new work */ 60 - mb(); 59 + atomic_inc(&chip->wq_processing); /* don't schedule new work */ 61 60 cancel_delayed_work_sync(&chip->work); 62 61 kfree(chip); 63 62 } ··· 88 89 chip->write = write; 89 90 chip->private_data = private_data; 90 91 INIT_DELAYED_WORK(&chip->work, ak4113_stats); 92 + atomic_set(&chip->wq_processing, 0); 91 93 92 94 for (reg = 0; reg < AK4113_WRITABLE_REGS ; reg++) 93 95 chip->regmap[reg] = pgm[reg]; ··· 139 139 140 140 void snd_ak4113_reinit(struct ak4113 *chip) 141 141 { 142 - chip->init = 1; 143 - mb(); 144 - flush_delayed_work(&chip->work); 142 + if (atomic_inc_return(&chip->wq_processing) == 1) 143 + cancel_delayed_work_sync(&chip->work); 145 144 ak4113_init_regs(chip); 146 145 /* bring up statistics / event queing */ 147 - chip->init = 0; 148 - if (chip->kctls[0]) 146 + if (atomic_dec_and_test(&chip->wq_processing)) 149 147 schedule_delayed_work(&chip->work, HZ / 10); 150 148 } 151 149 EXPORT_SYMBOL_GPL(snd_ak4113_reinit); ··· 630 632 { 631 633 struct ak4113 *chip = container_of(work, struct ak4113, work.work); 632 634 633 - if (!chip->init) 635 + if (atomic_inc_return(&chip->wq_processing) == 1) 634 636 snd_ak4113_check_rate_and_errors(chip, chip->check_flags); 635 637 636 - schedule_delayed_work(&chip->work, HZ / 10); 638 + if (atomic_dec_and_test(&chip->wq_processing)) 639 + schedule_delayed_work(&chip->work, HZ / 10); 637 640 }
+8 -10
sound/i2c/other/ak4114.c
··· 66 66 67 67 static void snd_ak4114_free(struct ak4114 *chip) 68 68 { 69 - chip->init = 1; /* don't schedule new work */ 70 - mb(); 69 + atomic_inc(&chip->wq_processing); /* don't schedule new work */ 71 70 cancel_delayed_work_sync(&chip->work); 72 71 kfree(chip); 73 72 } ··· 99 100 chip->write = write; 100 101 chip->private_data = private_data; 101 102 INIT_DELAYED_WORK(&chip->work, ak4114_stats); 103 + atomic_set(&chip->wq_processing, 0); 102 104 103 105 for (reg = 0; reg < 6; reg++) 104 106 chip->regmap[reg] = pgm[reg]; ··· 152 152 153 153 void snd_ak4114_reinit(struct ak4114 *chip) 154 154 { 155 - chip->init = 1; 156 - mb(); 157 - flush_delayed_work(&chip->work); 155 + if (atomic_inc_return(&chip->wq_processing) == 1) 156 + cancel_delayed_work_sync(&chip->work); 158 157 ak4114_init_regs(chip); 159 158 /* bring up statistics / event queing */ 160 - chip->init = 0; 161 - if (chip->kctls[0]) 159 + if (atomic_dec_and_test(&chip->wq_processing)) 162 160 schedule_delayed_work(&chip->work, HZ / 10); 163 161 } 164 162 ··· 610 612 { 611 613 struct ak4114 *chip = container_of(work, struct ak4114, work.work); 612 614 613 - if (!chip->init) 615 + if (atomic_inc_return(&chip->wq_processing) == 1) 614 616 snd_ak4114_check_rate_and_errors(chip, chip->check_flags); 615 - 616 - schedule_delayed_work(&chip->work, HZ / 10); 617 + if (atomic_dec_and_test(&chip->wq_processing)) 618 + schedule_delayed_work(&chip->work, HZ / 10); 617 619 } 618 620 619 621 EXPORT_SYMBOL(snd_ak4114_create);