]>
Commit | Line | Data |
---|---|---|
635d2b00 GKH |
1 | /* |
2 | * --------------------------------------------------------------------------- | |
3 | * FILE: bh.c | |
4 | * | |
5 | * PURPOSE: | |
6 | * Provides an implementation for the driver bottom-half. | |
7 | * It is part of the porting exercise in Linux. | |
8 | * | |
9 | * Copyright (C) 2005-2009 by Cambridge Silicon Radio Ltd. | |
10 | * | |
11 | * Refer to LICENSE.txt included with this source code for details on | |
12 | * the license terms. | |
13 | * | |
14 | * --------------------------------------------------------------------------- | |
15 | */ | |
16 | #include "csr_wifi_hip_unifi.h" | |
17 | #include "unifi_priv.h" | |
8bd75c77 | 18 | #include <linux/sched/rt.h> |
635d2b00 GKH |
19 | |
20 | /* | |
21 | * --------------------------------------------------------------------------- | |
22 | * uf_start_thread | |
23 | * | |
24 | * Helper function to start a new thread. | |
25 | * | |
26 | * Arguments: | |
27 | * priv Pointer to OS driver structure for the device. | |
28 | * thread Pointer to the thread object | |
29 | * func The thread function | |
30 | * | |
31 | * Returns: | |
32 | * 0 on success or else a Linux error code. | |
33 | * --------------------------------------------------------------------------- | |
34 | */ | |
cff9e59f DN |
35 | int uf_start_thread(unifi_priv_t *priv, |
36 | struct uf_thread *thread, int (*func)(void *)) | |
635d2b00 | 37 | { |
cff9e59f DN |
38 | if (thread->thread_task != NULL) { |
39 | unifi_error(priv, "%s thread already started\n", thread->name); | |
40 | return 0; | |
41 | } | |
635d2b00 | 42 | |
cff9e59f DN |
43 | /* Start the kernel thread that handles all h/w accesses. */ |
44 | thread->thread_task = kthread_run(func, priv, "%s", thread->name); | |
45 | if (IS_ERR(thread->thread_task)) | |
46 | return PTR_ERR(thread->thread_task); | |
635d2b00 | 47 | |
cff9e59f DN |
48 | /* Module parameter overides the thread priority */ |
49 | if (bh_priority != -1) { | |
50 | if (bh_priority >= 0 && bh_priority <= MAX_RT_PRIO) { | |
51 | struct sched_param param; | |
52 | priv->bh_thread.prio = bh_priority; | |
53 | unifi_trace(priv, UDBG1, | |
54 | "%s thread (RT) priority = %d\n", | |
55 | thread->name, bh_priority); | |
56 | param.sched_priority = bh_priority; | |
57 | sched_setscheduler(thread->thread_task, | |
58 | SCHED_FIFO, ¶m); | |
59 | } else if (bh_priority > MAX_RT_PRIO && | |
60 | bh_priority <= MAX_PRIO) { | |
61 | priv->bh_thread.prio = bh_priority; | |
62 | unifi_trace(priv, UDBG1, "%s thread priority = %d\n", | |
63 | thread->name, | |
64 | PRIO_TO_NICE(bh_priority)); | |
65 | set_user_nice(thread->thread_task, | |
66 | PRIO_TO_NICE(bh_priority)); | |
67 | } else { | |
68 | priv->bh_thread.prio = DEFAULT_PRIO; | |
69 | unifi_warning(priv, | |
70 | "%s thread unsupported (%d) priority\n", | |
71 | thread->name, bh_priority); | |
72 | } | |
73 | } else | |
74 | priv->bh_thread.prio = DEFAULT_PRIO; | |
75 | unifi_trace(priv, UDBG2, "Started %s thread\n", thread->name); | |
635d2b00 | 76 | |
cff9e59f | 77 | return 0; |
635d2b00 GKH |
78 | } /* uf_start_thread() */ |
79 | ||
80 | ||
81 | /* | |
82 | * --------------------------------------------------------------------------- | |
83 | * uf_stop_thread | |
84 | * | |
85 | * Helper function to stop a thread. | |
86 | * | |
87 | * Arguments: | |
88 | * priv Pointer to OS driver structure for the device. | |
89 | * thread Pointer to the thread object | |
90 | * | |
91 | * Returns: | |
92 | * | |
93 | * --------------------------------------------------------------------------- | |
94 | */ | |
cff9e59f | 95 | void uf_stop_thread(unifi_priv_t *priv, struct uf_thread *thread) |
635d2b00 | 96 | { |
f7523ab6 DN |
97 | if (!thread->thread_task) { |
98 | unifi_notice(priv, "%s thread is already stopped\n", | |
99 | thread->name); | |
100 | return; | |
101 | } | |
635d2b00 | 102 | |
f7523ab6 | 103 | unifi_trace(priv, UDBG2, "Stopping %s thread\n", thread->name); |
635d2b00 | 104 | |
f7523ab6 DN |
105 | kthread_stop(thread->thread_task); |
106 | thread->thread_task = NULL; | |
635d2b00 GKH |
107 | |
108 | } /* uf_stop_thread() */ | |
109 | ||
110 | ||
111 | ||
112 | /* | |
113 | * --------------------------------------------------------------------------- | |
114 | * uf_wait_for_thread_to_stop | |
115 | * | |
116 | * Helper function to wait until a thread is stopped. | |
117 | * | |
118 | * Arguments: | |
119 | * priv Pointer to OS driver structure for the device. | |
120 | * | |
121 | * Returns: | |
122 | * | |
123 | * --------------------------------------------------------------------------- | |
124 | */ | |
b29687fb | 125 | void |
635d2b00 GKH |
126 | uf_wait_for_thread_to_stop(unifi_priv_t *priv, struct uf_thread *thread) |
127 | { | |
b29687fb DN |
128 | /* |
129 | * kthread_stop() cannot handle the thread exiting while | |
130 | * kthread_should_stop() is false, so sleep until kthread_stop() | |
131 | * wakes us up | |
132 | */ | |
133 | unifi_trace(priv, UDBG2, "%s waiting for the stop signal.\n", | |
134 | thread->name); | |
135 | set_current_state(TASK_INTERRUPTIBLE); | |
136 | if (!kthread_should_stop()) { | |
137 | unifi_trace(priv, UDBG2, "%s schedule....\n", thread->name); | |
138 | schedule(); | |
139 | } | |
140 | ||
141 | thread->thread_task = NULL; | |
142 | unifi_trace(priv, UDBG2, "%s exiting....\n", thread->name); | |
635d2b00 GKH |
143 | } /* uf_wait_for_thread_to_stop() */ |
144 | ||
145 | ||
146 | /* | |
147 | * --------------------------------------------------------------------------- | |
148 | * handle_bh_error | |
149 | * | |
150 | * This function reports an error returned from the HIP core bottom-half. | |
151 | * Normally, implemented during the porting exercise, passing the error | |
152 | * to the SME using unifi_sys_wifi_off_ind(). | |
153 | * The SME will try to reset the device and go through | |
154 | * the initialisation of the UniFi. | |
155 | * | |
156 | * Arguments: | |
157 | * priv Pointer to OS driver structure for the device. | |
158 | * | |
159 | * Returns: | |
160 | * None. | |
161 | * --------------------------------------------------------------------------- | |
162 | */ | |
a6c42258 | 163 | static void |
635d2b00 GKH |
164 | handle_bh_error(unifi_priv_t *priv) |
165 | { | |
a6c42258 DN |
166 | netInterface_priv_t *interfacePriv; |
167 | u8 conf_param = CONFIG_IND_ERROR; | |
168 | u8 interfaceTag; | |
635d2b00 GKH |
169 | |
170 | ||
a6c42258 DN |
171 | /* Block unifi_run_bh() until the error has been handled. */ |
172 | priv->bh_thread.block_thread = 1; | |
635d2b00 | 173 | |
a6c42258 DN |
174 | /* Consider UniFi to be uninitialised */ |
175 | priv->init_progress = UNIFI_INIT_NONE; | |
635d2b00 | 176 | |
a6c42258 DN |
177 | /* Stop the network traffic */ |
178 | for (interfaceTag = 0; | |
179 | interfaceTag < CSR_WIFI_NUM_INTERFACES; interfaceTag++) { | |
180 | interfacePriv = priv->interfacePriv[interfaceTag]; | |
181 | if (interfacePriv->netdev_registered) | |
182 | netif_carrier_off(priv->netdev[interfaceTag]); | |
183 | } | |
635d2b00 GKH |
184 | |
185 | #ifdef CSR_NATIVE_LINUX | |
a6c42258 DN |
186 | /* Force any client waiting on an mlme_wait_for_reply() to abort. */ |
187 | uf_abort_mlme(priv); | |
635d2b00 | 188 | |
a6c42258 DN |
189 | /* Cancel any pending workqueue tasks */ |
190 | flush_workqueue(priv->unifi_workqueue); | |
635d2b00 GKH |
191 | |
192 | #endif /* CSR_NATIVE_LINUX */ | |
193 | ||
a6c42258 DN |
194 | unifi_error(priv, |
195 | "handle_bh_error: fatal error is reported to the SME.\n"); | |
196 | /* Notify the clients (SME or unifi_manager) for the error. */ | |
197 | ul_log_config_ind(priv, &conf_param, sizeof(u8)); | |
635d2b00 GKH |
198 | |
199 | } /* handle_bh_error() */ | |
200 | ||
201 | ||
202 | ||
203 | /* | |
204 | * --------------------------------------------------------------------------- | |
205 | * bh_thread_function | |
206 | * | |
207 | * All hardware access happens in this thread. | |
208 | * This means there is no need for locks on the hardware and we don't need | |
209 | * to worry about reentrancy with the SDIO library. | |
210 | * Provides and example implementation on how to call unifi_bh(), which | |
211 | * is part of the HIP core API. | |
212 | * | |
213 | * It processes the events generated by unifi_run_bh() to serialise calls | |
214 | * to unifi_bh(). It also demonstrates how the timeout parameter passed in | |
215 | * and returned from unifi_bh() needs to be handled. | |
216 | * | |
217 | * Arguments: | |
218 | * arg Pointer to OS driver structure for the device. | |
219 | * | |
220 | * Returns: | |
221 | * None. | |
222 | * | |
223 | * Notes: | |
224 | * When the bottom half of the driver needs to process signals, events, | |
225 | * or simply the host status (i.e sleep mode), it invokes unifi_run_bh(). | |
226 | * Since we need all SDIO transaction to be in a single thread, the | |
227 | * unifi_run_bh() will wake up this thread to process it. | |
228 | * | |
229 | * --------------------------------------------------------------------------- | |
230 | */ | |
f28c4075 | 231 | static int bh_thread_function(void *arg) |
635d2b00 | 232 | { |
f28c4075 CC |
233 | unifi_priv_t *priv = (unifi_priv_t *)arg; |
234 | CsrResult csrResult; | |
235 | long ret; | |
236 | u32 timeout, t; | |
237 | struct uf_thread *this_thread; | |
635d2b00 | 238 | |
f28c4075 | 239 | unifi_trace(priv, UDBG2, "bh_thread_function starting\n"); |
635d2b00 | 240 | |
f28c4075 | 241 | this_thread = &priv->bh_thread; |
635d2b00 | 242 | |
f28c4075 | 243 | t = timeout = 0; |
635d2b00 GKH |
244 | while (!kthread_should_stop()) { |
245 | /* wait until an error occurs, or we need to process something. */ | |
246 | unifi_trace(priv, UDBG3, "bh_thread goes to sleep.\n"); | |
247 | ||
248 | if (timeout > 0) { | |
249 | /* Convert t in ms to jiffies */ | |
250 | t = msecs_to_jiffies(timeout); | |
251 | ret = wait_event_interruptible_timeout(this_thread->wakeup_q, | |
252 | (this_thread->wakeup_flag && !this_thread->block_thread) || | |
253 | kthread_should_stop(), | |
254 | t); | |
255 | timeout = (ret > 0) ? jiffies_to_msecs(ret) : 0; | |
256 | } else { | |
257 | ret = wait_event_interruptible(this_thread->wakeup_q, | |
258 | (this_thread->wakeup_flag && !this_thread->block_thread) || | |
259 | kthread_should_stop()); | |
260 | } | |
261 | ||
262 | if (kthread_should_stop()) { | |
263 | unifi_trace(priv, UDBG2, "bh_thread: signalled to exit\n"); | |
264 | break; | |
265 | } | |
266 | ||
267 | if (ret < 0) { | |
268 | unifi_notice(priv, | |
269 | "bh_thread: wait_event returned %d, thread will exit\n", | |
270 | ret); | |
271 | uf_wait_for_thread_to_stop(priv, this_thread); | |
272 | break; | |
273 | } | |
274 | ||
275 | this_thread->wakeup_flag = 0; | |
276 | ||
277 | unifi_trace(priv, UDBG3, "bh_thread calls unifi_bh().\n"); | |
278 | ||
279 | CsrSdioClaim(priv->sdio); | |
280 | csrResult = unifi_bh(priv->card, &timeout); | |
281 | if(csrResult != CSR_RESULT_SUCCESS) { | |
282 | if (csrResult == CSR_WIFI_HIP_RESULT_NO_DEVICE) { | |
283 | CsrSdioRelease(priv->sdio); | |
284 | uf_wait_for_thread_to_stop(priv, this_thread); | |
285 | break; | |
286 | } | |
287 | /* Errors must be delivered to the error task */ | |
288 | handle_bh_error(priv); | |
289 | } | |
290 | CsrSdioRelease(priv->sdio); | |
291 | } | |
292 | ||
293 | /* | |
294 | * I would normally try to call csr_sdio_remove_irq() here to make sure | |
295 | * that we do not get any interrupts while this thread is not running. | |
296 | * However, the MMC/SDIO driver tries to kill its' interrupt thread. | |
297 | * The kernel threads implementation does not allow to kill threads | |
298 | * from a signalled to stop thread. | |
299 | * So, instead call csr_sdio_linux_remove_irq() always after calling | |
300 | * uf_stop_thread() to kill this thread. | |
301 | */ | |
302 | ||
303 | unifi_trace(priv, UDBG2, "bh_thread exiting....\n"); | |
304 | return 0; | |
305 | } /* bh_thread_function() */ | |
306 | ||
307 | ||
308 | /* | |
309 | * --------------------------------------------------------------------------- | |
310 | * uf_init_bh | |
311 | * | |
312 | * Helper function to start the bottom half of the driver. | |
313 | * All we need to do here is start the I/O bh thread. | |
314 | * | |
315 | * Arguments: | |
316 | * priv Pointer to OS driver structure for the device. | |
317 | * | |
318 | * Returns: | |
319 | * 0 on success or else a Linux error code. | |
320 | * --------------------------------------------------------------------------- | |
321 | */ | |
322 | int | |
323 | uf_init_bh(unifi_priv_t *priv) | |
324 | { | |
325 | int r; | |
326 | ||
327 | /* Enable mlme interface. */ | |
328 | priv->io_aborted = 0; | |
329 | ||
330 | ||
331 | /* Start the BH thread */ | |
332 | r = uf_start_thread(priv, &priv->bh_thread, bh_thread_function); | |
333 | if (r) { | |
334 | unifi_error(priv, | |
335 | "uf_init_bh: failed to start the BH thread.\n"); | |
336 | return r; | |
337 | } | |
338 | ||
339 | /* Allow interrupts */ | |
340 | r = csr_sdio_linux_install_irq(priv->sdio); | |
341 | if (r) { | |
342 | unifi_error(priv, | |
343 | "uf_init_bh: failed to install the IRQ.\n"); | |
344 | ||
345 | uf_stop_thread(priv, &priv->bh_thread); | |
346 | } | |
347 | ||
348 | return r; | |
349 | } /* uf_init_bh() */ | |
350 | ||
351 | ||
352 | /* | |
353 | * --------------------------------------------------------------------------- | |
354 | * unifi_run_bh | |
355 | * | |
356 | * Part of the HIP core lib API, implemented in the porting exercise. | |
357 | * The bottom half of the driver calls this function when | |
358 | * it wants to process anything that requires access to unifi. | |
359 | * We need to call unifi_bh() which in this implementation is done | |
360 | * by waking up the I/O thread. | |
361 | * | |
362 | * Arguments: | |
363 | * ospriv Pointer to OS driver structure for the device. | |
364 | * | |
365 | * Returns: | |
366 | * 0 on success or else a Linux error code. | |
367 | * | |
368 | * Notes: | |
369 | * --------------------------------------------------------------------------- | |
370 | */ | |
371 | CsrResult unifi_run_bh(void *ospriv) | |
372 | { | |
373 | unifi_priv_t *priv = ospriv; | |
374 | ||
375 | /* | |
376 | * If an error has occured, we discard silently all messages from the bh | |
377 | * until the error has been processed and the unifi has been reinitialised. | |
378 | */ | |
379 | if (priv->bh_thread.block_thread == 1) { | |
380 | unifi_trace(priv, UDBG3, "unifi_run_bh: discard message.\n"); | |
381 | /* | |
382 | * Do not try to acknowledge a pending interrupt here. | |
383 | * This function is called by unifi_send_signal() which in turn can be | |
384 | * running in an atomic or 'disabled irq' level if a signal is sent | |
385 | * from a workqueue task (i.e multicass addresses set). | |
386 | * We can not hold the SDIO lock because it might sleep. | |
387 | */ | |
388 | return CSR_RESULT_FAILURE; | |
389 | } | |
390 | ||
391 | priv->bh_thread.wakeup_flag = 1; | |
392 | /* wake up I/O thread */ | |
393 | wake_up_interruptible(&priv->bh_thread.wakeup_q); | |
394 | ||
395 | return CSR_RESULT_SUCCESS; | |
396 | } /* unifi_run_bh() */ | |
397 |