Merge branch develop-3.10-next
[firefly-linux-kernel-4.4.55.git] / drivers / net / wireless / rockchip_wlan / rkwifi / bcmdhd / dhd_linux_wq.c
1 /*
2  * Broadcom Dongle Host Driver (DHD), Generic work queue framework
3  * Generic interface to handle dhd deferred work events
4  *
5  * $Copyright Open Broadcom Corporation$
6  *
7  * $Id: dhd_linux_wq.c 449578 2014-01-17 13:53:20Z $
8  */
9
10 #include <linux/init.h>
11 #include <linux/kernel.h>
12 #include <linux/spinlock.h>
13 #include <linux/fcntl.h>
14 #include <linux/fs.h>
15 #include <linux/ip.h>
16 #include <linux/kfifo.h>
17
18 #include <linuxver.h>
19 #include <osl.h>
20 #include <bcmutils.h>
21 #include <bcmendian.h>
22 #include <bcmdevs.h>
23 #include <dngl_stats.h>
24 #include <dhd.h>
25 #include <dhd_dbg.h>
26 #include <dhd_linux_wq.h>
27
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;
32 };
33 #define DEFRD_EVT_SIZE  sizeof(struct dhd_deferred_event_t)
34
35 struct dhd_deferred_wq {
36         struct work_struct      deferred_work; /* should be the first member */
37
38         /*
39          * work events may occur simultaneously.
40          * Can hold upto 64 low priority events and 4 high priority events
41          */
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;
46         u8                              *prio_fifo_buf;
47         u8                              *work_fifo_buf;
48         spinlock_t                      work_lock;
49         void                            *dhd_info; /* review: does it require */
50 };
51
52 static inline struct kfifo*
53 dhd_kfifo_init(u8 *buf, int size, spinlock_t *lock)
54 {
55         struct kfifo *fifo;
56         gfp_t flags = CAN_SLEEP()? GFP_KERNEL : GFP_ATOMIC;
57
58 #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))
59         fifo = kfifo_init(buf, size, flags, lock);
60 #else
61         fifo = (struct kfifo *)kzalloc(sizeof(struct kfifo), flags);
62         if (!fifo) {
63                 return NULL;
64         }
65         kfifo_init(fifo, buf, size);
66 #endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33)) */
67         return fifo;
68 }
69
70 static inline void
71 dhd_kfifo_free(struct kfifo *fifo)
72 {
73         kfifo_free(fifo);
74 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 31))
75         /* FC11 releases the fifo memory */
76         kfree(fifo);
77 #endif
78 }
79
80 /* deferred work functions */
81 static void dhd_deferred_work_handler(struct work_struct *data);
82
83 void*
84 dhd_deferred_work_init(void *dhd_info)
85 {
86         struct dhd_deferred_wq  *work = NULL;
87         u8*     buf;
88         unsigned long   fifo_size = 0;
89         gfp_t   flags = CAN_SLEEP()? GFP_KERNEL : GFP_ATOMIC;
90
91         if (!dhd_info) {
92                 DHD_ERROR(("%s: dhd info not initialized\n", __FUNCTION__));
93                 goto return_null;
94         }
95
96         work = (struct dhd_deferred_wq *)kzalloc(sizeof(struct dhd_deferred_wq),
97                 flags);
98
99         if (!work) {
100                 DHD_ERROR(("%s: work queue creation failed \n", __FUNCTION__));
101                 goto return_null;
102         }
103
104         INIT_WORK((struct work_struct *)work, dhd_deferred_work_handler);
105
106         /* initialize event fifo */
107         spin_lock_init(&work->work_lock);
108
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);
113         if (!buf) {
114                 DHD_ERROR(("%s: prio work fifo allocation failed \n", __FUNCTION__));
115                 goto return_null;
116         }
117
118         /* Initialize prio event fifo */
119         work->prio_fifo = dhd_kfifo_init(buf, fifo_size, &work->work_lock);
120         if (!work->prio_fifo) {
121                 kfree(buf);
122                 goto return_null;
123         }
124
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);
129         if (!buf) {
130                 DHD_ERROR(("%s: work fifo allocation failed \n", __FUNCTION__));
131                 goto return_null;
132         }
133
134         /* Initialize event fifo */
135         work->work_fifo = dhd_kfifo_init(buf, fifo_size, &work->work_lock);
136         if (!work->work_fifo) {
137                 kfree(buf);
138                 goto return_null;
139         }
140
141         work->dhd_info = dhd_info;
142         DHD_ERROR(("%s: work queue initialized \n", __FUNCTION__));
143         return work;
144
145 return_null:
146
147         if (work)
148                 dhd_deferred_work_deinit(work);
149
150         return NULL;
151 }
152
153 void
154 dhd_deferred_work_deinit(void *work)
155 {
156         struct dhd_deferred_wq *deferred_work = work;
157
158
159         if (!deferred_work) {
160                 DHD_ERROR(("%s: deferred work has been freed alread \n", __FUNCTION__));
161                 return;
162         }
163
164         /* cancel the deferred work handling */
165         cancel_work_sync((struct work_struct *)deferred_work);
166
167         /*
168          * free work event fifo.
169          * kfifo_free frees locally allocated fifo buffer
170          */
171         if (deferred_work->prio_fifo)
172                 dhd_kfifo_free(deferred_work->prio_fifo);
173
174         if (deferred_work->work_fifo)
175                 dhd_kfifo_free(deferred_work->work_fifo);
176
177         kfree(deferred_work);
178 }
179
180 /*
181  *      Prepares event to be queued
182  *      Schedules the event
183  */
184 int
185 dhd_deferred_schedule_work(void *workq, void *event_data, u8 event,
186         event_handler_t event_handler, u8 priority)
187 {
188         struct dhd_deferred_wq *deferred_wq = (struct dhd_deferred_wq *) workq;
189         struct  dhd_deferred_event_t    deferred_event;
190         int     status;
191
192         if (!deferred_wq) {
193                 DHD_ERROR(("%s: work queue not initialized \n", __FUNCTION__));
194                 ASSERT(0);
195                 return DHD_WQ_STS_UNINITIALIZED;
196         }
197
198         if (!event || (event >= DHD_MAX_WQ_EVENTS)) {
199                 DHD_ERROR(("%s: Unknown event \n", __FUNCTION__));
200                 return DHD_WQ_STS_UNKNOWN_EVENT;
201         }
202
203         /*
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
208          */
209         ASSERT(kfifo_esize(deferred_wq->prio_fifo) == 1);
210         ASSERT(kfifo_esize(deferred_wq->work_fifo) == 1);
211
212         deferred_event.event = event;
213         deferred_event.event_data = event_data;
214         deferred_event.event_handler = event_handler;
215
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);
219         } else {
220                 status = kfifo_in_spinlocked(deferred_wq->work_fifo, &deferred_event,
221                         DEFRD_EVT_SIZE, &deferred_wq->work_lock);
222         }
223
224         if (!status) {
225                 return DHD_WQ_STS_SCHED_FAILED;
226         }
227         schedule_work((struct work_struct *)deferred_wq);
228         return DHD_WQ_STS_OK;
229 }
230
231 static int
232 dhd_get_scheduled_work(struct dhd_deferred_wq *deferred_wq, struct dhd_deferred_event_t *event)
233 {
234         int     status = 0;
235
236         if (!deferred_wq) {
237                 DHD_ERROR(("%s: work queue not initialized \n", __FUNCTION__));
238                 return DHD_WQ_STS_UNINITIALIZED;
239         }
240
241         /*
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
246          */
247         ASSERT(kfifo_esize(deferred_wq->prio_fifo) == 1);
248         ASSERT(kfifo_esize(deferred_wq->work_fifo) == 1);
249
250         /* first read  priorit event fifo */
251         status = kfifo_out_spinlocked(deferred_wq->prio_fifo, event,
252                 DEFRD_EVT_SIZE, &deferred_wq->work_lock);
253
254         if (!status) {
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);
258         }
259
260         return status;
261 }
262
263 /*
264  *      Called when work is scheduled
265  */
266 static void
267 dhd_deferred_work_handler(struct work_struct *work)
268 {
269         struct dhd_deferred_wq          *deferred_work = (struct dhd_deferred_wq *)work;
270         struct dhd_deferred_event_t     work_event;
271         int                             status;
272
273         if (!deferred_work) {
274                 DHD_ERROR(("%s: work queue not initialized\n", __FUNCTION__));
275                 return;
276         }
277
278         do {
279                 status = dhd_get_scheduled_work(deferred_work, &work_event);
280                 DHD_TRACE(("%s: event to handle %d \n", __FUNCTION__, status));
281                 if (!status) {
282                         DHD_TRACE(("%s: No event to handle %d \n", __FUNCTION__, status));
283                         break;
284                 }
285
286                 if (work_event.event > DHD_MAX_WQ_EVENTS) {
287                         DHD_TRACE(("%s: Unknown event %d \n", __FUNCTION__, work_event.event));
288                         break;
289                 }
290
291                 if (work_event.event_handler) {
292                         work_event.event_handler(deferred_work->dhd_info,
293                                 work_event.event_data, work_event.event);
294                 } else {
295                         DHD_ERROR(("%s: event not defined %d\n", __FUNCTION__, work_event.event));
296                 }
297         } while (1);
298         return;
299 }