summaryrefslogtreecommitdiffstats
path: root/sound/firewire/tascam/tascam-hwdep.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/firewire/tascam/tascam-hwdep.c')
-rw-r--r--sound/firewire/tascam/tascam-hwdep.c65
1 files changed, 63 insertions, 2 deletions
diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c
index 8f34cede2e9f..0414abf5daa8 100644
--- a/sound/firewire/tascam/tascam-hwdep.c
+++ b/sound/firewire/tascam/tascam-hwdep.c
@@ -35,6 +35,65 @@ static long tscm_hwdep_read_locked(struct snd_tscm *tscm, char __user *buf,
return count;
}
+static long tscm_hwdep_read_queue(struct snd_tscm *tscm, char __user *buf,
+ long remained, loff_t *offset)
+{
+ char __user *pos = buf;
+ unsigned int type = SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL;
+ struct snd_firewire_tascam_change *entries = tscm->queue;
+ long count;
+
+ // At least, one control event can be copied.
+ if (remained < sizeof(type) + sizeof(*entries)) {
+ spin_unlock_irq(&tscm->lock);
+ return -EINVAL;
+ }
+
+ // Copy the type field later.
+ count = sizeof(type);
+ remained -= sizeof(type);
+ pos += sizeof(type);
+
+ while (true) {
+ unsigned int head_pos;
+ unsigned int tail_pos;
+ unsigned int length;
+
+ if (tscm->pull_pos == tscm->push_pos)
+ break;
+ else if (tscm->pull_pos < tscm->push_pos)
+ tail_pos = tscm->push_pos;
+ else
+ tail_pos = SND_TSCM_QUEUE_COUNT;
+ head_pos = tscm->pull_pos;
+
+ length = (tail_pos - head_pos) * sizeof(*entries);
+ if (remained < length)
+ length = rounddown(remained, sizeof(*entries));
+ if (length == 0)
+ break;
+
+ spin_unlock_irq(&tscm->lock);
+ if (copy_to_user(pos, &entries[head_pos], length))
+ return -EFAULT;
+
+ spin_lock_irq(&tscm->lock);
+
+ tscm->pull_pos = tail_pos % SND_TSCM_QUEUE_COUNT;
+
+ count += length;
+ remained -= length;
+ pos += length;
+ }
+
+ spin_unlock_irq(&tscm->lock);
+
+ if (copy_to_user(buf, &type, sizeof(type)))
+ return -EFAULT;
+
+ return count;
+}
+
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
@@ -43,7 +102,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
spin_lock_irq(&tscm->lock);
- while (!tscm->dev_lock_changed) {
+ while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) {
prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&tscm->lock);
schedule();
@@ -56,6 +115,8 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
// NOTE: The acquired lock should be released in callee side.
if (tscm->dev_lock_changed) {
count = tscm_hwdep_read_locked(tscm, buf, count, offset);
+ } else if (tscm->push_pos != tscm->pull_pos) {
+ count = tscm_hwdep_read_queue(tscm, buf, count, offset);
} else {
spin_unlock_irq(&tscm->lock);
count = 0;
@@ -73,7 +134,7 @@ static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
poll_wait(file, &tscm->hwdep_wait, wait);
spin_lock_irq(&tscm->lock);
- if (tscm->dev_lock_changed)
+ if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos)
events = EPOLLIN | EPOLLRDNORM;
else
events = 0;