// SPDX-License-Identifier: GPL-2.0-only /* * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers * * Copyright (c) 2010, ST-Ericsson * Author: Dmitry Tarnyagin * * Based on: * ST-Ericsson UMAC CW1200 driver, which is * Copyright (c) 2010, ST-Ericsson * Author: Ajitpal Singh */ #include #include #include #include #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; }