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: Stefan Richter <stefanr@s5r6.in-berlin.de>
This commit is contained in:
parent
ae2a976614
commit
872e330e38
|
@ -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 {
|
||||||
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,
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
* 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)
|
||||||
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)
|
||||||
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 *) = {
|
||||||
[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,
|
||||||
}
|
}
|
||||||
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,
|
||||||
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);
|
||||||
|
|
||||||
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,
|
||||||
}
|
}
|
||||||
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 {
|
||||||
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,
|
||||||
|
|
|
@ -190,11 +190,13 @@ struct fw_ohci {
|
||||||
struct context at_request_ctx;
|
struct context at_request_ctx;
|
||||||
struct context at_response_ctx;
|
struct context at_response_ctx;
|
||||||
|
|
||||||
u32 it_context_mask;
|
u32 it_context_mask; /* unoccupied IT contexts */
|
||||||
struct iso_context *it_context_list;
|
struct iso_context *it_context_list;
|
||||||
u64 ir_context_channels;
|
u64 ir_context_channels; /* unoccupied channels */
|
||||||
u32 ir_context_mask;
|
u32 ir_context_mask; /* unoccupied IR contexts */
|
||||||
struct iso_context *ir_context_list;
|
struct iso_context *ir_context_list;
|
||||||
|
u64 mc_channels; /* channels in use by the multichannel IR context */
|
||||||
|
bool mc_allocated;
|
||||||
|
|
||||||
__be32 *config_rom;
|
__be32 *config_rom;
|
||||||
dma_addr_t config_rom_bus;
|
dma_addr_t config_rom_bus;
|
||||||
|
@ -2197,10 +2199,9 @@ static int handle_ir_packet_per_buffer(struct context *context,
|
||||||
__le32 *ir_header;
|
__le32 *ir_header;
|
||||||
void *p;
|
void *p;
|
||||||
|
|
||||||
for (pd = d; pd <= last; pd++) {
|
for (pd = d; pd <= last; pd++)
|
||||||
if (pd->transfer_status)
|
if (pd->transfer_status)
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
if (pd > last)
|
if (pd > last)
|
||||||
/* Descriptor(s) not done yet, stop iteration */
|
/* Descriptor(s) not done yet, stop iteration */
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2210,16 +2211,38 @@ static int handle_ir_packet_per_buffer(struct context *context,
|
||||||
|
|
||||||
if (le16_to_cpu(last->control) & DESCRIPTOR_IRQ_ALWAYS) {
|
if (le16_to_cpu(last->control) & DESCRIPTOR_IRQ_ALWAYS) {
|
||||||
ir_header = (__le32 *) p;
|
ir_header = (__le32 *) p;
|
||||||
ctx->base.callback(&ctx->base,
|
ctx->base.callback.sc(&ctx->base,
|
||||||
le32_to_cpu(ir_header[0]) & 0xffff,
|
le32_to_cpu(ir_header[0]) & 0xffff,
|
||||||
ctx->header_length, ctx->header,
|
ctx->header_length, ctx->header,
|
||||||
ctx->base.callback_data);
|
ctx->base.callback_data);
|
||||||
ctx->header_length = 0;
|
ctx->header_length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* d == last because each descriptor block is only a single descriptor. */
|
||||||
|
static int handle_ir_buffer_fill(struct context *context,
|
||||||
|
struct descriptor *d,
|
||||||
|
struct descriptor *last)
|
||||||
|
{
|
||||||
|
struct iso_context *ctx =
|
||||||
|
container_of(context, struct iso_context, context);
|
||||||
|
|
||||||
|
if (!last->transfer_status)
|
||||||
|
/* Descriptor(s) not done yet, stop iteration */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (le16_to_cpu(last->control) & DESCRIPTOR_IRQ_ALWAYS)
|
||||||
|
ctx->base.callback.mc(&ctx->base,
|
||||||
|
le32_to_cpu(last->data_address) +
|
||||||
|
le16_to_cpu(last->req_count) -
|
||||||
|
le16_to_cpu(last->res_count),
|
||||||
|
ctx->base.callback_data);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int handle_it_packet(struct context *context,
|
static int handle_it_packet(struct context *context,
|
||||||
struct descriptor *d,
|
struct descriptor *d,
|
||||||
struct descriptor *last)
|
struct descriptor *last)
|
||||||
|
@ -2245,72 +2268,118 @@ static int handle_it_packet(struct context *context,
|
||||||
ctx->header_length += 4;
|
ctx->header_length += 4;
|
||||||
}
|
}
|
||||||
if (le16_to_cpu(last->control) & DESCRIPTOR_IRQ_ALWAYS) {
|
if (le16_to_cpu(last->control) & DESCRIPTOR_IRQ_ALWAYS) {
|
||||||
ctx->base.callback(&ctx->base, le16_to_cpu(last->res_count),
|
ctx->base.callback.sc(&ctx->base, le16_to_cpu(last->res_count),
|
||||||
ctx->header_length, ctx->header,
|
ctx->header_length, ctx->header,
|
||||||
ctx->base.callback_data);
|
ctx->base.callback_data);
|
||||||
ctx->header_length = 0;
|
ctx->header_length = 0;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void set_multichannel_mask(struct fw_ohci *ohci, u64 channels)
|
||||||
|
{
|
||||||
|
u32 hi = channels >> 32, lo = channels;
|
||||||
|
|
||||||
|
reg_write(ohci, OHCI1394_IRMultiChanMaskHiClear, ~hi);
|
||||||
|
reg_write(ohci, OHCI1394_IRMultiChanMaskLoClear, ~lo);
|
||||||
|
reg_write(ohci, OHCI1394_IRMultiChanMaskHiSet, hi);
|
||||||
|
reg_write(ohci, OHCI1394_IRMultiChanMaskLoSet, lo);
|
||||||
|
mmiowb();
|
||||||
|
ohci->mc_channels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card,
|
static struct fw_iso_context *ohci_allocate_iso_context(struct fw_card *card,
|
||||||
int type, int channel, size_t header_size)
|
int type, int channel, size_t header_size)
|
||||||
{
|
{
|
||||||
struct fw_ohci *ohci = fw_ohci(card);
|
struct fw_ohci *ohci = fw_ohci(card);
|
||||||
struct iso_context *ctx, *list;
|
struct iso_context *uninitialized_var(ctx);
|
||||||
descriptor_callback_t callback;
|
descriptor_callback_t uninitialized_var(callback);
|
||||||
u64 *channels, dont_care = ~0ULL;
|
u64 *uninitialized_var(channels);
|
||||||
u32 *mask, regs;
|
u32 *uninitialized_var(mask), uninitialized_var(regs);
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
int index, ret = -ENOMEM;
|
int index, ret = -EBUSY;
|
||||||
|
|
||||||
if (type == FW_ISO_CONTEXT_TRANSMIT) {
|
|
||||||
channels = &dont_care;
|
|
||||||
mask = &ohci->it_context_mask;
|
|
||||||
list = ohci->it_context_list;
|
|
||||||
callback = handle_it_packet;
|
|
||||||
} else {
|
|
||||||
channels = &ohci->ir_context_channels;
|
|
||||||
mask = &ohci->ir_context_mask;
|
|
||||||
list = ohci->ir_context_list;
|
|
||||||
callback = handle_ir_packet_per_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_irqsave(&ohci->lock, flags);
|
spin_lock_irqsave(&ohci->lock, flags);
|
||||||
index = *channels & 1ULL << channel ? ffs(*mask) - 1 : -1;
|
|
||||||
if (index >= 0) {
|
switch (type) {
|
||||||
*channels &= ~(1ULL << channel);
|
case FW_ISO_CONTEXT_TRANSMIT:
|
||||||
*mask &= ~(1 << index);
|
mask = &ohci->it_context_mask;
|
||||||
|
callback = handle_it_packet;
|
||||||
|
index = ffs(*mask) - 1;
|
||||||
|
if (index >= 0) {
|
||||||
|
*mask &= ~(1 << index);
|
||||||
|
regs = OHCI1394_IsoXmitContextBase(index);
|
||||||
|
ctx = &ohci->it_context_list[index];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE:
|
||||||
|
channels = &ohci->ir_context_channels;
|
||||||
|
mask = &ohci->ir_context_mask;
|
||||||
|
callback = handle_ir_packet_per_buffer;
|
||||||
|
index = *channels & 1ULL << channel ? ffs(*mask) - 1 : -1;
|
||||||
|
if (index >= 0) {
|
||||||
|
*channels &= ~(1ULL << channel);
|
||||||
|
*mask &= ~(1 << index);
|
||||||
|
regs = OHCI1394_IsoRcvContextBase(index);
|
||||||
|
ctx = &ohci->ir_context_list[index];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
|
mask = &ohci->ir_context_mask;
|
||||||
|
callback = handle_ir_buffer_fill;
|
||||||
|
index = !ohci->mc_allocated ? ffs(*mask) - 1 : -1;
|
||||||
|
if (index >= 0) {
|
||||||
|
ohci->mc_allocated = true;
|
||||||
|
*mask &= ~(1 << index);
|
||||||
|
regs = OHCI1394_IsoRcvContextBase(index);
|
||||||
|
ctx = &ohci->ir_context_list[index];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
index = -1;
|
||||||
|
ret = -ENOSYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
spin_unlock_irqrestore(&ohci->lock, flags);
|
spin_unlock_irqrestore(&ohci->lock, flags);
|
||||||
|
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
return ERR_PTR(-EBUSY);
|
return ERR_PTR(ret);
|
||||||
|
|
||||||
if (type == FW_ISO_CONTEXT_TRANSMIT)
|
|
||||||
regs = OHCI1394_IsoXmitContextBase(index);
|
|
||||||
else
|
|
||||||
regs = OHCI1394_IsoRcvContextBase(index);
|
|
||||||
|
|
||||||
ctx = &list[index];
|
|
||||||
memset(ctx, 0, sizeof(*ctx));
|
memset(ctx, 0, sizeof(*ctx));
|
||||||
ctx->header_length = 0;
|
ctx->header_length = 0;
|
||||||
ctx->header = (void *) __get_free_page(GFP_KERNEL);
|
ctx->header = (void *) __get_free_page(GFP_KERNEL);
|
||||||
if (ctx->header == NULL)
|
if (ctx->header == NULL) {
|
||||||
|
ret = -ENOMEM;
|
||||||
goto out;
|
goto out;
|
||||||
|
}
|
||||||
ret = context_init(&ctx->context, ohci, regs, callback);
|
ret = context_init(&ctx->context, ohci, regs, callback);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto out_with_header;
|
goto out_with_header;
|
||||||
|
|
||||||
|
if (type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL)
|
||||||
|
set_multichannel_mask(ohci, 0);
|
||||||
|
|
||||||
return &ctx->base;
|
return &ctx->base;
|
||||||
|
|
||||||
out_with_header:
|
out_with_header:
|
||||||
free_page((unsigned long)ctx->header);
|
free_page((unsigned long)ctx->header);
|
||||||
out:
|
out:
|
||||||
spin_lock_irqsave(&ohci->lock, flags);
|
spin_lock_irqsave(&ohci->lock, flags);
|
||||||
*channels |= 1ULL << channel;
|
|
||||||
|
switch (type) {
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE:
|
||||||
|
*channels |= 1ULL << channel;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
|
ohci->mc_allocated = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
*mask |= 1 << index;
|
*mask |= 1 << index;
|
||||||
|
|
||||||
spin_unlock_irqrestore(&ohci->lock, flags);
|
spin_unlock_irqrestore(&ohci->lock, flags);
|
||||||
|
|
||||||
return ERR_PTR(ret);
|
return ERR_PTR(ret);
|
||||||
|
@ -2321,10 +2390,11 @@ static int ohci_start_iso(struct fw_iso_context *base,
|
||||||
{
|
{
|
||||||
struct iso_context *ctx = container_of(base, struct iso_context, base);
|
struct iso_context *ctx = container_of(base, struct iso_context, base);
|
||||||
struct fw_ohci *ohci = ctx->context.ohci;
|
struct fw_ohci *ohci = ctx->context.ohci;
|
||||||
u32 control, match;
|
u32 control = IR_CONTEXT_ISOCH_HEADER, match;
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
if (ctx->base.type == FW_ISO_CONTEXT_TRANSMIT) {
|
switch (ctx->base.type) {
|
||||||
|
case FW_ISO_CONTEXT_TRANSMIT:
|
||||||
index = ctx - ohci->it_context_list;
|
index = ctx - ohci->it_context_list;
|
||||||
match = 0;
|
match = 0;
|
||||||
if (cycle >= 0)
|
if (cycle >= 0)
|
||||||
|
@ -2334,9 +2404,13 @@ static int ohci_start_iso(struct fw_iso_context *base,
|
||||||
reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 1 << index);
|
reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 1 << index);
|
||||||
reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, 1 << index);
|
reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, 1 << index);
|
||||||
context_run(&ctx->context, match);
|
context_run(&ctx->context, match);
|
||||||
} else {
|
break;
|
||||||
|
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
|
control |= IR_CONTEXT_BUFFER_FILL|IR_CONTEXT_MULTI_CHANNEL_MODE;
|
||||||
|
/* fall through */
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE:
|
||||||
index = ctx - ohci->ir_context_list;
|
index = ctx - ohci->ir_context_list;
|
||||||
control = IR_CONTEXT_ISOCH_HEADER;
|
|
||||||
match = (tags << 28) | (sync << 8) | ctx->base.channel;
|
match = (tags << 28) | (sync << 8) | ctx->base.channel;
|
||||||
if (cycle >= 0) {
|
if (cycle >= 0) {
|
||||||
match |= (cycle & 0x07fff) << 12;
|
match |= (cycle & 0x07fff) << 12;
|
||||||
|
@ -2347,6 +2421,7 @@ static int ohci_start_iso(struct fw_iso_context *base,
|
||||||
reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, 1 << index);
|
reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, 1 << index);
|
||||||
reg_write(ohci, CONTEXT_MATCH(ctx->context.regs), match);
|
reg_write(ohci, CONTEXT_MATCH(ctx->context.regs), match);
|
||||||
context_run(&ctx->context, control);
|
context_run(&ctx->context, control);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2358,12 +2433,17 @@ static int ohci_stop_iso(struct fw_iso_context *base)
|
||||||
struct iso_context *ctx = container_of(base, struct iso_context, base);
|
struct iso_context *ctx = container_of(base, struct iso_context, base);
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
if (ctx->base.type == FW_ISO_CONTEXT_TRANSMIT) {
|
switch (ctx->base.type) {
|
||||||
|
case FW_ISO_CONTEXT_TRANSMIT:
|
||||||
index = ctx - ohci->it_context_list;
|
index = ctx - ohci->it_context_list;
|
||||||
reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 1 << index);
|
reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 1 << index);
|
||||||
} else {
|
break;
|
||||||
|
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE:
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
index = ctx - ohci->ir_context_list;
|
index = ctx - ohci->ir_context_list;
|
||||||
reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 1 << index);
|
reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 1 << index);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
flush_writes(ohci);
|
flush_writes(ohci);
|
||||||
context_stop(&ctx->context);
|
context_stop(&ctx->context);
|
||||||
|
@ -2384,24 +2464,65 @@ static void ohci_free_iso_context(struct fw_iso_context *base)
|
||||||
|
|
||||||
spin_lock_irqsave(&ohci->lock, flags);
|
spin_lock_irqsave(&ohci->lock, flags);
|
||||||
|
|
||||||
if (ctx->base.type == FW_ISO_CONTEXT_TRANSMIT) {
|
switch (base->type) {
|
||||||
|
case FW_ISO_CONTEXT_TRANSMIT:
|
||||||
index = ctx - ohci->it_context_list;
|
index = ctx - ohci->it_context_list;
|
||||||
ohci->it_context_mask |= 1 << index;
|
ohci->it_context_mask |= 1 << index;
|
||||||
} else {
|
break;
|
||||||
|
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE:
|
||||||
index = ctx - ohci->ir_context_list;
|
index = ctx - ohci->ir_context_list;
|
||||||
ohci->ir_context_mask |= 1 << index;
|
ohci->ir_context_mask |= 1 << index;
|
||||||
ohci->ir_context_channels |= 1ULL << base->channel;
|
ohci->ir_context_channels |= 1ULL << base->channel;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
|
index = ctx - ohci->ir_context_list;
|
||||||
|
ohci->ir_context_mask |= 1 << index;
|
||||||
|
ohci->ir_context_channels |= ohci->mc_channels;
|
||||||
|
ohci->mc_channels = 0;
|
||||||
|
ohci->mc_allocated = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
spin_unlock_irqrestore(&ohci->lock, flags);
|
spin_unlock_irqrestore(&ohci->lock, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ohci_queue_iso_transmit(struct fw_iso_context *base,
|
static int ohci_set_iso_channels(struct fw_iso_context *base, u64 *channels)
|
||||||
struct fw_iso_packet *packet,
|
{
|
||||||
struct fw_iso_buffer *buffer,
|
struct fw_ohci *ohci = fw_ohci(base->card);
|
||||||
unsigned long payload)
|
unsigned long flags;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
switch (base->type) {
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ohci->lock, flags);
|
||||||
|
|
||||||
|
/* Don't allow multichannel to grab other contexts' channels. */
|
||||||
|
if (~ohci->ir_context_channels & ~ohci->mc_channels & *channels) {
|
||||||
|
*channels = ohci->ir_context_channels;
|
||||||
|
ret = -EBUSY;
|
||||||
|
} else {
|
||||||
|
set_multichannel_mask(ohci, *channels);
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&ohci->lock, flags);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int queue_iso_transmit(struct iso_context *ctx,
|
||||||
|
struct fw_iso_packet *packet,
|
||||||
|
struct fw_iso_buffer *buffer,
|
||||||
|
unsigned long payload)
|
||||||
{
|
{
|
||||||
struct iso_context *ctx = container_of(base, struct iso_context, base);
|
|
||||||
struct descriptor *d, *last, *pd;
|
struct descriptor *d, *last, *pd;
|
||||||
struct fw_iso_packet *p;
|
struct fw_iso_packet *p;
|
||||||
__le32 *header;
|
__le32 *header;
|
||||||
|
@ -2497,14 +2618,12 @@ static int ohci_queue_iso_transmit(struct fw_iso_context *base,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ohci_queue_iso_receive_packet_per_buffer(struct fw_iso_context *base,
|
static int queue_iso_packet_per_buffer(struct 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 iso_context *ctx = container_of(base, struct iso_context, base);
|
|
||||||
struct descriptor *d, *pd;
|
struct descriptor *d, *pd;
|
||||||
struct fw_iso_packet *p = packet;
|
|
||||||
dma_addr_t d_bus, page_bus;
|
dma_addr_t d_bus, page_bus;
|
||||||
u32 z, header_z, rest;
|
u32 z, header_z, rest;
|
||||||
int i, j, length;
|
int i, j, length;
|
||||||
|
@ -2514,14 +2633,14 @@ static int ohci_queue_iso_receive_packet_per_buffer(struct fw_iso_context *base,
|
||||||
* The OHCI controller puts the isochronous header and trailer in the
|
* The OHCI controller puts the isochronous header and trailer in the
|
||||||
* buffer, so we need at least 8 bytes.
|
* buffer, so we need at least 8 bytes.
|
||||||
*/
|
*/
|
||||||
packet_count = p->header_length / ctx->base.header_size;
|
packet_count = packet->header_length / ctx->base.header_size;
|
||||||
header_size = max(ctx->base.header_size, (size_t)8);
|
header_size = max(ctx->base.header_size, (size_t)8);
|
||||||
|
|
||||||
/* Get header size in number of descriptors. */
|
/* Get header size in number of descriptors. */
|
||||||
header_z = DIV_ROUND_UP(header_size, sizeof(*d));
|
header_z = DIV_ROUND_UP(header_size, sizeof(*d));
|
||||||
page = payload >> PAGE_SHIFT;
|
page = payload >> PAGE_SHIFT;
|
||||||
offset = payload & ~PAGE_MASK;
|
offset = payload & ~PAGE_MASK;
|
||||||
payload_per_buffer = p->payload_length / packet_count;
|
payload_per_buffer = packet->payload_length / packet_count;
|
||||||
|
|
||||||
for (i = 0; i < packet_count; i++) {
|
for (i = 0; i < packet_count; i++) {
|
||||||
/* d points to the header descriptor */
|
/* d points to the header descriptor */
|
||||||
|
@ -2533,7 +2652,7 @@ static int ohci_queue_iso_receive_packet_per_buffer(struct fw_iso_context *base,
|
||||||
|
|
||||||
d->control = cpu_to_le16(DESCRIPTOR_STATUS |
|
d->control = cpu_to_le16(DESCRIPTOR_STATUS |
|
||||||
DESCRIPTOR_INPUT_MORE);
|
DESCRIPTOR_INPUT_MORE);
|
||||||
if (p->skip && i == 0)
|
if (packet->skip && i == 0)
|
||||||
d->control |= cpu_to_le16(DESCRIPTOR_WAIT);
|
d->control |= cpu_to_le16(DESCRIPTOR_WAIT);
|
||||||
d->req_count = cpu_to_le16(header_size);
|
d->req_count = cpu_to_le16(header_size);
|
||||||
d->res_count = d->req_count;
|
d->res_count = d->req_count;
|
||||||
|
@ -2566,7 +2685,7 @@ static int ohci_queue_iso_receive_packet_per_buffer(struct fw_iso_context *base,
|
||||||
pd->control = cpu_to_le16(DESCRIPTOR_STATUS |
|
pd->control = cpu_to_le16(DESCRIPTOR_STATUS |
|
||||||
DESCRIPTOR_INPUT_LAST |
|
DESCRIPTOR_INPUT_LAST |
|
||||||
DESCRIPTOR_BRANCH_ALWAYS);
|
DESCRIPTOR_BRANCH_ALWAYS);
|
||||||
if (p->interrupt && i == packet_count - 1)
|
if (packet->interrupt && i == packet_count - 1)
|
||||||
pd->control |= cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS);
|
pd->control |= cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS);
|
||||||
|
|
||||||
context_append(&ctx->context, d, z, header_z);
|
context_append(&ctx->context, d, z, header_z);
|
||||||
|
@ -2575,6 +2694,58 @@ static int ohci_queue_iso_receive_packet_per_buffer(struct fw_iso_context *base,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int queue_iso_buffer_fill(struct iso_context *ctx,
|
||||||
|
struct fw_iso_packet *packet,
|
||||||
|
struct fw_iso_buffer *buffer,
|
||||||
|
unsigned long payload)
|
||||||
|
{
|
||||||
|
struct descriptor *d;
|
||||||
|
dma_addr_t d_bus, page_bus;
|
||||||
|
int page, offset, rest, z, i, length;
|
||||||
|
|
||||||
|
page = payload >> PAGE_SHIFT;
|
||||||
|
offset = payload & ~PAGE_MASK;
|
||||||
|
rest = packet->payload_length;
|
||||||
|
|
||||||
|
/* We need one descriptor for each page in the buffer. */
|
||||||
|
z = DIV_ROUND_UP(offset + rest, PAGE_SIZE);
|
||||||
|
|
||||||
|
if (WARN_ON(offset & 3 || rest & 3 || page + z > buffer->page_count))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
for (i = 0; i < z; i++) {
|
||||||
|
d = context_get_descriptors(&ctx->context, 1, &d_bus);
|
||||||
|
if (d == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
d->control = cpu_to_le16(DESCRIPTOR_INPUT_MORE |
|
||||||
|
DESCRIPTOR_BRANCH_ALWAYS);
|
||||||
|
if (packet->skip && i == 0)
|
||||||
|
d->control |= cpu_to_le16(DESCRIPTOR_WAIT);
|
||||||
|
if (packet->interrupt && i == z - 1)
|
||||||
|
d->control |= cpu_to_le16(DESCRIPTOR_IRQ_ALWAYS);
|
||||||
|
|
||||||
|
if (offset + rest < PAGE_SIZE)
|
||||||
|
length = rest;
|
||||||
|
else
|
||||||
|
length = PAGE_SIZE - offset;
|
||||||
|
d->req_count = cpu_to_le16(length);
|
||||||
|
d->res_count = d->req_count;
|
||||||
|
d->transfer_status = 0;
|
||||||
|
|
||||||
|
page_bus = page_private(buffer->pages[page]);
|
||||||
|
d->data_address = cpu_to_le32(page_bus + offset);
|
||||||
|
|
||||||
|
rest -= length;
|
||||||
|
offset = 0;
|
||||||
|
page++;
|
||||||
|
|
||||||
|
context_append(&ctx->context, d, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int ohci_queue_iso(struct fw_iso_context *base,
|
static int ohci_queue_iso(struct fw_iso_context *base,
|
||||||
struct fw_iso_packet *packet,
|
struct fw_iso_packet *packet,
|
||||||
struct fw_iso_buffer *buffer,
|
struct fw_iso_buffer *buffer,
|
||||||
|
@ -2582,14 +2753,20 @@ static int ohci_queue_iso(struct fw_iso_context *base,
|
||||||
{
|
{
|
||||||
struct iso_context *ctx = container_of(base, struct iso_context, base);
|
struct iso_context *ctx = container_of(base, struct iso_context, base);
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
int ret;
|
int ret = -ENOSYS;
|
||||||
|
|
||||||
spin_lock_irqsave(&ctx->context.ohci->lock, flags);
|
spin_lock_irqsave(&ctx->context.ohci->lock, flags);
|
||||||
if (base->type == FW_ISO_CONTEXT_TRANSMIT)
|
switch (base->type) {
|
||||||
ret = ohci_queue_iso_transmit(base, packet, buffer, payload);
|
case FW_ISO_CONTEXT_TRANSMIT:
|
||||||
else
|
ret = queue_iso_transmit(ctx, packet, buffer, payload);
|
||||||
ret = ohci_queue_iso_receive_packet_per_buffer(base, packet,
|
break;
|
||||||
buffer, payload);
|
case FW_ISO_CONTEXT_RECEIVE:
|
||||||
|
ret = queue_iso_packet_per_buffer(ctx, packet, buffer, payload);
|
||||||
|
break;
|
||||||
|
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
|
ret = queue_iso_buffer_fill(ctx, packet, buffer, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
spin_unlock_irqrestore(&ctx->context.ohci->lock, flags);
|
spin_unlock_irqrestore(&ctx->context.ohci->lock, flags);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -2609,6 +2786,7 @@ static const struct fw_card_driver ohci_driver = {
|
||||||
|
|
||||||
.allocate_iso_context = ohci_allocate_iso_context,
|
.allocate_iso_context = ohci_allocate_iso_context,
|
||||||
.free_iso_context = ohci_free_iso_context,
|
.free_iso_context = ohci_free_iso_context,
|
||||||
|
.set_iso_channels = ohci_set_iso_channels,
|
||||||
.queue_iso = ohci_queue_iso,
|
.queue_iso = ohci_queue_iso,
|
||||||
.start_iso = ohci_start_iso,
|
.start_iso = ohci_start_iso,
|
||||||
.stop_iso = ohci_stop_iso,
|
.stop_iso = ohci_stop_iso,
|
||||||
|
|
|
@ -25,17 +25,18 @@
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/firewire-constants.h>
|
#include <linux/firewire-constants.h>
|
||||||
|
|
||||||
#define FW_CDEV_EVENT_BUS_RESET 0x00
|
#define FW_CDEV_EVENT_BUS_RESET 0x00
|
||||||
#define FW_CDEV_EVENT_RESPONSE 0x01
|
#define FW_CDEV_EVENT_RESPONSE 0x01
|
||||||
#define FW_CDEV_EVENT_REQUEST 0x02
|
#define FW_CDEV_EVENT_REQUEST 0x02
|
||||||
#define FW_CDEV_EVENT_ISO_INTERRUPT 0x03
|
#define FW_CDEV_EVENT_ISO_INTERRUPT 0x03
|
||||||
#define FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED 0x04
|
#define FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED 0x04
|
||||||
#define FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED 0x05
|
#define FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED 0x05
|
||||||
|
|
||||||
/* available since kernel version 2.6.36 */
|
/* available since kernel version 2.6.36 */
|
||||||
#define FW_CDEV_EVENT_REQUEST2 0x06
|
#define FW_CDEV_EVENT_REQUEST2 0x06
|
||||||
#define FW_CDEV_EVENT_PHY_PACKET_SENT 0x07
|
#define FW_CDEV_EVENT_PHY_PACKET_SENT 0x07
|
||||||
#define FW_CDEV_EVENT_PHY_PACKET_RECEIVED 0x08
|
#define FW_CDEV_EVENT_PHY_PACKET_RECEIVED 0x08
|
||||||
|
#define FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL 0x09
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct fw_cdev_event_common - Common part of all fw_cdev_event_ types
|
* struct fw_cdev_event_common - Common part of all fw_cdev_event_ types
|
||||||
|
@ -218,35 +219,41 @@ struct fw_cdev_event_request2 {
|
||||||
* This event is sent when the controller has completed an &fw_cdev_iso_packet
|
* This event is sent when the controller has completed an &fw_cdev_iso_packet
|
||||||
* with the %FW_CDEV_ISO_INTERRUPT bit set.
|
* with the %FW_CDEV_ISO_INTERRUPT bit set.
|
||||||
*
|
*
|
||||||
* Isochronous transmit events:
|
* Isochronous transmit events (context type %FW_CDEV_ISO_CONTEXT_TRANSMIT):
|
||||||
*
|
*
|
||||||
* In version 1 of the ABI, &header_length is 0. In version 3 and some
|
* In version 3 and some implementations of version 2 of the ABI, &header_length
|
||||||
* implementations of version 2 of the ABI, &header_length is a multiple of 4
|
* is a multiple of 4 and &header contains timestamps of all packets up until
|
||||||
* and &header contains timestamps of all packets up until the interrupt packet.
|
* the interrupt packet. The format of the timestamps is as described below for
|
||||||
* The format of the timestamps is as described below for isochronous reception.
|
* isochronous reception. In version 1 of the ABI, &header_length was 0.
|
||||||
*
|
*
|
||||||
* Isochronous receive events:
|
* Isochronous receive events (context type %FW_CDEV_ISO_CONTEXT_RECEIVE):
|
||||||
*
|
*
|
||||||
* The headers stripped of all packets up until and including the interrupt
|
* The headers stripped of all packets up until and including the interrupt
|
||||||
* packet are returned in the @header field. The amount of header data per
|
* packet are returned in the @header field. The amount of header data per
|
||||||
* packet is as specified at iso context creation by
|
* packet is as specified at iso context creation by
|
||||||
* &fw_cdev_create_iso_context.header_size.
|
* &fw_cdev_create_iso_context.header_size.
|
||||||
*
|
*
|
||||||
* In version 1 of this ABI, header data consisted of the 1394 isochronous
|
* Hence, _interrupt.header_length / _context.header_size is the number of
|
||||||
* packet header, followed by quadlets from the packet payload if
|
* packets received in this interrupt event. The client can now iterate
|
||||||
* &fw_cdev_create_iso_context.header_size > 4.
|
* through the mmap()'ed DMA buffer according to this number of packets and
|
||||||
|
* to the buffer sizes as the client specified in &fw_cdev_queue_iso.
|
||||||
*
|
*
|
||||||
* In version 2 of this ABI, header data consist of the 1394 isochronous
|
* Since version 2 of this ABI, the portion for each packet in _interrupt.header
|
||||||
* packet header, followed by a timestamp quadlet if
|
* consists of the 1394 isochronous packet header, followed by a timestamp
|
||||||
* &fw_cdev_create_iso_context.header_size > 4, followed by quadlets from the
|
* quadlet if &fw_cdev_create_iso_context.header_size > 4, followed by quadlets
|
||||||
* packet payload if &fw_cdev_create_iso_context.header_size > 8.
|
* from the packet payload if &fw_cdev_create_iso_context.header_size > 8.
|
||||||
*
|
*
|
||||||
|
* Format of 1394 iso packet header: 16 bits data_length, 2 bits tag, 6 bits
|
||||||
|
* channel, 4 bits tcode, 4 bits sy, in big endian byte order.
|
||||||
|
* data_length is the actual received size of the packet without the four
|
||||||
|
* 1394 iso packet header bytes.
|
||||||
|
*
|
||||||
|
* Format of timestamp: 16 bits invalid, 3 bits cycleSeconds, 13 bits
|
||||||
|
* cycleCount, in big endian byte order.
|
||||||
|
*
|
||||||
|
* In version 1 of the ABI, no timestamp quadlet was inserted; instead, payload
|
||||||
|
* data followed directly after the 1394 is header if header_size > 4.
|
||||||
* Behaviour of ver. 1 of this ABI is no longer available since ABI ver. 2.
|
* Behaviour of ver. 1 of this ABI is no longer available since ABI ver. 2.
|
||||||
*
|
|
||||||
* Format of 1394 iso packet header: 16 bits len, 2 bits tag, 6 bits channel,
|
|
||||||
* 4 bits tcode, 4 bits sy, in big endian byte order. Format of timestamp:
|
|
||||||
* 16 bits invalid, 3 bits cycleSeconds, 13 bits cycleCount, in big endian byte
|
|
||||||
* order.
|
|
||||||
*/
|
*/
|
||||||
struct fw_cdev_event_iso_interrupt {
|
struct fw_cdev_event_iso_interrupt {
|
||||||
__u64 closure;
|
__u64 closure;
|
||||||
|
@ -256,6 +263,43 @@ struct fw_cdev_event_iso_interrupt {
|
||||||
__u32 header[0];
|
__u32 header[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct fw_cdev_event_iso_interrupt_mc - An iso buffer chunk was completed
|
||||||
|
* @closure: See &fw_cdev_event_common;
|
||||||
|
* set by %FW_CDEV_CREATE_ISO_CONTEXT ioctl
|
||||||
|
* @type: %FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL
|
||||||
|
* @completed: Offset into the receive buffer; data before this offest is valid
|
||||||
|
*
|
||||||
|
* This event is sent in multichannel contexts (context type
|
||||||
|
* %FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL) for &fw_cdev_iso_packet buffer
|
||||||
|
* chunks that have the %FW_CDEV_ISO_INTERRUPT bit set. Whether this happens
|
||||||
|
* when a packet is completed and/or when a buffer chunk is completed depends
|
||||||
|
* on the hardware implementation.
|
||||||
|
*
|
||||||
|
* The buffer is continuously filled with the following data, per packet:
|
||||||
|
* - the 1394 iso packet header as described at &fw_cdev_event_iso_interrupt,
|
||||||
|
* but in little endian byte order,
|
||||||
|
* - packet payload (as many bytes as specified in the data_length field of
|
||||||
|
* the 1394 iso packet header) in big endian byte order,
|
||||||
|
* - 0...3 padding bytes as needed to align the following trailer quadlet,
|
||||||
|
* - trailer quadlet, containing the reception timestamp as described at
|
||||||
|
* &fw_cdev_event_iso_interrupt, but in little endian byte order.
|
||||||
|
*
|
||||||
|
* Hence the per-packet size is data_length (rounded up to a multiple of 4) + 8.
|
||||||
|
* When processing the data, stop before a packet that would cross the
|
||||||
|
* @completed offset.
|
||||||
|
*
|
||||||
|
* A packet near the end of a buffer chunk will typically spill over into the
|
||||||
|
* next queued buffer chunk. It is the responsibility of the client to check
|
||||||
|
* for this condition, assemble a broken-up packet from its parts, and not to
|
||||||
|
* re-queue any buffer chunks in which as yet unread packet parts reside.
|
||||||
|
*/
|
||||||
|
struct fw_cdev_event_iso_interrupt_mc {
|
||||||
|
__u64 closure;
|
||||||
|
__u32 type;
|
||||||
|
__u32 completed;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct fw_cdev_event_iso_resource - Iso resources were allocated or freed
|
* struct fw_cdev_event_iso_resource - Iso resources were allocated or freed
|
||||||
* @closure: See &fw_cdev_event_common;
|
* @closure: See &fw_cdev_event_common;
|
||||||
|
@ -311,16 +355,18 @@ struct fw_cdev_event_phy_packet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* union fw_cdev_event - Convenience union of fw_cdev_event_ types
|
* union fw_cdev_event - Convenience union of fw_cdev_event_ types
|
||||||
* @common: Valid for all types
|
* @common: Valid for all types
|
||||||
* @bus_reset: Valid if @common.type == %FW_CDEV_EVENT_BUS_RESET
|
* @bus_reset: Valid if @common.type == %FW_CDEV_EVENT_BUS_RESET
|
||||||
* @response: Valid if @common.type == %FW_CDEV_EVENT_RESPONSE
|
* @response: Valid if @common.type == %FW_CDEV_EVENT_RESPONSE
|
||||||
* @request: Valid if @common.type == %FW_CDEV_EVENT_REQUEST
|
* @request: Valid if @common.type == %FW_CDEV_EVENT_REQUEST
|
||||||
* @request2: Valid if @common.type == %FW_CDEV_EVENT_REQUEST2
|
* @request2: Valid if @common.type == %FW_CDEV_EVENT_REQUEST2
|
||||||
* @iso_interrupt: Valid if @common.type == %FW_CDEV_EVENT_ISO_INTERRUPT
|
* @iso_interrupt: Valid if @common.type == %FW_CDEV_EVENT_ISO_INTERRUPT
|
||||||
* @iso_resource: Valid if @common.type ==
|
* @iso_interrupt_mc: Valid if @common.type ==
|
||||||
|
* %FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL
|
||||||
|
* @iso_resource: Valid if @common.type ==
|
||||||
* %FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED or
|
* %FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED or
|
||||||
* %FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED
|
* %FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED
|
||||||
* @phy_packet: Valid if @common.type ==
|
* @phy_packet: Valid if @common.type ==
|
||||||
* %FW_CDEV_EVENT_PHY_PACKET_SENT or
|
* %FW_CDEV_EVENT_PHY_PACKET_SENT or
|
||||||
* %FW_CDEV_EVENT_PHY_PACKET_RECEIVED
|
* %FW_CDEV_EVENT_PHY_PACKET_RECEIVED
|
||||||
*
|
*
|
||||||
|
@ -337,10 +383,11 @@ union fw_cdev_event {
|
||||||
struct fw_cdev_event_bus_reset bus_reset;
|
struct fw_cdev_event_bus_reset bus_reset;
|
||||||
struct fw_cdev_event_response response;
|
struct fw_cdev_event_response response;
|
||||||
struct fw_cdev_event_request request;
|
struct fw_cdev_event_request request;
|
||||||
struct fw_cdev_event_request2 request2; /* added in 2.6.36 */
|
struct fw_cdev_event_request2 request2; /* added in 2.6.36 */
|
||||||
struct fw_cdev_event_iso_interrupt iso_interrupt;
|
struct fw_cdev_event_iso_interrupt iso_interrupt;
|
||||||
struct fw_cdev_event_iso_resource iso_resource; /* added in 2.6.30 */
|
struct fw_cdev_event_iso_interrupt_mc iso_interrupt_mc; /* added in 2.6.36 */
|
||||||
struct fw_cdev_event_phy_packet phy_packet; /* added in 2.6.36 */
|
struct fw_cdev_event_iso_resource iso_resource; /* added in 2.6.30 */
|
||||||
|
struct fw_cdev_event_phy_packet phy_packet; /* added in 2.6.36 */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* available since kernel version 2.6.22 */
|
/* available since kernel version 2.6.22 */
|
||||||
|
@ -375,6 +422,7 @@ union fw_cdev_event {
|
||||||
/* available since kernel version 2.6.36 */
|
/* available since kernel version 2.6.36 */
|
||||||
#define FW_CDEV_IOC_SEND_PHY_PACKET _IOWR('#', 0x15, struct fw_cdev_send_phy_packet)
|
#define FW_CDEV_IOC_SEND_PHY_PACKET _IOWR('#', 0x15, struct fw_cdev_send_phy_packet)
|
||||||
#define FW_CDEV_IOC_RECEIVE_PHY_PACKETS _IOW('#', 0x16, struct fw_cdev_receive_phy_packets)
|
#define FW_CDEV_IOC_RECEIVE_PHY_PACKETS _IOW('#', 0x16, struct fw_cdev_receive_phy_packets)
|
||||||
|
#define FW_CDEV_IOC_SET_ISO_CHANNELS _IOW('#', 0x17, struct fw_cdev_set_iso_channels)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ABI version history
|
* ABI version history
|
||||||
|
@ -391,10 +439,13 @@ union fw_cdev_event {
|
||||||
* - shared use and auto-response for FCP registers
|
* - shared use and auto-response for FCP registers
|
||||||
* 3 (2.6.34) - made &fw_cdev_get_cycle_timer reliable
|
* 3 (2.6.34) - made &fw_cdev_get_cycle_timer reliable
|
||||||
* - added %FW_CDEV_IOC_GET_CYCLE_TIMER2
|
* - added %FW_CDEV_IOC_GET_CYCLE_TIMER2
|
||||||
* 4 (2.6.36) - added %FW_CDEV_EVENT_REQUEST2, %FW_CDEV_EVENT_PHY_PACKET_*
|
* 4 (2.6.36) - added %FW_CDEV_EVENT_REQUEST2, %FW_CDEV_EVENT_PHY_PACKET_*,
|
||||||
|
* and &fw_cdev_allocate.region_end
|
||||||
* - implemented &fw_cdev_event_bus_reset.bm_node_id
|
* - implemented &fw_cdev_event_bus_reset.bm_node_id
|
||||||
* - added %FW_CDEV_IOC_SEND_PHY_PACKET, _RECEIVE_PHY_PACKETS
|
* - added %FW_CDEV_IOC_SEND_PHY_PACKET, _RECEIVE_PHY_PACKETS
|
||||||
* - added &fw_cdev_allocate.region_end
|
* - added %FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL,
|
||||||
|
* %FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL, and
|
||||||
|
* %FW_CDEV_IOC_SET_ISO_CHANNELS
|
||||||
*/
|
*/
|
||||||
#define FW_CDEV_VERSION 3 /* Meaningless; don't use this macro. */
|
#define FW_CDEV_VERSION 3 /* Meaningless; don't use this macro. */
|
||||||
|
|
||||||
|
@ -597,34 +648,43 @@ struct fw_cdev_remove_descriptor {
|
||||||
__u32 handle;
|
__u32 handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define FW_CDEV_ISO_CONTEXT_TRANSMIT 0
|
#define FW_CDEV_ISO_CONTEXT_TRANSMIT 0
|
||||||
#define FW_CDEV_ISO_CONTEXT_RECEIVE 1
|
#define FW_CDEV_ISO_CONTEXT_RECEIVE 1
|
||||||
|
#define FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL 2 /* added in 2.6.36 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct fw_cdev_create_iso_context - Create a context for isochronous IO
|
* struct fw_cdev_create_iso_context - Create a context for isochronous I/O
|
||||||
* @type: %FW_CDEV_ISO_CONTEXT_TRANSMIT or %FW_CDEV_ISO_CONTEXT_RECEIVE
|
* @type: %FW_CDEV_ISO_CONTEXT_TRANSMIT or %FW_CDEV_ISO_CONTEXT_RECEIVE or
|
||||||
* @header_size: Header size to strip for receive contexts
|
* %FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL
|
||||||
* @channel: Channel to bind to
|
* @header_size: Header size to strip in single-channel reception
|
||||||
* @speed: Speed for transmit contexts
|
* @channel: Channel to bind to in single-channel reception or transmission
|
||||||
* @closure: To be returned in &fw_cdev_event_iso_interrupt
|
* @speed: Transmission speed
|
||||||
|
* @closure: To be returned in &fw_cdev_event_iso_interrupt or
|
||||||
|
* &fw_cdev_event_iso_interrupt_multichannel
|
||||||
* @handle: Handle to context, written back by kernel
|
* @handle: Handle to context, written back by kernel
|
||||||
*
|
*
|
||||||
* Prior to sending or receiving isochronous I/O, a context must be created.
|
* Prior to sending or receiving isochronous I/O, a context must be created.
|
||||||
* The context records information about the transmit or receive configuration
|
* The context records information about the transmit or receive configuration
|
||||||
* and typically maps to an underlying hardware resource. A context is set up
|
* and typically maps to an underlying hardware resource. A context is set up
|
||||||
* for either sending or receiving. It is bound to a specific isochronous
|
* for either sending or receiving. It is bound to a specific isochronous
|
||||||
* channel.
|
* @channel.
|
||||||
|
*
|
||||||
|
* In case of multichannel reception, @header_size and @channel are ignored
|
||||||
|
* and the channels are selected by %FW_CDEV_IOC_SET_ISO_CHANNELS.
|
||||||
|
*
|
||||||
|
* For %FW_CDEV_ISO_CONTEXT_RECEIVE contexts, @header_size must be at least 4
|
||||||
|
* and must be a multiple of 4. It is ignored in other context types.
|
||||||
|
*
|
||||||
|
* @speed is ignored in receive context types.
|
||||||
*
|
*
|
||||||
* If a context was successfully created, the kernel writes back a handle to the
|
* If a context was successfully created, the kernel writes back a handle to the
|
||||||
* context, which must be passed in for subsequent operations on that context.
|
* context, which must be passed in for subsequent operations on that context.
|
||||||
*
|
*
|
||||||
* For receive contexts, @header_size must be at least 4 and must be a multiple
|
* Limitations:
|
||||||
* of 4.
|
|
||||||
*
|
|
||||||
* Note that the effect of a @header_size > 4 depends on
|
|
||||||
* &fw_cdev_get_info.version, as documented at &fw_cdev_event_iso_interrupt.
|
|
||||||
*
|
|
||||||
* No more than one iso context can be created per fd.
|
* No more than one iso context can be created per fd.
|
||||||
|
* The total number of contexts that all userspace and kernelspace drivers can
|
||||||
|
* create on a card at a time is a hardware limit, typically 4 or 8 contexts per
|
||||||
|
* direction, and of them at most one multichannel receive context.
|
||||||
*/
|
*/
|
||||||
struct fw_cdev_create_iso_context {
|
struct fw_cdev_create_iso_context {
|
||||||
__u32 type;
|
__u32 type;
|
||||||
|
@ -635,6 +695,22 @@ struct fw_cdev_create_iso_context {
|
||||||
__u32 handle;
|
__u32 handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct fw_cdev_set_iso_channels - Select channels in multichannel reception
|
||||||
|
* @channels: Bitmask of channels to listen to
|
||||||
|
* @handle: Handle of the mutichannel receive context
|
||||||
|
*
|
||||||
|
* @channels is the bitwise or of 1ULL << n for each channel n to listen to.
|
||||||
|
*
|
||||||
|
* The ioctl fails with errno %EBUSY if there is already another receive context
|
||||||
|
* on a channel in @channels. In that case, the bitmask of all unoccupied
|
||||||
|
* channels is returned in @channels.
|
||||||
|
*/
|
||||||
|
struct fw_cdev_set_iso_channels {
|
||||||
|
__u64 channels;
|
||||||
|
__u32 handle;
|
||||||
|
};
|
||||||
|
|
||||||
#define FW_CDEV_ISO_PAYLOAD_LENGTH(v) (v)
|
#define FW_CDEV_ISO_PAYLOAD_LENGTH(v) (v)
|
||||||
#define FW_CDEV_ISO_INTERRUPT (1 << 16)
|
#define FW_CDEV_ISO_INTERRUPT (1 << 16)
|
||||||
#define FW_CDEV_ISO_SKIP (1 << 17)
|
#define FW_CDEV_ISO_SKIP (1 << 17)
|
||||||
|
@ -645,42 +721,72 @@ struct fw_cdev_create_iso_context {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct fw_cdev_iso_packet - Isochronous packet
|
* struct fw_cdev_iso_packet - Isochronous packet
|
||||||
* @control: Contains the header length (8 uppermost bits), the sy field
|
* @control: Contains the header length (8 uppermost bits),
|
||||||
* (4 bits), the tag field (2 bits), a sync flag (1 bit),
|
* the sy field (4 bits), the tag field (2 bits), a sync flag
|
||||||
* a skip flag (1 bit), an interrupt flag (1 bit), and the
|
* or a skip flag (1 bit), an interrupt flag (1 bit), and the
|
||||||
* payload length (16 lowermost bits)
|
* payload length (16 lowermost bits)
|
||||||
* @header: Header and payload
|
* @header: Header and payload in case of a transmit context.
|
||||||
*
|
*
|
||||||
* &struct fw_cdev_iso_packet is used to describe isochronous packet queues.
|
* &struct fw_cdev_iso_packet is used to describe isochronous packet queues.
|
||||||
*
|
|
||||||
* Use the FW_CDEV_ISO_ macros to fill in @control.
|
* Use the FW_CDEV_ISO_ macros to fill in @control.
|
||||||
|
* The @header array is empty in case of receive contexts.
|
||||||
*
|
*
|
||||||
* For transmit packets, the header length must be a multiple of 4 and specifies
|
* Context type %FW_CDEV_ISO_CONTEXT_TRANSMIT:
|
||||||
* the numbers of bytes in @header that will be prepended to the packet's
|
|
||||||
* payload; these bytes are copied into the kernel and will not be accessed
|
|
||||||
* after the ioctl has returned. The sy and tag fields are copied to the iso
|
|
||||||
* packet header (these fields are specified by IEEE 1394a and IEC 61883-1).
|
|
||||||
* The skip flag specifies that no packet is to be sent in a frame; when using
|
|
||||||
* this, all other fields except the interrupt flag must be zero.
|
|
||||||
*
|
*
|
||||||
* For receive packets, the header length must be a multiple of the context's
|
* @control.HEADER_LENGTH must be a multiple of 4. It specifies the numbers of
|
||||||
* header size; if the header length is larger than the context's header size,
|
* bytes in @header that will be prepended to the packet's payload. These bytes
|
||||||
* multiple packets are queued for this entry. The sy and tag fields are
|
* are copied into the kernel and will not be accessed after the ioctl has
|
||||||
* ignored. If the sync flag is set, the context drops all packets until
|
* returned.
|
||||||
* a packet with a matching sy field is received (the sync value to wait for is
|
|
||||||
* specified in the &fw_cdev_start_iso structure). The payload length defines
|
|
||||||
* how many payload bytes can be received for one packet (in addition to payload
|
|
||||||
* quadlets that have been defined as headers and are stripped and returned in
|
|
||||||
* the &fw_cdev_event_iso_interrupt structure). If more bytes are received, the
|
|
||||||
* additional bytes are dropped. If less bytes are received, the remaining
|
|
||||||
* bytes in this part of the payload buffer will not be written to, not even by
|
|
||||||
* the next packet, i.e., packets received in consecutive frames will not
|
|
||||||
* necessarily be consecutive in memory. If an entry has queued multiple
|
|
||||||
* packets, the payload length is divided equally among them.
|
|
||||||
*
|
*
|
||||||
* When a packet with the interrupt flag set has been completed, the
|
* The @control.SY and TAG fields are copied to the iso packet header. These
|
||||||
|
* fields are specified by IEEE 1394a and IEC 61883-1.
|
||||||
|
*
|
||||||
|
* The @control.SKIP flag specifies that no packet is to be sent in a frame.
|
||||||
|
* When using this, all other fields except @control.INTERRUPT must be zero.
|
||||||
|
*
|
||||||
|
* When a packet with the @control.INTERRUPT flag set has been completed, an
|
||||||
|
* &fw_cdev_event_iso_interrupt event will be sent.
|
||||||
|
*
|
||||||
|
* Context type %FW_CDEV_ISO_CONTEXT_RECEIVE:
|
||||||
|
*
|
||||||
|
* @control.HEADER_LENGTH must be a multiple of the context's header_size.
|
||||||
|
* If the HEADER_LENGTH is larger than the context's header_size, multiple
|
||||||
|
* packets are queued for this entry.
|
||||||
|
*
|
||||||
|
* The @control.SY and TAG fields are ignored.
|
||||||
|
*
|
||||||
|
* If the @control.SYNC flag is set, the context drops all packets until a
|
||||||
|
* packet with a sy field is received which matches &fw_cdev_start_iso.sync.
|
||||||
|
*
|
||||||
|
* @control.PAYLOAD_LENGTH defines how many payload bytes can be received for
|
||||||
|
* one packet (in addition to payload quadlets that have been defined as headers
|
||||||
|
* and are stripped and returned in the &fw_cdev_event_iso_interrupt structure).
|
||||||
|
* If more bytes are received, the additional bytes are dropped. If less bytes
|
||||||
|
* are received, the remaining bytes in this part of the payload buffer will not
|
||||||
|
* be written to, not even by the next packet. I.e., packets received in
|
||||||
|
* consecutive frames will not necessarily be consecutive in memory. If an
|
||||||
|
* entry has queued multiple packets, the PAYLOAD_LENGTH is divided equally
|
||||||
|
* among them.
|
||||||
|
*
|
||||||
|
* When a packet with the @control.INTERRUPT flag set has been completed, an
|
||||||
* &fw_cdev_event_iso_interrupt event will be sent. An entry that has queued
|
* &fw_cdev_event_iso_interrupt event will be sent. An entry that has queued
|
||||||
* multiple receive packets is completed when its last packet is completed.
|
* multiple receive packets is completed when its last packet is completed.
|
||||||
|
*
|
||||||
|
* Context type %FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
|
||||||
|
*
|
||||||
|
* Here, &fw_cdev_iso_packet would be more aptly named _iso_buffer_chunk since
|
||||||
|
* it specifies a chunk of the mmap()'ed buffer, while the number and alignment
|
||||||
|
* of packets to be placed into the buffer chunk is not known beforehand.
|
||||||
|
*
|
||||||
|
* @control.PAYLOAD_LENGTH is the size of the buffer chunk and specifies room
|
||||||
|
* for header, payload, padding, and trailer bytes of one or more packets.
|
||||||
|
* It must be a multiple of 4.
|
||||||
|
*
|
||||||
|
* @control.HEADER_LENGTH, TAG and SY are ignored. SYNC is treated as described
|
||||||
|
* for single-channel reception.
|
||||||
|
*
|
||||||
|
* When a buffer chunk with the @control.INTERRUPT flag set has been filled
|
||||||
|
* entirely, an &fw_cdev_event_iso_interrupt_mc event will be sent.
|
||||||
*/
|
*/
|
||||||
struct fw_cdev_iso_packet {
|
struct fw_cdev_iso_packet {
|
||||||
__u32 control;
|
__u32 control;
|
||||||
|
@ -689,9 +795,9 @@ struct fw_cdev_iso_packet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct fw_cdev_queue_iso - Queue isochronous packets for I/O
|
* struct fw_cdev_queue_iso - Queue isochronous packets for I/O
|
||||||
* @packets: Userspace pointer to packet data
|
* @packets: Userspace pointer to an array of &fw_cdev_iso_packet
|
||||||
* @data: Pointer into mmap()'ed payload buffer
|
* @data: Pointer into mmap()'ed payload buffer
|
||||||
* @size: Size of packet data in bytes
|
* @size: Size of the @packets array, in bytes
|
||||||
* @handle: Isochronous context handle
|
* @handle: Isochronous context handle
|
||||||
*
|
*
|
||||||
* Queue a number of isochronous packets for reception or transmission.
|
* Queue a number of isochronous packets for reception or transmission.
|
||||||
|
@ -704,6 +810,9 @@ struct fw_cdev_iso_packet {
|
||||||
* The kernel may or may not queue all packets, but will write back updated
|
* The kernel may or may not queue all packets, but will write back updated
|
||||||
* values of the @packets, @data and @size fields, so the ioctl can be
|
* values of the @packets, @data and @size fields, so the ioctl can be
|
||||||
* resubmitted easily.
|
* resubmitted easily.
|
||||||
|
*
|
||||||
|
* In case of a multichannel receive context, @data must be quadlet-aligned
|
||||||
|
* relative to the buffer start.
|
||||||
*/
|
*/
|
||||||
struct fw_cdev_queue_iso {
|
struct fw_cdev_queue_iso {
|
||||||
__u64 packets;
|
__u64 packets;
|
||||||
|
|
|
@ -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 {
|
||||||
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,
|
||||||
|
|
Loading…
Reference in New Issue