diff options
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/Kconfig | 28 | ||||
-rw-r--r-- | sound/core/init.c | 1 | ||||
-rw-r--r-- | sound/core/memalloc.c | 25 | ||||
-rw-r--r-- | sound/core/pcm_dmaengine.c | 83 | ||||
-rw-r--r-- | sound/core/pcm_local.h | 7 | ||||
-rw-r--r-- | sound/core/pcm_memory.c | 88 | ||||
-rw-r--r-- | sound/core/pcm_native.c | 68 | ||||
-rw-r--r-- | sound/core/seq/seq_timer.c | 18 | ||||
-rw-r--r-- | sound/core/timer.c | 182 |
9 files changed, 366 insertions, 134 deletions
diff --git a/sound/core/Kconfig b/sound/core/Kconfig index 4ee79ad6ae22..4044c42d8595 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -72,11 +72,11 @@ config SND_PCM_OSS config SND_PCM_OSS_PLUGINS bool "OSS PCM (digital audio) API - Include plugin system" depends on SND_PCM_OSS - default y + default y help - If you disable this option, the ALSA's OSS PCM API will not - support conversion of channels, formats and rates. It will - behave like most of new OSS/Free drivers in 2.4/2.6 kernels. + If you disable this option, the ALSA's OSS PCM API will not + support conversion of channels, formats and rates. It will + behave like most of new OSS/Free drivers in 2.4/2.6 kernels. config SND_PCM_TIMER bool "PCM timer interface" if EXPERT @@ -128,13 +128,13 @@ config SND_SUPPORT_OLD_API or older). config SND_PROC_FS - bool "Sound Proc FS Support" if EXPERT - depends on PROC_FS - default y - help - Say 'N' to disable Sound proc FS, which may reduce code size about - 9KB on x86_64 platform. - If unsure say Y. + bool "Sound Proc FS Support" if EXPERT + depends on PROC_FS + default y + help + Say 'N' to disable Sound proc FS, which may reduce code size about + 9KB on x86_64 platform. + If unsure say Y. config SND_VERBOSE_PROCFS bool "Verbose procfs contents" @@ -142,8 +142,8 @@ config SND_VERBOSE_PROCFS default y help Say Y here to include code for verbose procfs contents (provides - useful information to developers when a problem occurs). On the - other side, it makes the ALSA subsystem larger. + useful information to developers when a problem occurs). On the + other side, it makes the ALSA subsystem larger. config SND_VERBOSE_PRINTK bool "Verbose printk" @@ -164,7 +164,7 @@ config SND_DEBUG_VERBOSE depends on SND_DEBUG help Say Y here to enable extra-verbose debugging messages. - + Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages. So, say Y only if you are ready to be annoyed. diff --git a/sound/core/init.c b/sound/core/init.c index db99b7fad6ad..faa9f03c01ca 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -215,6 +215,7 @@ int snd_card_new(struct device *parent, int idx, const char *xid, init_waitqueue_head(&card->power_sleep); #endif init_waitqueue_head(&card->remove_sleep); + card->sync_irq = -1; device_initialize(&card->card_dev); card->card_dev.parent = parent; diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 6850d13aa98c..a83553fbedf0 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -10,6 +10,7 @@ #include <linux/mm.h> #include <linux/dma-mapping.h> #include <linux/genalloc.h> +#include <linux/vmalloc.h> #ifdef CONFIG_X86 #include <asm/set_memory.h> #endif @@ -99,6 +100,14 @@ static void snd_free_dev_iram(struct snd_dma_buffer *dmab) * */ +static inline gfp_t snd_mem_get_gfp_flags(const struct device *dev, + gfp_t default_gfp) +{ + if (!dev) + return default_gfp; + else + return (__force gfp_t)(unsigned long)dev; +} /** * snd_dma_alloc_pages - allocate the buffer area according to the given type @@ -116,20 +125,25 @@ static void snd_free_dev_iram(struct snd_dma_buffer *dmab) int snd_dma_alloc_pages(int type, struct device *device, size_t size, struct snd_dma_buffer *dmab) { + gfp_t gfp; + if (WARN_ON(!size)) return -ENXIO; if (WARN_ON(!dmab)) return -ENXIO; - if (WARN_ON(!device)) - return -EINVAL; dmab->dev.type = type; dmab->dev.dev = device; dmab->bytes = 0; switch (type) { case SNDRV_DMA_TYPE_CONTINUOUS: - dmab->area = alloc_pages_exact(size, - (__force gfp_t)(unsigned long)device); + gfp = snd_mem_get_gfp_flags(device, GFP_KERNEL); + dmab->area = alloc_pages_exact(size, gfp); + dmab->addr = 0; + break; + case SNDRV_DMA_TYPE_VMALLOC: + gfp = snd_mem_get_gfp_flags(device, GFP_KERNEL | __GFP_HIGHMEM); + dmab->area = __vmalloc(size, gfp, PAGE_KERNEL); dmab->addr = 0; break; #ifdef CONFIG_HAS_DMA @@ -215,6 +229,9 @@ void snd_dma_free_pages(struct snd_dma_buffer *dmab) case SNDRV_DMA_TYPE_CONTINUOUS: free_pages_exact(dmab->area, dmab->bytes); break; + case SNDRV_DMA_TYPE_VMALLOC: + vfree(dmab->area); + break; #ifdef CONFIG_HAS_DMA #ifdef CONFIG_GENERIC_ALLOCATOR case SNDRV_DMA_TYPE_DEV_IRAM: diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 89a05926ac73..5749a8a49784 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -369,4 +369,87 @@ int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream) } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan); +/** + * snd_dmaengine_pcm_refine_runtime_hwparams - Refine runtime hw params + * @substream: PCM substream + * @dma_data: DAI DMA data + * @hw: PCM hw params + * @chan: DMA channel to use for data transfers + * + * Returns 0 on success, a negative error code otherwise. + * + * This function will query DMA capability, then refine the pcm hardware + * parameters. + */ +int snd_dmaengine_pcm_refine_runtime_hwparams( + struct snd_pcm_substream *substream, + struct snd_dmaengine_dai_dma_data *dma_data, + struct snd_pcm_hardware *hw, + struct dma_chan *chan) +{ + struct dma_slave_caps dma_caps; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + snd_pcm_format_t i; + int ret = 0; + + if (!hw || !chan || !dma_data) + return -EINVAL; + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause && dma_caps.cmd_resume) + hw->info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + hw->info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = SNDRV_PCM_FORMAT_FIRST; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + hw->formats |= pcm_format_to_bits(i); + break; + default: + /* Unsupported types */ + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_refine_runtime_hwparams); + MODULE_LICENSE("GPL"); diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h index 1161ab2d6a5b..384efd002984 100644 --- a/sound/core/pcm_local.h +++ b/sound/core/pcm_local.h @@ -67,4 +67,11 @@ static inline void snd_pcm_timer_done(struct snd_pcm_substream *substream) {} void __snd_pcm_xrun(struct snd_pcm_substream *substream); void snd_pcm_group_init(struct snd_pcm_group *group); +#ifdef CONFIG_SND_DMA_SGBUF +struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, + unsigned long offset); +#endif + +#define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime) + #endif /* __SOUND_CORE_PCM_LOCAL_H */ diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index 7600dcdf5fd4..d4702cc1d376 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -15,6 +15,7 @@ #include <sound/pcm.h> #include <sound/info.h> #include <sound/initval.h> +#include "pcm_local.h" static int preallocate_dma = 1; module_param(preallocate_dma, int, 0444); @@ -193,9 +194,15 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream) /* * pre-allocate the buffer and create a proc file for the substream */ -static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream, - size_t size, size_t max) +static void preallocate_pages(struct snd_pcm_substream *substream, + int type, struct device *data, + size_t size, size_t max, bool managed) { + if (snd_BUG_ON(substream->dma_buffer.dev.type)) + return; + + substream->dma_buffer.dev.type = type; + substream->dma_buffer.dev.dev = data; if (size > 0 && preallocate_dma && substream->number < maximum_substreams) preallocate_pcm_pages(substream, size); @@ -203,9 +210,25 @@ static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream, if (substream->dma_buffer.bytes > 0) substream->buffer_bytes_max = substream->dma_buffer.bytes; substream->dma_max = max; - preallocate_info_init(substream); + if (max > 0) + preallocate_info_init(substream); + if (managed) + substream->managed_buffer_alloc = 1; } +static void preallocate_pages_for_all(struct snd_pcm *pcm, int type, + void *data, size_t size, size_t max, + bool managed) +{ + struct snd_pcm_substream *substream; + int stream; + + for (stream = 0; stream < 2; stream++) + for (substream = pcm->streams[stream].substream; substream; + substream = substream->next) + preallocate_pages(substream, type, data, size, max, + managed); +} /** * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type @@ -221,9 +244,7 @@ void snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream, int type, struct device *data, size_t size, size_t max) { - substream->dma_buffer.dev.type = type; - substream->dma_buffer.dev.dev = data; - snd_pcm_lib_preallocate_pages1(substream, size, max); + preallocate_pages(substream, type, data, size, max, false); } EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages); @@ -242,17 +263,57 @@ void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, int type, void *data, size_t size, size_t max) { - struct snd_pcm_substream *substream; - int stream; - - for (stream = 0; stream < 2; stream++) - for (substream = pcm->streams[stream].substream; substream; substream = substream->next) - snd_pcm_lib_preallocate_pages(substream, type, data, size, max); + preallocate_pages_for_all(pcm, type, data, size, max, false); } EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all); -#ifdef CONFIG_SND_DMA_SGBUF /** + * snd_pcm_set_managed_buffer - set up buffer management for a substream + * @substream: the pcm substream instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation for the given DMA buffer type, and set the managed + * buffer allocation mode to the given substream. + * In this mode, PCM core will allocate a buffer automatically before PCM + * hw_params ops call, and release the buffer after PCM hw_free ops call + * as well, so that the driver doesn't need to invoke the allocation and + * the release explicitly in its callback. + * When a buffer is actually allocated before the PCM hw_params call, it + * turns on the runtime buffer_changed flag for drivers changing their h/w + * parameters accordingly. + */ +void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type, + struct device *data, size_t size, size_t max) +{ + preallocate_pages(substream, type, data, size, max, true); +} +EXPORT_SYMBOL(snd_pcm_set_managed_buffer); + +/** + * snd_pcm_set_managed_buffer_all - set up buffer management for all substreams + * for all substreams + * @pcm: the pcm instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation to all substreams of the given pcm for the specified DMA + * type and size, and set the managed_buffer_alloc flag to each substream. + */ +void snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type, + struct device *data, + size_t size, size_t max) +{ + preallocate_pages_for_all(pcm, type, data, size, max, true); +} +EXPORT_SYMBOL(snd_pcm_set_managed_buffer_all); + +#ifdef CONFIG_SND_DMA_SGBUF +/* * snd_pcm_sgbuf_ops_page - get the page struct at the given offset * @substream: the pcm substream instance * @offset: the buffer offset @@ -270,7 +331,6 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigne return NULL; return sgbuf->page_table[idx]; } -EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page); #endif /* CONFIG_SND_DMA_SGBUF */ /** diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 91c6ad58729f..1fe581167b7b 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -13,6 +13,7 @@ #include <linux/pm_qos.h> #include <linux/io.h> #include <linux/dma-mapping.h> +#include <linux/vmalloc.h> #include <sound/core.h> #include <sound/control.h> #include <sound/info.h> @@ -177,6 +178,16 @@ void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream, } EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore); +/* Run PCM ioctl ops */ +static int snd_pcm_ops_ioctl(struct snd_pcm_substream *substream, + unsigned cmd, void *arg) +{ + if (substream->ops->ioctl) + return substream->ops->ioctl(substream, cmd, arg); + else + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info) { struct snd_pcm *pcm = substream->pcm; @@ -222,7 +233,8 @@ static bool hw_support_mmap(struct snd_pcm_substream *substream) return false; if (substream->ops->mmap || - substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV) + (substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV && + substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV_UC)) return true; return dma_can_mmap(substream->dma_buffer.dev.dev); @@ -446,8 +458,9 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); if (snd_mask_single(m) && snd_interval_single(i)) { - err = substream->ops->ioctl(substream, - SNDRV_PCM_IOCTL1_FIFO_SIZE, params); + err = snd_pcm_ops_ioctl(substream, + SNDRV_PCM_IOCTL1_FIFO_SIZE, + params); if (err < 0) return err; } @@ -555,6 +568,17 @@ static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream, #endif } +static void snd_pcm_sync_stop(struct snd_pcm_substream *substream) +{ + if (substream->runtime->stop_operating) { + substream->runtime->stop_operating = false; + if (substream->ops->sync_stop) + substream->ops->sync_stop(substream); + else if (substream->pcm->card->sync_irq > 0) + synchronize_irq(substream->pcm->card->sync_irq); + } +} + /** * snd_pcm_hw_param_choose - choose a configuration defined by @params * @pcm: PCM instance @@ -647,6 +671,8 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, if (atomic_read(&substream->mmap_count)) return -EBADFD; + snd_pcm_sync_stop(substream); + params->rmask = ~0U; err = snd_pcm_hw_refine(substream, params); if (err < 0) @@ -660,6 +686,14 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, if (err < 0) goto _error; + if (substream->managed_buffer_alloc) { + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(params)); + if (err < 0) + goto _error; + runtime->buffer_changed = err > 0; + } + if (substream->ops->hw_params != NULL) { err = substream->ops->hw_params(substream, params); if (err < 0) @@ -721,6 +755,8 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); if (substream->ops->hw_free != NULL) substream->ops->hw_free(substream); + if (substream->managed_buffer_alloc) + snd_pcm_lib_free_pages(substream); return err; } @@ -765,8 +801,11 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream) snd_pcm_stream_unlock_irq(substream); if (atomic_read(&substream->mmap_count)) return -EBADFD; + snd_pcm_sync_stop(substream); if (substream->ops->hw_free) result = substream->ops->hw_free(substream); + if (substream->managed_buffer_alloc) + snd_pcm_lib_free_pages(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); pm_qos_remove_request(&substream->latency_pm_qos_req); return result; @@ -957,7 +996,7 @@ static int snd_pcm_channel_info(struct snd_pcm_substream *substream, return -EINVAL; memset(info, 0, sizeof(*info)); info->channel = channel; - return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info); + return snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info); } static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream, @@ -1288,6 +1327,7 @@ static void snd_pcm_post_stop(struct snd_pcm_substream *substream, int state) runtime->status->state = state; snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP); } + runtime->stop_operating = true; wake_up(&runtime->sleep); wake_up(&runtime->tsleep); } @@ -1564,6 +1604,7 @@ static void snd_pcm_post_resume(struct snd_pcm_substream *substream, int state) snd_pcm_trigger_tstamp(substream); runtime->status->state = runtime->status->suspended_state; snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME); + snd_pcm_sync_stop(substream); } static const struct action_ops snd_pcm_action_resume = { @@ -1633,7 +1674,7 @@ static int snd_pcm_pre_reset(struct snd_pcm_substream *substream, int state) static int snd_pcm_do_reset(struct snd_pcm_substream *substream, int state) { struct snd_pcm_runtime *runtime = substream->runtime; - int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL); + int err = snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL); if (err < 0) return err; runtime->hw_ptr_base = 0; @@ -1684,6 +1725,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, int state) { int err; + snd_pcm_sync_stop(substream); err = substream->ops->prepare(substream); if (err < 0) return err; @@ -3334,7 +3376,18 @@ static inline struct page * snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs) { void *vaddr = substream->runtime->dma_area + ofs; - return virt_to_page(vaddr); + + switch (substream->dma_buffer.dev.type) { +#ifdef CONFIG_SND_DMA_SGBUF + case SNDRV_DMA_TYPE_DEV_SG: + case SNDRV_DMA_TYPE_DEV_UC_SG: + return snd_pcm_sgbuf_ops_page(substream, ofs); +#endif /* CONFIG_SND_DMA_SGBUF */ + case SNDRV_DMA_TYPE_VMALLOC: + return vmalloc_to_page(vaddr); + default: + return virt_to_page(vaddr); + } } /* @@ -3403,7 +3456,8 @@ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream, #endif /* CONFIG_GENERIC_ALLOCATOR */ #ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */ if (IS_ENABLED(CONFIG_HAS_DMA) && !substream->ops->page && - substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) + (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV || + substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_UC)) return dma_mmap_coherent(substream->dma_buffer.dev.dev, area, substream->runtime->dma_area, diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c index 161f3170bd7e..63dc7bdb622d 100644 --- a/sound/core/seq/seq_timer.c +++ b/sound/core/seq/seq_timer.c @@ -272,7 +272,13 @@ int snd_seq_timer_open(struct snd_seq_queue *q) return -EINVAL; if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; - err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue); + t = snd_timer_instance_new(str); + if (!t) + return -ENOMEM; + t->callback = snd_seq_timer_interrupt; + t->callback_data = q; + t->flags |= SNDRV_TIMER_IFLG_AUTO; + err = snd_timer_open(t, &tmr->alsa_id, q->queue); if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) { if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL || tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) { @@ -282,16 +288,14 @@ int snd_seq_timer_open(struct snd_seq_queue *q) tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; tid.card = -1; tid.device = SNDRV_TIMER_GLOBAL_SYSTEM; - err = snd_timer_open(&t, str, &tid, q->queue); + err = snd_timer_open(t, &tid, q->queue); } } if (err < 0) { pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err); + snd_timer_instance_free(t); return err; } - t->callback = snd_seq_timer_interrupt; - t->callback_data = q; - t->flags |= SNDRV_TIMER_IFLG_AUTO; spin_lock_irq(&tmr->lock); tmr->timeri = t; spin_unlock_irq(&tmr->lock); @@ -310,8 +314,10 @@ int snd_seq_timer_close(struct snd_seq_queue *q) t = tmr->timeri; tmr->timeri = NULL; spin_unlock_irq(&tmr->lock); - if (t) + if (t) { snd_timer_close(t); + snd_timer_instance_free(t); + } return 0; } diff --git a/sound/core/timer.c b/sound/core/timer.c index 59ae21b0bb93..24fed5c78273 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -74,6 +74,9 @@ static LIST_HEAD(snd_timer_slave_list); /* lock for slave active lists */ static DEFINE_SPINLOCK(slave_active_lock); +#define MAX_SLAVE_INSTANCES 1000 +static int num_slaves; + static DEFINE_MUTEX(register_mutex); static int snd_timer_free(struct snd_timer *timer); @@ -85,12 +88,11 @@ static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_l /* * create a timer instance with the given owner string. - * when timer is not NULL, increments the module counter */ -static struct snd_timer_instance *snd_timer_instance_new(char *owner, - struct snd_timer *timer) +struct snd_timer_instance *snd_timer_instance_new(const char *owner) { struct snd_timer_instance *timeri; + timeri = kzalloc(sizeof(*timeri), GFP_KERNEL); if (timeri == NULL) return NULL; @@ -105,15 +107,20 @@ static struct snd_timer_instance *snd_timer_instance_new(char *owner, INIT_LIST_HEAD(&timeri->slave_list_head); INIT_LIST_HEAD(&timeri->slave_active_head); - timeri->timer = timer; - if (timer && !try_module_get(timer->module)) { + return timeri; +} +EXPORT_SYMBOL(snd_timer_instance_new); + +void snd_timer_instance_free(struct snd_timer_instance *timeri) +{ + if (timeri) { + if (timeri->private_free) + timeri->private_free(timeri); kfree(timeri->owner); kfree(timeri); - return NULL; } - - return timeri; } +EXPORT_SYMBOL(snd_timer_instance_free); /* * find a timer instance from the given timer id @@ -160,6 +167,28 @@ static void snd_timer_request(struct snd_timer_id *tid) #endif +/* move the slave if it belongs to the master; return 1 if match */ +static int check_matching_master_slave(struct snd_timer_instance *master, + struct snd_timer_instance *slave) +{ + if (slave->slave_class != master->slave_class || + slave->slave_id != master->slave_id) + return 0; + if (master->timer->num_instances >= master->timer->max_instances) + return -EBUSY; + list_move_tail(&slave->open_list, &master->slave_list_head); + master->timer->num_instances++; + spin_lock_irq(&slave_active_lock); + spin_lock(&master->timer->lock); + slave->master = master; + slave->timer = master->timer; + if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) + list_add_tail(&slave->active_list, &master->slave_active_head); + spin_unlock(&master->timer->lock); + spin_unlock_irq(&slave_active_lock); + return 1; +} + /* * look for a master instance matching with the slave id of the given slave. * when found, relink the open_link of the slave. @@ -170,27 +199,18 @@ static int snd_timer_check_slave(struct snd_timer_instance *slave) { struct snd_timer *timer; struct snd_timer_instance *master; + int err = 0; /* FIXME: it's really dumb to look up all entries.. */ list_for_each_entry(timer, &snd_timer_list, device_list) { list_for_each_entry(master, &timer->open_list_head, open_list) { - if (slave->slave_class == master->slave_class && - slave->slave_id == master->slave_id) { - if (master->timer->num_instances >= - master->timer->max_instances) - return -EBUSY; - list_move_tail(&slave->open_list, - &master->slave_list_head); - master->timer->num_instances++; - spin_lock_irq(&slave_active_lock); - slave->master = master; - slave->timer = master->timer; - spin_unlock_irq(&slave_active_lock); - return 0; - } + err = check_matching_master_slave(master, slave); + if (err != 0) /* match found or error */ + goto out; } } - return 0; + out: + return err < 0 ? err : 0; } /* @@ -202,43 +222,29 @@ static int snd_timer_check_slave(struct snd_timer_instance *slave) static int snd_timer_check_master(struct snd_timer_instance *master) { struct snd_timer_instance *slave, *tmp; + int err = 0; /* check all pending slaves */ list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) { - if (slave->slave_class == master->slave_class && - slave->slave_id == master->slave_id) { - if (master->timer->num_instances >= - master->timer->max_instances) - return -EBUSY; - list_move_tail(&slave->open_list, &master->slave_list_head); - master->timer->num_instances++; - spin_lock_irq(&slave_active_lock); - spin_lock(&master->timer->lock); - slave->master = master; - slave->timer = master->timer; - if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) - list_add_tail(&slave->active_list, - &master->slave_active_head); - spin_unlock(&master->timer->lock); - spin_unlock_irq(&slave_active_lock); - } + err = check_matching_master_slave(master, slave); + if (err < 0) + break; } - return 0; + return err < 0 ? err : 0; } -static int snd_timer_close_locked(struct snd_timer_instance *timeri, - struct device **card_devp_to_put); +static void snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put); /* * open a timer instance * when opening a master, the slave id must be here given. */ -int snd_timer_open(struct snd_timer_instance **ti, - char *owner, struct snd_timer_id *tid, +int snd_timer_open(struct snd_timer_instance *timeri, + struct snd_timer_id *tid, unsigned int slave_id) { struct snd_timer *timer; - struct snd_timer_instance *timeri = NULL; struct device *card_dev_to_put = NULL; int err; @@ -252,21 +258,17 @@ int snd_timer_open(struct snd_timer_instance **ti, err = -EINVAL; goto unlock; } - timeri = snd_timer_instance_new(owner, NULL); - if (!timeri) { - err = -ENOMEM; + if (num_slaves >= MAX_SLAVE_INSTANCES) { + err = -EBUSY; goto unlock; } timeri->slave_class = tid->dev_sclass; timeri->slave_id = tid->device; timeri->flags |= SNDRV_TIMER_IFLG_SLAVE; list_add_tail(&timeri->open_list, &snd_timer_slave_list); + num_slaves++; err = snd_timer_check_slave(timeri); - if (err < 0) { - snd_timer_close_locked(timeri, &card_dev_to_put); - timeri = NULL; - } - goto unlock; + goto list_added; } /* open a master instance */ @@ -296,45 +298,40 @@ int snd_timer_open(struct snd_timer_instance **ti, err = -EBUSY; goto unlock; } - timeri = snd_timer_instance_new(owner, timer); - if (!timeri) { - err = -ENOMEM; + if (!try_module_get(timer->module)) { + err = -EBUSY; goto unlock; } /* take a card refcount for safe disconnection */ - if (timer->card) + if (timer->card) { get_device(&timer->card->card_dev); - timeri->slave_class = tid->dev_sclass; - timeri->slave_id = slave_id; + card_dev_to_put = &timer->card->card_dev; + } if (list_empty(&timer->open_list_head) && timer->hw.open) { err = timer->hw.open(timer); if (err) { - kfree(timeri->owner); - kfree(timeri); - timeri = NULL; - - if (timer->card) - card_dev_to_put = &timer->card->card_dev; module_put(timer->module); goto unlock; } } + timeri->timer = timer; + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = slave_id; + list_add_tail(&timeri->open_list, &timer->open_list_head); timer->num_instances++; err = snd_timer_check_master(timeri); - if (err < 0) { +list_added: + if (err < 0) snd_timer_close_locked(timeri, &card_dev_to_put); - timeri = NULL; - } unlock: mutex_unlock(®ister_mutex); /* put_device() is called after unlock for avoiding deadlock */ - if (card_dev_to_put) + if (err < 0 && card_dev_to_put) put_device(card_dev_to_put); - *ti = timeri; return err; } EXPORT_SYMBOL(snd_timer_open); @@ -343,8 +340,8 @@ EXPORT_SYMBOL(snd_timer_open); * close a timer instance * call this with register_mutex down. */ -static int snd_timer_close_locked(struct snd_timer_instance *timeri, - struct device **card_devp_to_put) +static void snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put) { struct snd_timer *timer = timeri->timer; struct snd_timer_instance *slave, *tmp; @@ -355,7 +352,11 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri, spin_unlock_irq(&timer->lock); } - list_del(&timeri->open_list); + if (!list_empty(&timeri->open_list)) { + list_del_init(&timeri->open_list); + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + num_slaves--; + } /* force to stop the timer */ snd_timer_stop(timeri); @@ -374,6 +375,7 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri, /* remove slave links */ spin_lock_irq(&slave_active_lock); spin_lock(&timer->lock); + timeri->timer = NULL; list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head, open_list) { list_move_tail(&slave->open_list, &snd_timer_slave_list); @@ -391,11 +393,6 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri, timer = NULL; } - if (timeri->private_free) - timeri->private_free(timeri); - kfree(timeri->owner); - kfree(timeri); - if (timer) { if (list_empty(&timer->open_list_head) && timer->hw.close) timer->hw.close(timer); @@ -404,28 +401,24 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri, *card_devp_to_put = &timer->card->card_dev; module_put(timer->module); } - - return 0; } /* * close a timer instance */ -int snd_timer_close(struct snd_timer_instance *timeri) +void snd_timer_close(struct snd_timer_instance *timeri) { struct device *card_dev_to_put = NULL; - int err; if (snd_BUG_ON(!timeri)) - return -ENXIO; + return; mutex_lock(®ister_mutex); - err = snd_timer_close_locked(timeri, &card_dev_to_put); + snd_timer_close_locked(timeri, &card_dev_to_put); mutex_unlock(®ister_mutex); /* put_device() is called after unlock for avoiding deadlock */ if (card_dev_to_put) put_device(card_dev_to_put); - return err; } EXPORT_SYMBOL(snd_timer_close); @@ -1474,8 +1467,10 @@ static int snd_timer_user_release(struct inode *inode, struct file *file) tu = file->private_data; file->private_data = NULL; mutex_lock(&tu->ioctl_lock); - if (tu->timeri) + if (tu->timeri) { snd_timer_close(tu->timeri); + snd_timer_instance_free(tu->timeri); + } mutex_unlock(&tu->ioctl_lock); kfree(tu->queue); kfree(tu->tqueue); @@ -1716,6 +1711,7 @@ static int snd_timer_user_tselect(struct file *file, tu = file->private_data; if (tu->timeri) { snd_timer_close(tu->timeri); + snd_timer_instance_free(tu->timeri); tu->timeri = NULL; } if (copy_from_user(&tselect, _tselect, sizeof(tselect))) { @@ -1725,9 +1721,11 @@ static int snd_timer_user_tselect(struct file *file, sprintf(str, "application %i", current->pid); if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE) tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION; - err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid); - if (err < 0) + tu->timeri = snd_timer_instance_new(str); + if (!tu->timeri) { + err = -ENOMEM; goto __err; + } tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST; tu->timeri->callback = tu->tread @@ -1736,6 +1734,12 @@ static int snd_timer_user_tselect(struct file *file, tu->timeri->callback_data = (void *)tu; tu->timeri->disconnect = snd_timer_user_disconnect; + err = snd_timer_open(tu->timeri, &tselect.id, current->pid); + if (err < 0) { + snd_timer_instance_free(tu->timeri); + tu->timeri = NULL; + } + __err: return err; } |