Blame axfer/xfer-libasound-irq-mmap.c

Packit 229ac0
// SPDX-License-Identifier: GPL-2.0
Packit 229ac0
//
Packit 229ac0
// xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation.
Packit 229ac0
//
Packit 229ac0
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
Packit 229ac0
//
Packit 229ac0
// Licensed under the terms of the GNU General Public License, version 2.
Packit 229ac0
Packit 229ac0
#include "xfer-libasound.h"
Packit 229ac0
#include "misc.h"
Packit 229ac0
Packit 229ac0
struct map_layout {
Packit 229ac0
	snd_pcm_status_t *status;
Packit 229ac0
Packit 229ac0
	char **vector;
Packit 229ac0
	unsigned int samples_per_frame;
Packit 229ac0
};
Packit 229ac0
Packit 229ac0
static int irq_mmap_pre_process(struct libasound_state *state)
Packit 229ac0
{
Packit 229ac0
	struct map_layout *layout = state->private_data;
Packit 229ac0
	snd_pcm_access_t access;
Packit 229ac0
	snd_pcm_uframes_t frame_offset;
Packit 229ac0
	snd_pcm_uframes_t avail = 0;
Packit 229ac0
	int i;
Packit 229ac0
	int err;
Packit 229ac0
Packit 229ac0
	err = snd_pcm_status_malloc(&layout->status);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		return err;
Packit 229ac0
Packit 229ac0
	err = snd_pcm_hw_params_get_access(state->hw_params, &access);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		return err;
Packit 229ac0
Packit 229ac0
	err = snd_pcm_hw_params_get_channels(state->hw_params,
Packit 229ac0
					     &layout->samples_per_frame);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		return err;
Packit 229ac0
Packit 229ac0
	if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
Packit 229ac0
		layout->vector = calloc(layout->samples_per_frame,
Packit 229ac0
					sizeof(*layout->vector));
Packit 229ac0
		if (layout->vector == NULL)
Packit 229ac0
			return err;
Packit 229ac0
	}
Packit 229ac0
Packit 229ac0
	if (state->verbose) {
Packit 229ac0
		const snd_pcm_channel_area_t *areas;
Packit 229ac0
		err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
Packit 229ac0
					 &avail);
Packit 229ac0
		if (err < 0)
Packit 229ac0
			return err;
Packit 229ac0
Packit 229ac0
		logging(state, "attributes for mapped page frame:\n");
Packit 229ac0
		for (i = 0; i < layout->samples_per_frame; ++i) {
Packit 229ac0
			const snd_pcm_channel_area_t *area = areas + i;
Packit 229ac0
Packit 229ac0
			logging(state, "  sample number: %d\n", i);
Packit 229ac0
			logging(state, "    address: %p\n", area->addr);
Packit 229ac0
			logging(state, "    bits for offset: %u\n", area->first);
Packit 229ac0
			logging(state, "    bits/frame: %u\n", area->step);
Packit 229ac0
		}
Packit 229ac0
		logging(state, "\n");
Packit 229ac0
	}
Packit 229ac0
Packit 229ac0
	return 0;
Packit 229ac0
}
Packit 229ac0
Packit 229ac0
static int irq_mmap_process_frames(struct libasound_state *state,
Packit 229ac0
				   unsigned int *frame_count,
Packit 229ac0
				   struct mapper_context *mapper,
Packit 229ac0
				   struct container_context *cntrs)
Packit 229ac0
{
Packit 229ac0
	struct map_layout *layout = state->private_data;
Packit 229ac0
	const snd_pcm_channel_area_t *areas;
Packit 229ac0
	snd_pcm_uframes_t frame_offset;
Packit 229ac0
	snd_pcm_uframes_t avail;
Packit 229ac0
	unsigned int avail_count;
Packit 229ac0
	void *frame_buf;
Packit 229ac0
	snd_pcm_sframes_t consumed_count;
Packit 229ac0
	int err;
Packit 229ac0
Packit 229ac0
	if (state->use_waiter) {
Packit 229ac0
		unsigned int msec_per_buffer;
Packit 229ac0
		unsigned short revents;
Packit 229ac0
Packit 229ac0
		// Wait during msec equivalent to all audio data frames in
Packit 229ac0
		// buffer instead of period, for safe.
Packit 229ac0
		err = snd_pcm_hw_params_get_buffer_time(state->hw_params,
Packit 229ac0
							&msec_per_buffer, NULL);
Packit 229ac0
		if (err < 0)
Packit 229ac0
			return err;
Packit 229ac0
		msec_per_buffer /= 1000;
Packit 229ac0
Packit 229ac0
		// Wait for hardware IRQ when no avail space in buffer.
Packit 229ac0
		err = xfer_libasound_wait_event(state, msec_per_buffer,
Packit 229ac0
						&revents);
Packit 229ac0
		if (err == -ETIMEDOUT) {
Packit 229ac0
			logging(state,
Packit 229ac0
				"No event occurs for PCM substream during %u "
Packit 229ac0
				"msec. The implementaion of kernel driver or "
Packit 229ac0
				"userland backend causes this issue.\n",
Packit 229ac0
				msec_per_buffer);
Packit 229ac0
			return err;
Packit 229ac0
		}
Packit 229ac0
		if (err < 0)
Packit 229ac0
			return err;
Packit 229ac0
		if (revents & POLLERR) {
Packit 229ac0
			// TODO: error reporting?
Packit 229ac0
			return -EIO;
Packit 229ac0
		}
Packit 229ac0
		if (!(revents & (POLLIN | POLLOUT)))
Packit 229ac0
			return -EAGAIN;
Packit 229ac0
Packit 229ac0
		// When rescheduled, current position of data transmission was
Packit 229ac0
		// queried to actual hardware by a handler of IRQ. No need to
Packit 229ac0
		// perform it; e.g. ioctl(2) with SNDRV_PCM_IOCTL_HWSYNC.
Packit 229ac0
	}
Packit 229ac0
Packit 229ac0
	// Sync cache in user space to data in kernel space to calculate avail
Packit 229ac0
	// frames according to the latest positions on PCM buffer.
Packit 229ac0
	//
Packit 229ac0
	// This has an additional advantage to handle libasound PCM plugins.
Packit 229ac0
	// Most of libasound PCM plugins perform resampling in .avail_update()
Packit 229ac0
	// callback for capture PCM substream, then update positions on buffer.
Packit 229ac0
	//
Packit 229ac0
	// MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can
Packit 229ac0
	// return the same number of available frames.
Packit 229ac0
	avail = snd_pcm_avail_update(state->handle);
Packit 229ac0
	if ((snd_pcm_sframes_t)avail < 0)
Packit 229ac0
		return (int)avail;
Packit 229ac0
	if (*frame_count < avail)
Packit 229ac0
		avail = *frame_count;
Packit 229ac0
Packit 229ac0
	err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		return err;
Packit 229ac0
Packit 229ac0
	// Trim according up to expected frame count.
Packit 229ac0
	if (*frame_count < avail)
Packit 229ac0
		avail_count = *frame_count;
Packit 229ac0
	else
Packit 229ac0
		avail_count = (unsigned int)avail;
Packit 229ac0
Packit 229ac0
	// TODO: Perhaps, the complex layout can be supported as a variation of
Packit 229ac0
	// vector type. However, there's no driver with this layout.
Packit 229ac0
	if (layout->vector == NULL) {
Packit 229ac0
		frame_buf = areas[0].addr;
Packit 229ac0
		frame_buf += snd_pcm_frames_to_bytes(state->handle,
Packit 229ac0
						     frame_offset);
Packit 229ac0
	} else {
Packit 229ac0
		int i;
Packit 229ac0
		for (i = 0; i < layout->samples_per_frame; ++i) {
Packit 229ac0
			layout->vector[i] = areas[i].addr;
Packit 229ac0
			layout->vector[i] += snd_pcm_samples_to_bytes(
Packit 229ac0
						state->handle, frame_offset);
Packit 229ac0
		}
Packit 229ac0
		frame_buf = layout->vector;
Packit 229ac0
	}
Packit 229ac0
Packit 229ac0
	err = mapper_context_process_frames(mapper, frame_buf, &avail_count,
Packit 229ac0
					    cntrs);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		return err;
Packit 229ac0
	if (avail_count == 0) {
Packit 229ac0
		*frame_count = 0;
Packit 229ac0
		return 0;
Packit 229ac0
	}
Packit 229ac0
Packit 229ac0
	consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
Packit 229ac0
					     avail_count);
Packit 229ac0
	if (consumed_count < 0)
Packit 229ac0
		return (int)consumed_count;
Packit 229ac0
	if (consumed_count != avail_count)
Packit 229ac0
		logging(state, "A bug of access plugin for this PCM node.\n");
Packit 229ac0
Packit 229ac0
	*frame_count = consumed_count;
Packit 229ac0
Packit 229ac0
	return 0;
Packit 229ac0
}
Packit 229ac0
Packit 229ac0
static int irq_mmap_r_process_frames(struct libasound_state *state,
Packit 229ac0
				     unsigned *frame_count,
Packit 229ac0
				     struct mapper_context *mapper,
Packit 229ac0
				     struct container_context *cntrs)
Packit 229ac0
{
Packit 229ac0
	struct map_layout *layout = state->private_data;
Packit 229ac0
	snd_pcm_state_t s;
Packit 229ac0
	int err;
Packit 229ac0
Packit 229ac0
	// To querying current status of hardware, we need to care of
Packit 229ac0
	// synchronization between 3 levels:
Packit 229ac0
	//  1. status to actual hardware by driver.
Packit 229ac0
	//  2. status data in kernel space.
Packit 229ac0
	//  3. status data in user space.
Packit 229ac0
	//
Packit 229ac0
	// Kernel driver query 1 and sync 2, according to requests of some
Packit 229ac0
	// ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core
Packit 229ac0
	// supports mmap(2) operation on cache coherent architectures, some
Packit 229ac0
	// ioctl(2) commands on cache incoherent architecture. In usage of the
Packit 229ac0
	// former mechanism, we need to care of concurrent access by IRQ context
Packit 229ac0
	// and process context to the mapped page frame.
Packit 229ac0
	// In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and
Packit 229ac0
	// SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because
Packit 229ac0
	// mapped page frame is unused regardless of architectures in a point of
Packit 229ac0
	// cache coherency.
Packit 229ac0
	err = snd_pcm_status(state->handle, layout->status);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		goto error;
Packit 229ac0
	s = snd_pcm_status_get_state(layout->status);
Packit 229ac0
Packit 229ac0
	// TODO: if reporting something, do here with the status data.
Packit 229ac0
Packit 229ac0
	// For capture direction, need to start stream explicitly.
Packit 229ac0
	if (s != SND_PCM_STATE_RUNNING) {
Packit 229ac0
		if (s != SND_PCM_STATE_PREPARED) {
Packit 229ac0
			err = -EPIPE;
Packit 229ac0
			goto error;
Packit 229ac0
		}
Packit 229ac0
Packit 229ac0
		err = snd_pcm_start(state->handle);
Packit 229ac0
		if (err < 0)
Packit 229ac0
			goto error;
Packit 229ac0
	}
Packit 229ac0
Packit 229ac0
	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		goto error;
Packit 229ac0
Packit 229ac0
	return 0;
Packit 229ac0
error:
Packit 229ac0
	*frame_count = 0;
Packit 229ac0
	return err;
Packit 229ac0
}
Packit 229ac0
Packit 229ac0
static int irq_mmap_w_process_frames(struct libasound_state *state,
Packit 229ac0
				     unsigned *frame_count,
Packit 229ac0
				     struct mapper_context *mapper,
Packit 229ac0
				     struct container_context *cntrs)
Packit 229ac0
{
Packit 229ac0
	struct map_layout *layout = state->private_data;
Packit 229ac0
	snd_pcm_state_t s;
Packit 229ac0
	int err;
Packit 229ac0
Packit 229ac0
	// Read my comment in 'irq_mmap_r_process_frames().
Packit 229ac0
	err = snd_pcm_status(state->handle, layout->status);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		goto error;
Packit 229ac0
	s = snd_pcm_status_get_state(layout->status);
Packit 229ac0
Packit 229ac0
	// TODO: if reporting something, do here with the status data.
Packit 229ac0
Packit 229ac0
	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
Packit 229ac0
	if (err < 0)
Packit 229ac0
		goto error;
Packit 229ac0
Packit 229ac0
	// Need to start playback stream explicitly
Packit 229ac0
	if (s != SND_PCM_STATE_RUNNING) {
Packit 229ac0
		if (s != SND_PCM_STATE_PREPARED) {
Packit 229ac0
			err = -EPIPE;
Packit 229ac0
			goto error;
Packit 229ac0
		}
Packit 229ac0
Packit 229ac0
		err = snd_pcm_start(state->handle);
Packit 229ac0
		if (err < 0)
Packit 229ac0
			goto error;
Packit 229ac0
	}
Packit 229ac0
Packit 229ac0
	return 0;
Packit 229ac0
error:
Packit 229ac0
	*frame_count = 0;
Packit 229ac0
	return err;
Packit 229ac0
}
Packit 229ac0
Packit 229ac0
static void irq_mmap_post_process(struct libasound_state *state)
Packit 229ac0
{
Packit 229ac0
	struct map_layout *layout = state->private_data;
Packit 229ac0
Packit 229ac0
	if (layout->status)
Packit 229ac0
		snd_pcm_status_free(layout->status);
Packit 229ac0
	layout->status = NULL;
Packit 229ac0
Packit 229ac0
	free(layout->vector);
Packit 229ac0
	layout->vector = NULL;
Packit 229ac0
}
Packit 229ac0
Packit 229ac0
const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = {
Packit 229ac0
	.pre_process	= irq_mmap_pre_process,
Packit 229ac0
	.process_frames	= irq_mmap_w_process_frames,
Packit 229ac0
	.post_process	= irq_mmap_post_process,
Packit 229ac0
	.private_size	= sizeof(struct map_layout),
Packit 229ac0
};
Packit 229ac0
Packit 229ac0
const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = {
Packit 229ac0
	.pre_process	= irq_mmap_pre_process,
Packit 229ac0
	.process_frames	= irq_mmap_r_process_frames,
Packit 229ac0
	.post_process	= irq_mmap_post_process,
Packit 229ac0
	.private_size	= sizeof(struct map_layout),
Packit 229ac0
};