#define _UAPI_SOUND_FIREWIRE_H_INCLUDED
#include <linux/ioctl.h>
+#include <linux/types.h>
/* events can be read() from the hwdep device */
#define SNDRV_FIREWIRE_EVENT_LOCK_STATUS 0x000010cc
#define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e
+#define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475
struct snd_firewire_event_common {
unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
unsigned int notification; /* DICE-specific bits */
};
+#define SND_EFW_TRANSACTION_USER_SEQNUM_MAX ((__u32)((__u16)~0) - 1)
+/* each field should be in big endian */
+struct snd_efw_transaction {
+ __be32 length;
+ __be32 version;
+ __be32 seqnum;
+ __be32 category;
+ __be32 command;
+ __be32 status;
+ __be32 params[0];
+};
+struct snd_firewire_event_efw_response {
+ unsigned int type;
+ __be32 response[0]; /* some responses */
+};
+
union snd_firewire_event {
struct snd_firewire_event_common common;
struct snd_firewire_event_lock_status lock_status;
struct snd_firewire_event_dice_notification dice_notification;
+ struct snd_firewire_event_efw_response efw_response;
};
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+unsigned int snd_efw_resp_buf_size = 1024;
+bool snd_efw_resp_buf_debug = false;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "card index");
MODULE_PARM_DESC(id, "ID string");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "enable Fireworks sound card");
+module_param_named(resp_buf_size, snd_efw_resp_buf_size, uint, 0444);
+MODULE_PARM_DESC(resp_buf_size,
+ "response buffer size (max 4096, default 1024)");
+module_param_named(resp_buf_debug, snd_efw_resp_buf_debug, bool, 0444);
+MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer");
static DEFINE_MUTEX(devices_mutex);
static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
}
mutex_destroy(&efw->mutex);
+ kfree(efw->resp_buf);
}
static int
spin_lock_init(&efw->lock);
init_waitqueue_head(&efw->hwdep_wait);
+ /* prepare response buffer */
+ snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size,
+ SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U);
+ efw->resp_buf = kzalloc(snd_efw_resp_buf_size, GFP_KERNEL);
+ if (efw->resp_buf == NULL) {
+ err = -ENOMEM;
+ goto error;
+ }
+ efw->pull_ptr = efw->push_ptr = efw->resp_buf;
+ snd_efw_transaction_add_instance(efw);
+
err = get_hardware_info(efw);
if (err < 0)
goto error;
mutex_unlock(&devices_mutex);
return err;
error:
+ snd_efw_transaction_remove_instance(efw);
mutex_unlock(&devices_mutex);
snd_card_free(card);
return err;
struct snd_efw *efw = dev_get_drvdata(&unit->device);
snd_efw_stream_destroy_duplex(efw);
+ snd_efw_transaction_remove_instance(efw);
snd_card_disconnect(efw->card);
snd_card_free_when_closed(efw->card);
*/
#define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U
+extern unsigned int snd_efw_resp_buf_size;
+extern bool snd_efw_resp_buf_debug;
+
struct snd_efw_phys_grp {
u8 type; /* see enum snd_efw_grp_type */
u8 count;
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
-};
-struct snd_efw_transaction {
- __be32 length;
- __be32 version;
- __be32 seqnum;
- __be32 category;
- __be32 command;
- __be32 status;
- __be32 params[0];
+ /* response queue */
+ u8 *resp_buf;
+ u8 *pull_ptr;
+ u8 *push_ptr;
+ unsigned int resp_queues;
};
+
+int snd_efw_transaction_cmd(struct fw_unit *unit,
+ const void *cmd, unsigned int size);
int snd_efw_transaction_run(struct fw_unit *unit,
const void *cmd, unsigned int cmd_size,
void *resp, unsigned int resp_size);
int snd_efw_transaction_register(void);
void snd_efw_transaction_unregister(void);
void snd_efw_transaction_bus_reset(struct fw_unit *unit);
+void snd_efw_transaction_add_instance(struct snd_efw *efw);
+void snd_efw_transaction_remove_instance(struct snd_efw *efw);
struct snd_efw_hwinfo {
u32 flags;
* Information commands. But this module don't use them.
*/
-#define EFW_TRANSACTION_SEQNUM_MAX ((u32)~0)
+#define KERNEL_SEQNUM_MIN (SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 2)
+#define KERNEL_SEQNUM_MAX ((u32)~0)
/* for clock source and sampling rate */
struct efc_clock {
/* to keep consistency of sequence number */
spin_lock(&efw->lock);
- if (efw->seqnum + 2 >= EFW_TRANSACTION_SEQNUM_MAX)
- efw->seqnum = 0;
+ if ((efw->seqnum < KERNEL_SEQNUM_MIN) ||
+ (efw->seqnum >= KERNEL_SEQNUM_MAX - 2))
+ efw->seqnum = KERNEL_SEQNUM_MIN;
else
efw->seqnum += 2;
seqnum = efw->seqnum;
*/
/*
- * This codes have three functionalities.
+ * This codes have five functionalities.
*
* 1.get information about firewire node
* 2.get notification about starting/stopping stream
* 3.lock/unlock streaming
+ * 4.transmit command of EFW transaction
+ * 5.receive response of EFW transaction
+ *
*/
#include "fireworks.h"
static long
-hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
+hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
+ loff_t *offset)
+{
+ unsigned int length, till_end, type;
+ struct snd_efw_transaction *t;
+ long count = 0;
+
+ if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))
+ return -ENOSPC;
+
+ /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */
+ type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;
+ if (copy_to_user(buf, &type, sizeof(type)))
+ return -EFAULT;
+ remained -= sizeof(type);
+ buf += sizeof(type);
+
+ /* write into buffer as many responses as possible */
+ while (efw->resp_queues > 0) {
+ t = (struct snd_efw_transaction *)(efw->pull_ptr);
+ length = be32_to_cpu(t->length) * sizeof(__be32);
+
+ /* confirm enough space for this response */
+ if (remained < length)
+ break;
+
+ /* copy from ring buffer to user buffer */
+ while (length > 0) {
+ till_end = snd_efw_resp_buf_size -
+ (unsigned int)(efw->pull_ptr - efw->resp_buf);
+ till_end = min_t(unsigned int, length, till_end);
+
+ if (copy_to_user(buf, efw->pull_ptr, till_end))
+ return -EFAULT;
+
+ efw->pull_ptr += till_end;
+ if (efw->pull_ptr >= efw->resp_buf +
+ snd_efw_resp_buf_size)
+ efw->pull_ptr = efw->resp_buf;
+
+ length -= till_end;
+ buf += till_end;
+ count += till_end;
+ remained -= till_end;
+ }
+
+ efw->resp_queues--;
+ }
+
+ return count;
+}
+
+static long
+hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,
+ loff_t *offset)
+{
+ union snd_firewire_event event;
+
+ memset(&event, 0, sizeof(event));
+
+ event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+ event.lock_status.status = (efw->dev_lock_count > 0);
+ efw->dev_lock_changed = false;
+
+ count = min_t(long, count, sizeof(event.lock_status));
+
+ if (copy_to_user(buf, &event, count))
+ return -EFAULT;
+
+ return count;
+}
+
+static long
+hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_efw *efw = hwdep->private_data;
DEFINE_WAIT(wait);
- union snd_firewire_event event;
spin_lock_irq(&efw->lock);
- while (!efw->dev_lock_changed) {
+ while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) {
prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&efw->lock);
schedule();
spin_lock_irq(&efw->lock);
}
- memset(&event, 0, sizeof(event));
- if (efw->dev_lock_changed) {
- event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
- event.lock_status.status = (efw->dev_lock_count > 0);
- efw->dev_lock_changed = false;
-
- count = min_t(long, count, sizeof(event.lock_status));
- }
+ if (efw->dev_lock_changed)
+ count = hwdep_read_locked(efw, buf, count, offset);
+ else if (efw->resp_queues > 0)
+ count = hwdep_read_resp_buf(efw, buf, count, offset);
spin_unlock_irq(&efw->lock);
- if (copy_to_user(buf, &event, count))
- return -EFAULT;
+ return count;
+}
+static long
+hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
+ loff_t *offset)
+{
+ struct snd_efw *efw = hwdep->private_data;
+ u32 seqnum;
+ u8 *buf;
+
+ if (count < sizeof(struct snd_efw_transaction) ||
+ SND_EFW_RESPONSE_MAXIMUM_BYTES < count)
+ return -EINVAL;
+
+ buf = memdup_user(data, count);
+ if (IS_ERR(buf))
+ return PTR_ERR(data);
+
+ /* check seqnum is not for kernel-land */
+ seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);
+ if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {
+ count = -EINVAL;
+ goto end;
+ }
+
+ if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)
+ count = -EIO;
+end:
+ kfree(buf);
return count;
}
poll_wait(file, &efw->hwdep_wait, wait);
spin_lock_irq(&efw->lock);
- if (efw->dev_lock_changed)
+ if (efw->dev_lock_changed || (efw->resp_queues > 0))
events = POLLIN | POLLRDNORM;
else
events = 0;
spin_unlock_irq(&efw->lock);
- return events;
+ return events | POLLOUT;
}
static int
static const struct snd_hwdep_ops hwdep_ops = {
.read = hwdep_read,
+ .write = hwdep_write,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
kfree(meters);
}
+static void
+proc_read_queues_state(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_efw *efw = entry->private_data;
+ unsigned int consumed;
+
+ if (efw->pull_ptr > efw->push_ptr)
+ consumed = snd_efw_resp_buf_size -
+ (unsigned int)(efw->pull_ptr - efw->push_ptr);
+ else
+ consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr);
+
+ snd_iprintf(buffer, "%d %d/%d\n",
+ efw->resp_queues, consumed, snd_efw_resp_buf_size);
+}
+
static void
add_node(struct snd_efw *efw, struct snd_info_entry *root, const char *name,
void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b))
add_node(efw, root, "clock", proc_read_clock);
add_node(efw, root, "firmware", proc_read_hwinfo);
add_node(efw, root, "meters", proc_read_phys_meters);
+ add_node(efw, root, "queues", proc_read_queues_state);
}
#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);
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)
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;
}
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) {
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;