Skip to content

Commit

Permalink
firewire: add isochronous multichannel reception
Browse files Browse the repository at this point in the history
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: Stefan Richter <stefanr@s5r6.in-berlin.de>
  • Loading branch information
Stefan Richter committed Jul 29, 2010
1 parent ae2a976 commit 872e330
Show file tree
Hide file tree
Showing 6 changed files with 560 additions and 193 deletions.
93 changes: 72 additions & 21 deletions drivers/firewire/core-cdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ struct iso_interrupt_event {
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 event event;
struct fw_cdev_event_iso_resource iso_resource;
Expand Down Expand Up @@ -415,6 +420,7 @@ union ioctl_arg {
struct fw_cdev_get_cycle_timer2 get_cycle_timer2;
struct fw_cdev_send_phy_packet send_phy_packet;
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)
Expand Down Expand Up @@ -932,35 +938,62 @@ static void iso_callback(struct fw_iso_context *context, u32 cycle,
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)
{
struct fw_cdev_create_iso_context *a = &arg->create_iso_context;
struct fw_iso_context *context;
fw_iso_callback_t cb;

BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT ||
FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE);

if (a->channel > 63)
return -EINVAL;
FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE ||
FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL !=
FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL);

switch (a->type) {
case FW_ISO_CONTEXT_RECEIVE:
if (a->header_size < 4 || (a->header_size & 3))
case FW_ISO_CONTEXT_TRANSMIT:
if (a->speed > SCODE_3200 || a->channel > 63)
return -EINVAL;

cb = iso_callback;
break;

case FW_ISO_CONTEXT_TRANSMIT:
if (a->speed > SCODE_3200)
case FW_ISO_CONTEXT_RECEIVE:
if (a->header_size < 4 || (a->header_size & 3) ||
a->channel > 63)
return -EINVAL;

cb = iso_callback;
break;

case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
cb = (fw_iso_callback_t)iso_mc_callback;
break;

default:
return -EINVAL;
}

context = fw_iso_context_create(client->device->card, a->type,
a->channel, a->speed, a->header_size,
iso_callback, client);
a->channel, a->speed, a->header_size, cb, client);
if (IS_ERR(context))
return PTR_ERR(context);

Expand All @@ -980,6 +1013,17 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
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. */
#define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff)
#define GET_INTERRUPT(v) (((v) >> 16) & 0x01)
Expand All @@ -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_iso_packet __user *p, *end, *next;
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;
int count;
struct {
Expand All @@ -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
* and the a->data pointer is ignored.
*/

payload = (unsigned long)a->data - client->vm_start;
buffer_end = client->buffer.page_count << PAGE_SHIFT;
if (a->data == 0 || client->buffer.pages == NULL ||
Expand All @@ -1022,8 +1065,10 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
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))
return -EFAULT;

Expand All @@ -1039,19 +1084,24 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
u.packet.sy = GET_SY(control);
u.packet.header_length = GET_HEADER_LENGTH(control);

if (ctx->type == FW_ISO_CONTEXT_TRANSMIT) {
if (u.packet.header_length % 4 != 0)
switch (ctx->type) {
case FW_ISO_CONTEXT_TRANSMIT:
if (u.packet.header_length & 3)
return -EINVAL;
transmit_header_bytes = u.packet.header_length;
} else {
/*
* We require that header_length is a multiple of
* the fixed header size, ctx->header_size.
*/
break;

case FW_ISO_CONTEXT_RECEIVE:
if (u.packet.header_length == 0 ||
u.packet.header_length % ctx->header_size != 0)
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 *)
Expand Down Expand Up @@ -1534,6 +1584,7 @@ static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
[0x14] = ioctl_get_cycle_timer2,
[0x15] = ioctl_send_phy_packet,
[0x16] = ioctl_receive_phy_packets,
[0x17] = ioctl_set_iso_channels,
};

static int dispatch_ioctl(struct client *client,
Expand Down
32 changes: 25 additions & 7 deletions drivers/firewire/core-iso.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,23 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer,
}
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,
int type, int channel, int speed, size_t header_size,
fw_iso_callback_t callback, void *callback_data)
Expand All @@ -133,7 +150,7 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card,
ctx->channel = channel;
ctx->speed = speed;
ctx->header_size = header_size;
ctx->callback = callback;
ctx->callback.sc = callback;
ctx->callback_data = callback_data;

return ctx;
Expand All @@ -142,9 +159,7 @@ EXPORT_SYMBOL(fw_iso_context_create);

void fw_iso_context_destroy(struct fw_iso_context *ctx)
{
struct fw_card *card = ctx->card;

card->driver->free_iso_context(ctx);
ctx->card->driver->free_iso_context(ctx);
}
EXPORT_SYMBOL(fw_iso_context_destroy);

Expand All @@ -155,14 +170,17 @@ int fw_iso_context_start(struct fw_iso_context *ctx,
}
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,
struct fw_iso_packet *packet,
struct fw_iso_buffer *buffer,
unsigned long payload)
{
struct fw_card *card = ctx->card;

return card->driver->queue_iso(ctx, packet, buffer, payload);
return ctx->card->driver->queue_iso(ctx, packet, buffer, payload);
}
EXPORT_SYMBOL(fw_iso_context_queue);

Expand Down
2 changes: 2 additions & 0 deletions drivers/firewire/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ struct fw_card_driver {
int (*start_iso)(struct fw_iso_context *ctx,
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,
struct fw_iso_packet *packet,
struct fw_iso_buffer *buffer,
Expand Down
Loading

0 comments on commit 872e330

Please sign in to comment.