Runtime Pointer - The Chest of PCM Information

When the PCM substream is opened, a PCM runtime instance is allocated and assigned to the substream. This pointer is accessible via substream->runtime. This runtime pointer holds the various information; it holds the copy of hw_params and sw_params configurations, the buffer pointers, mmap records, spinlocks, etc. Almost everything you need for controlling the PCM can be found there.

The definition of runtime instance is found in <sound/pcm.h>. Here is the copy from the file.

struct _snd_pcm_runtime {
	/* -- Status -- */
	struct snd_pcm_substream *trigger_master;
	snd_timestamp_t trigger_tstamp;	/* trigger timestamp */
	int overrange;
	snd_pcm_uframes_t avail_max;
	snd_pcm_uframes_t hw_ptr_base;	/* Position at buffer restart */
	snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time*/

	/* -- HW params -- */
	snd_pcm_access_t access;	/* access mode */
	snd_pcm_format_t format;	/* SNDRV_PCM_FORMAT_* */
	snd_pcm_subformat_t subformat;	/* subformat */
	unsigned int rate;		/* rate in Hz */
	unsigned int channels;		/* channels */
	snd_pcm_uframes_t period_size;	/* period size */
	unsigned int periods;		/* periods */
	snd_pcm_uframes_t buffer_size;	/* buffer size */
	unsigned int tick_time;		/* tick time */
	snd_pcm_uframes_t min_align;	/* Min alignment for the format */
	size_t byte_align;
	unsigned int frame_bits;
	unsigned int sample_bits;
	unsigned int info;
	unsigned int rate_num;
	unsigned int rate_den;

	/* -- SW params -- */
	struct timespec tstamp_mode;	/* mmap timestamp is updated */
  	unsigned int period_step;
	unsigned int sleep_min;		/* min ticks to sleep */
	snd_pcm_uframes_t xfer_align;	/* xfer size need to be a multiple */
	snd_pcm_uframes_t start_threshold;
	snd_pcm_uframes_t stop_threshold;
	snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
						noise is nearest than this */
	snd_pcm_uframes_t silence_size;	/* Silence filling size */
	snd_pcm_uframes_t boundary;	/* pointers wrap point */

	snd_pcm_uframes_t silenced_start;
	snd_pcm_uframes_t silenced_size;

	snd_pcm_sync_id_t sync;		/* hardware synchronization ID */

	/* -- mmap -- */
	volatile struct snd_pcm_mmap_status *status;
	volatile struct snd_pcm_mmap_control *control;
	atomic_t mmap_count;

	/* -- locking / scheduling -- */
	spinlock_t lock;
	wait_queue_head_t sleep;
	struct timer_list tick_timer;
	struct fasync_struct *fasync;

	/* -- private section -- */
	void *private_data;
	void (*private_free)(struct snd_pcm_runtime *runtime);

	/* -- hardware description -- */
	struct snd_pcm_hardware hw;
	struct snd_pcm_hw_constraints hw_constraints;

	/* -- interrupt callbacks -- */
	void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
	void (*transfer_ack_end)(struct snd_pcm_substream *substream);

	/* -- timer -- */
	unsigned int timer_resolution;	/* timer resolution */

	/* -- DMA -- */           
	unsigned char *dma_area;	/* DMA area */
	dma_addr_t dma_addr;		/* physical bus address (not accessible from main CPU) */
	size_t dma_bytes;		/* size of DMA area */

	struct snd_dma_buffer *dma_buffer_p;	/* allocated buffer */

#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
	/* -- OSS things -- */
	struct snd_pcm_oss_runtime oss;
#endif
};
            

For the operators (callbacks) of each sound driver, most of these records are supposed to be read-only. Only the PCM middle-layer changes / updates these info. The exceptions are the hardware description (hw), interrupt callbacks (transfer_ack_xxx), DMA buffer information, and the private data. Besides, if you use the standard buffer allocation method via snd_pcm_lib_malloc_pages(), you don't need to set the DMA buffer information by yourself.

In the sections below, important records are explained.

Hardware Description

The hardware descriptor (struct snd_pcm_hardware) contains the definitions of the fundamental hardware configuration. Above all, you'll need to define this in the open callback. Note that the runtime instance holds the copy of the descriptor, not the pointer to the existing descriptor. That is, in the open callback, you can modify the copied descriptor (runtime->hw) as you need. For example, if the maximum number of channels is 1 only on some chip models, you can still use the same hardware descriptor and change the channels_max later:

          struct snd_pcm_runtime *runtime = substream->runtime;
          ...
          runtime->hw = snd_mychip_playback_hw; /* common definition */
          if (chip->model == VERY_OLD_ONE)
                  runtime->hw.channels_max = 1;
            

Typically, you'll have a hardware descriptor like below:

  static struct snd_pcm_hardware snd_mychip_playback_hw = {
          .info = (SNDRV_PCM_INFO_MMAP |
                   SNDRV_PCM_INFO_INTERLEAVED |
                   SNDRV_PCM_INFO_BLOCK_TRANSFER |
                   SNDRV_PCM_INFO_MMAP_VALID),
          .formats =          SNDRV_PCM_FMTBIT_S16_LE,
          .rates =            SNDRV_PCM_RATE_8000_48000,
          .rate_min =         8000,
          .rate_max =         48000,
          .channels_min =     2,
          .channels_max =     2,
          .buffer_bytes_max = 32768,
          .period_bytes_min = 4096,
          .period_bytes_max = 32768,
          .periods_min =      1,
          .periods_max =      1024,
  };
            

PCM Configurations

Ok, let's go back again to the PCM runtime records. The most frequently referred records in the runtime instance are the PCM configurations. The PCM configurations are stored on runtime instance after the application sends hw_params data via alsa-lib. There are many fields copied from hw_params and sw_params structs. For example, format holds the format type chosen by the application. This field contains the enum value SNDRV_PCM_FORMAT_XXX.

One thing to be noted is that the configured buffer and period sizes are stored in "frames" in the runtime In the ALSA world, 1 frame = channels * samples-size. For conversion between frames and bytes, you can use the helper functions, frames_to_bytes() and bytes_to_frames().

  period_bytes = frames_to_bytes(runtime, runtime->period_size);
            

Also, many software parameters (sw_params) are stored in frames, too. Please check the type of the field. snd_pcm_uframes_t is for the frames as unsigned integer while snd_pcm_sframes_t is for the frames as signed integer.

DMA Buffer Information

The DMA buffer is defined by the following four fields, dma_area, dma_addr, dma_bytes and dma_private. The dma_area holds the buffer pointer (the logical address). You can call memcpy from/to this pointer. Meanwhile, dma_addr holds the physical address of the buffer. This field is specified only when the buffer is a linear buffer. dma_bytes holds the size of buffer in bytes. dma_private is used for the ALSA DMA allocator.

If you use a standard ALSA function, snd_pcm_lib_malloc_pages(), for allocating the buffer, these fields are set by the ALSA middle layer, and you should not change them by yourself. You can read them but not write them. On the other hand, if you want to allocate the buffer by yourself, you'll need to manage it in hw_params callback. At least, dma_bytes is mandatory. dma_area is necessary when the buffer is mmapped. If your driver doesn't support mmap, this field is not necessary. dma_addr is also not mandatory. You can use dma_private as you like, too.

Running Status

The running status can be referred via runtime->status. This is the pointer to struct snd_pcm_mmap_status record. For example, you can get the current DMA hardware pointer via runtime->status->hw_ptr.

The DMA application pointer can be referred via runtime->control, which points struct snd_pcm_mmap_control record. However, accessing directly to this value is not recommended.

Private Data

You can allocate a record for the substream and store it in runtime->private_data. Usually, this done in the open callback. Don't mix this with pcm->private_data. The pcm->private_data usually points the chip instance assigned statically at the creation of PCM, while the runtime->private_data points a dynamic data created at the PCM open callback.

  static int snd_xxx_open(struct snd_pcm_substream *substream)
  {
          struct my_pcm_data *data;
          ....
          data = kmalloc(sizeof(*data), GFP_KERNEL);
          substream->runtime->private_data = data;
          ....
  }
            

The allocated object must be released in the close callback.

Interrupt Callbacks

The field transfer_ack_begin and transfer_ack_end are called at the beginning and the end of snd_pcm_period_elapsed(), respectively.