When trying to debug an interrupt delivery problem I noticed that not all of the wakeup conditions on the worker thread were included in the debug message. This patch rectifies that. Signed-off-by: Solomon Peachy <pizza@shaftnet.org> Signed-off-by: John W. Linville <linville@tuxdriver.com>
		
			
				
	
	
		
			619 lines
		
	
	
	
		
			14 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			619 lines
		
	
	
	
		
			14 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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"
 | 
						|
#include "hwbus.h"
 | 
						|
#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
 | 
						|
 * "*2" to cvt to bytes
 | 
						|
 */
 | 
						|
#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! */
 | 
						|
	/* NOTE:  hwbus_ops->lock already held */
 | 
						|
	__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
 | 
						|
	 * remain active.
 | 
						|
	 */
 | 
						|
	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)
 | 
						|
	 * to the NEXT Message length + 2 Bytes for SKB
 | 
						|
	 */
 | 
						|
	read_len = read_len + 2;
 | 
						|
 | 
						|
	alloc_len = priv->hwbus_ops->align_size(
 | 
						|
		priv->hwbus_priv, read_len);
 | 
						|
 | 
						|
	/* 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);
 | 
						|
 | 
						|
	tx_len = priv->hwbus_ops->align_size(
 | 
						|
		priv->hwbus_priv, tx_len);
 | 
						|
 | 
						|
	/* 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);
 | 
						|
 | 
						|
		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);
 | 
						|
 | 
						|
		/* 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 */
 | 
						|
		priv->hwbus_ops->lock(priv->hwbus_priv);
 | 
						|
		__cw1200_irq_enable(priv, 1);
 | 
						|
		priv->hwbus_ops->unlock(priv->hwbus_priv);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Explicitly disable device interrupts */
 | 
						|
	priv->hwbus_ops->lock(priv->hwbus_priv);
 | 
						|
	__cw1200_irq_enable(priv, 0);
 | 
						|
	priv->hwbus_ops->unlock(priv->hwbus_priv);
 | 
						|
 | 
						|
	if (!term) {
 | 
						|
		pr_err("[BH] Fatal error, exiting.\n");
 | 
						|
		priv->bh_error = 1;
 | 
						|
		/* TODO: schedule_work(recovery) */
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 |