2 * Broadcom Dongle Host Driver (DHD), Generic work queue framework
3 * Generic interface to handle dhd deferred work events
5 * Copyright (C) 1999-2017, Broadcom Corporation
7 * Unless you and Broadcom execute a separate written software license
8 * agreement governing use of this software, this software is licensed to you
9 * under the terms of the GNU General Public License version 2 (the "GPL"),
10 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
11 * following added to such license:
13 * As a special exception, the copyright holders of this software give you
14 * permission to link this software with independent modules, and to copy and
15 * distribute the resulting executable under terms of your choice, provided that
16 * you also meet, for each linked independent module, the terms and conditions of
17 * the license of that module. An independent module is a module which is not
18 * derived from this software. The special exception does not apply to any
19 * modifications of the software.
21 * Notwithstanding the above, under no circumstances may you combine this
22 * software in any way with any other Broadcom software provided under a license
23 * other than the GPL, without Broadcom's express prior written consent.
26 * <<Broadcom-WL-IPTag/Open:>>
28 * $Id: dhd_linux_wq.c 641330 2016-06-02 06:55:00Z $
31 #include <linux/init.h>
32 #include <linux/kernel.h>
33 #include <linux/spinlock.h>
34 #include <linux/fcntl.h>
37 #include <linux/kfifo.h>
42 #include <bcmendian.h>
44 #include <dngl_stats.h>
47 #include <dhd_linux_wq.h>
49 typedef struct dhd_deferred_event
{
50 u8 event
; /* holds the event */
51 void *event_data
; /* holds event specific data */
52 event_handler_t event_handler
;
53 unsigned long pad
; /* for memory alignment to power of 2 */
54 } dhd_deferred_event_t
;
56 #define DEFRD_EVT_SIZE (sizeof(dhd_deferred_event_t))
59 * work events may occur simultaneously.
60 * can hold upto 64 low priority events and 16 high priority events
62 #define DHD_PRIO_WORK_FIFO_SIZE (16 * DEFRD_EVT_SIZE)
63 #define DHD_WORK_FIFO_SIZE (64 * DEFRD_EVT_SIZE)
65 #define DHD_FIFO_HAS_FREE_SPACE(fifo) \
66 ((fifo) && (kfifo_avail(fifo) >= DEFRD_EVT_SIZE))
67 #define DHD_FIFO_HAS_ENOUGH_DATA(fifo) \
68 ((fifo) && (kfifo_len(fifo) >= DEFRD_EVT_SIZE))
70 struct dhd_deferred_wq
{
71 struct work_struct deferred_work
; /* should be the first member */
73 struct kfifo
*prio_fifo
;
74 struct kfifo
*work_fifo
;
78 void *dhd_info
; /* review: does it require */
81 static inline struct kfifo
*
82 dhd_kfifo_init(u8
*buf
, int size
, spinlock_t
*lock
)
85 gfp_t flags
= CAN_SLEEP() ? GFP_KERNEL
: GFP_ATOMIC
;
87 #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))
88 fifo
= kfifo_init(buf
, size
, flags
, lock
);
90 fifo
= (struct kfifo
*)kzalloc(sizeof(struct kfifo
), flags
);
94 kfifo_init(fifo
, buf
, size
);
95 #endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33)) */
100 dhd_kfifo_free(struct kfifo
*fifo
)
103 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 31))
104 /* FC11 releases the fifo memory */
109 /* deferred work functions */
110 static void dhd_deferred_work_handler(struct work_struct
*data
);
113 dhd_deferred_work_init(void *dhd_info
)
115 struct dhd_deferred_wq
*work
= NULL
;
117 unsigned long fifo_size
= 0;
118 gfp_t flags
= CAN_SLEEP() ? GFP_KERNEL
: GFP_ATOMIC
;
121 DHD_ERROR(("%s: dhd info not initialized\n", __FUNCTION__
));
125 work
= (struct dhd_deferred_wq
*)kzalloc(sizeof(struct dhd_deferred_wq
),
128 DHD_ERROR(("%s: work queue creation failed\n", __FUNCTION__
));
132 INIT_WORK((struct work_struct
*)work
, dhd_deferred_work_handler
);
134 /* initialize event fifo */
135 spin_lock_init(&work
->work_lock
);
137 /* allocate buffer to hold prio events */
138 fifo_size
= DHD_PRIO_WORK_FIFO_SIZE
;
139 fifo_size
= is_power_of_2(fifo_size
) ? fifo_size
:
140 roundup_pow_of_two(fifo_size
);
141 buf
= (u8
*)kzalloc(fifo_size
, flags
);
143 DHD_ERROR(("%s: prio work fifo allocation failed\n",
148 /* Initialize prio event fifo */
149 work
->prio_fifo
= dhd_kfifo_init(buf
, fifo_size
, &work
->work_lock
);
150 if (!work
->prio_fifo
) {
155 /* allocate buffer to hold work events */
156 fifo_size
= DHD_WORK_FIFO_SIZE
;
157 fifo_size
= is_power_of_2(fifo_size
) ? fifo_size
:
158 roundup_pow_of_two(fifo_size
);
159 buf
= (u8
*)kzalloc(fifo_size
, flags
);
161 DHD_ERROR(("%s: work fifo allocation failed\n", __FUNCTION__
));
165 /* Initialize event fifo */
166 work
->work_fifo
= dhd_kfifo_init(buf
, fifo_size
, &work
->work_lock
);
167 if (!work
->work_fifo
) {
172 work
->dhd_info
= dhd_info
;
173 DHD_ERROR(("%s: work queue initialized\n", __FUNCTION__
));
178 dhd_deferred_work_deinit(work
);
185 dhd_deferred_work_deinit(void *work
)
187 struct dhd_deferred_wq
*deferred_work
= work
;
190 if (!deferred_work
) {
191 DHD_ERROR(("%s: deferred work has been freed already\n",
196 /* cancel the deferred work handling */
197 cancel_work_sync((struct work_struct
*)deferred_work
);
200 * free work event fifo.
201 * kfifo_free frees locally allocated fifo buffer
203 if (deferred_work
->prio_fifo
) {
204 dhd_kfifo_free(deferred_work
->prio_fifo
);
207 if (deferred_work
->work_fifo
) {
208 dhd_kfifo_free(deferred_work
->work_fifo
);
211 kfree(deferred_work
);
214 /* select kfifo according to priority */
215 static inline struct kfifo
*
216 dhd_deferred_work_select_kfifo(struct dhd_deferred_wq
*deferred_wq
,
219 if (priority
== DHD_WQ_WORK_PRIORITY_HIGH
) {
220 return deferred_wq
->prio_fifo
;
221 } else if (priority
== DHD_WQ_WORK_PRIORITY_LOW
) {
222 return deferred_wq
->work_fifo
;
229 * Prepares event to be queued
230 * Schedules the event
233 dhd_deferred_schedule_work(void *workq
, void *event_data
, u8 event
,
234 event_handler_t event_handler
, u8 priority
)
236 struct dhd_deferred_wq
*deferred_wq
= (struct dhd_deferred_wq
*)workq
;
238 dhd_deferred_event_t deferred_event
;
239 int bytes_copied
= 0;
242 DHD_ERROR(("%s: work queue not initialized\n", __FUNCTION__
));
244 return DHD_WQ_STS_UNINITIALIZED
;
247 if (!event
|| (event
>= DHD_MAX_WQ_EVENTS
)) {
248 DHD_ERROR(("%s: unknown event, event=%d\n", __FUNCTION__
,
250 return DHD_WQ_STS_UNKNOWN_EVENT
;
253 if (!priority
|| (priority
>= DHD_WQ_MAX_PRIORITY
)) {
254 DHD_ERROR(("%s: unknown priority, priority=%d\n",
255 __FUNCTION__
, priority
));
256 return DHD_WQ_STS_UNKNOWN_PRIORITY
;
260 * default element size is 1, which can be changed
261 * using kfifo_esize(). Older kernel(FC11) doesn't support
262 * changing element size. For compatibility changing
263 * element size is not prefered
265 ASSERT(kfifo_esize(deferred_wq
->prio_fifo
) == 1);
266 ASSERT(kfifo_esize(deferred_wq
->work_fifo
) == 1);
268 deferred_event
.event
= event
;
269 deferred_event
.event_data
= event_data
;
270 deferred_event
.event_handler
= event_handler
;
272 fifo
= dhd_deferred_work_select_kfifo(deferred_wq
, priority
);
273 if (DHD_FIFO_HAS_FREE_SPACE(fifo
)) {
274 bytes_copied
= kfifo_in_spinlocked(fifo
, &deferred_event
,
275 DEFRD_EVT_SIZE
, &deferred_wq
->work_lock
);
277 if (bytes_copied
!= DEFRD_EVT_SIZE
) {
278 DHD_ERROR(("%s: failed to schedule deferred work, "
279 "priority=%d, bytes_copied=%d\n", __FUNCTION__
,
280 priority
, bytes_copied
));
281 return DHD_WQ_STS_SCHED_FAILED
;
283 schedule_work((struct work_struct
*)deferred_wq
);
284 return DHD_WQ_STS_OK
;
288 dhd_get_scheduled_work(struct dhd_deferred_wq
*deferred_wq
,
289 dhd_deferred_event_t
*event
)
291 int bytes_copied
= 0;
294 DHD_ERROR(("%s: work queue not initialized\n", __FUNCTION__
));
295 return DHD_WQ_STS_UNINITIALIZED
;
299 * default element size is 1 byte, which can be changed
300 * using kfifo_esize(). Older kernel(FC11) doesn't support
301 * changing element size. For compatibility changing
302 * element size is not prefered
304 ASSERT(kfifo_esize(deferred_wq
->prio_fifo
) == 1);
305 ASSERT(kfifo_esize(deferred_wq
->work_fifo
) == 1);
307 /* handle priority work */
308 if (DHD_FIFO_HAS_ENOUGH_DATA(deferred_wq
->prio_fifo
)) {
309 bytes_copied
= kfifo_out_spinlocked(deferred_wq
->prio_fifo
,
310 event
, DEFRD_EVT_SIZE
, &deferred_wq
->work_lock
);
313 /* handle normal work if priority work doesn't have enough data */
314 if ((bytes_copied
!= DEFRD_EVT_SIZE
) &&
315 DHD_FIFO_HAS_ENOUGH_DATA(deferred_wq
->work_fifo
)) {
316 bytes_copied
= kfifo_out_spinlocked(deferred_wq
->work_fifo
,
317 event
, DEFRD_EVT_SIZE
, &deferred_wq
->work_lock
);
320 return (bytes_copied
== DEFRD_EVT_SIZE
);
324 dhd_deferred_dump_work_event(dhd_deferred_event_t
*work_event
)
327 DHD_ERROR(("%s: work_event is null\n", __FUNCTION__
));
331 DHD_ERROR(("%s: work_event->event = %d\n", __FUNCTION__
,
333 DHD_ERROR(("%s: work_event->event_data = %p\n", __FUNCTION__
,
334 work_event
->event_data
));
335 DHD_ERROR(("%s: work_event->event_handler = %p\n", __FUNCTION__
,
336 work_event
->event_handler
));
340 * Called when work is scheduled
343 dhd_deferred_work_handler(struct work_struct
*work
)
345 struct dhd_deferred_wq
*deferred_work
= (struct dhd_deferred_wq
*)work
;
346 dhd_deferred_event_t work_event
;
348 if (!deferred_work
) {
349 DHD_ERROR(("%s: work queue not initialized\n", __FUNCTION__
));
354 if (!dhd_get_scheduled_work(deferred_work
, &work_event
)) {
355 DHD_TRACE(("%s: no event to handle\n", __FUNCTION__
));
359 if (work_event
.event
>= DHD_MAX_WQ_EVENTS
) {
360 DHD_ERROR(("%s: unknown event\n", __FUNCTION__
));
361 dhd_deferred_dump_work_event(&work_event
);
362 ASSERT(work_event
.event
< DHD_MAX_WQ_EVENTS
);
367 if (work_event
.event_handler
) {
368 work_event
.event_handler(deferred_work
->dhd_info
,
369 work_event
.event_data
, work_event
.event
);
371 DHD_ERROR(("%s: event handler is null\n",
373 dhd_deferred_dump_work_event(&work_event
);
374 ASSERT(work_event
.event_handler
!= NULL
);