Linux Audio Driver III: Call Flow of pcm Interface

Keywords: codec Linux less

This paper is a learning note based on Linux version number of mini2440 development board linux-2.6.32.2

I. open of pcm equipment

According to the article "One of Linux Audio Drivers: Audio Driver Registration Process", pcm device calls the snd_pcm_dev_register function registration.

snd_pcm_dev_register
err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,
						  &snd_pcm_f_ops[cidx],pcm, str, dev);

When registering and playing PCM devices, f OPS is snd_pcm_f_ops[0].
When registering and recording PCM devices, f OPS is snd_pcm_f_ops[1].
Now let's take playing PCM devices as an example and look at snd_pcm_f_ops[0].

const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	dummy_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	dummy_get_unmapped_area,
	}
};

When the pcm device device is opened, the snd_pcm_playback_open function above is called.
Enter snd_pcm_playback_open function analysis.

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
	struct snd_pcm *pcm;

	pcm = snd_lookup_minor_data(iminor(inode),
				    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
	return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
}
  • Get snd_pcm data.
    iminor(inode) = MINOR(inode->i_rdev)
    Inode - > i_rdev is the device number of the file corresponding device, and MINOR (inode - > i_rdev) is the secondary device number.
    When we register the pcm device, we look for an unused element in the snd_minors array and save fops, snd_pcm, type and other information. Subscript minor of this element is used as secondary device number.
    Main equipment number 116
#define CONFIG_SND_MAJOR	116

dev_t = 116 << 20 | minor
Finally, dev_t is registered with the kernel.
Now open finds snd_minors[minor] according to the secondary device number and gets snd_pcm data.

  • Call the snd_pcm_open function
    1. Call the snd_card_file_add function.
    This function saves the open file into the file_list list list of snd_card. The purpose is to track the connection status and avoid hot plugging releasing busy resources.
err = snd_card_file_add(pcm->card, file);
  1. Call the snd_pcm_open_file function.
err = snd_pcm_open_file(file, pcm, stream, &pcm_file);
  • snd_pcm_open_file function
    1. Call snd_pcm_open_substream function to get snd_pcm_substream data.
err = snd_pcm_open_substream(pcm, stream, file, &substream);

2. Apply for a snd_pcm_file structure and save the obtained snd_pcm_substream data to snd_pcm_file.substream.
Then assign snd_pcm_file to file - > private_data.

II. Analysis of snd_pcm_open_substream function
  • Call the function snd_pcm_attach_substream to get snd_pcm_substream.
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
    pstr = &pcm->streams[stream];
    for (substream = pstr->substream; substream; substream = substream->next)
		if (!SUBSTREAM_BUSY(substream))
			break;

According to snd_pcm.streams[0], snd_pcm_str is obtained.
Loop through the substream of snd_pcm_str to find the substream - > ref_count = 0. This substream is the snd_pcm_substream data we want above.

runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = snd_malloc_pages(size, GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
runtime->control = snd_malloc_pages(size, GFP_KERNEL);

Apply for a data runtime of snd_pcm_runtime type, and then allocate space to runtime - > status and runtime - > control.
Fill in the substream data.

substream->runtime = runtime;
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->f_flags = file->f_flags;
pstr->substream_opened++;

The final data are as follows:

  • Call the snd_pcm_hw_constraints_init function
    The snd_pcm_hw_constraints_init function involves several important structures.
struct snd_pcm_hw_constraints {
	struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - 
			 SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
	struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
			     SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};

SNDRV_PCM_HW_PARAM_FIRST_MASK = 0,SNDRV_PCM_HW_PARAM_LAST_MASK = 2;
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = 8, SNDRV_PCM_HW_PARAM_LAST_INTERVAL = 19
So:

struct snd_pcm_hw_constraints {
	struct snd_mask masks[3];
	struct snd_interval intervals[12];
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};
#define SNDRV_MASK_MAX	256
struct snd_mask 
{
	__u32 bits[(SNDRV_MASK_MAX+31)/32];
};
struct snd_interval {
    unsigned int min, 
    unsigned int max,
    unsigned int openmin:1,
    openmax:1,
    integer:1,
    empty:1;
};

So:

struct snd_pcm_hw_constraints 
{
    struct snd_mask masks[3] =
    {
        int bits[8];     //Setting Access type
        int bits[8];     //Set Format
        int bits[8];    //Setting Subformat
    };
	struct snd_interval intervals[12]=
    {
        //Setting the number of sampling data bits
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //Set the number of bits per frame
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //Setup channel
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //Setting Sampling Rate
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //Set the time interval between interrupts
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //Set data frames between interrupts (I don't know what that means)
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //Set up bytes between terminals (I don't know what that means)
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //Set the number of interrupts
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //Setting Buffer Time
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //Setting buffer size
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //Setting buffer size
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //Setting Approx tick duration
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },       
    }
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};

The first two masks elements are set to 0xff

for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
    snd_mask_any(constrs_mask(constrs, k));
}

After setting up:

struct snd_mask masks[3] =
{
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;   //Setting Access type
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;     //Set Format
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;    //Setting Subformat
};

Initialize all elements of intervals[12], and all variables in the elements are set to 0.

for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
    snd_interval_any(constrs_interval(constrs, k));
}

static inline void snd_interval_any(struct snd_interval *i)
{
	i->min = 0;
	i->openmin = 0;
	i->max = UINT_MAX;
	i->openmax = 0;
	i->integer = 0;
	i->empty = 0;
}

Initialize the integer tag of intervals[12], which elements in intervals[12] should be set as integers.

snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));

Channel, buffer size, sampling number, frame number and so on should be set to integers.

Adding rule rules is to assign values to snd_pcm_hw_constraints.rules.

err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,snd_pcm_hw_rule_format, NULL,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);

int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,
			int var,
			snd_pcm_hw_rule_func_t func, void *private,
			int dep, ...)
{
	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
	struct snd_pcm_hw_rule *c;
	unsigned int k;
	va_list args;
	va_start(args, dep);
	if (constrs->rules_num >= constrs->rules_all) {
		struct snd_pcm_hw_rule *new;
		unsigned int new_rules = constrs->rules_all + 16;
		new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
		if (!new)
			return -ENOMEM;
		if (constrs->rules) {
			memcpy(new, constrs->rules,
			       constrs->rules_num * sizeof(*c));
			kfree(constrs->rules);
		}
		constrs->rules = new;
		constrs->rules_all = new_rules;
	}
	c = &constrs->rules[constrs->rules_num];
	c->cond = cond;
	c->func = func;
	c->var = var;
	c->private = private;
	k = 0;
	while (1) {
		if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps)))
			return -EINVAL;
		c->deps[k++] = dep;
		if (dep < 0)
			break;
		dep = va_arg(args, int);
	}
	constrs->rules_num++;
	va_end(args);
	return 0;
}

Where va_start, va_arg, va_end are used to import variable parameters.
va_start(args, dep): Let the args pointer point to the address of the dep variable;
va_arg(args, int): Let the args pointer shift to the next address and return the value inside, int type. The condition for the end of migration is that the value taken out is less than 0.
va_end(args): args = 0
The above code saves the extracted values from the args pointer in the C - > DEPs array. Translate the snd_pcm_hw_rule_add function above to get:

constrs.rules[0] = 
{
    cond = 0;
    func = snd_pcm_hw_rule_format;
    var = SNDRV_PCM_HW_PARAM_FORMAT
    deps[0] = SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8;
    private = NULL;
}

A lot of rules have been added below, which will not be introduced one by one.

  • Call the substream - > Ops - > open function
    According to the chapter of Linux Audio Driver:
    To assign values to snd_pcm.streams[i].substream[i].ops, mmap, pointer, ioctl and other functions use s3c24xx_pcm_ops, others use soc_pcm_ops own. Soc_pcm_ops is as follows:
soc_pcm_ops.open		= soc_pcm_open,
soc_pcm_ops.close		= soc_codec_close,
soc_pcm_ops.hw_params	= soc_pcm_hw_params,
soc_pcm_ops.hw_free	= soc_pcm_hw_free,
soc_pcm_ops.prepare	= soc_pcm_prepare,
soc_pcm_ops.trigger	= soc_pcm_trigger,
soc_pcm_ops.mmap = platform->pcm_ops->mmap;
soc_pcm_ops.pointer = platform->pcm_ops->pointer;
soc_pcm_ops.ioctl = platform->pcm_ops->ioctl;
soc_pcm_ops.copy = platform->pcm_ops->copy;
soc_pcm_ops.silence = platform->pcm_ops->silence;
soc_pcm_ops.ack = platform->pcm_ops->ack;
soc_pcm_ops.page = platform->pcm_ops->page;
if (playback)
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);

if (capture)
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);

You can know the substream - > Ops - > open = soc_pcm_open function.
This step is to call the soc_pcm_open function.

  1. Call the cpu_dai-> ops-> startup function.
if (cpu_dai->ops->startup) 
{
    ret = cpu_dai->ops->startup(substream, cpu_dai);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open interface %s\n",
            cpu_dai->name);
        goto out;
    }
}

cpu_dai = s3c24xx_i2s_dai, s3c24xx_i2s_dai - > Ops - > open = NULL, not executed here.

2. Call platform - > pcm_ops - > Open

if (platform->pcm_ops->open) 
{
    ret = platform->pcm_ops->open(substream);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
        goto platform_err;
    }
}

platform = s3c24xx_soc_platform,s3c24xx_soc_platform->pcm_ops->open = s3c24xx_pcm_open
Enter the s3c24xx_pcm_open function.

static int s3c24xx_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct s3c24xx_runtime_data *prtd;
	pr_debug("Entered %s\n", __func__);
	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
	snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware);
	prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
	if (prtd == NULL)
		return -ENOMEM;

	spin_lock_init(&prtd->lock);
	runtime->private_data = prtd;
	return 0;
}

When setting SNDRV_PCM_HW_PARAM_PERIODS item, shaping data is used.

snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);

Assignment of snd_pcm_runtime.hw.
runtime->hw.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME;
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |SNDRV_PCM_FMTBIT_S8;
runtime->hw.period_bytes_min = 4Kb;
runtime->hw.period_bytes_max = 8Kb;
runtime->hw.periods_min = 2;
runtime->hw.periods_max =128 ;
runtime->hw.buffer_bytes_max =128Kb ;
runtime->hw.fifo_size = 32;

3. Call the codec_dai-> ops-> startup function.

if (codec_dai->ops->startup) 
{
    ret = codec_dai->ops->startup(substream, codec_dai);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open codec %s\n",
            codec_dai->name);
        goto codec_dai_err;
    }
}

Codc_dai = uda134x_dai, codec_dai - > Ops - > startup = uda134x_startup, analyze the uda134x_startup function.

static int uda134x_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_device *socdev = rtd->socdev;
	struct snd_soc_codec *codec = socdev->card->codec;
	struct uda134x_priv *uda134x = codec->private_data;
	struct snd_pcm_runtime *master_runtime;

	if (uda134x->master_substream) {
		master_runtime = uda134x->master_substream->runtime;

		pr_debug("%s constraining to %d bits at %d\n", __func__,
			 master_runtime->sample_bits,
			 master_runtime->rate);

		snd_pcm_hw_constraint_minmax(substream->runtime,
					     SNDRV_PCM_HW_PARAM_RATE,
					     master_runtime->rate,
					     master_runtime->rate);

		snd_pcm_hw_constraint_minmax(substream->runtime,
					     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
					     master_runtime->sample_bits,
					     master_runtime->sample_bits);

		uda134x->slave_substream = substream;
	} else
		uda134x->master_substream = substream;

	//
	uda134x_write(codec, 2, 2|(5U<<2));
	return 0;
}

Setting Sampling Rate

snd_pcm_hw_constraint_minmax(substream->runtime,SNDRV_PCM_HW_PARAM_RATE,
 master_runtime->rate,master_runtime->rate);

Set sampling digits, 8 or 16 or other

snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,master_runtime->sample_bits,
master_runtime->sample_bits);

Set the sensitivity of the mic and the recording channel

uda134x_write(codec, 2, 2|(5U<<2));


Sensitivity setting is 21, recording channel is 2

4. Call machine - > Ops - > startup function
machine = s3c24xx_uda134x_dai_link,machine->ops->startup = s3c24xx_uda134x_startup
The purpose of this function is to get the sampling rate through pclk and xtal.

for (i = 0; i < 2; i++) 
{
    int fs = i ? 256 : 384;

    rates[i*33] = clk_get_rate(xtal) / fs;
    for (j = 1; j < 33; j++)
        rates[i*33 + j] = clk_get_rate(pclk) / (j * fs);
}

5. To recalculate some values of runtime - > HW according to codec_dai and cpu_dai, it is necessary to satisfy both codec_dai and cpu_dai.

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 
{
    runtime->hw.rate_min =max(codec_dai->playback.rate_min,cpu_dai->playback.rate_min);
    runtime->hw.rate_max =min(codec_dai->playback.rate_max,cpu_dai->playback.rate_max);
    runtime->hw.channels_min =max(codec_dai->playback.channels_min,cpu_dai->playback.channels_min);
    runtime->hw.channels_max =min(codec_dai->playback.channels_max,cpu_dai->playback.channels_max);
    runtime->hw.formats =codec_dai->playback.formats & cpu_dai->playback.formats;
    runtime->hw.rates =codec_dai->playback.rates & cpu_dai->playback.rates;
}
  • Call the snd_pcm_hw_constraints_complete function
    Reset the parameters of SNDRV_PCM_HW_PARAM_ACCESS, SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS and set them to runtime.hw_constraints.rules.

Posted by MilesWilson on Thu, 16 May 2019 09:13:40 -0700