ALSA: fireworks: Add command/response functionality into hwdep interface
[firefly-linux-kernel-4.4.55.git] / sound / firewire / fireworks / fireworks_transaction.c
index aac91d8485d52738c89f04db58edcfa5346e17bb..81a65ebb5f71b37cb76d154cf6a7ad5819363b39 100644 (file)
@@ -38,6 +38,9 @@
 #define ERROR_DELAY_MS 5
 #define EFC_TIMEOUT_MS 125
 
+static DEFINE_SPINLOCK(instances_lock);
+static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
 static DEFINE_SPINLOCK(transaction_queues_lock);
 static LIST_HEAD(transaction_queues);
 
@@ -57,6 +60,14 @@ struct transaction_queue {
        wait_queue_head_t wait;
 };
 
+int snd_efw_transaction_cmd(struct fw_unit *unit,
+                           const void *cmd, unsigned int size)
+{
+       return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
+                                 MEMORY_SPACE_EFW_COMMAND,
+                                 (void *)cmd, size, 0);
+}
+
 int snd_efw_transaction_run(struct fw_unit *unit,
                            const void *cmd, unsigned int cmd_size,
                            void *resp, unsigned int resp_size)
@@ -78,9 +89,7 @@ int snd_efw_transaction_run(struct fw_unit *unit,
 
        tries = 0;
        do {
-               ret = snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
-                                        MEMORY_SPACE_EFW_COMMAND,
-                                        (void *)cmd, cmd_size, 0);
+               ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size);
                if (ret < 0)
                        break;
 
@@ -107,27 +116,92 @@ int snd_efw_transaction_run(struct fw_unit *unit,
 }
 
 static void
-efw_response(struct fw_card *card, struct fw_request *request,
-            int tcode, int destination, int source,
-            int generation, unsigned long long offset,
-            void *data, size_t length, void *callback_data)
+copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode)
 {
-       struct fw_device *device;
-       struct transaction_queue *t;
-       unsigned long flags;
-       int rcode;
-       u32 seqnum;
+       size_t capacity, till_end;
+       struct snd_efw_transaction *t;
 
-       rcode = RCODE_TYPE_ERROR;
-       if (length < sizeof(struct snd_efw_transaction)) {
-               rcode = RCODE_DATA_ERROR;
-               goto end;
-       } else if (offset != MEMORY_SPACE_EFW_RESPONSE) {
-               rcode = RCODE_ADDRESS_ERROR;
+       spin_lock_irq(&efw->lock);
+
+       t = (struct snd_efw_transaction *)data;
+       length = min_t(size_t, t->length * sizeof(t->length), length);
+
+       if (efw->push_ptr < efw->pull_ptr)
+               capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr);
+       else
+               capacity = snd_efw_resp_buf_size -
+                          (unsigned int)(efw->push_ptr - efw->pull_ptr);
+
+       /* confirm enough space for this response */
+       if (capacity < length) {
+               *rcode = RCODE_CONFLICT_ERROR;
                goto end;
        }
 
-       seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum);
+       /* copy to ring buffer */
+       while (length > 0) {
+               till_end = snd_efw_resp_buf_size -
+                          (unsigned int)(efw->push_ptr - efw->resp_buf);
+               till_end = min_t(unsigned int, length, till_end);
+
+               memcpy(efw->push_ptr, data, till_end);
+
+               efw->push_ptr += till_end;
+               if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size)
+                       efw->push_ptr = efw->resp_buf;
+
+               length -= till_end;
+               data += till_end;
+       }
+
+       /* for hwdep */
+       efw->resp_queues++;
+       wake_up(&efw->hwdep_wait);
+
+       *rcode = RCODE_COMPLETE;
+end:
+       spin_unlock_irq(&efw->lock);
+}
+
+static void
+handle_resp_for_user(struct fw_card *card, int generation, int source,
+                    void *data, size_t length, int *rcode)
+{
+       struct fw_device *device;
+       struct snd_efw *efw;
+       unsigned int i;
+
+       spin_lock_irq(&instances_lock);
+
+       for (i = 0; i < SNDRV_CARDS; i++) {
+               efw = instances[i];
+               if (efw == NULL)
+                       continue;
+               device = fw_parent_device(efw->unit);
+               if ((device->card != card) ||
+                   (device->generation != generation))
+                       continue;
+               smp_rmb();      /* node id vs. generation */
+               if (device->node_id != source)
+                       continue;
+
+               break;
+       }
+       if (i == SNDRV_CARDS)
+               goto end;
+
+       copy_resp_to_buf(efw, data, length, rcode);
+end:
+       spin_unlock_irq(&instances_lock);
+}
+
+static void
+handle_resp_for_kernel(struct fw_card *card, int generation, int source,
+                      void *data, size_t length, int *rcode, u32 seqnum)
+{
+       struct fw_device *device;
+       struct transaction_queue *t;
+       unsigned long flags;
 
        spin_lock_irqsave(&transaction_queues_lock, flags);
        list_for_each_entry(t, &transaction_queues, list) {
@@ -144,14 +218,76 @@ efw_response(struct fw_card *card, struct fw_request *request,
                        t->size = min_t(unsigned int, length, t->size);
                        memcpy(t->buf, data, t->size);
                        wake_up(&t->wait);
-                       rcode = RCODE_COMPLETE;
+                       *rcode = RCODE_COMPLETE;
                }
        }
        spin_unlock_irqrestore(&transaction_queues_lock, flags);
+}
+
+static void
+efw_response(struct fw_card *card, struct fw_request *request,
+            int tcode, int destination, int source,
+            int generation, unsigned long long offset,
+            void *data, size_t length, void *callback_data)
+{
+       int rcode, dummy;
+       u32 seqnum;
+
+       rcode = RCODE_TYPE_ERROR;
+       if (length < sizeof(struct snd_efw_transaction)) {
+               rcode = RCODE_DATA_ERROR;
+               goto end;
+       } else if (offset != MEMORY_SPACE_EFW_RESPONSE) {
+               rcode = RCODE_ADDRESS_ERROR;
+               goto end;
+       }
+
+       seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum);
+       if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) {
+               handle_resp_for_kernel(card, generation, source,
+                                      data, length, &rcode, seqnum);
+               if (snd_efw_resp_buf_debug)
+                       handle_resp_for_user(card, generation, source,
+                                            data, length, &dummy);
+       } else {
+               handle_resp_for_user(card, generation, source,
+                                    data, length, &rcode);
+       }
 end:
        fw_send_response(card, request, rcode);
 }
 
+void snd_efw_transaction_add_instance(struct snd_efw *efw)
+{
+       unsigned int i;
+
+       spin_lock_irq(&instances_lock);
+
+       for (i = 0; i < SNDRV_CARDS; i++) {
+               if (instances[i] != NULL)
+                       continue;
+               instances[i] = efw;
+               break;
+       }
+
+       spin_unlock_irq(&instances_lock);
+}
+
+void snd_efw_transaction_remove_instance(struct snd_efw *efw)
+{
+       unsigned int i;
+
+       spin_lock_irq(&instances_lock);
+
+       for (i = 0; i < SNDRV_CARDS; i++) {
+               if (instances[i] != efw)
+                       continue;
+               instances[i] = NULL;
+       }
+
+       spin_unlock_irq(&instances_lock);
+}
+
 void snd_efw_transaction_bus_reset(struct fw_unit *unit)
 {
        struct transaction_queue *t;