diff mbox series

[2/3] ALSA: firewire-lib: replay sequence of incoming packets for outgoing packets

Message ID 20210527122611.173711-3-o-takashi@sakamocchi.jp (mailing list archive)
State New, archived
Headers show
Series ALSA: firewire-lib: preparation for media clock recovery | expand

Commit Message

Takashi Sakamoto May 27, 2021, 12:26 p.m. UTC
ALSA IEC 61883-1/6 packet streaming engine uses pre-computed parameters
ideal for nominal sampling transfer frequency (STF) to transfer packets
to device since it was added 2011. As a result of user experience for a
decade, it is clear that the sequence is not suitable to some actual
devices. It takes the devices to generate noise, and causes any type of
discontinuity in the series of packet transferred from the device. It's
required for the engine to transfer packets according to effective STF.

The effective STF is given by media clock recovered by the sequence of
packet transferred from the target device. In the previous commit, the
sequence is already cached. The media clock recovery can be achieved by
analyzing the sequence.

In technological world, many ideas are proposed for media clock recovery.
However, the small part of them could be actually adopted in our case
since floating point arithmetic is not mostly available in Linux kernel
land.

This commit adopts the simple way from them; sequence replay, which means
that the sequence of parameters from incoming packet is used as is to
transfer outgoing packets. The media clock is not computed internally,
but the sequence of outgoing packet superficially looks to be generated by
the media clock.

The association between source and destination is decided when starting
AMDTP domain. When the target device supports a pair of isochronous packet
streams, the tx stream is source and the rx stream is destination. When it
supports two pair of streams, each of tx stream is associated to
corresponding rx stream in its order. When it supports less number of tx
streams than rx streams, the fist tx stream is selected for all of rx
streams. When it supports more tx streams than rx streams, the first tx
packet is associated to the rx stream.

As I noted in previous commit, the sequence of parameters from incoming
packet is different between devices, time to time. It is worse idea to
replay the sequence of parameters from a device for the sequence of
packet to the other devices even if they are in the same category of
device.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/amdtp-stream.c | 153 ++++++++++++++++++++++++++++++----
 sound/firewire/amdtp-stream.h |   3 +
 2 files changed, 142 insertions(+), 14 deletions(-)
diff mbox series

Patch

diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c
index 860942ffb1f1..47ea03370858 100644
--- a/sound/firewire/amdtp-stream.c
+++ b/sound/firewire/amdtp-stream.c
@@ -495,6 +495,22 @@  static unsigned int compute_syt_offset(unsigned int syt, unsigned int cycle,
 	return syt_offset - transfer_delay;
 }
 
+// Both of the producer and consumer of the queue runs in the same clock of IEEE 1394 bus.
+// Additionally, the sequence of tx packets is severely checked against any discontinuity
+// before filling entries in the queue. The calculation is safe even if it looks fragile by
+// overrun.
+static unsigned int calculate_cached_cycle_count(struct amdtp_stream *s, unsigned int head)
+{
+	const unsigned int cache_size = s->ctx_data.tx.cache.size;
+	unsigned int cycles = s->ctx_data.tx.cache.tail;
+
+	if (cycles < head)
+		cycles += cache_size;
+	cycles -= head;
+
+	return cycles;
+}
+
 static void cache_seq(struct amdtp_stream *s, const struct pkt_desc *descs, unsigned int desc_count)
 {
 	const unsigned int transfer_delay = s->transfer_delay;
@@ -536,6 +552,37 @@  static void pool_ideal_seq_descs(struct amdtp_stream *s, unsigned int count)
 	s->ctx_data.rx.seq.tail = (seq_tail + count) % seq_size;
 }
 
+static void pool_replayed_seq(struct amdtp_stream *s, unsigned int count)
+{
+	struct amdtp_stream *target = s->ctx_data.rx.replay_target;
+	const struct seq_desc *cache = target->ctx_data.tx.cache.descs;
+	const unsigned int cache_size = target->ctx_data.tx.cache.size;
+	unsigned int cache_head = s->ctx_data.rx.cache_head;
+	struct seq_desc *descs = s->ctx_data.rx.seq.descs;
+	const unsigned int seq_size = s->ctx_data.rx.seq.size;
+	unsigned int seq_tail = s->ctx_data.rx.seq.tail;
+	int i;
+
+	for (i = 0; i < count; ++i) {
+		descs[seq_tail] = cache[cache_head];
+		seq_tail = (seq_tail + 1) % seq_size;
+		cache_head = (cache_head + 1) % cache_size;
+	}
+
+	s->ctx_data.rx.seq.tail = seq_tail;
+	s->ctx_data.rx.cache_head = cache_head;
+}
+
+static void pool_seq_descs(struct amdtp_stream *s, unsigned int count)
+{
+	struct amdtp_domain *d = s->domain;
+
+	if (!d->replay.enable || !s->ctx_data.rx.replay_target)
+		pool_ideal_seq_descs(s, count);
+	else
+		pool_replayed_seq(s, count);
+}
+
 static void update_pcm_pointers(struct amdtp_stream *s,
 				struct snd_pcm_substream *pcm,
 				unsigned int frames)
@@ -1004,7 +1051,7 @@  static void process_rx_packets(struct fw_iso_context *context, u32 tstamp, size_
 	// Calculate the number of packets in buffer and check XRUN.
 	packets = header_length / sizeof(*ctx_header);
 
-	pool_ideal_seq_descs(s, packets);
+	pool_seq_descs(s, packets);
 
 	generate_pkt_descs(s, ctx_header, packets);
 
@@ -1392,28 +1439,54 @@  static void irq_target_callback_skip(struct fw_iso_context *context, u32 tstamp,
 {
 	struct amdtp_stream *s = private_data;
 	struct amdtp_domain *d = s->domain;
-	unsigned int cycle;
+	bool ready_to_start;
 
 	skip_rx_packets(context, tstamp, header_length, header, private_data);
 	process_ctxs_in_domain(d);
 
+	if (d->replay.enable) {
+		unsigned int rx_count = 0;
+		unsigned int rx_ready_count = 0;
+		struct amdtp_stream *rx;
+
+		list_for_each_entry(rx, &d->streams, list) {
+			struct amdtp_stream *tx;
+			unsigned int cached_cycles;
+
+			if (rx->direction != AMDTP_OUT_STREAM)
+				continue;
+			++rx_count;
+
+			tx = rx->ctx_data.rx.replay_target;
+			cached_cycles = calculate_cached_cycle_count(tx, 0);
+			if (cached_cycles > tx->ctx_data.tx.cache.size / 2)
+				++rx_ready_count;
+		}
+
+		ready_to_start = (rx_count == rx_ready_count);
+	} else {
+		ready_to_start = true;
+	}
+
 	// Decide the cycle count to begin processing content of packet in IT contexts. All of IT
 	// contexts are expected to start and get callback when reaching here.
-	cycle = s->next_cycle;
-	list_for_each_entry(s, &d->streams, list) {
-		if (s->direction != AMDTP_OUT_STREAM)
-			continue;
+	if (ready_to_start) {
+		unsigned int cycle = s->next_cycle;
+		list_for_each_entry(s, &d->streams, list) {
+			if (s->direction != AMDTP_OUT_STREAM)
+				continue;
 
-		if (compare_ohci_cycle_count(s->next_cycle, cycle) > 0)
-			cycle = s->next_cycle;
+			if (compare_ohci_cycle_count(s->next_cycle, cycle) > 0)
+				cycle = s->next_cycle;
 
-		if (s == d->irq_target)
-			s->context->callback.sc = irq_target_callback_intermediately;
-		else
-			s->context->callback.sc = process_rx_packets_intermediately;
-	}
+			if (s == d->irq_target)
+				s->context->callback.sc = irq_target_callback_intermediately;
+			else
+				s->context->callback.sc = process_rx_packets_intermediately;
+		}
 
-	d->processing_cycle.rx_start = cycle;
+		d->processing_cycle.rx_start = cycle;
+	}
 }
 
 // This is executed one time. For in-stream, first packet has come. For out-stream, prepared to
@@ -1802,6 +1875,53 @@  int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s,
 }
 EXPORT_SYMBOL_GPL(amdtp_domain_add_stream);
 
+// Make the reference from rx stream to tx stream for sequence replay. When the number of tx streams
+// is less than the number of rx streams, the first tx stream is selected.
+static int make_association(struct amdtp_domain *d)
+{
+	unsigned int dst_index = 0;
+	struct amdtp_stream *rx;
+
+	// Make association to replay target.
+	list_for_each_entry(rx, &d->streams, list) {
+		if (rx->direction == AMDTP_OUT_STREAM) {
+			unsigned int src_index = 0;
+			struct amdtp_stream *tx = NULL;
+			struct amdtp_stream *s;
+
+			list_for_each_entry(s, &d->streams, list) {
+				if (s->direction == AMDTP_IN_STREAM) {
+					if (dst_index == src_index) {
+						tx = s;
+						break;
+					}
+
+					++src_index;
+				}
+			}
+			if (!tx) {
+				// Select the first entry.
+				list_for_each_entry(s, &d->streams, list) {
+					if (s->direction == AMDTP_IN_STREAM) {
+						tx = s;
+						break;
+					}
+				}
+				// No target is available to replay sequence.
+				if (!tx)
+					return -EINVAL;
+			}
+
+			rx->ctx_data.rx.replay_target = tx;
+			rx->ctx_data.rx.cache_head = 0;
+
+			++dst_index;
+		}
+	}
+
+	return 0;
+}
+
 /**
  * amdtp_domain_start - start sending packets for isoc context in the domain.
  * @d: the AMDTP domain.
@@ -1818,6 +1938,11 @@  int amdtp_domain_start(struct amdtp_domain *d, unsigned int tx_init_skip_cycles,
 	struct amdtp_stream *s;
 	int err;
 
+	if (replay_seq) {
+		err = make_association(d);
+		if (err < 0)
+			return err;
+	}
 	d->replay.enable = replay_seq;
 
 	// Select an IT context as IRQ target.
diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h
index ddfb885b6113..61b6b5ae8b3b 100644
--- a/sound/firewire/amdtp-stream.h
+++ b/sound/firewire/amdtp-stream.h
@@ -166,6 +166,9 @@  struct amdtp_stream {
 			unsigned int data_block_state;
 			unsigned int syt_offset_state;
 			unsigned int last_syt_offset;
+
+			struct amdtp_stream *replay_target;
+			unsigned int cache_head;
 		} rx;
 	} ctx_data;