From: =?utf-8?q?Kristian_H=C3=B8gsberg?= Subject: [PATCH] firewire: Implement basic isochronous receive functionality. Date: Fri, 16 Feb 2007 17:34:40 -0500 Signed-off-by: Kristian Høgsberg Signed-off-by: Stefan Richter --- drivers/firewire/fw-device-cdev.c | 23 ++++- drivers/firewire/fw-device-cdev.h | 5 + drivers/firewire/fw-iso.c | 7 +- drivers/firewire/fw-ohci.c | 168 ++++++++++++++++++++++++++++++++---- drivers/firewire/fw-ohci.h | 1 + drivers/firewire/fw-transaction.h | 7 +- 6 files changed, 181 insertions(+), 30 deletions(-) diff --git a/drivers/firewire/fw-device-cdev.c b/drivers/firewire/fw-device-cdev.c index 6284375..1101ccd 100644 --- a/drivers/firewire/fw-device-cdev.c +++ b/drivers/firewire/fw-device-cdev.c @@ -406,8 +406,12 @@ static int ioctl_create_iso_context(struct client *client, void __user *arg) if (copy_from_user(&request, arg, sizeof request)) return -EFAULT; + if (request.type > FW_ISO_CONTEXT_RECEIVE) + return -EINVAL; + client->iso_context = fw_iso_context_create(client->device->card, - FW_ISO_CONTEXT_TRANSMIT, + request.type, + request.header_size, iso_callback, client); if (IS_ERR(client->iso_context)) return PTR_ERR(client->iso_context); @@ -419,7 +423,7 @@ static int ioctl_queue_iso(struct client *client, void __user *arg) { struct fw_cdev_queue_iso request; struct fw_cdev_iso_packet __user *p, *end, *next; - unsigned long payload, payload_end; + unsigned long payload, payload_end, header_length; int count; struct { struct fw_iso_packet packet; @@ -456,12 +460,23 @@ static int ioctl_queue_iso(struct client *client, void __user *arg) while (p < end) { if (__copy_from_user(&u.packet, p, sizeof *p)) return -EFAULT; + + if (client->iso_context->type == FW_ISO_CONTEXT_TRANSMIT) { + header_length = u.packet.header_length; + } else { + /* We require that header_length is a multiple of + * the fixed header size, ctx->header_size */ + if (u.packet.header_length % client->iso_context->header_size != 0) + return -EINVAL; + header_length = 0; + } + next = (struct fw_cdev_iso_packet __user *) - &p->header[u.packet.header_length / 4]; + &p->header[header_length / 4]; if (next > end) return -EINVAL; if (__copy_from_user - (u.packet.header, p->header, u.packet.header_length)) + (u.packet.header, p->header, header_length)) return -EFAULT; if (u.packet.skip && u.packet.header_length + u.packet.payload_length > 0) diff --git a/drivers/firewire/fw-device-cdev.h b/drivers/firewire/fw-device-cdev.h index 003cc66..4e766ec 100644 --- a/drivers/firewire/fw-device-cdev.h +++ b/drivers/firewire/fw-device-cdev.h @@ -125,7 +125,12 @@ struct fw_cdev_allocate { __u32 length; }; +#define FW_CDEV_ISO_CONTEXT_TRANSMIT 0 +#define FW_CDEV_ISO_CONTEXT_RECEIVE 1 + struct fw_cdev_create_iso_context { + __u32 type; + __u32 header_size; __u32 handle; }; diff --git a/drivers/firewire/fw-iso.c b/drivers/firewire/fw-iso.c index 4e7ba86..4c6dc99 100644 --- a/drivers/firewire/fw-iso.c +++ b/drivers/firewire/fw-iso.c @@ -105,9 +105,9 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, buffer->pages = NULL; } -struct fw_iso_context *fw_iso_context_create(struct fw_card *card, int type, - fw_iso_callback_t callback, - void *callback_data) +struct fw_iso_context * +fw_iso_context_create(struct fw_card *card, int type, size_t header_size, + fw_iso_callback_t callback, void *callback_data) { struct fw_iso_context *ctx; @@ -117,6 +117,7 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card, int type, ctx->card = card; ctx->type = type; + ctx->header_size = header_size; ctx->callback = callback; ctx->callback_data = callback_data; diff --git a/drivers/firewire/fw-ohci.c b/drivers/firewire/fw-ohci.c index 86fe55c..90db5a4 100644 --- a/drivers/firewire/fw-ohci.c +++ b/drivers/firewire/fw-ohci.c @@ -45,6 +45,7 @@ #define descriptor_irq_error (1 << 4) #define descriptor_irq_always (3 << 4) #define descriptor_branch_always (3 << 2) +#define descriptor_wait (3 << 0) struct descriptor { __le16 req_count; @@ -55,6 +56,20 @@ struct descriptor { __le16 transfer_status; } __attribute__((aligned(16))); +struct db_descriptor { + __le16 first_size; + __le16 control; + __le16 second_req_count; + __le16 first_req_count; + __le32 branch_address; + __le16 second_res_count; + __le16 first_res_count; + __le32 reserved0; + __le32 first_buffer; + __le32 second_buffer; + __le32 reserved1; +} __attribute__((aligned(16))); + #define control_set(regs) (regs) #define control_clear(regs) ((regs) + 4) #define command_ptr(regs) ((regs) + 12) @@ -171,7 +186,12 @@ static inline struct fw_ohci *fw_ohci(struct fw_card *card) return container_of(card, struct fw_ohci, card); } -#define CONTEXT_CYCLE_MATCH_ENABLE 0x80000000 +#define IT_CONTEXT_CYCLE_MATCH_ENABLE 0x80000000 +#define IR_CONTEXT_BUFFER_FILL 0x80000000 +#define IR_CONTEXT_ISOCH_HEADER 0x40000000 +#define IR_CONTEXT_CYCLE_MATCH_ENABLE 0x20000000 +#define IR_CONTEXT_MULTI_CHANNEL_MODE 0x10000000 +#define IR_CONTEXT_DUAL_BUFFER_MODE 0x08000000 #define CONTEXT_RUN 0x8000 #define CONTEXT_WAKE 0x1000 @@ -518,14 +538,14 @@ context_get_descriptors(struct context *ctx, int z, dma_addr_t *d_bus) return d; } -static void context_run(struct context *ctx, u32 cycle_match) +static void context_run(struct context *ctx, u32 extra) { struct fw_ohci *ohci = ctx->ohci; reg_write(ohci, command_ptr(ctx->regs), le32_to_cpu(ctx->tail_descriptor_last->branch_address)); reg_write(ohci, control_clear(ctx->regs), ~0); - reg_write(ohci, control_set(ctx->regs), CONTEXT_RUN | cycle_match); + reg_write(ohci, control_set(ctx->regs), CONTEXT_RUN | extra); flush_writes(ohci); } @@ -1240,11 +1260,25 @@ ohci_enable_phys_dma(struct fw_card *card, int node_id, int generation) return retval; } -static void ir_context_tasklet(unsigned long data) +static int handle_ir_packet(struct context *context, + struct descriptor *d, + struct descriptor *last) { - struct iso_context *ctx = (struct iso_context *)data; + struct iso_context *ctx = + container_of(context, struct iso_context, context); + struct db_descriptor *db = (struct db_descriptor *) d; + + if (db->first_res_count > 0 && db->second_res_count > 0) + /* This descriptor isn't done yet, stop iteration. */ + return 0; + + if (le16_to_cpu(db->control) & descriptor_irq_always) + /* FIXME: we should pass payload address here. */ + ctx->base.callback(&ctx->base, + 0, 0, + ctx->base.callback_data); - (void)ctx; + return 1; } #define ISO_BUFFER_SIZE (64 * 1024) @@ -1274,7 +1308,7 @@ ohci_allocate_iso_context(struct fw_card *card, int type) struct fw_ohci *ohci = fw_ohci(card); struct iso_context *ctx, *list; descriptor_callback_t callback; - u32 *mask; + u32 *mask, regs; unsigned long flags; int index, retval; @@ -1283,7 +1317,9 @@ ohci_allocate_iso_context(struct fw_card *card, int type) list = ohci->it_context_list; callback = handle_it_packet; } else { - return ERR_PTR(-EINVAL); + mask = &ohci->ir_context_mask; + list = ohci->ir_context_list; + callback = handle_ir_packet; } spin_lock_irqsave(&ohci->lock, flags); @@ -1295,10 +1331,15 @@ ohci_allocate_iso_context(struct fw_card *card, int type) if (index < 0) return ERR_PTR(-EBUSY); + if (type == FW_ISO_CONTEXT_TRANSMIT) + regs = OHCI1394_IsoXmitContextBase(index); + else + regs = OHCI1394_IsoRcvContextBase(index); + ctx = &list[index]; memset(ctx, 0, sizeof *ctx); retval = context_init(&ctx->context, ohci, ISO_BUFFER_SIZE, - OHCI1394_IsoXmitContextBase(index), callback); + regs, callback); if (retval < 0) { spin_lock_irqsave(&ohci->lock, flags); *mask |= 1 << index; @@ -1316,13 +1357,24 @@ static int ohci_send_iso(struct fw_iso_context *base, s32 cycle) u32 cycle_match = 0; int index; - index = ctx - ohci->it_context_list; - if (cycle > 0) - cycle_match = CONTEXT_CYCLE_MATCH_ENABLE | - (cycle & 0x7fff) << 16; + if (ctx->base.type == FW_ISO_CONTEXT_TRANSMIT) { + index = ctx - ohci->it_context_list; + if (cycle > 0) + cycle_match = IT_CONTEXT_CYCLE_MATCH_ENABLE | + (cycle & 0x7fff) << 16; + + reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 1 << index); + reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, 1 << index); + context_run(&ctx->context, cycle_match); + } else { + index = ctx - ohci->ir_context_list; - reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, 1 << index); - context_run(&ctx->context, cycle_match); + reg_write(ohci, OHCI1394_IsoRecvIntEventClear, 1 << index); + reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, 1 << index); + reg_write(ohci, context_match(ctx->context.regs), + 0xf0000000 | ctx->base.channel); + context_run(&ctx->context, IR_CONTEXT_DUAL_BUFFER_MODE); + } return 0; } @@ -1355,10 +1407,10 @@ static void ohci_free_iso_context(struct fw_iso_context *base) } static int -ohci_queue_iso(struct fw_iso_context *base, - struct fw_iso_packet *packet, - struct fw_iso_buffer *buffer, - unsigned long payload) +ohci_queue_iso_transmit(struct fw_iso_context *base, + 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; @@ -1451,6 +1503,84 @@ ohci_queue_iso(struct fw_iso_context *base, return 0; } +static int +ohci_queue_iso_receive(struct fw_iso_context *base, + 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 db_descriptor *db = NULL; + struct descriptor *d; + struct fw_iso_packet *p; + dma_addr_t d_bus, page_bus; + u32 z, header_z, length, rest; + int page, offset; + + /* FIXME: Cycle lost behavior should be configurable: lose + * packet, retransmit or terminate.. */ + + p = packet; + z = 2; + + /* Get header size in number of descriptors. */ + header_z = DIV_ROUND_UP(p->header_length, sizeof *d); + page = payload >> PAGE_SHIFT; + offset = payload & ~PAGE_MASK; + rest = p->payload_length; + + /* FIXME: OHCI 1.0 doesn't support dual buffer receive */ + /* FIXME: handle descriptor_wait */ + /* FIXME: make packet-per-buffer/dual-buffer a context option */ + while (rest > 0) { + d = context_get_descriptors(&ctx->context, + z + header_z, &d_bus); + if (d == NULL) + return -ENOMEM; + + db = (struct db_descriptor *) d; + db->control = cpu_to_le16(descriptor_status | + descriptor_branch_always); + db->first_size = cpu_to_le16(ctx->base.header_size); + db->first_req_count = cpu_to_le16(p->header_length); + db->second_req_count = cpu_to_le16(p->payload_length); + db->first_res_count = cpu_to_le16(db->first_req_count); + db->second_res_count = cpu_to_le16(db->second_req_count); + + db->first_buffer = cpu_to_le32(d_bus + sizeof *db); + + if (offset + rest < PAGE_SIZE) + length = rest; + else + length = PAGE_SIZE - offset; + + page_bus = page_private(buffer->pages[page]); + db->second_buffer = cpu_to_le32(page_bus + offset); + + context_append(&ctx->context, d, z, header_z); + offset = (offset + length) & ~PAGE_MASK; + rest -= length; + page++; + } + + if (p->interrupt) + db->control |= cpu_to_le16(descriptor_irq_always); + + return 0; + } + +static int +ohci_queue_iso(struct fw_iso_context *base, + struct fw_iso_packet *packet, + struct fw_iso_buffer *buffer, + unsigned long payload) +{ + if (base->type == FW_ISO_CONTEXT_TRANSMIT) + return ohci_queue_iso_transmit(base, packet, buffer, payload); + else + return ohci_queue_iso_receive(base, packet, buffer, payload); +} + static const struct fw_card_driver ohci_driver = { .name = ohci_driver_name, .enable = ohci_enable, diff --git a/drivers/firewire/fw-ohci.h b/drivers/firewire/fw-ohci.h index a562305..fa15706 100644 --- a/drivers/firewire/fw-ohci.h +++ b/drivers/firewire/fw-ohci.h @@ -103,6 +103,7 @@ #define OHCI1394_IsoXmitCommandPtr(n) (0x20C + 16 * (n)) /* Isochronous receive registers */ +#define OHCI1394_IsoRcvContextBase(n) (0x400 + 32 * (n)) #define OHCI1394_IsoRcvContextControlSet(n) (0x400 + 32 * (n)) #define OHCI1394_IsoRcvContextControlClear(n) (0x404 + 32 * (n)) #define OHCI1394_IsoRcvCommandPtr(n) (0x40C + 32 * (n)) diff --git a/drivers/firewire/fw-transaction.h b/drivers/firewire/fw-transaction.h index 89c6dda..9e92eda 100644 --- a/drivers/firewire/fw-transaction.h +++ b/drivers/firewire/fw-transaction.h @@ -354,6 +354,7 @@ struct fw_iso_context { int type; int channel; int speed; + size_t header_size; fw_iso_callback_t callback; void *callback_data; }; @@ -369,10 +370,8 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, struct fw_card *card); struct fw_iso_context * -fw_iso_context_create(struct fw_card *card, int type, - fw_iso_callback_t callback, - void *callback_data); - +fw_iso_context_create(struct fw_card *card, int type, size_t header_size, + fw_iso_callback_t callback, void *callback_data); void fw_iso_context_destroy(struct fw_iso_context *ctx); -- 1.4.4.2