diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2015-02-14 15:18:52 -0500 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2015-03-16 03:27:08 -0400 |
commit | 36480c259fd087c6c24f0046addf8d5c0380889a (patch) | |
tree | fef736be4ab1c2df33d15715badd6b5ff04c2285 | |
parent | 98a69ec5009104088cdad3506836e2d042173f64 (diff) | |
download | rockbox-36480c259fd087c6c24f0046addf8d5c0380889a.tar.gz rockbox-36480c259fd087c6c24f0046addf8d5c0380889a.zip |
Fix a race condition in as3525 I2C driver caused by stacked ISRs.
It was possible for interrupts of higher priority than the current IRQ
level to attempt to restart the interface while it was still active on
a transfer. The list modification also wasn't protected within the I2C
ISR itself.
Change-Id: I70635c307a1443bba6801c588cf1efde299db9a4
-rw-r--r-- | firmware/target/arm/as3525/ascodec-as3525.c | 42 |
1 files changed, 25 insertions, 17 deletions
diff --git a/firmware/target/arm/as3525/ascodec-as3525.c b/firmware/target/arm/as3525/ascodec-as3525.c index be81178859..634648afe4 100644 --- a/firmware/target/arm/as3525/ascodec-as3525.c +++ b/firmware/target/arm/as3525/ascodec-as3525.c | |||
@@ -159,21 +159,12 @@ static void ascodec_finish_req(struct ascodec_request *req) | |||
159 | */ | 159 | */ |
160 | while (i2c_busy()); | 160 | while (i2c_busy()); |
161 | 161 | ||
162 | /* disable clock */ | ||
163 | bitclr32(&CGU_PERI, CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE); | ||
164 | |||
165 | req->status = 1; | 162 | req->status = 1; |
166 | 163 | ||
167 | if (req->callback) { | 164 | if (req->callback) { |
168 | req->callback(req->data, req_data_ptr - req->data); | 165 | req->callback(req->data, req_data_ptr - req->data); |
169 | } | 166 | } |
170 | semaphore_release(&req->complete); | 167 | semaphore_release(&req->complete); |
171 | |||
172 | req_head = req->next; | ||
173 | req->next = NULL; | ||
174 | if (req_head == NULL) | ||
175 | req_tail = NULL; | ||
176 | |||
177 | } | 168 | } |
178 | 169 | ||
179 | static int ascodec_continue_req(struct ascodec_request *req, int irq_status) | 170 | static int ascodec_continue_req(struct ascodec_request *req, int irq_status) |
@@ -226,13 +217,12 @@ static void ascodec_start_req(struct ascodec_request *req) | |||
226 | 217 | ||
227 | void INT_I2C_AUDIO(void) | 218 | void INT_I2C_AUDIO(void) |
228 | { | 219 | { |
229 | int irq_status = I2C2_MIS; | 220 | struct ascodec_request *req = req_head; |
230 | int status = REQ_FINISHED; | ||
231 | 221 | ||
232 | if (req_head != NULL) | 222 | int irq_status = I2C2_MIS; |
233 | status = ascodec_continue_req(req_head, irq_status); | 223 | int status = ascodec_continue_req(req, irq_status); |
234 | 224 | ||
235 | I2C2_INT_CLR |= irq_status; /* clear interrupt status */ | 225 | I2C2_INT_CLR = irq_status; /* clear interrupt status */ |
236 | 226 | ||
237 | if (status != REQ_UNFINISHED) { | 227 | if (status != REQ_UNFINISHED) { |
238 | /* mask rx/tx interrupts */ | 228 | /* mask rx/tx interrupts */ |
@@ -240,15 +230,31 @@ void INT_I2C_AUDIO(void) | |||
240 | I2C2_IRQ_RXOVER|I2C2_IRQ_ACKTIMEO); | 230 | I2C2_IRQ_RXOVER|I2C2_IRQ_ACKTIMEO); |
241 | 231 | ||
242 | if (status == REQ_FINISHED) | 232 | if (status == REQ_FINISHED) |
243 | ascodec_finish_req(req_head); | 233 | ascodec_finish_req(req); |
234 | |||
235 | int oldlevel = disable_irq_save(); /* IRQs are stacked */ | ||
244 | 236 | ||
245 | /* | 237 | /* |
246 | * If status == REQ_RETRY, this will restart | 238 | * If status == REQ_RETRY, this will restart |
247 | * the request because we didn't remove it from | 239 | * the request because we didn't remove it from |
248 | * the request list | 240 | * the request list |
249 | */ | 241 | */ |
250 | if (req_head) | 242 | |
243 | if (status == REQ_FINISHED) { | ||
244 | req_head = req->next; | ||
245 | req->next = NULL; | ||
246 | } | ||
247 | |||
248 | if (req_head == NULL) { | ||
249 | req_tail = NULL; | ||
250 | |||
251 | /* disable clock */ | ||
252 | bitclr32(&CGU_PERI, CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE); | ||
253 | } else { | ||
251 | ascodec_start_req(req_head); | 254 | ascodec_start_req(req_head); |
255 | } | ||
256 | |||
257 | restore_irq(oldlevel); | ||
252 | } | 258 | } |
253 | } | 259 | } |
254 | 260 | ||
@@ -278,7 +284,7 @@ void ascodec_init(void) | |||
278 | I2C2_CNTRL = I2C2_CNTRL_DEFAULT; | 284 | I2C2_CNTRL = I2C2_CNTRL_DEFAULT; |
279 | 285 | ||
280 | I2C2_IMR = 0x00; /* disable interrupts */ | 286 | I2C2_IMR = 0x00; /* disable interrupts */ |
281 | I2C2_INT_CLR |= I2C2_RIS; /* clear interrupt status */ | 287 | I2C2_INT_CLR = I2C2_RIS; /* clear interrupt status */ |
282 | VIC_INT_ENABLE = INTERRUPT_I2C_AUDIO; | 288 | VIC_INT_ENABLE = INTERRUPT_I2C_AUDIO; |
283 | VIC_INT_ENABLE = INTERRUPT_AUDIO; | 289 | VIC_INT_ENABLE = INTERRUPT_AUDIO; |
284 | 290 | ||
@@ -338,6 +344,8 @@ static void ascodec_submit(struct ascodec_request *req) | |||
338 | 344 | ||
339 | static void ascodec_wait(struct ascodec_request *req) | 345 | static void ascodec_wait(struct ascodec_request *req) |
340 | { | 346 | { |
347 | /* NOTE: Only safe from thread context */ | ||
348 | |||
341 | if (irq_enabled()) { | 349 | if (irq_enabled()) { |
342 | semaphore_wait(&req->complete, TIMEOUT_BLOCK); | 350 | semaphore_wait(&req->complete, TIMEOUT_BLOCK); |
343 | return; | 351 | return; |