| |
| #include <wl_android.h> |
| #ifdef WL_EVENT |
| #include <bcmendian.h> |
| #include <dhd_config.h> |
| |
| #define EVENT_ERROR(name, arg1, args...) \ |
| do { \ |
| if (android_msg_level & ANDROID_ERROR_LEVEL) { \ |
| printk(KERN_ERR DHD_LOG_PREFIX "[%s] EVENT-ERROR) %s : " arg1, name, __func__, ## args); \ |
| } \ |
| } while (0) |
| #define EVENT_TRACE(name, arg1, args...) \ |
| do { \ |
| if (android_msg_level & ANDROID_TRACE_LEVEL) { \ |
| printk(KERN_INFO DHD_LOG_PREFIX "[%s] EVENT-TRACE) %s : " arg1, name, __func__, ## args); \ |
| } \ |
| } while (0) |
| #define EVENT_DBG(name, arg1, args...) \ |
| do { \ |
| if (android_msg_level & ANDROID_DBG_LEVEL) { \ |
| printk(KERN_INFO DHD_LOG_PREFIX "[%s] EVENT-DBG) %s : " arg1, name, __func__, ## args); \ |
| } \ |
| } while (0) |
| |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == \ |
| 4 && __GNUC_MINOR__ >= 6)) |
| #define BCM_SET_LIST_FIRST_ENTRY(entry, ptr, type, member) \ |
| _Pragma("GCC diagnostic push") \ |
| _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") \ |
| (entry) = list_first_entry((ptr), type, member); \ |
| _Pragma("GCC diagnostic pop") \ |
| |
| #define BCM_SET_CONTAINER_OF(entry, ptr, type, member) \ |
| _Pragma("GCC diagnostic push") \ |
| _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") \ |
| entry = container_of((ptr), type, member); \ |
| _Pragma("GCC diagnostic pop") \ |
| |
| #else |
| #define BCM_SET_LIST_FIRST_ENTRY(entry, ptr, type, member) \ |
| (entry) = list_first_entry((ptr), type, member); \ |
| |
| #define BCM_SET_CONTAINER_OF(entry, ptr, type, member) \ |
| entry = container_of((ptr), type, member); \ |
| |
| #endif /* STRICT_GCC_WARNINGS */ |
| |
| /* event queue for cfg80211 main event */ |
| struct wl_event_q { |
| struct list_head eq_list; |
| u32 etype; |
| wl_event_msg_t emsg; |
| s8 edata[1]; |
| }; |
| |
| typedef void(*EXT_EVENT_HANDLER) (struct net_device *dev, void *cb_argu, |
| const wl_event_msg_t *e, void *data); |
| |
| typedef struct event_handler_list { |
| struct event_handler_list *next; |
| struct net_device *dev; |
| uint32 etype; |
| EXT_EVENT_HANDLER cb_func; |
| void *cb_argu; |
| wl_event_prio_t prio; |
| } event_handler_list_t; |
| |
| typedef struct event_handler_head { |
| event_handler_list_t *evt_head; |
| } event_handler_head_t; |
| |
| typedef struct wl_event_params { |
| dhd_pub_t *pub; |
| struct net_device *dev[DHD_MAX_IFS]; |
| struct event_handler_head evt_head; |
| struct list_head eq_list; /* used for event queue */ |
| spinlock_t eq_lock; /* for event queue synchronization */ |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| tsk_ctl_t thr_event_ctl; |
| #else |
| struct workqueue_struct *event_workq; /* workqueue for event */ |
| struct work_struct event_work; /* work item for event */ |
| #endif |
| struct mutex event_sync; |
| } wl_event_params_t; |
| |
| static unsigned long |
| wl_ext_event_lock_eq(struct wl_event_params *event_params) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&event_params->eq_lock, flags); |
| return flags; |
| } |
| |
| static void |
| wl_ext_event_unlock_eq(struct wl_event_params *event_params, unsigned long flags) |
| { |
| spin_unlock_irqrestore(&event_params->eq_lock, flags); |
| } |
| |
| static void |
| wl_ext_event_init_eq_lock(struct wl_event_params *event_params) |
| { |
| spin_lock_init(&event_params->eq_lock); |
| } |
| |
| static void |
| wl_ext_event_init_eq(struct wl_event_params *event_params) |
| { |
| wl_ext_event_init_eq_lock(event_params); |
| INIT_LIST_HEAD(&event_params->eq_list); |
| } |
| |
| static void |
| wl_ext_event_flush_eq(struct wl_event_params *event_params) |
| { |
| struct wl_event_q *e; |
| unsigned long flags; |
| |
| flags = wl_ext_event_lock_eq(event_params); |
| while (!list_empty_careful(&event_params->eq_list)) { |
| BCM_SET_LIST_FIRST_ENTRY(e, &event_params->eq_list, struct wl_event_q, eq_list); |
| list_del(&e->eq_list); |
| kfree(e); |
| } |
| wl_ext_event_unlock_eq(event_params, flags); |
| } |
| |
| /* |
| * retrieve first queued event from head |
| */ |
| |
| static struct wl_event_q * |
| wl_ext_event_deq_event(struct wl_event_params *event_params) |
| { |
| struct wl_event_q *e = NULL; |
| unsigned long flags; |
| |
| flags = wl_ext_event_lock_eq(event_params); |
| if (likely(!list_empty(&event_params->eq_list))) { |
| BCM_SET_LIST_FIRST_ENTRY(e, &event_params->eq_list, struct wl_event_q, eq_list); |
| list_del(&e->eq_list); |
| } |
| wl_ext_event_unlock_eq(event_params, flags); |
| |
| return e; |
| } |
| |
| /* |
| * push event to tail of the queue |
| */ |
| |
| static s32 |
| wl_ext_event_enq_event(struct wl_event_params *event_params, u32 event, |
| const wl_event_msg_t *msg, void *data) |
| { |
| struct wl_event_q *e; |
| s32 err = 0; |
| uint32 evtq_size; |
| uint32 data_len; |
| unsigned long flags; |
| gfp_t aflags; |
| |
| data_len = 0; |
| if (data) |
| data_len = ntoh32(msg->datalen); |
| evtq_size = sizeof(struct wl_event_q) + data_len; |
| aflags = (in_atomic()) ? GFP_ATOMIC : GFP_KERNEL; |
| e = kzalloc(evtq_size, aflags); |
| if (unlikely(!e)) { |
| EVENT_ERROR("wlan", "event alloc failed\n"); |
| return -ENOMEM; |
| } |
| e->etype = event; |
| memcpy(&e->emsg, msg, sizeof(wl_event_msg_t)); |
| if (data) |
| memcpy(e->edata, data, data_len); |
| flags = wl_ext_event_lock_eq(event_params); |
| list_add_tail(&e->eq_list, &event_params->eq_list); |
| wl_ext_event_unlock_eq(event_params, flags); |
| |
| return err; |
| } |
| |
| static void |
| wl_ext_event_put_event(struct wl_event_q *e) |
| { |
| kfree(e); |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| static int wl_ext_event_handler(void *data); |
| #define WL_EXT_EVENT_HANDLER() static int wl_ext_event_handler(void *data) |
| #else |
| static void wl_ext_event_handler(struct work_struct *data); |
| #define WL_EXT_EVENT_HANDLER() static void wl_ext_event_handler(struct work_struct *data) |
| #endif |
| |
| WL_EXT_EVENT_HANDLER() |
| { |
| struct wl_event_params *event_params = NULL; |
| struct wl_event_q *e; |
| struct net_device *dev = NULL; |
| struct event_handler_list *evt_node; |
| dhd_pub_t *dhd; |
| unsigned long flags = 0; |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| tsk_ctl_t *tsk = (tsk_ctl_t *)data; |
| event_params = (struct wl_event_params *)tsk->parent; |
| #else |
| BCM_SET_CONTAINER_OF(event_params, data, struct wl_event_params, event_work); |
| #endif |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| while (1) { |
| if (down_interruptible(&tsk->sema) == 0) { |
| SMP_RD_BARRIER_DEPENDS(); |
| if (tsk->terminated) { |
| break; |
| } |
| #endif |
| DHD_EVENT_WAKE_LOCK(event_params->pub); |
| while ((e = wl_ext_event_deq_event(event_params))) { |
| if (e->emsg.ifidx >= DHD_MAX_IFS) { |
| EVENT_ERROR("wlan", "ifidx=%d not in range\n", e->emsg.ifidx); |
| goto fail; |
| } |
| dev = event_params->dev[e->emsg.ifidx]; |
| if (!dev) { |
| EVENT_DBG("wlan", "ifidx=%d dev not ready\n", e->emsg.ifidx); |
| goto fail; |
| } |
| dhd = dhd_get_pub(dev); |
| if (e->etype > WLC_E_LAST) { |
| EVENT_TRACE(dev->name, "Unknown Event (%d): ignoring\n", e->etype); |
| goto fail; |
| } |
| DHD_GENERAL_LOCK(dhd, flags); |
| if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(dhd)) { |
| EVENT_ERROR(dev->name, "BUS is DOWN.\n"); |
| DHD_GENERAL_UNLOCK(dhd, flags); |
| goto fail; |
| } |
| DHD_GENERAL_UNLOCK(dhd, flags); |
| EVENT_DBG(dev->name, "event type (%d)\n", e->etype); |
| mutex_lock(&event_params->event_sync); |
| evt_node = event_params->evt_head.evt_head; |
| for (;evt_node;) { |
| if (evt_node->dev == dev && |
| (evt_node->etype == e->etype || evt_node->etype == WLC_E_LAST)) |
| evt_node->cb_func(dev, evt_node->cb_argu, &e->emsg, e->edata); |
| evt_node = evt_node->next; |
| } |
| mutex_unlock(&event_params->event_sync); |
| fail: |
| wl_ext_event_put_event(e); |
| } |
| DHD_EVENT_WAKE_UNLOCK(event_params->pub); |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| } else { |
| break; |
| } |
| } |
| complete_and_exit(&tsk->completed, 0); |
| #endif |
| } |
| |
| void |
| wl_ext_event_send(void *params, const wl_event_msg_t * e, void *data) |
| { |
| struct wl_event_params *event_params = params; |
| u32 event_type = ntoh32(e->event_type); |
| |
| if (event_params == NULL) { |
| EVENT_ERROR("wlan", "Stale event %d(%s) ignored\n", |
| event_type, bcmevent_get_name(event_type)); |
| return; |
| } |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0)) |
| if (event_params->event_workq == NULL) { |
| EVENT_ERROR("wlan", "Event handler is not created %d(%s)\n", |
| event_type, bcmevent_get_name(event_type)); |
| return; |
| } |
| #endif |
| |
| if (likely(!wl_ext_event_enq_event(event_params, event_type, e, data))) { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| if (event_params->thr_event_ctl.thr_pid >= 0) { |
| up(&event_params->thr_event_ctl.sema); |
| } |
| #else |
| queue_work(event_params->event_workq, &event_params->event_work); |
| #endif |
| } |
| } |
| |
| static s32 |
| wl_ext_event_create_handler(struct wl_event_params *event_params) |
| { |
| int ret = 0; |
| EVENT_TRACE("wlan", "Enter\n"); |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| PROC_START(wl_ext_event_handler, event_params, &event_params->thr_event_ctl, 0, "ext_eventd"); |
| if (event_params->thr_event_ctl.thr_pid < 0) { |
| ret = -ENOMEM; |
| } |
| #else |
| /* Allocate workqueue for event */ |
| if (!event_params->event_workq) { |
| event_params->event_workq = alloc_workqueue("ext_eventd", WQ_MEM_RECLAIM | WQ_HIGHPRI, 0); |
| } |
| |
| if (!event_params->event_workq) { |
| EVENT_ERROR("wlan", "event_workq alloc_workqueue failed\n"); |
| ret = -ENOMEM; |
| } else { |
| INIT_WORK(&event_params->event_work, wl_ext_event_handler); |
| } |
| #endif |
| |
| return ret; |
| } |
| |
| static void |
| wl_ext_event_free(struct wl_event_params *event_params) |
| { |
| struct event_handler_list *node, *cur, **evt_head; |
| |
| evt_head = &event_params->evt_head.evt_head; |
| node = *evt_head; |
| |
| for (;node;) { |
| EVENT_TRACE(node->dev->name, "Free etype=%d\n", node->etype); |
| cur = node; |
| node = cur->next; |
| kfree(cur); |
| } |
| *evt_head = NULL; |
| } |
| |
| static void |
| wl_ext_event_destroy_handler(struct wl_event_params *event_params) |
| { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0)) |
| if (event_params->thr_event_ctl.thr_pid >= 0) { |
| PROC_STOP(&event_params->thr_event_ctl); |
| } |
| #else |
| if (event_params && event_params->event_workq) { |
| cancel_work_sync(&event_params->event_work); |
| destroy_workqueue(event_params->event_workq); |
| event_params->event_workq = NULL; |
| } |
| #endif |
| } |
| |
| int |
| wl_ext_event_register(struct net_device *dev, dhd_pub_t *dhd, uint32 event, |
| void *cb_func, void *data, wl_event_prio_t prio) |
| { |
| struct wl_event_params *event_params = dhd->event_params; |
| struct event_handler_list *node, *leaf, *node_prev, **evt_head; |
| int ret = 0; |
| |
| if (event_params) { |
| mutex_lock(&event_params->event_sync); |
| evt_head = &event_params->evt_head.evt_head; |
| node = *evt_head; |
| for (;node;) { |
| if (node->dev == dev && node->etype == event && node->cb_func == cb_func) { |
| EVENT_TRACE(dev->name, "skip event %d\n", event); |
| mutex_unlock(&event_params->event_sync); |
| return 0; |
| } |
| node = node->next; |
| } |
| leaf = kmalloc(sizeof(event_handler_list_t), GFP_KERNEL); |
| if (!leaf) { |
| EVENT_ERROR(dev->name, "Memory alloc failure %d for event %d\n", |
| (int)sizeof(event_handler_list_t), event); |
| mutex_unlock(&event_params->event_sync); |
| return -ENOMEM; |
| } |
| leaf->next = NULL; |
| leaf->dev = dev; |
| leaf->etype = event; |
| leaf->cb_func = cb_func; |
| leaf->cb_argu = data; |
| leaf->prio = prio; |
| if (*evt_head == NULL) { |
| *evt_head = leaf; |
| } else { |
| node = *evt_head; |
| node_prev = NULL; |
| for (;node;) { |
| if (node->prio <= prio) { |
| leaf->next = node; |
| if (node_prev) |
| node_prev->next = leaf; |
| else |
| *evt_head = leaf; |
| break; |
| } else if (node->next == NULL) { |
| node->next = leaf; |
| break; |
| } |
| node_prev = node; |
| node = node->next; |
| } |
| } |
| EVENT_TRACE(dev->name, "event %d registered\n", event); |
| mutex_unlock(&event_params->event_sync); |
| } else { |
| EVENT_ERROR(dev->name, "event_params not ready %d\n", event); |
| ret = -ENODEV; |
| } |
| |
| return ret; |
| } |
| |
| void |
| wl_ext_event_deregister(struct net_device *dev, dhd_pub_t *dhd, |
| uint32 event, void *cb_func) |
| { |
| struct wl_event_params *event_params = dhd->event_params; |
| struct event_handler_list *node, *prev, **evt_head; |
| int tmp = 0; |
| |
| if (event_params) { |
| mutex_lock(&event_params->event_sync); |
| evt_head = &event_params->evt_head.evt_head; |
| node = *evt_head; |
| prev = node; |
| for (;node;) { |
| if (node->dev == dev && node->etype == event && node->cb_func == cb_func) { |
| if (node == *evt_head) { |
| tmp = 1; |
| *evt_head = node->next; |
| } else { |
| tmp = 0; |
| prev->next = node->next; |
| } |
| EVENT_TRACE(dev->name, "event %d deregistered\n", event); |
| kfree(node); |
| if (tmp == 1) { |
| node = *evt_head; |
| prev = node; |
| } else { |
| node = prev->next; |
| } |
| continue; |
| } |
| prev = node; |
| node = node->next; |
| } |
| mutex_unlock(&event_params->event_sync); |
| } else { |
| EVENT_ERROR(dev->name, "event_params not ready %d\n", event); |
| } |
| } |
| |
| static s32 |
| wl_ext_event_init_priv(struct wl_event_params *event_params) |
| { |
| s32 err = 0; |
| |
| mutex_init(&event_params->event_sync); |
| wl_ext_event_init_eq(event_params); |
| if (wl_ext_event_create_handler(event_params)) |
| return -ENOMEM; |
| |
| return err; |
| } |
| |
| static void |
| wl_ext_event_deinit_priv(struct wl_event_params *event_params) |
| { |
| wl_ext_event_destroy_handler(event_params); |
| wl_ext_event_flush_eq(event_params); |
| wl_ext_event_free(event_params); |
| } |
| |
| int |
| wl_ext_event_attach_netdev(struct net_device *net, int ifidx, uint8 bssidx) |
| { |
| struct dhd_pub *dhd = dhd_get_pub(net); |
| struct wl_event_params *event_params = dhd->event_params; |
| |
| if (event_params && ifidx < DHD_MAX_IFS) { |
| EVENT_TRACE(net->name, "ifidx=%d, bssidx=%d\n", ifidx, bssidx); |
| event_params->dev[ifidx] = net; |
| } |
| |
| return 0; |
| } |
| |
| int |
| wl_ext_event_dettach_netdev(struct net_device *net, int ifidx) |
| { |
| struct dhd_pub *dhd = dhd_get_pub(net); |
| struct wl_event_params *event_params = dhd->event_params; |
| |
| if (event_params && ifidx < DHD_MAX_IFS) { |
| EVENT_TRACE(net->name, "ifidx=%d\n", ifidx); |
| event_params->dev[ifidx] = NULL; |
| } |
| |
| return 0; |
| } |
| |
| s32 |
| wl_ext_event_attach(struct net_device *net) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(net); |
| struct wl_event_params *event_params = NULL; |
| s32 err = 0; |
| |
| event_params = kmalloc(sizeof(wl_event_params_t), GFP_KERNEL); |
| if (!event_params) { |
| EVENT_ERROR(net->name, "Failed to allocate memory (%zu)\n", |
| sizeof(wl_event_params_t)); |
| return -ENOMEM; |
| } |
| dhdp->event_params = event_params; |
| memset(event_params, 0, sizeof(wl_event_params_t)); |
| event_params->pub = dhdp; |
| |
| err = wl_ext_event_init_priv(event_params); |
| if (err) { |
| EVENT_ERROR(net->name, "Failed to wl_ext_event_init_priv (%d)\n", err); |
| goto ext_attach_out; |
| } |
| |
| return err; |
| ext_attach_out: |
| wl_ext_event_dettach(dhdp); |
| return err; |
| } |
| |
| void |
| wl_ext_event_dettach(dhd_pub_t *dhdp) |
| { |
| struct wl_event_params *event_params = dhdp->event_params; |
| |
| if (event_params) { |
| wl_ext_event_deinit_priv(event_params); |
| kfree(event_params); |
| dhdp->event_params = NULL; |
| } |
| } |
| #endif |