Commit 872e330e authored by Stefan Richter's avatar Stefan Richter

firewire: add isochronous multichannel reception

This adds the DMA context programming and userspace ABI for multichannel
reception, i.e. for listening on multiple channel numbers by means of a
single DMA context.

The use case is reception of more streams than there are IR DMA units
offered by the link layer.  This is already implemented by the older
ohci1394 + ieee1394 + raw1394 stack.  And as discussed recently on
linux1394-devel, this feature is occasionally used in practice.

The big drawbacks of this mode are that buffer layout and interrupt
generation necessarily differ from single-channel reception:  Headers
and trailers are not stripped from packets, packets are not aligned with
buffer chunks, interrupts are per buffer chunk, not per packet.

These drawbacks also cause a rather hefty code footprint to support this
rarely used OHCI-1394 feature.  (367 lines added, among them 94 lines of
added userspace ABI documentation.)

This implementation enforces that a multichannel reception context may
only listen to channels to which no single-channel context on the same
link layer is presently listening to.  OHCI-1394 would allow to overlay
single-channel contexts by the multi-channel context, but this would be
a departure from the present first-come-first-served policy of IR
context creation.

The implementation is heavily based on an earlier one by Jay Fenlason.
Thanks Jay.
Signed-off-by: default avatarStefan Richter <stefanr@s5r6.in-berlin.de>
parent ae2a9766
...@@ -193,6 +193,11 @@ struct iso_interrupt_event { ...@@ -193,6 +193,11 @@ struct iso_interrupt_event {
struct fw_cdev_event_iso_interrupt interrupt; struct fw_cdev_event_iso_interrupt interrupt;
}; };
struct iso_interrupt_mc_event {
struct event event;
struct fw_cdev_event_iso_interrupt_mc interrupt;
};
struct iso_resource_event { struct iso_resource_event {
struct event event; struct event event;
struct fw_cdev_event_iso_resource iso_resource; struct fw_cdev_event_iso_resource iso_resource;
...@@ -415,6 +420,7 @@ union ioctl_arg { ...@@ -415,6 +420,7 @@ union ioctl_arg {
struct fw_cdev_get_cycle_timer2 get_cycle_timer2; struct fw_cdev_get_cycle_timer2 get_cycle_timer2;
struct fw_cdev_send_phy_packet send_phy_packet; struct fw_cdev_send_phy_packet send_phy_packet;
struct fw_cdev_receive_phy_packets receive_phy_packets; struct fw_cdev_receive_phy_packets receive_phy_packets;
struct fw_cdev_set_iso_channels set_iso_channels;
}; };
static int ioctl_get_info(struct client *client, union ioctl_arg *arg) static int ioctl_get_info(struct client *client, union ioctl_arg *arg)
...@@ -932,26 +938,54 @@ static void iso_callback(struct fw_iso_context *context, u32 cycle, ...@@ -932,26 +938,54 @@ static void iso_callback(struct fw_iso_context *context, u32 cycle,
sizeof(e->interrupt) + header_length, NULL, 0); sizeof(e->interrupt) + header_length, NULL, 0);
} }
static void iso_mc_callback(struct fw_iso_context *context,
dma_addr_t completed, void *data)
{
struct client *client = data;
struct iso_interrupt_mc_event *e;
e = kmalloc(sizeof(*e), GFP_ATOMIC);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
return;
}
e->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL;
e->interrupt.closure = client->iso_closure;
e->interrupt.completed = fw_iso_buffer_lookup(&client->buffer,
completed);
queue_event(client, &e->event, &e->interrupt,
sizeof(e->interrupt), NULL, 0);
}
static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
{ {
struct fw_cdev_create_iso_context *a = &arg->create_iso_context; struct fw_cdev_create_iso_context *a = &arg->create_iso_context;
struct fw_iso_context *context; struct fw_iso_context *context;
fw_iso_callback_t cb;
BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT || BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT ||
FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE); FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE ||
FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL !=
if (a->channel > 63) FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL);
return -EINVAL;
switch (a->type) { switch (a->type) {
case FW_ISO_CONTEXT_RECEIVE: case FW_ISO_CONTEXT_TRANSMIT:
if (a->header_size < 4 || (a->header_size & 3)) if (a->speed > SCODE_3200 || a->channel > 63)
return -EINVAL; return -EINVAL;
cb = iso_callback;
break; break;
case FW_ISO_CONTEXT_TRANSMIT: case FW_ISO_CONTEXT_RECEIVE:
if (a->speed > SCODE_3200) if (a->header_size < 4 || (a->header_size & 3) ||
a->channel > 63)
return -EINVAL; return -EINVAL;
cb = iso_callback;
break;
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
cb = (fw_iso_callback_t)iso_mc_callback;
break; break;
default: default:
...@@ -959,8 +993,7 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) ...@@ -959,8 +993,7 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
} }
context = fw_iso_context_create(client->device->card, a->type, context = fw_iso_context_create(client->device->card, a->type,
a->channel, a->speed, a->header_size, a->channel, a->speed, a->header_size, cb, client);
iso_callback, client);
if (IS_ERR(context)) if (IS_ERR(context))
return PTR_ERR(context); return PTR_ERR(context);
...@@ -980,6 +1013,17 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg) ...@@ -980,6 +1013,17 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
return 0; return 0;
} }
static int ioctl_set_iso_channels(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_set_iso_channels *a = &arg->set_iso_channels;
struct fw_iso_context *ctx = client->iso_context;
if (ctx == NULL || a->handle != 0)
return -EINVAL;
return fw_iso_context_set_channels(ctx, &a->channels);
}
/* Macros for decoding the iso packet control header. */ /* Macros for decoding the iso packet control header. */
#define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff) #define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff)
#define GET_INTERRUPT(v) (((v) >> 16) & 0x01) #define GET_INTERRUPT(v) (((v) >> 16) & 0x01)
...@@ -993,7 +1037,7 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) ...@@ -993,7 +1037,7 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
struct fw_cdev_queue_iso *a = &arg->queue_iso; struct fw_cdev_queue_iso *a = &arg->queue_iso;
struct fw_cdev_iso_packet __user *p, *end, *next; struct fw_cdev_iso_packet __user *p, *end, *next;
struct fw_iso_context *ctx = client->iso_context; struct fw_iso_context *ctx = client->iso_context;
unsigned long payload, buffer_end, transmit_header_bytes; unsigned long payload, buffer_end, transmit_header_bytes = 0;
u32 control; u32 control;
int count; int count;
struct { struct {
...@@ -1013,7 +1057,6 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) ...@@ -1013,7 +1057,6 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
* use the indirect payload, the iso buffer need not be mapped * use the indirect payload, the iso buffer need not be mapped
* and the a->data pointer is ignored. * and the a->data pointer is ignored.
*/ */
payload = (unsigned long)a->data - client->vm_start; payload = (unsigned long)a->data - client->vm_start;
buffer_end = client->buffer.page_count << PAGE_SHIFT; buffer_end = client->buffer.page_count << PAGE_SHIFT;
if (a->data == 0 || client->buffer.pages == NULL || if (a->data == 0 || client->buffer.pages == NULL ||
...@@ -1022,8 +1065,10 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) ...@@ -1022,8 +1065,10 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
buffer_end = 0; buffer_end = 0;
} }
p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets); if (ctx->type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL && payload & 3)
return -EINVAL;
p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets);
if (!access_ok(VERIFY_READ, p, a->size)) if (!access_ok(VERIFY_READ, p, a->size))
return -EFAULT; return -EFAULT;
...@@ -1039,19 +1084,24 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg) ...@@ -1039,19 +1084,24 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
u.packet.sy = GET_SY(control); u.packet.sy = GET_SY(control);
u.packet.header_length = GET_HEADER_LENGTH(control); u.packet.header_length = GET_HEADER_LENGTH(control);
if (ctx->type == FW_ISO_CONTEXT_TRANSMIT) { switch (ctx->type) {
if (u.packet.header_length % 4 != 0) case FW_ISO_CONTEXT_TRANSMIT:
if (u.packet.header_length & 3)
return -EINVAL; return -EINVAL;
transmit_header_bytes = u.packet.header_length; transmit_header_bytes = u.packet.header_length;
} else { break;
/*
* We require that header_length is a multiple of case FW_ISO_CONTEXT_RECEIVE:
* the fixed header size, ctx->header_size.
*/
if (u.packet.header_length == 0 || if (u.packet.header_length == 0 ||
u.packet.header_length % ctx->header_size != 0) u.packet.header_length % ctx->header_size != 0)
return -EINVAL; return -EINVAL;
transmit_header_bytes = 0; break;
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
if (u.packet.payload_length == 0 ||
u.packet.payload_length & 3)
return -EINVAL;
break;
} }
next = (struct fw_cdev_iso_packet __user *) next = (struct fw_cdev_iso_packet __user *)
...@@ -1534,6 +1584,7 @@ static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = { ...@@ -1534,6 +1584,7 @@ static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
[0x14] = ioctl_get_cycle_timer2, [0x14] = ioctl_get_cycle_timer2,
[0x15] = ioctl_send_phy_packet, [0x15] = ioctl_send_phy_packet,
[0x16] = ioctl_receive_phy_packets, [0x16] = ioctl_receive_phy_packets,
[0x17] = ioctl_set_iso_channels,
}; };
static int dispatch_ioctl(struct client *client, static int dispatch_ioctl(struct client *client,
......
...@@ -117,6 +117,23 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, ...@@ -117,6 +117,23 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer,
} }
EXPORT_SYMBOL(fw_iso_buffer_destroy); EXPORT_SYMBOL(fw_iso_buffer_destroy);
/* Convert DMA address to offset into virtually contiguous buffer. */
size_t fw_iso_buffer_lookup(struct fw_iso_buffer *buffer, dma_addr_t completed)
{
int i;
dma_addr_t address;
ssize_t offset;
for (i = 0; i < buffer->page_count; i++) {
address = page_private(buffer->pages[i]);
offset = (ssize_t)completed - (ssize_t)address;
if (offset > 0 && offset <= PAGE_SIZE)
return (i << PAGE_SHIFT) + offset;
}
return 0;
}
struct fw_iso_context *fw_iso_context_create(struct fw_card *card, struct fw_iso_context *fw_iso_context_create(struct fw_card *card,
int type, int channel, int speed, size_t header_size, int type, int channel, int speed, size_t header_size,
fw_iso_callback_t callback, void *callback_data) fw_iso_callback_t callback, void *callback_data)
...@@ -133,7 +150,7 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card, ...@@ -133,7 +150,7 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card,
ctx->channel = channel; ctx->channel = channel;
ctx->speed = speed; ctx->speed = speed;
ctx->header_size = header_size; ctx->header_size = header_size;
ctx->callback = callback; ctx->callback.sc = callback;
ctx->callback_data = callback_data; ctx->callback_data = callback_data;
return ctx; return ctx;
...@@ -142,9 +159,7 @@ EXPORT_SYMBOL(fw_iso_context_create); ...@@ -142,9 +159,7 @@ EXPORT_SYMBOL(fw_iso_context_create);
void fw_iso_context_destroy(struct fw_iso_context *ctx) void fw_iso_context_destroy(struct fw_iso_context *ctx)
{ {
struct fw_card *card = ctx->card; ctx->card->driver->free_iso_context(ctx);
card->driver->free_iso_context(ctx);
} }
EXPORT_SYMBOL(fw_iso_context_destroy); EXPORT_SYMBOL(fw_iso_context_destroy);
...@@ -155,14 +170,17 @@ int fw_iso_context_start(struct fw_iso_context *ctx, ...@@ -155,14 +170,17 @@ int fw_iso_context_start(struct fw_iso_context *ctx,
} }
EXPORT_SYMBOL(fw_iso_context_start); EXPORT_SYMBOL(fw_iso_context_start);
int fw_iso_context_set_channels(struct fw_iso_context *ctx, u64 *channels)
{
return ctx->card->driver->set_iso_channels(ctx, channels);
}
int fw_iso_context_queue(struct fw_iso_context *ctx, int fw_iso_context_queue(struct fw_iso_context *ctx,
struct fw_iso_packet *packet, struct fw_iso_packet *packet,
struct fw_iso_buffer *buffer, struct fw_iso_buffer *buffer,
unsigned long payload) unsigned long payload)
{ {
struct fw_card *card = ctx->card; return ctx->card->driver->queue_iso(ctx, packet, buffer, payload);
return card->driver->queue_iso(ctx, packet, buffer, payload);
} }
EXPORT_SYMBOL(fw_iso_context_queue); EXPORT_SYMBOL(fw_iso_context_queue);
......
...@@ -90,6 +90,8 @@ struct fw_card_driver { ...@@ -90,6 +90,8 @@ struct fw_card_driver {
int (*start_iso)(struct fw_iso_context *ctx, int (*start_iso)(struct fw_iso_context *ctx,
s32 cycle, u32 sync, u32 tags); s32 cycle, u32 sync, u32 tags);
int (*set_iso_channels)(struct fw_iso_context *ctx, u64 *channels);
int (*queue_iso)(struct fw_iso_context *ctx, int (*queue_iso)(struct fw_iso_context *ctx,
struct fw_iso_packet *packet, struct fw_iso_packet *packet,
struct fw_iso_buffer *buffer, struct fw_iso_buffer *buffer,
......
This diff is collapsed.
This diff is collapsed.
...@@ -372,17 +372,19 @@ void fw_core_remove_descriptor(struct fw_descriptor *desc); ...@@ -372,17 +372,19 @@ void fw_core_remove_descriptor(struct fw_descriptor *desc);
* scatter-gather streaming (e.g. assembling video frame automatically). * scatter-gather streaming (e.g. assembling video frame automatically).
*/ */
struct fw_iso_packet { struct fw_iso_packet {
u16 payload_length; /* Length of indirect payload. */ u16 payload_length; /* Length of indirect payload */
u32 interrupt:1; /* Generate interrupt on this packet */ u32 interrupt:1; /* Generate interrupt on this packet */
u32 skip:1; /* Set to not send packet at all. */ u32 skip:1; /* tx: Set to not send packet at all */
u32 tag:2; /* rx: Sync bit, wait for matching sy */
u32 sy:4; u32 tag:2; /* tx: Tag in packet header */
u32 header_length:8; /* Length of immediate header. */ u32 sy:4; /* tx: Sy in packet header */
u32 header[0]; u32 header_length:8; /* Length of immediate header */
u32 header[0]; /* tx: Top of 1394 isoch. data_block */
}; };
#define FW_ISO_CONTEXT_TRANSMIT 0 #define FW_ISO_CONTEXT_TRANSMIT 0
#define FW_ISO_CONTEXT_RECEIVE 1 #define FW_ISO_CONTEXT_RECEIVE 1
#define FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL 2
#define FW_ISO_CONTEXT_MATCH_TAG0 1 #define FW_ISO_CONTEXT_MATCH_TAG0 1
#define FW_ISO_CONTEXT_MATCH_TAG1 2 #define FW_ISO_CONTEXT_MATCH_TAG1 2
...@@ -406,24 +408,31 @@ struct fw_iso_buffer { ...@@ -406,24 +408,31 @@ struct fw_iso_buffer {
int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card, int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card,
int page_count, enum dma_data_direction direction); int page_count, enum dma_data_direction direction);
void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, struct fw_card *card); void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, struct fw_card *card);
size_t fw_iso_buffer_lookup(struct fw_iso_buffer *buffer, dma_addr_t completed);
struct fw_iso_context; struct fw_iso_context;
typedef void (*fw_iso_callback_t)(struct fw_iso_context *context, typedef void (*fw_iso_callback_t)(struct fw_iso_context *context,
u32 cycle, size_t header_length, u32 cycle, size_t header_length,
void *header, void *data); void *header, void *data);
typedef void (*fw_iso_mc_callback_t)(struct fw_iso_context *context,
dma_addr_t completed, void *data);
struct fw_iso_context { struct fw_iso_context {
struct fw_card *card; struct fw_card *card;
int type; int type;
int channel; int channel;
int speed; int speed;
size_t header_size; size_t header_size;
fw_iso_callback_t callback; union {
fw_iso_callback_t sc;
fw_iso_mc_callback_t mc;
} callback;
void *callback_data; void *callback_data;
}; };
struct fw_iso_context *fw_iso_context_create(struct fw_card *card, struct fw_iso_context *fw_iso_context_create(struct fw_card *card,
int type, int channel, int speed, size_t header_size, int type, int channel, int speed, size_t header_size,
fw_iso_callback_t callback, void *callback_data); fw_iso_callback_t callback, void *callback_data);
int fw_iso_context_set_channels(struct fw_iso_context *ctx, u64 *channels);
int fw_iso_context_queue(struct fw_iso_context *ctx, int fw_iso_context_queue(struct fw_iso_context *ctx,
struct fw_iso_packet *packet, struct fw_iso_packet *packet,
struct fw_iso_buffer *buffer, struct fw_iso_buffer *buffer,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment