2 * Broadcom Dongle Host Driver (DHD), Generic work queue framework
3 * Generic interface to handle dhd deferred work events
5 * $Copyright Open Broadcom Corporation$
7 * $Id: dhd_linux_wq.c 449578 2014-01-17 13:53:20Z $
10 #include <linux/init.h>
11 #include <linux/kernel.h>
12 #include <linux/spinlock.h>
13 #include <linux/fcntl.h>
16 #include <linux/kfifo.h>
21 #include <bcmendian.h>
23 #include <dngl_stats.h>
26 #include <dhd_linux_wq.h>
28 struct dhd_deferred_event_t {
29 u8 event; /* holds the event */
30 void *event_data; /* Holds event specific data */
31 event_handler_t event_handler;
33 #define DEFRD_EVT_SIZE sizeof(struct dhd_deferred_event_t)
35 struct dhd_deferred_wq {
36 struct work_struct deferred_work; /* should be the first member */
39 * work events may occur simultaneously.
40 * Can hold upto 64 low priority events and 4 high priority events
42 #define DHD_PRIO_WORK_FIFO_SIZE (4 * sizeof(struct dhd_deferred_event_t))
43 #define DHD_WORK_FIFO_SIZE (64 * sizeof(struct dhd_deferred_event_t))
44 struct kfifo *prio_fifo;
45 struct kfifo *work_fifo;
49 void *dhd_info; /* review: does it require */
52 static inline struct kfifo*
53 dhd_kfifo_init(u8 *buf, int size, spinlock_t *lock)
56 gfp_t flags = CAN_SLEEP()? GFP_KERNEL : GFP_ATOMIC;
58 #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))
59 fifo = kfifo_init(buf, size, flags, lock);
61 fifo = (struct kfifo *)kzalloc(sizeof(struct kfifo), flags);
65 kfifo_init(fifo, buf, size);
66 #endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33)) */
71 dhd_kfifo_free(struct kfifo *fifo)
74 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 31))
75 /* FC11 releases the fifo memory */
80 /* deferred work functions */
81 static void dhd_deferred_work_handler(struct work_struct *data);
84 dhd_deferred_work_init(void *dhd_info)
86 struct dhd_deferred_wq *work = NULL;
88 unsigned long fifo_size = 0;
89 gfp_t flags = CAN_SLEEP()? GFP_KERNEL : GFP_ATOMIC;
92 DHD_ERROR(("%s: dhd info not initialized\n", __FUNCTION__));
96 work = (struct dhd_deferred_wq *)kzalloc(sizeof(struct dhd_deferred_wq),
100 DHD_ERROR(("%s: work queue creation failed \n", __FUNCTION__));
104 INIT_WORK((struct work_struct *)work, dhd_deferred_work_handler);
106 /* initialize event fifo */
107 spin_lock_init(&work->work_lock);
109 /* allocate buffer to hold prio events */
110 fifo_size = DHD_PRIO_WORK_FIFO_SIZE;
111 fifo_size = is_power_of_2(fifo_size)? fifo_size : roundup_pow_of_two(fifo_size);
112 buf = (u8*)kzalloc(fifo_size, flags);
114 DHD_ERROR(("%s: prio work fifo allocation failed \n", __FUNCTION__));
118 /* Initialize prio event fifo */
119 work->prio_fifo = dhd_kfifo_init(buf, fifo_size, &work->work_lock);
120 if (!work->prio_fifo) {
125 /* allocate buffer to hold work events */
126 fifo_size = DHD_WORK_FIFO_SIZE;
127 fifo_size = is_power_of_2(fifo_size)? fifo_size : roundup_pow_of_two(fifo_size);
128 buf = (u8*)kzalloc(fifo_size, flags);
130 DHD_ERROR(("%s: work fifo allocation failed \n", __FUNCTION__));
134 /* Initialize event fifo */
135 work->work_fifo = dhd_kfifo_init(buf, fifo_size, &work->work_lock);
136 if (!work->work_fifo) {
141 work->dhd_info = dhd_info;
142 DHD_ERROR(("%s: work queue initialized \n", __FUNCTION__));
148 dhd_deferred_work_deinit(work);
154 dhd_deferred_work_deinit(void *work)
156 struct dhd_deferred_wq *deferred_work = work;
159 if (!deferred_work) {
160 DHD_ERROR(("%s: deferred work has been freed alread \n", __FUNCTION__));
164 /* cancel the deferred work handling */
165 cancel_work_sync((struct work_struct *)deferred_work);
168 * free work event fifo.
169 * kfifo_free frees locally allocated fifo buffer
171 if (deferred_work->prio_fifo)
172 dhd_kfifo_free(deferred_work->prio_fifo);
174 if (deferred_work->work_fifo)
175 dhd_kfifo_free(deferred_work->work_fifo);
177 kfree(deferred_work);
181 * Prepares event to be queued
182 * Schedules the event
185 dhd_deferred_schedule_work(void *workq, void *event_data, u8 event,
186 event_handler_t event_handler, u8 priority)
188 struct dhd_deferred_wq *deferred_wq = (struct dhd_deferred_wq *) workq;
189 struct dhd_deferred_event_t deferred_event;
193 DHD_ERROR(("%s: work queue not initialized \n", __FUNCTION__));
195 return DHD_WQ_STS_UNINITIALIZED;
198 if (!event || (event >= DHD_MAX_WQ_EVENTS)) {
199 DHD_ERROR(("%s: Unknown event \n", __FUNCTION__));
200 return DHD_WQ_STS_UNKNOWN_EVENT;
204 * default element size is 1, which can be changed
205 * using kfifo_esize(). Older kernel(FC11) doesn't support
206 * changing element size. For compatibility changing
207 * element size is not prefered
209 ASSERT(kfifo_esize(deferred_wq->prio_fifo) == 1);
210 ASSERT(kfifo_esize(deferred_wq->work_fifo) == 1);
212 deferred_event.event = event;
213 deferred_event.event_data = event_data;
214 deferred_event.event_handler = event_handler;
216 if (priority == DHD_WORK_PRIORITY_HIGH) {
217 status = kfifo_in_spinlocked(deferred_wq->prio_fifo, &deferred_event,
218 DEFRD_EVT_SIZE, &deferred_wq->work_lock);
220 status = kfifo_in_spinlocked(deferred_wq->work_fifo, &deferred_event,
221 DEFRD_EVT_SIZE, &deferred_wq->work_lock);
225 return DHD_WQ_STS_SCHED_FAILED;
227 schedule_work((struct work_struct *)deferred_wq);
228 return DHD_WQ_STS_OK;
232 dhd_get_scheduled_work(struct dhd_deferred_wq *deferred_wq, struct dhd_deferred_event_t *event)
237 DHD_ERROR(("%s: work queue not initialized \n", __FUNCTION__));
238 return DHD_WQ_STS_UNINITIALIZED;
242 * default element size is 1 byte, which can be changed
243 * using kfifo_esize(). Older kernel(FC11) doesn't support
244 * changing element size. For compatibility changing
245 * element size is not prefered
247 ASSERT(kfifo_esize(deferred_wq->prio_fifo) == 1);
248 ASSERT(kfifo_esize(deferred_wq->work_fifo) == 1);
250 /* first read priorit event fifo */
251 status = kfifo_out_spinlocked(deferred_wq->prio_fifo, event,
252 DEFRD_EVT_SIZE, &deferred_wq->work_lock);
255 /* priority fifo is empty. Now read low prio work fifo */
256 status = kfifo_out_spinlocked(deferred_wq->work_fifo, event,
257 DEFRD_EVT_SIZE, &deferred_wq->work_lock);
264 * Called when work is scheduled
267 dhd_deferred_work_handler(struct work_struct *work)
269 struct dhd_deferred_wq *deferred_work = (struct dhd_deferred_wq *)work;
270 struct dhd_deferred_event_t work_event;
273 if (!deferred_work) {
274 DHD_ERROR(("%s: work queue not initialized\n", __FUNCTION__));
279 status = dhd_get_scheduled_work(deferred_work, &work_event);
280 DHD_TRACE(("%s: event to handle %d \n", __FUNCTION__, status));
282 DHD_TRACE(("%s: No event to handle %d \n", __FUNCTION__, status));
286 if (work_event.event > DHD_MAX_WQ_EVENTS) {
287 DHD_TRACE(("%s: Unknown event %d \n", __FUNCTION__, work_event.event));
291 if (work_event.event_handler) {
292 work_event.event_handler(deferred_work->dhd_info,
293 work_event.event_data, work_event.event);
295 DHD_ERROR(("%s: event not defined %d\n", __FUNCTION__, work_event.event));