| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (c) 2010, ST-Ericsson | 
					
						
							|  |  |  |  * Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Based on: | 
					
						
							|  |  |  |  * ST-Ericsson UMAC CW1200 driver, which is | 
					
						
							|  |  |  |  * Copyright (c) 2010, ST-Ericsson | 
					
						
							|  |  |  |  * Author: Ajitpal Singh <ajitpal.singh@stericsson.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License version 2 as | 
					
						
							|  |  |  |  * published by the Free Software Foundation. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <net/mac80211.h>
 | 
					
						
							|  |  |  | #include <linux/kthread.h>
 | 
					
						
							|  |  |  | #include <linux/timer.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "cw1200.h"
 | 
					
						
							|  |  |  | #include "bh.h"
 | 
					
						
							|  |  |  | #include "hwio.h"
 | 
					
						
							|  |  |  | #include "wsm.h"
 | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | #include "hwbus.h"
 | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | #include "debug.h"
 | 
					
						
							|  |  |  | #include "fwio.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cw1200_bh(void *arg); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define DOWNLOAD_BLOCK_SIZE_WR	(0x1000 - 4)
 | 
					
						
							|  |  |  | /* an SPI message cannot be bigger than (2"12-1)*2 bytes
 | 
					
						
							| 
									
										
										
										
											2013-06-11 09:49:40 -04:00
										 |  |  |  * "*2" to cvt to bytes | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | #define MAX_SZ_RD_WR_BUFFERS	(DOWNLOAD_BLOCK_SIZE_WR*2)
 | 
					
						
							|  |  |  | #define PIGGYBACK_CTRL_REG	(2)
 | 
					
						
							|  |  |  | #define EFFECTIVE_BUF_SIZE	(MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Suspend state privates */ | 
					
						
							|  |  |  | enum cw1200_bh_pm_state { | 
					
						
							|  |  |  | 	CW1200_BH_RESUMED = 0, | 
					
						
							|  |  |  | 	CW1200_BH_SUSPEND, | 
					
						
							|  |  |  | 	CW1200_BH_SUSPENDED, | 
					
						
							|  |  |  | 	CW1200_BH_RESUME, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef int (*cw1200_wsm_handler)(struct cw1200_common *priv, | 
					
						
							|  |  |  | 	u8 *data, size_t size); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void cw1200_bh_work(struct work_struct *work) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cw1200_common *priv = | 
					
						
							|  |  |  | 	container_of(work, struct cw1200_common, bh_work); | 
					
						
							|  |  |  | 	cw1200_bh(priv); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int cw1200_register_bh(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int err = 0; | 
					
						
							|  |  |  | 	/* Realtime workqueue */ | 
					
						
							|  |  |  | 	priv->bh_workqueue = alloc_workqueue("cw1200_bh", | 
					
						
							|  |  |  | 				WQ_MEM_RECLAIM | WQ_HIGHPRI | 
					
						
							|  |  |  | 				| WQ_CPU_INTENSIVE, 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!priv->bh_workqueue) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	INIT_WORK(&priv->bh_work, cw1200_bh_work); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("[BH] register.\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	atomic_set(&priv->bh_rx, 0); | 
					
						
							|  |  |  | 	atomic_set(&priv->bh_tx, 0); | 
					
						
							|  |  |  | 	atomic_set(&priv->bh_term, 0); | 
					
						
							|  |  |  | 	atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED); | 
					
						
							|  |  |  | 	priv->bh_error = 0; | 
					
						
							|  |  |  | 	priv->hw_bufs_used = 0; | 
					
						
							|  |  |  | 	priv->buf_id_tx = 0; | 
					
						
							|  |  |  | 	priv->buf_id_rx = 0; | 
					
						
							|  |  |  | 	init_waitqueue_head(&priv->bh_wq); | 
					
						
							|  |  |  | 	init_waitqueue_head(&priv->bh_evt_wq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = !queue_work(priv->bh_workqueue, &priv->bh_work); | 
					
						
							|  |  |  | 	WARN_ON(err); | 
					
						
							|  |  |  | 	return err; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cw1200_unregister_bh(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	atomic_add(1, &priv->bh_term); | 
					
						
							|  |  |  | 	wake_up(&priv->bh_wq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	flush_workqueue(priv->bh_workqueue); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	destroy_workqueue(priv->bh_workqueue); | 
					
						
							|  |  |  | 	priv->bh_workqueue = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("[BH] unregistered.\n"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cw1200_irq_handler(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pr_debug("[BH] irq.\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Disable Interrupts! */ | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | 	/* NOTE:  hwbus_ops->lock already held */ | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 	__cw1200_irq_enable(priv, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (/* WARN_ON */(priv->bh_error)) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (atomic_add_return(1, &priv->bh_rx) == 1) | 
					
						
							|  |  |  | 		wake_up(&priv->bh_wq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(cw1200_irq_handler); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void cw1200_bh_wakeup(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pr_debug("[BH] wakeup.\n"); | 
					
						
							|  |  |  | 	if (priv->bh_error) { | 
					
						
							|  |  |  | 		pr_err("[BH] wakeup failed (BH error)\n"); | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (atomic_add_return(1, &priv->bh_tx) == 1) | 
					
						
							|  |  |  | 		wake_up(&priv->bh_wq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int cw1200_bh_suspend(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pr_debug("[BH] suspend.\n"); | 
					
						
							|  |  |  | 	if (priv->bh_error) { | 
					
						
							|  |  |  | 		wiphy_warn(priv->hw->wiphy, "BH error -- can't suspend\n"); | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND); | 
					
						
							|  |  |  | 	wake_up(&priv->bh_wq); | 
					
						
							|  |  |  | 	return wait_event_timeout(priv->bh_evt_wq, priv->bh_error || | 
					
						
							|  |  |  | 		(CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)), | 
					
						
							|  |  |  | 		 1 * HZ) ? 0 : -ETIMEDOUT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int cw1200_bh_resume(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pr_debug("[BH] resume.\n"); | 
					
						
							|  |  |  | 	if (priv->bh_error) { | 
					
						
							|  |  |  | 		wiphy_warn(priv->hw->wiphy, "BH error -- can't resume\n"); | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	atomic_set(&priv->bh_suspend, CW1200_BH_RESUME); | 
					
						
							|  |  |  | 	wake_up(&priv->bh_wq); | 
					
						
							|  |  |  | 	return wait_event_timeout(priv->bh_evt_wq, priv->bh_error || | 
					
						
							|  |  |  | 		(CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)), | 
					
						
							|  |  |  | 		1 * HZ) ? 0 : -ETIMEDOUT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void wsm_alloc_tx_buffer(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	++priv->hw_bufs_used; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int wsm_release_tx_buffer(struct cw1200_common *priv, int count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 	int hw_bufs_used = priv->hw_bufs_used; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv->hw_bufs_used -= count; | 
					
						
							|  |  |  | 	if (WARN_ON(priv->hw_bufs_used < 0)) | 
					
						
							|  |  |  | 		ret = -1; | 
					
						
							|  |  |  | 	else if (hw_bufs_used >= priv->wsm_caps.input_buffers) | 
					
						
							|  |  |  | 		ret = 1; | 
					
						
							|  |  |  | 	if (!priv->hw_bufs_used) | 
					
						
							|  |  |  | 		wake_up(&priv->bh_evt_wq); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv, | 
					
						
							|  |  |  | 					  u16 *ctrl_reg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cw1200_reg_read_16(priv, | 
					
						
							|  |  |  | 			ST90TDS_CONTROL_REG_ID, ctrl_reg); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		ret = cw1200_reg_read_16(priv, | 
					
						
							|  |  |  | 				ST90TDS_CONTROL_REG_ID, ctrl_reg); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			pr_err("[BH] Failed to read control register.\n"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cw1200_device_wakeup(struct cw1200_common *priv) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u16 ctrl_reg; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("[BH] Device wakeup.\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* First, set the dpll register */ | 
					
						
							|  |  |  | 	ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID, | 
					
						
							|  |  |  | 				  cw1200_dpll_from_clk(priv->hw_refclk)); | 
					
						
							|  |  |  | 	if (WARN_ON(ret)) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* To force the device to be always-on, the host sets WLAN_UP to 1 */ | 
					
						
							|  |  |  | 	ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, | 
					
						
							|  |  |  | 			ST90TDS_CONT_WUP_BIT); | 
					
						
							|  |  |  | 	if (WARN_ON(ret)) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg); | 
					
						
							|  |  |  | 	if (WARN_ON(ret)) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If the device returns WLAN_RDY as 1, the device is active and will
 | 
					
						
							| 
									
										
										
										
											2013-06-11 09:49:40 -04:00
										 |  |  | 	 * remain active. | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 	if (ctrl_reg & ST90TDS_CONT_RDY_BIT) { | 
					
						
							|  |  |  | 		pr_debug("[BH] Device awake.\n"); | 
					
						
							|  |  |  | 		return 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Must be called from BH thraed. */ | 
					
						
							|  |  |  | void cw1200_enable_powersave(struct cw1200_common *priv, | 
					
						
							|  |  |  | 			     bool enable) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pr_debug("[BH] Powerave is %s.\n", | 
					
						
							|  |  |  | 		 enable ? "enabled" : "disabled"); | 
					
						
							|  |  |  | 	priv->powersave_enabled = enable; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cw1200_bh_rx_helper(struct cw1200_common *priv, | 
					
						
							|  |  |  | 			       uint16_t *ctrl_reg, | 
					
						
							|  |  |  | 			       int *tx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	size_t read_len = 0; | 
					
						
							|  |  |  | 	struct sk_buff *skb_rx = NULL; | 
					
						
							|  |  |  | 	struct wsm_hdr *wsm; | 
					
						
							|  |  |  | 	size_t wsm_len; | 
					
						
							|  |  |  | 	u16 wsm_id; | 
					
						
							|  |  |  | 	u8 wsm_seq; | 
					
						
							|  |  |  | 	int rx_resync = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	size_t alloc_len; | 
					
						
							|  |  |  | 	u8 *data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	read_len = (*ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2; | 
					
						
							|  |  |  | 	if (!read_len) | 
					
						
							|  |  |  | 		return 0; /* No more work */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (WARN_ON((read_len < sizeof(struct wsm_hdr)) || | 
					
						
							|  |  |  | 		    (read_len > EFFECTIVE_BUF_SIZE))) { | 
					
						
							|  |  |  | 		pr_debug("Invalid read len: %zu (%04x)", | 
					
						
							|  |  |  | 			 read_len, *ctrl_reg); | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Add SIZE of PIGGYBACK reg (CONTROL Reg)
 | 
					
						
							| 
									
										
										
										
											2013-06-11 09:49:40 -04:00
										 |  |  | 	 * to the NEXT Message length + 2 Bytes for SKB | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 	read_len = read_len + 2; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | 	alloc_len = priv->hwbus_ops->align_size( | 
					
						
							|  |  |  | 		priv->hwbus_priv, read_len); | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* Check if not exceeding CW1200 capabilities */ | 
					
						
							|  |  |  | 	if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) { | 
					
						
							|  |  |  | 		pr_debug("Read aligned len: %zu\n", | 
					
						
							|  |  |  | 			 alloc_len); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	skb_rx = dev_alloc_skb(alloc_len); | 
					
						
							|  |  |  | 	if (WARN_ON(!skb_rx)) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	skb_trim(skb_rx, 0); | 
					
						
							|  |  |  | 	skb_put(skb_rx, read_len); | 
					
						
							|  |  |  | 	data = skb_rx->data; | 
					
						
							|  |  |  | 	if (WARN_ON(!data)) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) { | 
					
						
							|  |  |  | 		pr_err("rx blew up, len %zu\n", alloc_len); | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Piggyback */ | 
					
						
							|  |  |  | 	*ctrl_reg = __le16_to_cpu( | 
					
						
							|  |  |  | 		((__le16 *)data)[alloc_len / 2 - 1]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wsm = (struct wsm_hdr *)data; | 
					
						
							|  |  |  | 	wsm_len = __le16_to_cpu(wsm->len); | 
					
						
							|  |  |  | 	if (WARN_ON(wsm_len > read_len)) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (priv->wsm_enable_wsm_dumps) | 
					
						
							|  |  |  | 		print_hex_dump_bytes("<-- ", | 
					
						
							|  |  |  | 				     DUMP_PREFIX_NONE, | 
					
						
							|  |  |  | 				     data, wsm_len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wsm_id  = __le16_to_cpu(wsm->id) & 0xFFF; | 
					
						
							|  |  |  | 	wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	skb_trim(skb_rx, wsm_len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (wsm_id == 0x0800) { | 
					
						
							|  |  |  | 		wsm_handle_exception(priv, | 
					
						
							|  |  |  | 				     &data[sizeof(*wsm)], | 
					
						
							|  |  |  | 				     wsm_len - sizeof(*wsm)); | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 	} else if (!rx_resync) { | 
					
						
							|  |  |  | 		if (WARN_ON(wsm_seq != priv->wsm_rx_seq)) | 
					
						
							|  |  |  | 			goto err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	priv->wsm_rx_seq = (wsm_seq + 1) & 7; | 
					
						
							|  |  |  | 	rx_resync = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (wsm_id & 0x0400) { | 
					
						
							|  |  |  | 		int rc = wsm_release_tx_buffer(priv, 1); | 
					
						
							|  |  |  | 		if (WARN_ON(rc < 0)) | 
					
						
							|  |  |  | 			return rc; | 
					
						
							|  |  |  | 		else if (rc > 0) | 
					
						
							|  |  |  | 			*tx = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* cw1200_wsm_rx takes care on SKB livetime */ | 
					
						
							|  |  |  | 	if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx))) | 
					
						
							|  |  |  | 		goto err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (skb_rx) { | 
					
						
							|  |  |  | 		dev_kfree_skb(skb_rx); | 
					
						
							|  |  |  | 		skb_rx = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | err: | 
					
						
							|  |  |  | 	if (skb_rx) { | 
					
						
							|  |  |  | 		dev_kfree_skb(skb_rx); | 
					
						
							|  |  |  | 		skb_rx = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return -1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cw1200_bh_tx_helper(struct cw1200_common *priv, | 
					
						
							|  |  |  | 			       int *pending_tx, | 
					
						
							|  |  |  | 			       int *tx_burst) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	size_t tx_len; | 
					
						
							|  |  |  | 	u8 *data; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	struct wsm_hdr *wsm; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (priv->device_can_sleep) { | 
					
						
							|  |  |  | 		ret = cw1200_device_wakeup(priv); | 
					
						
							|  |  |  | 		if (WARN_ON(ret < 0)) { /* Error in wakeup */ | 
					
						
							|  |  |  | 			*pending_tx = 1; | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 		} else if (ret) { /* Woke up */ | 
					
						
							|  |  |  | 			priv->device_can_sleep = false; | 
					
						
							|  |  |  | 		} else { /* Did not awake */ | 
					
						
							|  |  |  | 			*pending_tx = 1; | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wsm_alloc_tx_buffer(priv); | 
					
						
							|  |  |  | 	ret = wsm_get_tx(priv, &data, &tx_len, tx_burst); | 
					
						
							|  |  |  | 	if (ret <= 0) { | 
					
						
							|  |  |  | 		wsm_release_tx_buffer(priv, 1); | 
					
						
							|  |  |  | 		if (WARN_ON(ret < 0)) | 
					
						
							|  |  |  | 			return ret; /* Error */ | 
					
						
							|  |  |  | 		return 0; /* No work */ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wsm = (struct wsm_hdr *)data; | 
					
						
							|  |  |  | 	BUG_ON(tx_len < sizeof(*wsm)); | 
					
						
							|  |  |  | 	BUG_ON(__le16_to_cpu(wsm->len) != tx_len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	atomic_add(1, &priv->bh_tx); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | 	tx_len = priv->hwbus_ops->align_size( | 
					
						
							|  |  |  | 		priv->hwbus_priv, tx_len); | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* Check if not exceeding CW1200 capabilities */ | 
					
						
							|  |  |  | 	if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE)) | 
					
						
							|  |  |  | 		pr_debug("Write aligned len: %zu\n", tx_len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wsm->id &= __cpu_to_le16(0xffff ^ WSM_TX_SEQ(WSM_TX_SEQ_MAX)); | 
					
						
							|  |  |  | 	wsm->id |= __cpu_to_le16(WSM_TX_SEQ(priv->wsm_tx_seq)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (WARN_ON(cw1200_data_write(priv, data, tx_len))) { | 
					
						
							|  |  |  | 		pr_err("tx blew up, len %zu\n", tx_len); | 
					
						
							|  |  |  | 		wsm_release_tx_buffer(priv, 1); | 
					
						
							|  |  |  | 		return -1; /* Error */ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (priv->wsm_enable_wsm_dumps) | 
					
						
							|  |  |  | 		print_hex_dump_bytes("--> ", | 
					
						
							|  |  |  | 				     DUMP_PREFIX_NONE, | 
					
						
							|  |  |  | 				     data, | 
					
						
							|  |  |  | 				     __le16_to_cpu(wsm->len)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wsm_txed(priv, data); | 
					
						
							|  |  |  | 	priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (*tx_burst > 1) { | 
					
						
							|  |  |  | 		cw1200_debug_tx_burst(priv); | 
					
						
							|  |  |  | 		return 1; /* Work remains */ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cw1200_bh(void *arg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cw1200_common *priv = arg; | 
					
						
							|  |  |  | 	int rx, tx, term, suspend; | 
					
						
							|  |  |  | 	u16 ctrl_reg = 0; | 
					
						
							|  |  |  | 	int tx_allowed; | 
					
						
							|  |  |  | 	int pending_tx = 0; | 
					
						
							|  |  |  | 	int tx_burst; | 
					
						
							|  |  |  | 	long status; | 
					
						
							|  |  |  | 	u32 dummy; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (;;) { | 
					
						
							|  |  |  | 		if (!priv->hw_bufs_used && | 
					
						
							|  |  |  | 		    priv->powersave_enabled && | 
					
						
							|  |  |  | 		    !priv->device_can_sleep && | 
					
						
							|  |  |  | 		    !atomic_read(&priv->recent_scan)) { | 
					
						
							|  |  |  | 			status = 1 * HZ; | 
					
						
							|  |  |  | 			pr_debug("[BH] Device wakedown. No data.\n"); | 
					
						
							|  |  |  | 			cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, 0); | 
					
						
							|  |  |  | 			priv->device_can_sleep = true; | 
					
						
							|  |  |  | 		} else if (priv->hw_bufs_used) { | 
					
						
							|  |  |  | 			/* Interrupt loss detection */ | 
					
						
							|  |  |  | 			status = 1 * HZ; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			status = MAX_SCHEDULE_TIMEOUT; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Dummy Read for SDIO retry mechanism*/ | 
					
						
							|  |  |  | 		if ((priv->hw_type != -1) && | 
					
						
							|  |  |  | 		    (atomic_read(&priv->bh_rx) == 0) && | 
					
						
							|  |  |  | 		    (atomic_read(&priv->bh_tx) == 0)) | 
					
						
							|  |  |  | 			cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID, | 
					
						
							|  |  |  | 					&dummy, sizeof(dummy)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pr_debug("[BH] waiting ...\n"); | 
					
						
							|  |  |  | 		status = wait_event_interruptible_timeout(priv->bh_wq, ({ | 
					
						
							|  |  |  | 				rx = atomic_xchg(&priv->bh_rx, 0); | 
					
						
							|  |  |  | 				tx = atomic_xchg(&priv->bh_tx, 0); | 
					
						
							|  |  |  | 				term = atomic_xchg(&priv->bh_term, 0); | 
					
						
							|  |  |  | 				suspend = pending_tx ? | 
					
						
							|  |  |  | 					0 : atomic_read(&priv->bh_suspend); | 
					
						
							|  |  |  | 				(rx || tx || term || suspend || priv->bh_error); | 
					
						
							|  |  |  | 			}), status); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-27 20:17:13 -04:00
										 |  |  | 		pr_debug("[BH] - rx: %d, tx: %d, term: %d, bh_err: %d, suspend: %d, status: %ld\n", | 
					
						
							|  |  |  | 			 rx, tx, term, suspend, priv->bh_error, status); | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Did an error occur? */ | 
					
						
							|  |  |  | 		if ((status < 0 && status != -ERESTARTSYS) || | 
					
						
							|  |  |  | 		    term || priv->bh_error) { | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (!status) {  /* wait_event timed out */ | 
					
						
							|  |  |  | 			unsigned long timestamp = jiffies; | 
					
						
							|  |  |  | 			long timeout; | 
					
						
							|  |  |  | 			int pending = 0; | 
					
						
							|  |  |  | 			int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Check to see if we have any outstanding frames */ | 
					
						
							|  |  |  | 			if (priv->hw_bufs_used && (!rx || !tx)) { | 
					
						
							|  |  |  | 				wiphy_warn(priv->hw->wiphy, | 
					
						
							|  |  |  | 					   "Missed interrupt? (%d frames outstanding)\n", | 
					
						
							|  |  |  | 					   priv->hw_bufs_used); | 
					
						
							|  |  |  | 				rx = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				/* Get a timestamp of "oldest" frame */ | 
					
						
							|  |  |  | 				for (i = 0; i < 4; ++i) | 
					
						
							|  |  |  | 					pending += cw1200_queue_get_xmit_timestamp( | 
					
						
							|  |  |  | 						&priv->tx_queue[i], | 
					
						
							|  |  |  | 						×tamp, | 
					
						
							|  |  |  | 						priv->pending_frame_id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				/* Check if frame transmission is timed out.
 | 
					
						
							|  |  |  | 				 * Add an extra second with respect to possible | 
					
						
							|  |  |  | 				 * interrupt loss. | 
					
						
							|  |  |  | 				 */ | 
					
						
							|  |  |  | 				timeout = timestamp + | 
					
						
							|  |  |  | 					WSM_CMD_LAST_CHANCE_TIMEOUT + | 
					
						
							|  |  |  | 					1 * HZ  - | 
					
						
							|  |  |  | 					jiffies; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				/* And terminate BH thread if the frame is "stuck" */ | 
					
						
							|  |  |  | 				if (pending && timeout < 0) { | 
					
						
							|  |  |  | 					wiphy_warn(priv->hw->wiphy, | 
					
						
							|  |  |  | 						   "Timeout waiting for TX confirm (%d/%d pending, %ld vs %lu).\n", | 
					
						
							|  |  |  | 						   priv->hw_bufs_used, pending, | 
					
						
							|  |  |  | 						   timestamp, jiffies); | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else if (!priv->device_can_sleep && | 
					
						
							|  |  |  | 				   !atomic_read(&priv->recent_scan)) { | 
					
						
							|  |  |  | 				pr_debug("[BH] Device wakedown. Timeout.\n"); | 
					
						
							|  |  |  | 				cw1200_reg_write_16(priv, | 
					
						
							|  |  |  | 						    ST90TDS_CONTROL_REG_ID, 0); | 
					
						
							|  |  |  | 				priv->device_can_sleep = true; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			goto done; | 
					
						
							|  |  |  | 		} else if (suspend) { | 
					
						
							|  |  |  | 			pr_debug("[BH] Device suspend.\n"); | 
					
						
							|  |  |  | 			if (priv->powersave_enabled) { | 
					
						
							|  |  |  | 				pr_debug("[BH] Device wakedown. Suspend.\n"); | 
					
						
							|  |  |  | 				cw1200_reg_write_16(priv, | 
					
						
							|  |  |  | 						    ST90TDS_CONTROL_REG_ID, 0); | 
					
						
							|  |  |  | 				priv->device_can_sleep = true; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED); | 
					
						
							|  |  |  | 			wake_up(&priv->bh_evt_wq); | 
					
						
							|  |  |  | 			status = wait_event_interruptible(priv->bh_wq, | 
					
						
							|  |  |  | 							  CW1200_BH_RESUME == atomic_read(&priv->bh_suspend)); | 
					
						
							|  |  |  | 			if (status < 0) { | 
					
						
							|  |  |  | 				wiphy_err(priv->hw->wiphy, | 
					
						
							|  |  |  | 					  "Failed to wait for resume: %ld.\n", | 
					
						
							|  |  |  | 					  status); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			pr_debug("[BH] Device resume.\n"); | 
					
						
							|  |  |  | 			atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED); | 
					
						
							|  |  |  | 			wake_up(&priv->bh_evt_wq); | 
					
						
							|  |  |  | 			atomic_add(1, &priv->bh_rx); | 
					
						
							|  |  |  | 			goto done; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rx: | 
					
						
							|  |  |  | 		tx += pending_tx; | 
					
						
							|  |  |  | 		pending_tx = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg)) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Don't bother trying to rx unless we have data to read */ | 
					
						
							|  |  |  | 		if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) { | 
					
						
							|  |  |  | 			ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx); | 
					
						
							|  |  |  | 			if (ret < 0) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			/* Double up here if there's more data.. */ | 
					
						
							|  |  |  | 			if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) { | 
					
						
							|  |  |  | 				ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx); | 
					
						
							|  |  |  | 				if (ret < 0) | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tx: | 
					
						
							|  |  |  | 		if (tx) { | 
					
						
							|  |  |  | 			tx = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			BUG_ON(priv->hw_bufs_used > priv->wsm_caps.input_buffers); | 
					
						
							|  |  |  | 			tx_burst = priv->wsm_caps.input_buffers - priv->hw_bufs_used; | 
					
						
							|  |  |  | 			tx_allowed = tx_burst > 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (!tx_allowed) { | 
					
						
							|  |  |  | 				/* Buffers full.  Ensure we process tx
 | 
					
						
							|  |  |  | 				 * after we handle rx.. | 
					
						
							|  |  |  | 				 */ | 
					
						
							|  |  |  | 				pending_tx = tx; | 
					
						
							|  |  |  | 				goto done_rx; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			ret = cw1200_bh_tx_helper(priv, &pending_tx, &tx_burst); | 
					
						
							|  |  |  | 			if (ret < 0) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			if (ret > 0) /* More to transmit */ | 
					
						
							|  |  |  | 				tx = ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Re-read ctrl reg */ | 
					
						
							|  |  |  | 			if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg)) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	done_rx: | 
					
						
							|  |  |  | 		if (priv->bh_error) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) | 
					
						
							|  |  |  | 			goto rx; | 
					
						
							|  |  |  | 		if (tx) | 
					
						
							|  |  |  | 			goto tx; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	done: | 
					
						
							|  |  |  | 		/* Re-enable device interrupts */ | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | 		priv->hwbus_ops->lock(priv->hwbus_priv); | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 		__cw1200_irq_enable(priv, 1); | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | 		priv->hwbus_ops->unlock(priv->hwbus_priv); | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Explicitly disable device interrupts */ | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | 	priv->hwbus_ops->lock(priv->hwbus_priv); | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 	__cw1200_irq_enable(priv, 0); | 
					
						
							| 
									
										
										
										
											2013-06-01 08:08:42 -04:00
										 |  |  | 	priv->hwbus_ops->unlock(priv->hwbus_priv); | 
					
						
							| 
									
										
										
										
											2013-05-24 20:04:38 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (!term) { | 
					
						
							|  |  |  | 		pr_err("[BH] Fatal error, exiting.\n"); | 
					
						
							|  |  |  | 		priv->bh_error = 1; | 
					
						
							|  |  |  | 		/* TODO: schedule_work(recovery) */ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } |