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

Packit Service a9274b
// SPDX-License-Identifier: GPL-2.0
Packit Service a9274b
//
Packit Service a9274b
// xfer-libasound-irq-mmap.c - Timer-based scheduling model for mmap operation.
Packit Service a9274b
//
Packit Service a9274b
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
Packit Service a9274b
//
Packit Service a9274b
// Licensed under the terms of the GNU General Public License, version 2.
Packit Service a9274b
Packit Service a9274b
#include "xfer-libasound.h"
Packit Service a9274b
#include "misc.h"
Packit Service a9274b
Packit Service a9274b
struct map_layout {
Packit Service a9274b
	snd_pcm_status_t *status;
Packit Service a9274b
	bool need_forward_or_rewind;
Packit Service a9274b
	char **vector;
Packit Service a9274b
Packit Service a9274b
	unsigned int frames_per_second;
Packit Service a9274b
	unsigned int samples_per_frame;
Packit Service a9274b
	unsigned int frames_per_buffer;
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
static int timer_mmap_pre_process(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	snd_pcm_access_t access;
Packit Service a9274b
	snd_pcm_uframes_t frame_offset;
Packit Service a9274b
	snd_pcm_uframes_t avail = 0;
Packit Service a9274b
	snd_pcm_uframes_t frames_per_buffer;
Packit Service a9274b
	int i;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	// This parameter, 'period event', is a software feature in alsa-lib.
Packit Service a9274b
	// This switch a handler in 'hw' PCM plugin from irq-based one to
Packit Service a9274b
	// timer-based one. This handler has two file descriptors for
Packit Service a9274b
	// ALSA PCM character device and ALSA timer device. The latter is used
Packit Service a9274b
	// to catch suspend/resume events as wakeup event.
Packit Service a9274b
	err = snd_pcm_sw_params_set_period_event(state->handle,
Packit Service a9274b
						 state->sw_params, 1);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_status_malloc(&layout->status);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_access(state->hw_params, &access);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_channels(state->hw_params,
Packit Service a9274b
					     &layout->samples_per_frame);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_rate(state->hw_params,
Packit Service a9274b
					 &layout->frames_per_second, NULL);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
Packit Service a9274b
						&frames_per_buffer);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
	layout->frames_per_buffer = (unsigned int)frames_per_buffer;
Packit Service a9274b
Packit Service a9274b
	if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
Packit Service a9274b
		layout->vector = calloc(layout->samples_per_frame,
Packit Service a9274b
					sizeof(*layout->vector));
Packit Service a9274b
		if (layout->vector == NULL)
Packit Service a9274b
			return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (state->verbose) {
Packit Service a9274b
		const snd_pcm_channel_area_t *areas;
Packit Service a9274b
		err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
Packit Service a9274b
					 &avail);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			return err;
Packit Service a9274b
Packit Service a9274b
		logging(state, "attributes for mapped page frame:\n");
Packit Service a9274b
		for (i = 0; i < layout->samples_per_frame; ++i) {
Packit Service a9274b
			const snd_pcm_channel_area_t *area = areas + i;
Packit Service a9274b
Packit Service a9274b
			logging(state, "  sample number: %d\n", i);
Packit Service a9274b
			logging(state, "    address: %p\n", area->addr);
Packit Service a9274b
			logging(state, "    bits for offset: %u\n", area->first);
Packit Service a9274b
			logging(state, "    bits/frame: %u\n", area->step);
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static void *get_buffer(struct libasound_state *state,
Packit Service a9274b
			const snd_pcm_channel_area_t *areas,
Packit Service a9274b
			snd_pcm_uframes_t frame_offset)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	void *frame_buf;
Packit Service a9274b
Packit Service a9274b
	if (layout->vector == NULL) {
Packit Service a9274b
		char *buf;
Packit Service a9274b
		buf = areas[0].addr;
Packit Service a9274b
		buf += snd_pcm_frames_to_bytes(state->handle, frame_offset);
Packit Service a9274b
		frame_buf = buf;
Packit Service a9274b
	} else {
Packit Service a9274b
		int i;
Packit Service a9274b
		for (i = 0; i < layout->samples_per_frame; ++i) {
Packit Service a9274b
			layout->vector[i] = areas[i].addr;
Packit Service a9274b
			layout->vector[i] += snd_pcm_samples_to_bytes(
Packit Service a9274b
						state->handle, frame_offset);
Packit Service a9274b
		}
Packit Service a9274b
		frame_buf = layout->vector;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return frame_buf;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int timer_mmap_process_frames(struct libasound_state *state,
Packit Service a9274b
				     unsigned int *frame_count,
Packit Service a9274b
				     struct mapper_context *mapper,
Packit Service a9274b
				     struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	snd_pcm_uframes_t planned_count;
Packit Service a9274b
	snd_pcm_sframes_t avail;
Packit Service a9274b
	snd_pcm_uframes_t avail_count;
Packit Service a9274b
	const snd_pcm_channel_area_t *areas;
Packit Service a9274b
	snd_pcm_uframes_t frame_offset;
Packit Service a9274b
	void *frame_buf;
Packit Service a9274b
	snd_pcm_sframes_t consumed_count;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	// Retrieve avail space on PCM buffer between kernel/user spaces.
Packit Service a9274b
	// On cache incoherent architectures, still care of data
Packit Service a9274b
	// synchronization.
Packit Service a9274b
	avail = snd_pcm_avail_update(state->handle);
Packit Service a9274b
	if (avail < 0)
Packit Service a9274b
		return (int)avail;
Packit Service a9274b
Packit Service a9274b
	// Retrieve pointers of the buffer and left space up to the boundary.
Packit Service a9274b
	avail_count = (snd_pcm_uframes_t)avail;
Packit Service a9274b
	err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
Packit Service a9274b
				 &avail_count);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	// MEMO: Use the amount of data frames as you like.
Packit Service a9274b
	planned_count = layout->frames_per_buffer * random() / RAND_MAX;
Packit Service a9274b
	if (frame_offset + planned_count > layout->frames_per_buffer)
Packit Service a9274b
		planned_count = layout->frames_per_buffer - frame_offset;
Packit Service a9274b
Packit Service a9274b
	// Trim up to expected frame count.
Packit Service a9274b
	if (*frame_count < planned_count)
Packit Service a9274b
		planned_count = *frame_count;
Packit Service a9274b
Packit Service a9274b
	// Yield this CPU till planned amount of frames become available.
Packit Service a9274b
	if (avail_count < planned_count) {
Packit Service a9274b
		unsigned short revents;
Packit Service a9274b
		int timeout_msec;
Packit Service a9274b
Packit Service a9274b
		// TODO; precise granularity of timeout; e.g. ppoll(2).
Packit Service a9274b
		// Furthermore, wrap up according to granularity of reported
Packit Service a9274b
		// value for hw_ptr.
Packit Service a9274b
		timeout_msec = ((planned_count - avail_count) * 1000 +
Packit Service a9274b
				layout->frames_per_second - 1) /
Packit Service a9274b
			       layout->frames_per_second;
Packit Service a9274b
Packit Service a9274b
		// TODO: However, experimentally, the above is not enough to
Packit Service a9274b
		// keep planned amount of frames when waking up. I don't know
Packit Service a9274b
		// exactly the mechanism yet.
Packit Service a9274b
		err = xfer_libasound_wait_event(state, timeout_msec,
Packit Service a9274b
						&revents);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			return err;
Packit Service a9274b
		if (revents & POLLERR) {
Packit Service a9274b
			// TODO: error reporting.
Packit Service a9274b
			return -EIO;
Packit Service a9274b
		}
Packit Service a9274b
		if (!(revents & (POLLIN | POLLOUT)))
Packit Service a9274b
			return -EAGAIN;
Packit Service a9274b
Packit Service a9274b
		// MEMO: Need to perform hwsync explicitly because hwptr is not
Packit Service a9274b
		// synchronized to actual position of data frame transmission
Packit Service a9274b
		// on hardware because IRQ handlers are not used in this
Packit Service a9274b
		// scheduling strategy.
Packit Service a9274b
		avail = snd_pcm_avail(state->handle);
Packit Service a9274b
		if (avail < 0)
Packit Service a9274b
			return (int)avail;
Packit Service a9274b
		if (avail < planned_count) {
Packit Service a9274b
			logging(state,
Packit Service a9274b
				"Wake up but not enough space: %lu %lu %u\n",
Packit Service a9274b
				planned_count, avail, timeout_msec);
Packit Service a9274b
			planned_count = avail;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	// Let's process data frames.
Packit Service a9274b
	*frame_count = planned_count;
Packit Service a9274b
	frame_buf = get_buffer(state, areas, frame_offset);
Packit Service a9274b
	err = mapper_context_process_frames(mapper, frame_buf, frame_count,
Packit Service a9274b
					    cntrs);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
Packit Service a9274b
					     *frame_count);
Packit Service a9274b
	if (consumed_count != *frame_count) {
Packit Service a9274b
		logging(state,
Packit Service a9274b
			"A bug of 'hw' PCM plugin or driver for this PCM "
Packit Service a9274b
			"node.\n");
Packit Service a9274b
	}
Packit Service a9274b
	*frame_count = consumed_count;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int forward_appl_ptr(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	snd_pcm_uframes_t forwardable_count;
Packit Service a9274b
	snd_pcm_sframes_t forward_count;
Packit Service a9274b
Packit Service a9274b
	forward_count = snd_pcm_forwardable(state->handle);
Packit Service a9274b
	if (forward_count < 0)
Packit Service a9274b
		return (int)forward_count;
Packit Service a9274b
	forwardable_count = forward_count;
Packit Service a9274b
Packit Service a9274b
	// No need to add safe-gurard because hwptr goes ahead.
Packit Service a9274b
	forward_count = snd_pcm_forward(state->handle, forwardable_count);
Packit Service a9274b
	if (forward_count < 0)
Packit Service a9274b
		return (int)forward_count;
Packit Service a9274b
Packit Service a9274b
	if (state->verbose) {
Packit Service a9274b
		logging(state,
Packit Service a9274b
			"  forwarded: %lu/%u\n",
Packit Service a9274b
			forward_count, layout->frames_per_buffer);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int timer_mmap_r_process_frames(struct libasound_state *state,
Packit Service a9274b
				       unsigned *frame_count,
Packit Service a9274b
				       struct mapper_context *mapper,
Packit Service a9274b
				       struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	snd_pcm_state_t s;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	// SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP suppresses any IRQ to notify
Packit Service a9274b
	// period elapse for data transmission, therefore no need to care of
Packit Service a9274b
	// concurrent access by IRQ context and process context, unlike
Packit Service a9274b
	// IRQ-based operations.
Packit Service a9274b
	// Here, this is just to query current status to hardware, for later
Packit Service a9274b
	// processing.
Packit Service a9274b
	err = snd_pcm_status(state->handle, layout->status);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		goto error;
Packit Service a9274b
	s = snd_pcm_status_get_state(layout->status);
Packit Service a9274b
Packit Service a9274b
	// TODO: if reporting something, do here with the status data.
Packit Service a9274b
Packit Service a9274b
	if (s == SND_PCM_STATE_RUNNING) {
Packit Service a9274b
		// Reduce delay between sampling on hardware and handling by
Packit Service a9274b
		// this program.
Packit Service a9274b
		if (layout->need_forward_or_rewind) {
Packit Service a9274b
			err = forward_appl_ptr(state);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				goto error;
Packit Service a9274b
			layout->need_forward_or_rewind = false;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		err = timer_mmap_process_frames(state, frame_count, mapper,
Packit Service a9274b
						cntrs);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
	} else {
Packit Service a9274b
		if (s == SND_PCM_STATE_PREPARED) {
Packit Service a9274b
			// For capture direction, need to start stream
Packit Service a9274b
			// explicitly.
Packit Service a9274b
			err = snd_pcm_start(state->handle);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				goto error;
Packit Service a9274b
			layout->need_forward_or_rewind = true;
Packit Service a9274b
			// Not yet.
Packit Service a9274b
			*frame_count = 0;
Packit Service a9274b
		} else {
Packit Service a9274b
			err = -EPIPE;
Packit Service a9274b
			goto error;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
error:
Packit Service a9274b
	*frame_count = 0;
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int rewind_appl_ptr(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	snd_pcm_uframes_t rewindable_count;
Packit Service a9274b
	snd_pcm_sframes_t rewind_count;
Packit Service a9274b
Packit Service a9274b
	rewind_count = snd_pcm_rewindable(state->handle);
Packit Service a9274b
	if (rewind_count < 0)
Packit Service a9274b
		return (int)rewind_count;
Packit Service a9274b
	rewindable_count = rewind_count;
Packit Service a9274b
Packit Service a9274b
	// If appl_ptr were rewound just to position of hw_ptr, at next time,
Packit Service a9274b
	// hw_ptr could catch up appl_ptr. This is overrun. We need a space
Packit Service a9274b
	// between these two pointers to prevent this XRUN.
Packit Service a9274b
	// This space is largely affected by time to process data frames later.
Packit Service a9274b
	//
Packit Service a9274b
	// TODO: a generous way to estimate a good value.
Packit Service a9274b
	if (rewindable_count < 32)
Packit Service a9274b
		return 0;
Packit Service a9274b
	rewindable_count -= 32;
Packit Service a9274b
Packit Service a9274b
	rewind_count = snd_pcm_rewind(state->handle, rewindable_count);
Packit Service a9274b
	if (rewind_count < 0)
Packit Service a9274b
		return (int)rewind_count;
Packit Service a9274b
Packit Service a9274b
	if (state->verbose) {
Packit Service a9274b
		logging(state,
Packit Service a9274b
			"  rewound: %lu/%u\n",
Packit Service a9274b
			rewind_count, layout->frames_per_buffer);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int fill_buffer_with_zero_samples(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	const snd_pcm_channel_area_t *areas;
Packit Service a9274b
	snd_pcm_uframes_t frame_offset;
Packit Service a9274b
	snd_pcm_uframes_t avail_count;
Packit Service a9274b
	snd_pcm_format_t sample_format;
Packit Service a9274b
	snd_pcm_uframes_t consumed_count;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
Packit Service a9274b
						&avail_count);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
Packit Service a9274b
				 &avail_count);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_hw_params_get_format(state->hw_params, &sample_format);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	err = snd_pcm_areas_silence(areas, frame_offset,
Packit Service a9274b
				    layout->samples_per_frame, avail_count,
Packit Service a9274b
				    sample_format);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
Packit Service a9274b
					     avail_count);
Packit Service a9274b
	if (consumed_count != avail_count)
Packit Service a9274b
		logging(state, "A bug of access plugin for this PCM node.\n");
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int timer_mmap_w_process_frames(struct libasound_state *state,
Packit Service a9274b
				       unsigned *frame_count,
Packit Service a9274b
				       struct mapper_context *mapper,
Packit Service a9274b
				       struct container_context *cntrs)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
	snd_pcm_state_t s;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	// Read my comment in 'timer_mmap_w_process_frames()'.
Packit Service a9274b
	err = snd_pcm_status(state->handle, layout->status);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		goto error;
Packit Service a9274b
	s = snd_pcm_status_get_state(layout->status);
Packit Service a9274b
Packit Service a9274b
	// TODO: if reporting something, do here with the status data.
Packit Service a9274b
Packit Service a9274b
	if (s == SND_PCM_STATE_RUNNING) {
Packit Service a9274b
		// Reduce delay between queueing by this program and presenting
Packit Service a9274b
		// on hardware.
Packit Service a9274b
		if (layout->need_forward_or_rewind) {
Packit Service a9274b
			err = rewind_appl_ptr(state);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				goto error;
Packit Service a9274b
			layout->need_forward_or_rewind = false;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		err = timer_mmap_process_frames(state, frame_count, mapper,
Packit Service a9274b
						cntrs);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			goto error;
Packit Service a9274b
	} else {
Packit Service a9274b
		// Need to start playback stream explicitly
Packit Service a9274b
		if (s == SND_PCM_STATE_PREPARED) {
Packit Service a9274b
			err = fill_buffer_with_zero_samples(state);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				goto error;
Packit Service a9274b
Packit Service a9274b
			err = snd_pcm_start(state->handle);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				goto error;
Packit Service a9274b
Packit Service a9274b
			layout->need_forward_or_rewind = true;
Packit Service a9274b
			// Not yet.
Packit Service a9274b
			*frame_count = 0;
Packit Service a9274b
		} else {
Packit Service a9274b
			err = -EPIPE;
Packit Service a9274b
			goto error;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
error:
Packit Service a9274b
	*frame_count = 0;
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static void timer_mmap_post_process(struct libasound_state *state)
Packit Service a9274b
{
Packit Service a9274b
	struct map_layout *layout = state->private_data;
Packit Service a9274b
Packit Service a9274b
	if (layout->status)
Packit Service a9274b
		snd_pcm_status_free(layout->status);
Packit Service a9274b
	layout->status = NULL;
Packit Service a9274b
Packit Service a9274b
	if (layout->vector)
Packit Service a9274b
		free(layout->vector);
Packit Service a9274b
	layout->vector = NULL;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
const struct xfer_libasound_ops xfer_libasound_timer_mmap_w_ops = {
Packit Service a9274b
	.pre_process	= timer_mmap_pre_process,
Packit Service a9274b
	.process_frames	= timer_mmap_w_process_frames,
Packit Service a9274b
	.post_process	= timer_mmap_post_process,
Packit Service a9274b
	.private_size	= sizeof(struct map_layout),
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
const struct xfer_libasound_ops xfer_libasound_timer_mmap_r_ops = {
Packit Service a9274b
	.pre_process	= timer_mmap_pre_process,
Packit Service a9274b
	.process_frames	= timer_mmap_r_process_frames,
Packit Service a9274b
	.post_process	= timer_mmap_post_process,
Packit Service a9274b
	.private_size	= sizeof(struct map_layout),
Packit Service a9274b
};