]> err.no Git - linux-2.6/commitdiff
firewire: Rework async receive DMA.
authorKristian Høgsberg <krh@redhat.com>
Tue, 6 Feb 2007 19:49:30 +0000 (14:49 -0500)
committerStefan Richter <stefanr@s5r6.in-berlin.de>
Fri, 9 Mar 2007 21:02:50 +0000 (22:02 +0100)
The old DMA program for receiving async packets stops DMA while
processing received packets and only expects one packet per
interrupt.  Stopping DMA can silently drop packets and we need to
handle multiple received packets per interrupt.

This new version keeps DMA running at all times and just append new
pages as buffers fill up, and supports multiple packets per interrupt.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
drivers/firewire/fw-ohci.c
drivers/firewire/fw-transaction.c

index 29285f209dcf818d12b4fdc612c910cbc0a31c68..ec47ae9a2dd1bf3028bfae55066a7d947bf0587b 100644 (file)
@@ -55,17 +55,20 @@ struct descriptor {
        __le16 transfer_status;
 } __attribute__((aligned(16)));
 
-struct ar_context {
-       struct fw_ohci *ohci;
+struct ar_buffer {
        struct descriptor descriptor;
-       __le32 buffer[512];
-       dma_addr_t descriptor_bus;
-       dma_addr_t buffer_bus;
+       struct ar_buffer *next;
+       __le32 data[0];
+};
 
+struct ar_context {
+       struct fw_ohci *ohci;
+       struct ar_buffer *current_buffer;
+       struct ar_buffer *last_buffer;
+       void *pointer;
        u32 command_ptr;
        u32 control_set;
        u32 control_clear;
-
        struct tasklet_struct tasklet;
 };
 
@@ -169,8 +172,7 @@ static inline struct fw_ohci *fw_ohci(struct fw_card *card)
 #define OHCI_LOOP_COUNT                        500
 #define OHCI1394_PCI_HCI_Control       0x40
 #define SELF_ID_BUF_SIZE               0x800
-
-#define MAX_STOP_CONTEXT_LOOPS         1000
+#define OHCI_TCODE_PHY_PACKET          0x0e
 
 static char ohci_driver_name[] = KBUILD_MODNAME;
 
@@ -213,66 +215,97 @@ ohci_update_phy_reg(struct fw_card *card, int addr,
        return 0;
 }
 
-static void ar_context_run(struct ar_context *ctx)
+static int ar_context_add_page(struct ar_context *ctx)
 {
-       reg_write(ctx->ohci, ctx->command_ptr, ctx->descriptor_bus | 1);
-       reg_write(ctx->ohci, ctx->control_set, CONTEXT_RUN);
+       struct device *dev = ctx->ohci->card.device;
+       struct ar_buffer *ab;
+       dma_addr_t ab_bus;
+       size_t offset;
+
+       ab = (struct ar_buffer *) __get_free_page(GFP_ATOMIC);
+       if (ab == NULL)
+               return -ENOMEM;
+
+       ab_bus = dma_map_single(dev, ab, PAGE_SIZE, DMA_BIDIRECTIONAL);
+       if (dma_mapping_error(ab_bus)) {
+               free_page((unsigned long) ab);
+               return -ENOMEM;
+       }
+
+       memset(&ab->descriptor, 0, sizeof ab->descriptor);
+       ab->descriptor.control        = cpu_to_le16(descriptor_input_more |
+                                                   descriptor_status |
+                                                   descriptor_branch_always);
+       offset = offsetof(struct ar_buffer, data);
+       ab->descriptor.req_count      = cpu_to_le16(PAGE_SIZE - offset);
+       ab->descriptor.data_address   = cpu_to_le32(ab_bus + offset);
+       ab->descriptor.res_count      = cpu_to_le16(PAGE_SIZE - offset);
+       ab->descriptor.branch_address = 0;
+
+       dma_sync_single_for_device(dev, ab_bus, PAGE_SIZE, DMA_BIDIRECTIONAL);
+
+       ctx->last_buffer->descriptor.branch_address = ab_bus | 1;
+       ctx->last_buffer->next = ab;
+       ctx->last_buffer = ab;
+
+       reg_write(ctx->ohci, ctx->control_set, CONTEXT_WAKE);
        flush_writes(ctx->ohci);
+
+       return 0;
 }
 
-static void ar_context_tasklet(unsigned long data)
+static __le32 *handle_ar_packet(struct ar_context *ctx, __le32 *buffer)
 {
-       struct ar_context *ctx = (struct ar_context *)data;
        struct fw_ohci *ohci = ctx->ohci;
        struct fw_packet p;
        u32 status, length, tcode;
-       int i;
 
-       /* FIXME: We stop and restart the ar context here, what if we
-        * stop while a receive is in progress? Maybe we could just
-        * loop the context back to itself and use it in buffer fill
-        * mode as intended... */
-       reg_write(ctx->ohci, ctx->control_clear, CONTEXT_RUN);
-
-       /* FIXME: What to do about evt_* errors? */
-       length    = le16_to_cpu(ctx->descriptor.req_count) -
-               le16_to_cpu(ctx->descriptor.res_count) - 4;
-       status    = le32_to_cpu(ctx->buffer[length / 4]);
-
-       p.ack        = ((status >> 16) & 0x1f) - 16;
-       p.speed      = (status >> 21) & 0x7;
-       p.timestamp  = status & 0xffff;
-       p.generation = ohci->request_generation;
-
-       p.header[0] = le32_to_cpu(ctx->buffer[0]);
-       p.header[1] = le32_to_cpu(ctx->buffer[1]);
-       p.header[2] = le32_to_cpu(ctx->buffer[2]);
+       p.header[0] = le32_to_cpu(buffer[0]);
+       p.header[1] = le32_to_cpu(buffer[1]);
+       p.header[2] = le32_to_cpu(buffer[2]);
 
        tcode = (p.header[0] >> 4) & 0x0f;
        switch (tcode) {
        case TCODE_WRITE_QUADLET_REQUEST:
        case TCODE_READ_QUADLET_RESPONSE:
-               p.header[3] = ctx->buffer[3];
+               p.header[3] = (__force __u32) buffer[3];
                p.header_length = 16;
+               p.payload_length = 0;
                break;
 
-       case TCODE_WRITE_BLOCK_REQUEST:
        case TCODE_READ_BLOCK_REQUEST :
+               p.header[3] = le32_to_cpu(buffer[3]);
+               p.header_length = 16;
+               p.payload_length = 0;
+               break;
+
+       case TCODE_WRITE_BLOCK_REQUEST:
        case TCODE_READ_BLOCK_RESPONSE:
        case TCODE_LOCK_REQUEST:
        case TCODE_LOCK_RESPONSE:
-               p.header[3] = le32_to_cpu(ctx->buffer[3]);
+               p.header[3] = le32_to_cpu(buffer[3]);
                p.header_length = 16;
+               p.payload_length = p.header[3] >> 16;
                break;
 
        case TCODE_WRITE_RESPONSE:
        case TCODE_READ_QUADLET_REQUEST:
+       case OHCI_TCODE_PHY_PACKET:
                p.header_length = 12;
+               p.payload_length = 0;
                break;
        }
 
-       p.payload = (void *) ctx->buffer + p.header_length;
-       p.payload_length = length - p.header_length;
+       p.payload = (void *) buffer + p.header_length;
+
+       /* FIXME: What to do about evt_* errors? */
+       length = (p.header_length + p.payload_length + 3) / 4;
+       status = le32_to_cpu(buffer[length]);
+
+       p.ack        = ((status >> 16) & 0x1f) - 16;
+       p.speed      = (status >> 21) & 0x7;
+       p.timestamp  = status & 0xffff;
+       p.generation = ohci->request_generation;
 
        /* The OHCI bus reset handler synthesizes a phy packet with
         * the new generation number when a bus reset happens (see
@@ -283,69 +316,84 @@ static void ar_context_tasklet(unsigned long data)
         * request. */
 
        if (p.ack + 16 == 0x09)
-               ohci->request_generation = (ctx->buffer[2] >> 16) & 0xff;
+               ohci->request_generation = (buffer[2] >> 16) & 0xff;
        else if (ctx == &ohci->ar_request_ctx)
                fw_core_handle_request(&ohci->card, &p);
        else
                fw_core_handle_response(&ohci->card, &p);
 
-       ctx->descriptor.data_address = cpu_to_le32(ctx->buffer_bus);
-       ctx->descriptor.req_count    = cpu_to_le16(sizeof ctx->buffer);
-       ctx->descriptor.res_count    = cpu_to_le16(sizeof ctx->buffer);
-
-       dma_sync_single_for_device(ohci->card.device, ctx->descriptor_bus,
-                                  sizeof ctx->descriptor_bus, DMA_TO_DEVICE);
+       return buffer + length + 1;
+}
 
-       /* Make sure the active bit is 0 before we reprogram the DMA. */
-       for (i = 0; i < MAX_STOP_CONTEXT_LOOPS; i++)
-               if (!(reg_read(ctx->ohci,
-                              ctx->control_clear) & CONTEXT_ACTIVE))
-                       break;
-       if (i == MAX_STOP_CONTEXT_LOOPS)
-               fw_error("Failed to stop ar context\n");
+static void ar_context_tasklet(unsigned long data)
+{
+       struct ar_context *ctx = (struct ar_context *)data;
+       struct fw_ohci *ohci = ctx->ohci;
+       struct ar_buffer *ab;
+       struct descriptor *d;
+       void *buffer, *end;
+
+       ab = ctx->current_buffer;
+       d = &ab->descriptor;
+
+       if (d->res_count == 0) {
+               size_t size, rest, offset;
+
+               /* This descriptor is finished and we may have a
+                * packet split across this and the next buffer. We
+                * reuse the page for reassembling the split packet. */
+
+               offset = offsetof(struct ar_buffer, data);
+               dma_unmap_single(ohci->card.device,
+                                ab->descriptor.data_address - offset,
+                                PAGE_SIZE, DMA_BIDIRECTIONAL);
+
+               buffer = ab;
+               ab = ab->next;
+               d = &ab->descriptor;
+               size = buffer + PAGE_SIZE - ctx->pointer;
+               rest = le16_to_cpu(d->req_count) - le16_to_cpu(d->res_count);
+               memmove(buffer, ctx->pointer, size);
+               memcpy(buffer + size, ab->data, rest);
+               ctx->current_buffer = ab;
+               ctx->pointer = (void *) ab->data + rest;
+               end = buffer + size + rest;
+
+               while (buffer < end)
+                       buffer = handle_ar_packet(ctx, buffer);
+
+               free_page((unsigned long)buffer);
+               ar_context_add_page(ctx);
+       } else {
+               buffer = ctx->pointer;
+               ctx->pointer = end =
+                       (void *) ab + PAGE_SIZE - le16_to_cpu(d->res_count);
 
-       ar_context_run(ctx);
+               while (buffer < end)
+                       buffer = handle_ar_packet(ctx, buffer);
+       }
 }
 
 static int
 ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci, u32 control_set)
 {
-       ctx->descriptor_bus =
-               dma_map_single(ohci->card.device, &ctx->descriptor,
-                              sizeof ctx->descriptor, DMA_TO_DEVICE);
-       if (ctx->descriptor_bus == 0)
-               return -ENOMEM;
-
-       if (ctx->descriptor_bus & 0xf)
-               fw_notify("descriptor not 16-byte aligned: 0x%08lx\n",
-                         (unsigned long)ctx->descriptor_bus);
-
-       ctx->buffer_bus =
-               dma_map_single(ohci->card.device, ctx->buffer,
-                              sizeof ctx->buffer, DMA_FROM_DEVICE);
-
-       if (ctx->buffer_bus == 0) {
-               dma_unmap_single(ohci->card.device, ctx->descriptor_bus,
-                                sizeof ctx->descriptor, DMA_TO_DEVICE);
-               return -ENOMEM;
-       }
-
-       memset(&ctx->descriptor, 0, sizeof ctx->descriptor);
-       ctx->descriptor.control      = cpu_to_le16(descriptor_input_more |
-                                                  descriptor_status |
-                                                  descriptor_branch_always);
-       ctx->descriptor.req_count    = cpu_to_le16(sizeof ctx->buffer);
-       ctx->descriptor.data_address = cpu_to_le32(ctx->buffer_bus);
-       ctx->descriptor.res_count    = cpu_to_le16(sizeof ctx->buffer);
+       struct ar_buffer ab;
 
        ctx->control_set   = control_set;
        ctx->control_clear = control_set + 4;
        ctx->command_ptr   = control_set + 12;
        ctx->ohci          = ohci;
-
+       ctx->last_buffer   = &ab;
        tasklet_init(&ctx->tasklet, ar_context_tasklet, (unsigned long)ctx);
 
-       ar_context_run(ctx);
+       ar_context_add_page(ctx);
+       ar_context_add_page(ctx);
+       ctx->current_buffer = ab.next;
+       ctx->pointer = ctx->current_buffer->data;
+
+       reg_write(ctx->ohci, ctx->command_ptr, ab.descriptor.branch_address);
+       reg_write(ctx->ohci, ctx->control_set, CONTEXT_RUN);
+       flush_writes(ctx->ohci);
 
        return 0;
 }
index 4a48e2d7694e3228534760e75c46d651711ff814..fb3b77e1bb2dc1e9cf61085403274c7dd06e6b11 100644 (file)
@@ -640,7 +640,8 @@ fw_core_handle_response(struct fw_card *card, struct fw_packet *p)
        spin_unlock_irqrestore(&card->lock, flags);
 
        if (&t->link == &card->transaction_list) {
-               fw_notify("Unsolicited response\n");
+               fw_notify("Unsolicited response (source %x, tlabel %x)\n",
+                         source, tlabel);
                return;
        }