+--------+ +--------+ +--------+
|apaly | |arecord | |amixer |
+--------+ +--------+ +--------+
| ^ ^
V | V
+--------------------------------+
| ALSA Library API |
| (tinyalsa, alsa-lib) |
+--------------------------------+
user space ^
----------------------|---------------------
kernel space V
+--------------------------------+
| ALSA CORE |
| +-------+ +-------+ +------+ |
| | PCM | |CONTROL| | MIDI |...|
| +-------+ +-------+ +------+ |
+--------------------------------+
|
+--------------------------------+
| ASoC CORE |
+--------------------------------+
|
+--------------------------------+
| hardware driver |
| +-------+ +--------+ +-----+ |
| |Machine| |Platform| |Codec| |
| +-------+ +--------+ +-----+ |
+--------------------------------+
+------------------------------------------+
| Machine |
| +--------------+ +--------------+ |
| | Platform | | Codec | |
| | | I2S | | |
| | cpu_dai|<---->|codec_dai | |
| | | | | |
| +--------------+ +--------------+ |
+------------------------------------------+
pcm数据流向,以playback为例
copy_from_user DMA I2S DAC
^ ^ ^ ^
+---------+ | +----------+ | +-----------+ | +-----+ | +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+ +----------+ +-----------+ +-----+ +------+
alsa驱动框架核心层:创建声卡设备的控制接口和PCM设备
sound/core/pcm_native.c 对下层的PCM驱动提供包装,为上层提供统一的接口。
以播放为例。
handle 返回PCM handle,pcm_name为设备名,stream为SND_PCM_STREAM_PLAYBACK,
open_mode 为打开pcm句柄时的一些附加参数 SND_PCM_NONBLOCK 非阻塞打开(默认阻塞打开),SND_PCM_ASYNC 异步模式打开。
err = snd_pcm_open(&handle, pcm_name, stream, open_mode);
snd_pcm_hw_open
int snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name,
int card, int device, int subdevice,
snd_pcm_stream_t stream, int mode,
int mmap_emulation ATTRIBUTE_UNUSED,
int sync_ptr_ioctl)
{
……
snd_ctl_close(ctl);
return snd_pcm_hw_open_fd(pcmp, name, fd, sync_ptr_ioctl);
_err:
snd_ctl_close(ctl);
return ret;
}
snd_pcm_open
└── snd_pcm_open_noupdate
└── snd_pcm_open_conf
└── snd_dlobj_cache_get
└── _snd_pcm_hw_open [假定传入hw参数,从lib库中获取]
└── snd_pcm_hw_open
└── snd_pcm_hw_open_fd
└── pcm->fast_ops = &snd_pcm_hw_fast_ops;
参数如下:
static const char *const build_in_pcms[] = {
"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
"linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share",
"shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
NULL
};
alsa-lib的pcm_hw.c中定义了snd_pcm_hw_fast_ops,其中.writei = snd_pcm_hw_writei,这个后面会用到。
static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = {
……
.writei = snd_pcm_hw_writei,
.writen = snd_pcm_hw_writen,
.readi = snd_pcm_hw_readi,
.readn = snd_pcm_hw_readn,
……
};
if (interleaved) {
if (optind > argc - 1) {
if (stream == SND_PCM_STREAM_PLAYBACK)
playback(NULL);
else
capture(NULL);
} else {
while (optind <= argc - 1) {
if (stream == SND_PCM_STREAM_PLAYBACK)
playback(argv[optind++]);
else
capture(argv[optind++]);
}
}
} else {
if (stream == SND_PCM_STREAM_PLAYBACK)
playbackv(&argv[optind], argc - optind);
else
capturev(&argv[optind], argc - optind);
}
switch(file_type) {
case FORMAT_AU:
playback_au(name, &loaded);
break;
case FORMAT_VOC:
playback_voc(name, &loaded);
break;
case FORMAT_WAVE:
playback_wave(name, &loaded);
break;
case FORMAT_RAW:
playback_raw(name, &loaded);
break;
default:
/* parse the file header */
if (playback_au(name, &loaded) < 0 &&
playback_voc(name, &loaded) < 0 &&
playback_wave(name, &loaded) < 0)
playback_raw(name, &loaded); /* should be raw data */
break;
}
static int playback_raw(char *name, int *loaded)
{
init_raw_data();// setting the globals for playing raw data
pbrec_count = calc_count(); //calculate the data count to read from/to dsp
playback_go(fd, *loaded, pbrec_count, FORMAT_RAW, name);// playing raw data
return 0;
}
static void playback_go(int fd, size_t loaded, off64_t count, int rtype, char *name)
{
int l, r;
off64_t written = 0;
off64_t c;
header(rtype, name);
set_params();
while (loaded > chunk_bytes && written < count && !in_aborting) {
if (pcm_write(audiobuf + written, chunk_size) <= 0)
return;
written += chunk_bytes;
loaded -= chunk_bytes;
}
if (written > 0 && loaded > 0)
memmove(audiobuf, audiobuf + written, loaded);
//由$2所指内存区域复制$3个字节到$1所指内存区域。
l = loaded;
while (written < count && !in_aborting) {
do {
c = count - written;
if (c > chunk_bytes)
c = chunk_bytes;
/* c < l, there is more data loaded
* then we actually need to write
*/
if (c < l)
l = c;
c -= l;
if (c == 0)
break;
r = safe_read(fd, audiobuf + l, c);
if (r < 0) {
perror(name);
prg_exit(EXIT_FAILURE);
}
fdcount += r;
if (r == 0)
break;
l += r;
} while ((size_t)l < chunk_bytes);
l = l * 8 / bits_per_frame;
r = pcm_write(audiobuf, l);
if (r != l)
break;
r = r * bits_per_frame / 8;
written += r;
l = 0;
}
snd_pcm_nonblock(handle, 0);
snd_pcm_drain(handle);
snd_pcm_nonblock(handle, nonblock);
}
static ssize_t pcm_write(u_char *data, size_t count)
{
ssize_t r;
ssize_t result = 0;
if (count < chunk_size) {
snd_pcm_format_set_silence(hwparams.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwparams.channels);
count = chunk_size;
}
data = remap_data(data, count);
while (count > 0 && !in_aborting) {
if (test_position)
do_test_position();
check_stdin();
r = writei_func(handle, data, count); //主要是这一句
if (test_position)
do_test_position();
if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) {
if (!test_nowait)
snd_pcm_wait(handle, 100);
} else if (r == -EPIPE) {
xrun();
} else if (r == -ESTRPIPE) {
suspend();
} else if (r < 0) {
error(_("write error: %s"), snd_strerror(r));
prg_exit(EXIT_FAILURE);
}
if (r > 0) {
if (vumeter)
compute_max_peak(data, r * hwparams.channels);
result += r;
count -= r;
data += r * bits_per_frame / 8;
}
}
return result;
}
aplay.c的main函数中
if (mmap_flag) {
writei_func = snd_pcm_mmap_writei;
readi_func = snd_pcm_mmap_readi;
writen_func = snd_pcm_mmap_writen;
readn_func = snd_pcm_mmap_readn;
} else {
writei_func = snd_pcm_writei; //interleaved frames
readi_func = snd_pcm_readi;
writen_func = snd_pcm_writen; //non-interleaved frames
readn_func = snd_pcm_readn;
}
static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size);
以上都在aplay.c中,为应用层
snd_pcm_writei在alsa-lib的pcm.c中,为alsa-lib层。
**以snd_pcm_t handle传入,作为snd_pcm_writei的snd_pcm_t pcm
snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
{
assert(pcm);
assert(size == 0 || buffer);
if (CHECK_SANITY(! pcm->setup)) {
SNDMSG("PCM not set up");
return -EIO;
}
if (pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED) {
SNDMSG("invalid access type %s", snd_pcm_access_name(pcm->access));
return -EINVAL;
}
if (bad_pcm_state(pcm, P_STATE_RUNNABLE))
return -EBADFD;
return _snd_pcm_writei(pcm, buffer, size);
}
static inline snd_pcm_sframes_t _snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
{
/* lock handled in the callback */
return pcm->fast_ops->writei(pcm->fast_op_arg, buffer, size);
}
/** PCM handle */
typedef struct _snd_pcm snd_pcm_t;
struct _snd_pcm {
void *open_func;
char *name;
snd_pcm_type_t type;
snd_pcm_stream_t stream;
int mode;
……
const snd_pcm_ops_t *ops;
const snd_pcm_fast_ops_t *fast_ops; //这里定义了
snd_pcm_t *op_arg;
snd_pcm_t *fast_op_arg;
void *private_data;
……
};
前面调用snd_pcm_open时,指定了.writei = snd_pcm_hw_writei
static snd_pcm_sframes_t snd_pcm_hw_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
{
int err;
snd_pcm_hw_t *hw = pcm->private_data;
int fd = hw->fd;
struct snd_xferi xferi;
xferi.buf = (char*) buffer;
xferi.frames = size;
xferi.result = 0; /* make valgrind happy */
if (ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi) < 0) //io操作
err = -errno;
else
//查询control/status数据,以避免内核空间中control数据的意外更改。
err = query_status_and_control_data(hw);
#ifdef DEBUG_RW
fprintf(stderr, "hw_writei: frames = %li, xferi.result = %li, err = %i\n", size, xferi.result, err);
#endif
if (err < 0)
return snd_pcm_check_error(pcm, err);
return xferi.result;
}
首先是在snd_pcm_dev_register时,调用了snd_register_device,并传入snd_pcm_f_ops结构体,其中.write = snd_pcm_write,
static int snd_pcm_dev_register(struct snd_device *device)
{
……
/* register pcm */
err = snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm, //此处传入snd_pcm_f_ops
&pcm->streams[cidx].dev);
……
}
int snd_register_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data, struct device *device)
{
int minor;
int err = 0;
struct snd_minor *preg;
……
preg = kmalloc(sizeof *preg, GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops; //此处指定f_ops
preg->private_data = private_data;
……
}
EXPORT_SYMBOL(snd_register_device);
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.write_iter = snd_pcm_writev,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_poll,
//64位 SNDRV_PCM_IOCTL_WRITEN_FRAMES
.unlocked_ioctl = snd_pcm_ioctl,
//32位 SNDRV_PCM_IOCTL_WRITEN_FRAMES32
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.read_iter = snd_pcm_readv,
……
}
};
static long snd_pcm_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct snd_pcm_file *pcm_file;
pcm_file = file->private_data;
if (((cmd >> 8) & 0xff) != 'A')
return -ENOTTY;
return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,
(void __user *)arg);
}
static int snd_pcm_common_ioctl(struct file *file,
struct snd_pcm_substream *substream,
unsigned int cmd, void __user *arg)
{
struct snd_pcm_file *pcm_file = file->private_data;
int res;
if (PCM_RUNTIME_CHECK(substream))
return -ENXIO;
res = snd_power_wait(substream->pcm->card, SNDRV_CTL_POWER_D0);
if (res < 0)
return res;
switch (cmd) { //根据传入的cmd,调用函数
case SNDRV_PCM_IOCTL_PVERSION:
return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
case SNDRV_PCM_IOCTL_INFO:
return snd_pcm_info_user(substream, arg);
……
case SNDRV_PCM_IOCTL_WRITEI_FRAMES: //假定传入这个
case SNDRV_PCM_IOCTL_READI_FRAMES:
return snd_pcm_xferi_frames_ioctl(substream, arg);
case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
case SNDRV_PCM_IOCTL_READN_FRAMES:
return snd_pcm_xfern_frames_ioctl(substream, arg);
case SNDRV_PCM_IOCTL_REWIND:
return snd_pcm_rewind_ioctl(substream, arg);
case SNDRV_PCM_IOCTL_FORWARD:
return snd_pcm_forward_ioctl(substream, arg);
}
pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd);
return -ENOTTY;
}
static int snd_pcm_xferi_frames_ioctl(struct snd_pcm_substream *substream,
struct snd_xferi __user *_xferi)
{
struct snd_xferi xferi;
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_sframes_t result;
if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
return -EBADFD;
if (put_user(0, &_xferi->result)) //Write a simple value into user space.
return -EFAULT;
if (copy_from_user(&xferi, _xferi, sizeof(xferi)))//从用户空间拷贝数据
return -EFAULT;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames); //播放流调用此函数
else
result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames);
//不进行地址空间检查,在对同一区域进行多次访问时很有用
__put_user(result, &_xferi->result);
return result < 0 ? result : 0;
}
└── __snd_pcm_lib_xfer
└── snd_pcm_start
└── snd_pcm_action
└── snd_pcm_action_single
└── snd_pcm_do_start
static inline snd_pcm_sframes_t
snd_pcm_lib_write(struct snd_pcm_substream *substream,
const void __user *buf, snd_pcm_uframes_t frames)
{
return __snd_pcm_lib_xfer(substream, (void __force *)buf, true, frames, false);
}
snd_pcm_lib/kernel_write/writev/read/readv 八个函数都是对这个函数的封装。
/* the common loop for read/write data */
snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
void *data, bool interleaved,
snd_pcm_uframes_t size, bool in_kernel)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t xfer = 0;
snd_pcm_uframes_t offset = 0;
snd_pcm_uframes_t avail;
pcm_copy_f writer;
pcm_transfer_f transfer;
bool nonblock;
bool is_playback;
int err;
err = pcm_sanity_check(substream);/* sanity-check for read/write methods */
if (err < 0)
return err;
is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; //确认是播放流
if (interleaved) {
if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
runtime->channels > 1)//数据为交错,且通道数>1
return -EINVAL;
//指定writer,后面会调用。interleaved_copy主要做了frames_to_bytes
writer = interleaved_copy;
} else {
if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
return -EINVAL;
writer = noninterleaved_copy;
}
if (!data) {
if (is_playback)
transfer = fill_silence;//如果数据为空且在播放,则填充静音而不是复制数据
else
return -EINVAL;
} else if (in_kernel) {
if (substream->ops->copy_kernel)
transfer = substream->ops->copy_kernel;
else
transfer = is_playback ?
default_write_copy_kernel : default_read_copy_kernel;
} else {
if (substream->ops->copy_user)
transfer = (pcm_transfer_f)substream->ops->copy_user;
else
transfer = is_playback ?
default_write_copy : default_read_copy;
}
if (size == 0)
return 0;
nonblock = !!(substream->f_flags & O_NONBLOCK);
snd_pcm_stream_lock_irq(substream);
err = pcm_accessible_state(runtime);
if (err < 0)
goto _end_unlock;
if (!is_playback &&
runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
size >= runtime->start_threshold) { //录音流,状态处于PREPARED,size大于阈值
err = snd_pcm_start(substream);
if (err < 0)
goto _end_unlock;
}
runtime->twake = runtime->control->avail_min ? : 1;
if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
snd_pcm_update_hw_ptr(substream);//更新hw_ptr【分析在buffer管理部分】
//若substream为播放流,调用snd_pcm_playback_avail
//获取可用(可写)的播放空间
//avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;
//hw_ptr指向硬件已经处理过的数据位置,appl_ptr为用户程序已经处理过的数据位置。appl_ptr之后到buffer末尾这段,是可以写入的,hw_ptr之前的空间也是可写的(其中的数据已被播放)。 appl_ptr比hw_ptr靠后。
avail = snd_pcm_avail(substream);
while (size > 0) {
snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
snd_pcm_uframes_t cont;
if (!avail) {//当无可写空间时
if (!is_playback &&
runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
//处于录音流且状态为DRAINING,暂停
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
goto _end_unlock;
}
if (nonblock) {
err = -EAGAIN;
goto _end_unlock;
}
runtime->twake = min_t(snd_pcm_uframes_t, size,
runtime->control->avail_min ? : 1);
err = wait_for_avail(substream, &avail);//等待avail_min数据可用
if (err < 0)
goto _end_unlock;
if (!avail)
continue; /* draining */
}
frames = size > avail ? avail : size; //frames取size和avail中较小的值
//READ_ONCE可保证在多线程下被其它函数调用变量时不会出错
appl_ptr = READ_ONCE(runtime->control->appl_ptr);//HW buffer的写指针
appl_ofs = appl_ptr % runtime->buffer_size; //写指针在当前HW buffer中的位置。
cont = runtime->buffer_size - appl_ofs; //当前HW buffer中还未被写过的空间数
if (frames > cont)
frames = cont; //令frames不超出count
if (snd_BUG_ON(!frames)) {
runtime->twake = 0;
snd_pcm_stream_unlock_irq(substream);
return -EINVAL;
}
snd_pcm_stream_unlock_irq(substream);
err = writer(substream, appl_ofs, data, offset, frames,
transfer);//调用前面的interleaved_copy 主要是frames_to_bytes
//将数据写入DMA缓冲区,分析在下面。
snd_pcm_stream_lock_irq(substream);
if (err < 0)
goto _end_unlock;
err = pcm_accessible_state(runtime);
if (err < 0)
goto _end_unlock;
appl_ptr += frames; //向fifo写入了frames大小,appl_ptr指针后移
if (appl_ptr >= runtime->boundary)
appl_ptr -= runtime->boundary;
//更新给定的appl_ptr,在需要时调用ack callback,返回错误时,恢复为原始值。
err = pcm_lib_apply_appl_ptr(substream, appl_ptr);
if (err < 0)
goto _end_unlock;
offset += frames; //偏移量增加frames,用于interleaved_copy frames_to_bytes
size -= frames; //要写的数据减少frames
xfer += frames; //用于出错时的返回值
avail -= frames; //有效空间减少frames
if (is_playback &&
runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) { //当处于播放流,状态为PREPARED,有效数据大于启动阈值时
err = snd_pcm_start(substream); //这里开始DMA传输
if (err < 0)
goto _end_unlock;
}
}
_end_unlock:
runtime->twake = 0;
if (xfer > 0 && err >= 0)
snd_pcm_update_state(substream, runtime);
snd_pcm_stream_unlock_irq(substream);
return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
}
EXPORT_SYMBOL(__snd_pcm_lib_xfer);
这里主要是
err = writer(substream, appl_ofs, data, offset, frames,
transfer);//调用前面的interleaved_copy frames_to_bytes
__snd_pcm_lib_xfer中指定了writer为interleaved_copy或noninterleaved_copy。
/* call transfer function with the converted pointers and sizes;
* for interleaved mode, it's one shot for all samples
*/
static int interleaved_copy(struct snd_pcm_substream *substream,
snd_pcm_uframes_t hwoff, void *data,
snd_pcm_uframes_t off,
snd_pcm_uframes_t frames,
pcm_transfer_f transfer)
{
struct snd_pcm_runtime *runtime = substream->runtime;
/* convert to bytes */
//frame_bits = snd_pcm_format_physical_width(pcm_format) * channels
hwoff = frames_to_bytes(runtime, hwoff); //返回 hwoff * runtime->frame_bits / 8;
off = frames_to_bytes(runtime, off);
frames = frames_to_bytes(runtime, frames);
return transfer(substream, 0, hwoff, data + off, frames);
}
/* default copy_user ops for write; used for both interleaved and non- modes */
static int default_write_copy(struct snd_pcm_substream *substream,
int channel, unsigned long hwoff,
void *buf, unsigned long bytes)
{
if (copy_from_user(get_dma_ptr(substream->runtime, channel, hwoff),
//get_dma_ptr:计算要写入/读取的目标DMA缓冲区位置
(void __user *)buf, bytes))
return -EFAULT;
return 0;
}
/* calculate the target DMA-buffer position to be written/read */
static void *get_dma_ptr(struct snd_pcm_runtime *runtime,
int channel, unsigned long hwoff)
{
return runtime->dma_area + hwoff +
channel * (runtime->dma_bytes / runtime->channels);
}
/**
* snd_pcm_start - start all linked streams
* @substream: the PCM substream instance
* Return: Zero if successful, or a negative error code.
* The stream lock must be acquired before calling this function.
*/
int snd_pcm_start(struct snd_pcm_substream *substream)
{
return snd_pcm_action(&snd_pcm_action_start, substream, //传入snd_pcm_action_start为ops
SNDRV_PCM_STATE_RUNNING);
}
snd_pcm_action_start中定义了.do_action为snd_pcm_do_start,后面会调用到。
static const struct action_ops snd_pcm_action_start = {
.pre_action = snd_pcm_pre_start,
.do_action = snd_pcm_do_start,
.undo_action = snd_pcm_undo_start,
.post_action = snd_pcm_post_start
};
/*
* Note: call with stream lock
*/
static int snd_pcm_action(const struct action_ops *ops,
struct snd_pcm_substream *substream,
int state)
{
int res;
if (!snd_pcm_stream_linked(substream))
return snd_pcm_action_single(ops, substream, state);
if (substream->pcm->nonatomic) {
if (!mutex_trylock(&substream->group->mutex)) {
mutex_unlock(&substream->self_group.mutex);
mutex_lock(&substream->group->mutex);
mutex_lock(&substream->self_group.mutex);
}
res = snd_pcm_action_group(ops, substream, state, 1);
mutex_unlock(&substream->group->mutex);
} else {
if (!spin_trylock(&substream->group->lock)) {
spin_unlock(&substream->self_group.lock);
spin_lock(&substream->group->lock);
spin_lock(&substream->self_group.lock);
}
res = snd_pcm_action_group(ops, substream, state, 1);
spin_unlock(&substream->group->lock);
}
return res;
}
假定substream尚未link
/*
* Note: call with stream lock
*/
static int snd_pcm_action_single(const struct action_ops *ops,
struct snd_pcm_substream *substream,
int state)
{
int res;
res = ops->pre_action(substream, state);
if (res < 0)
return res;
res = ops->do_action(substream, state);
if (res == 0)
ops->post_action(substream, state);
else if (ops->undo_action)
ops->undo_action(substream, state);
return res;
}
static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
{
//在snd_pcm_pre_start中会runtime->trigger_master = substream;
if (substream->runtime->trigger_master != substream)
return 0;
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
}
传入到驱动中的trigger函数。
以rockchipo_pdm驱动为例
static int rockchip_pdm_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct rk_pdm_dev *pdm = to_info(dai);
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
rockchip_pdm_rxctrl(pdm, 1);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
rockchip_pdm_rxctrl(pdm, 0);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static void rockchip_pdm_rxctrl(struct rk_pdm_dev *pdm, int on)
{
if (on) {
regmap_update_bits(pdm->regmap, PDM_DMA_CTRL,
PDM_DMA_RD_MSK, PDM_DMA_RD_EN);
regmap_update_bits(pdm->regmap, PDM_SYSCONFIG,
PDM_RX_MASK, PDM_RX_START);
} else {
regmap_update_bits(pdm->regmap, PDM_DMA_CTRL,
PDM_DMA_RD_MSK, PDM_DMA_RD_DIS);
regmap_update_bits(pdm->regmap, PDM_SYSCONFIG,
PDM_RX_MASK | PDM_RX_CLR_MASK,
PDM_RX_STOP | PDM_RX_CLR_WR);
}
}
在regmap.h中
#define regmap_update_bits(map, reg, mask, val) \
regmap_update_bits_base(map, reg, mask, val, NULL, false, false)
regmap_update_bits
└── regmap_update_bits_base
└── _regmap_update_bits
└── __regmap_write
└── regcache_write
└── map->cache_ops->write
sound/core/control.c对下层的Control提供包装,为上层提供统一的接口,snd_ctl_f_ops文件操作结构提供控制功能函数,其中主要是snd_ctl_ioctl函数。
Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或控制control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。
alsa-utils-1.1.5中amixer.c,配置音量。
amixer cset 为例。
static int cset(int argc, char *argv[], int roflag, int keep_handle)
{
int err;
static snd_ctl_t *handle = NULL;
snd_ctl_elem_info_t *info;
snd_ctl_elem_id_t *id;
snd_ctl_elem_value_t *control;
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
……
//此处调用snd_ctl_open,返回&handle
if (handle == NULL &&
(err = snd_ctl_open(&handle, card, 0)) < 0) {
error("Control %s open error: %s\n", card, snd_strerror(err));
return err;
}
……
if (!roflag) {
……
//把control信息写入handle
if ((err = snd_ctl_elem_write(handle, control)) < 0) {
if (!ignore_error)
error("Control %s element write error: %s\n", card, snd_strerror(err));
if (!keep_handle) {
snd_ctl_close(handle);
handle = NULL;
}
return ignore_error ? 0 : err;
}
}
……
return 0;
}
alsa-lib的src/control/control.c中
/**
* \brief Set CTL element value
* \param ctl CTL handle
* \param data Data of an element.
* \retval 0 on success
* \retval >0 on success when value was changed
* \retval <0 a negative error code
*/
int snd_ctl_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *data)
{
assert(ctl && data && (data->id.name[0] || data->id.numid));
return ctl->ops->element_write(ctl, data);
}
而具体函数则要看调用snd_ctl_open时指定的是哪一个。
/**
* \brief Opens a CTL
* \param ctlp Returned CTL handle
* \param name ASCII identifier of the CTL handle
* \param mode Open mode (see #SND_CTL_NONBLOCK, #SND_CTL_ASYNC)
* \return 0 on success otherwise a negative error code
*/
int snd_ctl_open(snd_ctl_t **ctlp, const char *name, int mode)
{
snd_config_t *top;
int err;
assert(ctlp && name);
err = snd_config_update_ref(&top);
if (err < 0)
return err;
err = snd_ctl_open_noupdate(ctlp, top, name, mode);
snd_config_unref(top);
return err;
}
后续追踪代码太繁杂,仅列出调用关系。
snd_ctl_open
└── snd_ctl_open_noupdate
└── snd_ctl_open_conf
└── 调用snd_dlobj_cache_get从lib库中获取
└── 假定获取_snd_ctl_shm_open
以下函数均在alsa-lib的 src/control/control_hw.c中,代码太繁杂,只列出重点
_snd_ctl_shm_open
└── snd_ctl_shm_open
└── ctl->ops = &snd_ctl_shm_ops
└── .element_write = snd_ctl_shm_elem_write
└── ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, control)
int _snd_ctl_hw_open(snd_ctl_t **handlep, char *name, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, int mode)
{
……
return snd_ctl_hw_open(handlep, name, card, mode);
}
SND_DLSYM_BUILD_VERSION(_snd_ctl_hw_open, SND_CONTROL_DLSYM_VERSION);
int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)
{
int fd, ver;
char filename[sizeof(SNDRV_FILE_CONTROL) + 10];
int fmode;
snd_ctl_t *ctl;
snd_ctl_hw_t *hw;
int err;
……
err = snd_ctl_new(&ctl, SND_CTL_TYPE_HW, name);
if (err < 0) {
close(fd);
free(hw);
return err;
}
ctl->ops = &snd_ctl_hw_ops; //指定ctl->ops
ctl->private_data = hw;
ctl->poll_fd = fd;
*handle = ctl;
return 0;
}
static const snd_ctl_ops_t snd_ctl_hw_ops = {
……
.element_read = snd_ctl_hw_elem_read,
.element_write = snd_ctl_hw_elem_write,
……
};
static int snd_ctl_hw_elem_write(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
{
snd_ctl_hw_t *hw = handle->private_data;
if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, control) < 0)
return -errno;
return 0;
}
/*
* create control core:
* called from init.c
*/
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register, //会在其他地方被调用,注册control设备
.dev_disconnect = snd_ctl_dev_disconnect,
};
int err;
if (snd_BUG_ON(!card))
return -ENXIO;
if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS))
return -ENXIO;
snd_device_initialize(&card->ctl_dev, card);
dev_set_name(&card->ctl_dev, "controlC%d", card->number);
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
if (err < 0)
put_device(&card->ctl_dev);
return err;
}
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, &card->ctl_dev); //传入了snd_ctl_f_ops
}
static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl, //指定snd_ctl_ioctl
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_ctl_file *ctl;
struct snd_card *card;
struct snd_kctl_ioctl *p;
void __user *argp = (void __user *)arg;
int __user *ip = argp;
int err;
ctl = file->private_data;
card = ctl->card;
if (snd_BUG_ON(!card))
return -ENXIO;
switch (cmd) { //根据传入cmd,调用函数
case SNDRV_CTL_IOCTL_PVERSION:
return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
case SNDRV_CTL_IOCTL_CARD_INFO:
return snd_ctl_card_info(card, ctl, cmd, argp);
case SNDRV_CTL_IOCTL_ELEM_LIST:
return snd_ctl_elem_list(card, argp);
case SNDRV_CTL_IOCTL_ELEM_INFO:
return snd_ctl_elem_info_user(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_READ:
return snd_ctl_elem_read_user(card, argp);
case SNDRV_CTL_IOCTL_ELEM_WRITE: //write
return snd_ctl_elem_write_user(ctl, argp);
……
}
#define SNDRV_CTL_IOCTL_ELEM_WRITE _IOWR('U', 0x13, struct snd_ctl_elem_value)
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
bit29~bit15 14位为 "数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit20~bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。
static int snd_ctl_elem_write_user(struct snd_ctl_file *file,
struct snd_ctl_elem_value __user *_control)
{
struct snd_ctl_elem_value *control;
struct snd_card *card;
int result;
control = memdup_user(_control, sizeof(*control)); //从用户空间拷贝数据到内核中
if (IS_ERR(control))
return PTR_ERR(control);
card = file->card;
result = snd_power_wait(card, SNDRV_CTL_POWER_D0);
if (result < 0)
goto error;
//使用该函数来得到读写信号量,它也会导致调用者睡眠,因此只能在进程上下文使用
down_write(&card->controls_rwsem); /* controls_rwsem 为 controls list lock */
result = snd_ctl_elem_write(card, file, control); //调用snd_ctl_elem_write
//调用该函数释放信号量
up_write(&card->controls_rwsem);
if (result < 0)
goto error;
if (copy_to_user(_control, control, sizeof(*control))) //传回用户空间
result = -EFAULT;
error:
kfree(control);
return result;
}
memdup_user先从用户空间拷贝数据到内核中,进行处理,然后再用copy_to_user传回用户空间。
struct snd_ctl_elem_value *control;
struct snd_ctl_elem_value {
struct snd_ctl_elem_id id; /* W: element ID */
unsigned int indirect: 1; /* W: indirect access - obsoleted */
union {
union {
long value[128];
long *value_ptr; /* obsoleted */
} integer;
union {
long long value[64];
long long *value_ptr; /* obsoleted */
} integer64;
union {
unsigned int item[128];
unsigned int *item_ptr; /* obsoleted */
} enumerated;
union {
unsigned char data[512];
unsigned char *data_ptr; /* obsoleted */
} bytes;
struct snd_aes_iec958 iec958;
} value; /* RO */
struct timespec tstamp;
unsigned char reserved[128-sizeof(struct timespec)];
};
struct snd_ctl_elem_id {
unsigned int numid; /* numeric identifier, zero = invalid */
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
unsigned char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; /* ASCII name of item */
unsigned int index; /* index of item */
};
static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
struct snd_ctl_elem_value *control)
{
struct snd_kcontrol *kctl;
struct snd_kcontrol_volatile *vd;
unsigned int index_offset;
int result;
kctl = snd_ctl_find_id(card, &control->id);
//snd_ctl_find_id,遍历kcontrol链表找到匹配的kctl
if (kctl == NULL)
return -ENOENT;
index_offset = snd_ctl_get_ioff(kctl, &control->id);
vd = &kctl->vd[index_offset];
if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || kctl->put == NULL ||
(file && vd->owner && vd->owner != file)) {
return -EPERM; //判断control的acess是否是write权限
}
snd_ctl_build_ioff(&control->id, kctl, index_offset);
result = kctl->put(kctl, control); //调用kctl->put 函数
if (result < 0)
return result;
if (result > 0) {
struct snd_ctl_elem_id id = control->id;
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id);
}
return 0;
}
snd_ctl_find_id,遍历kcontrol链表找到匹配的kctl
static const struct snd_kcontrol_new rk3308_codec_dapm_controls[] = {
……
SOC_SINGLE_RANGE_TLV("ADC ALC Group 0 Left Volume",
RK3308_ADC_ANA_CON03(0),
RK3308_ADC_CH1_ALC_GAIN_SFT,
RK3308_ADC_CH1_ALC_GAIN_MIN,
RK3308_ADC_CH1_ALC_GAIN_MAX,
0, rk3308_codec_adc_alc_gain_tlv),
……
}
以ADC ALC Group 0 Left Volume为例,依次传入name,regshift,min,max,invert, tlv字段 为该control提供元数据。
static const DECLARE_TLV_DB_SCALE(rk3308_codec_adc_alc_gain_tlv,
-1800, 150, 2850);
**DECLARE_TLV_DB_SCALE**宏定义的mixer control,它所代表的值按一个固定的dB值的步长变化。该宏的第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位。第三个参数是变化的步长,也是以0.01dB为单位。如果该control处于最小值时会做出mute时,需要把第四个参数设为1。
所谓tlv,就是Type-Length-Value的意思,数组的第0个元素代表数据的类型,第1个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据。
#define SOC_SINGLE_RANGE_TLV(xname, xreg, xshift, xmin, xmax, xinvert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.info = snd_soc_info_volsw_range, \
.get = snd_soc_get_volsw_range, .put = snd_soc_put_volsw_range, \
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .rreg = xreg, .shift = xshift, \
.rshift = xshift, .min = xmin, .max = xmax, \
.platform_max = xmax, .invert = xinvert} }
access:字段是访问控制权限。SNDRV_CTL_ELEM_ACCESS_READ意味着只读,这时put()函数不必实现;SNDRV_CTL_ELEM_ACCESS_WRITE意味着只写,这时get()函数不必实现。若control值频繁变化,则需定义VOLATILE标志。当control处于非激活状态时,应设置INACTIVE标志。
info回调函数用于 获取control的详细信息。它的主要工作就是填充通过参数传入的 snd_ctl_elem_info对象
get回调函数用于 读取control的当前值,并返回给 用户空间的 应用程序。
put回调函数用于 把应用程序的控制值 设置到control中。
get和put函数,有的control项可以改用SOC_SINGLE_EXT_TLV宏,在codec代码中自定义
snd_kcontrol_new结构体并没有numid这个成员,是因为numid是系统自动管理的,原则上是该control的注册次序,保存到snd_ctl_elem_value结构体中。
可以通过private_value给info()、get()和put()函数传递参数。
有的用snd_soc_add_component_controls添加。有的直接在snd_soc_component_driver中添加。
static struct snd_soc_component_driver soc_codec_dev_rk3308 = {
.probe = rk3308_probe,
.remove = rk3308_remove,
.suspend = rk3308_suspend,
.resume = rk3308_resume,
.set_bias_level = rk3308_set_bias_level,
.controls = rk3308_codec_dapm_controls,
.num_controls = ARRAY_SIZE(rk3308_codec_dapm_controls),
};
snd_soc_add_component_controls
└── snd_soc_add_controls中snd_ctl_add(card, snd_soc_cnew(control, data,control->name, prefix));
└── snd_soc_cnew→snd_ctl_new1→snd_ctl_new
└── snd_ctl_add→__snd_ctl_add
static int rk3308_codec_mic_gain_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(codec);
unsigned int gain = ucontrol->value.integer.value[0];
……
return snd_soc_put_volsw_range(kcontrol, ucontrol);
}
/**
* snd_soc_put_volsw_range - single mixer put value callback with range.
* @kcontrol: mixer control
* @ucontrol: control element information
* Callback to set the value, within a range, for a single mixer control.
* Returns 0 for success.
*/
int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
unsigned int reg = mc->reg;
unsigned int rreg = mc->rreg;
unsigned int shift = mc->shift;
int min = mc->min;
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
unsigned int val, val_mask;
int ret;
if (invert)
val = (max - ucontrol->value.integer.value[0]) & mask;
else
val = ((ucontrol->value.integer.value[0] + min) & mask);
val_mask = mask << shift;
val = val << shift;
ret = snd_soc_component_update_bits(component, reg, val_mask, val);
if (ret < 0)
return ret;
if (snd_soc_volsw_is_stereo(mc)) {
if (invert)
val = (max - ucontrol->value.integer.value[1]) & mask;
else
val = ((ucontrol->value.integer.value[1] + min) & mask);
val_mask = mask << shift;
val = val << shift;
ret = snd_soc_component_update_bits(component, rreg, val_mask,
val);
}
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_put_volsw_range);
snd_ctl_elem_value 这个结构体,用于数据在user space和kernel space的传递。
可以从命名看出来(kcontrol-kernel control,ucontrol-user control);
往下都是regmap的操作了。
# amixer cset name='ADC ALC Group 0 Left Volume' 20
numid=61,iface=MIXER,name='ADC ALC Group 0 Left Volume'
; type=INTEGER,access=rw---R--,values=1,min=0,max=31,step=0
: values=20
| dBscale-min=-18.00dB,step=1.50dB,mute=1
在kernel/sound/core/control.c的snd_ctl_elem_write_user()添加numid等打印语句后变为以下内容。存在打印冲突,但仍可确认其调用了snd_ctl_elem_write_user函数。
# amixer cset name='ADC ALC Group 0 Left Volume' 20
[ 157.757224] asoc-simple-card acodec-sound: control.c numid =numid=61,iface=MIXER,name='ADC ALC Group 0 Left Volume'
61
; type=INTEGER,access=rw---R-[-,values=1,min=0,max=31,step= 0
. : values=20157
757265] as | dBscale-min=-18.00dB,stepoc=1.50dB,mute=1
-simple-card acodec-sound: control.c device = 0
[ 157.757305] asoc-simple-card acodec-sound: control.c subdevice = 0
[ 157.757347] asoc-simple-card acodec-sound: control.c index = 0
之前在alsa-lib添加打印信息还是上层了,不够靠近真正的底层,也更难发现出错点。
如果对内核音频代码有足够的了解,就可以快速地在底层定位到出错点,提高效率。
但是需要有足够的了解,否则打印出来都看不懂。