
#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) { \
			printf("[%s] EVENT-ERROR) %s : " arg1, name, __func__, ## args); \
		} \
	} while (0)
#define EVENT_TRACE(name, arg1, args...) \
	do { \
		if (android_msg_level & ANDROID_TRACE_LEVEL) { \
			printf("[%s] EVENT-TRACE) %s : " arg1, name, __func__, ## args); \
		} \
	} while (0)
#define EVENT_DBG(name, arg1, args...) \
	do { \
		if (android_msg_level & ANDROID_DBG_LEVEL) { \
			printf("[%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 | WQ_UNBOUND, 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;
		}
		memset(leaf, 0, sizeof(event_handler_list_t));
		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
