firewire: use atomic type for fw_device.state
[safe/jmp/linux-2.6] / drivers / firewire / fw-ohci.c
index d6f0644..29285f2 100644 (file)
@@ -121,6 +121,7 @@ struct fw_ohci {
        dma_addr_t self_id_bus;
        __le32 *self_id_cpu;
        struct tasklet_struct bus_reset_tasklet;
+       int node_id;
        int generation;
        int request_generation;
 
@@ -169,6 +170,8 @@ static inline struct fw_ohci *fw_ohci(struct fw_card *card)
 #define OHCI1394_PCI_HCI_Control       0x40
 #define SELF_ID_BUF_SIZE               0x800
 
+#define MAX_STOP_CONTEXT_LOOPS         1000
+
 static char ohci_driver_name[] = KBUILD_MODNAME;
 
 static inline void reg_write(const struct fw_ohci *ohci, int offset, u32 data)
@@ -221,24 +224,55 @@ static void ar_context_tasklet(unsigned long data)
 {
        struct ar_context *ctx = (struct ar_context *)data;
        struct fw_ohci *ohci = ctx->ohci;
-       u32 status;
-       int length, speed, ack, timestamp, tcode;
+       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]);
-       ack       = ((status >> 16) & 0x1f) - 16;
-       speed     = (status >> 21) & 0x7;
-       timestamp = status & 0xffff;
 
-       ctx->buffer[0] = le32_to_cpu(ctx->buffer[0]);
-       ctx->buffer[1] = le32_to_cpu(ctx->buffer[1]);
-       ctx->buffer[2] = le32_to_cpu(ctx->buffer[2]);
+       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]);
+
+       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_length = 16;
+               break;
+
+       case TCODE_WRITE_BLOCK_REQUEST:
+       case TCODE_READ_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_length = 16;
+               break;
+
+       case TCODE_WRITE_RESPONSE:
+       case TCODE_READ_QUADLET_REQUEST:
+               p.header_length = 12;
+               break;
+       }
 
-       tcode = (ctx->buffer[0] >> 4) & 0x0f;
-       if (TCODE_IS_BLOCK_PACKET(tcode))
-               ctx->buffer[3] = le32_to_cpu(ctx->buffer[3]);
+       p.payload = (void *) ctx->buffer + p.header_length;
+       p.payload_length = length - p.header_length;
 
        /* The OHCI bus reset handler synthesizes a phy packet with
         * the new generation number when a bus reset happens (see
@@ -248,15 +282,12 @@ static void ar_context_tasklet(unsigned long data)
         * we use the unique tlabel for finding the matching
         * request. */
 
-       if (ack + 16 == 0x09)
+       if (p.ack + 16 == 0x09)
                ohci->request_generation = (ctx->buffer[2] >> 16) & 0xff;
        else if (ctx == &ohci->ar_request_ctx)
-               fw_core_handle_request(&ohci->card, speed, ack, timestamp,
-                                      ohci->request_generation,
-                                      length, ctx->buffer);
+               fw_core_handle_request(&ohci->card, &p);
        else
-               fw_core_handle_response(&ohci->card, speed, ack, timestamp,
-                                       length, ctx->buffer);
+               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);
@@ -265,12 +296,14 @@ static void ar_context_tasklet(unsigned long data)
        dma_sync_single_for_device(ohci->card.device, ctx->descriptor_bus,
                                   sizeof ctx->descriptor_bus, DMA_TO_DEVICE);
 
-       /* 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... */
+       /* 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");
 
-       reg_write(ctx->ohci, ctx->control_clear, CONTEXT_RUN);
        ar_context_run(ctx);
 }
 
@@ -323,15 +356,15 @@ do_packet_callbacks(struct fw_ohci *ohci, struct list_head *list)
        struct fw_packet *p, *next;
 
        list_for_each_entry_safe(p, next, list, link)
-               p->callback(p, &ohci->card, p->status);
+               p->callback(p, &ohci->card, p->ack);
 }
 
 static void
 complete_transmission(struct fw_packet *packet,
-                     int status, struct list_head *list)
+                     int ack, struct list_head *list)
 {
        list_move_tail(&packet->link, list);
-       packet->status = status;
+       packet->ack = ack;
 }
 
 /* This function prepares the first packet in the context queue for
@@ -354,7 +387,7 @@ at_context_setup_packet(struct at_context *ctx, struct list_head *list)
                                                     packet->payload_length,
                                                     DMA_TO_DEVICE);
                if (packet->payload_bus == 0) {
-                       complete_transmission(packet, -ENOMEM, list);
+                       complete_transmission(packet, RCODE_SEND_ERROR, list);
                        return;
                }
 
@@ -416,7 +449,7 @@ at_context_setup_packet(struct at_context *ctx, struct list_head *list)
                /* We dont return error codes from this function; all
                 * transmission errors are reported through the
                 * callback. */
-               complete_transmission(packet, -ESTALE, list);
+               complete_transmission(packet, RCODE_GENERATION, list);
        }
 }
 
@@ -462,26 +495,26 @@ static void at_context_tasklet(unsigned long data)
                switch (evt) {
                case OHCI1394_evt_timeout:
                        /* Async response transmit timed out. */
-                       complete_transmission(packet, -ETIMEDOUT, &list);
+                       complete_transmission(packet, RCODE_CANCELLED, &list);
                        break;
 
                case OHCI1394_evt_flushed:
                        /* The packet was flushed should give same
                         * error as when we try to use a stale
                         * generation count. */
-                       complete_transmission(packet, -ESTALE, &list);
+                       complete_transmission(packet,
+                                             RCODE_GENERATION, &list);
                        break;
 
                case OHCI1394_evt_missing_ack:
-                       /* This would be a higher level software
-                        * error, it is using a valid (current)
-                        * generation count, but the node is not on
-                        * the bus. */
-                       complete_transmission(packet, -ENODEV, &list);
+                       /* Using a valid (current) generation count,
+                        * but the node is not on the bus or not
+                        * sending acks. */
+                       complete_transmission(packet, RCODE_NO_ACK, &list);
                        break;
 
                default:
-                       complete_transmission(packet, -EIO, &list);
+                       complete_transmission(packet, RCODE_SEND_ERROR, &list);
                        break;
                }
        } else
@@ -517,18 +550,132 @@ at_context_init(struct at_context *ctx, struct fw_ohci *ohci, u32 control_set)
        return 0;
 }
 
+#define header_get_destination(q)      (((q) >> 16) & 0xffff)
+#define header_get_tcode(q)            (((q) >> 4) & 0x0f)
+#define header_get_offset_high(q)      (((q) >> 0) & 0xffff)
+#define header_get_data_length(q)      (((q) >> 16) & 0xffff)
+#define header_get_extended_tcode(q)   (((q) >> 0) & 0xffff)
+
+static void
+handle_local_rom(struct fw_ohci *ohci, struct fw_packet *packet, u32 csr)
+{
+       struct fw_packet response;
+       int tcode, length, i;
+
+       tcode = header_get_tcode(packet->header[0]);
+       if (TCODE_IS_BLOCK_PACKET(tcode))
+               length = header_get_data_length(packet->header[3]);
+       else
+               length = 4;
+
+       i = csr - CSR_CONFIG_ROM;
+       if (i + length > CONFIG_ROM_SIZE) {
+               fw_fill_response(&response, packet->header,
+                                RCODE_ADDRESS_ERROR, NULL, 0);
+       } else if (!TCODE_IS_READ_REQUEST(tcode)) {
+               fw_fill_response(&response, packet->header,
+                                RCODE_TYPE_ERROR, NULL, 0);
+       } else {
+               fw_fill_response(&response, packet->header, RCODE_COMPLETE,
+                                (void *) ohci->config_rom + i, length);
+       }
+
+       fw_core_handle_response(&ohci->card, &response);
+}
+
+static void
+handle_local_lock(struct fw_ohci *ohci, struct fw_packet *packet, u32 csr)
+{
+       struct fw_packet response;
+       int tcode, length, ext_tcode, sel;
+       __be32 *payload, lock_old;
+       u32 lock_arg, lock_data;
+
+       tcode = header_get_tcode(packet->header[0]);
+       length = header_get_data_length(packet->header[3]);
+       payload = packet->payload;
+       ext_tcode = header_get_extended_tcode(packet->header[3]);
+
+       if (tcode == TCODE_LOCK_REQUEST &&
+           ext_tcode == EXTCODE_COMPARE_SWAP && length == 8) {
+               lock_arg = be32_to_cpu(payload[0]);
+               lock_data = be32_to_cpu(payload[1]);
+       } else if (tcode == TCODE_READ_QUADLET_REQUEST) {
+               lock_arg = 0;
+               lock_data = 0;
+       } else {
+               fw_fill_response(&response, packet->header,
+                                RCODE_TYPE_ERROR, NULL, 0);
+               goto out;
+       }
+
+       sel = (csr - CSR_BUS_MANAGER_ID) / 4;
+       reg_write(ohci, OHCI1394_CSRData, lock_data);
+       reg_write(ohci, OHCI1394_CSRCompareData, lock_arg);
+       reg_write(ohci, OHCI1394_CSRControl, sel);
+
+       if (reg_read(ohci, OHCI1394_CSRControl) & 0x80000000)
+               lock_old = cpu_to_be32(reg_read(ohci, OHCI1394_CSRData));
+       else
+               fw_notify("swap not done yet\n");
+
+       fw_fill_response(&response, packet->header,
+                        RCODE_COMPLETE, &lock_old, sizeof lock_old);
+ out:
+       fw_core_handle_response(&ohci->card, &response);
+}
+
+static void
+handle_local_request(struct at_context *ctx, struct fw_packet *packet)
+{
+       u64 offset;
+       u32 csr;
+
+       packet->ack = ACK_PENDING;
+       packet->callback(packet, &ctx->ohci->card, packet->ack);
+
+       offset =
+               ((unsigned long long)
+                header_get_offset_high(packet->header[1]) << 32) |
+               packet->header[2];
+       csr = offset - CSR_REGISTER_BASE;
+
+       /* Handle config rom reads. */
+       if (csr >= CSR_CONFIG_ROM && csr < CSR_CONFIG_ROM_END)
+               handle_local_rom(ctx->ohci, packet, csr);
+       else switch (csr) {
+       case CSR_BUS_MANAGER_ID:
+       case CSR_BANDWIDTH_AVAILABLE:
+       case CSR_CHANNELS_AVAILABLE_HI:
+       case CSR_CHANNELS_AVAILABLE_LO:
+               handle_local_lock(ctx->ohci, packet, csr);
+               break;
+       default:
+               if (ctx == &ctx->ohci->at_request_ctx)
+                       fw_core_handle_request(&ctx->ohci->card, packet);
+               else
+                       fw_core_handle_response(&ctx->ohci->card, packet);
+               break;
+       }
+}
+
 static void
 at_context_transmit(struct at_context *ctx, struct fw_packet *packet)
 {
        LIST_HEAD(list);
        unsigned long flags;
-       int was_empty;
 
        spin_lock_irqsave(&ctx->ohci->lock, flags);
 
-       was_empty = list_empty(&ctx->list);
+       if (header_get_destination(packet->header[0]) == ctx->ohci->node_id &&
+           ctx->ohci->generation == packet->generation) {
+               spin_unlock_irqrestore(&ctx->ohci->lock, flags);
+               handle_local_request(ctx, packet);
+               return;
+       }
+
        list_add_tail(&packet->link, &ctx->list);
-       if (was_empty)
+       if (ctx->list.next == &packet->link)
                at_context_setup_packet(ctx, &list);
 
        spin_unlock_irqrestore(&ctx->ohci->lock, flags);
@@ -539,7 +686,7 @@ at_context_transmit(struct at_context *ctx, struct fw_packet *packet)
 static void bus_reset_tasklet(unsigned long data)
 {
        struct fw_ohci *ohci = (struct fw_ohci *)data;
-       int self_id_count, i, j, reg, node_id;
+       int self_id_count, i, j, reg;
        int generation, new_generation;
        unsigned long flags;
 
@@ -548,7 +695,7 @@ static void bus_reset_tasklet(unsigned long data)
                fw_error("node ID not valid, new bus reset in progress\n");
                return;
        }
-       node_id = reg & 0xffff;
+       ohci->node_id = reg & 0xffff;
 
        /* The count in the SelfIDCount register is the number of
         * bytes in the self ID receive buffer.  Since we also receive
@@ -617,7 +764,7 @@ static void bus_reset_tasklet(unsigned long data)
 
        spin_unlock_irqrestore(&ohci->lock, flags);
 
-       fw_core_handle_bus_reset(&ohci->card, node_id, generation,
+       fw_core_handle_bus_reset(&ohci->card, ohci->node_id, generation,
                                 self_id_count, ohci->self_id_buffer);
 }