From c631d03c6873b9e17906556e84fcafc42f26a7c2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2009 15:59:26 +0200 Subject: [PATCH 1/9] ALSA: dummy - Support high-res timer mode Allow snd-dummy driver to use high-res timer as its timing source instead of the system timer. The new module option "hrtimer" is added to turn on/off the high-res timer support. It can be switched even dynamically via sysfs. Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 429 +++++++++++++++++++++++++++--------------- 1 file changed, 280 insertions(+), 149 deletions(-) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index 54239d2e099..f387d53e503 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -148,6 +150,9 @@ static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8}; //static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +#ifdef CONFIG_HIGH_RES_TIMERS +static int hrtimer = 1; +#endif module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for dummy soundcard."); @@ -161,6 +166,10 @@ module_param_array(pcm_substreams, int, NULL, 0444); MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-16) for dummy driver."); //module_param_array(midi_devs, int, NULL, 0444); //MODULE_PARM_DESC(midi_devs, "MIDI devices # (0-2) for dummy driver."); +#ifdef CONFIG_HIGH_RES_TIMERS +module_param(hrtimer, bool, 0644); +MODULE_PARM_DESC(hrtimer, "Use hrtimer as the timer source."); +#endif static struct platform_device *devices[SNDRV_CARDS]; @@ -171,16 +180,29 @@ static struct platform_device *devices[SNDRV_CARDS]; #define MIXER_ADDR_CD 4 #define MIXER_ADDR_LAST 4 +struct dummy_timer_ops { + int (*create)(struct snd_pcm_substream *); + void (*free)(struct snd_pcm_substream *); + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); +}; + struct snd_dummy { struct snd_card *card; struct snd_pcm *pcm; spinlock_t mixer_lock; int mixer_volume[MIXER_ADDR_LAST+1][2]; int capture_source[MIXER_ADDR_LAST+1][2]; + const struct dummy_timer_ops *timer_ops; }; -struct snd_dummy_pcm { - struct snd_dummy *dummy; +/* + * system timer interface + */ + +struct dummy_systimer_pcm { spinlock_t lock; struct timer_list timer; unsigned int pcm_buffer_size; @@ -192,46 +214,29 @@ struct snd_dummy_pcm { struct snd_pcm_substream *substream; }; - -static inline void snd_card_dummy_pcm_timer_start(struct snd_dummy_pcm *dpcm) +static int dummy_systimer_start(struct snd_pcm_substream *substream) { + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); dpcm->timer.expires = 1 + jiffies; add_timer(&dpcm->timer); -} - -static inline void snd_card_dummy_pcm_timer_stop(struct snd_dummy_pcm *dpcm) -{ - del_timer(&dpcm->timer); -} - -static int snd_card_dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_dummy_pcm *dpcm = runtime->private_data; - int err = 0; - - spin_lock(&dpcm->lock); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - snd_card_dummy_pcm_timer_start(dpcm); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - snd_card_dummy_pcm_timer_stop(dpcm); - break; - default: - err = -EINVAL; - break; - } spin_unlock(&dpcm->lock); return 0; } -static int snd_card_dummy_pcm_prepare(struct snd_pcm_substream *substream) +static int dummy_systimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); + del_timer(&dpcm->timer); + spin_unlock(&dpcm->lock); + return 0; +} + +static int dummy_systimer_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_dummy_pcm *dpcm = runtime->private_data; + struct dummy_systimer_pcm *dpcm = runtime->private_data; int bps; bps = snd_pcm_format_width(runtime->format) * runtime->rate * @@ -247,15 +252,12 @@ static int snd_card_dummy_pcm_prepare(struct snd_pcm_substream *substream) dpcm->pcm_irq_pos = 0; dpcm->pcm_buf_pos = 0; - snd_pcm_format_set_silence(runtime->format, runtime->dma_area, - bytes_to_samples(runtime, runtime->dma_bytes)); - return 0; } -static void snd_card_dummy_pcm_timer_function(unsigned long data) +static void dummy_systimer_callback(unsigned long data) { - struct snd_dummy_pcm *dpcm = (struct snd_dummy_pcm *)data; + struct dummy_systimer_pcm *dpcm = (struct dummy_systimer_pcm *)data; unsigned long flags; spin_lock_irqsave(&dpcm->lock, flags); @@ -272,18 +274,212 @@ static void snd_card_dummy_pcm_timer_function(unsigned long data) spin_unlock_irqrestore(&dpcm->lock, flags); } -static snd_pcm_uframes_t snd_card_dummy_pcm_pointer(struct snd_pcm_substream *substream) +static snd_pcm_uframes_t +dummy_systimer_pointer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_dummy_pcm *dpcm = runtime->private_data; + struct dummy_systimer_pcm *dpcm = runtime->private_data; return bytes_to_frames(runtime, dpcm->pcm_buf_pos / dpcm->pcm_hz); } -static struct snd_pcm_hardware snd_card_dummy_playback = +static int dummy_systimer_create(struct snd_pcm_substream *substream) { - .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), + struct dummy_systimer_pcm *dpcm; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + init_timer(&dpcm->timer); + dpcm->timer.data = (unsigned long) dpcm; + dpcm->timer.function = dummy_systimer_callback; + spin_lock_init(&dpcm->lock); + dpcm->substream = substream; + return 0; +} + +static void dummy_systimer_free(struct snd_pcm_substream *substream) +{ + kfree(substream->runtime->private_data); +} + +static struct dummy_timer_ops dummy_systimer_ops = { + .create = dummy_systimer_create, + .free = dummy_systimer_free, + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, + .pointer = dummy_systimer_pointer, +}; + +#ifdef CONFIG_HIGH_RES_TIMERS +/* + * hrtimer interface + */ + +struct dummy_hrtimer_pcm { + ktime_t base_time; + ktime_t period_time; + atomic_t running; + struct hrtimer timer; + struct tasklet_struct tasklet; + struct snd_pcm_substream *substream; +}; + +static void dummy_hrtimer_pcm_elapsed(unsigned long priv) +{ + struct dummy_hrtimer_pcm *dpcm = (struct dummy_hrtimer_pcm *)priv; + if (atomic_read(&dpcm->running)) + snd_pcm_period_elapsed(dpcm->substream); +} + +static enum hrtimer_restart dummy_hrtimer_callback(struct hrtimer *timer) +{ + struct dummy_hrtimer_pcm *dpcm; + + dpcm = container_of(timer, struct dummy_hrtimer_pcm, timer); + if (!atomic_read(&dpcm->running)) + return HRTIMER_NORESTART; + tasklet_schedule(&dpcm->tasklet); + hrtimer_forward_now(timer, dpcm->period_time); + return HRTIMER_RESTART; +} + +static int dummy_hrtimer_start(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + + dpcm->base_time = hrtimer_cb_get_time(&dpcm->timer); + hrtimer_start(&dpcm->timer, dpcm->period_time, HRTIMER_MODE_REL); + atomic_set(&dpcm->running, 1); + return 0; +} + +static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + + atomic_set(&dpcm->running, 0); + hrtimer_cancel(&dpcm->timer); + return 0; +} + +static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) +{ + tasklet_kill(&dpcm->tasklet); +} + +static snd_pcm_uframes_t +dummy_hrtimer_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_hrtimer_pcm *dpcm = runtime->private_data; + u64 delta; + u32 pos; + + delta = ktime_us_delta(hrtimer_cb_get_time(&dpcm->timer), + dpcm->base_time); + delta = div_u64(delta * runtime->rate + 999999, 1000000); + div_u64_rem(delta, runtime->buffer_size, &pos); + return pos; +} + +static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_hrtimer_pcm *dpcm = runtime->private_data; + unsigned int period, rate; + long sec; + unsigned long nsecs; + + dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; + period %= rate; + nsecs = div_u64((u64)period * 1000000000UL + rate - 1, rate); + dpcm->period_time = ktime_set(sec, nsecs); + + return 0; +} + +static int dummy_hrtimer_create(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + hrtimer_init(&dpcm->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + dpcm->timer.function = dummy_hrtimer_callback; + dpcm->substream = substream; + atomic_set(&dpcm->running, 0); + tasklet_init(&dpcm->tasklet, dummy_hrtimer_pcm_elapsed, + (unsigned long)dpcm); + return 0; +} + +static void dummy_hrtimer_free(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + dummy_hrtimer_sync(dpcm); + kfree(dpcm); +} + +static struct dummy_timer_ops dummy_hrtimer_ops = { + .create = dummy_hrtimer_create, + .free = dummy_hrtimer_free, + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, + .pointer = dummy_hrtimer_pointer, +}; + +#endif /* CONFIG_HIGH_RES_TIMERS */ + +/* + * PCM interface + */ + +static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dummy *dummy = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return dummy->timer_ops->start(substream); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + return dummy->timer_ops->stop(substream); + } + return -EINVAL; +} + +static int dummy_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dummy *dummy = snd_pcm_substream_chip(substream); + + snd_pcm_format_set_silence(runtime->format, runtime->dma_area, + bytes_to_samples(runtime, runtime->dma_bytes)); + return dummy->timer_ops->prepare(substream); +} + +static snd_pcm_uframes_t dummy_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_dummy *dummy = snd_pcm_substream_chip(substream); + + return dummy->timer_ops->pointer(substream); +} + +static struct snd_pcm_hardware dummy_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), .formats = USE_FORMATS, .rates = USE_RATE, .rate_min = USE_RATE_MIN, @@ -298,135 +494,70 @@ static struct snd_pcm_hardware snd_card_dummy_playback = .fifo_size = 0, }; -static struct snd_pcm_hardware snd_card_dummy_capture = +static int dummy_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) { - .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), - .formats = USE_FORMATS, - .rates = USE_RATE, - .rate_min = USE_RATE_MIN, - .rate_max = USE_RATE_MAX, - .channels_min = USE_CHANNELS_MIN, - .channels_max = USE_CHANNELS_MAX, - .buffer_bytes_max = MAX_BUFFER_SIZE, - .period_bytes_min = 64, - .period_bytes_max = MAX_PERIOD_SIZE, - .periods_min = USE_PERIODS_MIN, - .periods_max = USE_PERIODS_MAX, - .fifo_size = 0, -}; - -static void snd_card_dummy_runtime_free(struct snd_pcm_runtime *runtime) -{ - kfree(runtime->private_data); + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); } -static int snd_card_dummy_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); -} - -static int snd_card_dummy_hw_free(struct snd_pcm_substream *substream) +static int dummy_pcm_hw_free(struct snd_pcm_substream *substream) { return snd_pcm_lib_free_pages(substream); } -static struct snd_dummy_pcm *new_pcm_stream(struct snd_pcm_substream *substream) -{ - struct snd_dummy_pcm *dpcm; - - dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); - if (! dpcm) - return dpcm; - init_timer(&dpcm->timer); - dpcm->timer.data = (unsigned long) dpcm; - dpcm->timer.function = snd_card_dummy_pcm_timer_function; - spin_lock_init(&dpcm->lock); - dpcm->substream = substream; - return dpcm; -} - -static int snd_card_dummy_playback_open(struct snd_pcm_substream *substream) +static int dummy_pcm_open(struct snd_pcm_substream *substream) { + struct snd_dummy *dummy = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_dummy_pcm *dpcm; int err; - if ((dpcm = new_pcm_stream(substream)) == NULL) - return -ENOMEM; - runtime->private_data = dpcm; - /* makes the infrastructure responsible for freeing dpcm */ - runtime->private_free = snd_card_dummy_runtime_free; - runtime->hw = snd_card_dummy_playback; + dummy->timer_ops = &dummy_systimer_ops; +#ifdef CONFIG_HIGH_RES_TIMERS + if (hrtimer) + dummy->timer_ops = &dummy_hrtimer_ops; +#endif + + err = dummy->timer_ops->create(substream); + if (err < 0) + return err; + + runtime->hw = dummy_pcm_hardware; if (substream->pcm->device & 1) { runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; } if (substream->pcm->device & 2) - runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP|SNDRV_PCM_INFO_MMAP_VALID); - err = add_playback_constraints(runtime); - if (err < 0) + runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + err = add_playback_constraints(substream->runtime); + else + err = add_capture_constraints(substream->runtime); + if (err < 0) { + dummy->timer_ops->free(substream); return err; - - return 0; -} - -static int snd_card_dummy_capture_open(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_dummy_pcm *dpcm; - int err; - - if ((dpcm = new_pcm_stream(substream)) == NULL) - return -ENOMEM; - runtime->private_data = dpcm; - /* makes the infrastructure responsible for freeing dpcm */ - runtime->private_free = snd_card_dummy_runtime_free; - runtime->hw = snd_card_dummy_capture; - if (substream->pcm->device == 1) { - runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; - runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; } - if (substream->pcm->device & 2) - runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP|SNDRV_PCM_INFO_MMAP_VALID); - err = add_capture_constraints(runtime); - if (err < 0) - return err; - return 0; } -static int snd_card_dummy_playback_close(struct snd_pcm_substream *substream) +static int dummy_pcm_close(struct snd_pcm_substream *substream) { + struct snd_dummy *dummy = snd_pcm_substream_chip(substream); + dummy->timer_ops->free(substream); return 0; } -static int snd_card_dummy_capture_close(struct snd_pcm_substream *substream) -{ - return 0; -} - -static struct snd_pcm_ops snd_card_dummy_playback_ops = { - .open = snd_card_dummy_playback_open, - .close = snd_card_dummy_playback_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_card_dummy_hw_params, - .hw_free = snd_card_dummy_hw_free, - .prepare = snd_card_dummy_pcm_prepare, - .trigger = snd_card_dummy_pcm_trigger, - .pointer = snd_card_dummy_pcm_pointer, -}; - -static struct snd_pcm_ops snd_card_dummy_capture_ops = { - .open = snd_card_dummy_capture_open, - .close = snd_card_dummy_capture_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_card_dummy_hw_params, - .hw_free = snd_card_dummy_hw_free, - .prepare = snd_card_dummy_pcm_prepare, - .trigger = snd_card_dummy_pcm_trigger, - .pointer = snd_card_dummy_pcm_pointer, +static struct snd_pcm_ops dummy_pcm_ops = { + .open = dummy_pcm_open, + .close = dummy_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dummy_pcm_hw_params, + .hw_free = dummy_pcm_hw_free, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, + .pointer = dummy_pcm_pointer, }; static int __devinit snd_card_dummy_pcm(struct snd_dummy *dummy, int device, @@ -440,8 +571,8 @@ static int __devinit snd_card_dummy_pcm(struct snd_dummy *dummy, int device, if (err < 0) return err; dummy->pcm = pcm; - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_dummy_playback_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_dummy_capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dummy_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &dummy_pcm_ops); pcm->private_data = dummy; pcm->info_flags = 0; strcpy(pcm->name, "Dummy PCM"); From b142037b4c1edf5ad0b2871c518d4f14ac1cd470 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2009 16:01:06 +0200 Subject: [PATCH 2/9] ALSA: dummy - Better jiffies handling In the system-timer mode, snd-dummy driver issues each tick to update the position. This is highly inefficient and even inaccurate if the timer can't be triggered at each tick. Now rewritten to wake up only at the period boundary. The position is calculated from the current jiffies. Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 78 ++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index f387d53e503..0a798bde0d0 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -205,21 +205,43 @@ struct snd_dummy { struct dummy_systimer_pcm { spinlock_t lock; struct timer_list timer; - unsigned int pcm_buffer_size; - unsigned int pcm_period_size; - unsigned int pcm_bps; /* bytes per second */ - unsigned int pcm_hz; /* HZ */ - unsigned int pcm_irq_pos; /* IRQ position */ - unsigned int pcm_buf_pos; /* position in buffer */ + unsigned long base_time; + unsigned int frac_pos; /* fractional sample position (based HZ) */ + unsigned int frac_buffer_size; /* buffer_size * HZ */ + unsigned int frac_period_size; /* period_size * HZ */ + unsigned int rate; struct snd_pcm_substream *substream; }; +static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm) +{ + unsigned long frac; + + frac = dpcm->frac_pos % dpcm->frac_period_size; + dpcm->timer.expires = jiffies + + (dpcm->frac_period_size + dpcm->rate - 1) / dpcm->rate; + add_timer(&dpcm->timer); +} + +static void dummy_systimer_update(struct dummy_systimer_pcm *dpcm) +{ + unsigned long delta; + + delta = jiffies - dpcm->base_time; + if (!delta) + return; + dpcm->base_time = jiffies; + dpcm->frac_pos += delta * dpcm->rate; + while (dpcm->frac_pos >= dpcm->frac_buffer_size) + dpcm->frac_pos -= dpcm->frac_buffer_size; +} + static int dummy_systimer_start(struct snd_pcm_substream *substream) { struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; spin_lock(&dpcm->lock); - dpcm->timer.expires = 1 + jiffies; - add_timer(&dpcm->timer); + dpcm->base_time = jiffies; + dummy_systimer_rearm(dpcm); spin_unlock(&dpcm->lock); return 0; } @@ -237,20 +259,11 @@ static int dummy_systimer_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct dummy_systimer_pcm *dpcm = runtime->private_data; - int bps; - bps = snd_pcm_format_width(runtime->format) * runtime->rate * - runtime->channels / 8; - - if (bps <= 0) - return -EINVAL; - - dpcm->pcm_bps = bps; - dpcm->pcm_hz = HZ; - dpcm->pcm_buffer_size = snd_pcm_lib_buffer_bytes(substream); - dpcm->pcm_period_size = snd_pcm_lib_period_bytes(substream); - dpcm->pcm_irq_pos = 0; - dpcm->pcm_buf_pos = 0; + dpcm->frac_pos = 0; + dpcm->rate = runtime->rate; + dpcm->frac_buffer_size = runtime->buffer_size * HZ; + dpcm->frac_period_size = runtime->period_size * HZ; return 0; } @@ -261,26 +274,21 @@ static void dummy_systimer_callback(unsigned long data) unsigned long flags; spin_lock_irqsave(&dpcm->lock, flags); - dpcm->timer.expires = 1 + jiffies; - add_timer(&dpcm->timer); - dpcm->pcm_irq_pos += dpcm->pcm_bps; - dpcm->pcm_buf_pos += dpcm->pcm_bps; - dpcm->pcm_buf_pos %= dpcm->pcm_buffer_size * dpcm->pcm_hz; - if (dpcm->pcm_irq_pos >= dpcm->pcm_period_size * dpcm->pcm_hz) { - dpcm->pcm_irq_pos %= dpcm->pcm_period_size * dpcm->pcm_hz; - spin_unlock_irqrestore(&dpcm->lock, flags); - snd_pcm_period_elapsed(dpcm->substream); - } else - spin_unlock_irqrestore(&dpcm->lock, flags); + dummy_systimer_update(dpcm); + dummy_systimer_rearm(dpcm); + spin_unlock_irqrestore(&dpcm->lock, flags); + snd_pcm_period_elapsed(dpcm->substream); } static snd_pcm_uframes_t dummy_systimer_pointer(struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime = substream->runtime; - struct dummy_systimer_pcm *dpcm = runtime->private_data; + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; - return bytes_to_frames(runtime, dpcm->pcm_buf_pos / dpcm->pcm_hz); + spin_lock(&dpcm->lock); + dummy_systimer_update(dpcm); + spin_unlock(&dpcm->lock); + return dpcm->frac_pos / HZ; } static int dummy_systimer_create(struct snd_pcm_substream *substream) From 30681bcf2b15a601b9460e6ddab22077998b8d4c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2009 16:08:08 +0200 Subject: [PATCH 3/9] ALSA: dummy - Add more description Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 4252697a95d..bb488b575de 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -513,6 +513,19 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. or input, but you may use this module for any application which requires a sound card (like RealPlayer). + pcm_devs - Number of PCM devices assigned to each card + (default = 1, up to 4) + pcm_substreams - Number of PCM substreams assigned to each PCM + (default = 8, up to 16) + hrtimer - Use hrtimer (=1, default) or system timer (=0) + + When multiple PCM devices are created, snd-dummy gives different + behavior to each PCM device: + 0 = interleaved with mmap support + 1 = non-interleaved with mmap support + 2 = interleaved without mmap + 3 = non-interleaved without mmap + The power-management is supported. Module snd-echo3g From b5d10781731ece07bb2049e7743907194a5cc3f1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 4 Sep 2009 08:45:11 +0200 Subject: [PATCH 4/9] ALSA: dummy - Fix the timer calculation in systimer mode Fix the expire-time calculation in the systimer mode when the buffer size isn't aligned to the period size. Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index 0a798bde0d0..e8e29bfb85e 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -207,19 +207,18 @@ struct dummy_systimer_pcm { struct timer_list timer; unsigned long base_time; unsigned int frac_pos; /* fractional sample position (based HZ) */ + unsigned int frac_period_rest; unsigned int frac_buffer_size; /* buffer_size * HZ */ unsigned int frac_period_size; /* period_size * HZ */ unsigned int rate; + int elapsed; struct snd_pcm_substream *substream; }; static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm) { - unsigned long frac; - - frac = dpcm->frac_pos % dpcm->frac_period_size; dpcm->timer.expires = jiffies + - (dpcm->frac_period_size + dpcm->rate - 1) / dpcm->rate; + (dpcm->frac_period_rest + dpcm->rate - 1) / dpcm->rate; add_timer(&dpcm->timer); } @@ -230,10 +229,16 @@ static void dummy_systimer_update(struct dummy_systimer_pcm *dpcm) delta = jiffies - dpcm->base_time; if (!delta) return; - dpcm->base_time = jiffies; - dpcm->frac_pos += delta * dpcm->rate; + dpcm->base_time += delta; + delta *= dpcm->rate; + dpcm->frac_pos += delta; while (dpcm->frac_pos >= dpcm->frac_buffer_size) dpcm->frac_pos -= dpcm->frac_buffer_size; + while (dpcm->frac_period_rest <= delta) { + dpcm->elapsed++; + dpcm->frac_period_rest += dpcm->frac_period_size; + } + dpcm->frac_period_rest -= delta; } static int dummy_systimer_start(struct snd_pcm_substream *substream) @@ -264,6 +269,8 @@ static int dummy_systimer_prepare(struct snd_pcm_substream *substream) dpcm->rate = runtime->rate; dpcm->frac_buffer_size = runtime->buffer_size * HZ; dpcm->frac_period_size = runtime->period_size * HZ; + dpcm->frac_period_rest = dpcm->frac_period_size; + dpcm->elapsed = 0; return 0; } @@ -272,23 +279,29 @@ static void dummy_systimer_callback(unsigned long data) { struct dummy_systimer_pcm *dpcm = (struct dummy_systimer_pcm *)data; unsigned long flags; + int elapsed = 0; spin_lock_irqsave(&dpcm->lock, flags); dummy_systimer_update(dpcm); dummy_systimer_rearm(dpcm); + elapsed = dpcm->elapsed; + dpcm->elapsed = 0; spin_unlock_irqrestore(&dpcm->lock, flags); - snd_pcm_period_elapsed(dpcm->substream); + if (elapsed) + snd_pcm_period_elapsed(dpcm->substream); } static snd_pcm_uframes_t dummy_systimer_pointer(struct snd_pcm_substream *substream) { struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + snd_pcm_uframes_t pos; spin_lock(&dpcm->lock); dummy_systimer_update(dpcm); + pos = dpcm->frac_pos / HZ; spin_unlock(&dpcm->lock); - return dpcm->frac_pos / HZ; + return pos; } static int dummy_systimer_create(struct snd_pcm_substream *substream) From a68c4d11336610dc348620766119db09675707c2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 4 Sep 2009 12:19:36 +0200 Subject: [PATCH 5/9] ALSA: dummy - Fake buffer allocations Instead of allocating the real buffers, use a fake buffer and ignore read/write in the dummy driver so that we can save the resources. For mmap, a single page (unique to the direction, though) is reused to all buffers. When the app requires to read/write the real buffers, pass fake_buffer=0 module option at loading time. This will get back to the old behavior. Signed-off-by: Takashi Iwai --- .../sound/alsa/ALSA-Configuration.txt | 7 ++ sound/drivers/dummy.c | 106 ++++++++++++++++-- 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index bb488b575de..ea16e7d184a 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -518,6 +518,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. pcm_substreams - Number of PCM substreams assigned to each PCM (default = 8, up to 16) hrtimer - Use hrtimer (=1, default) or system timer (=0) + fake_buffer - Fake buffer allocations (default = 1) When multiple PCM devices are created, snd-dummy gives different behavior to each PCM device: @@ -526,6 +527,12 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. 2 = interleaved without mmap 3 = non-interleaved without mmap + As default, snd-dummy drivers doesn't allocate the real buffers + but either ignores read/write or mmap a single dummy page to all + buffer pages, in order to save the resouces. If your apps need + the read/ written buffer data to be consistent, pass fake_buffer=0 + option. + The power-management is supported. Module snd-echo3g diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index e8e29bfb85e..2ee6c8ebe25 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -153,6 +153,7 @@ static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8}; #ifdef CONFIG_HIGH_RES_TIMERS static int hrtimer = 1; #endif +static int fake_buffer = 1; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for dummy soundcard."); @@ -166,6 +167,8 @@ module_param_array(pcm_substreams, int, NULL, 0444); MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-16) for dummy driver."); //module_param_array(midi_devs, int, NULL, 0444); //MODULE_PARM_DESC(midi_devs, "MIDI devices # (0-2) for dummy driver."); +module_param(fake_buffer, bool, 0444); +MODULE_PARM_DESC(fake_buffer, "Fake buffer allocations."); #ifdef CONFIG_HIGH_RES_TIMERS module_param(hrtimer, bool, 0644); MODULE_PARM_DESC(hrtimer, "Use hrtimer as the timer source."); @@ -481,11 +484,8 @@ static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) static int dummy_pcm_prepare(struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dummy *dummy = snd_pcm_substream_chip(substream); - snd_pcm_format_set_silence(runtime->format, runtime->dma_area, - bytes_to_samples(runtime, runtime->dma_bytes)); return dummy->timer_ops->prepare(substream); } @@ -518,12 +518,19 @@ static struct snd_pcm_hardware dummy_pcm_hardware = { static int dummy_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { + if (fake_buffer) { + /* runtime->dma_bytes has to be set manually to allow mmap */ + substream->runtime->dma_bytes = params_buffer_bytes(hw_params); + return 0; + } return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } static int dummy_pcm_hw_free(struct snd_pcm_substream *substream) { + if (fake_buffer) + return 0; return snd_pcm_lib_free_pages(substream); } @@ -570,6 +577,60 @@ static int dummy_pcm_close(struct snd_pcm_substream *substream) return 0; } +/* + * dummy buffer handling + */ + +static void *dummy_page[2]; + +static void free_fake_buffer(void) +{ + if (fake_buffer) { + int i; + for (i = 0; i < 2; i++) + if (dummy_page[i]) { + free_page((unsigned long)dummy_page[i]); + dummy_page[i] = NULL; + } + } +} + +static int alloc_fake_buffer(void) +{ + int i; + + if (!fake_buffer) + return 0; + for (i = 0; i < 2; i++) { + dummy_page[i] = (void *)get_zeroed_page(GFP_KERNEL); + if (!dummy_page[i]) { + free_fake_buffer(); + return -ENOMEM; + } + } + return 0; +} + +static int dummy_pcm_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + void __user *dst, snd_pcm_uframes_t count) +{ + return 0; /* do nothing */ +} + +static int dummy_pcm_silence(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + snd_pcm_uframes_t count) +{ + return 0; /* do nothing */ +} + +static struct page *dummy_pcm_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + return virt_to_page(dummy_page[substream->stream]); /* the same page */ +} + static struct snd_pcm_ops dummy_pcm_ops = { .open = dummy_pcm_open, .close = dummy_pcm_close, @@ -581,10 +642,25 @@ static struct snd_pcm_ops dummy_pcm_ops = { .pointer = dummy_pcm_pointer, }; +static struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .open = dummy_pcm_open, + .close = dummy_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dummy_pcm_hw_params, + .hw_free = dummy_pcm_hw_free, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, + .pointer = dummy_pcm_pointer, + .copy = dummy_pcm_copy, + .silence = dummy_pcm_silence, + .page = dummy_pcm_page, +}; + static int __devinit snd_card_dummy_pcm(struct snd_dummy *dummy, int device, int substreams) { struct snd_pcm *pcm; + struct snd_pcm_ops *ops; int err; err = snd_pcm_new(dummy->card, "Dummy PCM", device, @@ -592,14 +668,21 @@ static int __devinit snd_card_dummy_pcm(struct snd_dummy *dummy, int device, if (err < 0) return err; dummy->pcm = pcm; - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dummy_pcm_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &dummy_pcm_ops); + if (fake_buffer) + ops = &dummy_pcm_ops_no_buf; + else + ops = &dummy_pcm_ops; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops); pcm->private_data = dummy; pcm->info_flags = 0; strcpy(pcm->name, "Dummy PCM"); - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), - 0, 64*1024); + if (!fake_buffer) { + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 0, 64*1024); + } return 0; } @@ -822,6 +905,7 @@ static void snd_dummy_unregister_all(void) for (i = 0; i < ARRAY_SIZE(devices); ++i) platform_device_unregister(devices[i]); platform_driver_unregister(&snd_dummy_driver); + free_fake_buffer(); } static int __init alsa_card_dummy_init(void) @@ -832,6 +916,12 @@ static int __init alsa_card_dummy_init(void) if (err < 0) return err; + err = alloc_fake_buffer(); + if (err < 0) { + platform_driver_unregister(&snd_dummy_driver); + return err; + } + cards = 0; for (i = 0; i < SNDRV_CARDS; i++) { struct platform_device *device; From 6e5265ec34d3b9578973841ddec8b925e986136a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 8 Sep 2009 14:26:51 +0200 Subject: [PATCH 6/9] ALSA: Re-export snd_pcm_format_name() function Re-export snd_pcm_format_name() function to be used outside the PCM core. As a first example, usbaudio is changed to use it now again. Signed-off-by: Takashi Iwai --- include/sound/pcm.h | 2 ++ sound/core/pcm.c | 26 ++++++++++++++------------ sound/usb/usbaudio.c | 4 ++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 23893523dc8..4d5b2407514 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -965,4 +965,6 @@ static inline void snd_pcm_limit_isa_dma_size(int dma, size_t *max) #define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime) +const char *snd_pcm_format_name(snd_pcm_format_t format); + #endif /* __SOUND_PCM_H */ diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 145931a9ff3..0c1440121c2 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -162,18 +162,7 @@ static int snd_pcm_control_ioctl(struct snd_card *card, return -ENOIOCTLCMD; } -#ifdef CONFIG_SND_VERBOSE_PROCFS - -#define STATE(v) [SNDRV_PCM_STATE_##v] = #v -#define STREAM(v) [SNDRV_PCM_STREAM_##v] = #v -#define READY(v) [SNDRV_PCM_READY_##v] = #v -#define XRUN(v) [SNDRV_PCM_XRUN_##v] = #v -#define SILENCE(v) [SNDRV_PCM_SILENCE_##v] = #v -#define TSTAMP(v) [SNDRV_PCM_TSTAMP_##v] = #v -#define ACCESS(v) [SNDRV_PCM_ACCESS_##v] = #v -#define START(v) [SNDRV_PCM_START_##v] = #v #define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v -#define SUBFORMAT(v) [SNDRV_PCM_SUBFORMAT_##v] = #v static char *snd_pcm_format_names[] = { FORMAT(S8), @@ -216,10 +205,23 @@ static char *snd_pcm_format_names[] = { FORMAT(U18_3BE), }; -static const char *snd_pcm_format_name(snd_pcm_format_t format) +const char *snd_pcm_format_name(snd_pcm_format_t format) { return snd_pcm_format_names[format]; } +EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +#ifdef CONFIG_SND_VERBOSE_PROCFS + +#define STATE(v) [SNDRV_PCM_STATE_##v] = #v +#define STREAM(v) [SNDRV_PCM_STREAM_##v] = #v +#define READY(v) [SNDRV_PCM_READY_##v] = #v +#define XRUN(v) [SNDRV_PCM_XRUN_##v] = #v +#define SILENCE(v) [SNDRV_PCM_SILENCE_##v] = #v +#define TSTAMP(v) [SNDRV_PCM_TSTAMP_##v] = #v +#define ACCESS(v) [SNDRV_PCM_ACCESS_##v] = #v +#define START(v) [SNDRV_PCM_START_##v] = #v +#define SUBFORMAT(v) [SNDRV_PCM_SUBFORMAT_##v] = #v static char *snd_pcm_stream_names[] = { STREAM(PLAYBACK), diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c index 44b9cdc8a83..3a53c79f48b 100644 --- a/sound/usb/usbaudio.c +++ b/sound/usb/usbaudio.c @@ -2124,8 +2124,8 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s fp = list_entry(p, struct audioformat, list); snd_iprintf(buffer, " Interface %d\n", fp->iface); snd_iprintf(buffer, " Altset %d\n", fp->altsetting); - snd_iprintf(buffer, " Format: %#x (%d bits)\n", - fp->format, snd_pcm_format_width(fp->format)); + snd_iprintf(buffer, " Format: %s\n", + snd_pcm_format_name(fp->format)); snd_iprintf(buffer, " Channels: %d\n", fp->channels); snd_iprintf(buffer, " Endpoint: %d %s (%s)\n", fp->endpoint & USB_ENDPOINT_NUMBER_MASK, From 4f7454a9970fa0f3e9f1a68201520e3df1bb5224 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 8 Sep 2009 14:29:58 +0200 Subject: [PATCH 7/9] ALSA: Add const prefix to proc helper functions Add appropriate const prefix to char * arguments in proc helper functions. Also fixed the caller side to be proper const pointers. Signed-off-by: Takashi Iwai --- include/sound/info.h | 4 ++-- sound/core/info.c | 4 ++-- sound/core/oss/mixer_oss.c | 3 ++- sound/core/oss/pcm_oss.c | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/sound/info.h b/include/sound/info.h index 7c2ee1a21b0..112e8949e1a 100644 --- a/include/sound/info.h +++ b/include/sound/info.h @@ -110,13 +110,13 @@ void snd_card_info_read_oss(struct snd_info_buffer *buffer); static inline void snd_card_info_read_oss(struct snd_info_buffer *buffer) {} #endif -int snd_iprintf(struct snd_info_buffer *buffer, char *fmt, ...) \ +int snd_iprintf(struct snd_info_buffer *buffer, const char *fmt, ...) \ __attribute__ ((format (printf, 2, 3))); int snd_info_init(void); int snd_info_done(void); int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len); -char *snd_info_get_str(char *dest, char *src, int len); +const char *snd_info_get_str(char *dest, const char *src, int len); struct snd_info_entry *snd_info_create_module_entry(struct module *module, const char *name, struct snd_info_entry *parent); diff --git a/sound/core/info.c b/sound/core/info.c index 35df614f6c5..6eb53930ea1 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -108,7 +108,7 @@ static int resize_info_buffer(struct snd_info_buffer *buffer, * * Returns the size of output string. */ -int snd_iprintf(struct snd_info_buffer *buffer, char *fmt,...) +int snd_iprintf(struct snd_info_buffer *buffer, const char *fmt, ...) { va_list args; int len, res; @@ -727,7 +727,7 @@ EXPORT_SYMBOL(snd_info_get_line); * Returns the updated pointer of the original string so that * it can be used for the next call. */ -char *snd_info_get_str(char *dest, char *src, int len) +const char *snd_info_get_str(char *dest, const char *src, int len) { int c; diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index 5dcd8a52697..772423889eb 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -1154,7 +1154,8 @@ static void snd_mixer_oss_proc_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct snd_mixer_oss *mixer = entry->private_data; - char line[128], str[32], idxstr[16], *cptr; + char line[128], str[32], idxstr[16]; + const char *cptr; int ch, idx; struct snd_mixer_oss_assign_table *tbl; struct slot *slot; diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index dbe406b8259..d8b2d76125b 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -2836,7 +2836,8 @@ static void snd_pcm_oss_proc_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct snd_pcm_str *pstr = entry->private_data; - char line[128], str[32], task_name[32], *ptr; + char line[128], str[32], task_name[32]; + const char *ptr; int idx1; struct snd_pcm_oss_setup *setup, *setup1, template; From 9b151fec139d32ab3acce5da5761d868e205fadd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 8 Sep 2009 14:30:49 +0200 Subject: [PATCH 8/9] ALSA: dummy - Add debug proc file Added the debug proc file to see or change the snd_pcm_hardware fields to emulate. The parameters can be changed by writing to a proc file like: # echo periods_min 4 > /proc/asound/card1/dummy_pcm Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 132 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index 2ee6c8ebe25..ccfbdfa7551 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -33,6 +33,7 @@ #include #include #include +#include #include MODULE_AUTHOR("Jaroslav Kysela "); @@ -686,6 +687,10 @@ static int __devinit snd_card_dummy_pcm(struct snd_dummy *dummy, int device, return 0; } +/* + * mixer interface + */ + #define DUMMY_VOLUME(xname, xindex, addr) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ @@ -816,6 +821,131 @@ static int __devinit snd_card_dummy_new_mixer(struct snd_dummy *dummy) return 0; } +#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_PROC_FS) +/* + * proc interface + */ +static void print_formats(struct snd_info_buffer *buffer) +{ + int i; + + for (i = 0; i < SNDRV_PCM_FORMAT_LAST; i++) { + if (dummy_pcm_hardware.formats & (1ULL << i)) + snd_iprintf(buffer, " %s", snd_pcm_format_name(i)); + } +} + +static void print_rates(struct snd_info_buffer *buffer) +{ + static int rates[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, + 64000, 88200, 96000, 176400, 192000, + }; + int i; + + if (dummy_pcm_hardware.rates & SNDRV_PCM_RATE_CONTINUOUS) + snd_iprintf(buffer, " continuous"); + if (dummy_pcm_hardware.rates & SNDRV_PCM_RATE_KNOT) + snd_iprintf(buffer, " knot"); + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (dummy_pcm_hardware.rates & (1 << i)) + snd_iprintf(buffer, " %d", rates[i]); +} + +#define get_dummy_int_ptr(ofs) \ + (unsigned int *)((char *)&dummy_pcm_hardware + (ofs)) +#define get_dummy_ll_ptr(ofs) \ + (unsigned long long *)((char *)&dummy_pcm_hardware + (ofs)) + +struct dummy_hw_field { + const char *name; + const char *format; + unsigned int offset; + unsigned int size; +}; +#define FIELD_ENTRY(item, fmt) { \ + .name = #item, \ + .format = fmt, \ + .offset = offsetof(struct snd_pcm_hardware, item), \ + .size = sizeof(dummy_pcm_hardware.item) } + +static struct dummy_hw_field fields[] = { + FIELD_ENTRY(formats, "%#llx"), + FIELD_ENTRY(rates, "%#x"), + FIELD_ENTRY(rate_min, "%d"), + FIELD_ENTRY(rate_max, "%d"), + FIELD_ENTRY(channels_min, "%d"), + FIELD_ENTRY(channels_max, "%d"), + FIELD_ENTRY(buffer_bytes_max, "%ld"), + FIELD_ENTRY(period_bytes_min, "%ld"), + FIELD_ENTRY(period_bytes_max, "%ld"), + FIELD_ENTRY(periods_min, "%d"), + FIELD_ENTRY(periods_max, "%d"), +}; + +static void dummy_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + snd_iprintf(buffer, "%s ", fields[i].name); + if (fields[i].size == sizeof(int)) + snd_iprintf(buffer, fields[i].format, + *get_dummy_int_ptr(fields[i].offset)); + else + snd_iprintf(buffer, fields[i].format, + *get_dummy_ll_ptr(fields[i].offset)); + if (!strcmp(fields[i].name, "formats")) + print_formats(buffer); + else if (!strcmp(fields[i].name, "rates")) + print_rates(buffer); + snd_iprintf(buffer, "\n"); + } +} + +static void dummy_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + char line[64]; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + char item[20]; + const char *ptr; + unsigned long long val; + int i; + + ptr = snd_info_get_str(item, line, sizeof(item)); + for (i = 0; i < ARRAY_SIZE(fields); i++) { + if (!strcmp(item, fields[i].name)) + break; + } + if (i >= ARRAY_SIZE(fields)) + continue; + snd_info_get_str(item, ptr, sizeof(item)); + if (strict_strtoull(item, 0, &val)) + continue; + if (fields[i].size == sizeof(int)) + *get_dummy_int_ptr(fields[i].offset) = val; + else + *get_dummy_ll_ptr(fields[i].offset) = val; + } +} + +static void __devinit dummy_proc_init(struct snd_dummy *chip) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "dummy_pcm", &entry)) { + snd_info_set_text_ops(entry, chip, dummy_proc_read); + entry->c.text.write = dummy_proc_write; + entry->mode |= S_IWUSR; + } +} +#else +#define dummy_proc_init(x) +#endif /* CONFIG_SND_DEBUG && CONFIG_PROC_FS */ + static int __devinit snd_dummy_probe(struct platform_device *devptr) { struct snd_card *card; @@ -845,6 +975,8 @@ static int __devinit snd_dummy_probe(struct platform_device *devptr) strcpy(card->shortname, "Dummy"); sprintf(card->longname, "Dummy %i", dev + 1); + dummy_proc_init(dummy); + snd_card_set_dev(card, &devptr->dev); err = snd_card_register(card); From b888d1ce82ebd2bafecf64c765754968e78b4228 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 8 Sep 2009 18:15:17 +0200 Subject: [PATCH 9/9] ALSA: dummy - Increase MAX_PCM_SUBSTREAMS to 128 Increase the limit of PCM substreams to 128. The default value is unchanged; only the max accept value is increased. Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index ccfbdfa7551..6ba066c41d2 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -42,7 +42,7 @@ MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("{{ALSA,Dummy soundcard}}"); #define MAX_PCM_DEVICES 4 -#define MAX_PCM_SUBSTREAMS 16 +#define MAX_PCM_SUBSTREAMS 128 #define MAX_MIDI_DEVICES 2 #if 0 /* emu10k1 emulation */