kernel 4.19音频框架超详细分析(ALSA数据流程、控制流程、驱动层)

4.19音频框架

文章目录

  • 4.19音频框架
    • 内核音频大致框架
    • 数据流程
      • 应用层[aplay.c]
          • 调用snd_pcm_open [alsa-lib pcm.c]
            • .writei = snd_pcm_hw_writei
          • main函数判断是否交错
          • 以playback为例
          • 以playback_raw为例
          • 调用playback_go
          • 具体应用层的数据处理,暂且略过。后续有空再分析
          • 调用pcm_write
          • 调用writei_func
      • ALSA Library API
          • 调用snd_pcm_writei [alsa-lib pcm.c]
          • 调用_snd_pcm_writei [alsa-lib pcm_local.h]
          • pcm->fast_ops->writei = snd_pcm_hw_writei
          • snd_pcm_hw_writei [alsa-lib pcm_hw.c]
          • ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi)
      • ALSA-CORE
          • 注册pcm_dev时,指定.unlocked_ioctl
            • snd_pcm_dev_register [pcm.c]
            • snd_register_device [pcm.c]
            • snd_pcm_f_ops [pcm_native.c]
            • .unlocked_ioctl=snd_pcm_ioctl, [pcm_native.c]
            • snd_pcm_common_ioctl [pcm_native.c]
          • 根据alsa-lib传入的cmd,调用snd_pcm_xferi_frames_ioctl [pcm_native.c]
          • snd_pcm_lib_write [pcm_native.c]
          • snd_pcm_lib_write [pcm.h]
          • __snd_pcm_lib_xfer [pcm\_lib.c] 【重点】
          • 数据写入DMA buffer
            • 假定write为interleaved_copy
            • 假定transfer为default_write_copy。
            • get_dma_ptr
          • 调用snd_pcm_start [pcm_native.c]
          • snd_pcm_action [pcm_native.c]
          • 调用snd_pcm_action_single [pcm_native.c]
          • snd_pcm_do_start [pcm_native.c]
          • substream->ops->trigger
      • 驱动层
          • rockchip_pdm_trigger
          • 调用 rockchip_pdm_rxctrl
          • regmap_update_bits
    • 控制流程
      • 应用层 [amixer.c]
          • cset调用snd_ctl_open和snd_ctl_elem_write
      • ALSA Library API
          • snd_ctl_elem_write
          • 调用ctl->ops->element_write,
          • snd_ctl_open
          • _snd_ctl_hw_open
            • snd_ctl_hw_open
            • ctl->ops = &snd_ctl_hw_ops
          • .element_write = snd_ctl_hw_elem_write
          • ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, control)
      • ALSA-CORE [control.c]
          • **snd_ctl_create创建control core**
          • **snd_ctl_dev_register注册control设备**
          • 调用snd_register_device并且传入了snd_ctl_f_ops
          • .unlocked_ioctl = snd_ctl_ioctl,
          • 根据传入的cmd,进行操作。
          • 假定调用snd_ctl_elem_write_user
          • **memdup_user** →snd_ctl_elem_write→ **copy_to_user**
          • 数据类型为struct snd_ctl_elem_value
          • 调用snd_ctl_elem_write
          • result = kctl->put(kctl, control);
      • 驱动层
          • 以rk3308_codec为例,创建controls
          • 首先要定义struct **snd_kcontrol_new** 。
            • **调用include/sound/soc.h中的宏SOC_SINGLE_RANGE_TLV**
            • 这里又调用了个DECLARE_TLV_DB_SCALE宏来设置。
            • SOC_SINGLE_RANGE_TLV宏
          • 添加contols
            • **snd_soc_component_driver**
            • snd_soc_add_component_controls [soc/soc-core.c]
          • 驱动中自定义的put函数
          • snd_soc_put_volsw_range [soc/soc-ops.c]
          • **snd_soc_component_update_bits负责更新寄存器的值**
    • 实践认识

内核音频大致框架

	   +--------+  +--------+  +--------+
	   |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设备

kernel 4.19音频框架超详细分析(ALSA数据流程、控制流程、驱动层)_第1张图片

数据流程

​ sound/core/pcm_native.c 对下层的PCM驱动提供包装,为上层提供统一的接口。

kernel 4.19音频框架超详细分析(ALSA数据流程、控制流程、驱动层)_第2张图片

应用层[aplay.c]

​ 以播放为例。

调用snd_pcm_open [alsa-lib pcm.c]

​ 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
};
.writei = snd_pcm_hw_writei

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,
	……
};
main函数判断是否交错
	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);
	}
以playback为例
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;
    }
以playback_raw为例
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;
}
调用playback_go
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);
}
具体应用层的数据处理,暂且略过。后续有空再分析
调用pcm_write
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;
}
调用writei_func

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

ALSA Library API

调用snd_pcm_writei [alsa-lib pcm.c]
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);
}
调用_snd_pcm_writei [alsa-lib pcm_local.h]
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;
	……
};
pcm->fast_ops->writei = snd_pcm_hw_writei

前面调用snd_pcm_open时,指定了.writei = snd_pcm_hw_writei

snd_pcm_hw_writei [alsa-lib pcm_hw.c]
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;
}
ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi)

ALSA-CORE

注册pcm_dev时,指定.unlocked_ioctl

​ 首先是在snd_pcm_dev_register时,调用了snd_register_device,并传入snd_pcm_f_ops结构体,其中.write = snd_pcm_write,

snd_pcm_dev_register [pcm.c]
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);
	……
	}
snd_register_device [pcm.c]
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);
snd_pcm_f_ops [pcm_native.c]
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,
		……
	}
};
.unlocked_ioctl=snd_pcm_ioctl, [pcm_native.c]
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);

}
snd_pcm_common_ioctl [pcm_native.c]
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;
}
根据alsa-lib传入的cmd,调用snd_pcm_xferi_frames_ioctl [pcm_native.c]
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_write [pcm_native.c]

​ └── __snd_pcm_lib_xfer

​ └── snd_pcm_start

​ └── snd_pcm_action

​ └── snd_pcm_action_single

​ └── snd_pcm_do_start

snd_pcm_lib_write [pcm.h]
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_xfer [pcm_lib.c] 【重点】

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);
数据写入DMA buffer

​ 这里主要是

err = writer(substream, appl_ofs, data, offset, frames,
			     transfer);//调用前面的interleaved_copy frames_to_bytes

​ __snd_pcm_lib_xfer中指定了writer为interleaved_copy或noninterleaved_copy。

假定write为interleaved_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);
   }
假定transfer为default_write_copy。
/* 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;
}
get_dma_ptr
/* 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 [pcm_native.c]
/**
 * 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
};
snd_pcm_action [pcm_native.c]
/*
 * 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

调用snd_pcm_action_single [pcm_native.c]
/*
 * 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;
   }
snd_pcm_do_start [pcm_native.c]
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);
}
substream->ops->trigger

驱动层

传入到驱动中的trigger函数。

以rockchipo_pdm驱动为例

rockchip_pdm_trigger
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;
}
调用 rockchip_pdm_rxctrl
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_update_bits

在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等控制操作。

kernel 4.19音频框架超详细分析(ALSA数据流程、控制流程、驱动层)_第3张图片

应用层 [amixer.c]

alsa-utils-1.1.5中amixer.c,配置音量。

amixer cset 为例。

cset调用snd_ctl_open和snd_ctl_elem_write
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 Library API

alsa-lib的src/control/control.c中

snd_ctl_elem_write
/**
 * \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);
   }
调用ctl->ops->element_write,

而具体函数则要看调用snd_ctl_open时指定的是哪一个。

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)

_snd_ctl_hw_open
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);
snd_ctl_hw_open
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;
}
ctl->ops = &snd_ctl_hw_ops
static const snd_ctl_ops_t snd_ctl_hw_ops = {
	……
	.element_read = snd_ctl_hw_elem_read,
	.element_write = snd_ctl_hw_elem_write,
	……
};
.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;
}
ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, control)

ALSA-CORE [control.c]

snd_ctl_create创建control core
/*
 * 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;
   }
snd_ctl_dev_register注册control设备
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
}
调用snd_register_device并且传入了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,
};
.unlocked_ioctl = snd_ctl_ioctl,
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);
	……
}
根据传入的cmd,进行操作。
#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位为 "区别序号" 区,是区分命令的命令顺序序号。
假定调用snd_ctl_elem_write_user
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 →snd_ctl_elem_write→ copy_to_user

​ memdup_user先从用户空间拷贝数据到内核中,进行处理,然后再用copy_to_user传回用户空间。

数据类型为struct snd_ctl_elem_value
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 */
};
调用snd_ctl_elem_write
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

result = kctl->put(kctl, control);

驱动层

以rk3308_codec为例,创建controls
首先要定义struct snd_kcontrol_new
调用include/sound/soc.h中的宏SOC_SINGLE_RANGE_TLV
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提供元数据。

这里又调用了个DECLARE_TLV_DB_SCALE宏来设置。
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个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据。

SOC_SINGLE_RANGE_TLV宏
#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()函数传递参数。

添加contols

​ 有的用snd_soc_add_component_controls添加。有的直接在snd_soc_component_driver中添加。

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 [soc/soc-core.c]

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

驱动中自定义的put函数
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 [soc/soc-ops.c]
/**
 * 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);

snd_soc_component_update_bits负责更新寄存器的值

往下都是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添加打印信息还是上层了,不够靠近真正的底层,也更难发现出错点。

​ 如果对内核音频代码有足够的了解,就可以快速地在底层定位到出错点,提高效率。

​ 但是需要有足够的了解,否则打印出来都看不懂。

你可能感兴趣的:(kernel-sound,linux,alsa,嵌入式,内核)