Skip to content

Commit 6b1ca4b

Browse files
takaswietiwai
authored andcommitted
ALSA: fireworks: accessing to user space outside spinlock
In hwdep interface of fireworks driver, accessing to user space is in a critical section with disabled local interrupt. Depending on architecture, accessing to user space can cause page fault exception. Then local processor stores machine status and handles the synchronous event. A handler corresponding to the event can call task scheduler to wait for preparing pages. In a case of usage of single core processor, the state to disable local interrupt is worse because it don't handle usual interrupts from hardware. This commit fixes this bug, performing the accessing outside spinlock. This commit also gives up counting the number of queued response messages to simplify ring-buffer management. Reported-by: Vaishali Thakkar <[email protected]> Cc: [email protected] Fixes: 555e8a8('ALSA: fireworks: Add command/response functionality into hwdep interface') Signed-off-by: Takashi Sakamoto <[email protected]> Signed-off-by: Takashi Iwai <[email protected]>
1 parent 04b2d9c commit 6b1ca4b

File tree

4 files changed

+56
-25
lines changed

4 files changed

+56
-25
lines changed

sound/firewire/fireworks/fireworks.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ struct snd_efw {
108108
u8 *resp_buf;
109109
u8 *pull_ptr;
110110
u8 *push_ptr;
111-
unsigned int resp_queues;
112111
};
113112

114113
int snd_efw_transaction_cmd(struct fw_unit *unit,

sound/firewire/fireworks/fireworks_hwdep.c

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
2525
{
2626
unsigned int length, till_end, type;
2727
struct snd_efw_transaction *t;
28+
u8 *pull_ptr;
2829
long count = 0;
2930

3031
if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))
@@ -38,8 +39,17 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
3839
buf += sizeof(type);
3940

4041
/* write into buffer as many responses as possible */
41-
while (efw->resp_queues > 0) {
42-
t = (struct snd_efw_transaction *)(efw->pull_ptr);
42+
spin_lock_irq(&efw->lock);
43+
44+
/*
45+
* When another task reaches here during this task's access to user
46+
* space, it picks up current position in buffer and can read the same
47+
* series of responses.
48+
*/
49+
pull_ptr = efw->pull_ptr;
50+
51+
while (efw->push_ptr != pull_ptr) {
52+
t = (struct snd_efw_transaction *)(pull_ptr);
4353
length = be32_to_cpu(t->length) * sizeof(__be32);
4454

4555
/* confirm enough space for this response */
@@ -49,41 +59,57 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
4959
/* copy from ring buffer to user buffer */
5060
while (length > 0) {
5161
till_end = snd_efw_resp_buf_size -
52-
(unsigned int)(efw->pull_ptr - efw->resp_buf);
62+
(unsigned int)(pull_ptr - efw->resp_buf);
5363
till_end = min_t(unsigned int, length, till_end);
5464

55-
if (copy_to_user(buf, efw->pull_ptr, till_end))
65+
spin_unlock_irq(&efw->lock);
66+
67+
if (copy_to_user(buf, pull_ptr, till_end))
5668
return -EFAULT;
5769

58-
efw->pull_ptr += till_end;
59-
if (efw->pull_ptr >= efw->resp_buf +
60-
snd_efw_resp_buf_size)
61-
efw->pull_ptr -= snd_efw_resp_buf_size;
70+
spin_lock_irq(&efw->lock);
71+
72+
pull_ptr += till_end;
73+
if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size)
74+
pull_ptr -= snd_efw_resp_buf_size;
6275

6376
length -= till_end;
6477
buf += till_end;
6578
count += till_end;
6679
remained -= till_end;
6780
}
68-
69-
efw->resp_queues--;
7081
}
7182

83+
/*
84+
* All of tasks can read from the buffer nearly simultaneously, but the
85+
* last position for each task is different depending on the length of
86+
* given buffer. Here, for simplicity, a position of buffer is set by
87+
* the latest task. It's better for a listening application to allow one
88+
* thread to read from the buffer. Unless, each task can read different
89+
* sequence of responses depending on variation of buffer length.
90+
*/
91+
efw->pull_ptr = pull_ptr;
92+
93+
spin_unlock_irq(&efw->lock);
94+
7295
return count;
7396
}
7497

7598
static long
7699
hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,
77100
loff_t *offset)
78101
{
79-
union snd_firewire_event event;
102+
union snd_firewire_event event = {
103+
.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
104+
};
80105

81-
memset(&event, 0, sizeof(event));
106+
spin_lock_irq(&efw->lock);
82107

83-
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
84108
event.lock_status.status = (efw->dev_lock_count > 0);
85109
efw->dev_lock_changed = false;
86110

111+
spin_unlock_irq(&efw->lock);
112+
87113
count = min_t(long, count, sizeof(event.lock_status));
88114

89115
if (copy_to_user(buf, &event, count))
@@ -98,26 +124,33 @@ hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
98124
{
99125
struct snd_efw *efw = hwdep->private_data;
100126
DEFINE_WAIT(wait);
127+
bool dev_lock_changed;
128+
bool queued;
101129

102130
spin_lock_irq(&efw->lock);
103131

104-
while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) {
132+
dev_lock_changed = efw->dev_lock_changed;
133+
queued = efw->push_ptr != efw->pull_ptr;
134+
135+
while (!dev_lock_changed && !queued) {
105136
prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
106137
spin_unlock_irq(&efw->lock);
107138
schedule();
108139
finish_wait(&efw->hwdep_wait, &wait);
109140
if (signal_pending(current))
110141
return -ERESTARTSYS;
111142
spin_lock_irq(&efw->lock);
143+
dev_lock_changed = efw->dev_lock_changed;
144+
queued = efw->push_ptr != efw->pull_ptr;
112145
}
113146

114-
if (efw->dev_lock_changed)
147+
spin_unlock_irq(&efw->lock);
148+
149+
if (dev_lock_changed)
115150
count = hwdep_read_locked(efw, buf, count, offset);
116-
else if (efw->resp_queues > 0)
151+
else if (queued)
117152
count = hwdep_read_resp_buf(efw, buf, count, offset);
118153

119-
spin_unlock_irq(&efw->lock);
120-
121154
return count;
122155
}
123156

@@ -160,7 +193,7 @@ hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
160193
poll_wait(file, &efw->hwdep_wait, wait);
161194

162195
spin_lock_irq(&efw->lock);
163-
if (efw->dev_lock_changed || (efw->resp_queues > 0))
196+
if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)
164197
events = POLLIN | POLLRDNORM;
165198
else
166199
events = 0;

sound/firewire/fireworks/fireworks_proc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ proc_read_queues_state(struct snd_info_entry *entry,
188188
else
189189
consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr);
190190

191-
snd_iprintf(buffer, "%d %d/%d\n",
192-
efw->resp_queues, consumed, snd_efw_resp_buf_size);
191+
snd_iprintf(buffer, "%d/%d\n",
192+
consumed, snd_efw_resp_buf_size);
193193
}
194194

195195
static void

sound/firewire/fireworks/fireworks_transaction.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@ copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode)
121121
size_t capacity, till_end;
122122
struct snd_efw_transaction *t;
123123

124-
spin_lock_irq(&efw->lock);
125-
126124
t = (struct snd_efw_transaction *)data;
127125
length = min_t(size_t, be32_to_cpu(t->length) * sizeof(u32), length);
128126

127+
spin_lock_irq(&efw->lock);
128+
129129
if (efw->push_ptr < efw->pull_ptr)
130130
capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr);
131131
else
@@ -155,7 +155,6 @@ copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode)
155155
}
156156

157157
/* for hwdep */
158-
efw->resp_queues++;
159158
wake_up(&efw->hwdep_wait);
160159

161160
*rcode = RCODE_COMPLETE;

0 commit comments

Comments
 (0)