ALSA: firewire-lib: handle several AMDTP streams in callback handler of IRQ target

This commit changes AMDTP domain to run on an IT context of 1394 OHCI as
IRQ target. No hardware interrupt is scheduled for the other isoc
contexts. All of the isoc context are processed in a callback for an isoc
context of IRQ target.

The IRQ target is automatically selected from a list of AMDTP streams,
thus users of AMDTP domain should add an AMDTP stream for IT context
at least.

The reason to select IT context as IRQ target is that the IT context
runs on local 1394 OHCI controller and it can be used as reliable,
constant IRQ generator. On the other hand, IR context can include skip
cycle according to isoc packet transferred by device.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Link: https://lore.kernel.org/r/20191018061911.24909-6-o-takashi@sakamocchi.jp
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Sakamoto 2019-10-18 15:19:10 +09:00 committed by Takashi Iwai
parent 813dfbd684
commit 60dd49298e
2 changed files with 141 additions and 47 deletions

View File

@ -482,13 +482,13 @@ static inline int queue_out_packet(struct amdtp_stream *s,
}
static inline int queue_in_packet(struct amdtp_stream *s,
struct fw_iso_packet *params, bool sched_irq)
struct fw_iso_packet *params)
{
// Queue one packet for IR context.
params->header_length = s->ctx_data.tx.ctx_header_size;
params->payload_length = s->ctx_data.tx.max_ctx_payload_length;
params->skip = false;
return queue_packet(s, params, sched_irq);
return queue_packet(s, params, false);
}
static void generate_cip_header(struct amdtp_stream *s, __be32 cip_header[2],
@ -790,15 +790,24 @@ static void process_ctx_payloads(struct amdtp_stream *s,
update_pcm_pointers(s, pcm, pcm_frames);
}
static void amdtp_stream_master_callback(struct fw_iso_context *context,
u32 tstamp, size_t header_length,
void *header, void *private_data);
static void amdtp_stream_master_first_callback(struct fw_iso_context *context,
u32 tstamp, size_t header_length,
void *header, void *private_data);
static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
size_t header_length, void *header,
void *private_data)
{
struct amdtp_stream *s = private_data;
const __be32 *ctx_header = header;
unsigned int events_per_period = s->events_per_period;
unsigned int event_count = s->event_count;
unsigned int events_per_period = s->ctx_data.rx.events_per_period;
unsigned int event_count = s->ctx_data.rx.event_count;
unsigned int packets;
bool is_irq_target;
int i;
if (s->packet_index < 0)
@ -811,6 +820,10 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
process_ctx_payloads(s, s->pkt_descs, packets);
is_irq_target =
!!(context->callback.sc == amdtp_stream_master_callback ||
context->callback.sc == amdtp_stream_master_first_callback);
for (i = 0; i < packets; ++i) {
const struct pkt_desc *desc = s->pkt_descs + i;
unsigned int syt;
@ -829,10 +842,12 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
desc->data_blocks, desc->data_block_counter,
syt, i);
event_count += desc->data_blocks;
if (event_count >= events_per_period) {
event_count -= events_per_period;
sched_irq = true;
if (is_irq_target) {
event_count += desc->data_blocks;
if (event_count >= events_per_period) {
event_count -= events_per_period;
sched_irq = true;
}
}
if (queue_out_packet(s, &template.params, sched_irq) < 0) {
@ -841,7 +856,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
}
}
s->event_count = event_count;
s->ctx_data.rx.event_count = event_count;
}
static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
@ -850,8 +865,6 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
{
struct amdtp_stream *s = private_data;
__be32 *ctx_header = header;
unsigned int events_per_period = s->events_per_period;
unsigned int event_count = s->event_count;
unsigned int packets;
int i;
int err;
@ -873,31 +886,47 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
}
for (i = 0; i < packets; ++i) {
const struct pkt_desc *desc = s->pkt_descs + i;
struct fw_iso_packet params = {0};
bool sched_irq = false;
if (err >= 0) {
event_count += desc->data_blocks;
if (event_count >= events_per_period) {
event_count -= events_per_period;
sched_irq = true;
}
} else {
sched_irq =
!((s->packet_index + 1) % s->idle_irq_interval);
}
if (queue_in_packet(s, &params, sched_irq) < 0) {
if (queue_in_packet(s, &params) < 0) {
cancel_stream(s);
return;
}
}
s->event_count = event_count;
}
/* this is executed one time */
static void amdtp_stream_master_callback(struct fw_iso_context *context,
u32 tstamp, size_t header_length,
void *header, void *private_data)
{
struct amdtp_domain *d = private_data;
struct amdtp_stream *irq_target = d->irq_target;
struct amdtp_stream *s;
out_stream_callback(context, tstamp, header_length, header, irq_target);
if (amdtp_streaming_error(irq_target))
goto error;
list_for_each_entry(s, &d->streams, list) {
if (s != irq_target && amdtp_stream_running(s)) {
fw_iso_context_flush_completions(s->context);
if (amdtp_streaming_error(s))
goto error;
}
}
return;
error:
if (amdtp_stream_running(irq_target))
cancel_stream(irq_target);
list_for_each_entry(s, &d->streams, list) {
if (amdtp_stream_running(s))
cancel_stream(s);
}
}
// this is executed one time.
static void amdtp_stream_first_callback(struct fw_iso_context *context,
u32 tstamp, size_t header_length,
void *header, void *private_data)
@ -928,18 +957,39 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
context->callback.sc(context, tstamp, header_length, header, s);
}
static void amdtp_stream_master_first_callback(struct fw_iso_context *context,
u32 tstamp, size_t header_length,
void *header, void *private_data)
{
struct amdtp_domain *d = private_data;
struct amdtp_stream *s = d->irq_target;
const __be32 *ctx_header = header;
s->callbacked = true;
wake_up(&s->callback_wait);
s->start_cycle = compute_it_cycle(*ctx_header, s->queue_size);
context->callback.sc = amdtp_stream_master_callback;
context->callback.sc(context, tstamp, header_length, header, d);
}
/**
* amdtp_stream_start - start transferring packets
* @s: the AMDTP stream to start
* @channel: the isochronous channel on the bus
* @speed: firewire speed code
* @d: the AMDTP domain to which the AMDTP stream belongs
* @is_irq_target: whether isoc context for the AMDTP stream is used to generate
* hardware IRQ.
*
* The stream cannot be started until it has been configured with
* amdtp_stream_set_parameters() and it must be started before any PCM or MIDI
* device can be started.
*/
static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
struct amdtp_domain *d)
struct amdtp_domain *d, bool is_irq_target)
{
static const struct {
unsigned int data_block;
@ -955,10 +1005,13 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
};
unsigned int events_per_buffer = d->events_per_buffer;
unsigned int events_per_period = d->events_per_period;
unsigned int idle_irq_interval;
unsigned int ctx_header_size;
unsigned int max_ctx_payload_size;
enum dma_data_direction dir;
int type, tag, err;
fw_iso_callback_t ctx_cb;
void *ctx_data;
mutex_lock(&s->mutex);
@ -969,6 +1022,12 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
}
if (s->direction == AMDTP_IN_STREAM) {
// NOTE: IT context should be used for constant IRQ.
if (is_irq_target) {
err = -EINVAL;
goto err_unlock;
}
s->data_block_counter = UINT_MAX;
} else {
entry = &initial_state[s->sfc];
@ -1008,22 +1067,29 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
if (events_per_buffer == 0)
events_per_buffer = events_per_period * 3;
s->idle_irq_interval =
DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_period,
amdtp_rate_table[s->sfc]);
idle_irq_interval = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_period,
amdtp_rate_table[s->sfc]);
s->queue_size = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_buffer,
amdtp_rate_table[s->sfc]);
s->events_per_period = events_per_period;
s->event_count = 0;
err = iso_packets_buffer_init(&s->buffer, s->unit, s->queue_size,
max_ctx_payload_size, dir);
if (err < 0)
goto err_unlock;
if (is_irq_target) {
s->ctx_data.rx.events_per_period = events_per_period;
s->ctx_data.rx.event_count = 0;
ctx_cb = amdtp_stream_master_first_callback;
ctx_data = d;
} else {
ctx_cb = amdtp_stream_first_callback;
ctx_data = s;
}
s->context = fw_iso_context_create(fw_parent_device(s->unit)->card,
type, channel, speed, ctx_header_size,
amdtp_stream_first_callback, s);
ctx_cb, ctx_data);
if (IS_ERR(s->context)) {
err = PTR_ERR(s->context);
if (err == -EBUSY)
@ -1054,14 +1120,20 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
s->packet_index = 0;
do {
struct fw_iso_packet params;
bool sched_irq;
sched_irq = !((s->packet_index + 1) % s->idle_irq_interval);
if (s->direction == AMDTP_IN_STREAM) {
err = queue_in_packet(s, &params, sched_irq);
err = queue_in_packet(s, &params);
} else {
bool sched_irq = false;
params.header_length = 0;
params.payload_length = 0;
if (is_irq_target) {
sched_irq = !((s->packet_index + 1) %
idle_irq_interval);
}
err = queue_out_packet(s, &params, sched_irq);
}
if (err < 0)
@ -1276,17 +1348,33 @@ int amdtp_domain_start(struct amdtp_domain *d)
struct amdtp_stream *s;
int err = 0;
// Select an IT context as IRQ target.
list_for_each_entry(s, &d->streams, list) {
err = amdtp_stream_start(s, s->channel, s->speed, d);
if (err < 0)
if (s->direction == AMDTP_OUT_STREAM)
break;
}
if (!s)
return -ENXIO;
d->irq_target = s;
if (err < 0) {
list_for_each_entry(s, &d->streams, list)
amdtp_stream_stop(s);
list_for_each_entry(s, &d->streams, list) {
if (s != d->irq_target) {
err = amdtp_stream_start(s, s->channel, s->speed, d,
false);
if (err < 0)
goto error;
}
}
s = d->irq_target;
err = amdtp_stream_start(s, s->channel, s->speed, d, true);
if (err < 0)
goto error;
return 0;
error:
list_for_each_entry(s, &d->streams, list)
amdtp_stream_stop(s);
return err;
}
EXPORT_SYMBOL_GPL(amdtp_domain_start);
@ -1299,12 +1387,17 @@ void amdtp_domain_stop(struct amdtp_domain *d)
{
struct amdtp_stream *s, *next;
if (d->irq_target)
amdtp_stream_stop(d->irq_target);
list_for_each_entry_safe(s, next, &d->streams, list) {
list_del(&s->list);
amdtp_stream_stop(s);
if (s != d->irq_target)
amdtp_stream_stop(s);
}
d->events_per_period = 0;
d->irq_target = NULL;
}
EXPORT_SYMBOL_GPL(amdtp_domain_stop);

View File

@ -143,11 +143,12 @@ struct amdtp_stream {
// To generate CIP header.
unsigned int fdf;
int syt_override;
// To generate constant hardware IRQ.
unsigned int event_count;
unsigned int events_per_period;
} rx;
} ctx_data;
unsigned int event_count;
unsigned int events_per_period;
unsigned int idle_irq_interval;
/* For CIP headers. */
unsigned int source_node_id_field;