linux/sound/core/pcm.c
Takashi Sakamoto 2c4842d3b6 ALSA: pcm: add local header file for snd-pcm module
Several files are used to construct PCM core module, a.k.a snd-pcm.
Although available APIs are described in 'include/sound/pcm.h', some of
them are not exported as symbols in kernel space. Such APIs are just for
module local usage.

This commit adds module local header file and move some function prototypes
into it so that scopes of them are controlled properly and developers
get no confusion from unavailable symbols.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-05-26 08:38:14 +02:00

1269 lines
35 KiB
C

/*
* Digital Audio (PCM) abstract layer
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/info.h>
#include "pcm_local.h"
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Abramo Bagnara <abramo@alsa-project.org>");
MODULE_DESCRIPTION("Midlevel PCM code for ALSA.");
MODULE_LICENSE("GPL");
static LIST_HEAD(snd_pcm_devices);
static DEFINE_MUTEX(register_mutex);
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
static LIST_HEAD(snd_pcm_notify_list);
#endif
static int snd_pcm_free(struct snd_pcm *pcm);
static int snd_pcm_dev_free(struct snd_device *device);
static int snd_pcm_dev_register(struct snd_device *device);
static int snd_pcm_dev_disconnect(struct snd_device *device);
static struct snd_pcm *snd_pcm_get(struct snd_card *card, int device)
{
struct snd_pcm *pcm;
list_for_each_entry(pcm, &snd_pcm_devices, list) {
if (pcm->card == card && pcm->device == device)
return pcm;
}
return NULL;
}
static int snd_pcm_next(struct snd_card *card, int device)
{
struct snd_pcm *pcm;
list_for_each_entry(pcm, &snd_pcm_devices, list) {
if (pcm->card == card && pcm->device > device)
return pcm->device;
else if (pcm->card->number > card->number)
return -1;
}
return -1;
}
static int snd_pcm_add(struct snd_pcm *newpcm)
{
struct snd_pcm *pcm;
if (newpcm->internal)
return 0;
list_for_each_entry(pcm, &snd_pcm_devices, list) {
if (pcm->card == newpcm->card && pcm->device == newpcm->device)
return -EBUSY;
if (pcm->card->number > newpcm->card->number ||
(pcm->card == newpcm->card &&
pcm->device > newpcm->device)) {
list_add(&newpcm->list, pcm->list.prev);
return 0;
}
}
list_add_tail(&newpcm->list, &snd_pcm_devices);
return 0;
}
static int snd_pcm_control_ioctl(struct snd_card *card,
struct snd_ctl_file *control,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE:
{
int device;
if (get_user(device, (int __user *)arg))
return -EFAULT;
mutex_lock(&register_mutex);
device = snd_pcm_next(card, device);
mutex_unlock(&register_mutex);
if (put_user(device, (int __user *)arg))
return -EFAULT;
return 0;
}
case SNDRV_CTL_IOCTL_PCM_INFO:
{
struct snd_pcm_info __user *info;
unsigned int device, subdevice;
int stream;
struct snd_pcm *pcm;
struct snd_pcm_str *pstr;
struct snd_pcm_substream *substream;
int err;
info = (struct snd_pcm_info __user *)arg;
if (get_user(device, &info->device))
return -EFAULT;
if (get_user(stream, &info->stream))
return -EFAULT;
if (stream < 0 || stream > 1)
return -EINVAL;
if (get_user(subdevice, &info->subdevice))
return -EFAULT;
mutex_lock(&register_mutex);
pcm = snd_pcm_get(card, device);
if (pcm == NULL) {
err = -ENXIO;
goto _error;
}
pstr = &pcm->streams[stream];
if (pstr->substream_count == 0) {
err = -ENOENT;
goto _error;
}
if (subdevice >= pstr->substream_count) {
err = -ENXIO;
goto _error;
}
for (substream = pstr->substream; substream;
substream = substream->next)
if (substream->number == (int)subdevice)
break;
if (substream == NULL) {
err = -ENXIO;
goto _error;
}
err = snd_pcm_info_user(substream, info);
_error:
mutex_unlock(&register_mutex);
return err;
}
case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE:
{
int val;
if (get_user(val, (int __user *)arg))
return -EFAULT;
control->preferred_subdevice[SND_CTL_SUBDEV_PCM] = val;
return 0;
}
}
return -ENOIOCTLCMD;
}
#define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v
static char *snd_pcm_format_names[] = {
FORMAT(S8),
FORMAT(U8),
FORMAT(S16_LE),
FORMAT(S16_BE),
FORMAT(U16_LE),
FORMAT(U16_BE),
FORMAT(S24_LE),
FORMAT(S24_BE),
FORMAT(U24_LE),
FORMAT(U24_BE),
FORMAT(S32_LE),
FORMAT(S32_BE),
FORMAT(U32_LE),
FORMAT(U32_BE),
FORMAT(FLOAT_LE),
FORMAT(FLOAT_BE),
FORMAT(FLOAT64_LE),
FORMAT(FLOAT64_BE),
FORMAT(IEC958_SUBFRAME_LE),
FORMAT(IEC958_SUBFRAME_BE),
FORMAT(MU_LAW),
FORMAT(A_LAW),
FORMAT(IMA_ADPCM),
FORMAT(MPEG),
FORMAT(GSM),
FORMAT(SPECIAL),
FORMAT(S24_3LE),
FORMAT(S24_3BE),
FORMAT(U24_3LE),
FORMAT(U24_3BE),
FORMAT(S20_3LE),
FORMAT(S20_3BE),
FORMAT(U20_3LE),
FORMAT(U20_3BE),
FORMAT(S18_3LE),
FORMAT(S18_3BE),
FORMAT(U18_3LE),
FORMAT(U18_3BE),
FORMAT(G723_24),
FORMAT(G723_24_1B),
FORMAT(G723_40),
FORMAT(G723_40_1B),
FORMAT(DSD_U8),
FORMAT(DSD_U16_LE),
FORMAT(DSD_U32_LE),
FORMAT(DSD_U16_BE),
FORMAT(DSD_U32_BE),
};
/**
* snd_pcm_format_name - Return a name string for the given PCM format
* @format: PCM format
*/
const char *snd_pcm_format_name(snd_pcm_format_t format)
{
if ((__force unsigned int)format >= ARRAY_SIZE(snd_pcm_format_names))
return "Unknown";
return snd_pcm_format_names[(__force unsigned int)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),
STREAM(CAPTURE),
};
static char *snd_pcm_state_names[] = {
STATE(OPEN),
STATE(SETUP),
STATE(PREPARED),
STATE(RUNNING),
STATE(XRUN),
STATE(DRAINING),
STATE(PAUSED),
STATE(SUSPENDED),
};
static char *snd_pcm_access_names[] = {
ACCESS(MMAP_INTERLEAVED),
ACCESS(MMAP_NONINTERLEAVED),
ACCESS(MMAP_COMPLEX),
ACCESS(RW_INTERLEAVED),
ACCESS(RW_NONINTERLEAVED),
};
static char *snd_pcm_subformat_names[] = {
SUBFORMAT(STD),
};
static char *snd_pcm_tstamp_mode_names[] = {
TSTAMP(NONE),
TSTAMP(ENABLE),
};
static const char *snd_pcm_stream_name(int stream)
{
return snd_pcm_stream_names[stream];
}
static const char *snd_pcm_access_name(snd_pcm_access_t access)
{
return snd_pcm_access_names[(__force int)access];
}
static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat)
{
return snd_pcm_subformat_names[(__force int)subformat];
}
static const char *snd_pcm_tstamp_mode_name(int mode)
{
return snd_pcm_tstamp_mode_names[mode];
}
static const char *snd_pcm_state_name(snd_pcm_state_t state)
{
return snd_pcm_state_names[(__force int)state];
}
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
#include <linux/soundcard.h>
static const char *snd_pcm_oss_format_name(int format)
{
switch (format) {
case AFMT_MU_LAW:
return "MU_LAW";
case AFMT_A_LAW:
return "A_LAW";
case AFMT_IMA_ADPCM:
return "IMA_ADPCM";
case AFMT_U8:
return "U8";
case AFMT_S16_LE:
return "S16_LE";
case AFMT_S16_BE:
return "S16_BE";
case AFMT_S8:
return "S8";
case AFMT_U16_LE:
return "U16_LE";
case AFMT_U16_BE:
return "U16_BE";
case AFMT_MPEG:
return "MPEG";
default:
return "unknown";
}
}
#endif
static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream,
struct snd_info_buffer *buffer)
{
struct snd_pcm_info *info;
int err;
if (! substream)
return;
info = kmalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return;
err = snd_pcm_info(substream, info);
if (err < 0) {
snd_iprintf(buffer, "error %d\n", err);
kfree(info);
return;
}
snd_iprintf(buffer, "card: %d\n", info->card);
snd_iprintf(buffer, "device: %d\n", info->device);
snd_iprintf(buffer, "subdevice: %d\n", info->subdevice);
snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream));
snd_iprintf(buffer, "id: %s\n", info->id);
snd_iprintf(buffer, "name: %s\n", info->name);
snd_iprintf(buffer, "subname: %s\n", info->subname);
snd_iprintf(buffer, "class: %d\n", info->dev_class);
snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass);
snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count);
snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail);
kfree(info);
}
static void snd_pcm_stream_proc_info_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_pcm_proc_info_read(((struct snd_pcm_str *)entry->private_data)->substream,
buffer);
}
static void snd_pcm_substream_proc_info_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
snd_pcm_proc_info_read(entry->private_data, buffer);
}
static void snd_pcm_substream_proc_hw_params_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_substream *substream = entry->private_data;
struct snd_pcm_runtime *runtime;
mutex_lock(&substream->pcm->open_mutex);
runtime = substream->runtime;
if (!runtime) {
snd_iprintf(buffer, "closed\n");
goto unlock;
}
if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
snd_iprintf(buffer, "no setup\n");
goto unlock;
}
snd_iprintf(buffer, "access: %s\n", snd_pcm_access_name(runtime->access));
snd_iprintf(buffer, "format: %s\n", snd_pcm_format_name(runtime->format));
snd_iprintf(buffer, "subformat: %s\n", snd_pcm_subformat_name(runtime->subformat));
snd_iprintf(buffer, "channels: %u\n", runtime->channels);
snd_iprintf(buffer, "rate: %u (%u/%u)\n", runtime->rate, runtime->rate_num, runtime->rate_den);
snd_iprintf(buffer, "period_size: %lu\n", runtime->period_size);
snd_iprintf(buffer, "buffer_size: %lu\n", runtime->buffer_size);
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
if (substream->oss.oss) {
snd_iprintf(buffer, "OSS format: %s\n", snd_pcm_oss_format_name(runtime->oss.format));
snd_iprintf(buffer, "OSS channels: %u\n", runtime->oss.channels);
snd_iprintf(buffer, "OSS rate: %u\n", runtime->oss.rate);
snd_iprintf(buffer, "OSS period bytes: %lu\n", (unsigned long)runtime->oss.period_bytes);
snd_iprintf(buffer, "OSS periods: %u\n", runtime->oss.periods);
snd_iprintf(buffer, "OSS period frames: %lu\n", (unsigned long)runtime->oss.period_frames);
}
#endif
unlock:
mutex_unlock(&substream->pcm->open_mutex);
}
static void snd_pcm_substream_proc_sw_params_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_substream *substream = entry->private_data;
struct snd_pcm_runtime *runtime;
mutex_lock(&substream->pcm->open_mutex);
runtime = substream->runtime;
if (!runtime) {
snd_iprintf(buffer, "closed\n");
goto unlock;
}
if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
snd_iprintf(buffer, "no setup\n");
goto unlock;
}
snd_iprintf(buffer, "tstamp_mode: %s\n", snd_pcm_tstamp_mode_name(runtime->tstamp_mode));
snd_iprintf(buffer, "period_step: %u\n", runtime->period_step);
snd_iprintf(buffer, "avail_min: %lu\n", runtime->control->avail_min);
snd_iprintf(buffer, "start_threshold: %lu\n", runtime->start_threshold);
snd_iprintf(buffer, "stop_threshold: %lu\n", runtime->stop_threshold);
snd_iprintf(buffer, "silence_threshold: %lu\n", runtime->silence_threshold);
snd_iprintf(buffer, "silence_size: %lu\n", runtime->silence_size);
snd_iprintf(buffer, "boundary: %lu\n", runtime->boundary);
unlock:
mutex_unlock(&substream->pcm->open_mutex);
}
static void snd_pcm_substream_proc_status_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_substream *substream = entry->private_data;
struct snd_pcm_runtime *runtime;
struct snd_pcm_status status;
int err;
mutex_lock(&substream->pcm->open_mutex);
runtime = substream->runtime;
if (!runtime) {
snd_iprintf(buffer, "closed\n");
goto unlock;
}
memset(&status, 0, sizeof(status));
err = snd_pcm_status(substream, &status);
if (err < 0) {
snd_iprintf(buffer, "error %d\n", err);
goto unlock;
}
snd_iprintf(buffer, "state: %s\n", snd_pcm_state_name(status.state));
snd_iprintf(buffer, "owner_pid : %d\n", pid_vnr(substream->pid));
snd_iprintf(buffer, "trigger_time: %ld.%09ld\n",
status.trigger_tstamp.tv_sec, status.trigger_tstamp.tv_nsec);
snd_iprintf(buffer, "tstamp : %ld.%09ld\n",
status.tstamp.tv_sec, status.tstamp.tv_nsec);
snd_iprintf(buffer, "delay : %ld\n", status.delay);
snd_iprintf(buffer, "avail : %ld\n", status.avail);
snd_iprintf(buffer, "avail_max : %ld\n", status.avail_max);
snd_iprintf(buffer, "-----\n");
snd_iprintf(buffer, "hw_ptr : %ld\n", runtime->status->hw_ptr);
snd_iprintf(buffer, "appl_ptr : %ld\n", runtime->control->appl_ptr);
unlock:
mutex_unlock(&substream->pcm->open_mutex);
}
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
static void snd_pcm_xrun_injection_write(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_substream *substream = entry->private_data;
struct snd_pcm_runtime *runtime;
snd_pcm_stream_lock_irq(substream);
runtime = substream->runtime;
if (runtime && runtime->status->state == SNDRV_PCM_STATE_RUNNING)
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
snd_pcm_stream_unlock_irq(substream);
}
static void snd_pcm_xrun_debug_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_str *pstr = entry->private_data;
snd_iprintf(buffer, "%d\n", pstr->xrun_debug);
}
static void snd_pcm_xrun_debug_write(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm_str *pstr = entry->private_data;
char line[64];
if (!snd_info_get_line(buffer, line, sizeof(line)))
pstr->xrun_debug = simple_strtoul(line, NULL, 10);
}
#endif
static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr)
{
struct snd_pcm *pcm = pstr->pcm;
struct snd_info_entry *entry;
char name[16];
sprintf(name, "pcm%i%c", pcm->device,
pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
if ((entry = snd_info_create_card_entry(pcm->card, name, pcm->card->proc_root)) == NULL)
return -ENOMEM;
entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
pstr->proc_root = entry;
if ((entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root)) != NULL) {
snd_info_set_text_ops(entry, pstr, snd_pcm_stream_proc_info_read);
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
pstr->proc_info_entry = entry;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
if ((entry = snd_info_create_card_entry(pcm->card, "xrun_debug",
pstr->proc_root)) != NULL) {
entry->c.text.read = snd_pcm_xrun_debug_read;
entry->c.text.write = snd_pcm_xrun_debug_write;
entry->mode |= S_IWUSR;
entry->private_data = pstr;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
pstr->proc_xrun_debug_entry = entry;
#endif
return 0;
}
static int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr)
{
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
snd_info_free_entry(pstr->proc_xrun_debug_entry);
pstr->proc_xrun_debug_entry = NULL;
#endif
snd_info_free_entry(pstr->proc_info_entry);
pstr->proc_info_entry = NULL;
snd_info_free_entry(pstr->proc_root);
pstr->proc_root = NULL;
return 0;
}
static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream)
{
struct snd_info_entry *entry;
struct snd_card *card;
char name[16];
card = substream->pcm->card;
sprintf(name, "sub%i", substream->number);
if ((entry = snd_info_create_card_entry(card, name, substream->pstr->proc_root)) == NULL)
return -ENOMEM;
entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return -ENOMEM;
}
substream->proc_root = entry;
if ((entry = snd_info_create_card_entry(card, "info", substream->proc_root)) != NULL) {
snd_info_set_text_ops(entry, substream,
snd_pcm_substream_proc_info_read);
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_info_entry = entry;
if ((entry = snd_info_create_card_entry(card, "hw_params", substream->proc_root)) != NULL) {
snd_info_set_text_ops(entry, substream,
snd_pcm_substream_proc_hw_params_read);
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_hw_params_entry = entry;
if ((entry = snd_info_create_card_entry(card, "sw_params", substream->proc_root)) != NULL) {
snd_info_set_text_ops(entry, substream,
snd_pcm_substream_proc_sw_params_read);
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_sw_params_entry = entry;
if ((entry = snd_info_create_card_entry(card, "status", substream->proc_root)) != NULL) {
snd_info_set_text_ops(entry, substream,
snd_pcm_substream_proc_status_read);
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_status_entry = entry;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
entry = snd_info_create_card_entry(card, "xrun_injection",
substream->proc_root);
if (entry) {
entry->private_data = substream;
entry->c.text.read = NULL;
entry->c.text.write = snd_pcm_xrun_injection_write;
entry->mode = S_IFREG | S_IWUSR;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_xrun_injection_entry = entry;
#endif /* CONFIG_SND_PCM_XRUN_DEBUG */
return 0;
}
static int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream)
{
snd_info_free_entry(substream->proc_info_entry);
substream->proc_info_entry = NULL;
snd_info_free_entry(substream->proc_hw_params_entry);
substream->proc_hw_params_entry = NULL;
snd_info_free_entry(substream->proc_sw_params_entry);
substream->proc_sw_params_entry = NULL;
snd_info_free_entry(substream->proc_status_entry);
substream->proc_status_entry = NULL;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
snd_info_free_entry(substream->proc_xrun_injection_entry);
substream->proc_xrun_injection_entry = NULL;
#endif
snd_info_free_entry(substream->proc_root);
substream->proc_root = NULL;
return 0;
}
#else /* !CONFIG_SND_VERBOSE_PROCFS */
static inline int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { return 0; }
static inline int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) { return 0; }
static inline int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) { return 0; }
static inline int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream) { return 0; }
#endif /* CONFIG_SND_VERBOSE_PROCFS */
static const struct attribute_group *pcm_dev_attr_groups[];
/**
* snd_pcm_new_stream - create a new PCM stream
* @pcm: the pcm instance
* @stream: the stream direction, SNDRV_PCM_STREAM_XXX
* @substream_count: the number of substreams
*
* Creates a new stream for the pcm.
* The corresponding stream on the pcm must have been empty before
* calling this, i.e. zero must be given to the argument of
* snd_pcm_new().
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
int idx, err;
struct snd_pcm_str *pstr = &pcm->streams[stream];
struct snd_pcm_substream *substream, *prev;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
mutex_init(&pstr->oss.setup_mutex);
#endif
pstr->stream = stream;
pstr->pcm = pcm;
pstr->substream_count = substream_count;
if (!substream_count)
return 0;
snd_device_initialize(&pstr->dev, pcm->card);
pstr->dev.groups = pcm_dev_attr_groups;
dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
if (!pcm->internal) {
err = snd_pcm_stream_proc_init(pstr);
if (err < 0) {
pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
return err;
}
}
prev = NULL;
for (idx = 0, prev = NULL; idx < substream_count; idx++) {
substream = kzalloc(sizeof(*substream), GFP_KERNEL);
if (!substream)
return -ENOMEM;
substream->pcm = pcm;
substream->pstr = pstr;
substream->number = idx;
substream->stream = stream;
sprintf(substream->name, "subdevice #%i", idx);
substream->buffer_bytes_max = UINT_MAX;
if (prev == NULL)
pstr->substream = substream;
else
prev->next = substream;
if (!pcm->internal) {
err = snd_pcm_substream_proc_init(substream);
if (err < 0) {
pcm_err(pcm,
"Error in snd_pcm_stream_proc_init\n");
if (prev == NULL)
pstr->substream = NULL;
else
prev->next = NULL;
kfree(substream);
return err;
}
}
substream->group = &substream->self_group;
spin_lock_init(&substream->self_group.lock);
mutex_init(&substream->self_group.mutex);
INIT_LIST_HEAD(&substream->self_group.substreams);
list_add_tail(&substream->link_list, &substream->self_group.substreams);
atomic_set(&substream->mmap_count, 0);
prev = substream;
}
return 0;
}
EXPORT_SYMBOL(snd_pcm_new_stream);
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return -ENOMEM;
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
INIT_LIST_HEAD(&pcm->list);
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
/**
* snd_pcm_new - create a new PCM instance
* @card: the card instance
* @id: the id string
* @device: the device index (zero based)
* @playback_count: the number of substreams for playback
* @capture_count: the number of substreams for capture
* @rpcm: the pointer to store the new pcm instance
*
* Creates a new PCM instance.
*
* The pcm operators have to be set afterwards to the new instance
* via snd_pcm_set_ops().
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, struct snd_pcm **rpcm)
{
return _snd_pcm_new(card, id, device, playback_count, capture_count,
false, rpcm);
}
EXPORT_SYMBOL(snd_pcm_new);
/**
* snd_pcm_new_internal - create a new internal PCM instance
* @card: the card instance
* @id: the id string
* @device: the device index (zero based - shared with normal PCMs)
* @playback_count: the number of substreams for playback
* @capture_count: the number of substreams for capture
* @rpcm: the pointer to store the new pcm instance
*
* Creates a new internal PCM instance with no userspace device or procfs
* entries. This is used by ASoC Back End PCMs in order to create a PCM that
* will only be used internally by kernel drivers. i.e. it cannot be opened
* by userspace. It provides existing ASoC components drivers with a substream
* and access to any private data.
*
* The pcm operators have to be set afterwards to the new instance
* via snd_pcm_set_ops().
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_new_internal(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count,
struct snd_pcm **rpcm)
{
return _snd_pcm_new(card, id, device, playback_count, capture_count,
true, rpcm);
}
EXPORT_SYMBOL(snd_pcm_new_internal);
static void free_chmap(struct snd_pcm_str *pstr)
{
if (pstr->chmap_kctl) {
snd_ctl_remove(pstr->pcm->card, pstr->chmap_kctl);
pstr->chmap_kctl = NULL;
}
}
static void snd_pcm_free_stream(struct snd_pcm_str * pstr)
{
struct snd_pcm_substream *substream, *substream_next;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
struct snd_pcm_oss_setup *setup, *setupn;
#endif
substream = pstr->substream;
while (substream) {
substream_next = substream->next;
snd_pcm_timer_done(substream);
snd_pcm_substream_proc_done(substream);
kfree(substream);
substream = substream_next;
}
snd_pcm_stream_proc_done(pstr);
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
for (setup = pstr->oss.setup_list; setup; setup = setupn) {
setupn = setup->next;
kfree(setup->task_name);
kfree(setup);
}
#endif
free_chmap(pstr);
if (pstr->substream_count)
put_device(&pstr->dev);
}
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
#define pcm_call_notify(pcm, call) \
do { \
struct snd_pcm_notify *_notify; \
list_for_each_entry(_notify, &snd_pcm_notify_list, list) \
_notify->call(pcm); \
} while (0)
#else
#define pcm_call_notify(pcm, call) do {} while (0)
#endif
static int snd_pcm_free(struct snd_pcm *pcm)
{
if (!pcm)
return 0;
if (!pcm->internal)
pcm_call_notify(pcm, n_unregister);
if (pcm->private_free)
pcm->private_free(pcm);
snd_pcm_lib_preallocate_free_for_all(pcm);
snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]);
snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_CAPTURE]);
kfree(pcm);
return 0;
}
static int snd_pcm_dev_free(struct snd_device *device)
{
struct snd_pcm *pcm = device->device_data;
return snd_pcm_free(pcm);
}
int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
struct file *file,
struct snd_pcm_substream **rsubstream)
{
struct snd_pcm_str * pstr;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
struct snd_card *card;
int prefer_subdevice;
size_t size;
if (snd_BUG_ON(!pcm || !rsubstream))
return -ENXIO;
if (snd_BUG_ON(stream != SNDRV_PCM_STREAM_PLAYBACK &&
stream != SNDRV_PCM_STREAM_CAPTURE))
return -EINVAL;
*rsubstream = NULL;
pstr = &pcm->streams[stream];
if (pstr->substream == NULL || pstr->substream_count == 0)
return -ENODEV;
card = pcm->card;
prefer_subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_PCM);
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
int opposite = !stream;
for (substream = pcm->streams[opposite].substream; substream;
substream = substream->next) {
if (SUBSTREAM_BUSY(substream))
return -EAGAIN;
}
}
if (file->f_flags & O_APPEND) {
if (prefer_subdevice < 0) {
if (pstr->substream_count > 1)
return -EINVAL; /* must be unique */
substream = pstr->substream;
} else {
for (substream = pstr->substream; substream;
substream = substream->next)
if (substream->number == prefer_subdevice)
break;
}
if (! substream)
return -ENODEV;
if (! SUBSTREAM_BUSY(substream))
return -EBADFD;
substream->ref_count++;
*rsubstream = substream;
return 0;
}
for (substream = pstr->substream; substream; substream = substream->next) {
if (!SUBSTREAM_BUSY(substream) &&
(prefer_subdevice == -1 ||
substream->number == prefer_subdevice))
break;
}
if (substream == NULL)
return -EAGAIN;
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
if (runtime == NULL)
return -ENOMEM;
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = snd_malloc_pages(size, GFP_KERNEL);
if (runtime->status == NULL) {
kfree(runtime);
return -ENOMEM;
}
memset((void*)runtime->status, 0, size);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
runtime->control = snd_malloc_pages(size, GFP_KERNEL);
if (runtime->control == NULL) {
snd_free_pages((void*)runtime->status,
PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
kfree(runtime);
return -ENOMEM;
}
memset((void*)runtime->control, 0, size);
init_waitqueue_head(&runtime->sleep);
init_waitqueue_head(&runtime->tsleep);
runtime->status->state = SNDRV_PCM_STATE_OPEN;
substream->runtime = runtime;
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->f_flags = file->f_flags;
substream->pid = get_pid(task_pid(current));
pstr->substream_opened++;
*rsubstream = substream;
return 0;
}
void snd_pcm_detach_substream(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
if (PCM_RUNTIME_CHECK(substream))
return;
runtime = substream->runtime;
if (runtime->private_free != NULL)
runtime->private_free(runtime);
snd_free_pages((void*)runtime->status,
PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
snd_free_pages((void*)runtime->control,
PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)));
kfree(runtime->hw_constraints.rules);
kfree(runtime);
substream->runtime = NULL;
put_pid(substream->pid);
substream->pid = NULL;
substream->pstr->substream_opened--;
}
static ssize_t show_pcm_class(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct snd_pcm_str *pstr = container_of(dev, struct snd_pcm_str, dev);
struct snd_pcm *pcm = pstr->pcm;
const char *str;
static const char *strs[SNDRV_PCM_CLASS_LAST + 1] = {
[SNDRV_PCM_CLASS_GENERIC] = "generic",
[SNDRV_PCM_CLASS_MULTI] = "multi",
[SNDRV_PCM_CLASS_MODEM] = "modem",
[SNDRV_PCM_CLASS_DIGITIZER] = "digitizer",
};
if (pcm->dev_class > SNDRV_PCM_CLASS_LAST)
str = "none";
else
str = strs[pcm->dev_class];
return snprintf(buf, PAGE_SIZE, "%s\n", str);
}
static DEVICE_ATTR(pcm_class, S_IRUGO, show_pcm_class, NULL);
static struct attribute *pcm_dev_attrs[] = {
&dev_attr_pcm_class.attr,
NULL
};
static struct attribute_group pcm_dev_attr_group = {
.attrs = pcm_dev_attrs,
};
static const struct attribute_group *pcm_dev_attr_groups[] = {
&pcm_dev_attr_group,
NULL
};
static int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
struct snd_pcm_substream *substream;
struct snd_pcm *pcm;
if (snd_BUG_ON(!device || !device->device_data))
return -ENXIO;
pcm = device->device_data;
if (pcm->internal)
return 0;
mutex_lock(&register_mutex);
err = snd_pcm_add(pcm);
if (err)
goto unlock;
for (cidx = 0; cidx < 2; cidx++) {
int devtype = -1;
if (pcm->streams[cidx].substream == NULL)
continue;
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
/* register pcm */
err = snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm,
&pcm->streams[cidx].dev);
if (err < 0) {
list_del_init(&pcm->list);
goto unlock;
}
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
snd_pcm_timer_init(substream);
}
pcm_call_notify(pcm, n_register);
unlock:
mutex_unlock(&register_mutex);
return err;
}
static int snd_pcm_dev_disconnect(struct snd_device *device)
{
struct snd_pcm *pcm = device->device_data;
struct snd_pcm_substream *substream;
int cidx;
mutex_lock(&register_mutex);
mutex_lock(&pcm->open_mutex);
wake_up(&pcm->open_wait);
list_del_init(&pcm->list);
for (cidx = 0; cidx < 2; cidx++) {
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) {
snd_pcm_stream_lock_irq(substream);
if (substream->runtime) {
substream->runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED;
wake_up(&substream->runtime->sleep);
wake_up(&substream->runtime->tsleep);
}
snd_pcm_stream_unlock_irq(substream);
}
}
if (!pcm->internal) {
pcm_call_notify(pcm, n_disconnect);
}
for (cidx = 0; cidx < 2; cidx++) {
if (!pcm->internal)
snd_unregister_device(&pcm->streams[cidx].dev);
free_chmap(&pcm->streams[cidx]);
}
mutex_unlock(&pcm->open_mutex);
mutex_unlock(&register_mutex);
return 0;
}
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
/**
* snd_pcm_notify - Add/remove the notify list
* @notify: PCM notify list
* @nfree: 0 = register, 1 = unregister
*
* This adds the given notifier to the global list so that the callback is
* called for each registered PCM devices. This exists only for PCM OSS
* emulation, so far.
*/
int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree)
{
struct snd_pcm *pcm;
if (snd_BUG_ON(!notify ||
!notify->n_register ||
!notify->n_unregister ||
!notify->n_disconnect))
return -EINVAL;
mutex_lock(&register_mutex);
if (nfree) {
list_del(&notify->list);
list_for_each_entry(pcm, &snd_pcm_devices, list)
notify->n_unregister(pcm);
} else {
list_add_tail(&notify->list, &snd_pcm_notify_list);
list_for_each_entry(pcm, &snd_pcm_devices, list)
notify->n_register(pcm);
}
mutex_unlock(&register_mutex);
return 0;
}
EXPORT_SYMBOL(snd_pcm_notify);
#endif /* CONFIG_SND_PCM_OSS */
#ifdef CONFIG_SND_PROC_FS
/*
* Info interface
*/
static void snd_pcm_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_pcm *pcm;
mutex_lock(&register_mutex);
list_for_each_entry(pcm, &snd_pcm_devices, list) {
snd_iprintf(buffer, "%02i-%02i: %s : %s",
pcm->card->number, pcm->device, pcm->id, pcm->name);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
snd_iprintf(buffer, " : playback %i",
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count);
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
snd_iprintf(buffer, " : capture %i",
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count);
snd_iprintf(buffer, "\n");
}
mutex_unlock(&register_mutex);
}
static struct snd_info_entry *snd_pcm_proc_entry;
static void snd_pcm_proc_init(void)
{
struct snd_info_entry *entry;
if ((entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL)) != NULL) {
snd_info_set_text_ops(entry, NULL, snd_pcm_proc_read);
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
snd_pcm_proc_entry = entry;
}
static void snd_pcm_proc_done(void)
{
snd_info_free_entry(snd_pcm_proc_entry);
}
#else /* !CONFIG_SND_PROC_FS */
#define snd_pcm_proc_init()
#define snd_pcm_proc_done()
#endif /* CONFIG_SND_PROC_FS */
/*
* ENTRY functions
*/
static int __init alsa_pcm_init(void)
{
snd_ctl_register_ioctl(snd_pcm_control_ioctl);
snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl);
snd_pcm_proc_init();
return 0;
}
static void __exit alsa_pcm_exit(void)
{
snd_ctl_unregister_ioctl(snd_pcm_control_ioctl);
snd_ctl_unregister_ioctl_compat(snd_pcm_control_ioctl);
snd_pcm_proc_done();
}
module_init(alsa_pcm_init)
module_exit(alsa_pcm_exit)