From: Takashi Iwai Subject: ALSA: hda - Use own workqueue Patch-mainline: 2.6.29-rc2 References: bnc#502733 snd-hda-intel driver used schedule_work() fot the delayed DMA pointer updates, but this has several potential problems: - it may block other eventsd works longer - it may deadlock when probing fails and flush_scheduled_work() is called during probe callback (as probe callback itself could be invoked from eventd) This patch adds an own workq for each driver instance to solve these problems. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_beep.c | 1 - sound/pci/hda/hda_codec.c | 24 ++++++++++++++++++------ sound/pci/hda/hda_codec.h | 2 ++ sound/pci/hda/hda_intel.c | 6 +++--- 4 files changed, 23 insertions(+), 10 deletions(-) --- a/sound/pci/hda/hda_beep.c +++ b/sound/pci/hda/hda_beep.c @@ -134,7 +134,6 @@ struct hda_beep *beep = codec->beep; if (beep) { cancel_work_sync(&beep->beep_work); - flush_scheduled_work(); input_unregister_device(beep->dev); kfree(beep); --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -310,7 +310,7 @@ unsol->queue[wp] = res; unsol->queue[wp + 1] = res_ex; - schedule_work(&unsol->work); + queue_work(bus->workq, &unsol->work); return 0; } @@ -373,15 +373,17 @@ if (!bus) return 0; - if (bus->unsol) { - flush_scheduled_work(); + if (bus->workq) + flush_workqueue(bus->workq); + if (bus->unsol) kfree(bus->unsol); - } list_for_each_entry_safe(codec, n, &bus->codec_list, list) { snd_hda_codec_free(codec); } if (bus->ops.private_free) bus->ops.private_free(bus); + if (bus->workq) + destroy_workqueue(bus->workq); kfree(bus); return 0; } @@ -431,6 +433,16 @@ mutex_init(&bus->cmd_mutex); INIT_LIST_HEAD(&bus->codec_list); + snprintf(bus->workq_name, sizeof(bus->workq_name), + "hd-audio%d", card->number); + bus->workq = create_singlethread_workqueue(bus->workq_name); + if (!bus->workq) { + snd_printk(KERN_ERR "cannot create workqueue %s\n", + bus->workq_name); + kfree(bus); + return -ENOMEM; + } + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops); if (err < 0) { snd_hda_bus_free(bus); @@ -564,7 +576,7 @@ return; #ifdef CONFIG_SND_HDA_POWER_SAVE cancel_delayed_work(&codec->power_work); - flush_scheduled_work(); + flush_workqueue(codec->bus->workq); #endif list_del(&codec->list); codec->bus->caddr_tbl[codec->addr] = NULL; @@ -2533,7 +2545,7 @@ return; if (power_save) { codec->power_transition = 1; /* avoid reentrance */ - schedule_delayed_work(&codec->power_work, + queue_delayed_work(codec->bus->workq, &codec->power_work, msecs_to_jiffies(power_save * 1000)); } } --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -580,6 +580,8 @@ /* unsolicited event queue */ struct hda_bus_unsolicited *unsol; + char workq_name[16]; + struct workqueue_struct *workq; /* common workqueue for codecs */ struct snd_info_entry *proc; --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -986,10 +986,11 @@ spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(azx_dev->substream); spin_lock(&chip->reg_lock); - } else { + } else if (chip->bus && chip->bus->workq) { /* bogus IRQ, process it later */ azx_dev->irq_pending = 1; - schedule_work(&chip->irq_pending_work); + queue_work(chip->bus->workq, + &chip->irq_pending_work); } } } @@ -1708,7 +1709,6 @@ for (i = 0; i < chip->num_streams; i++) chip->azx_dev[i].irq_pending = 0; spin_unlock_irq(&chip->reg_lock); - flush_scheduled_work(); } static struct snd_pcm_ops azx_pcm_ops = {