Commit 2c92e2fb authored by Joris Verhaegen's avatar Joris Verhaegen Committed by Takashi Iwai
Browse files

ALSA: compress_offload: Add 64-bit safe timestamp infrastructure



The copied_total field in struct snd_compr_tstamp is a 32-bit
value that can overflow on long-running high-bitrate streams,
leading to incorrect calculations for buffer availablility.

This patch adds a 64-bit safe timestamping mechanism.
A new UAPI struct, snd_compr_tstamp64, is added which uses 64-bit
types for byte counters. The relevant ops structures across the
ASoC and core compress code are updated to use this new struct.
ASoC drivers are updated to use u64 counters.

Internal timestamps being u64 now, a compatibility function is added
to convert the 64-bit timestamp back to the 32-bit format for legacy
ioctl callers.

Reviewed-by: default avatarMiller Liang <millerliang@google.com>
Tested-by: default avatarJoris Verhaegen <verhaegen@google.com>
Signed-off-by: default avatarJoris Verhaegen <verhaegen@google.com>
Reviewed-by: default avatarSrinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
Reviewed-by: default avatarCharles Keepax <ckeepax@opensource.cirrus.com>
Acked-by: default avatarMark Brown <broonie@kernel.org>
Acked-by: default avatarVinod Koul <vkoul@kernel.org>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20250905091301.2711705-2-verhaegen@google.com
parent cbd67687
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -161,7 +161,7 @@ struct snd_compr_ops {
			struct snd_compr_metadata *metadata);
	int (*trigger)(struct snd_compr_stream *stream, int cmd);
	int (*pointer)(struct snd_compr_stream *stream,
			struct snd_compr_tstamp *tstamp);
		       struct snd_compr_tstamp64 *tstamp);
	int (*copy)(struct snd_compr_stream *stream, char __user *buf,
		       size_t count);
	int (*mmap)(struct snd_compr_stream *stream,
+2 −2
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ struct snd_compress_ops {
		       struct snd_compr_stream *stream, int cmd);
	int (*pointer)(struct snd_soc_component *component,
		       struct snd_compr_stream *stream,
		       struct snd_compr_tstamp *tstamp);
		       struct snd_compr_tstamp64 *tstamp);
	int (*copy)(struct snd_soc_component *component,
		    struct snd_compr_stream *stream, char __user *buf,
		    size_t count);
@@ -498,7 +498,7 @@ int snd_soc_component_compr_get_codec_caps(struct snd_compr_stream *cstream,
					   struct snd_compr_codec_caps *codec);
int snd_soc_component_compr_ack(struct snd_compr_stream *cstream, size_t bytes);
int snd_soc_component_compr_pointer(struct snd_compr_stream *cstream,
				    struct snd_compr_tstamp *tstamp);
				    struct snd_compr_tstamp64 *tstamp);
int snd_soc_component_compr_copy(struct snd_compr_stream *cstream,
				 char __user *buf, size_t count);
int snd_soc_component_compr_set_metadata(struct snd_compr_stream *cstream,
+4 −3
Original line number Diff line number Diff line
@@ -256,7 +256,7 @@ int snd_soc_dai_compr_ack(struct snd_soc_dai *dai,
			  size_t bytes);
int snd_soc_dai_compr_pointer(struct snd_soc_dai *dai,
			      struct snd_compr_stream *cstream,
			      struct snd_compr_tstamp *tstamp);
			      struct snd_compr_tstamp64 *tstamp);
int snd_soc_dai_compr_set_metadata(struct snd_soc_dai *dai,
				   struct snd_compr_stream *cstream,
				   struct snd_compr_metadata *metadata);
@@ -383,8 +383,9 @@ struct snd_soc_cdai_ops {
			struct snd_compr_metadata *, struct snd_soc_dai *);
	int (*trigger)(struct snd_compr_stream *, int,
			struct snd_soc_dai *);
	int (*pointer)(struct snd_compr_stream *,
			struct snd_compr_tstamp *, struct snd_soc_dai *);
	int (*pointer)(struct snd_compr_stream *stream,
		       struct snd_compr_tstamp64 *tstamp,
		       struct snd_soc_dai *dai);
	int (*ack)(struct snd_compr_stream *, size_t,
			struct snd_soc_dai *);
};
+19 −0
Original line number Diff line number Diff line
@@ -56,6 +56,25 @@ struct snd_compr_tstamp {
	__u32 sampling_rate;
} __attribute__((packed, aligned(4)));

/**
 * struct snd_compr_tstamp64 - timestamp descriptor with fields in 64 bit
 * @byte_offset: Byte offset in ring buffer to DSP
 * @copied_total: Total number of bytes copied from/to ring buffer to/by DSP
 * @pcm_frames: Frames decoded or encoded by DSP. This field will evolve by
 *	large steps and should only be used to monitor encoding/decoding
 *	progress. It shall not be used for timing estimates.
 * @pcm_io_frames: Frames rendered or received by DSP into a mixer or an audio
 * output/input. This field should be used for A/V sync or time estimates.
 * @sampling_rate: sampling rate of audio
 */
struct snd_compr_tstamp64 {
	__u32 byte_offset;
	__u64 copied_total;
	__u64 pcm_frames;
	__u64 pcm_io_frames;
	__u32 sampling_rate;
} __attribute__((packed, aligned(4)));

/**
 * struct snd_compr_avail - avail descriptor
 * @avail: Number of bytes available in ring buffer for writing/reading
+35 −17
Original line number Diff line number Diff line
@@ -176,14 +176,25 @@ static int snd_compr_free(struct inode *inode, struct file *f)
	return 0;
}

static void
snd_compr_tstamp32_from_64(struct snd_compr_tstamp *tstamp32,
			   const struct snd_compr_tstamp64 *tstamp64)
{
	tstamp32->byte_offset = tstamp64->byte_offset;
	tstamp32->copied_total = (u32)tstamp64->copied_total;
	tstamp32->pcm_frames = (u32)tstamp64->pcm_frames;
	tstamp32->pcm_io_frames = (u32)tstamp64->pcm_io_frames;
	tstamp32->sampling_rate = tstamp64->sampling_rate;
}

static int snd_compr_update_tstamp(struct snd_compr_stream *stream,
		struct snd_compr_tstamp *tstamp)
				   struct snd_compr_tstamp64 *tstamp)
{
	if (!stream->ops->pointer)
		return -ENOTSUPP;
	stream->ops->pointer(stream, tstamp);
	pr_debug("dsp consumed till %d total %d bytes\n",
		tstamp->byte_offset, tstamp->copied_total);
	pr_debug("dsp consumed till %u total %llu bytes\n", tstamp->byte_offset,
		 tstamp->copied_total);
	if (stream->direction == SND_COMPRESS_PLAYBACK)
		stream->runtime->total_bytes_transferred = tstamp->copied_total;
	else
@@ -194,8 +205,11 @@ static int snd_compr_update_tstamp(struct snd_compr_stream *stream,
static size_t snd_compr_calc_avail(struct snd_compr_stream *stream,
		struct snd_compr_avail *avail)
{
	struct snd_compr_tstamp64 tstamp64 = { 0 };

	memset(avail, 0, sizeof(*avail));
	snd_compr_update_tstamp(stream, &avail->tstamp);
	snd_compr_update_tstamp(stream, &tstamp64);
	snd_compr_tstamp32_from_64(&avail->tstamp, &tstamp64);
	/* Still need to return avail even if tstamp can't be filled in */

	if (stream->runtime->total_bytes_available == 0 &&
@@ -204,7 +218,7 @@ static size_t snd_compr_calc_avail(struct snd_compr_stream *stream,
		pr_debug("detected init and someone forgot to do a write\n");
		return stream->runtime->buffer_size;
	}
	pr_debug("app wrote %lld, DSP consumed %lld\n",
	pr_debug("app wrote %llu, DSP consumed %llu\n",
		 stream->runtime->total_bytes_available,
		 stream->runtime->total_bytes_transferred);
	if (stream->runtime->total_bytes_available ==
@@ -223,7 +237,7 @@ static size_t snd_compr_calc_avail(struct snd_compr_stream *stream,
	if (stream->direction == SND_COMPRESS_PLAYBACK)
		avail->avail = stream->runtime->buffer_size - avail->avail;

	pr_debug("ret avail as %lld\n", avail->avail);
	pr_debug("ret avail as %llu\n", avail->avail);
	return avail->avail;
}

@@ -274,8 +288,7 @@ static int snd_compr_write_data(struct snd_compr_stream *stream,
		      (app_pointer * runtime->buffer_size);

	dstn = runtime->buffer + app_pointer;
	pr_debug("copying %ld at %lld\n",
			(unsigned long)count, app_pointer);
	pr_debug("copying %lu at %llu\n", (unsigned long)count, app_pointer);
	if (count < runtime->buffer_size - app_pointer) {
		if (copy_from_user(dstn, buf, count))
			return -EFAULT;
@@ -318,7 +331,7 @@ static ssize_t snd_compr_write(struct file *f, const char __user *buf,
	}

	avail = snd_compr_get_avail(stream);
	pr_debug("avail returned %ld\n", (unsigned long)avail);
	pr_debug("avail returned %lu\n", (unsigned long)avail);
	/* calculate how much we can write to buffer */
	if (avail > count)
		avail = count;
@@ -374,7 +387,7 @@ static ssize_t snd_compr_read(struct file *f, char __user *buf,
	}

	avail = snd_compr_get_avail(stream);
	pr_debug("avail returned %ld\n", (unsigned long)avail);
	pr_debug("avail returned %lu\n", (unsigned long)avail);
	/* calculate how much we can read from buffer */
	if (avail > count)
		avail = count;
@@ -443,7 +456,7 @@ static __poll_t snd_compr_poll(struct file *f, poll_table *wait)
#endif

	avail = snd_compr_get_avail(stream);
	pr_debug("avail is %ld\n", (unsigned long)avail);
	pr_debug("avail is %lu\n", (unsigned long)avail);
	/* check if we have at least one fragment to fill */
	switch (runtime->state) {
	case SNDRV_PCM_STATE_DRAINING:
@@ -726,13 +739,18 @@ snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg)
static inline int
snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg)
{
	struct snd_compr_tstamp tstamp = {0};
	struct snd_compr_tstamp64 tstamp64 = { 0 };
	struct snd_compr_tstamp tstamp32 = { 0 };
	int ret;

	ret = snd_compr_update_tstamp(stream, &tstamp);
	if (ret == 0)
	ret = snd_compr_update_tstamp(stream, &tstamp64);
	if (ret == 0) {
		snd_compr_tstamp32_from_64(&tstamp32, &tstamp64);
		ret = copy_to_user((struct snd_compr_tstamp __user *)arg,
			&tstamp, sizeof(tstamp)) ? -EFAULT : 0;
				   &tstamp32, sizeof(tstamp32)) ?
			      -EFAULT :
			      0;
	}
	return ret;
}

Loading