2012-03-02 20:26:46

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Please find below in the patchset implementation of a new driver for
the ST-Ericsson cw1200 wlan device.

Signed-off-by: Dmitry Tarnyagin <[email protected]>


2012-03-02 20:27:00

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 12/21] cw1200: sta.*, mac80211 STA callbacks.

The code implements mac80211 STA callbacks.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/sta.c | 1542 +++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/sta.h | 87 +++
2 files changed, 1629 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/sta.c
create mode 100644 drivers/net/wireless/cw1200/sta.h

diff --git a/drivers/net/wireless/cw1200/sta.c b/drivers/net/wireless/cw1200/sta.c
new file mode 100644
index 0000000..e741bd4
--- /dev/null
+++ b/drivers/net/wireless/cw1200/sta.c
@@ -0,0 +1,1542 @@
+/*
+ * Mac80211 STA API for ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/firmware.h>
+
+#include "cw1200.h"
+#include "sta.h"
+#include "ap.h"
+#include "fwio.h"
+#include "bh.h"
+#include "debug.h"
+
+#if defined(CONFIG_CW1200_STA_DEBUG)
+#define sta_printk(...) printk(__VA_ARGS__)
+#else
+#define sta_printk(...)
+#endif
+
+static inline void __cw1200_free_event_queue(struct list_head *list)
+{
+ while (!list_empty(list)) {
+ struct cw1200_wsm_event *event =
+ list_first_entry(list, struct cw1200_wsm_event,
+ link);
+ list_del(&event->link);
+ kfree(event);
+ }
+}
+
+/* ******************************************************************** */
+/* STA API */
+
+int cw1200_start(struct ieee80211_hw *dev)
+{
+ struct cw1200_common *priv = dev->priv;
+ int ret = 0;
+
+ mutex_lock(&priv->conf_mutex);
+
+ /* default EDCA */
+ WSM_EDCA_SET(&priv->edca, 0, 0x0002, 0x0003, 0x0007, 47, 0xc8, false);
+ WSM_EDCA_SET(&priv->edca, 1, 0x0002, 0x0007, 0x000f, 94, 0xc8, false);
+ WSM_EDCA_SET(&priv->edca, 2, 0x0003, 0x000f, 0x03ff, 0, 0xc8, false);
+ WSM_EDCA_SET(&priv->edca, 3, 0x0007, 0x000f, 0x03ff, 0, 0xc8, false);
+ ret = wsm_set_edca_params(priv, &priv->edca);
+ if (WARN_ON(ret))
+ goto out;
+
+ ret = cw1200_set_uapsd_param(priv, &priv->edca);
+ if (WARN_ON(ret))
+ goto out;
+
+ priv->setbssparams_done = false;
+
+ memset(priv->bssid, ~0, ETH_ALEN);
+ memcpy(priv->mac_addr, dev->wiphy->perm_addr, ETH_ALEN);
+ priv->mode = NL80211_IFTYPE_MONITOR;
+ priv->softled_state = 0;
+ priv->wep_default_key_id = -1;
+
+ priv->cqm_link_loss_count = 60;
+ priv->cqm_beacon_loss_count = 20;
+
+ /* Temporary configuration - beacon filter table */
+ priv->bf_table.numOfIEs = __cpu_to_le32(2);
+ priv->bf_table.entry[0].ieId = WLAN_EID_VENDOR_SPECIFIC;
+ priv->bf_table.entry[0].actionFlags = WSM_BEACON_FILTER_IE_HAS_CHANGED |
+ WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT |
+ WSM_BEACON_FILTER_IE_HAS_APPEARED;
+ priv->bf_table.entry[0].oui[0] = 0x50;
+ priv->bf_table.entry[0].oui[1] = 0x6F;
+ priv->bf_table.entry[0].oui[2] = 0x9A;
+ priv->bf_table.entry[1].ieId = WLAN_EID_ERP_INFO;
+ priv->bf_table.entry[1].actionFlags = WSM_BEACON_FILTER_IE_HAS_CHANGED |
+ WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT |
+ WSM_BEACON_FILTER_IE_HAS_APPEARED;
+
+ priv->bf_control.enabled = 1;
+ ret = cw1200_setup_mac(priv);
+ if (WARN_ON(ret))
+ goto out;
+
+ /* err = cw1200_set_leds(priv); */
+
+out:
+ mutex_unlock(&priv->conf_mutex);
+ return ret;
+}
+
+void cw1200_stop(struct ieee80211_hw *dev)
+{
+ struct cw1200_common *priv = dev->priv;
+ LIST_HEAD(list);
+ int i;
+
+ wsm_lock_tx(priv);
+
+ while (down_trylock(&priv->scan.lock)) {
+ /* Scan is in progress. Force it to stop. */
+ priv->scan.req = NULL;
+ schedule();
+ }
+ up(&priv->scan.lock);
+
+ cancel_delayed_work_sync(&priv->scan.probe_work);
+ cancel_delayed_work_sync(&priv->scan.timeout);
+ cancel_delayed_work_sync(&priv->join_timeout);
+ cancel_delayed_work_sync(&priv->bss_loss_work);
+ cancel_delayed_work_sync(&priv->connection_loss_work);
+ cancel_delayed_work_sync(&priv->link_id_gc_work);
+ flush_workqueue(priv->workqueue);
+ del_timer_sync(&priv->mcast_timeout);
+ del_timer_sync(&priv->ba_timer);
+
+ mutex_lock(&priv->conf_mutex);
+ priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+ priv->listening = false;
+
+ priv->softled_state = 0;
+ /* cw1200_set_leds(priv); */
+
+ spin_lock(&priv->event_queue_lock);
+ list_splice_init(&priv->event_queue, &list);
+ spin_unlock(&priv->event_queue_lock);
+ __cw1200_free_event_queue(&list);
+
+ priv->delayed_link_loss = 0;
+
+ priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
+
+ for (i = 0; i < 4; i++)
+ cw1200_queue_clear(&priv->tx_queue[i]);
+
+ /* HACK! */
+ if (atomic_xchg(&priv->tx_lock, 1) != 1)
+ sta_printk(KERN_DEBUG "[STA] TX is force-unlocked "
+ "due to stop request.\n");
+
+ wsm_unlock_tx(priv);
+
+ mutex_unlock(&priv->conf_mutex);
+}
+
+int cw1200_add_interface(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif)
+{
+ int ret;
+ struct cw1200_common *priv = dev->priv;
+ /* __le32 auto_calibration_mode = __cpu_to_le32(1); */
+
+ mutex_lock(&priv->conf_mutex);
+
+ if (priv->mode != NL80211_IFTYPE_MONITOR) {
+ mutex_unlock(&priv->conf_mutex);
+ return -EOPNOTSUPP;
+ }
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_MESH_POINT:
+ case NL80211_IFTYPE_AP:
+ priv->mode = vif->type;
+ break;
+ default:
+ mutex_unlock(&priv->conf_mutex);
+ return -EOPNOTSUPP;
+ }
+
+ priv->vif = vif;
+ memcpy(priv->mac_addr, vif->addr, ETH_ALEN);
+
+ ret = WARN_ON(cw1200_setup_mac(priv));
+ /* Enable auto-calibration */
+ /* Exception in subsequent channel switch; disabled.
+ WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE,
+ &auto_calibration_mode, sizeof(auto_calibration_mode)));
+ */
+
+ mutex_unlock(&priv->conf_mutex);
+ return ret;
+}
+
+void cw1200_remove_interface(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif)
+{
+ struct cw1200_common *priv = dev->priv;
+ struct wsm_reset reset = {
+ .reset_statistics = true,
+ };
+ int i;
+
+ mutex_lock(&priv->conf_mutex);
+ wsm_lock_tx(priv);
+ switch (priv->join_status) {
+ case CW1200_JOIN_STATUS_STA:
+ wsm_lock_tx(priv);
+ if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
+ wsm_unlock_tx(priv);
+ break;
+ case CW1200_JOIN_STATUS_AP:
+ for (i = 0; priv->link_id_map; ++i) {
+ if (priv->link_id_map & BIT(i)) {
+ reset.link_id = i;
+ wsm_reset(priv, &reset);
+ priv->link_id_map &= ~BIT(i);
+ }
+ }
+ memset(priv->link_id_db, 0,
+ sizeof(priv->link_id_db));
+ priv->sta_asleep_mask = 0;
+ priv->enable_beacon = false;
+ priv->tx_multicast = false;
+ priv->aid0_bit_set = false;
+ priv->buffered_multicasts = false;
+ priv->pspoll_mask = 0;
+ reset.link_id = 0;
+ wsm_reset(priv, &reset);
+ break;
+ case CW1200_JOIN_STATUS_MONITOR:
+ cw1200_update_listening(priv, false);
+ break;
+ default:
+ break;
+ }
+ priv->vif = NULL;
+ priv->mode = NL80211_IFTYPE_MONITOR;
+ memset(priv->mac_addr, 0, ETH_ALEN);
+ memset(priv->bssid, 0, ETH_ALEN);
+ cw1200_free_keys(priv);
+ cw1200_setup_mac(priv);
+ priv->listening = false;
+ priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
+ if (!__cw1200_flush(priv, true))
+ wsm_unlock_tx(priv);
+ wsm_unlock_tx(priv);
+
+ mutex_unlock(&priv->conf_mutex);
+}
+
+int cw1200_config(struct ieee80211_hw *dev, u32 changed)
+{
+ int ret = 0;
+ struct cw1200_common *priv = dev->priv;
+ struct ieee80211_conf *conf = &dev->conf;
+
+ down(&priv->scan.lock);
+ mutex_lock(&priv->conf_mutex);
+ /* TODO: IEEE80211_CONF_CHANGE_QOS */
+ if (changed & IEEE80211_CONF_CHANGE_POWER) {
+ priv->output_power = conf->power_level;
+ sta_printk(KERN_DEBUG "[STA] TX power: %d\n",
+ priv->output_power);
+ WARN_ON(wsm_set_output_power(priv, priv->output_power * 10));
+ }
+
+ if ((changed & IEEE80211_CONF_CHANGE_CHANNEL) &&
+ (priv->channel != conf->channel)) {
+ struct ieee80211_channel *ch = conf->channel;
+ struct wsm_switch_channel channel = {
+ .newChannelNumber = ch->hw_value,
+ };
+ sta_printk(KERN_DEBUG "[STA] Freq %d (wsm ch: %d).\n",
+ ch->center_freq, ch->hw_value);
+
+ ret = WARN_ON(__cw1200_flush(priv, false));
+ if (!ret) {
+ ret = WARN_ON(wsm_switch_channel(priv, &channel));
+ if (!ret) {
+ ret = wait_event_timeout(
+ priv->channel_switch_done,
+ !priv->channel_switch_in_progress,
+ 3 * HZ);
+ /* TODO: We should check also switch channel
+ * complete indication
+ */
+ if (ret) {
+ priv->channel = ch;
+ ret = 0;
+ } else
+ ret = -ETIMEDOUT;
+ } else
+ wsm_unlock_tx(priv);
+ }
+ }
+
+ if (changed & IEEE80211_CONF_CHANGE_PS) {
+ if (!(conf->flags & IEEE80211_CONF_PS))
+ priv->powersave_mode.pmMode = WSM_PSM_ACTIVE;
+ else if (conf->dynamic_ps_timeout <= 0)
+ priv->powersave_mode.pmMode = WSM_PSM_PS;
+ else
+ priv->powersave_mode.pmMode = WSM_PSM_FAST_PS;
+
+ /* Firmware requires that value for this 1-byte field must
+ * be specified in units of 500us. Values above the 128ms
+ * threshold are not supported. */
+ if (conf->dynamic_ps_timeout >= 0x80)
+ priv->powersave_mode.fastPsmIdlePeriod = 0xFF;
+ else
+ priv->powersave_mode.fastPsmIdlePeriod =
+ conf->dynamic_ps_timeout << 1;
+
+ if (priv->join_status == CW1200_JOIN_STATUS_STA &&
+ priv->bss_params.aid)
+ cw1200_set_pm(priv, &priv->powersave_mode);
+ }
+
+ if (changed & IEEE80211_CONF_CHANGE_MONITOR) {
+ /* TBD: It looks like it's transparent
+ * there's a monitor interface present -- use this
+ * to determine for example whether to calculate
+ * timestamps for packets or not, do not use instead
+ * of filter flags! */
+ }
+
+ if (changed & IEEE80211_CONF_CHANGE_IDLE) {
+ struct wsm_operational_mode mode = {
+ .power_mode = wsm_power_mode_quiescent,
+ .disableMoreFlagUsage = true,
+ };
+
+ wsm_lock_tx(priv);
+ /* Disable p2p-dev mode forced by TX request */
+ if ((priv->join_status == CW1200_JOIN_STATUS_MONITOR) &&
+ (conf->flags & IEEE80211_CONF_IDLE) &&
+ !priv->listening) {
+ cw1200_disable_listening(priv);
+ priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
+ }
+ WARN_ON(wsm_set_operational_mode(priv, &mode));
+ wsm_unlock_tx(priv);
+ }
+
+ if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) {
+ sta_printk(KERN_DEBUG "[STA] Retry limits: %d (long), " \
+ "%d (short).\n",
+ conf->long_frame_max_tx_count,
+ conf->short_frame_max_tx_count);
+ spin_lock_bh(&priv->tx_policy_cache.lock);
+ priv->long_frame_max_tx_count = conf->long_frame_max_tx_count;
+ priv->short_frame_max_tx_count =
+ (conf->short_frame_max_tx_count < 0x0F) ?
+ conf->short_frame_max_tx_count : 0x0F;
+ priv->hw->max_rate_tries = priv->short_frame_max_tx_count;
+ spin_unlock_bh(&priv->tx_policy_cache.lock);
+ /* TBD: I think we don't need tx_policy_force_upload().
+ * Outdated policies will leave cache in a normal way. */
+ /* WARN_ON(tx_policy_force_upload(priv)); */
+ }
+ mutex_unlock(&priv->conf_mutex);
+ up(&priv->scan.lock);
+ return ret;
+}
+
+void cw1200_update_filtering(struct cw1200_common *priv)
+{
+ int ret;
+ bool bssid_filtering = !priv->rx_filter.bssid;
+ static struct wsm_beacon_filter_control bf_disabled = {
+ .enabled = 0,
+ .bcn_count = 1,
+ };
+
+ if (priv->join_status == CW1200_JOIN_STATUS_PASSIVE)
+ return;
+ else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR)
+ bssid_filtering = false;
+
+ /*
+ * When acting as p2p client being connected to p2p GO, in order to
+ * receive frames from a different p2p device, turn off bssid filter.
+ *
+ * WARNING: FW dependency!
+ * This can only be used with FW WSM371 and its successors.
+ * In that FW version even with bssid filter turned off,
+ * device will block most of the unwanted frames.
+ */
+ if (priv->vif->p2p)
+ bssid_filtering = false;
+
+ ret = wsm_set_rx_filter(priv, &priv->rx_filter);
+ if (!ret)
+ ret = wsm_set_beacon_filter_table(priv, &priv->bf_table);
+ if (!ret) {
+ if (priv->disable_beacon_filter)
+ ret = wsm_beacon_filter_control(priv,
+ &bf_disabled);
+ else
+ ret = wsm_beacon_filter_control(priv,
+ &priv->bf_control);
+ }
+ if (!ret)
+ ret = wsm_set_bssid_filtering(priv, bssid_filtering);
+ if (!ret)
+ ret = wsm_set_multicast_filter(priv, &priv->multicast_filter);
+ if (ret)
+ wiphy_err(priv->hw->wiphy,
+ "%s: Update filtering failed: %d.\n",
+ __func__, ret);
+ return;
+}
+
+void cw1200_update_filtering_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common,
+ update_filtering_work);
+
+ cw1200_update_filtering(priv);
+}
+
+u64 cw1200_prepare_multicast(struct ieee80211_hw *hw,
+ struct netdev_hw_addr_list *mc_list)
+{
+ static u8 broadcast_ipv6[ETH_ALEN] = {
+ 0x33, 0x33, 0x00, 0x00, 0x00, 0x01
+ };
+ static u8 broadcast_ipv4[ETH_ALEN] = {
+ 0x01, 0x00, 0x5e, 0x00, 0x00, 0x01
+ };
+ struct cw1200_common *priv = hw->priv;
+ struct netdev_hw_addr *ha;
+ int count = 0;
+
+ /* Disable multicast filtering */
+ priv->has_multicast_subscription = false;
+ memset(&priv->multicast_filter, 0x00, sizeof(priv->multicast_filter));
+
+ if (netdev_hw_addr_list_count(mc_list) > WSM_MAX_GRP_ADDRTABLE_ENTRIES)
+ return 0;
+
+ /* Enable if requested */
+ netdev_hw_addr_list_for_each(ha, mc_list) {
+ sta_printk(KERN_DEBUG "[STA] multicast: %pM\n", ha->addr);
+ memcpy(&priv->multicast_filter.macAddress[count],
+ ha->addr, ETH_ALEN);
+ if (memcmp(ha->addr, broadcast_ipv4, ETH_ALEN) &&
+ memcmp(ha->addr, broadcast_ipv6, ETH_ALEN))
+ priv->has_multicast_subscription = true;
+ count++;
+ }
+
+ if (count) {
+ priv->multicast_filter.enable = __cpu_to_le32(1);
+ priv->multicast_filter.numOfAddresses = __cpu_to_le32(count);
+ }
+
+ return netdev_hw_addr_list_count(mc_list);
+}
+
+void cw1200_configure_filter(struct ieee80211_hw *dev,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast)
+{
+ struct cw1200_common *priv = dev->priv;
+ bool listening = !!(*total_flags &
+ (FIF_PROMISC_IN_BSS |
+ FIF_OTHER_BSS |
+ FIF_BCN_PRBRESP_PROMISC |
+ FIF_PROBE_REQ));
+
+ *total_flags &= FIF_PROMISC_IN_BSS |
+ FIF_OTHER_BSS |
+ FIF_FCSFAIL |
+ FIF_BCN_PRBRESP_PROMISC |
+ FIF_PROBE_REQ;
+
+ down(&priv->scan.lock);
+ mutex_lock(&priv->conf_mutex);
+
+ priv->rx_filter.promiscuous = (*total_flags & FIF_PROMISC_IN_BSS)
+ ? 1 : 0;
+ priv->rx_filter.bssid = (*total_flags & (FIF_OTHER_BSS |
+ FIF_PROBE_REQ)) ? 1 : 0;
+ priv->rx_filter.fcs = (*total_flags & FIF_FCSFAIL) ? 1 : 0;
+ priv->bf_control.bcn_count = (*total_flags &
+ (FIF_BCN_PRBRESP_PROMISC |
+ FIF_PROMISC_IN_BSS |
+ FIF_PROBE_REQ)) ? 1 : 0;
+ if (priv->listening ^ listening) {
+ priv->listening = listening;
+ wsm_lock_tx(priv);
+ cw1200_update_listening(priv, listening);
+ wsm_unlock_tx(priv);
+ }
+ cw1200_update_filtering(priv);
+ mutex_unlock(&priv->conf_mutex);
+ up(&priv->scan.lock);
+}
+
+int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
+ u16 queue, const struct ieee80211_tx_queue_params *params)
+{
+ struct cw1200_common *priv = dev->priv;
+ int ret = 0;
+ /* To prevent re-applying PM request OID again and again*/
+ bool old_uapsdFlags;
+
+ mutex_lock(&priv->conf_mutex);
+
+ if (queue < dev->queues) {
+ old_uapsdFlags = priv->uapsd_info.uapsdFlags;
+
+ WSM_TX_QUEUE_SET(&priv->tx_queue_params, queue, 0, 0, 0);
+ ret = wsm_set_tx_queue_params(priv,
+ &priv->tx_queue_params.params[queue], queue);
+ if (ret) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ WSM_EDCA_SET(&priv->edca, queue, params->aifs,
+ params->cw_min, params->cw_max, params->txop, 0xc8,
+ params->uapsd);
+ ret = wsm_set_edca_params(priv, &priv->edca);
+ if (ret) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (priv->mode == NL80211_IFTYPE_STATION) {
+ ret = cw1200_set_uapsd_param(priv, &priv->edca);
+ if (!ret && priv->setbssparams_done &&
+ (priv->join_status == CW1200_JOIN_STATUS_STA) &&
+ (old_uapsdFlags != priv->uapsd_info.uapsdFlags))
+ cw1200_set_pm(priv, &priv->powersave_mode);
+ }
+ } else
+ ret = -EINVAL;
+
+out:
+ mutex_unlock(&priv->conf_mutex);
+ return ret;
+}
+
+int cw1200_get_stats(struct ieee80211_hw *dev,
+ struct ieee80211_low_level_stats *stats)
+{
+ struct cw1200_common *priv = dev->priv;
+
+ memcpy(stats, &priv->stats, sizeof(*stats));
+ return 0;
+}
+
+/*
+int cw1200_get_tx_stats(struct ieee80211_hw *dev,
+ struct ieee80211_tx_queue_stats *stats)
+{
+ int i;
+ struct cw1200_common *priv = dev->priv;
+
+ for (i = 0; i < dev->queues; ++i)
+ cw1200_queue_get_stats(&priv->tx_queue[i], &stats[i]);
+
+ return 0;
+}
+*/
+
+int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg)
+{
+ struct wsm_set_pm pm = *arg;
+
+ if (priv->uapsd_info.uapsdFlags != 0)
+ pm.pmMode &= ~WSM_PSM_FAST_PS_FLAG;
+
+ if (memcmp(&pm, &priv->firmware_ps_mode,
+ sizeof(struct wsm_set_pm))) {
+ priv->firmware_ps_mode = pm;
+ return wsm_set_pm(priv, &pm);
+ } else {
+ return 0;
+ }
+}
+
+int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd,
+ struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key)
+{
+ int ret = -EOPNOTSUPP;
+ struct cw1200_common *priv = dev->priv;
+
+ mutex_lock(&priv->conf_mutex);
+
+ if (cmd == SET_KEY) {
+ u8 *peer_addr = NULL;
+ int pairwise = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
+ 1 : 0;
+ int idx = cw1200_alloc_key(priv);
+ struct wsm_add_key *wsm_key = &priv->keys[idx];
+
+ if (idx < 0) {
+ ret = -EINVAL;
+ goto finally;
+ }
+
+ BUG_ON(pairwise && !sta);
+ if (sta)
+ peer_addr = sta->addr;
+
+ switch (key->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ if (key->keylen > 16) {
+ cw1200_free_key(priv, idx);
+ ret = -EINVAL;
+ goto finally;
+ }
+
+ if (pairwise) {
+ wsm_key->type = WSM_KEY_TYPE_WEP_PAIRWISE;
+ memcpy(wsm_key->wepPairwiseKey.peerAddress,
+ peer_addr, ETH_ALEN);
+ memcpy(wsm_key->wepPairwiseKey.keyData,
+ &key->key[0], key->keylen);
+ wsm_key->wepPairwiseKey.keyLength = key->keylen;
+ } else {
+ wsm_key->type = WSM_KEY_TYPE_WEP_DEFAULT;
+ memcpy(wsm_key->wepGroupKey.keyData,
+ &key->key[0], key->keylen);
+ wsm_key->wepGroupKey.keyLength = key->keylen;
+ wsm_key->wepGroupKey.keyId = key->keyidx;
+ }
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ if (pairwise) {
+ wsm_key->type = WSM_KEY_TYPE_TKIP_PAIRWISE;
+ memcpy(wsm_key->tkipPairwiseKey.peerAddress,
+ peer_addr, ETH_ALEN);
+ memcpy(wsm_key->tkipPairwiseKey.tkipKeyData,
+ &key->key[0], 16);
+ memcpy(wsm_key->tkipPairwiseKey.txMicKey,
+ &key->key[16], 8);
+ memcpy(wsm_key->tkipPairwiseKey.rxMicKey,
+ &key->key[24], 8);
+ } else {
+ size_t mic_offset =
+ (priv->mode == NL80211_IFTYPE_AP) ?
+ 16 : 24;
+ wsm_key->type = WSM_KEY_TYPE_TKIP_GROUP;
+ memcpy(wsm_key->tkipGroupKey.tkipKeyData,
+ &key->key[0], 16);
+ memcpy(wsm_key->tkipGroupKey.rxMicKey,
+ &key->key[mic_offset], 8);
+
+ /* TODO: Where can I find TKIP SEQ? */
+ memset(wsm_key->tkipGroupKey.rxSeqCounter,
+ 0, 8);
+ wsm_key->tkipGroupKey.keyId = key->keyidx;
+
+ print_hex_dump_bytes("TKIP: ", DUMP_PREFIX_NONE,
+ key->key, key->keylen);
+ }
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ if (pairwise) {
+ wsm_key->type = WSM_KEY_TYPE_AES_PAIRWISE;
+ memcpy(wsm_key->aesPairwiseKey.peerAddress,
+ peer_addr, ETH_ALEN);
+ memcpy(wsm_key->aesPairwiseKey.aesKeyData,
+ &key->key[0], 16);
+ } else {
+ wsm_key->type = WSM_KEY_TYPE_AES_GROUP;
+ memcpy(wsm_key->aesGroupKey.aesKeyData,
+ &key->key[0], 16);
+ /* TODO: Where can I find AES SEQ? */
+ memset(wsm_key->aesGroupKey.rxSeqCounter,
+ 0, 8);
+ wsm_key->aesGroupKey.keyId = key->keyidx;
+ }
+ break;
+ case WLAN_CIPHER_SUITE_SMS4:
+ if (pairwise) {
+ wsm_key->type = WSM_KEY_TYPE_WAPI_PAIRWISE;
+ memcpy(wsm_key->wapiPairwiseKey.peerAddress,
+ peer_addr, ETH_ALEN);
+ memcpy(wsm_key->wapiPairwiseKey.wapiKeyData,
+ &key->key[0], 16);
+ memcpy(wsm_key->wapiPairwiseKey.micKeyData,
+ &key->key[16], 16);
+ wsm_key->wapiPairwiseKey.keyId = key->keyidx;
+ } else {
+ wsm_key->type = WSM_KEY_TYPE_WAPI_GROUP;
+ memcpy(wsm_key->wapiGroupKey.wapiKeyData,
+ &key->key[0], 16);
+ memcpy(wsm_key->wapiGroupKey.micKeyData,
+ &key->key[16], 16);
+ wsm_key->wapiGroupKey.keyId = key->keyidx;
+ }
+ break;
+ default:
+ WARN_ON(1);
+ cw1200_free_key(priv, idx);
+ ret = -EOPNOTSUPP;
+ goto finally;
+ }
+ ret = WARN_ON(wsm_add_key(priv, wsm_key));
+ if (!ret)
+ key->hw_key_idx = idx;
+ else
+ cw1200_free_key(priv, idx);
+ } else if (cmd == DISABLE_KEY) {
+ struct wsm_remove_key wsm_key = {
+ .entryIndex = key->hw_key_idx,
+ };
+
+ if (wsm_key.entryIndex > WSM_KEY_MAX_INDEX) {
+ ret = -EINVAL;
+ goto finally;
+ }
+
+ cw1200_free_key(priv, wsm_key.entryIndex);
+ ret = wsm_remove_key(priv, &wsm_key);
+ } else {
+ BUG_ON("Unsupported command");
+ }
+
+finally:
+ mutex_unlock(&priv->conf_mutex);
+ return ret;
+}
+
+void cw1200_wep_key_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, wep_key_work);
+ u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id);
+ struct cw1200_queue *queue = &priv->tx_queue[queueId];
+ __le32 wep_default_key_id = __cpu_to_le32(
+ priv->wep_default_key_id);
+
+ BUG_ON(queueId >= 4);
+
+ sta_printk(KERN_DEBUG "[STA] Setting default WEP key: %d\n",
+ priv->wep_default_key_id);
+ wsm_flush_tx(priv);
+ WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID,
+ &wep_default_key_id, sizeof(wep_default_key_id)));
+ cw1200_queue_requeue(queue, priv->pending_frame_id);
+ wsm_unlock_tx(priv);
+}
+
+int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
+{
+ int ret;
+ __le32 val32;
+
+ if (value != (u32) -1)
+ val32 = __cpu_to_le32(value);
+ else
+ val32 = 0; /* disabled */
+
+ /* mutex_lock(&priv->conf_mutex); */
+ ret = WARN_ON(wsm_write_mib(hw->priv, WSM_MIB_ID_DOT11_RTS_THRESHOLD,
+ &val32, sizeof(val32)));
+ /* mutex_unlock(&priv->conf_mutex); */
+ return ret;
+}
+
+int __cw1200_flush(struct cw1200_common *priv, bool drop)
+{
+ int i, ret;
+
+ for (;;) {
+ /* TODO: correct flush handling is required when dev_stop.
+ * Temporary workaround: 2s
+ */
+ if (drop) {
+ for (i = 0; i < 4; ++i)
+ cw1200_queue_clear(&priv->tx_queue[i]);
+ } else {
+ ret = wait_event_timeout(
+ priv->tx_queue_stats.wait_link_id_empty,
+ cw1200_queue_stats_is_empty(
+ &priv->tx_queue_stats, -1),
+ 2 * HZ);
+ }
+
+ if (!drop && unlikely(ret <= 0)) {
+ ret = -ETIMEDOUT;
+ break;
+ } else {
+ ret = 0;
+ }
+
+ wsm_lock_tx(priv);
+ if (unlikely(!cw1200_queue_stats_is_empty(
+ &priv->tx_queue_stats, -1))) {
+ /* Highly unlekely: WSM requeued frames. */
+ wsm_unlock_tx(priv);
+ continue;
+ }
+ break;
+ }
+ return ret;
+}
+
+void cw1200_flush(struct ieee80211_hw *hw, bool drop)
+{
+ struct cw1200_common *priv = hw->priv;
+
+ switch (priv->mode) {
+ case NL80211_IFTYPE_MONITOR:
+ drop = true;
+ break;
+ case NL80211_IFTYPE_AP:
+ if (!priv->enable_beacon)
+ drop = true;
+ break;
+ }
+
+ if (!WARN_ON(__cw1200_flush(priv, drop)))
+ wsm_unlock_tx(priv);
+
+ return;
+}
+
+/* ******************************************************************** */
+/* WSM callbacks */
+
+void cw1200_channel_switch_cb(struct cw1200_common *priv)
+{
+ wsm_unlock_tx(priv);
+}
+
+void cw1200_free_event_queue(struct cw1200_common *priv)
+{
+ LIST_HEAD(list);
+
+ spin_lock(&priv->event_queue_lock);
+ list_splice_init(&priv->event_queue, &list);
+ spin_unlock(&priv->event_queue_lock);
+
+ __cw1200_free_event_queue(&list);
+}
+
+void cw1200_event_handler(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, event_handler);
+ struct cw1200_wsm_event *event;
+ LIST_HEAD(list);
+
+ spin_lock(&priv->event_queue_lock);
+ list_splice_init(&priv->event_queue, &list);
+ spin_unlock(&priv->event_queue_lock);
+
+ list_for_each_entry(event, &list, link) {
+ switch (event->evt.eventId) {
+ case WSM_EVENT_ERROR:
+ /* I even don't know what is it about.. */
+ STUB();
+ break;
+ case WSM_EVENT_BSS_LOST:
+ {
+ spin_lock(&priv->bss_loss_lock);
+ if (priv->bss_loss_status > CW1200_BSS_LOSS_NONE) {
+ spin_unlock(&priv->bss_loss_lock);
+ break;
+ }
+ priv->bss_loss_status = CW1200_BSS_LOSS_CHECKING;
+ spin_unlock(&priv->bss_loss_lock);
+
+ sta_printk(KERN_DEBUG "[CQM] BSS lost.\n");
+ cancel_delayed_work_sync(&priv->bss_loss_work);
+ cancel_delayed_work_sync(&priv->connection_loss_work);
+ if (!down_trylock(&priv->scan.lock)) {
+ up(&priv->scan.lock);
+ priv->delayed_link_loss = 0;
+ queue_delayed_work(priv->workqueue,
+ &priv->bss_loss_work, 0);
+ } else {
+ /* Scan is in progress. Delay reporting. */
+ /* Scan complete will trigger bss_loss_work */
+ priv->delayed_link_loss = 1;
+ /* Also we're starting watchdog. */
+ queue_delayed_work(priv->workqueue,
+ &priv->bss_loss_work, 10 * HZ);
+ }
+ break;
+ }
+ case WSM_EVENT_BSS_REGAINED:
+ {
+ sta_printk(KERN_DEBUG "[CQM] BSS regained.\n");
+ priv->delayed_link_loss = 0;
+ spin_lock(&priv->bss_loss_lock);
+ priv->bss_loss_status = CW1200_BSS_LOSS_NONE;
+ spin_unlock(&priv->bss_loss_lock);
+ cancel_delayed_work_sync(&priv->bss_loss_work);
+ cancel_delayed_work_sync(&priv->connection_loss_work);
+ break;
+ }
+ case WSM_EVENT_RADAR_DETECTED:
+ STUB();
+ break;
+ case WSM_EVENT_RCPI_RSSI:
+ {
+ /* RSSI: signed Q8.0, RCPI: unsigned Q7.1
+ * RSSI = RCPI / 2 - 110 */
+ int rcpiRssi = (int)(event->evt.eventData & 0xFF);
+ int cqm_evt;
+ if (priv->cqm_use_rssi)
+ rcpiRssi = (s8)rcpiRssi;
+ else
+ rcpiRssi = rcpiRssi / 2 - 110;
+
+ cqm_evt = (rcpiRssi <= priv->cqm_rssi_thold) ?
+ NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW :
+ NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH;
+ sta_printk(KERN_DEBUG "[CQM] RSSI event: %d", rcpiRssi);
+ ieee80211_cqm_rssi_notify(priv->vif, cqm_evt,
+ GFP_KERNEL);
+ break;
+ }
+ case WSM_EVENT_BT_INACTIVE:
+ STUB();
+ break;
+ case WSM_EVENT_BT_ACTIVE:
+ STUB();
+ break;
+ }
+ }
+ __cw1200_free_event_queue(&list);
+}
+
+void cw1200_bss_loss_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, bss_loss_work.work);
+ int timeout; /* in beacons */
+ struct sk_buff *skb;
+
+ timeout = priv->cqm_link_loss_count -
+ priv->cqm_beacon_loss_count;
+
+ /* Skip the confimration procedure in P2P case */
+ if (priv->vif->p2p)
+ goto report;
+
+ spin_lock(&priv->bss_loss_lock);
+ if (priv->bss_loss_status == CW1200_BSS_LOSS_CHECKING) {
+ spin_unlock(&priv->bss_loss_lock);
+ skb = ieee80211_nullfunc_get(priv->hw, priv->vif);
+ if (!(WARN_ON(!skb))) {
+ cw1200_tx(priv->hw, skb);
+ /* Start watchdog -- if nullfunc TX doesn't fail
+ * in 1 sec, forward event to upper layers */
+ queue_delayed_work(priv->workqueue,
+ &priv->bss_loss_work, 1 * HZ);
+ }
+ return;
+ } else if (priv->bss_loss_status == CW1200_BSS_LOSS_CONFIRMING) {
+ priv->bss_loss_status = CW1200_BSS_LOSS_NONE;
+ spin_unlock(&priv->bss_loss_lock);
+ return;
+ }
+ spin_unlock(&priv->bss_loss_lock);
+
+report:
+ if (priv->cqm_beacon_loss_count) {
+ sta_printk(KERN_DEBUG "[CQM] Beacon loss.\n");
+ if (timeout <= 0)
+ timeout = 0;
+ /* ieee80211_cqm_beacon_miss_notify(priv->vif, GFP_KERNEL); */
+ } else {
+ timeout = 0;
+ }
+
+ cancel_delayed_work_sync(&priv->connection_loss_work);
+ queue_delayed_work(priv->workqueue,
+ &priv->connection_loss_work,
+ timeout * HZ / 10);
+
+ spin_lock(&priv->bss_loss_lock);
+ priv->bss_loss_status = CW1200_BSS_LOSS_NONE;
+ spin_unlock(&priv->bss_loss_lock);
+}
+
+void cw1200_connection_loss_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common,
+ connection_loss_work.work);
+ sta_printk(KERN_DEBUG "[CQM] Reporting connection loss.\n");
+ ieee80211_connection_loss(priv->vif);
+}
+
+void cw1200_tx_failure_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, tx_failure_work);
+ sta_printk(KERN_DEBUG "[CQM] Reporting TX failure.\n");
+ /* ieee80211_cqm_tx_fail_notify(priv->vif, GFP_KERNEL); */
+ (void)priv;
+}
+
+/* ******************************************************************** */
+/* Internal API */
+
+
+
+/*
+* This function is called to Parse the SDD file
+ *to extract listen_interval and PTA related information
+*/
+static int cw1200_parse_SDD_file(struct cw1200_common *priv)
+{
+ u8 *sdd_data = (u8 *)priv->sdd->data;
+ struct cw1200_sdd {
+ u8 id ;
+ u8 length ;
+ u8 data[] ;
+ } *pElement;
+ int parsedLength = 0;
+ #define SDD_PTA_CFG_ELT_ID 0xEB
+ #define FIELD_OFFSET(type, field) ((u8 *)&((type *)0)->field - (u8 *)0)
+
+ priv->is_BT_Present = false;
+
+ pElement = (struct cw1200_sdd *)sdd_data;
+
+ pElement = (struct cw1200_sdd *)((u8 *)pElement +
+ FIELD_OFFSET(struct cw1200_sdd, data) + pElement->length);
+
+ parsedLength += (FIELD_OFFSET(struct cw1200_sdd, data) +
+ pElement->length);
+
+ while (parsedLength <= priv->sdd->size) {
+ switch (pElement->id) {
+ case SDD_PTA_CFG_ELT_ID:
+ {
+ priv->conf_listen_interval =
+ (*((u16 *)pElement->data+1) >> 7) & 0x1F;
+ priv->is_BT_Present = true;
+ sta_printk(KERN_DEBUG "PTA element found.\n");
+ sta_printk(KERN_DEBUG "Listen Interval %d\n",
+ priv->conf_listen_interval);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ pElement = (struct cw1200_sdd *)
+ ((u8 *)pElement + FIELD_OFFSET(struct cw1200_sdd, data)
+ + pElement->length);
+ parsedLength +=
+ (FIELD_OFFSET(struct cw1200_sdd, data) + pElement->length);
+ }
+
+ if (priv->is_BT_Present == false) {
+ sta_printk(KERN_DEBUG "PTA element NOT found.\n");
+ priv->conf_listen_interval = 0;
+ }
+ return 0;
+
+ #undef SDD_PTA_CFG_ELT_ID
+ #undef FIELD_OFFSET
+}
+
+
+int cw1200_setup_mac(struct cw1200_common *priv)
+{
+ int ret = 0;
+
+ /* NOTE: There is a bug in FW: it reports signal
+ * as RSSI if RSSI subscription is enabled.
+ * It's not enough to set WSM_RCPI_RSSI_USE_RSSI. */
+ /* NOTE2: RSSI based reports have been switched to RCPI, since
+ * FW has a bug and RSSI reported values are not stable,
+ * what can leads to signal level oscilations in user-end applications */
+ struct wsm_rcpi_rssi_threshold threshold = {
+ .rssiRcpiMode = WSM_RCPI_RSSI_THRESHOLD_ENABLE |
+ WSM_RCPI_RSSI_DONT_USE_UPPER |
+ WSM_RCPI_RSSI_DONT_USE_LOWER,
+ .rollingAverageCount = 16,
+ };
+
+ /* Remember the decission here to make sure, we will handle
+ * the RCPI/RSSI value correctly on WSM_EVENT_RCPI_RSS */
+ if (threshold.rssiRcpiMode & WSM_RCPI_RSSI_USE_RSSI)
+ priv->cqm_use_rssi = true;
+
+ if (!priv->sdd) {
+ const char *sdd_path = NULL;
+ struct wsm_configuration cfg = {
+ .dot11StationId = &priv->mac_addr[0],
+ };
+
+ switch (priv->hw_revision) {
+ case CW1200_HW_REV_CUT10:
+ sdd_path = SDD_FILE_10;
+ break;
+ case CW1200_HW_REV_CUT11:
+ sdd_path = SDD_FILE_11;
+ break;
+ case CW1200_HW_REV_CUT20:
+ sdd_path = SDD_FILE_20;
+ break;
+ case CW1200_HW_REV_CUT22:
+ sdd_path = SDD_FILE_22;
+ break;
+ default:
+ BUG_ON(1);
+ }
+
+ ret = request_firmware(&priv->sdd,
+ sdd_path, priv->pdev);
+
+ if (unlikely(ret)) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't load sdd file %s.\n",
+ __func__, sdd_path);
+ return ret;
+ }
+
+ cfg.dpdData = priv->sdd->data;
+ cfg.dpdData_size = priv->sdd->size;
+ ret = WARN_ON(wsm_configuration(priv, &cfg));
+ /* Parse SDD file for PTA element */
+ cw1200_parse_SDD_file(priv);
+ }
+ if (ret)
+ return ret;
+
+ /* Configure RSSI/SCPI reporting as RSSI. */
+ WARN_ON(wsm_set_rcpi_rssi_threshold(priv, &threshold));
+
+ /* TODO: */
+ switch (priv->mode) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_MONITOR:
+ case NL80211_IFTYPE_AP:
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_MESH_POINT:
+ /* TODO: Not verified yet. */
+ STUB();
+ break;
+ }
+
+ return 0;
+}
+
+void cw1200_offchannel_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, offchannel_work);
+ u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id);
+ struct cw1200_queue *queue = &priv->tx_queue[queueId];
+
+ BUG_ON(queueId >= 4);
+ BUG_ON(!priv->channel);
+
+ mutex_lock(&priv->conf_mutex);
+ if (likely(!priv->join_status)) {
+ wsm_flush_tx(priv);
+ cw1200_update_listening(priv, true);
+ cw1200_update_filtering(priv);
+ }
+ if (unlikely(!priv->join_status))
+ cw1200_queue_remove(queue, priv->pending_frame_id);
+ else
+ cw1200_queue_requeue(queue, priv->pending_frame_id);
+ mutex_unlock(&priv->conf_mutex);
+ wsm_unlock_tx(priv);
+}
+
+void cw1200_join_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, join_work);
+ u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id);
+ struct cw1200_queue *queue = &priv->tx_queue[queueId];
+ const struct cw1200_txpriv *txpriv = NULL;
+ struct sk_buff *skb = NULL;
+ const struct wsm_tx *wsm;
+ const struct ieee80211_hdr *frame;
+ const u8 *bssid;
+ struct cfg80211_bss *bss;
+ const u8 *ssidie;
+ const u8 *dtimie;
+ const struct ieee80211_tim_ie *tim = NULL;
+ struct wsm_protected_mgmt_policy mgmt_policy;
+
+ BUG_ON(queueId >= 4);
+ if (cw1200_queue_get_skb(queue, priv->pending_frame_id,
+ &skb, &txpriv)) {
+ wsm_unlock_tx(priv);
+ return;
+ }
+ wsm = (struct wsm_tx *)&skb->data[0];
+ frame = (struct ieee80211_hdr *)&skb->data[txpriv->offset];
+ bssid = &frame->addr1[0]; /* AP SSID in a 802.11 frame */
+
+ BUG_ON(!wsm);
+ BUG_ON(!priv->channel);
+
+ if (unlikely(priv->join_status)) {
+ wsm_lock_tx(priv);
+ cw1200_unjoin_work(&priv->unjoin_work);
+ }
+
+ cancel_delayed_work_sync(&priv->join_timeout);
+
+ bss = cfg80211_get_bss(priv->hw->wiphy, priv->channel,
+ bssid, NULL, 0, 0, 0);
+ if (!bss) {
+ cw1200_queue_remove(queue, priv->pending_frame_id);
+ wsm_unlock_tx(priv);
+ return;
+ }
+ ssidie = cfg80211_find_ie(WLAN_EID_SSID,
+ bss->information_elements,
+ bss->len_information_elements);
+ dtimie = cfg80211_find_ie(WLAN_EID_TIM,
+ bss->information_elements,
+ bss->len_information_elements);
+ if (dtimie)
+ tim = (struct ieee80211_tim_ie *)&dtimie[2];
+
+ mutex_lock(&priv->conf_mutex);
+ {
+ struct wsm_join join = {
+ .mode = (bss->capability & WLAN_CAPABILITY_IBSS) ?
+ WSM_JOIN_MODE_IBSS : WSM_JOIN_MODE_BSS,
+ .preambleType = WSM_JOIN_PREAMBLE_SHORT,
+ .probeForJoin = 1,
+ /* dtimPeriod will be updated after association */
+ .dtimPeriod = 1,
+ .beaconInterval = bss->beacon_interval,
+ /* basicRateSet will be updated after association */
+ .basicRateSet = 7,
+ };
+
+ /* BT Coex related changes */
+ if (priv->is_BT_Present) {
+ if (((priv->conf_listen_interval * 100) %
+ bss->beacon_interval) == 0)
+ priv->listen_interval =
+ ((priv->conf_listen_interval * 100) /
+ bss->beacon_interval);
+ else
+ priv->listen_interval =
+ ((priv->conf_listen_interval * 100) /
+ bss->beacon_interval + 1);
+ }
+
+ if (tim && tim->dtim_period > 1) {
+ join.dtimPeriod = tim->dtim_period;
+ priv->join_dtim_period = tim->dtim_period;
+ }
+ priv->beacon_int = bss->beacon_interval;
+ sta_printk(KERN_DEBUG "[STA] Join DTIM: %d, interval: %d\n",
+ join.dtimPeriod, priv->beacon_int);
+
+ join.channelNumber = priv->channel->hw_value;
+ join.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ?
+ WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G;
+
+ memcpy(&join.bssid[0], bssid, sizeof(join.bssid));
+ memcpy(&priv->join_bssid[0], bssid, sizeof(priv->join_bssid));
+
+ if (ssidie) {
+ join.ssidLength = ssidie[1];
+ if (WARN_ON(join.ssidLength > sizeof(join.ssid)))
+ join.ssidLength = sizeof(join.ssid);
+ memcpy(&join.ssid[0], &ssidie[2], join.ssidLength);
+ }
+
+ if (priv->vif->p2p) {
+ join.flags |= WSM_JOIN_FLAGS_P2P_GO;
+ join.basicRateSet =
+ cw1200_rate_mask_to_wsm(priv, 0xFF0);
+ }
+
+ wsm_flush_tx(priv);
+
+ /* Queue unjoin if not associated in 3 sec. */
+ queue_delayed_work(priv->workqueue,
+ &priv->join_timeout, 3 * HZ);
+ /*Stay Awake for Join Timeout*/
+ cw1200_pm_stay_awake(&priv->pm_state, 3 * HZ);
+
+ cw1200_update_listening(priv, false);
+ /* BlockACK policy will be updated when assoc is done */
+ WARN_ON(wsm_set_block_ack_policy(priv,
+ 0, priv->ba_tid_mask));
+
+ spin_lock_bh(&priv->ba_lock);
+ priv->ba_ena = false;
+ priv->ba_cnt = 0;
+ priv->ba_acc = 0;
+ priv->ba_hist = 0;
+ spin_unlock_bh(&priv->ba_lock);
+
+ mgmt_policy.protectedMgmtEnable = 0;
+ mgmt_policy.unprotectedMgmtFramesAllowed = 1;
+ mgmt_policy.encryptionForAuthFrame = 1;
+ wsm_set_protected_mgmt_policy(priv, &mgmt_policy);
+
+ if (wsm_join(priv, &join)) {
+ memset(&priv->join_bssid[0],
+ 0, sizeof(priv->join_bssid));
+ cw1200_queue_remove(queue, priv->pending_frame_id);
+ cancel_delayed_work_sync(&priv->join_timeout);
+ cw1200_update_listening(priv, priv->listening);
+ } else {
+ /* Upload keys */
+ WARN_ON(cw1200_upload_keys(priv));
+ cw1200_queue_requeue(queue, priv->pending_frame_id);
+ priv->join_status = CW1200_JOIN_STATUS_STA;
+
+ /* Due to beacon filtering it is possible that the
+ * AP's beacon is not known for the mac80211 stack.
+ * Disable filtering temporary to make sure the stack
+ * receives at least one */
+ priv->disable_beacon_filter = true;
+
+ }
+ cw1200_update_filtering(priv);
+ }
+ mutex_unlock(&priv->conf_mutex);
+ cfg80211_put_bss(bss);
+ wsm_unlock_tx(priv);
+}
+
+void cw1200_join_timeout(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, join_timeout.work);
+ sta_printk(KERN_DEBUG "[WSM] Issue unjoin command (TMO).\n");
+ wsm_lock_tx(priv);
+ cw1200_unjoin_work(&priv->unjoin_work);
+}
+
+void cw1200_unjoin_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, unjoin_work);
+
+ struct wsm_reset reset = {
+ .reset_statistics = true,
+ };
+
+ del_timer_sync(&priv->ba_timer);
+ mutex_lock(&priv->conf_mutex);
+ if (unlikely(atomic_read(&priv->scan.in_progress))) {
+ if (priv->delayed_unjoin) {
+ wiphy_dbg(priv->hw->wiphy,
+ "%s: Delayed unjoin "
+ "is already scheduled.\n",
+ __func__);
+ wsm_unlock_tx(priv);
+ } else {
+ priv->delayed_unjoin = true;
+ }
+ mutex_unlock(&priv->conf_mutex);
+ return;
+ }
+
+ if (priv->join_status &&
+ priv->join_status > CW1200_JOIN_STATUS_STA) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: Unexpected: join status: %d\n",
+ __func__, priv->join_status);
+ BUG_ON(1);
+ }
+ if (priv->join_status) {
+ cancel_work_sync(&priv->update_filtering_work);
+ memset(&priv->join_bssid[0], 0, sizeof(priv->join_bssid));
+ priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
+
+ /* Unjoin is a reset. */
+ wsm_flush_tx(priv);
+ WARN_ON(wsm_keep_alive_period(priv, 0));
+ WARN_ON(wsm_reset(priv, &reset));
+ priv->join_dtim_period = 0;
+ WARN_ON(cw1200_setup_mac(priv));
+ cw1200_free_event_queue(priv);
+ cancel_work_sync(&priv->event_handler);
+ cancel_delayed_work_sync(&priv->connection_loss_work);
+ cw1200_update_listening(priv, priv->listening);
+ WARN_ON(wsm_set_block_ack_policy(priv,
+ 0, priv->ba_tid_mask));
+ priv->disable_beacon_filter = false;
+ cw1200_update_filtering(priv);
+ priv->setbssparams_done = false;
+ memset(&priv->association_mode, 0,
+ sizeof(priv->association_mode));
+ memset(&priv->bss_params, 0, sizeof(priv->bss_params));
+ memset(&priv->firmware_ps_mode, 0,
+ sizeof(priv->firmware_ps_mode));
+ sta_printk(KERN_DEBUG "[STA] Unjoin.\n");
+ }
+ mutex_unlock(&priv->conf_mutex);
+ wsm_unlock_tx(priv);
+}
+
+int cw1200_enable_listening(struct cw1200_common *priv)
+{
+ struct wsm_start start = {
+ .mode = WSM_START_MODE_P2P_DEV,
+ .band = (priv->channel->band == IEEE80211_BAND_5GHZ) ?
+ WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G,
+ .channelNumber = priv->channel->hw_value,
+ .beaconInterval = 100,
+ .DTIMPeriod = 1,
+ .probeDelay = 0,
+ .basicRateSet = 0x0F,
+ };
+ return wsm_start(priv, &start);
+}
+
+int cw1200_disable_listening(struct cw1200_common *priv)
+{
+ int ret;
+ struct wsm_reset reset = {
+ .reset_statistics = true,
+ };
+ ret = wsm_reset(priv, &reset);
+ return ret;
+}
+
+void cw1200_update_listening(struct cw1200_common *priv, bool enabled)
+{
+ if (enabled) {
+ switch (priv->join_status) {
+ case CW1200_JOIN_STATUS_PASSIVE:
+ if (!WARN_ON(cw1200_enable_listening(priv)))
+ priv->join_status = CW1200_JOIN_STATUS_MONITOR;
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (priv->join_status) {
+ case CW1200_JOIN_STATUS_MONITOR:
+ if (!WARN_ON(cw1200_disable_listening(priv)))
+ priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
+ default:
+ break;
+ }
+ }
+}
+
+int cw1200_set_uapsd_param(struct cw1200_common *priv,
+ const struct wsm_edca_params *arg)
+{
+ int ret;
+ u16 uapsdFlags = 0;
+
+ /* Here's the mapping AC [queue, bit]
+ VO [0,3], VI [1, 2], BE [2, 1], BK [3, 0]*/
+
+ if (arg->params[0].uapsdEnable)
+ uapsdFlags |= 1 << 3;
+
+ if (arg->params[1].uapsdEnable)
+ uapsdFlags |= 1 << 2;
+
+ if (arg->params[2].uapsdEnable)
+ uapsdFlags |= 1 << 1;
+
+ if (arg->params[3].uapsdEnable)
+ uapsdFlags |= 1;
+
+ /* Currently pseudo U-APSD operation is not supported, so setting
+ * MinAutoTriggerInterval, MaxAutoTriggerInterval and
+ * AutoTriggerStep to 0 */
+
+ priv->uapsd_info.uapsdFlags = cpu_to_le16(uapsdFlags);
+ priv->uapsd_info.minAutoTriggerInterval = 0;
+ priv->uapsd_info.maxAutoTriggerInterval = 0;
+ priv->uapsd_info.autoTriggerStep = 0;
+
+ ret = wsm_set_uapsd_info(priv, &priv->uapsd_info);
+ return ret;
+}
+
+void cw1200_ba_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, ba_work);
+ u8 tx_ba_tid_mask;
+
+ if (priv->join_status != CW1200_JOIN_STATUS_STA)
+ return;
+ if (!priv->setbssparams_done)
+ return;
+
+ spin_lock_bh(&priv->ba_lock);
+ tx_ba_tid_mask = priv->ba_ena ? priv->ba_tid_mask : 0;
+ spin_unlock_bh(&priv->ba_lock);
+
+ WARN_ON(wsm_set_block_ack_policy(priv,
+ tx_ba_tid_mask, priv->ba_tid_mask));
+}
+
+void cw1200_ba_timer(unsigned long arg)
+{
+ bool ba_ena;
+ struct cw1200_common *priv =
+ (struct cw1200_common *)arg;
+
+ spin_lock_bh(&priv->ba_lock);
+ cw1200_debug_ba(priv, priv->ba_cnt, priv->ba_acc);
+
+ if (atomic_read(&priv->scan.in_progress))
+ goto skip_statistic_update;
+
+ ba_ena = (priv->ba_cnt >= CW1200_BLOCK_ACK_CNT &&
+ priv->ba_acc / priv->ba_cnt >= CW1200_BLOCK_ACK_THLD);
+ priv->ba_cnt = 0;
+ priv->ba_acc = 0;
+
+ if (ba_ena != priv->ba_ena) {
+ if (ba_ena || ++priv->ba_hist >= CW1200_BLOCK_ACK_HIST) {
+ priv->ba_ena = ba_ena;
+ priv->ba_hist = 0;
+ sta_printk(KERN_DEBUG "[STA] %s block ACK:\n",
+ ba_ena ? "enable" : "disable");
+ queue_work(priv->workqueue, &priv->ba_work);
+ }
+ } else if (priv->ba_hist)
+ --priv->ba_hist;
+
+skip_statistic_update:
+ spin_unlock_bh(&priv->ba_lock);
+}
diff --git a/drivers/net/wireless/cw1200/sta.h b/drivers/net/wireless/cw1200/sta.h
new file mode 100644
index 0000000..4e4833a
--- /dev/null
+++ b/drivers/net/wireless/cw1200/sta.h
@@ -0,0 +1,87 @@
+/*
+ * Mac80211 STA interface for ST-Ericsson CW1200 mac80211 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef STA_H_INCLUDED
+#define STA_H_INCLUDED
+
+/* ******************************************************************** */
+/* mac80211 API */
+
+int cw1200_start(struct ieee80211_hw *dev);
+void cw1200_stop(struct ieee80211_hw *dev);
+int cw1200_add_interface(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif);
+void cw1200_remove_interface(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif);
+int cw1200_config(struct ieee80211_hw *dev, u32 changed);
+void cw1200_configure_filter(struct ieee80211_hw *dev,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast);
+int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
+ u16 queue, const struct ieee80211_tx_queue_params *params);
+int cw1200_get_stats(struct ieee80211_hw *dev,
+ struct ieee80211_low_level_stats *stats);
+/* Not more a part of interface?
+int cw1200_get_tx_stats(struct ieee80211_hw *dev,
+ struct ieee80211_tx_queue_stats *stats);
+*/
+int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd,
+ struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key);
+
+int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value);
+
+void cw1200_flush(struct ieee80211_hw *hw, bool drop);
+
+u64 cw1200_prepare_multicast(struct ieee80211_hw *hw,
+ struct netdev_hw_addr_list *mc_list);
+
+int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg);
+
+/* ******************************************************************** */
+/* WSM callbacks */
+
+/* void cw1200_set_pm_complete_cb(struct cw1200_common *priv,
+ struct wsm_set_pm_complete *arg); */
+void cw1200_channel_switch_cb(struct cw1200_common *priv);
+
+/* ******************************************************************** */
+/* WSM events */
+
+void cw1200_free_event_queue(struct cw1200_common *priv);
+void cw1200_event_handler(struct work_struct *work);
+void cw1200_bss_loss_work(struct work_struct *work);
+void cw1200_connection_loss_work(struct work_struct *work);
+void cw1200_keep_alive_work(struct work_struct *work);
+void cw1200_tx_failure_work(struct work_struct *work);
+
+/* ******************************************************************** */
+/* Internal API */
+
+int cw1200_setup_mac(struct cw1200_common *priv);
+void cw1200_join_work(struct work_struct *work);
+void cw1200_join_timeout(struct work_struct *work);
+void cw1200_unjoin_work(struct work_struct *work);
+void cw1200_offchannel_work(struct work_struct *work);
+void cw1200_wep_key_work(struct work_struct *work);
+void cw1200_update_listening(struct cw1200_common *priv, bool enabled);
+void cw1200_update_filtering(struct cw1200_common *priv);
+void cw1200_update_filtering_work(struct work_struct *work);
+int __cw1200_flush(struct cw1200_common *priv, bool drop);
+int cw1200_enable_listening(struct cw1200_common *priv);
+int cw1200_disable_listening(struct cw1200_common *priv);
+int cw1200_set_uapsd_param(struct cw1200_common *priv,
+ const struct wsm_edca_params *arg);
+void cw1200_ba_work(struct work_struct *work);
+void cw1200_ba_timer(unsigned long arg);
+
+#endif
--
1.7.9


2012-03-02 20:26:52

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 03/21] cw1200: sbus.h, common device interface abstraction.

The patch contains definition of hardware abstraction layer between
cw1200 core driver and physical device interface. A bunch of wrappers
can be build using this interface: sdio, spi, c2c etc.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/sbus.h | 39 ++++++++++++++++++++++++++++++++++++
1 files changed, 39 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/sbus.h

diff --git a/drivers/net/wireless/cw1200/sbus.h b/drivers/net/wireless/cw1200/sbus.h
new file mode 100644
index 0000000..49bd06d
--- /dev/null
+++ b/drivers/net/wireless/cw1200/sbus.h
@@ -0,0 +1,39 @@
+/*
+ * Common sbus abstraction layer interface for cw1200 wireless driver
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_SBUS_H
+#define CW1200_SBUS_H
+
+/*
+ * sbus priv forward definition.
+ * Implemented and instantiated in particular modules.
+ */
+struct sbus_priv;
+
+typedef void (*sbus_irq_handler)(void *priv);
+
+struct sbus_ops {
+ int (*sbus_memcpy_fromio)(struct sbus_priv *self, unsigned int addr,
+ void *dst, int count);
+ int (*sbus_memcpy_toio)(struct sbus_priv *self, unsigned int addr,
+ const void *src, int count);
+ void (*lock)(struct sbus_priv *self);
+ void (*unlock)(struct sbus_priv *self);
+ int (*irq_subscribe)(struct sbus_priv *self, sbus_irq_handler handler,
+ void *priv);
+ int (*irq_unsubscribe)(struct sbus_priv *self);
+ int (*reset)(struct sbus_priv *self);
+ size_t (*align_size)(struct sbus_priv *self, size_t size);
+ int (*power_mgmt)(struct sbus_priv *self, bool suspend);
+ int (*set_block_size)(struct sbus_priv *self, size_t size);
+};
+
+#endif /* CW1200_SBUS_H */
--
1.7.9


2012-03-02 20:27:06

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 13/21] cw1200: ap.*, mac80211 AP callbacks.

The code implements mac80211 AP callbacks.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/ap.c | 1112 ++++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/ap.h | 46 ++
2 files changed, 1158 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/ap.c
create mode 100644 drivers/net/wireless/cw1200/ap.h

diff --git a/drivers/net/wireless/cw1200/ap.c b/drivers/net/wireless/cw1200/ap.c
new file mode 100644
index 0000000..1cdef84
--- /dev/null
+++ b/drivers/net/wireless/cw1200/ap.c
@@ -0,0 +1,1112 @@
+/*
+ * mac80211 STA and AP API for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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 "cw1200.h"
+#include "sta.h"
+#include "ap.h"
+#include "bh.h"
+
+#if defined(CONFIG_CW1200_STA_DEBUG)
+#define ap_printk(...) printk(__VA_ARGS__)
+#else
+#define ap_printk(...)
+#endif
+
+#define CW1200_LINK_ID_GC_TIMEOUT ((unsigned long)(10 * HZ))
+
+#ifndef ERP_INFO_BYTE_OFFSET
+#define ERP_INFO_BYTE_OFFSET 2
+#endif
+
+static int cw1200_upload_beacon(struct cw1200_common *priv);
+static int cw1200_upload_pspoll(struct cw1200_common *priv);
+static int cw1200_upload_null(struct cw1200_common *priv);
+static int cw1200_start_ap(struct cw1200_common *priv);
+static int cw1200_update_beaconing(struct cw1200_common *priv);
+static int cw1200_enable_beaconing(struct cw1200_common *priv,
+ bool enable);
+static void __cw1200_sta_notify(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif,
+ enum sta_notify_cmd notify_cmd,
+ int link_id);
+
+/* ******************************************************************** */
+/* AP API */
+
+int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct cw1200_common *priv = hw->priv;
+ struct cw1200_sta_priv *sta_priv =
+ (struct cw1200_sta_priv *)&sta->drv_priv;
+ struct cw1200_link_entry *entry;
+ struct sk_buff *skb;
+
+ if (priv->mode != NL80211_IFTYPE_AP)
+ return 0;
+
+ sta_priv->link_id = cw1200_find_link_id(priv, sta->addr);
+ if (WARN_ON(!sta_priv->link_id)) {
+ /* Impossible error */
+ wiphy_info(priv->hw->wiphy,
+ "[AP] No more link IDs available.\n");
+ return -ENOENT;
+ }
+
+ entry = &priv->link_id_db[sta_priv->link_id - 1];
+ spin_lock_bh(&priv->ps_state_lock);
+ if ((sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) ==
+ IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
+ priv->sta_asleep_mask |= BIT(sta_priv->link_id);
+ entry->status = CW1200_LINK_HARD;
+ while ((skb = skb_dequeue(&entry->rx_queue)))
+ ieee80211_rx_irqsafe(priv->hw, skb);
+ spin_unlock_bh(&priv->ps_state_lock);
+ return 0;
+}
+
+int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct cw1200_common *priv = hw->priv;
+ struct cw1200_sta_priv *sta_priv =
+ (struct cw1200_sta_priv *)&sta->drv_priv;
+ struct cw1200_link_entry *entry;
+
+ if (priv->mode != NL80211_IFTYPE_AP || !sta_priv->link_id)
+ return 0;
+
+ entry = &priv->link_id_db[sta_priv->link_id - 1];
+ spin_lock_bh(&priv->ps_state_lock);
+ entry->status = CW1200_LINK_RESERVE;
+ entry->timestamp = jiffies;
+ wsm_lock_tx_async(priv);
+ if (queue_work(priv->workqueue, &priv->link_id_work) <= 0)
+ wsm_unlock_tx(priv);
+ spin_unlock_bh(&priv->ps_state_lock);
+ flush_workqueue(priv->workqueue);
+ return 0;
+}
+
+static void __cw1200_sta_notify(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif,
+ enum sta_notify_cmd notify_cmd,
+ int link_id)
+{
+ struct cw1200_common *priv = dev->priv;
+ u32 bit, prev;
+
+ /* Zero link id means "for all link IDs" */
+ if (link_id)
+ bit = BIT(link_id);
+ else if (WARN_ON_ONCE(notify_cmd != STA_NOTIFY_AWAKE))
+ bit = 0;
+ else
+ bit = priv->link_id_map;
+ prev = priv->sta_asleep_mask & bit;
+
+ switch (notify_cmd) {
+ case STA_NOTIFY_SLEEP:
+ if (!prev) {
+ if (priv->buffered_multicasts &&
+ !priv->sta_asleep_mask)
+ queue_work(priv->workqueue,
+ &priv->multicast_start_work);
+ priv->sta_asleep_mask |= bit;
+ }
+ break;
+ case STA_NOTIFY_AWAKE:
+ if (prev) {
+ priv->sta_asleep_mask &= ~bit;
+ priv->pspoll_mask &= ~bit;
+ if (priv->tx_multicast && link_id &&
+ !priv->sta_asleep_mask)
+ queue_work(priv->workqueue,
+ &priv->multicast_stop_work);
+ cw1200_bh_wakeup(priv);
+ }
+ break;
+ }
+}
+
+void cw1200_sta_notify(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif,
+ enum sta_notify_cmd notify_cmd,
+ struct ieee80211_sta *sta)
+{
+ struct cw1200_common *priv = dev->priv;
+ struct cw1200_sta_priv *sta_priv =
+ (struct cw1200_sta_priv *)&sta->drv_priv;
+
+ spin_lock_bh(&priv->ps_state_lock);
+ __cw1200_sta_notify(dev, vif, notify_cmd, sta_priv->link_id);
+ spin_unlock_bh(&priv->ps_state_lock);
+}
+
+static void cw1200_ps_notify(struct cw1200_common *priv,
+ int link_id, bool ps)
+{
+ if (link_id > CW1200_MAX_STA_IN_AP_MODE)
+ return;
+
+ txrx_printk(KERN_DEBUG "%s for LinkId: %d. STAs asleep: %.8X\n",
+ ps ? "Stop" : "Start",
+ link_id, priv->sta_asleep_mask);
+
+ __cw1200_sta_notify(priv->hw, priv->vif,
+ ps ? STA_NOTIFY_SLEEP : STA_NOTIFY_AWAKE, link_id);
+}
+
+static int cw1200_set_tim_impl(struct cw1200_common *priv, bool aid0_bit_set)
+{
+ struct sk_buff *skb;
+ struct wsm_update_ie update_ie = {
+ .what = WSM_UPDATE_IE_BEACON,
+ .count = 1,
+ };
+ u16 tim_offset, tim_length;
+
+ ap_printk(KERN_DEBUG "[AP] %s mcast: %s.\n",
+ __func__, aid0_bit_set ? "ena" : "dis");
+
+ skb = ieee80211_beacon_get_tim(priv->hw, priv->vif,
+ &tim_offset, &tim_length);
+ if (!skb) {
+ if (!__cw1200_flush(priv, true))
+ wsm_unlock_tx(priv);
+ return -ENOENT;
+ }
+
+ if (tim_offset && tim_length >= 6) {
+ /* Ignore DTIM count from mac80211:
+ * firmware handles DTIM internally. */
+ skb->data[tim_offset + 2] = 0;
+
+ /* Set/reset aid0 bit */
+ if (aid0_bit_set)
+ skb->data[tim_offset + 4] |= 1;
+ else
+ skb->data[tim_offset + 4] &= ~1;
+ }
+
+ update_ie.ies = &skb->data[tim_offset];
+ update_ie.length = tim_length;
+ WARN_ON(wsm_update_ie(priv, &update_ie));
+
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+void cw1200_set_tim_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, set_tim_work);
+ (void)cw1200_set_tim_impl(priv, priv->aid0_bit_set);
+}
+
+int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta,
+ bool set)
+{
+ struct cw1200_common *priv = dev->priv;
+ queue_work(priv->workqueue, &priv->set_tim_work);
+ return 0;
+}
+
+void cw1200_set_cts_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, set_cts_work.work);
+
+ u8 erp_ie[3] = {WLAN_EID_ERP_INFO, 0x1, 0};
+ struct wsm_update_ie update_ie = {
+ .what = WSM_UPDATE_IE_BEACON,
+ .count = 1,
+ .ies = erp_ie,
+ .length = 3,
+ };
+ u32 erp_info;
+ __le32 use_cts_prot;
+ mutex_lock(&priv->conf_mutex);
+ erp_info = priv->erp_info;
+ mutex_unlock(&priv->conf_mutex);
+ use_cts_prot =
+ erp_info & WLAN_ERP_USE_PROTECTION ?
+ __cpu_to_le32(1) : 0;
+
+ erp_ie[ERP_INFO_BYTE_OFFSET] = erp_info;
+
+ ap_printk(KERN_DEBUG "[STA] ERP information 0x%x\n", erp_info);
+
+ WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_NON_ERP_PROTECTION,
+ &use_cts_prot, sizeof(use_cts_prot)));
+ WARN_ON(wsm_update_ie(priv, &update_ie));
+
+ return;
+}
+
+static int cw1200_set_btcoexinfo(struct cw1200_common *priv)
+{
+ struct wsm_override_internal_txrate arg;
+ int ret = 0;
+
+ if (priv->mode == NL80211_IFTYPE_STATION) {
+ /* Plumb PSPOLL and NULL template */
+ WARN_ON(cw1200_upload_pspoll(priv));
+ WARN_ON(cw1200_upload_null(priv));
+ } else {
+ return 0;
+ }
+
+ memset(&arg, 0, sizeof(struct wsm_override_internal_txrate));
+
+ if (!priv->vif->p2p) {
+ /* STATION mode */
+ if (priv->bss_params.operationalRateSet & ~0xF) {
+ ap_printk(KERN_DEBUG "[STA] STA has ERP rates\n");
+ /* G or BG mode */
+ arg.internalTxRate = (__ffs(
+ priv->bss_params.operationalRateSet & ~0xF));
+ } else {
+ ap_printk(KERN_DEBUG "[STA] STA has non ERP rates\n");
+ /* B only mode */
+ arg.internalTxRate = (__ffs(
+ priv->association_mode.basicRateSet));
+ }
+ arg.nonErpInternalTxRate = (__ffs(
+ priv->association_mode.basicRateSet));
+ } else {
+ /* P2P mode */
+ arg.internalTxRate = (__ffs(
+ priv->bss_params.operationalRateSet & ~0xF));
+ arg.nonErpInternalTxRate = (__ffs(
+ priv->bss_params.operationalRateSet & ~0xF));
+ }
+
+ ap_printk(KERN_DEBUG "[STA] BTCOEX_INFO"
+ "MODE %d, internalTxRate : %x, nonErpInternalTxRate: %x\n",
+ priv->mode,
+ arg.internalTxRate,
+ arg.nonErpInternalTxRate);
+
+ ret = WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE,
+ &arg, sizeof(arg)));
+
+ return ret;
+}
+
+void cw1200_bss_info_changed(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ struct cw1200_common *priv = dev->priv;
+
+ mutex_lock(&priv->conf_mutex);
+ if (changed & BSS_CHANGED_BSSID) {
+ memcpy(priv->bssid, info->bssid, ETH_ALEN);
+ cw1200_setup_mac(priv);
+ }
+
+ /* TODO: BSS_CHANGED_IBSS */
+
+ if (changed & BSS_CHANGED_ARP_FILTER) {
+ struct wsm_arp_ipv4_filter filter = {0};
+ int i;
+
+ ap_printk(KERN_DEBUG "[STA] BSS_CHANGED_ARP_FILTER "
+ "enabled: %d, cnt: %d\n",
+ info->arp_filter_enabled,
+ info->arp_addr_cnt);
+
+ if (info->arp_filter_enabled)
+ filter.enable = __cpu_to_le32(1);
+
+ /* Currently only one IP address is supported by firmware.
+ * In case of more IPs arp filtering will be disabled. */
+ if (info->arp_addr_cnt > 0 &&
+ info->arp_addr_cnt <= WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES) {
+ for (i = 0; i < info->arp_addr_cnt; i++) {
+ filter.ipv4Address[i] = info->arp_addr_list[i];
+ ap_printk(KERN_DEBUG "[STA] addr[%d]: 0x%X\n",
+ i, filter.ipv4Address[i]);
+ }
+ } else
+ filter.enable = 0;
+
+ ap_printk(KERN_DEBUG "[STA] arp ip filter enable: %d\n",
+ __le32_to_cpu(filter.enable));
+
+ if (wsm_set_arp_ipv4_filter(priv, &filter))
+ WARN_ON(1);
+ }
+
+
+ if (changed & BSS_CHANGED_BEACON) {
+ ap_printk(KERN_DEBUG "BSS_CHANGED_BEACON\n");
+ WARN_ON(cw1200_update_beaconing(priv));
+ WARN_ON(cw1200_upload_beacon(priv));
+ }
+
+ if (changed & BSS_CHANGED_BEACON_ENABLED) {
+ ap_printk(KERN_DEBUG "BSS_CHANGED_BEACON_ENABLED\n");
+
+ if (priv->enable_beacon != info->enable_beacon) {
+ WARN_ON(cw1200_enable_beaconing(priv,
+ info->enable_beacon));
+ priv->enable_beacon = info->enable_beacon;
+ }
+ }
+
+ if (changed & BSS_CHANGED_BEACON_INT) {
+ ap_printk(KERN_DEBUG "CHANGED_BEACON_INT\n");
+ /* Restart AP only when connected */
+ if (priv->join_status == CW1200_JOIN_STATUS_AP)
+ WARN_ON(cw1200_update_beaconing(priv));
+ }
+
+
+ if (changed & BSS_CHANGED_ASSOC) {
+ wsm_lock_tx(priv);
+ priv->wep_default_key_id = -1;
+ wsm_unlock_tx(priv);
+
+ if (!info->assoc /* && !info->ibss_joined */) {
+ priv->cqm_link_loss_count = 60;
+ priv->cqm_beacon_loss_count = 20;
+ priv->cqm_tx_failure_thold = 0;
+ }
+ priv->cqm_tx_failure_count = 0;
+ }
+
+ if (changed &
+ (BSS_CHANGED_ASSOC |
+ BSS_CHANGED_BASIC_RATES |
+ BSS_CHANGED_ERP_PREAMBLE |
+ BSS_CHANGED_HT |
+ BSS_CHANGED_ERP_SLOT)) {
+ ap_printk(KERN_DEBUG "BSS_CHANGED_ASSOC.\n");
+ if (info->assoc) { /* TODO: ibss_joined */
+ struct ieee80211_sta *sta = NULL;
+ priv->join_dtim_period = info->dtim_period;
+ priv->beacon_int = info->beacon_int;
+
+ /* Associated: kill join timeout */
+ cancel_delayed_work_sync(&priv->join_timeout);
+
+ rcu_read_lock();
+ if (info->bssid)
+ sta = ieee80211_find_sta(vif, info->bssid);
+ if (sta) {
+ BUG_ON(!priv->channel);
+ priv->ht_info.ht_cap = sta->ht_cap;
+ priv->bss_params.operationalRateSet =
+ __cpu_to_le32(
+ cw1200_rate_mask_to_wsm(priv,
+ sta->supp_rates[priv->channel->band]));
+ priv->ht_info.channel_type =
+ info->channel_type;
+ priv->ht_info.operation_mode =
+ info->ht_operation_mode;
+ } else {
+ memset(&priv->ht_info, 0,
+ sizeof(priv->ht_info));
+ priv->bss_params.operationalRateSet = -1;
+ }
+ rcu_read_unlock();
+
+ if (sta) {
+ __le32 val = 0;
+ if (priv->ht_info.operation_mode &
+ IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT) {
+ ap_printk(KERN_DEBUG"[STA]"
+ " Non-GF STA present\n");
+ /* Non Green field capable STA */
+ val = __cpu_to_le32(BIT(1));
+ }
+ WARN_ON(wsm_write_mib(priv,
+ WSM_MID_ID_SET_HT_PROTECTION,
+ &val, sizeof(val)));
+ }
+
+ priv->association_mode.greenfieldMode =
+ cw1200_ht_greenfield(&priv->ht_info);
+ priv->association_mode.flags =
+ WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES |
+ WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE |
+ WSM_ASSOCIATION_MODE_USE_HT_MODE |
+ WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET |
+ WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING;
+ priv->association_mode.preambleType =
+ info->use_short_preamble ?
+ WSM_JOIN_PREAMBLE_SHORT :
+ WSM_JOIN_PREAMBLE_LONG;
+ priv->association_mode.basicRateSet = __cpu_to_le32(
+ cw1200_rate_mask_to_wsm(priv,
+ info->basic_rates));
+ priv->association_mode.mpduStartSpacing =
+ cw1200_ht_ampdu_density(&priv->ht_info);
+
+#if defined(CW1200_ENABLE_CQM_EXTENSIONS)
+ priv->cqm_beacon_loss_count =
+ info->cqm_beacon_miss_thold;
+ priv->cqm_tx_failure_thold =
+ info->cqm_tx_fail_thold;
+ priv->cqm_tx_failure_count = 0;
+ cancel_delayed_work_sync(&priv->bss_loss_work);
+ cancel_delayed_work_sync(&priv->connection_loss_work);
+#endif /* CW1200_ENABLE_CQM_EXTENSIONS */
+
+ priv->bss_params.beaconLostCount =
+ priv->cqm_beacon_loss_count ?
+ priv->cqm_beacon_loss_count :
+ priv->cqm_link_loss_count;
+
+ priv->bss_params.aid = info->aid;
+
+ if (priv->join_dtim_period < 1)
+ priv->join_dtim_period = 1;
+
+ ap_printk(KERN_DEBUG "[STA] DTIM %d, interval: %d\n",
+ priv->join_dtim_period, priv->beacon_int);
+ ap_printk(KERN_DEBUG "[STA] Preamble: %d, " \
+ "Greenfield: %d, Aid: %d, " \
+ "Rates: 0x%.8X, Basic: 0x%.8X\n",
+ priv->association_mode.preambleType,
+ priv->association_mode.greenfieldMode,
+ priv->bss_params.aid,
+ priv->bss_params.operationalRateSet,
+ priv->association_mode.basicRateSet);
+ WARN_ON(wsm_set_association_mode(priv,
+ &priv->association_mode));
+ WARN_ON(wsm_keep_alive_period(priv, 30 /* sec */));
+ WARN_ON(wsm_set_bss_params(priv, &priv->bss_params));
+ priv->setbssparams_done = true;
+ WARN_ON(wsm_set_beacon_wakeup_period(priv,
+ priv->beacon_int * priv->join_dtim_period >
+ MAX_BEACON_SKIP_TIME_MS ? 1 :
+ priv->join_dtim_period, 0));
+
+ cw1200_set_pm(priv, &priv->powersave_mode);
+ if (priv->vif->p2p) {
+ ap_printk(KERN_DEBUG
+ "[STA] Setting p2p powersave "
+ "configuration.\n");
+ WARN_ON(wsm_set_p2p_ps_modeinfo(priv,
+ &priv->p2p_ps_modeinfo));
+ }
+
+ if (priv->is_BT_Present)
+ WARN_ON(cw1200_set_btcoexinfo(priv));
+#if 0
+ /* It's better to override internal TX rete; otherwise
+ * device sends RTS at too high rate. However device
+ * can't receive CTS at 1 and 2 Mbps. Well, 5.5 is a
+ * good choice for RTS/CTS, but that means PS poll
+ * will be sent at the same rate - impact on link
+ * budget. Not sure what is better.. */
+
+ /* Update: internal rate selection algorythm is not
+ * bad: if device is not receiving CTS at high rate,
+ * it drops RTS rate.
+ * So, conclusion: if-0 the code. Keep code just for
+ * information:
+ * Do not touch WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE! */
+
+ /* ~3 is a bug in device: RTS/CTS is not working at
+ * low rates */
+
+ __le32 internal_tx_rate = __cpu_to_le32(__ffs(
+ priv->association_mode.basicRateSet & ~3));
+ WARN_ON(wsm_write_mib(priv,
+ WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE,
+ &internal_tx_rate,
+ sizeof(internal_tx_rate)));
+#endif
+ } else {
+ memset(&priv->association_mode, 0,
+ sizeof(priv->association_mode));
+ memset(&priv->bss_params, 0, sizeof(priv->bss_params));
+ }
+ }
+ if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_CTS_PROT)) {
+ u32 prev_erp_info = priv->erp_info;
+
+ if (info->use_cts_prot)
+ priv->erp_info |= WLAN_ERP_USE_PROTECTION;
+ else if (!(prev_erp_info & WLAN_ERP_NON_ERP_PRESENT))
+ priv->erp_info &= ~WLAN_ERP_USE_PROTECTION;
+
+ if (prev_erp_info != priv->erp_info)
+ queue_delayed_work(priv->workqueue,
+ &priv->set_cts_work, 0*HZ);
+ }
+
+ if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_SLOT)) {
+ __le32 slot_time = info->use_short_slot ?
+ __cpu_to_le32(9) : __cpu_to_le32(20);
+ ap_printk(KERN_DEBUG "[STA] Slot time :%d us.\n",
+ __le32_to_cpu(slot_time));
+ WARN_ON(wsm_write_mib(priv, WSM_MIB_ID_DOT11_SLOT_TIME,
+ &slot_time, sizeof(slot_time)));
+ }
+ if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_CQM)) {
+ struct wsm_rcpi_rssi_threshold threshold = {
+ .rollingAverageCount = 8,
+ };
+
+#if 0
+ /* For verification purposes */
+ info->cqm_rssi_thold = -50;
+ info->cqm_rssi_hyst = 4;
+#endif /* 0 */
+
+ ap_printk(KERN_DEBUG "[CQM] RSSI threshold "
+ "subscribe: %d +- %d\n",
+ info->cqm_rssi_thold, info->cqm_rssi_hyst);
+#if defined(CW1200_ENABLE_CQM_EXTENSIONS)
+ ap_printk(KERN_DEBUG "[CQM] Beacon loss subscribe: %d\n",
+ info->cqm_beacon_miss_thold);
+ ap_printk(KERN_DEBUG "[CQM] TX failure subscribe: %d\n",
+ info->cqm_tx_fail_thold);
+ priv->cqm_rssi_thold = info->cqm_rssi_thold;
+ priv->cqm_rssi_hyst = info->cqm_rssi_hyst;
+#endif /* CW1200_ENABLE_CQM_EXTENSIONS */
+ if (info->cqm_rssi_thold || info->cqm_rssi_hyst) {
+ /* RSSI subscription enabled */
+ /* TODO: It's not a correct way of setting threshold.
+ * Upper and lower must be set equal here and adjusted
+ * in callback. However current implementation is much
+ * more relaible and stable. */
+ threshold.upperThreshold =
+ info->cqm_rssi_thold + info->cqm_rssi_hyst;
+ threshold.lowerThreshold =
+ info->cqm_rssi_thold;
+ threshold.rssiRcpiMode |=
+ WSM_RCPI_RSSI_THRESHOLD_ENABLE;
+ } else {
+ /* There is a bug in FW, see sta.c. We have to enable
+ * dummy subscription to get correct RSSI values. */
+ threshold.rssiRcpiMode |=
+ WSM_RCPI_RSSI_THRESHOLD_ENABLE |
+ WSM_RCPI_RSSI_DONT_USE_UPPER |
+ WSM_RCPI_RSSI_DONT_USE_LOWER;
+ }
+ WARN_ON(wsm_set_rcpi_rssi_threshold(priv, &threshold));
+
+#if defined(CW1200_ENABLE_CQM_EXTENSIONS)
+ priv->cqm_tx_failure_thold = info->cqm_tx_fail_thold;
+ priv->cqm_tx_failure_count = 0;
+
+ if (priv->cqm_beacon_loss_count !=
+ info->cqm_beacon_miss_thold) {
+ priv->cqm_beacon_loss_count =
+ info->cqm_beacon_miss_thold;
+ priv->bss_params.beaconLostCount =
+ priv->cqm_beacon_loss_count ?
+ priv->cqm_beacon_loss_count :
+ priv->cqm_link_loss_count;
+ /* Make sure we are associated before sending
+ * set_bss_params to firmware */
+ if (priv->bss_params.aid) {
+ WARN_ON(wsm_set_bss_params(priv,
+ &priv->bss_params));
+ priv->setbssparams_done = true;
+ }
+ }
+#endif /* CW1200_ENABLE_CQM_EXTENSIONS */
+ }
+ mutex_unlock(&priv->conf_mutex);
+}
+
+void cw1200_multicast_start_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, multicast_start_work);
+ long tmo = priv->join_dtim_period *
+ (priv->beacon_int + 20) * HZ / 1024;
+
+ cancel_work_sync(&priv->multicast_stop_work);
+
+ if (!priv->aid0_bit_set) {
+ wsm_lock_tx(priv);
+ cw1200_set_tim_impl(priv, true);
+ priv->aid0_bit_set = true;
+ mod_timer(&priv->mcast_timeout, jiffies + tmo);
+ wsm_unlock_tx(priv);
+ }
+}
+
+void cw1200_multicast_stop_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, multicast_stop_work);
+
+ if (priv->aid0_bit_set) {
+ del_timer_sync(&priv->mcast_timeout);
+ wsm_lock_tx(priv);
+ priv->aid0_bit_set = false;
+ cw1200_set_tim_impl(priv, false);
+ wsm_unlock_tx(priv);
+ }
+}
+
+void cw1200_mcast_timeout(unsigned long arg)
+{
+ struct cw1200_common *priv =
+ (struct cw1200_common *)arg;
+
+ wiphy_warn(priv->hw->wiphy,
+ "Multicast delivery timeout.\n");
+ spin_lock_bh(&priv->ps_state_lock);
+ priv->tx_multicast = priv->aid0_bit_set &&
+ priv->buffered_multicasts;
+ if (priv->tx_multicast)
+ cw1200_bh_wakeup(priv);
+ spin_unlock_bh(&priv->ps_state_lock);
+}
+
+int cw1200_ampdu_action(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ enum ieee80211_ampdu_mlme_action action,
+ struct ieee80211_sta *sta, u16 tid, u16 *ssn,
+ u8 buf_size)
+{
+ /* Aggregation is implemented fully in firmware,
+ * including block ack negotiation. Do not allow
+ * mac80211 stack to do anything: it interferes with
+ * the firmware. */
+ return -ENOTSUPP;
+}
+
+/* ******************************************************************** */
+/* WSM callback */
+void cw1200_suspend_resume(struct cw1200_common *priv,
+ struct wsm_suspend_resume *arg)
+{
+ ap_printk(KERN_DEBUG "[AP] %s: %s\n",
+ arg->stop ? "stop" : "start",
+ arg->multicast ? "broadcast" : "unicast");
+
+ if (arg->multicast) {
+ bool cancel_tmo = false;
+ spin_lock_bh(&priv->ps_state_lock);
+ if (arg->stop) {
+ priv->tx_multicast = false;
+ } else {
+ /* Firmware sends this indication every DTIM if there
+ * is a STA in powersave connected. There is no reason
+ * to suspend, following wakeup will consume much more
+ * power than it could be saved. */
+ cw1200_pm_stay_awake(&priv->pm_state,
+ priv->join_dtim_period *
+ (priv->beacon_int + 20) * HZ / 1024);
+ priv->tx_multicast = priv->aid0_bit_set &&
+ priv->buffered_multicasts;
+ if (priv->tx_multicast) {
+ cancel_tmo = true;
+ cw1200_bh_wakeup(priv);
+ }
+ }
+ spin_unlock_bh(&priv->ps_state_lock);
+ if (cancel_tmo)
+ del_timer_sync(&priv->mcast_timeout);
+ } else {
+ spin_lock_bh(&priv->ps_state_lock);
+ cw1200_ps_notify(priv, arg->link_id, arg->stop);
+ spin_unlock_bh(&priv->ps_state_lock);
+ if (!arg->stop)
+ cw1200_bh_wakeup(priv);
+ }
+ return;
+}
+
+/* ******************************************************************** */
+/* AP privates */
+
+static int cw1200_upload_beacon(struct cw1200_common *priv)
+{
+ int ret = 0;
+ struct wsm_template_frame frame = {
+ .frame_type = WSM_FRAME_TYPE_BEACON,
+ };
+ struct ieee80211_mgmt *mgmt;
+ u8 *erp_inf, *ies;
+ u32 ies_len;
+
+ if (priv->vif->p2p)
+ frame.rate = WSM_TRANSMIT_RATE_6;
+
+ frame.skb = ieee80211_beacon_get(priv->hw, priv->vif);
+ if (WARN_ON(!frame.skb))
+ return -ENOMEM;
+
+ mgmt = (void *)frame.skb->data;
+ ies = mgmt->u.beacon.variable;
+ ies_len = frame.skb->len - (u32)(ies - (u8 *)mgmt);
+ erp_inf = (u8 *)cfg80211_find_ie(WLAN_EID_ERP_INFO, ies, ies_len);
+ if (erp_inf) {
+ if (erp_inf[ERP_INFO_BYTE_OFFSET]
+ & WLAN_ERP_BARKER_PREAMBLE)
+ priv->erp_info |= WLAN_ERP_BARKER_PREAMBLE;
+ else
+ priv->erp_info &= ~WLAN_ERP_BARKER_PREAMBLE;
+
+ if (erp_inf[ERP_INFO_BYTE_OFFSET]
+ & WLAN_ERP_NON_ERP_PRESENT) {
+ priv->erp_info |= WLAN_ERP_USE_PROTECTION;
+ priv->erp_info |= WLAN_ERP_NON_ERP_PRESENT;
+ } else {
+ priv->erp_info &= ~WLAN_ERP_USE_PROTECTION;
+ priv->erp_info &= ~WLAN_ERP_NON_ERP_PRESENT;
+ }
+ }
+
+ ret = wsm_set_template_frame(priv, &frame);
+ if (!ret) {
+ /* TODO: Distille probe resp; remove TIM
+ * and other beacon-specific IEs */
+ *(__le16 *)frame.skb->data =
+ __cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_PROBE_RESP);
+ frame.frame_type = WSM_FRAME_TYPE_PROBE_RESPONSE;
+ if (priv->vif->p2p)
+ frame.disable = true;
+ ret = wsm_set_template_frame(priv, &frame);
+ }
+ dev_kfree_skb(frame.skb);
+
+ return ret;
+}
+
+static int cw1200_upload_pspoll(struct cw1200_common *priv)
+{
+ int ret = 0;
+ struct wsm_template_frame frame = {
+ .frame_type = WSM_FRAME_TYPE_PS_POLL,
+ .rate = 0xFF,
+ };
+
+
+ frame.skb = ieee80211_pspoll_get(priv->hw, priv->vif);
+ if (WARN_ON(!frame.skb))
+ return -ENOMEM;
+
+ ret = wsm_set_template_frame(priv, &frame);
+
+ dev_kfree_skb(frame.skb);
+
+ return ret;
+}
+
+static int cw1200_upload_null(struct cw1200_common *priv)
+{
+ int ret = 0;
+ struct wsm_template_frame frame = {
+ .frame_type = WSM_FRAME_TYPE_NULL,
+ .rate = 0xFF,
+ };
+
+
+ frame.skb = ieee80211_nullfunc_get(priv->hw, priv->vif);
+ if (WARN_ON(!frame.skb))
+ return -ENOMEM;
+
+ ret = wsm_set_template_frame(priv, &frame);
+
+ dev_kfree_skb(frame.skb);
+
+ return ret;
+}
+
+static int cw1200_enable_beaconing(struct cw1200_common *priv,
+ bool enable)
+{
+ struct wsm_beacon_transmit transmit = {
+ .enableBeaconing = enable,
+ };
+
+ return wsm_beacon_transmit(priv, &transmit);
+}
+
+static int cw1200_start_ap(struct cw1200_common *priv)
+{
+ int ret;
+ const u8 *ssidie;
+ struct sk_buff *skb;
+ int offset;
+ struct ieee80211_bss_conf *conf = &priv->vif->bss_conf;
+ struct wsm_start start = {
+ .mode = priv->vif->p2p ?
+ WSM_START_MODE_P2P_GO : WSM_START_MODE_AP,
+ .band = (priv->channel->band == IEEE80211_BAND_5GHZ) ?
+ WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G,
+ .channelNumber = priv->channel->hw_value,
+ .beaconInterval = conf->beacon_int,
+ .DTIMPeriod = conf->dtim_period,
+ .preambleType = conf->use_short_preamble ?
+ WSM_JOIN_PREAMBLE_SHORT :
+ WSM_JOIN_PREAMBLE_LONG,
+ .probeDelay = 100,
+ .basicRateSet = cw1200_rate_mask_to_wsm(priv,
+ conf->basic_rates),
+ };
+ struct wsm_operational_mode mode = {
+ .power_mode = wsm_power_mode_quiescent,
+ .disableMoreFlagUsage = true,
+ };
+
+ /* Get SSID */
+ skb = ieee80211_beacon_get(priv->hw, priv->vif);
+ if (WARN_ON(!skb))
+ return -ENOMEM;
+
+ offset = offsetof(struct ieee80211_mgmt, u.beacon.variable);
+ ssidie = cfg80211_find_ie(WLAN_EID_SSID, skb->data + offset,
+ skb->len - offset);
+
+ memset(priv->ssid, 0, sizeof(priv->ssid));
+ if (ssidie) {
+ priv->ssid_length = ssidie[1];
+ if (WARN_ON(priv->ssid_length > sizeof(priv->ssid)))
+ priv->ssid_length = sizeof(priv->ssid);
+ memcpy(priv->ssid, &ssidie[2], priv->ssid_length);
+ } else {
+ priv->ssid_length = 0;
+ }
+ dev_kfree_skb(skb);
+
+ priv->beacon_int = conf->beacon_int;
+ priv->join_dtim_period = conf->dtim_period;
+
+ start.ssidLength = priv->ssid_length;
+ memcpy(&start.ssid[0], priv->ssid, start.ssidLength);
+
+ memset(&priv->link_id_db, 0, sizeof(priv->link_id_db));
+
+ ap_printk(KERN_DEBUG "[AP] ch: %d(%d), bcn: %d(%d), "
+ "brt: 0x%.8X, ssid: %.*s.\n",
+ start.channelNumber, start.band,
+ start.beaconInterval, start.DTIMPeriod,
+ start.basicRateSet,
+ start.ssidLength, start.ssid);
+ ret = WARN_ON(wsm_start(priv, &start));
+ if (!ret)
+ ret = WARN_ON(cw1200_upload_keys(priv));
+ if (!ret && priv->vif->p2p) {
+ ap_printk(KERN_DEBUG
+ "[AP] Setting p2p powersave "
+ "configuration.\n");
+ WARN_ON(wsm_set_p2p_ps_modeinfo(priv,
+ &priv->p2p_ps_modeinfo));
+ }
+ if (!ret) {
+ WARN_ON(wsm_set_block_ack_policy(priv,
+ 0, 0));
+ priv->join_status = CW1200_JOIN_STATUS_AP;
+ cw1200_update_filtering(priv);
+ }
+ WARN_ON(wsm_set_operational_mode(priv, &mode));
+ return ret;
+}
+
+static int cw1200_update_beaconing(struct cw1200_common *priv)
+{
+ struct ieee80211_bss_conf *conf = &priv->vif->bss_conf;
+ struct wsm_reset reset = {
+ .link_id = 0,
+ .reset_statistics = true,
+ };
+
+ if (priv->mode == NL80211_IFTYPE_AP) {
+ /* TODO: check if changed channel, band */
+ if (priv->join_status != CW1200_JOIN_STATUS_AP ||
+ priv->beacon_int != conf->beacon_int) {
+ ap_printk(KERN_DEBUG "ap restarting\n");
+ wsm_lock_tx(priv);
+ if (priv->join_status != CW1200_JOIN_STATUS_PASSIVE)
+ WARN_ON(wsm_reset(priv, &reset));
+ priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
+ WARN_ON(cw1200_start_ap(priv));
+ wsm_unlock_tx(priv);
+ } else
+ ap_printk(KERN_DEBUG "ap started join_status: %d\n",
+ priv->join_status);
+ }
+ return 0;
+}
+
+int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac)
+{
+ int i, ret = 0;
+ spin_lock_bh(&priv->ps_state_lock);
+ for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
+ if (!memcmp(mac, priv->link_id_db[i].mac, ETH_ALEN) &&
+ priv->link_id_db[i].status) {
+ priv->link_id_db[i].timestamp = jiffies;
+ ret = i + 1;
+ break;
+ }
+ }
+ spin_unlock_bh(&priv->ps_state_lock);
+ return ret;
+}
+
+int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac)
+{
+ int i, ret = 0;
+ unsigned long max_inactivity = 0;
+ unsigned long now = jiffies;
+
+ spin_lock_bh(&priv->ps_state_lock);
+ for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
+ if (!priv->link_id_db[i].status) {
+ ret = i + 1;
+ break;
+ } else if (priv->link_id_db[i].status != CW1200_LINK_HARD &&
+ !priv->tx_queue_stats.link_map_cache[i + 1]) {
+
+ unsigned long inactivity =
+ now - priv->link_id_db[i].timestamp;
+ if (inactivity < max_inactivity)
+ continue;
+ max_inactivity = inactivity;
+ ret = i + 1;
+ }
+ }
+ if (ret) {
+ struct cw1200_link_entry *entry = &priv->link_id_db[ret - 1];
+ ap_printk(KERN_DEBUG "[AP] STA added, link_id: %d\n",
+ ret);
+ entry->status = CW1200_LINK_RESERVE;
+ memcpy(&entry->mac, mac, ETH_ALEN);
+ memset(&entry->buffered, 0, CW1200_MAX_TID);
+ skb_queue_head_init(&entry->rx_queue);
+ wsm_lock_tx_async(priv);
+ if (queue_work(priv->workqueue, &priv->link_id_work) <= 0)
+ wsm_unlock_tx(priv);
+ } else {
+ wiphy_info(priv->hw->wiphy,
+ "[AP] Early: no more link IDs available.\n");
+ }
+
+ spin_unlock_bh(&priv->ps_state_lock);
+ return ret;
+}
+
+void cw1200_link_id_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, link_id_work);
+ wsm_flush_tx(priv);
+ cw1200_link_id_gc_work(&priv->link_id_gc_work.work);
+ wsm_unlock_tx(priv);
+}
+
+void cw1200_link_id_gc_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, link_id_gc_work.work);
+ struct wsm_reset reset = {
+ .reset_statistics = false,
+ };
+ struct wsm_map_link map_link = {
+ .link_id = 0,
+ };
+ unsigned long now = jiffies;
+ unsigned long next_gc = -1;
+ long ttl;
+ bool need_reset;
+ u32 mask;
+ int i;
+
+ if (priv->join_status != CW1200_JOIN_STATUS_AP)
+ return;
+
+ wsm_lock_tx(priv);
+ spin_lock_bh(&priv->ps_state_lock);
+ for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
+ need_reset = false;
+ mask = BIT(i + 1);
+ if (priv->link_id_db[i].status == CW1200_LINK_RESERVE ||
+ (priv->link_id_db[i].status == CW1200_LINK_HARD &&
+ !(priv->link_id_map & mask))) {
+ if (priv->link_id_map & mask) {
+ priv->sta_asleep_mask &= ~mask;
+ priv->pspoll_mask &= ~mask;
+ need_reset = true;
+ }
+ priv->link_id_map |= mask;
+ if (priv->link_id_db[i].status != CW1200_LINK_HARD)
+ priv->link_id_db[i].status = CW1200_LINK_SOFT;
+ memcpy(map_link.mac_addr, priv->link_id_db[i].mac,
+ ETH_ALEN);
+ spin_unlock_bh(&priv->ps_state_lock);
+ if (need_reset) {
+ reset.link_id = i + 1;
+ WARN_ON(wsm_reset(priv, &reset));
+ }
+ map_link.link_id = i + 1;
+ WARN_ON(wsm_map_link(priv, &map_link));
+ next_gc = min(next_gc, CW1200_LINK_ID_GC_TIMEOUT);
+ spin_lock_bh(&priv->ps_state_lock);
+ } else if (priv->link_id_db[i].status == CW1200_LINK_SOFT) {
+ ttl = priv->link_id_db[i].timestamp - now +
+ CW1200_LINK_ID_GC_TIMEOUT;
+ if (ttl <= 0) {
+ need_reset = true;
+ priv->link_id_db[i].status = CW1200_LINK_OFF;
+ priv->link_id_map &= ~mask;
+ priv->sta_asleep_mask &= ~mask;
+ priv->pspoll_mask &= ~mask;
+ memset(map_link.mac_addr, 0, ETH_ALEN);
+ spin_unlock_bh(&priv->ps_state_lock);
+ reset.link_id = i + 1;
+ WARN_ON(wsm_reset(priv, &reset));
+ spin_lock_bh(&priv->ps_state_lock);
+ } else {
+ next_gc = min_t(unsigned long, next_gc, ttl);
+ }
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+ } else if (priv->link_id_db[i].status == CW1200_LINK_RESET ||
+ priv->link_id_db[i].status ==
+ CW1200_LINK_RESET_REMAP) {
+ int status = priv->link_id_db[i].status;
+ priv->link_id_db[i].status =
+ priv->link_id_db[i].prev_status;
+ priv->link_id_db[i].timestamp = now;
+ reset.link_id = i + 1;
+ spin_unlock_bh(&priv->ps_state_lock);
+ WARN_ON(wsm_reset(priv, &reset));
+ if (status == CW1200_LINK_RESET_REMAP) {
+ memcpy(map_link.mac_addr,
+ priv->link_id_db[i].mac,
+ ETH_ALEN);
+ map_link.link_id = i + 1;
+ WARN_ON(wsm_map_link(priv, &map_link));
+ next_gc = min(next_gc,
+ CW1200_LINK_ID_GC_TIMEOUT);
+ }
+ spin_lock_bh(&priv->ps_state_lock);
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+ }
+ if (need_reset) {
+ skb_queue_purge(&priv->link_id_db[i].rx_queue);
+ ap_printk(KERN_DEBUG "[AP] STA removed, link_id: %d\n",
+ reset.link_id);
+ }
+ }
+ spin_unlock_bh(&priv->ps_state_lock);
+ if (next_gc != -1)
+ queue_delayed_work(priv->workqueue,
+ &priv->link_id_gc_work, next_gc);
+ wsm_unlock_tx(priv);
+}
diff --git a/drivers/net/wireless/cw1200/ap.h b/drivers/net/wireless/cw1200/ap.h
new file mode 100644
index 0000000..2175a82
--- /dev/null
+++ b/drivers/net/wireless/cw1200/ap.h
@@ -0,0 +1,46 @@
+/*
+ * mac80211 STA and AP API for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef AP_H_INCLUDED
+#define AP_H_INCLUDED
+
+int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta,
+ bool set);
+int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta);
+int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta);
+void cw1200_sta_notify(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
+ enum sta_notify_cmd notify_cmd,
+ struct ieee80211_sta *sta);
+void cw1200_bss_info_changed(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed);
+int cw1200_ampdu_action(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ enum ieee80211_ampdu_mlme_action action,
+ struct ieee80211_sta *sta, u16 tid, u16 *ssn,
+ u8 buf_size);
+
+void cw1200_suspend_resume(struct cw1200_common *priv,
+ struct wsm_suspend_resume *arg);
+void cw1200_set_tim_work(struct work_struct *work);
+void cw1200_set_cts_work(struct work_struct *work);
+void cw1200_multicast_start_work(struct work_struct *work);
+void cw1200_multicast_stop_work(struct work_struct *work);
+void cw1200_mcast_timeout(unsigned long arg);
+int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac);
+int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac);
+void cw1200_link_id_work(struct work_struct *work);
+void cw1200_link_id_gc_work(struct work_struct *work);
+
+#endif
--
1.7.9


2012-03-02 20:27:04

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 15/21] cw1200: debug.*, implementation of the driver's debugfs.

The code contains implementation of debugfs entries used primary
for debugging purposes.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/debug.c | 577 +++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/debug.h | 168 ++++++++++
2 files changed, 745 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/debug.c
create mode 100644 drivers/net/wireless/cw1200/debug.h

diff --git a/drivers/net/wireless/cw1200/debug.c b/drivers/net/wireless/cw1200/debug.c
new file mode 100644
index 0000000..030ea36
--- /dev/null
+++ b/drivers/net/wireless/cw1200/debug.c
@@ -0,0 +1,577 @@
+/*
+ * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
+ * DebugFS code
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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 <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include "cw1200.h"
+#include "debug.h"
+
+/* join_status */
+static const char * const cw1200_debug_join_status[] = {
+ "passive",
+ "monitor",
+ "station",
+ "access point",
+};
+
+/* WSM_JOIN_PREAMBLE_... */
+static const char * const cw1200_debug_preamble[] = {
+ "long",
+ "short",
+ "long on 1 and 2 Mbps",
+};
+
+static const char * const cw1200_debug_fw_types[] = {
+ "ETF",
+ "WFM",
+ "WSM",
+ "HI test",
+ "Platform test",
+};
+
+static const char * const cw1200_debug_link_id[] = {
+ "OFF",
+ "REQ",
+ "SOFT",
+ "HARD",
+};
+
+static const char *cw1200_debug_mode(int mode)
+{
+ switch (mode) {
+ case NL80211_IFTYPE_UNSPECIFIED:
+ return "unspecified";
+ case NL80211_IFTYPE_MONITOR:
+ return "monitor";
+ case NL80211_IFTYPE_STATION:
+ return "station";
+ case NL80211_IFTYPE_ADHOC:
+ return "ad-hok";
+ case NL80211_IFTYPE_MESH_POINT:
+ return "mesh point";
+ case NL80211_IFTYPE_AP:
+ return "access point";
+ case NL80211_IFTYPE_P2P_CLIENT:
+ return "p2p client";
+ case NL80211_IFTYPE_P2P_GO:
+ return "p2p go";
+ default:
+ return "unsupported";
+ }
+}
+
+static void cw1200_queue_status_show(struct seq_file *seq,
+ struct cw1200_queue *q)
+{
+ int i;
+ seq_printf(seq, "Queue %d:\n", q->queue_id);
+ seq_printf(seq, " capacity: %ld\n", q->capacity);
+ seq_printf(seq, " queued: %ld\n", q->num_queued);
+ seq_printf(seq, " pending: %ld\n", q->num_pending);
+ seq_printf(seq, " sent: %ld\n", q->num_sent);
+ seq_printf(seq, " locked: %s\n", q->tx_locked_cnt ? "yes" : "no");
+ seq_printf(seq, " overfull: %s\n", q->overfull ? "yes" : "no");
+ seq_puts(seq, " link map: 0-> ");
+ for (i = 0; i < q->stats->map_capacity; ++i)
+ seq_printf(seq, "%.2d ", q->link_map_cache[i]);
+ seq_printf(seq, "<-%ld\n", q->stats->map_capacity);
+}
+
+static void cw1200_debug_print_map(struct seq_file *seq,
+ struct cw1200_common *priv,
+ const char *label,
+ u32 map)
+{
+ int i;
+ seq_printf(seq, "%s0-> ", label);
+ for (i = 0; i < priv->tx_queue_stats.map_capacity; ++i)
+ seq_printf(seq, "%s ", (map & BIT(i)) ? "**" : "..");
+ seq_printf(seq, "<-%ld\n", priv->tx_queue_stats.map_capacity - 1);
+}
+
+static int cw1200_status_show(struct seq_file *seq, void *v)
+{
+ int i;
+ struct list_head *item;
+ struct cw1200_common *priv = seq->private;
+ struct cw1200_debug_priv *d = priv->debug;
+ int ba_cnt, ba_acc, ba_avg = 0;
+ bool ba_ena;
+
+ spin_lock_bh(&priv->ba_lock);
+ ba_cnt = priv->debug->ba_cnt;
+ ba_acc = priv->debug->ba_acc;
+ ba_ena = priv->ba_ena;
+ if (ba_cnt)
+ ba_avg = ba_acc / ba_cnt;
+ spin_unlock_bh(&priv->ba_lock);
+
+ seq_puts(seq, "CW1200 Wireless LAN driver status\n");
+ seq_printf(seq, "Hardware: %d.%d\n",
+ priv->wsm_caps.hardwareId,
+ priv->wsm_caps.hardwareSubId);
+ seq_printf(seq, "Firmware: %s %d.%d\n",
+ cw1200_debug_fw_types[priv->wsm_caps.firmwareType],
+ priv->wsm_caps.firmwareVersion,
+ priv->wsm_caps.firmwareBuildNumber);
+ seq_printf(seq, "FW API: %d\n",
+ priv->wsm_caps.firmwareApiVer);
+ seq_printf(seq, "FW caps: 0x%.4X\n",
+ priv->wsm_caps.firmwareCap);
+ seq_printf(seq, "Mode: %s%s\n",
+ cw1200_debug_mode(priv->mode),
+ priv->listening ? " (listening)" : "");
+ seq_printf(seq, "Assoc: %s\n",
+ cw1200_debug_join_status[priv->join_status]);
+ if (priv->channel)
+ seq_printf(seq, "Channel: %d%s\n",
+ priv->channel->hw_value,
+ priv->channel_switch_in_progress ?
+ " (switching)" : "");
+ if (priv->rx_filter.promiscuous)
+ seq_puts(seq, "Filter: promisc\n");
+ else if (priv->rx_filter.fcs)
+ seq_puts(seq, "Filter: fcs\n");
+ if (priv->rx_filter.bssid)
+ seq_puts(seq, "Filter: bssid\n");
+ if (priv->bf_control.bcn_count)
+ seq_puts(seq, "Filter: beacons\n");
+
+ if (priv->enable_beacon ||
+ priv->mode == NL80211_IFTYPE_AP ||
+ priv->mode == NL80211_IFTYPE_ADHOC ||
+ priv->mode == NL80211_IFTYPE_MESH_POINT ||
+ priv->mode == NL80211_IFTYPE_P2P_GO)
+ seq_printf(seq, "Beaconing: %s\n",
+ priv->enable_beacon ?
+ "enabled" : "disabled");
+ if (priv->ssid_length ||
+ priv->mode == NL80211_IFTYPE_AP ||
+ priv->mode == NL80211_IFTYPE_ADHOC ||
+ priv->mode == NL80211_IFTYPE_MESH_POINT ||
+ priv->mode == NL80211_IFTYPE_P2P_GO)
+ seq_printf(seq, "SSID: %.*s\n",
+ (int)priv->ssid_length, priv->ssid);
+
+ for (i = 0; i < 4; ++i) {
+ seq_printf(seq, "EDCA(%d): %d, %d, %d, %d, %d\n", i,
+ priv->edca.params[i].cwMin,
+ priv->edca.params[i].cwMax,
+ priv->edca.params[i].aifns,
+ priv->edca.params[i].txOpLimit,
+ priv->edca.params[i].maxReceiveLifetime);
+ }
+ if (priv->join_status == CW1200_JOIN_STATUS_STA) {
+ static const char *pmMode = "unknown";
+ switch (priv->powersave_mode.pmMode) {
+ case WSM_PSM_ACTIVE:
+ pmMode = "off";
+ break;
+ case WSM_PSM_PS:
+ pmMode = "on";
+ break;
+ case WSM_PSM_FAST_PS:
+ pmMode = "dynamic";
+ break;
+ }
+ seq_printf(seq, "Preamble: %s\n",
+ cw1200_debug_preamble[
+ priv->association_mode.preambleType]);
+ seq_printf(seq, "AMPDU spcn: %d\n",
+ priv->association_mode.mpduStartSpacing);
+ seq_printf(seq, "Basic rate: 0x%.8X\n",
+ le32_to_cpu(priv->association_mode.basicRateSet));
+ seq_printf(seq, "Bss lost: %d beacons\n",
+ priv->bss_params.beaconLostCount);
+ seq_printf(seq, "AID: %d\n",
+ priv->bss_params.aid);
+ seq_printf(seq, "Rates: 0x%.8X\n",
+ priv->bss_params.operationalRateSet);
+ seq_printf(seq, "Powersave: %s\n", pmMode);
+ }
+ seq_printf(seq, "HT: %s\n",
+ cw1200_is_ht(&priv->ht_info) ? "on" : "off");
+ if (cw1200_is_ht(&priv->ht_info)) {
+ seq_printf(seq, "Greenfield: %s\n",
+ cw1200_ht_greenfield(&priv->ht_info) ? "yes" : "no");
+ seq_printf(seq, "AMPDU dens: %d\n",
+ cw1200_ht_ampdu_density(&priv->ht_info));
+ }
+ seq_printf(seq, "RSSI thold: %d\n",
+ priv->cqm_rssi_thold);
+ seq_printf(seq, "RSSI hyst: %d\n",
+ priv->cqm_rssi_hyst);
+ seq_printf(seq, "TXFL thold: %d\n",
+ priv->cqm_tx_failure_thold);
+ seq_printf(seq, "Linkloss: %d\n",
+ priv->cqm_link_loss_count);
+ seq_printf(seq, "Bcnloss: %d\n",
+ priv->cqm_beacon_loss_count);
+ seq_printf(seq, "Long retr: %d\n",
+ priv->long_frame_max_tx_count);
+ seq_printf(seq, "Short retr: %d\n",
+ priv->short_frame_max_tx_count);
+ spin_lock_bh(&priv->tx_policy_cache.lock);
+ i = 0;
+ list_for_each(item, &priv->tx_policy_cache.used)
+ ++i;
+ spin_unlock_bh(&priv->tx_policy_cache.lock);
+ seq_printf(seq, "RC in use: %d\n", i);
+ seq_printf(seq, "BA stat: %d, %d (%d)\n",
+ ba_cnt, ba_acc, ba_avg);
+ seq_printf(seq, "Block ACK: %s\n", ba_ena ? "on" : "off");
+
+ seq_puts(seq, "\n");
+ for (i = 0; i < 4; ++i) {
+ cw1200_queue_status_show(seq, &priv->tx_queue[i]);
+ seq_puts(seq, "\n");
+ }
+
+ cw1200_debug_print_map(seq, priv, "Link map: ",
+ priv->link_id_map);
+ cw1200_debug_print_map(seq, priv, "Asleep map: ",
+ priv->sta_asleep_mask);
+ cw1200_debug_print_map(seq, priv, "PSPOLL map: ",
+ priv->pspoll_mask);
+
+ seq_puts(seq, "\n");
+
+ for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
+ if (priv->link_id_db[i].status) {
+ seq_printf(seq, "Link %d: %s, %pM\n",
+ i + 1, cw1200_debug_link_id[
+ priv->link_id_db[i].status],
+ priv->link_id_db[i].mac);
+ }
+ }
+
+ seq_puts(seq, "\n");
+
+ seq_printf(seq, "BH status: %s\n",
+ atomic_read(&priv->bh_term) ? "terminated" : "alive");
+ seq_printf(seq, "Pending RX: %d\n",
+ atomic_read(&priv->bh_rx));
+ seq_printf(seq, "Pending TX: %d\n",
+ atomic_read(&priv->bh_tx));
+ if (priv->bh_error)
+ seq_printf(seq, "BH errcode: %d\n",
+ priv->bh_error);
+ seq_printf(seq, "TX bufs: %d x %d bytes\n",
+ priv->wsm_caps.numInpChBufs,
+ priv->wsm_caps.sizeInpChBuf);
+ seq_printf(seq, "Used bufs: %d\n",
+ priv->hw_bufs_used);
+ seq_printf(seq, "Powermgmt: %s\n",
+ priv->powersave_enabled ? "on" : "off");
+ seq_printf(seq, "Device: %s\n",
+ priv->device_can_sleep ? "alseep" : "awake");
+
+ spin_lock(&priv->wsm_cmd.lock);
+ seq_printf(seq, "WSM status: %s\n",
+ priv->wsm_cmd.done ? "idle" : "active");
+ seq_printf(seq, "WSM cmd: 0x%.4X (%ld bytes)\n",
+ priv->wsm_cmd.cmd, priv->wsm_cmd.len);
+ seq_printf(seq, "WSM retval: %d\n",
+ priv->wsm_cmd.ret);
+ spin_unlock(&priv->wsm_cmd.lock);
+
+ seq_printf(seq, "Datapath: %s\n",
+ atomic_read(&priv->tx_lock) ? "locked" : "unlocked");
+ if (atomic_read(&priv->tx_lock))
+ seq_printf(seq, "TXlock cnt: %d\n",
+ atomic_read(&priv->tx_lock));
+
+ seq_printf(seq, "TXed: %d\n",
+ d->tx);
+ seq_printf(seq, "AGG TXed: %d\n",
+ d->tx_agg);
+ seq_printf(seq, "MULTI TXed: %d (%d)\n",
+ d->tx_multi, d->tx_multi_frames);
+ seq_printf(seq, "RXed: %d\n",
+ d->rx);
+ seq_printf(seq, "AGG RXed: %d\n",
+ d->rx_agg);
+ seq_printf(seq, "TX miss: %d\n",
+ d->tx_cache_miss);
+ seq_printf(seq, "TX align: %d\n",
+ d->tx_align);
+ seq_printf(seq, "TX burst: %d\n",
+ d->tx_burst);
+ seq_printf(seq, "RX burst: %d\n",
+ d->rx_burst);
+ seq_printf(seq, "TX TTL: %d\n",
+ d->tx_ttl);
+ seq_printf(seq, "Scan: %s\n",
+ atomic_read(&priv->scan.in_progress) ? "active" : "idle");
+ seq_printf(seq, "Led state: 0x%.2X\n",
+ priv->softled_state);
+
+ return 0;
+}
+
+static int cw1200_status_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, &cw1200_status_show,
+ inode->i_private);
+}
+
+static const struct file_operations fops_status = {
+ .open = cw1200_status_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static int cw1200_counters_show(struct seq_file *seq, void *v)
+{
+ int ret;
+ struct cw1200_common *priv = seq->private;
+ struct wsm_counters_table counters;
+
+ ret = wsm_get_counters_table(priv, &counters);
+ if (ret)
+ return ret;
+
+#define CAT_STR(x, y) x ## y
+#define PUT_COUNTER(tab, name) \
+ seq_printf(seq, "%s:" tab "%d\n", #name, \
+ __le32_to_cpu(counters.CAT_STR(count, name)))
+
+ PUT_COUNTER("\t\t", PlcpErrors);
+ PUT_COUNTER("\t\t", FcsErrors);
+ PUT_COUNTER("\t\t", TxPackets);
+ PUT_COUNTER("\t\t", RxPackets);
+ PUT_COUNTER("\t\t", RxPacketErrors);
+ PUT_COUNTER("\t", RxDecryptionFailures);
+ PUT_COUNTER("\t\t", RxMicFailures);
+ PUT_COUNTER("\t", RxNoKeyFailures);
+ PUT_COUNTER("\t", TxMulticastFrames);
+ PUT_COUNTER("\t", TxFramesSuccess);
+ PUT_COUNTER("\t", TxFrameFailures);
+ PUT_COUNTER("\t", TxFramesRetried);
+ PUT_COUNTER("\t", TxFramesMultiRetried);
+ PUT_COUNTER("\t", RxFrameDuplicates);
+ PUT_COUNTER("\t\t", RtsSuccess);
+ PUT_COUNTER("\t\t", RtsFailures);
+ PUT_COUNTER("\t\t", AckFailures);
+ PUT_COUNTER("\t", RxMulticastFrames);
+ PUT_COUNTER("\t", RxFramesSuccess);
+ PUT_COUNTER("\t", RxCMACICVErrors);
+ PUT_COUNTER("\t\t", RxCMACReplays);
+ PUT_COUNTER("\t", RxMgmtCCMPReplays);
+
+#undef PUT_COUNTER
+#undef CAT_STR
+
+ return 0;
+}
+
+static int cw1200_counters_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, &cw1200_counters_show,
+ inode->i_private);
+}
+
+static const struct file_operations fops_counters = {
+ .open = cw1200_counters_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static int cw1200_generic_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t cw1200_11n_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cw1200_common *priv = file->private_data;
+ struct ieee80211_supported_band *band =
+ priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ];
+ return simple_read_from_buffer(user_buf, count, ppos,
+ band->ht_cap.ht_supported ? "1\n" : "0\n", 2);
+}
+
+static ssize_t cw1200_11n_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cw1200_common *priv = file->private_data;
+ struct ieee80211_supported_band *band[2] = {
+ priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ],
+ priv->hw->wiphy->bands[IEEE80211_BAND_5GHZ],
+ };
+ char buf[1];
+ int ena = 0;
+
+ if (!count)
+ return -EINVAL;
+ if (copy_from_user(buf, user_buf, 1))
+ return -EFAULT;
+ if (buf[0] == 1)
+ ena = 1;
+
+ band[0]->ht_cap.ht_supported = ena;
+#ifdef CONFIG_CW1200_5GHZ_SUPPORT
+ band[1]->ht_cap.ht_supported = ena;
+#endif /* CONFIG_CW1200_5GHZ_SUPPORT */
+
+ return count;
+}
+
+static const struct file_operations fops_11n = {
+ .open = cw1200_generic_open,
+ .read = cw1200_11n_read,
+ .write = cw1200_11n_write,
+ .llseek = default_llseek,
+};
+
+static ssize_t cw1200_wsm_dumps(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cw1200_common *priv = file->private_data;
+ char buf[1];
+
+ if (!count)
+ return -EINVAL;
+ if (copy_from_user(buf, user_buf, 1))
+ return -EFAULT;
+
+ if (buf[0] == '1')
+ priv->wsm_enable_wsm_dumps = 1;
+ else
+ priv->wsm_enable_wsm_dumps = 0;
+
+ return count;
+}
+
+static const struct file_operations fops_wsm_dumps = {
+ .open = cw1200_generic_open,
+ .write = cw1200_wsm_dumps,
+ .llseek = default_llseek,
+};
+
+#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT)
+static ssize_t cw1200_short_dump_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cw1200_common *priv = file->private_data;
+ char buf[20];
+ size_t size = 0;
+
+ sprintf(buf, "Size: %u\n", priv->wsm_dump_max_size);
+ size = strlen(buf);
+
+ return simple_read_from_buffer(user_buf, count, ppos,
+ buf, size);
+}
+
+static ssize_t cw1200_short_dump_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cw1200_common *priv = file->private_data;
+ char buf[20];
+ unsigned long dump_size = 0;
+
+ if (!count || count > 20)
+ return -EINVAL;
+ if (copy_from_user(buf, user_buf, count))
+ return -EFAULT;
+
+ if (kstrtoul(buf, 10, &dump_size))
+ return -EINVAL;
+ printk(KERN_ERR "%s get %lu\n", __func__, dump_size);
+
+ priv->wsm_dump_max_size = dump_size;
+
+ return count;
+}
+
+static const struct file_operations fops_short_dump = {
+ .open = cw1200_generic_open,
+ .write = cw1200_short_dump_write,
+ .read = cw1200_short_dump_read,
+ .llseek = default_llseek,
+};
+#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */
+
+int cw1200_debug_init(struct cw1200_common *priv)
+{
+ int ret = -ENOMEM;
+ struct cw1200_debug_priv *d = kzalloc(sizeof(struct cw1200_debug_priv),
+ GFP_KERNEL);
+ priv->debug = d;
+ if (!d)
+ return ret;
+
+ d->debugfs_phy = debugfs_create_dir("cw1200",
+ priv->hw->wiphy->debugfsdir);
+ if (!d->debugfs_phy)
+ goto err;
+
+ if (!debugfs_create_file("status", S_IRUSR, d->debugfs_phy,
+ priv, &fops_status))
+ goto err;
+
+ if (!debugfs_create_file("counters", S_IRUSR, d->debugfs_phy,
+ priv, &fops_counters))
+ goto err;
+
+ if (!debugfs_create_file("11n", S_IRUSR | S_IWUSR,
+ d->debugfs_phy, priv, &fops_11n))
+ goto err;
+
+ if (!debugfs_create_file("wsm_dumps", S_IWUSR, d->debugfs_phy,
+ priv, &fops_wsm_dumps))
+ goto err;
+
+#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT)
+ if (!debugfs_create_file("wsm_dump_size", S_IRUSR | S_IWUSR,
+ d->debugfs_phy, priv, &fops_short_dump))
+ goto err;
+#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */
+
+ ret = cw1200_itp_init(priv);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ priv->debug = NULL;
+ debugfs_remove_recursive(d->debugfs_phy);
+ kfree(d);
+ return ret;
+}
+
+void cw1200_debug_release(struct cw1200_common *priv)
+{
+ struct cw1200_debug_priv *d = priv->debug;
+ if (d) {
+ cw1200_itp_release(priv);
+ priv->debug = NULL;
+ kfree(d);
+ }
+}
+
+int cw1200_print_fw_version(struct cw1200_common *priv, u8 *buf, size_t len)
+{
+ return snprintf(buf, len, "%s %d.%d",
+ cw1200_debug_fw_types[priv->wsm_caps.firmwareType],
+ priv->wsm_caps.firmwareVersion,
+ priv->wsm_caps.firmwareBuildNumber);
+}
diff --git a/drivers/net/wireless/cw1200/debug.h b/drivers/net/wireless/cw1200/debug.h
new file mode 100644
index 0000000..72b827f
--- /dev/null
+++ b/drivers/net/wireless/cw1200/debug.h
@@ -0,0 +1,168 @@
+/*
+ * DebugFS code for ST-Ericsson CW1200 mac80211 driver
+ *
+ * Copyright (c) 2011-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_DEBUG_H_INCLUDED
+#define CW1200_DEBUG_H_INCLUDED
+
+#include "itp.h"
+
+struct cw200_common;
+
+#ifdef CONFIG_CW1200_DEBUGFS
+
+struct cw1200_debug_priv {
+ struct dentry *debugfs_phy;
+ int tx;
+ int tx_agg;
+ int rx;
+ int rx_agg;
+ int tx_multi;
+ int tx_multi_frames;
+ int tx_cache_miss;
+ int tx_align;
+ int tx_ttl;
+ int tx_burst;
+ int rx_burst;
+ int ba_cnt;
+ int ba_acc;
+#ifdef CONFIG_CW1200_ITP
+ struct cw1200_itp itp;
+#endif /* CONFIG_CW1200_ITP */
+};
+
+int cw1200_debug_init(struct cw1200_common *priv);
+void cw1200_debug_release(struct cw1200_common *priv);
+
+static inline void cw1200_debug_txed(struct cw1200_common *priv)
+{
+ ++priv->debug->tx;
+}
+
+static inline void cw1200_debug_txed_agg(struct cw1200_common *priv)
+{
+ ++priv->debug->tx_agg;
+}
+
+static inline void cw1200_debug_txed_multi(struct cw1200_common *priv,
+ int count)
+{
+ ++priv->debug->tx_multi;
+ priv->debug->tx_multi_frames += count;
+}
+
+static inline void cw1200_debug_rxed(struct cw1200_common *priv)
+{
+ ++priv->debug->rx;
+}
+
+static inline void cw1200_debug_rxed_agg(struct cw1200_common *priv)
+{
+ ++priv->debug->rx_agg;
+}
+
+static inline void cw1200_debug_tx_cache_miss(struct cw1200_common *priv)
+{
+ ++priv->debug->tx_cache_miss;
+}
+
+static inline void cw1200_debug_tx_align(struct cw1200_common *priv)
+{
+ ++priv->debug->tx_align;
+}
+
+static inline void cw1200_debug_tx_ttl(struct cw1200_common *priv)
+{
+ ++priv->debug->tx_ttl;
+}
+
+static inline void cw1200_debug_tx_burst(struct cw1200_common *priv)
+{
+ ++priv->debug->tx_burst;
+}
+
+static inline void cw1200_debug_rx_burst(struct cw1200_common *priv)
+{
+ ++priv->debug->rx_burst;
+}
+
+static inline void cw1200_debug_ba(struct cw1200_common *priv,
+ int ba_cnt, int ba_acc)
+{
+ priv->debug->ba_cnt = ba_cnt;
+ priv->debug->ba_acc = ba_acc;
+}
+
+int cw1200_print_fw_version(struct cw1200_common *priv, u8 *buf, size_t len);
+
+#else /* CONFIG_CW1200_DEBUGFS */
+
+static inline int cw1200_debug_init(struct cw1200_common *priv)
+{
+ return 0;
+}
+
+static inline void cw1200_debug_release(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_txed(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_txed_agg(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_txed_multi(struct cw1200_common *priv,
+ int count)
+{
+}
+
+static inline void cw1200_debug_rxed(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_rxed_agg(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_tx_cache_miss(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_tx_align(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_tx_ttl(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_tx_burst(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_rx_burst(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_debug_ba(struct cw1200_common *priv,
+ int ba_cnt, int ba_acc)
+{
+}
+
+int cw1200_print_fw_version(struct cw1200_common *priv, u8 *buf, size_t len)
+{
+}
+
+#endif /* CONFIG_CW1200_DEBUGFS */
+
+#endif /* CW1200_DEBUG_H_INCLUDED */
--
1.7.9


2012-03-02 20:26:56

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 10/21] cw1200: ht.h, small helper header with HT definitions.

The patch implements HT-related inlines, used in the driver.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/ht.h | 43 ++++++++++++++++++++++++++++++++++++++
1 files changed, 43 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/ht.h

diff --git a/drivers/net/wireless/cw1200/ht.h b/drivers/net/wireless/cw1200/ht.h
new file mode 100644
index 0000000..5c486a6
--- /dev/null
+++ b/drivers/net/wireless/cw1200/ht.h
@@ -0,0 +1,43 @@
+/*
+ * HT-related code for ST-Ericsson CW1200 driver
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_HT_H_INCLUDED
+#define CW1200_HT_H_INCLUDED
+
+#include <net/mac80211.h>
+
+struct cw1200_ht_info {
+ struct ieee80211_sta_ht_cap ht_cap;
+ enum nl80211_channel_type channel_type;
+ u16 operation_mode;
+};
+
+static inline int cw1200_is_ht(const struct cw1200_ht_info *ht_info)
+{
+ return ht_info->channel_type != NL80211_CHAN_NO_HT;
+}
+
+static inline int cw1200_ht_greenfield(const struct cw1200_ht_info *ht_info)
+{
+ return cw1200_is_ht(ht_info) &&
+ (ht_info->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) &&
+ !(ht_info->operation_mode &
+ IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT);
+}
+
+static inline int cw1200_ht_ampdu_density(const struct cw1200_ht_info *ht_info)
+{
+ if (!cw1200_is_ht(ht_info))
+ return 0;
+ return ht_info->ht_cap.ampdu_density;
+}
+
+#endif /* CW1200_HT_H_INCLUDED */
--
1.7.9


2012-03-02 20:27:07

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 18/21] cw1200: main.c, core initialization code.

The code in the patch creates and destroys the driver core.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/main.c | 558 ++++++++++++++++++++++++++++++++++++
1 files changed, 558 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/main.c

diff --git a/drivers/net/wireless/cw1200/main.c b/drivers/net/wireless/cw1200/main.c
new file mode 100644
index 0000000..eb1ce5b
--- /dev/null
+++ b/drivers/net/wireless/cw1200/main.c
@@ -0,0 +1,558 @@
+/*
+ * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on:
+ * Copyright (c) 2006, Michael Wu <[email protected]>
+ * Copyright (c) 2007-2009, Christian Lamparter <[email protected]>
+ * Copyright 2008, Johannes Berg <[email protected]>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <[email protected]>, et al.
+ * - stlc45xx driver
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * 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 <linux/init.h>
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <linux/vmalloc.h>
+#include <linux/random.h>
+#include <linux/sched.h>
+#include <net/mac80211.h>
+
+#include "cw1200.h"
+#include "txrx.h"
+#include "sbus.h"
+#include "fwio.h"
+#include "hwio.h"
+#include "bh.h"
+#include "sta.h"
+#include "ap.h"
+#include "scan.h"
+#include "debug.h"
+#include "pm.h"
+
+MODULE_AUTHOR("Dmitry Tarnyagin <[email protected]>");
+MODULE_DESCRIPTION("Softmac ST-Ericsson CW1200 common code");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("cw1200_core");
+
+/* Accept MAC address of the form macaddr=0x00,0x80,0xE1,0x30,0x40,0x50 */
+static u8 cw1200_mac_template[ETH_ALEN] = {0x00, 0x80, 0xe1, 0x00, 0x00, 0x00};
+module_param_array_named(macaddr, cw1200_mac_template, byte, NULL, S_IRUGO);
+MODULE_PARM_DESC(macaddr, "MAC address");
+
+/* TODO: use rates and channels from the device */
+#define RATETAB_ENT(_rate, _rateid, _flags) \
+ { \
+ .bitrate = (_rate), \
+ .hw_value = (_rateid), \
+ .flags = (_flags), \
+ }
+
+static struct ieee80211_rate cw1200_rates[] = {
+ RATETAB_ENT(10, 0, 0),
+ RATETAB_ENT(20, 1, 0),
+ RATETAB_ENT(55, 2, 0),
+ RATETAB_ENT(110, 3, 0),
+ RATETAB_ENT(60, 6, 0),
+ RATETAB_ENT(90, 7, 0),
+ RATETAB_ENT(120, 8, 0),
+ RATETAB_ENT(180, 9, 0),
+ RATETAB_ENT(240, 10, 0),
+ RATETAB_ENT(360, 11, 0),
+ RATETAB_ENT(480, 12, 0),
+ RATETAB_ENT(540, 13, 0),
+};
+
+static struct ieee80211_rate cw1200_mcs_rates[] = {
+ RATETAB_ENT(65, 14, IEEE80211_TX_RC_MCS),
+ RATETAB_ENT(130, 15, IEEE80211_TX_RC_MCS),
+ RATETAB_ENT(195, 16, IEEE80211_TX_RC_MCS),
+ RATETAB_ENT(260, 17, IEEE80211_TX_RC_MCS),
+ RATETAB_ENT(390, 18, IEEE80211_TX_RC_MCS),
+ RATETAB_ENT(520, 19, IEEE80211_TX_RC_MCS),
+ RATETAB_ENT(585, 20, IEEE80211_TX_RC_MCS),
+ RATETAB_ENT(650, 21, IEEE80211_TX_RC_MCS),
+};
+
+#define cw1200_a_rates (cw1200_rates + 4)
+#define cw1200_a_rates_size (ARRAY_SIZE(cw1200_rates) - 4)
+#define cw1200_g_rates (cw1200_rates + 0)
+#define cw1200_g_rates_size (ARRAY_SIZE(cw1200_rates))
+#define cw1200_n_rates (cw1200_mcs_rates)
+#define cw1200_n_rates_size (ARRAY_SIZE(cw1200_mcs_rates))
+
+
+#define CHAN2G(_channel, _freq, _flags) { \
+ .band = IEEE80211_BAND_2GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_channel), \
+ .flags = (_flags), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+#define CHAN5G(_channel, _flags) { \
+ .band = IEEE80211_BAND_5GHZ, \
+ .center_freq = 5000 + (5 * (_channel)), \
+ .hw_value = (_channel), \
+ .flags = (_flags), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+static struct ieee80211_channel cw1200_2ghz_chantable[] = {
+ CHAN2G(1, 2412, 0),
+ CHAN2G(2, 2417, 0),
+ CHAN2G(3, 2422, 0),
+ CHAN2G(4, 2427, 0),
+ CHAN2G(5, 2432, 0),
+ CHAN2G(6, 2437, 0),
+ CHAN2G(7, 2442, 0),
+ CHAN2G(8, 2447, 0),
+ CHAN2G(9, 2452, 0),
+ CHAN2G(10, 2457, 0),
+ CHAN2G(11, 2462, 0),
+ CHAN2G(12, 2467, 0),
+ CHAN2G(13, 2472, 0),
+ CHAN2G(14, 2484, 0),
+};
+
+#ifdef CONFIG_CW1200_5GHZ_SUPPORT
+static struct ieee80211_channel cw1200_5ghz_chantable[] = {
+ CHAN5G(34, 0), CHAN5G(36, 0),
+ CHAN5G(38, 0), CHAN5G(40, 0),
+ CHAN5G(42, 0), CHAN5G(44, 0),
+ CHAN5G(46, 0), CHAN5G(48, 0),
+ CHAN5G(52, 0), CHAN5G(56, 0),
+ CHAN5G(60, 0), CHAN5G(64, 0),
+ CHAN5G(100, 0), CHAN5G(104, 0),
+ CHAN5G(108, 0), CHAN5G(112, 0),
+ CHAN5G(116, 0), CHAN5G(120, 0),
+ CHAN5G(124, 0), CHAN5G(128, 0),
+ CHAN5G(132, 0), CHAN5G(136, 0),
+ CHAN5G(140, 0), CHAN5G(149, 0),
+ CHAN5G(153, 0), CHAN5G(157, 0),
+ CHAN5G(161, 0), CHAN5G(165, 0),
+ CHAN5G(184, 0), CHAN5G(188, 0),
+ CHAN5G(192, 0), CHAN5G(196, 0),
+ CHAN5G(200, 0), CHAN5G(204, 0),
+ CHAN5G(208, 0), CHAN5G(212, 0),
+ CHAN5G(216, 0),
+};
+#endif /* CONFIG_CW1200_5GHZ_SUPPORT */
+
+static struct ieee80211_supported_band cw1200_band_2ghz = {
+ .channels = cw1200_2ghz_chantable,
+ .n_channels = ARRAY_SIZE(cw1200_2ghz_chantable),
+ .bitrates = cw1200_g_rates,
+ .n_bitrates = cw1200_g_rates_size,
+ .ht_cap = {
+ .cap = IEEE80211_HT_CAP_GRN_FLD |
+ (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) |
+ IEEE80211_HT_CAP_MAX_AMSDU,
+ .ht_supported = 1,
+ .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K,
+ .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
+ .mcs = {
+ .rx_mask[0] = 0xFF,
+ .rx_highest = __cpu_to_le16(0x41),
+ .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+ },
+ },
+};
+
+#ifdef CONFIG_CW1200_5GHZ_SUPPORT
+static struct ieee80211_supported_band cw1200_band_5ghz = {
+ .channels = cw1200_5ghz_chantable,
+ .n_channels = ARRAY_SIZE(cw1200_5ghz_chantable),
+ .bitrates = cw1200_a_rates,
+ .n_bitrates = cw1200_a_rates_size,
+ .ht_cap = {
+ .cap = IEEE80211_HT_CAP_GRN_FLD |
+ (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) |
+ IEEE80211_HT_CAP_MAX_AMSDU,
+ .ht_supported = 1,
+ .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K,
+ .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
+ .mcs = {
+ .rx_mask[0] = 0xFF,
+ .rx_highest = __cpu_to_le16(0x41),
+ .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+ },
+ },
+};
+#endif /* CONFIG_CW1200_5GHZ_SUPPORT */
+
+static const unsigned long cw1200_ttl[] = {
+ 1 * HZ, /* VO */
+ 2 * HZ, /* VI */
+ 5 * HZ, /* BE */
+ 10 * HZ /* BK */
+};
+
+static const struct ieee80211_ops cw1200_ops = {
+ .start = cw1200_start,
+ .stop = cw1200_stop,
+ .add_interface = cw1200_add_interface,
+ .remove_interface = cw1200_remove_interface,
+ .tx = cw1200_tx,
+ .hw_scan = cw1200_hw_scan,
+ .set_tim = cw1200_set_tim,
+ .sta_notify = cw1200_sta_notify,
+ .sta_add = cw1200_sta_add,
+ .sta_remove = cw1200_sta_remove,
+ .set_key = cw1200_set_key,
+ .set_rts_threshold = cw1200_set_rts_threshold,
+ .config = cw1200_config,
+ .bss_info_changed = cw1200_bss_info_changed,
+ .prepare_multicast = cw1200_prepare_multicast,
+ .configure_filter = cw1200_configure_filter,
+ .conf_tx = cw1200_conf_tx,
+ .get_stats = cw1200_get_stats,
+ .ampdu_action = cw1200_ampdu_action,
+ .flush = cw1200_flush,
+#ifdef CONFIG_PM
+ .suspend = cw1200_wow_suspend,
+ .resume = cw1200_wow_resume,
+#endif /* CONFIG_PM */
+ /* Intentionally not offloaded: */
+ /*.channel_switch = cw1200_channel_switch, */
+ /*.remain_on_channel = cw1200_remain_on_channel, */
+ /*.cancel_remain_on_channel = cw1200_cancel_remain_on_channel, */
+};
+
+struct ieee80211_hw *cw1200_init_common(size_t priv_data_len)
+{
+ int i;
+ struct ieee80211_hw *hw;
+ struct cw1200_common *priv;
+
+ hw = ieee80211_alloc_hw(priv_data_len, &cw1200_ops);
+ if (!hw)
+ return NULL;
+
+ priv = hw->priv;
+ priv->hw = hw;
+ priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+ priv->rates = cw1200_rates; /* TODO: fetch from FW */
+ priv->mcs_rates = cw1200_n_rates;
+ /* Enable block ACK for every TID but voice. */
+ priv->ba_tid_mask = 0x3F;
+
+ hw->flags = IEEE80211_HW_SIGNAL_DBM |
+ IEEE80211_HW_SUPPORTS_PS |
+ IEEE80211_HW_SUPPORTS_DYNAMIC_PS |
+ IEEE80211_HW_REPORTS_TX_ACK_STATUS |
+ IEEE80211_HW_SUPPORTS_UAPSD |
+ IEEE80211_HW_CONNECTION_MONITOR |
+ IEEE80211_HW_NEED_DTIM_PERIOD;
+ /* Aggregation is fully controlled by firmware.
+ * Do not need any support from the mac80211 stack */
+ /* IEEE80211_HW_AMPDU_AGGREGATION | */
+
+ hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_ADHOC) |
+ BIT(NL80211_IFTYPE_AP) |
+ BIT(NL80211_IFTYPE_MESH_POINT) |
+ BIT(NL80211_IFTYPE_P2P_CLIENT) |
+ BIT(NL80211_IFTYPE_P2P_GO);
+
+ /* Support only for limited wowlan functionalities */
+ hw->wiphy->wowlan.flags = WIPHY_WOWLAN_ANY |
+ WIPHY_WOWLAN_DISCONNECT;
+ hw->wiphy->wowlan.n_patterns = 0;
+
+ hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+
+#if defined(CONFIG_CW1200_DISABLE_BEACON_HINTS)
+ hw->wiphy->flags |= WIPHY_FLAG_DISABLE_BEACON_HINTS;
+#endif
+
+ hw->channel_change_time = 1000; /* TODO: find actual value */
+ /* priv->beacon_req_id = cpu_to_le32(0); */
+ hw->queues = 4;
+ priv->noise = -94;
+
+ hw->max_rates = 8;
+ hw->max_rate_tries = 15;
+ hw->extra_tx_headroom = WSM_TX_EXTRA_HEADROOM +
+ 8 /* TKIP IV */ +
+ 12 /* TKIP ICV and MIC */;
+
+ hw->sta_data_size = sizeof(struct cw1200_sta_priv);
+
+ hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &cw1200_band_2ghz;
+#ifdef CONFIG_CW1200_5GHZ_SUPPORT
+ hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &cw1200_band_5ghz;
+#endif /* CONFIG_CW1200_5GHZ_SUPPORT */
+
+ hw->wiphy->max_scan_ssids = 2;
+ hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
+
+ SET_IEEE80211_PERM_ADDR(hw, cw1200_mac_template);
+
+ if (hw->wiphy->perm_addr[3] == 0 &&
+ hw->wiphy->perm_addr[4] == 0 &&
+ hw->wiphy->perm_addr[5] == 0) {
+ get_random_bytes(&hw->wiphy->perm_addr[3], 3);
+ }
+
+ mutex_init(&priv->wsm_cmd_mux);
+ mutex_init(&priv->conf_mutex);
+ priv->workqueue = create_singlethread_workqueue("cw1200_wq");
+ sema_init(&priv->scan.lock, 1);
+ INIT_WORK(&priv->scan.work, cw1200_scan_work);
+ INIT_DELAYED_WORK(&priv->scan.probe_work, cw1200_probe_work);
+ INIT_DELAYED_WORK(&priv->scan.timeout, cw1200_scan_timeout);
+ INIT_WORK(&priv->join_work, cw1200_join_work);
+ INIT_DELAYED_WORK(&priv->join_timeout, cw1200_join_timeout);
+ INIT_WORK(&priv->unjoin_work, cw1200_unjoin_work);
+ INIT_WORK(&priv->offchannel_work, cw1200_offchannel_work);
+ INIT_WORK(&priv->wep_key_work, cw1200_wep_key_work);
+ INIT_WORK(&priv->tx_policy_upload_work, tx_policy_upload_work);
+ spin_lock_init(&priv->event_queue_lock);
+ INIT_LIST_HEAD(&priv->event_queue);
+ INIT_WORK(&priv->event_handler, cw1200_event_handler);
+ INIT_DELAYED_WORK(&priv->bss_loss_work, cw1200_bss_loss_work);
+ INIT_DELAYED_WORK(&priv->connection_loss_work,
+ cw1200_connection_loss_work);
+ spin_lock_init(&priv->bss_loss_lock);
+ INIT_WORK(&priv->tx_failure_work, cw1200_tx_failure_work);
+ spin_lock_init(&priv->ps_state_lock);
+ INIT_DELAYED_WORK(&priv->set_cts_work, cw1200_set_cts_work);
+ INIT_WORK(&priv->set_tim_work, cw1200_set_tim_work);
+ INIT_WORK(&priv->multicast_start_work, cw1200_multicast_start_work);
+ INIT_WORK(&priv->multicast_stop_work, cw1200_multicast_stop_work);
+ INIT_WORK(&priv->link_id_work, cw1200_link_id_work);
+ INIT_DELAYED_WORK(&priv->link_id_gc_work, cw1200_link_id_gc_work);
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+ INIT_WORK(&priv->linkid_reset_work, cw1200_link_id_reset);
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+ INIT_WORK(&priv->update_filtering_work, cw1200_update_filtering_work);
+ INIT_WORK(&priv->ba_work, cw1200_ba_work);
+ init_timer(&priv->mcast_timeout);
+ priv->mcast_timeout.data = (unsigned long)priv;
+ priv->mcast_timeout.function = cw1200_mcast_timeout;
+ spin_lock_init(&priv->ba_lock);
+ init_timer(&priv->ba_timer);
+ priv->ba_timer.data = (unsigned long)priv;
+ priv->ba_timer.function = cw1200_ba_timer;
+
+ if (unlikely(cw1200_queue_stats_init(&priv->tx_queue_stats,
+ CW1200_LINK_ID_MAX,
+ cw1200_skb_dtor,
+ priv))) {
+ ieee80211_free_hw(hw);
+ return NULL;
+ }
+
+ for (i = 0; i < 4; ++i) {
+ if (unlikely(cw1200_queue_init(&priv->tx_queue[i],
+ &priv->tx_queue_stats, i, 16,
+ cw1200_ttl[i]))) {
+ for (; i > 0; i--)
+ cw1200_queue_deinit(&priv->tx_queue[i - 1]);
+ cw1200_queue_stats_deinit(&priv->tx_queue_stats);
+ ieee80211_free_hw(hw);
+ return NULL;
+ }
+ }
+
+ init_waitqueue_head(&priv->channel_switch_done);
+ init_waitqueue_head(&priv->wsm_cmd_wq);
+ init_waitqueue_head(&priv->wsm_startup_done);
+ wsm_buf_init(&priv->wsm_cmd_buf);
+ spin_lock_init(&priv->wsm_cmd.lock);
+ tx_policy_init(priv);
+#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT)
+ priv->wsm_dump_max_size = 20;
+#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */
+
+ return hw;
+}
+EXPORT_SYMBOL_GPL(cw1200_init_common);
+
+int cw1200_register_common(struct ieee80211_hw *dev)
+{
+ struct cw1200_common *priv = dev->priv;
+ int err;
+
+ err = cw1200_pm_init(&priv->pm_state, priv);
+ if (err) {
+ cw1200_dbg(CW1200_DBG_ERROR, "Cannot init PM. (%d).\n",
+ err);
+ return err;
+ }
+
+ err = ieee80211_register_hw(dev);
+ if (err) {
+ cw1200_dbg(CW1200_DBG_ERROR, "Cannot register device (%d).\n",
+ err);
+ cw1200_pm_deinit(&priv->pm_state);
+ return err;
+ }
+
+#ifdef CONFIG_CW1200_LEDS
+ err = cw1200_init_leds(priv);
+ if (err) {
+ cw1200_pm_deinit(&priv->pm_state);
+ ieee80211_unregister_hw(dev);
+ return err;
+ }
+#endif /* CONFIG_CW1200_LEDS */
+
+ cw1200_debug_init(priv);
+
+ cw1200_dbg(CW1200_DBG_MSG, "is registered as '%s'\n",
+ wiphy_name(dev->wiphy));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cw1200_register_common);
+
+void cw1200_free_common(struct ieee80211_hw *dev)
+{
+ /* struct cw1200_common *priv = dev->priv; */
+ /* unsigned int i; */
+
+ ieee80211_free_hw(dev);
+}
+EXPORT_SYMBOL_GPL(cw1200_free_common);
+
+void cw1200_unregister_common(struct ieee80211_hw *dev)
+{
+ struct cw1200_common *priv = dev->priv;
+ int i;
+
+ ieee80211_unregister_hw(dev);
+
+ del_timer_sync(&priv->mcast_timeout);
+ del_timer_sync(&priv->ba_timer);
+
+ priv->sbus_ops->irq_unsubscribe(priv->sbus_priv);
+ cw1200_unregister_bh(priv);
+
+ cw1200_debug_release(priv);
+
+#ifdef CONFIG_CW1200_LEDS
+ cw1200_unregister_leds(priv);
+#endif /* CONFIG_CW1200_LEDS */
+
+ mutex_destroy(&priv->conf_mutex);
+
+ wsm_buf_deinit(&priv->wsm_cmd_buf);
+
+ destroy_workqueue(priv->workqueue);
+ priv->workqueue = NULL;
+
+ if (priv->skb_cache) {
+ dev_kfree_skb(priv->skb_cache);
+ priv->skb_cache = NULL;
+ }
+
+ if (priv->sdd) {
+ release_firmware(priv->sdd);
+ priv->sdd = NULL;
+ }
+
+ for (i = 0; i < 4; ++i)
+ cw1200_queue_deinit(&priv->tx_queue[i]);
+ cw1200_queue_stats_deinit(&priv->tx_queue_stats);
+ cw1200_pm_deinit(&priv->pm_state);
+}
+EXPORT_SYMBOL_GPL(cw1200_unregister_common);
+
+int cw1200_core_probe(const struct sbus_ops *sbus_ops,
+ struct sbus_priv *sbus,
+ struct device *pdev,
+ struct cw1200_common **pself)
+{
+ int err = -ENOMEM;
+ struct ieee80211_hw *dev;
+ struct cw1200_common *priv;
+ struct wsm_operational_mode mode = {
+ .power_mode = wsm_power_mode_quiescent,
+ .disableMoreFlagUsage = true,
+ };
+
+ dev = cw1200_init_common(sizeof(struct cw1200_common));
+ if (!dev)
+ goto err;
+
+ priv = dev->priv;
+
+ priv->sbus_ops = sbus_ops;
+ priv->sbus_priv = sbus;
+ priv->pdev = pdev;
+ SET_IEEE80211_DEV(priv->hw, pdev);
+
+ /* WSM callbacks. */
+ priv->wsm_cbc.scan_complete = cw1200_scan_complete_cb;
+ priv->wsm_cbc.tx_confirm = cw1200_tx_confirm_cb;
+ priv->wsm_cbc.rx = cw1200_rx_cb;
+ priv->wsm_cbc.suspend_resume = cw1200_suspend_resume;
+ /* priv->wsm_cbc.set_pm_complete = cw1200_set_pm_complete_cb; */
+ priv->wsm_cbc.channel_switch = cw1200_channel_switch_cb;
+
+ err = cw1200_register_bh(priv);
+ if (err)
+ goto err1;
+
+ err = cw1200_load_firmware(priv);
+ if (err)
+ goto err2;
+ priv->sbus_ops->lock(priv->sbus_priv);
+ WARN_ON(priv->sbus_ops->set_block_size(priv->sbus_priv,
+ SDIO_BLOCK_SIZE));
+ priv->sbus_ops->unlock(priv->sbus_priv);
+
+ if (wait_event_interruptible_timeout(priv->wsm_startup_done,
+ priv->wsm_caps.firmwareReady, 3*HZ) <= 0) {
+ /* TODO: Needs to find how to reset device */
+ /* in QUEUE mode properly. */
+ goto err3;
+ }
+
+ /* Set low-power mode. */
+ WARN_ON(wsm_set_operational_mode(priv, &mode));
+
+ /* Enable multi-TX confirmation */
+ WARN_ON(wsm_use_multi_tx_conf(priv, true));
+
+ err = cw1200_register_common(dev);
+ if (err) {
+ priv->sbus_ops->irq_unsubscribe(priv->sbus_priv);
+ goto err3;
+ }
+
+ *pself = dev->priv;
+ return err;
+
+err3:
+ sbus_ops->reset(sbus);
+err2:
+ cw1200_unregister_bh(priv);
+err1:
+ cw1200_free_common(dev);
+err:
+ return err;
+}
+EXPORT_SYMBOL_GPL(cw1200_core_probe);
+
+void cw1200_core_release(struct cw1200_common *self)
+{
+ cw1200_unregister_common(self->hw);
+ cw1200_free_common(self->hw);
+ return;
+}
+EXPORT_SYMBOL_GPL(cw1200_core_release);
--
1.7.9


2012-03-02 20:26:58

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 09/21] cw1200: txrx.*, implementation of datapath.

The patch implements datapath for the cw1200 device driver.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/txrx.c | 1366 ++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/txrx.h | 95 +++
2 files changed, 1461 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/txrx.c
create mode 100644 drivers/net/wireless/cw1200/txrx.h

diff --git a/drivers/net/wireless/cw1200/txrx.c b/drivers/net/wireless/cw1200/txrx.c
new file mode 100644
index 0000000..3dbe33e
--- /dev/null
+++ b/drivers/net/wireless/cw1200/txrx.c
@@ -0,0 +1,1366 @@
+/*
+ * Datapath implementation for ST-Ericsson CW1200 mac80211 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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 <net/mac80211.h>
+#include <linux/etherdevice.h>
+
+#include "cw1200.h"
+#include "wsm.h"
+#include "bh.h"
+#include "ap.h"
+#include "debug.h"
+
+#if defined(CONFIG_CW1200_TX_POLICY_DEBUG)
+#define tx_policy_printk(...) printk(__VA_ARGS__)
+#else
+#define tx_policy_printk(...)
+#endif
+
+#define CW1200_INVALID_RATE_ID (0xFF)
+
+static int cw1200_handle_action_rx(struct cw1200_common *priv,
+ struct sk_buff *skb);
+static const struct ieee80211_rate *
+cw1200_get_tx_rate(const struct cw1200_common *priv,
+ const struct ieee80211_tx_rate *rate);
+
+/* ******************************************************************** */
+/* TX queue lock / unlock */
+
+static inline void cw1200_tx_queues_lock(struct cw1200_common *priv)
+{
+ int i;
+ for (i = 0; i < 4; ++i)
+ cw1200_queue_lock(&priv->tx_queue[i]);
+}
+
+static inline void cw1200_tx_queues_unlock(struct cw1200_common *priv)
+{
+ int i;
+ for (i = 0; i < 4; ++i)
+ cw1200_queue_unlock(&priv->tx_queue[i]);
+}
+
+/* ******************************************************************** */
+/* TX policy cache implementation */
+
+static void tx_policy_dump(struct tx_policy *policy)
+{
+ tx_policy_printk(KERN_DEBUG "[TX policy] "
+ "%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X"
+ "%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X"
+ "%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X: %d\n",
+ policy->raw[0] & 0x0F, policy->raw[0] >> 4,
+ policy->raw[1] & 0x0F, policy->raw[1] >> 4,
+ policy->raw[2] & 0x0F, policy->raw[2] >> 4,
+ policy->raw[3] & 0x0F, policy->raw[3] >> 4,
+ policy->raw[4] & 0x0F, policy->raw[4] >> 4,
+ policy->raw[5] & 0x0F, policy->raw[5] >> 4,
+ policy->raw[6] & 0x0F, policy->raw[6] >> 4,
+ policy->raw[7] & 0x0F, policy->raw[7] >> 4,
+ policy->raw[8] & 0x0F, policy->raw[8] >> 4,
+ policy->raw[9] & 0x0F, policy->raw[9] >> 4,
+ policy->raw[10] & 0x0F, policy->raw[10] >> 4,
+ policy->raw[11] & 0x0F, policy->raw[11] >> 4,
+ policy->defined);
+}
+
+static void tx_policy_build(const struct cw1200_common *priv,
+ /* [out] */ struct tx_policy *policy,
+ struct ieee80211_tx_rate *rates, size_t count)
+{
+ int i, j;
+ unsigned limit = priv->short_frame_max_tx_count;
+ unsigned total = 0;
+ BUG_ON(rates[0].idx < 0);
+ memset(policy, 0, sizeof(*policy));
+
+ /* minstrel is buggy a little bit, so distille
+ * incoming rates first. */
+
+ /* Sort rates in descending order. */
+ for (i = 1; i < count; ++i) {
+ if (rates[i].idx < 0) {
+ count = i;
+ break;
+ }
+ if (rates[i].idx > rates[i - 1].idx) {
+ struct ieee80211_tx_rate tmp = rates[i - 1];
+ rates[i - 1] = rates[i];
+ rates[i] = tmp;
+ }
+ }
+
+ /* Eliminate duplicates. */
+ total = rates[0].count;
+ for (i = 0, j = 1; j < count; ++j) {
+ if (rates[j].idx == rates[i].idx) {
+ rates[i].count += rates[j].count;
+ } else if (rates[j].idx > rates[i].idx) {
+ break;
+ } else {
+ ++i;
+ if (i != j)
+ rates[i] = rates[j];
+ }
+ total += rates[j].count;
+ }
+ count = i + 1;
+
+ /* Re-fill policy trying to keep every requested rate and with
+ * respect to the global max tx retransmission count. */
+ if (limit < count)
+ limit = count;
+ if (total > limit) {
+ for (i = 0; i < count; ++i) {
+ int left = count - i - 1;
+ if (rates[i].count > limit - left)
+ rates[i].count = limit - left;
+ limit -= rates[i].count;
+ }
+ }
+
+ /* HACK!!! Device has problems (at least) switching from
+ * 54Mbps CTS to 1Mbps. This switch takes enormous amount
+ * of time (100-200 ms), leading to valuable throughput drop.
+ * As a workaround, additional g-rates are injected to the
+ * policy.
+ */
+ if (count == 2 && !(rates[0].flags & IEEE80211_TX_RC_MCS) &&
+ rates[0].idx > 4 && rates[0].count > 2 &&
+ rates[1].idx < 2) {
+ /* ">> 1" is an equivalent of "/ 2", but faster */
+ int mid_rate = (rates[0].idx + 4) >> 1;
+
+ /* Decrease number of retries for the initial rate */
+ rates[0].count -= 2;
+
+ if (mid_rate != 4) {
+ /* Keep fallback rate at 1Mbps. */
+ rates[3] = rates[1];
+
+ /* Inject 1 transmission on lowest g-rate */
+ rates[2].idx = 4;
+ rates[2].count = 1;
+ rates[2].flags = rates[1].flags;
+
+ /* Inject 1 transmission on mid-rate */
+ rates[1].idx = mid_rate;
+ rates[1].count = 1;
+
+ /* Fallback to 1 Mbps is a really bad thing,
+ * so let's try to increase probability of
+ * successful transmission on the lowest g rate
+ * even more */
+ if (rates[0].count >= 3) {
+ --rates[0].count;
+ ++rates[2].count;
+ }
+
+ /* Adjust amount of rates defined */
+ count += 2;
+ } else {
+ /* Keep fallback rate at 1Mbps. */
+ rates[2] = rates[1];
+
+ /* Inject 2 transmissions on lowest g-rate */
+ rates[1].idx = 4;
+ rates[1].count = 2;
+
+ /* Adjust amount of rates defined */
+ count += 1;
+ }
+ }
+
+ policy->defined = cw1200_get_tx_rate(priv, &rates[0])->hw_value + 1;
+
+ for (i = 0; i < count; ++i) {
+ register unsigned rateid, off, shift, retries;
+
+ rateid = cw1200_get_tx_rate(priv, &rates[i])->hw_value;
+ off = rateid >> 3; /* eq. rateid / 8 */
+ shift = (rateid & 0x07) << 2; /* eq. (rateid % 8) * 4 */
+
+ retries = rates[i].count;
+ if (unlikely(retries > 0x0F))
+ rates[i].count = retries = 0x0F;
+ policy->tbl[off] |= __cpu_to_le32(retries << shift);
+ policy->retry_count += retries;
+ }
+
+ tx_policy_printk(KERN_DEBUG "[TX policy] Policy (%ld): " \
+ "%d:%d, %d:%d, %d:%d, %d:%d, %d:%d\n",
+ count,
+ rates[0].idx, rates[0].count,
+ rates[1].idx, rates[1].count,
+ rates[2].idx, rates[2].count,
+ rates[3].idx, rates[3].count,
+ rates[4].idx, rates[4].count);
+}
+
+static inline bool tx_policy_is_equal(const struct tx_policy *wanted,
+ const struct tx_policy *cached)
+{
+ size_t count = wanted->defined >> 1;
+ if (wanted->defined > cached->defined)
+ return false;
+ if (count) {
+ if (memcmp(wanted->raw, cached->raw, count))
+ return false;
+ }
+ if (wanted->defined & 1) {
+ if ((wanted->raw[count] & 0x0F) != (cached->raw[count] & 0x0F))
+ return false;
+ }
+ return true;
+}
+
+static int tx_policy_find(struct tx_policy_cache *cache,
+ const struct tx_policy *wanted)
+{
+ /* O(n) complexity. Not so good, but there's only 8 entries in
+ * the cache.
+ * Also lru helps to reduce search time. */
+ struct tx_policy_cache_entry *it;
+ /* First search for policy in "used" list */
+ list_for_each_entry(it, &cache->used, link) {
+ if (tx_policy_is_equal(wanted, &it->policy))
+ return it - cache->cache;
+ }
+ /* Then - in "free list" */
+ list_for_each_entry(it, &cache->free, link) {
+ if (tx_policy_is_equal(wanted, &it->policy))
+ return it - cache->cache;
+ }
+ return -1;
+}
+
+static inline void tx_policy_use(struct tx_policy_cache *cache,
+ struct tx_policy_cache_entry *entry)
+{
+ ++entry->policy.usage_count;
+ list_move(&entry->link, &cache->used);
+}
+
+static inline int tx_policy_release(struct tx_policy_cache *cache,
+ struct tx_policy_cache_entry *entry)
+{
+ int ret = --entry->policy.usage_count;
+ if (!ret)
+ list_move(&entry->link, &cache->free);
+ return ret;
+}
+
+/* ******************************************************************** */
+/* External TX policy cache API */
+
+void tx_policy_init(struct cw1200_common *priv)
+{
+ struct tx_policy_cache *cache = &priv->tx_policy_cache;
+ int i;
+
+ memset(cache, 0, sizeof(*cache));
+
+ spin_lock_init(&cache->lock);
+ INIT_LIST_HEAD(&cache->used);
+ INIT_LIST_HEAD(&cache->free);
+
+ for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i)
+ list_add(&cache->cache[i].link, &cache->free);
+}
+
+static int tx_policy_get(struct cw1200_common *priv,
+ struct ieee80211_tx_rate *rates,
+ size_t count, bool *renew)
+{
+ int idx;
+ struct tx_policy_cache *cache = &priv->tx_policy_cache;
+ struct tx_policy wanted;
+
+ tx_policy_build(priv, &wanted, rates, count);
+
+ spin_lock_bh(&cache->lock);
+ if (WARN_ON_ONCE(list_empty(&cache->free))) {
+ spin_unlock_bh(&cache->lock);
+ return CW1200_INVALID_RATE_ID;
+ }
+ idx = tx_policy_find(cache, &wanted);
+ if (idx >= 0) {
+ tx_policy_printk(KERN_DEBUG "[TX policy] Used TX policy: %d\n",
+ idx);
+ *renew = false;
+ } else {
+ struct tx_policy_cache_entry *entry;
+ *renew = true;
+ /* If policy is not found create a new one
+ * using the oldest entry in "free" list */
+ entry = list_entry(cache->free.prev,
+ struct tx_policy_cache_entry, link);
+ entry->policy = wanted;
+ idx = entry - cache->cache;
+ tx_policy_printk(KERN_DEBUG "[TX policy] New TX policy: %d\n",
+ idx);
+ tx_policy_dump(&entry->policy);
+ }
+ tx_policy_use(cache, &cache->cache[idx]);
+ if (unlikely(list_empty(&cache->free))) {
+ /* Lock TX queues. */
+ cw1200_tx_queues_lock(priv);
+ }
+ spin_unlock_bh(&cache->lock);
+ return idx;
+}
+
+static void tx_policy_put(struct cw1200_common *priv, int idx)
+{
+ int usage, locked;
+ struct tx_policy_cache *cache = &priv->tx_policy_cache;
+
+ spin_lock_bh(&cache->lock);
+ locked = list_empty(&cache->free);
+ usage = tx_policy_release(cache, &cache->cache[idx]);
+ if (unlikely(locked) && !usage) {
+ /* Unlock TX queues. */
+ cw1200_tx_queues_unlock(priv);
+ }
+ spin_unlock_bh(&cache->lock);
+}
+
+/*
+bool tx_policy_cache_full(struct cw1200_common *priv)
+{
+ bool ret;
+ struct tx_policy_cache *cache = &priv->tx_policy_cache;
+ spin_lock_bh(&cache->lock);
+ ret = list_empty(&cache->free);
+ spin_unlock_bh(&cache->lock);
+ return ret;
+}
+*/
+
+static int tx_policy_upload(struct cw1200_common *priv)
+{
+ struct tx_policy_cache *cache = &priv->tx_policy_cache;
+ int i;
+ struct wsm_set_tx_rate_retry_policy arg = {
+ .hdr = {
+ .numTxRatePolicies = 0,
+ }
+ };
+ spin_lock_bh(&cache->lock);
+
+ /* Upload only modified entries. */
+ for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i) {
+ struct tx_policy *src = &cache->cache[i].policy;
+ if (src->retry_count && !src->uploaded) {
+ struct wsm_set_tx_rate_retry_policy_policy *dst =
+ &arg.tbl[arg.hdr.numTxRatePolicies];
+ dst->policyIndex = i;
+ dst->shortRetryCount = priv->short_frame_max_tx_count;
+ dst->longRetryCount = priv->long_frame_max_tx_count;
+
+ /* BIT(2) - Terminate retries when Tx rate retry policy
+ * finishes.
+ * BIT(3) - Count initial frame transmission as part of
+ * rate retry counting but not as a retry
+ * attempt */
+ dst->policyFlags = BIT(2) | BIT(3);
+
+ memcpy(dst->rateCountIndices, src->tbl,
+ sizeof(dst->rateCountIndices));
+ src->uploaded = 1;
+ ++arg.hdr.numTxRatePolicies;
+ }
+ }
+ spin_unlock_bh(&cache->lock);
+ cw1200_debug_tx_cache_miss(priv);
+ tx_policy_printk(KERN_DEBUG "[TX policy] Upload %d policies\n",
+ arg.hdr.numTxRatePolicies);
+ return wsm_set_tx_rate_retry_policy(priv, &arg);
+}
+
+void tx_policy_upload_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, tx_policy_upload_work);
+
+ tx_policy_printk(KERN_DEBUG "[TX] TX policy upload.\n");
+ WARN_ON(tx_policy_upload(priv));
+
+ wsm_unlock_tx(priv);
+ cw1200_tx_queues_unlock(priv);
+}
+
+/* ******************************************************************** */
+/* cw1200 TX implementation */
+
+struct cw1200_txinfo {
+ struct sk_buff *skb;
+ unsigned queue;
+ struct ieee80211_tx_info *tx_info;
+ const struct ieee80211_rate *rate;
+ struct ieee80211_hdr *hdr;
+ size_t hdrlen;
+ const u8 *da;
+ struct cw1200_sta_priv *sta_priv;
+ struct cw1200_txpriv txpriv;
+};
+
+u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv, u32 rates)
+{
+ u32 ret = 0;
+ int i;
+ for (i = 0; i < 32; ++i) {
+ if (rates & BIT(i))
+ ret |= BIT(priv->rates[i].hw_value);
+ }
+ return ret;
+}
+
+static const struct ieee80211_rate *
+cw1200_get_tx_rate(const struct cw1200_common *priv,
+ const struct ieee80211_tx_rate *rate)
+{
+ if (rate->idx < 0)
+ return NULL;
+ if (rate->flags & IEEE80211_TX_RC_MCS)
+ return &priv->mcs_rates[rate->idx];
+ return &priv->hw->wiphy->bands[priv->channel->band]->
+ bitrates[rate->idx];
+}
+
+static int
+cw1200_tx_h_calc_link_ids(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+
+ if (likely(t->tx_info->control.sta && t->sta_priv->link_id))
+ t->txpriv.raw_link_id =
+ t->txpriv.link_id =
+ t->sta_priv->link_id;
+ else if (priv->mode != NL80211_IFTYPE_AP)
+ t->txpriv.raw_link_id =
+ t->txpriv.link_id = 0;
+ else if (is_multicast_ether_addr(t->da)) {
+ if (priv->enable_beacon) {
+ t->txpriv.raw_link_id = 0;
+ t->txpriv.link_id = CW1200_LINK_ID_AFTER_DTIM;
+ } else {
+ t->txpriv.raw_link_id = 0;
+ t->txpriv.link_id = 0;
+ }
+ } else {
+ t->txpriv.link_id = cw1200_find_link_id(priv, t->da);
+ if (!t->txpriv.link_id)
+ t->txpriv.link_id = cw1200_alloc_link_id(priv, t->da);
+ if (!t->txpriv.link_id) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: No more link IDs available.\n",
+ __func__);
+ return -ENOENT;
+ }
+ t->txpriv.raw_link_id = t->txpriv.link_id;
+ }
+ if (t->txpriv.raw_link_id)
+ priv->link_id_db[t->txpriv.raw_link_id - 1].timestamp =
+ jiffies;
+
+ if (t->tx_info->control.sta &&
+ (t->tx_info->control.sta->uapsd_queues & BIT(t->queue)))
+ t->txpriv.link_id = CW1200_LINK_ID_UAPSD;
+ return 0;
+}
+
+static void
+cw1200_tx_h_pm(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+ if (unlikely(ieee80211_is_auth(t->hdr->frame_control))) {
+ u32 mask = ~BIT(t->txpriv.raw_link_id);
+ spin_lock_bh(&priv->ps_state_lock);
+ priv->sta_asleep_mask &= mask;
+ priv->pspoll_mask &= mask;
+ spin_unlock_bh(&priv->ps_state_lock);
+ }
+}
+
+static void
+cw1200_tx_h_calc_tid(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+ if (ieee80211_is_data_qos(t->hdr->frame_control)) {
+ u8 *qos = ieee80211_get_qos_ctl(t->hdr);
+ t->txpriv.tid = qos[0] & IEEE80211_QOS_CTL_TID_MASK;
+ } else if (ieee80211_is_data(t->hdr->frame_control)) {
+ t->txpriv.tid = 0;
+ }
+}
+
+/* IV/ICV injection. */
+/* TODO: Quite unoptimal. It's better co modify mac80211
+ * to reserve space for IV */
+static int
+cw1200_tx_h_crypt(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+ size_t iv_len;
+ size_t icv_len;
+ u8 *icv;
+ u8 *newhdr;
+
+ if (!t->tx_info->control.hw_key ||
+ !(t->hdr->frame_control &
+ __cpu_to_le32(IEEE80211_FCTL_PROTECTED)))
+ return 0;
+
+ iv_len = t->tx_info->control.hw_key->iv_len;
+ icv_len = t->tx_info->control.hw_key->icv_len;
+
+ if (t->tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP)
+ icv_len += 8; /* MIC */
+
+ if ((skb_headroom(t->skb) + skb_tailroom(t->skb) <
+ iv_len + icv_len + WSM_TX_EXTRA_HEADROOM) ||
+ (skb_headroom(t->skb) <
+ iv_len + WSM_TX_EXTRA_HEADROOM)) {
+ wiphy_err(priv->hw->wiphy,
+ "Bug: no space allocated for crypto headers.\n"
+ "headroom: %d, tailroom: %d, "
+ "req_headroom: %ld, req_tailroom: %ld\n"
+ "Please fix it in cw1200_get_skb().\n",
+ skb_headroom(t->skb), skb_tailroom(t->skb),
+ iv_len + WSM_TX_EXTRA_HEADROOM, icv_len);
+ return -ENOMEM;
+ } else if (skb_tailroom(t->skb) < icv_len) {
+ size_t offset = icv_len - skb_tailroom(t->skb);
+ u8 *p;
+ wiphy_warn(priv->hw->wiphy,
+ "Slowpath: tailroom is not big enough. "
+ "Req: %ld, got: %d.\n",
+ icv_len, skb_tailroom(t->skb));
+
+ p = skb_push(t->skb, offset);
+ memmove(p, &p[offset], t->skb->len - offset);
+ skb_trim(t->skb, t->skb->len - offset);
+ }
+
+ newhdr = skb_push(t->skb, iv_len);
+ memmove(newhdr, newhdr + iv_len, t->hdrlen);
+ t->hdr = (struct ieee80211_hdr *) newhdr;
+ t->hdrlen += iv_len;
+ icv = skb_put(t->skb, icv_len);
+
+ return 0;
+}
+
+static int
+cw1200_tx_h_align(struct cw1200_common *priv,
+ struct cw1200_txinfo *t,
+ u8 *flags)
+{
+ size_t offset = (size_t)t->skb->data & 3;
+
+ if (!offset)
+ return 0;
+
+ if (offset & 1) {
+ wiphy_err(priv->hw->wiphy,
+ "Bug: attempt to transmit a frame "
+ "with wrong alignment: %ld\n",
+ offset);
+ return -EINVAL;
+ }
+
+ if (skb_headroom(t->skb) < offset) {
+ wiphy_err(priv->hw->wiphy,
+ "Bug: no space allocated "
+ "for DMA alignment.\n"
+ "headroom: %d\n",
+ skb_headroom(t->skb));
+ return -ENOMEM;
+ }
+ skb_push(t->skb, offset);
+ t->hdrlen += offset;
+ t->txpriv.offset += offset;
+ *flags |= WSM_TX_2BYTES_SHIFT;
+ cw1200_debug_tx_align(priv);
+ return 0;
+}
+
+static int
+cw1200_tx_h_action(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+ struct ieee80211_mgmt *mgmt =
+ (struct ieee80211_mgmt *)t->hdr;
+ if (ieee80211_is_action(t->hdr->frame_control) &&
+ mgmt->u.action.category == WLAN_CATEGORY_BACK)
+ return 1;
+ else
+ return 0;
+}
+
+/* Add WSM header */
+static struct wsm_tx *
+cw1200_tx_h_wsm(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+ struct wsm_tx *wsm;
+
+ if (skb_headroom(t->skb) < sizeof(struct wsm_tx)) {
+ wiphy_err(priv->hw->wiphy,
+ "Bug: no space allocated "
+ "for WSM header.\n"
+ "headroom: %d\n",
+ skb_headroom(t->skb));
+ return NULL;
+ }
+
+ wsm = (struct wsm_tx *)skb_push(t->skb, sizeof(struct wsm_tx));
+ t->txpriv.offset += sizeof(struct wsm_tx);
+ memset(wsm, 0, sizeof(*wsm));
+ wsm->hdr.len = __cpu_to_le16(t->skb->len);
+ wsm->hdr.id = __cpu_to_le16(0x0004);
+ wsm->queueId = wsm_queue_id_to_wsm(t->queue);
+ return wsm;
+}
+
+/* BT Coex specific handling */
+static void
+cw1200_tx_h_bt(struct cw1200_common *priv,
+ struct cw1200_txinfo *t,
+ struct wsm_tx *wsm)
+{
+ u8 priority = 0;
+
+ if (!priv->is_BT_Present)
+ return;
+
+ if (unlikely(ieee80211_is_nullfunc(t->hdr->frame_control)))
+ priority = WSM_EPTA_PRIORITY_MGT;
+ else if (ieee80211_is_data(t->hdr->frame_control)) {
+ /* Skip LLC SNAP header (+6) */
+ u8 *payload = &t->skb->data[t->hdrlen];
+ u16 *ethertype = (u16 *) &payload[6];
+ if (unlikely(*ethertype == __be16_to_cpu(ETH_P_PAE)))
+ priority = WSM_EPTA_PRIORITY_EAPOL;
+ } else if (unlikely(ieee80211_is_assoc_req(t->hdr->frame_control) ||
+ ieee80211_is_reassoc_req(t->hdr->frame_control))) {
+ struct ieee80211_mgmt *mgt_frame =
+ (struct ieee80211_mgmt *)t->hdr;
+
+ if (mgt_frame->u.assoc_req.listen_interval <
+ priv->listen_interval) {
+ txrx_printk(KERN_DEBUG
+ "Modified Listen Interval to %d from %d\n",
+ priv->listen_interval,
+ mgt_frame->u.assoc_req.listen_interval);
+ /* Replace listen interval derieved from
+ * the one read from SDD */
+ mgt_frame->u.assoc_req.listen_interval =
+ priv->listen_interval;
+ }
+ }
+
+ if (likely(!priority)) {
+ if (ieee80211_is_action(t->hdr->frame_control))
+ priority = WSM_EPTA_PRIORITY_ACTION;
+ else if (ieee80211_is_mgmt(t->hdr->frame_control))
+ priority = WSM_EPTA_PRIORITY_MGT;
+ else if ((wsm->queueId == WSM_QUEUE_VOICE))
+ priority = WSM_EPTA_PRIORITY_VOICE;
+ else if ((wsm->queueId == WSM_QUEUE_VIDEO))
+ priority = WSM_EPTA_PRIORITY_VIDEO;
+ else
+ priority = WSM_EPTA_PRIORITY_DATA;
+ }
+
+ txrx_printk(KERN_DEBUG "[TX] EPTA priority %d.\n",
+ priority);
+
+ wsm->flags |= priority << 1;
+}
+
+static int
+cw1200_tx_h_rate_policy(struct cw1200_common *priv,
+ struct cw1200_txinfo *t,
+ struct wsm_tx *wsm)
+{
+ bool tx_policy_renew = false;
+
+ t->txpriv.rate_id = tx_policy_get(priv,
+ t->tx_info->control.rates, IEEE80211_TX_MAX_RATES,
+ &tx_policy_renew);
+ if (t->txpriv.rate_id == CW1200_INVALID_RATE_ID)
+ return -EFAULT;
+
+ wsm->flags |= t->txpriv.rate_id << 4;
+
+ t->rate = cw1200_get_tx_rate(priv,
+ &t->tx_info->control.rates[0]),
+ wsm->maxTxRate = t->rate->hw_value;
+ if (t->rate->flags & IEEE80211_TX_RC_MCS) {
+ if (cw1200_ht_greenfield(&priv->ht_info))
+ wsm->htTxParameters |=
+ __cpu_to_le32(WSM_HT_TX_GREENFIELD);
+ else
+ wsm->htTxParameters |=
+ __cpu_to_le32(WSM_HT_TX_MIXED);
+ }
+
+ if (tx_policy_renew) {
+ tx_policy_printk(KERN_DEBUG "[TX] TX policy renew.\n");
+ /* It's not so optimal to stop TX queues every now and then.
+ * Maybe it's better to reimplement task scheduling with
+ * a counter. */
+ /* cw1200_tx_queues_lock(priv); */
+ /* Definetly better. TODO. */
+ wsm_lock_tx_async(priv);
+ cw1200_tx_queues_lock(priv);
+ if (queue_work(priv->workqueue,
+ &priv->tx_policy_upload_work) <= 0) {
+ cw1200_tx_queues_unlock(priv);
+ wsm_unlock_tx(priv);
+ }
+ }
+ return 0;
+}
+
+static bool
+cw1200_tx_h_pm_state(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+ int was_buffered = 1;
+
+ if (t->txpriv.link_id == CW1200_LINK_ID_AFTER_DTIM &&
+ !priv->buffered_multicasts) {
+ priv->buffered_multicasts = true;
+ if (priv->sta_asleep_mask)
+ queue_work(priv->workqueue,
+ &priv->multicast_start_work);
+ }
+
+ if (t->txpriv.raw_link_id && t->txpriv.tid < CW1200_MAX_TID)
+ was_buffered = priv->link_id_db[t->txpriv.raw_link_id - 1]
+ .buffered[t->txpriv.tid]++;
+
+ return !was_buffered;
+}
+
+static void
+cw1200_tx_h_ba_stat(struct cw1200_common *priv,
+ struct cw1200_txinfo *t)
+{
+ if (priv->join_status != CW1200_JOIN_STATUS_STA)
+ return;
+ if (!cw1200_is_ht(&priv->ht_info))
+ return;
+ if (!priv->setbssparams_done)
+ return;
+ if (!ieee80211_is_data(t->hdr->frame_control))
+ return;
+
+ spin_lock_bh(&priv->ba_lock);
+ priv->ba_acc += t->skb->len - t->hdrlen;
+ if (!priv->ba_cnt++) {
+ mod_timer(&priv->ba_timer,
+ jiffies + CW1200_BLOCK_ACK_INTERVAL);
+ }
+ spin_unlock_bh(&priv->ba_lock);
+}
+
+/* ******************************************************************** */
+
+void cw1200_tx(struct ieee80211_hw *dev, struct sk_buff *skb)
+{
+ struct cw1200_common *priv = dev->priv;
+ struct cw1200_txinfo t = {
+ .skb = skb,
+ .queue = skb_get_queue_mapping(skb),
+ .tx_info = IEEE80211_SKB_CB(skb),
+ .hdr = (struct ieee80211_hdr *)skb->data,
+ .txpriv.tid = CW1200_MAX_TID,
+ .txpriv.rate_id = CW1200_INVALID_RATE_ID,
+ };
+ struct ieee80211_sta *sta;
+ struct wsm_tx *wsm;
+ bool tid_update = 0;
+ u8 flags = 0;
+ int ret;
+
+ t.hdrlen = ieee80211_hdrlen(t.hdr->frame_control);
+ t.da = ieee80211_get_DA(t.hdr);
+ t.sta_priv =
+ (struct cw1200_sta_priv *)&t.tx_info->control.sta->drv_priv;
+
+ if (WARN_ON(t.queue >= 4))
+ goto drop;
+
+ ret = cw1200_tx_h_calc_link_ids(priv, &t);
+ if (ret)
+ goto drop;
+
+ txrx_printk(KERN_DEBUG "[TX] TX %d bytes "
+ "(queue: %d, link_id: %d (%d)).\n",
+ skb->len, t.queue, t.txpriv.link_id,
+ t.txpriv.raw_link_id);
+
+ cw1200_tx_h_pm(priv, &t);
+ cw1200_tx_h_calc_tid(priv, &t);
+ ret = cw1200_tx_h_crypt(priv, &t);
+ if (ret)
+ goto drop;
+ ret = cw1200_tx_h_align(priv, &t, &flags);
+ if (ret)
+ goto drop;
+ ret = cw1200_tx_h_action(priv, &t);
+ if (ret)
+ goto drop;
+ wsm = cw1200_tx_h_wsm(priv, &t);
+ if (!wsm) {
+ ret = -ENOMEM;
+ goto drop;
+ }
+ wsm->flags |= flags;
+ cw1200_tx_h_bt(priv, &t, wsm);
+ ret = cw1200_tx_h_rate_policy(priv, &t, wsm);
+ if (ret)
+ goto drop;
+
+ rcu_read_lock();
+ sta = rcu_dereference(t.tx_info->control.sta);
+
+ cw1200_tx_h_ba_stat(priv, &t);
+ spin_lock_bh(&priv->ps_state_lock);
+ {
+ tid_update = cw1200_tx_h_pm_state(priv, &t);
+ BUG_ON(cw1200_queue_put(&priv->tx_queue[t.queue],
+ t.skb, &t.txpriv));
+ }
+ spin_unlock_bh(&priv->ps_state_lock);
+
+ if (tid_update && sta)
+ ieee80211_sta_set_buffered(sta,
+ t.txpriv.tid, true);
+
+ rcu_read_unlock();
+
+ cw1200_bh_wakeup(priv);
+
+ return;
+
+drop:
+ cw1200_skb_dtor(priv, skb, &t.txpriv);
+ return;
+}
+
+/* ******************************************************************** */
+
+static int cw1200_handle_action_rx(struct cw1200_common *priv,
+ struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (void *)skb->data;
+
+ /* Filter block ACK negotiation: fully controlled by firmware */
+ if (mgmt->u.action.category == WLAN_CATEGORY_BACK)
+ return 1;
+
+ return 0;
+}
+
+static int cw1200_handle_pspoll(struct cw1200_common *priv,
+ struct sk_buff *skb)
+{
+ struct ieee80211_sta *sta;
+ struct ieee80211_pspoll *pspoll =
+ (struct ieee80211_pspoll *) skb->data;
+ int link_id = 0;
+ u32 pspoll_mask = 0;
+ int drop = 1;
+ int i;
+
+ if (priv->join_status != CW1200_JOIN_STATUS_AP)
+ goto done;
+ if (memcmp(priv->vif->addr, pspoll->bssid, ETH_ALEN))
+ goto done;
+
+ rcu_read_lock();
+ sta = ieee80211_find_sta(priv->vif, pspoll->ta);
+ if (sta) {
+ struct cw1200_sta_priv *sta_priv;
+ sta_priv = (struct cw1200_sta_priv *)&sta->drv_priv;
+ link_id = sta_priv->link_id;
+ pspoll_mask = BIT(sta_priv->link_id);
+ }
+ rcu_read_unlock();
+ if (!link_id)
+ goto done;
+
+ priv->pspoll_mask |= pspoll_mask;
+ drop = 0;
+
+ /* Do not report pspols if data for given link id is
+ * queued already. */
+ for (i = 0; i < 4; ++i) {
+ if (cw1200_queue_get_num_queued(
+ &priv->tx_queue[i],
+ pspoll_mask)) {
+ cw1200_bh_wakeup(priv);
+ drop = 1;
+ break;
+ }
+ }
+ txrx_printk(KERN_DEBUG "[RX] PSPOLL: %s\n", drop ? "local" : "fwd");
+done:
+ return drop;
+}
+
+/* ******************************************************************** */
+
+void cw1200_tx_confirm_cb(struct cw1200_common *priv,
+ struct wsm_tx_confirm *arg)
+{
+ u8 queue_id = cw1200_queue_get_queue_id(arg->packetID);
+ struct cw1200_queue *queue = &priv->tx_queue[queue_id];
+ struct sk_buff *skb;
+ const struct cw1200_txpriv *txpriv;
+
+ txrx_printk(KERN_DEBUG "[TX] TX confirm: %d, %d.\n",
+ arg->status, arg->ackFailures);
+
+ if (unlikely(cw1200_itp_tx_running(priv)))
+ return;
+
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) {
+ /* STA is stopped. */
+ return;
+ }
+
+ if (WARN_ON(queue_id >= 4))
+ return;
+
+ if (arg->status)
+ txrx_printk(KERN_DEBUG "TX failed: %d.\n",
+ arg->status);
+
+ if ((arg->status == WSM_REQUEUE) &&
+ (arg->flags & WSM_TX_STATUS_REQUEUE)) {
+ /* "Requeue" means "implicit suspend" */
+ struct wsm_suspend_resume suspend = {
+ .link_id = arg->link_id,
+ .stop = 1,
+ .multicast = !arg->link_id,
+ };
+ cw1200_suspend_resume(priv, &suspend);
+ wiphy_warn(priv->hw->wiphy, "Requeue for link_id %d (try %d)."
+ " STAs asleep: 0x%.8X\n",
+ arg->link_id,
+ cw1200_queue_get_generation(arg->packetID) + 1,
+ priv->sta_asleep_mask);
+ WARN_ON(cw1200_queue_requeue(queue,
+ arg->packetID));
+ spin_lock_bh(&priv->ps_state_lock);
+ if (!arg->link_id) {
+ priv->buffered_multicasts = true;
+ if (priv->sta_asleep_mask) {
+ queue_work(priv->workqueue,
+ &priv->multicast_start_work);
+ }
+ }
+ spin_unlock_bh(&priv->ps_state_lock);
+ } else if (!WARN_ON(cw1200_queue_get_skb(
+ queue, arg->packetID, &skb, &txpriv))) {
+ struct ieee80211_tx_info *tx = IEEE80211_SKB_CB(skb);
+ int tx_count = arg->ackFailures;
+ u8 ht_flags = 0;
+ int i;
+
+ if (cw1200_ht_greenfield(&priv->ht_info))
+ ht_flags |= IEEE80211_TX_RC_GREEN_FIELD;
+
+ if (likely(!arg->status)) {
+ tx->flags |= IEEE80211_TX_STAT_ACK;
+ priv->cqm_tx_failure_count = 0;
+ ++tx_count;
+ cw1200_debug_txed(priv);
+ if (arg->flags & WSM_TX_STATUS_AGGREGATION) {
+ /* Do not report aggregation to mac80211:
+ * it confuses minstrel a lot. */
+ /* tx->flags |= IEEE80211_TX_STAT_AMPDU; */
+ cw1200_debug_txed_agg(priv);
+ }
+ } else {
+ spin_lock(&priv->bss_loss_lock);
+ if (priv->bss_loss_status ==
+ CW1200_BSS_LOSS_CONFIRMING &&
+ priv->bss_loss_confirm_id ==
+ arg->packetID) {
+ priv->bss_loss_status =
+ CW1200_BSS_LOSS_CONFIRMED;
+ spin_unlock(&priv->bss_loss_lock);
+ cancel_delayed_work(&priv->bss_loss_work);
+ queue_delayed_work(priv->workqueue,
+ &priv->bss_loss_work, 0);
+ } else
+ spin_unlock(&priv->bss_loss_lock);
+
+ /* TODO: Update TX failure counters */
+ if (unlikely(priv->cqm_tx_failure_thold &&
+ (++priv->cqm_tx_failure_count >
+ priv->cqm_tx_failure_thold))) {
+ priv->cqm_tx_failure_thold = 0;
+ queue_work(priv->workqueue,
+ &priv->tx_failure_work);
+ }
+ if (tx_count)
+ ++tx_count;
+ }
+
+ for (i = 0; i < IEEE80211_TX_MAX_RATES; ++i) {
+ if (tx->status.rates[i].count >= tx_count) {
+ tx->status.rates[i].count = tx_count;
+ break;
+ }
+ tx_count -= tx->status.rates[i].count;
+ if (tx->status.rates[i].flags & IEEE80211_TX_RC_MCS)
+ tx->status.rates[i].flags |= ht_flags;
+ }
+
+ for (++i; i < IEEE80211_TX_MAX_RATES; ++i) {
+ tx->status.rates[i].count = 0;
+ tx->status.rates[i].idx = -1;
+ }
+
+
+ cw1200_queue_remove(queue, arg->packetID);
+ }
+}
+
+static void cw1200_notify_buffered_tx(struct cw1200_common *priv,
+ struct sk_buff *skb, int link_id, int tid)
+{
+ struct ieee80211_sta *sta;
+ struct ieee80211_hdr *hdr;
+ u8 *buffered;
+ u8 still_buffered = 0;
+
+ if (link_id && tid < CW1200_MAX_TID) {
+ buffered = priv->link_id_db
+ [link_id - 1].buffered;
+
+ spin_lock_bh(&priv->ps_state_lock);
+ if (!WARN_ON(!buffered[tid]))
+ still_buffered = --buffered[tid];
+ spin_unlock_bh(&priv->ps_state_lock);
+
+ if (!still_buffered && tid < CW1200_MAX_TID) {
+ hdr = (struct ieee80211_hdr *) skb->data;
+ rcu_read_lock();
+ sta = ieee80211_find_sta(priv->vif, hdr->addr1);
+ if (sta)
+ ieee80211_sta_set_buffered(sta, tid, false);
+ rcu_read_unlock();
+ }
+ }
+}
+
+void cw1200_skb_dtor(struct cw1200_common *priv,
+ struct sk_buff *skb,
+ const struct cw1200_txpriv *txpriv)
+{
+ skb_pull(skb, txpriv->offset);
+ if (txpriv->rate_id != CW1200_INVALID_RATE_ID) {
+ cw1200_notify_buffered_tx(priv, skb,
+ txpriv->raw_link_id, txpriv->tid);
+ tx_policy_put(priv, txpriv->rate_id);
+ }
+ if (likely(!cw1200_is_itp(priv)))
+ ieee80211_tx_status(priv->hw, skb);
+}
+
+void cw1200_rx_cb(struct cw1200_common *priv,
+ struct wsm_rx *arg,
+ struct sk_buff **skb_p)
+{
+ struct sk_buff *skb = *skb_p;
+ struct ieee80211_rx_status *hdr = IEEE80211_SKB_RXCB(skb);
+ struct ieee80211_hdr *frame = (struct ieee80211_hdr *)skb->data;
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+ struct cw1200_link_entry *entry = NULL;
+ unsigned long grace_period;
+ bool early_data = false;
+ hdr->flag = 0;
+
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) {
+ /* STA is stopped. */
+ goto drop;
+ }
+
+ if (arg->link_id && arg->link_id <= CW1200_MAX_STA_IN_AP_MODE) {
+ entry = &priv->link_id_db[arg->link_id - 1];
+ if (entry->status == CW1200_LINK_SOFT &&
+ ieee80211_is_data(frame->frame_control))
+ early_data = true;
+ entry->timestamp = jiffies;
+ }
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+ else if ((priv->vif->p2p == WSM_START_MODE_P2P_GO)
+ && ieee80211_is_action(frame->frame_control)
+ && (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) {
+ txrx_printk(KERN_DEBUG "[RX] Going to MAP&RESET link ID\n");
+
+ if (work_pending(&priv->linkid_reset_work))
+ WARN_ON(1);
+
+ memcpy(&priv->action_frame_sa[0],
+ ieee80211_get_SA(frame), ETH_ALEN);
+ priv->action_linkid = 0;
+ schedule_work(&priv->linkid_reset_work);
+ }
+
+ if (arg->link_id && (priv->vif->p2p == WSM_START_MODE_P2P_GO)
+ && ieee80211_is_action(frame->frame_control)
+ && (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) {
+ /* Link ID already exists for the ACTION frame.
+ * Reset and Remap */
+ if (work_pending(&priv->linkid_reset_work))
+ WARN_ON(1);
+ memcpy(&priv->action_frame_sa[0],
+ ieee80211_get_SA(frame), ETH_ALEN);
+ priv->action_linkid = arg->link_id;
+ schedule_work(&priv->linkid_reset_work);
+ }
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+ if (unlikely(arg->status)) {
+ if (arg->status == WSM_STATUS_MICFAILURE) {
+ txrx_printk(KERN_DEBUG "[RX] MIC failure.\n");
+ hdr->flag |= RX_FLAG_MMIC_ERROR;
+ } else if (arg->status == WSM_STATUS_NO_KEY_FOUND) {
+ txrx_printk(KERN_DEBUG "[RX] No key found.\n");
+ goto drop;
+ } else {
+ txrx_printk(KERN_DEBUG "[RX] Receive failure: %d.\n",
+ arg->status);
+ goto drop;
+ }
+ }
+
+ if (skb->len < sizeof(struct ieee80211_pspoll)) {
+ wiphy_warn(priv->hw->wiphy, "Mailformed SDU rx'ed. "
+ "Size is lesser than IEEE header.\n");
+ goto drop;
+ }
+
+ if (unlikely(ieee80211_is_pspoll(frame->frame_control)))
+ if (cw1200_handle_pspoll(priv, skb))
+ goto drop;
+
+ hdr->mactime = 0; /* Not supported by WSM */
+ hdr->band = (arg->channelNumber > 14) ?
+ IEEE80211_BAND_5GHZ : IEEE80211_BAND_2GHZ;
+ hdr->freq = ieee80211_channel_to_frequency(
+ arg->channelNumber,
+ hdr->band);
+
+ if (arg->rxedRate >= 14) {
+ hdr->flag |= RX_FLAG_HT;
+ hdr->rate_idx = arg->rxedRate - 14;
+ } else if (arg->rxedRate >= 4) {
+ hdr->rate_idx = arg->rxedRate - 2;
+ } else {
+ hdr->rate_idx = arg->rxedRate;
+ }
+
+ hdr->signal = (s8)arg->rcpiRssi;
+ hdr->antenna = 0;
+
+ if (WSM_RX_STATUS_ENCRYPTION(arg->flags)) {
+ size_t iv_len = 0, icv_len = 0;
+ size_t hdrlen = ieee80211_hdrlen(frame->frame_control);
+
+ hdr->flag |= RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED;
+
+ /* Oops... There is no fast way to ask mac80211 about
+ * IV/ICV lengths. Even defineas are not exposed.*/
+ switch (WSM_RX_STATUS_ENCRYPTION(arg->flags)) {
+ case WSM_RX_STATUS_WEP:
+ iv_len = 4 /* WEP_IV_LEN */;
+ icv_len = 4 /* WEP_ICV_LEN */;
+ break;
+ case WSM_RX_STATUS_TKIP:
+ iv_len = 8 /* TKIP_IV_LEN */;
+ icv_len = 4 /* TKIP_ICV_LEN */
+ + 8 /*MICHAEL_MIC_LEN*/;
+ hdr->flag |= RX_FLAG_MMIC_STRIPPED;
+ break;
+ case WSM_RX_STATUS_AES:
+ iv_len = 8 /* CCMP_HDR_LEN */;
+ icv_len = 8 /* CCMP_MIC_LEN */;
+ break;
+ case WSM_RX_STATUS_WAPI:
+ iv_len = 18 /* WAPI_HDR_LEN */;
+ icv_len = 16 /* WAPI_MIC_LEN */;
+ break;
+ default:
+ WARN_ON("Unknown encryption type");
+ goto drop;
+ }
+
+ /* Firmware strips ICV in case of MIC failure. */
+ if (arg->status == WSM_STATUS_MICFAILURE)
+ icv_len = 0;
+
+ if (skb->len < hdrlen + iv_len + icv_len) {
+ wiphy_warn(priv->hw->wiphy, "Mailformed SDU rx'ed. "
+ "Size is lesser than crypto headers.\n");
+ goto drop;
+ }
+
+ /* Remove IV, ICV and MIC */
+ skb_trim(skb, skb->len - icv_len);
+ memmove(skb->data + iv_len, skb->data, hdrlen);
+ skb_pull(skb, iv_len);
+ }
+
+ cw1200_debug_rxed(priv);
+ if (arg->flags & WSM_RX_STATUS_AGGREGATE)
+ cw1200_debug_rxed_agg(priv);
+
+ if (ieee80211_is_action(frame->frame_control) &&
+ (arg->flags & WSM_RX_STATUS_ADDRESS1)) {
+ if (cw1200_handle_action_rx(priv, skb))
+ return;
+ } else if (unlikely(priv->disable_beacon_filter) &&
+ !arg->status &&
+ ieee80211_is_beacon(frame->frame_control) &&
+ !memcmp(ieee80211_get_SA(frame), priv->join_bssid,
+ ETH_ALEN)) {
+ priv->disable_beacon_filter = false;
+ queue_work(priv->workqueue, &priv->update_filtering_work);
+ }
+
+ /* Stay awake for 1sec. after frame is received to give
+ * userspace chance to react and acquire appropriate
+ * wakelock. */
+ if (ieee80211_is_auth(frame->frame_control))
+ grace_period = 5 * HZ;
+ else if (ieee80211_is_deauth(frame->frame_control))
+ grace_period = 5 * HZ;
+ else
+ grace_period = 1 * HZ;
+ cw1200_pm_stay_awake(&priv->pm_state, grace_period);
+
+ if (unlikely(cw1200_itp_rxed(priv, skb)))
+ consume_skb(skb);
+ else if (unlikely(early_data)) {
+ spin_lock_bh(&priv->ps_state_lock);
+ /* Double-check status with lock held */
+ if (entry->status == CW1200_LINK_SOFT)
+ skb_queue_tail(&entry->rx_queue, skb);
+ else
+ ieee80211_rx_irqsafe(priv->hw, skb);
+ spin_unlock_bh(&priv->ps_state_lock);
+ } else {
+ ieee80211_rx_irqsafe(priv->hw, skb);
+ }
+ *skb_p = NULL;
+
+ return;
+
+drop:
+ /* TODO: update failure counters */
+ return;
+}
+
+/* ******************************************************************** */
+/* Security */
+
+int cw1200_alloc_key(struct cw1200_common *priv)
+{
+ int idx;
+
+ idx = ffs(~priv->key_map) - 1;
+ if (idx < 0 || idx > WSM_KEY_MAX_INDEX)
+ return -1;
+
+ priv->key_map |= BIT(idx);
+ priv->keys[idx].entryIndex = idx;
+ return idx;
+}
+
+void cw1200_free_key(struct cw1200_common *priv, int idx)
+{
+ BUG_ON(!(priv->key_map & BIT(idx)));
+ memset(&priv->keys[idx], 0, sizeof(priv->keys[idx]));
+ priv->key_map &= ~BIT(idx);
+}
+
+void cw1200_free_keys(struct cw1200_common *priv)
+{
+ memset(&priv->keys, 0, sizeof(priv->keys));
+ priv->key_map = 0;
+}
+
+int cw1200_upload_keys(struct cw1200_common *priv)
+{
+ int idx, ret = 0;
+ for (idx = 0; idx <= WSM_KEY_MAX_INDEX; ++idx)
+ if (priv->key_map & BIT(idx)) {
+ ret = wsm_add_key(priv, &priv->keys[idx]);
+ if (ret < 0)
+ break;
+ }
+ return ret;
+}
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+/* Workaround for WFD test case 6.1.10 */
+void cw1200_link_id_reset(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, linkid_reset_work);
+ int temp_linkid;
+
+ if (!priv->action_linkid) {
+ /* In GO mode we can receive ACTION frames without a linkID */
+ temp_linkid = cw1200_alloc_link_id(priv,
+ &priv->action_frame_sa[0]);
+ WARN_ON(!temp_linkid);
+ if (temp_linkid) {
+ /* Make sure we execute the WQ */
+ flush_workqueue(priv->workqueue);
+ /* Release the link ID */
+ spin_lock_bh(&priv->ps_state_lock);
+ priv->link_id_db[temp_linkid - 1].prev_status =
+ priv->link_id_db[temp_linkid - 1].status;
+ priv->link_id_db[temp_linkid - 1].status =
+ CW1200_LINK_RESET;
+ spin_unlock_bh(&priv->ps_state_lock);
+ wsm_lock_tx_async(priv);
+ if (queue_work(priv->workqueue,
+ &priv->link_id_work) <= 0)
+ wsm_unlock_tx(priv);
+ }
+ } else {
+ spin_lock_bh(&priv->ps_state_lock);
+ priv->link_id_db[priv->action_linkid - 1].prev_status =
+ priv->link_id_db[priv->action_linkid - 1].status;
+ priv->link_id_db[priv->action_linkid - 1].status =
+ CW1200_LINK_RESET_REMAP;
+ spin_unlock_bh(&priv->ps_state_lock);
+ wsm_lock_tx_async(priv);
+ if (queue_work(priv->workqueue, &priv->link_id_work) <= 0)
+ wsm_unlock_tx(priv);
+ flush_workqueue(priv->workqueue);
+ }
+}
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
diff --git a/drivers/net/wireless/cw1200/txrx.h b/drivers/net/wireless/cw1200/txrx.h
new file mode 100644
index 0000000..3eb56b3
--- /dev/null
+++ b/drivers/net/wireless/cw1200/txrx.h
@@ -0,0 +1,95 @@
+/*
+ * Datapath interface for ST-Ericsson CW1200 mac80211 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_TXRX_H
+#define CW1200_TXRX_H
+
+#include <linux/list.h>
+
+/* extern */ struct ieee80211_hw;
+/* extern */ struct sk_buff;
+/* extern */ struct wsm_tx;
+/* extern */ struct wsm_rx;
+/* extern */ struct wsm_tx_confirm;
+/* extern */ struct cw1200_txpriv;
+
+struct tx_policy {
+ union {
+ __le32 tbl[3];
+ u8 raw[12];
+ };
+ u8 defined; /* TODO: u32 or u8, profile and select best */
+ u8 usage_count; /* --// -- */
+ u8 retry_count; /* --// -- */
+ u8 uploaded;
+};
+
+struct tx_policy_cache_entry {
+ struct tx_policy policy;
+ struct list_head link;
+};
+
+#define TX_POLICY_CACHE_SIZE (8)
+struct tx_policy_cache {
+ struct tx_policy_cache_entry cache[TX_POLICY_CACHE_SIZE];
+ struct list_head used;
+ struct list_head free;
+ spinlock_t lock;
+};
+
+/* ******************************************************************** */
+/* TX policy cache */
+/* Intention of TX policy cache is an overcomplicated WSM API.
+ * Device does not accept per-PDU tx retry sequence.
+ * It uses "tx retry policy id" instead, so driver code has to sync
+ * linux tx retry sequences with a retry policy table in the device.
+ */
+void tx_policy_init(struct cw1200_common *priv);
+void tx_policy_upload_work(struct work_struct *work);
+
+/* ******************************************************************** */
+/* TX implementation */
+
+u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv,
+ u32 rates);
+void cw1200_tx(struct ieee80211_hw *dev, struct sk_buff *skb);
+void cw1200_skb_dtor(struct cw1200_common *priv,
+ struct sk_buff *skb,
+ const struct cw1200_txpriv *txpriv);
+
+/* ******************************************************************** */
+/* WSM callbacks */
+
+void cw1200_tx_confirm_cb(struct cw1200_common *priv,
+ struct wsm_tx_confirm *arg);
+void cw1200_rx_cb(struct cw1200_common *priv,
+ struct wsm_rx *arg,
+ struct sk_buff **skb_p);
+
+/* ******************************************************************** */
+/* Timeout */
+
+void cw1200_tx_timeout(struct work_struct *work);
+
+/* ******************************************************************** */
+/* Security */
+int cw1200_alloc_key(struct cw1200_common *priv);
+void cw1200_free_key(struct cw1200_common *priv, int idx);
+void cw1200_free_keys(struct cw1200_common *priv);
+int cw1200_upload_keys(struct cw1200_common *priv);
+
+/* ******************************************************************** */
+/* Workaround for WFD test case 6.1.10 */
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+void cw1200_link_id_reset(struct work_struct *work);
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+
+#endif /* CW1200_TXRX_H */
--
1.7.9


2012-03-02 20:27:09

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 16/21] cw1200: itp.*, internal device test and calibration code.

The patch implements a debugfs entry which is used primary
for device calibration purposes. Not needed for an end-user.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/itp.c | 739 +++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/itp.h | 151 ++++++++
2 files changed, 890 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/itp.c
create mode 100644 drivers/net/wireless/cw1200/itp.h

diff --git a/drivers/net/wireless/cw1200/itp.c b/drivers/net/wireless/cw1200/itp.c
new file mode 100644
index 0000000..eb7e53b
--- /dev/null
+++ b/drivers/net/wireless/cw1200/itp.c
@@ -0,0 +1,739 @@
+/*
+ * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
+ * ITP code
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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 <linux/debugfs.h>
+#include <linux/poll.h>
+#include <linux/time.h>
+#include <linux/kallsyms.h>
+#include <net/mac80211.h>
+#include "cw1200.h"
+#include "debug.h"
+#include "itp.h"
+#include "sta.h"
+
+static int __cw1200_itp_open(struct cw1200_common *priv);
+static int __cw1200_itp_close(struct cw1200_common *priv);
+static void cw1200_itp_rx_start(struct cw1200_common *priv);
+static void cw1200_itp_rx_stop(struct cw1200_common *priv);
+static void cw1200_itp_rx_stats(struct cw1200_common *priv);
+static void cw1200_itp_rx_reset(struct cw1200_common *priv);
+static void cw1200_itp_tx_stop(struct cw1200_common *priv);
+static void cw1200_itp_handle(struct cw1200_common *priv,
+ struct sk_buff *skb);
+static void cw1200_itp_err(struct cw1200_common *priv,
+ int err,
+ int arg);
+static void __cw1200_itp_tx_stop(struct cw1200_common *priv);
+
+static ssize_t cw1200_itp_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cw1200_common *priv = file->private_data;
+ struct cw1200_itp *itp = &priv->debug->itp;
+ struct sk_buff *skb;
+ int ret;
+
+ if (skb_queue_empty(&itp->log_queue))
+ return 0;
+
+ skb = skb_dequeue(&itp->log_queue);
+ ret = copy_to_user(user_buf, skb->data, skb->len);
+ *ppos += skb->len;
+ skb->data[skb->len] = 0;
+ itp_printk(KERN_DEBUG "[ITP] >>> %s", skb->data);
+ consume_skb(skb);
+
+ return skb->len - ret;
+}
+
+static ssize_t cw1200_itp_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct cw1200_common *priv = file->private_data;
+ struct sk_buff *skb;
+
+ if (!count || count > 1024)
+ return -EINVAL;
+ skb = dev_alloc_skb(count + 1);
+ if (!skb)
+ return -ENOMEM;
+ skb_trim(skb, 0);
+ skb_put(skb, count + 1);
+ if (copy_from_user(skb->data, user_buf, count)) {
+ kfree_skb(skb);
+ return -EFAULT;
+ }
+ skb->data[count] = 0;
+
+ cw1200_itp_handle(priv, skb);
+ consume_skb(skb);
+ return count;
+}
+
+static unsigned int cw1200_itp_poll(struct file *file, poll_table *wait)
+{
+ struct cw1200_common *priv = file->private_data;
+ struct cw1200_itp *itp = &priv->debug->itp;
+ unsigned int mask = 0;
+
+ poll_wait(file, &itp->read_wait, wait);
+
+ if (!skb_queue_empty(&itp->log_queue))
+ mask |= POLLIN | POLLRDNORM;
+
+ mask |= POLLOUT | POLLWRNORM;
+
+ return mask;
+}
+
+static int cw1200_itp_open(struct inode *inode, struct file *file)
+{
+ struct cw1200_common *priv = inode->i_private;
+ struct cw1200_itp *itp = &priv->debug->itp;
+ int ret = 0;
+
+ file->private_data = priv;
+ if (atomic_inc_return(&itp->open_count) == 1) {
+ ret = __cw1200_itp_open(priv);
+ if (ret && !atomic_dec_return(&itp->open_count))
+ __cw1200_itp_close(priv);
+ } else {
+ atomic_dec(&itp->open_count);
+ ret = -EBUSY;
+ }
+
+ return ret;
+}
+
+static int cw1200_itp_close(struct inode *inode, struct file *file)
+{
+ struct cw1200_common *priv = file->private_data;
+ struct cw1200_itp *itp = &priv->debug->itp;
+ if (!atomic_dec_return(&itp->open_count)) {
+ __cw1200_itp_close(priv);
+ wake_up(&itp->close_wait);
+ }
+ return 0;
+}
+
+static const struct file_operations fops_itp = {
+ .open = cw1200_itp_open,
+ .read = cw1200_itp_read,
+ .write = cw1200_itp_write,
+ .poll = cw1200_itp_poll,
+ .release = cw1200_itp_close,
+ .llseek = default_llseek,
+ .owner = THIS_MODULE,
+};
+
+static void cw1200_itp_fill_pattern(u8 *data, int size,
+ enum cw1200_itp_data_modes mode)
+{
+ u8 *p = data;
+
+ if (size <= 0)
+ return;
+
+ switch (mode) {
+ default:
+ case ITP_DATA_ZEROS:
+ memset(data, 0x0, size);
+ break;
+ case ITP_DATA_ONES:
+ memset(data, 0xff, size);
+ break;
+ case ITP_DATA_ZERONES:
+ memset(data, 0x55, size);
+ break;
+ case ITP_DATA_RANDOM:
+ while (p < data+size-sizeof(u32)) {
+ (*(u32 *)p) = random32();
+ p += sizeof(u32);
+ }
+ while (p < data+size) {
+ (*p) = random32() & 0xFF;
+ p++;
+ }
+ break;
+ }
+ return;
+}
+
+static void cw1200_itp_tx_work(struct work_struct *work)
+{
+ struct cw1200_itp *itp = container_of(work, struct cw1200_itp,
+ tx_work.work);
+ struct cw1200_common *priv = itp->priv;
+ atomic_set(&priv->bh_tx, 1);
+ wake_up(&priv->bh_wq);
+}
+
+static void cw1200_itp_tx_finish(struct work_struct *work)
+{
+ struct cw1200_itp *itp = container_of(work, struct cw1200_itp,
+ tx_finish.work);
+ __cw1200_itp_tx_stop(itp->priv);
+}
+
+int cw1200_itp_init(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+
+ itp->priv = priv;
+ atomic_set(&itp->open_count, 0);
+ atomic_set(&itp->stop_tx, 0);
+ atomic_set(&itp->awaiting_confirm, 0);
+ skb_queue_head_init(&itp->log_queue);
+ spin_lock_init(&itp->tx_lock);
+ init_waitqueue_head(&itp->read_wait);
+ init_waitqueue_head(&itp->write_wait);
+ init_waitqueue_head(&itp->close_wait);
+ INIT_DELAYED_WORK(&itp->tx_work, cw1200_itp_tx_work);
+ INIT_DELAYED_WORK(&itp->tx_finish, cw1200_itp_tx_finish);
+ itp->data = NULL;
+ itp->hdr_len = WSM_TX_EXTRA_HEADROOM +
+ sizeof(struct ieee80211_hdr_3addr);
+
+ if (!debugfs_create_file("itp", S_IRUSR | S_IWUSR,
+ priv->debug->debugfs_phy, priv, &fops_itp))
+ return -ENOMEM;
+
+ return 0;
+}
+
+void cw1200_itp_release(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+
+ wait_event_interruptible(itp->close_wait,
+ !atomic_read(&itp->open_count));
+
+ WARN_ON(atomic_read(&itp->open_count));
+
+ skb_queue_purge(&itp->log_queue);
+ cw1200_itp_tx_stop(priv);
+}
+
+static int __cw1200_itp_open(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+
+ if (!priv->vif)
+ return -EINVAL;
+ if (priv->join_status)
+ return -EINVAL;
+ itp->saved_channel = priv->channel;
+ if (!priv->channel)
+ priv->channel = &priv->hw->
+ wiphy->bands[IEEE80211_BAND_2GHZ]->channels[0];
+ wsm_set_bssid_filtering(priv, false);
+ cw1200_itp_rx_reset(priv);
+ return 0;
+}
+
+static int __cw1200_itp_close(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ if (atomic_read(&itp->test_mode) == TEST_MODE_RX_TEST)
+ cw1200_itp_rx_stop(priv);
+ cw1200_itp_tx_stop(priv);
+ cw1200_disable_listening(priv);
+ cw1200_update_filtering(priv);
+ priv->channel = itp->saved_channel;
+ return 0;
+}
+
+bool cw1200_is_itp(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ return atomic_read(&itp->open_count) != 0;
+}
+
+static void cw1200_itp_rx_reset(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ itp->rx_cnt = 0;
+ itp->rx_rssi = 0;
+ itp->rx_rssi_max = -1000;
+ itp->rx_rssi_min = 1000;
+}
+
+static void cw1200_itp_rx_start(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+
+ itp_printk(KERN_DEBUG "[ITP] RX start, band = %d, ch = %d\n",
+ itp->band, itp->ch);
+ atomic_set(&itp->test_mode, TEST_MODE_RX_TEST);
+ cw1200_update_listening(priv, false);
+ priv->channel = &priv->hw->
+ wiphy->bands[itp->band]->channels[itp->ch];
+ cw1200_update_listening(priv, true);
+ wsm_set_bssid_filtering(priv, false);
+}
+
+static void cw1200_itp_rx_stop(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ itp_printk(KERN_DEBUG "[ITP] RX stop\n");
+ atomic_set(&itp->test_mode, TEST_MODE_NO_TEST);
+ cw1200_itp_rx_reset(priv);
+}
+
+static void cw1200_itp_rx_stats(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ struct sk_buff *skb;
+ char buf[128];
+ int len, ret;
+ struct wsm_counters_table counters;
+
+ ret = wsm_get_counters_table(priv, &counters);
+
+ if (ret)
+ cw1200_itp_err(priv, -EBUSY, 20);
+
+ if (!itp->rx_cnt)
+ len = snprintf(buf, sizeof(buf), "1,0,0,0,0,%d\n",
+ counters.countRxPacketErrors);
+ else
+ len = snprintf(buf, sizeof(buf), "1,%d,%ld,%d,%d,%d\n",
+ itp->rx_cnt,
+ itp->rx_cnt ? itp->rx_rssi / itp->rx_cnt : 0,
+ itp->rx_rssi_min, itp->rx_rssi_max,
+ counters.countRxPacketErrors);
+
+ if (len <= 0) {
+ cw1200_itp_err(priv, -EBUSY, 21);
+ return;
+ }
+
+ skb = dev_alloc_skb(len);
+ if (!skb) {
+ cw1200_itp_err(priv, -ENOMEM, 22);
+ return;
+ }
+
+ itp->rx_cnt = 0;
+ itp->rx_rssi = 0;
+ itp->rx_rssi_max = -1000;
+ itp->rx_rssi_min = 1000;
+
+ skb_trim(skb, 0);
+ skb_put(skb, len);
+
+ memcpy(skb->data, buf, len);
+ skb_queue_tail(&itp->log_queue, skb);
+ wake_up(&itp->read_wait);
+}
+
+static void cw1200_itp_tx_start(struct cw1200_common *priv)
+{
+ struct wsm_tx *tx;
+ struct ieee80211_hdr_3addr *hdr;
+ struct cw1200_itp *itp = &priv->debug->itp;
+ struct wsm_association_mode assoc_mode = {
+ .flags = WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE,
+ .preambleType = itp->preamble,
+ };
+ int len;
+ u8 da_addr[6] = ITP_DEFAULT_DA_ADDR;
+
+ /* Rates index 4 and 5 are not supported */
+ if (itp->rate > 3)
+ itp->rate += 2;
+
+ itp_printk(KERN_DEBUG "[ITP] TX start: band = %d, ch = %d, rate = %d,"
+ " preamble = %d, number = %d, data_mode = %d,"
+ " interval = %d, power = %d, data_len = %d\n",
+ itp->band, itp->ch, itp->rate, itp->preamble,
+ itp->number, itp->data_mode, itp->interval_us,
+ itp->power, itp->data_len);
+
+ len = itp->hdr_len + itp->data_len;
+
+ itp->data = kmalloc(len, GFP_KERNEL);
+ tx = (struct wsm_tx *)itp->data;
+ tx->hdr.len = itp->data_len + itp->hdr_len;
+ tx->hdr.id = __cpu_to_le16(0x0004 | 1 << 6);
+ tx->maxTxRate = itp->rate;
+ tx->queueId = 3;
+ tx->more = 0;
+ tx->flags = 0xc;
+ tx->packetID = 0x55ff55;
+ tx->reserved = 0;
+ tx->expireTime = 1;
+
+ if (itp->preamble == ITP_PREAMBLE_GREENFIELD)
+ tx->htTxParameters = WSM_HT_TX_GREENFIELD;
+ else if (itp->preamble == ITP_PREAMBLE_MIXED)
+ tx->htTxParameters = WSM_HT_TX_MIXED;
+
+ hdr = (struct ieee80211_hdr_3addr *)&itp->data[sizeof(struct wsm_tx)];
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
+ IEEE80211_FCTL_TODS);
+ memcpy(hdr->addr1, da_addr, ETH_ALEN);
+ memcpy(hdr->addr2, priv->vif->addr, ETH_ALEN);
+ memcpy(hdr->addr3, da_addr, ETH_ALEN);
+
+ cw1200_itp_fill_pattern(&itp->data[itp->hdr_len],
+ itp->data_len, itp->data_mode);
+
+ cw1200_update_listening(priv, false);
+ priv->channel = &priv->hw->
+ wiphy->bands[itp->band]->channels[itp->ch];
+ WARN_ON(wsm_set_output_power(priv, itp->power));
+ if (itp->preamble == ITP_PREAMBLE_SHORT ||
+ itp->preamble == ITP_PREAMBLE_LONG)
+ WARN_ON(wsm_set_association_mode(priv,
+ &assoc_mode));
+ wsm_set_bssid_filtering(priv, false);
+ cw1200_update_listening(priv, true);
+
+ spin_lock_bh(&itp->tx_lock);
+ atomic_set(&itp->test_mode, TEST_MODE_TX_TEST);
+ atomic_set(&itp->awaiting_confirm, 0);
+ atomic_set(&itp->stop_tx, 0);
+ atomic_set(&priv->bh_tx, 1);
+ ktime_get_ts(&itp->last_sent);
+ wake_up(&priv->bh_wq);
+ spin_unlock_bh(&itp->tx_lock);
+}
+
+void __cw1200_itp_tx_stop(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ spin_lock_bh(&itp->tx_lock);
+ kfree(itp->data);
+ itp->data = NULL;
+ atomic_set(&itp->test_mode, TEST_MODE_NO_TEST);
+ spin_unlock_bh(&itp->tx_lock);
+}
+
+static void cw1200_itp_tx_stop(struct cw1200_common *priv)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ itp_printk(KERN_DEBUG "[ITP] TX stop\n");
+ atomic_set(&itp->stop_tx, 1);
+ flush_workqueue(priv->workqueue);
+
+ /* time for FW to confirm all tx requests */
+ msleep(500);
+
+ __cw1200_itp_tx_stop(priv);
+}
+
+static void cw1200_itp_get_version(struct cw1200_common *priv,
+ enum cw1200_itp_version_type type)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ struct sk_buff *skb;
+ char buf[ITP_BUF_SIZE];
+ size_t size = 0;
+ int len;
+ itp_printk(KERN_DEBUG "[ITP] print %s version\n", type == ITP_CHIP_ID ?
+ "chip" : "firmware");
+
+ len = snprintf(buf, ITP_BUF_SIZE, "2,");
+ if (len <= 0) {
+ cw1200_itp_err(priv, -EINVAL, 40);
+ return;
+ }
+ size += len;
+
+ switch (type) {
+ case ITP_CHIP_ID:
+ len = cw1200_print_fw_version(priv, buf+size,
+ ITP_BUF_SIZE - size);
+
+ if (len <= 0) {
+ cw1200_itp_err(priv, -EINVAL, 41);
+ return;
+ }
+ size += len;
+ break;
+ case ITP_FW_VER:
+ len = snprintf(buf+size, ITP_BUF_SIZE - size,
+ "%d.%d", priv->wsm_caps.hardwareId,
+ priv->wsm_caps.hardwareSubId);
+ if (len <= 0) {
+ cw1200_itp_err(priv, -EINVAL, 42);
+ return;
+ }
+ size += len;
+ break;
+ default:
+ cw1200_itp_err(priv, -EINVAL, 43);
+ break;
+ }
+
+ len = snprintf(buf+size, ITP_BUF_SIZE-size, "\n");
+ if (len <= 0) {
+ cw1200_itp_err(priv, -EINVAL, 44);
+ return;
+ }
+ size += len;
+
+ skb = dev_alloc_skb(size);
+ if (!skb) {
+ cw1200_itp_err(priv, -ENOMEM, 45);
+ return;
+ }
+
+ skb_trim(skb, 0);
+ skb_put(skb, size);
+
+ memcpy(skb->data, buf, size);
+ skb_queue_tail(&itp->log_queue, skb);
+ wake_up(&itp->read_wait);
+}
+
+int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data,
+ size_t *tx_len, int *burst)
+{
+ struct cw1200_itp *itp;
+ struct timespec now;
+ int time_left_us;
+
+ if (!priv->debug)
+ return 0;
+
+ itp = &priv->debug->itp;
+
+ if (!itp)
+ return 0;
+
+ spin_lock_bh(&itp->tx_lock);
+ if (atomic_read(&itp->test_mode) != TEST_MODE_TX_TEST)
+ goto out;
+
+ if (atomic_read(&itp->stop_tx))
+ goto out;
+
+ if (itp->number == 0) {
+ atomic_set(&itp->stop_tx, 1);
+ queue_delayed_work(priv->workqueue, &itp->tx_finish,
+ HZ/10);
+ goto out;
+ }
+
+ if (!itp->data)
+ goto out;
+
+ if (priv->hw_bufs_used >= 2) {
+ if (!atomic_read(&priv->bh_rx))
+ atomic_set(&priv->bh_rx, 1);
+ atomic_set(&priv->bh_tx, 1);
+ goto out;
+ }
+
+ ktime_get_ts(&now);
+ time_left_us = (itp->last_sent.tv_sec -
+ now.tv_sec)*1000000 +
+ (itp->last_sent.tv_nsec - now.tv_nsec)/1000
+ + itp->interval_us;
+
+ if (time_left_us > ITP_TIME_THRES_US) {
+ queue_delayed_work(priv->workqueue, &itp->tx_work,
+ ITP_US_TO_MS(time_left_us)*HZ/1000);
+ goto out;
+ }
+
+ if (time_left_us > 50)
+ udelay(time_left_us);
+
+ if (itp->number > 0)
+ itp->number--;
+
+ *data = itp->data;
+ *tx_len = itp->data_len + itp->hdr_len;
+
+ if (itp->data_mode == ITP_DATA_RANDOM)
+ cw1200_itp_fill_pattern(&itp->data[itp->hdr_len],
+ itp->data_len, itp->data_mode);
+ *burst = 2;
+ atomic_set(&priv->bh_tx, 1);
+ ktime_get_ts(&itp->last_sent);
+ atomic_add(1, &itp->awaiting_confirm);
+ spin_unlock_bh(&itp->tx_lock);
+ return 1;
+
+out:
+ spin_unlock_bh(&itp->tx_lock);
+ return 0;
+}
+
+bool cw1200_itp_rxed(struct cw1200_common *priv, struct sk_buff *skb)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ struct ieee80211_rx_status *rx = IEEE80211_SKB_RXCB(skb);
+ int signal;
+
+ if (atomic_read(&itp->test_mode) != TEST_MODE_RX_TEST)
+ return cw1200_is_itp(priv);
+ if (rx->freq != priv->channel->center_freq)
+ return true;
+
+ signal = rx->signal;
+ itp->rx_cnt++;
+ itp->rx_rssi += signal;
+ if (itp->rx_rssi_min > rx->signal)
+ itp->rx_rssi_min = rx->signal;
+ if (itp->rx_rssi_max < rx->signal)
+ itp->rx_rssi_max = rx->signal;
+
+ return true;
+}
+
+void cw1200_itp_wake_up_tx(struct cw1200_common *priv)
+{
+ wake_up(&priv->debug->itp.write_wait);
+}
+
+bool cw1200_itp_tx_running(struct cw1200_common *priv)
+{
+ if (atomic_read(&priv->debug->itp.awaiting_confirm) ||
+ atomic_read(&priv->debug->itp.test_mode) ==
+ TEST_MODE_TX_TEST) {
+ atomic_sub(1, &priv->debug->itp.awaiting_confirm);
+ return true;
+ }
+ return false;
+}
+
+static void cw1200_itp_handle(struct cw1200_common *priv,
+ struct sk_buff *skb)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ const struct wiphy *wiphy = priv->hw->wiphy;
+ int cmd;
+ int ret;
+
+ itp_printk(KERN_DEBUG "[ITP] <<< %s", skb->data);
+ if (sscanf(skb->data, "%d", &cmd) != 1) {
+ cw1200_itp_err(priv, -EINVAL, 1);
+ return;
+ }
+
+ switch (cmd) {
+ case 1: /* RX test */
+ if (atomic_read(&itp->test_mode)) {
+ cw1200_itp_err(priv, -EBUSY, 0);
+ return;
+ }
+ ret = sscanf(skb->data, "%d,%d,%d",
+ &cmd, &itp->band, &itp->ch);
+ if (ret != 3) {
+ cw1200_itp_err(priv, -EINVAL, ret + 1);
+ return;
+ }
+ if (itp->band >= 2)
+ cw1200_itp_err(priv, -EINVAL, 2);
+ else if (!wiphy->bands[itp->band])
+ cw1200_itp_err(priv, -EINVAL, 2);
+ else if (itp->ch >=
+ wiphy->bands[itp->band]->n_channels)
+ cw1200_itp_err(priv, -EINVAL, 3);
+ else {
+ cw1200_itp_rx_stats(priv);
+ cw1200_itp_rx_start(priv);
+ }
+ break;
+ case 2: /* RX stat */
+ cw1200_itp_rx_stats(priv);
+ break;
+ case 3: /* RX/TX stop */
+ if (atomic_read(&itp->test_mode) == TEST_MODE_RX_TEST) {
+ cw1200_itp_rx_stats(priv);
+ cw1200_itp_rx_stop(priv);
+ } else if (atomic_read(&itp->test_mode) == TEST_MODE_TX_TEST) {
+ cw1200_itp_tx_stop(priv);
+ } else
+ cw1200_itp_err(priv, -EBUSY, 0);
+ break;
+ case 4: /* TX start */
+ if (atomic_read(&itp->test_mode) != TEST_MODE_NO_TEST) {
+ cw1200_itp_err(priv, -EBUSY, 0);
+ return;
+ }
+ ret = sscanf(skb->data, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+ &cmd, &itp->band, &itp->ch, &itp->rate,
+ &itp->preamble, &itp->number, &itp->data_mode,
+ &itp->interval_us, &itp->power, &itp->data_len);
+ if (ret != 10) {
+ cw1200_itp_err(priv, -EINVAL, ret + 1);
+ return;
+ }
+ if (itp->band >= 2)
+ cw1200_itp_err(priv, -EINVAL, 2);
+ else if (!wiphy->bands[itp->band])
+ cw1200_itp_err(priv, -EINVAL, 2);
+ else if (itp->ch >=
+ wiphy->bands[itp->band]->n_channels)
+ cw1200_itp_err(priv, -EINVAL, 3);
+ else if (itp->rate >= 20)
+ cw1200_itp_err(priv, -EINVAL, 4);
+ else if (itp->preamble >= ITP_PREAMBLE_MAX)
+ cw1200_itp_err(priv, -EINVAL, 5);
+ else if (itp->data_mode >= ITP_DATA_MAX_MODE)
+ cw1200_itp_err(priv, -EINVAL, 7);
+ else if (itp->data_len < ITP_MIN_DATA_SIZE ||
+ itp->data_len > priv->wsm_caps.sizeInpChBuf -
+ itp->hdr_len)
+ cw1200_itp_err(priv, -EINVAL, 8);
+ else {
+ cw1200_itp_tx_start(priv);
+ }
+ break;
+ case 5:
+ cw1200_itp_get_version(priv, ITP_CHIP_ID);
+ break;
+ case 6:
+ cw1200_itp_get_version(priv, ITP_FW_VER);
+ break;
+
+ }
+}
+
+static void cw1200_itp_err(struct cw1200_common *priv,
+ int err, int arg)
+{
+ struct cw1200_itp *itp = &priv->debug->itp;
+ struct sk_buff *skb;
+ static char buf[255];
+ int len;
+
+ len = snprintf(buf, sizeof(buf), "%d,%d\n",
+ err, arg);
+ if (len <= 0)
+ return;
+
+ skb = dev_alloc_skb(len);
+ if (!skb)
+ return;
+
+ skb_trim(skb, 0);
+ skb_put(skb, len);
+
+ memcpy(skb->data, buf, len);
+ skb_queue_tail(&itp->log_queue, skb);
+ wake_up(&itp->read_wait);
+
+ len = sprint_symbol(buf,
+ (unsigned long)__builtin_return_address(0));
+ if (len <= 0)
+ return;
+ itp_printk(KERN_DEBUG "[ITP] error %d,%d from %s\n",
+ err, arg, buf);
+}
diff --git a/drivers/net/wireless/cw1200/itp.h b/drivers/net/wireless/cw1200/itp.h
new file mode 100644
index 0000000..635e7f8
--- /dev/null
+++ b/drivers/net/wireless/cw1200/itp.h
@@ -0,0 +1,151 @@
+/*
+ * ITP code for ST-Ericsson CW1200 mac80211 driver
+ *
+ * Copyright (c) 2011-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_ITP_H_INCLUDED
+#define CW1200_ITP_H_INCLUDED
+
+struct cw200_common;
+struct wsm_tx_confirm;
+struct dentry;
+
+#ifdef CONFIG_CW1200_ITP
+
+/*extern*/ struct ieee80211_channel;
+
+#define TEST_MODE_NO_TEST (0)
+#define TEST_MODE_RX_TEST (1)
+#define TEST_MODE_TX_TEST (2)
+
+#define itp_printk(...) printk(__VA_ARGS__)
+#define ITP_DEFAULT_DA_ADDR {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+#define ITP_MIN_DATA_SIZE 6
+#define ITP_MAX_DATA_SIZE 1600
+#define ITP_TIME_THRES_US 10000
+#define ITP_US_TO_MS(x) ((x)/1000)
+#define ITP_MS_TO_US(x) ((x)*1000)
+#if ((ITP_US_TO_MS(ITP_TIME_THRES_US))*HZ/1000) < 1
+#warning not able to achieve non-busywaiting ITP_TIME_THRES_US\
+precision with current HZ value !
+#endif
+#define ITP_BUF_SIZE 255
+
+
+enum cw1200_itp_data_modes {
+ ITP_DATA_ZEROS,
+ ITP_DATA_ONES,
+ ITP_DATA_ZERONES,
+ ITP_DATA_RANDOM,
+ ITP_DATA_MAX_MODE,
+};
+
+enum cw1200_itp_version_type {
+ ITP_CHIP_ID,
+ ITP_FW_VER,
+};
+
+enum cw1200_itp_preamble_type {
+ ITP_PREAMBLE_LONG,
+ ITP_PREAMBLE_SHORT,
+ ITP_PREAMBLE_OFDM,
+ ITP_PREAMBLE_MIXED,
+ ITP_PREAMBLE_GREENFIELD,
+ ITP_PREAMBLE_MAX,
+};
+
+
+struct cw1200_itp {
+ struct cw1200_common *priv;
+ atomic_t open_count;
+ atomic_t awaiting_confirm;
+ struct sk_buff_head log_queue;
+ wait_queue_head_t read_wait;
+ wait_queue_head_t write_wait;
+ wait_queue_head_t close_wait;
+ struct ieee80211_channel *saved_channel;
+ atomic_t stop_tx;
+ struct delayed_work tx_work;
+ struct delayed_work tx_finish;
+ spinlock_t tx_lock;
+ struct timespec last_sent;
+ atomic_t test_mode;
+ int rx_cnt;
+ long rx_rssi;
+ int rx_rssi_max;
+ int rx_rssi_min;
+ unsigned band;
+ unsigned ch;
+ unsigned rate;
+ unsigned preamble;
+ unsigned int number;
+ unsigned data_mode;
+ int interval_us;
+ int power;
+ u8 *data;
+ int hdr_len;
+ int data_len;
+};
+
+int cw1200_itp_init(struct cw1200_common *priv);
+void cw1200_itp_release(struct cw1200_common *priv);
+
+bool cw1200_is_itp(struct cw1200_common *priv);
+bool cw1200_itp_rxed(struct cw1200_common *priv, struct sk_buff *skb);
+void cw1200_itp_wake_up_tx(struct cw1200_common *priv);
+int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data,
+ size_t *tx_len, int *burst);
+bool cw1200_itp_tx_running(struct cw1200_common *priv);
+
+#else /* CONFIG_CW1200_ITP */
+
+static inline int
+cw1200_itp_init(struct cw1200_common *priv)
+{
+ return 0;
+}
+
+static inline void cw1200_itp_release(struct cw1200_common *priv)
+{
+}
+
+static inline bool cw1200_is_itp(struct cw1200_common *priv)
+{
+ return false;
+}
+
+static inline bool cw1200_itp_rxed(struct cw1200_common *priv,
+ struct sk_buff *skb)
+{
+ return false;
+}
+
+
+static inline void cw1200_itp_consume_txed(struct cw1200_common *priv)
+{
+}
+
+static inline void cw1200_itp_wake_up_tx(struct cw1200_common *priv)
+{
+}
+
+static inline int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data,
+ size_t *tx_len, int *burst)
+{
+ return 0;
+}
+
+static inline bool cw1200_itp_tx_running(struct cw1200_common *priv)
+{
+ return false;
+}
+
+#endif /* CONFIG_CW1200_ITP */
+
+#endif /* CW1200_ITP_H_INCLUDED */
--
1.7.9


2012-03-02 20:27:10

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 19/21] cw1200: TODO list.

Just a TODO list for the driver.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/TODO | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/TODO

diff --git a/drivers/net/wireless/cw1200/TODO b/drivers/net/wireless/cw1200/TODO
new file mode 100644
index 0000000..f40fd20
--- /dev/null
+++ b/drivers/net/wireless/cw1200/TODO
@@ -0,0 +1,8 @@
+TODO:
+ - IBSS: Not implemented. Consider as obsolete?
+ - Convert bh thread to a realtime workqueue.
+ - Submit mac80211 CQM (beacon miss, tx fail) changes
+ and reenable CQM extensions in the driver.
+ - Align filters in pm.c with wowlan filters.
+ - Different suspend strategy for Linux and Android.
+ - Get use of traceing instead of printing in datapath.
--
1.7.9


2012-03-02 20:26:58

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 11/21] cw1200: bh.*, device serving thread.

The files has name bh, but it is not really a standard Linux bottom half.
SDIO interface does not support asyncronous non-blocking i/o, so serving
code was implemented as a realtime thread. Some effort is ongoing to
convert the thread to a realtime workqueue and start/stop it on interface
start/stop.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/bh.c | 619 ++++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/bh.h | 30 ++
2 files changed, 649 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/bh.c
create mode 100644 drivers/net/wireless/cw1200/bh.h

diff --git a/drivers/net/wireless/cw1200/bh.c b/drivers/net/wireless/cw1200/bh.c
new file mode 100644
index 0000000..2f78e60
--- /dev/null
+++ b/drivers/net/wireless/cw1200/bh.c
@@ -0,0 +1,619 @@
+/*
+ * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on:
+ * ST-Ericsson UMAC CW1200 driver, which is
+ * Copyright (c) 2010, ST-Ericsson AB
+ * Author: Ajitpal Singh <[email protected]>
+ *
+ * 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 <net/mac80211.h>
+#include <linux/kthread.h>
+
+#include "cw1200.h"
+#include "bh.h"
+#include "hwio.h"
+#include "wsm.h"
+#include "sbus.h"
+#include "debug.h"
+
+#if defined(CONFIG_CW1200_BH_DEBUG)
+#define bh_printk(...) printk(__VA_ARGS__)
+#else
+#define bh_printk(...)
+#endif
+
+static int cw1200_bh(void *arg);
+
+/* TODO: Verify these numbers with WSM specification. */
+#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);
+
+
+int cw1200_register_bh(struct cw1200_common *priv)
+{
+ int err = 0;
+ struct sched_param param = { .sched_priority = 1 };
+ bh_printk(KERN_DEBUG "[BH] register.\n");
+ BUG_ON(priv->bh_thread);
+ 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->buf_id_tx = 0;
+ priv->buf_id_rx = 0;
+ init_waitqueue_head(&priv->bh_wq);
+ init_waitqueue_head(&priv->bh_evt_wq);
+ priv->bh_thread = kthread_create(&cw1200_bh, priv, "cw1200_bh");
+ if (IS_ERR(priv->bh_thread)) {
+ err = PTR_ERR(priv->bh_thread);
+ priv->bh_thread = NULL;
+ } else {
+ WARN_ON(sched_setscheduler(priv->bh_thread,
+ SCHED_FIFO, &param));
+#ifdef HAS_PUT_TASK_STRUCT
+ get_task_struct(priv->bh_thread);
+#endif
+ wake_up_process(priv->bh_thread);
+ }
+ return err;
+}
+
+void cw1200_unregister_bh(struct cw1200_common *priv)
+{
+ struct task_struct *thread = priv->bh_thread;
+ if (WARN_ON(!thread))
+ return;
+
+ priv->bh_thread = NULL;
+ bh_printk(KERN_DEBUG "[BH] unregister.\n");
+ atomic_add(1, &priv->bh_term);
+ wake_up(&priv->bh_wq);
+ kthread_stop(thread);
+#ifdef HAS_PUT_TASK_STRUCT
+ put_task_struct(thread);
+#endif
+}
+
+void cw1200_irq_handler(struct cw1200_common *priv)
+{
+ bh_printk(KERN_DEBUG "[BH] irq.\n");
+ if (/* WARN_ON */(priv->bh_error))
+ return;
+
+ if (atomic_add_return(1, &priv->bh_rx) == 1)
+ wake_up(&priv->bh_wq);
+}
+
+void cw1200_bh_wakeup(struct cw1200_common *priv)
+{
+ bh_printk(KERN_DEBUG "[BH] wakeup.\n");
+ if (WARN_ON(priv->bh_error))
+ return;
+
+ if (atomic_add_return(1, &priv->bh_tx) == 1)
+ wake_up(&priv->bh_wq);
+}
+
+int cw1200_bh_suspend(struct cw1200_common *priv)
+{
+ bh_printk(KERN_DEBUG "[BH] suspend.\n");
+ if (WARN_ON(priv->bh_error))
+ return 0;
+
+ 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)
+{
+ bh_printk(KERN_DEBUG "[BH] resume.\n");
+ if (WARN_ON(priv->bh_error))
+ return 0;
+
+ 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.numInpChBufs)
+ ret = 1;
+ if (!priv->hw_bufs_used)
+ wake_up(&priv->bh_evt_wq);
+ return ret;
+}
+
+static struct sk_buff *cw1200_get_skb(struct cw1200_common *priv, size_t len)
+{
+ struct sk_buff *skb;
+ size_t alloc_len = (len > SDIO_BLOCK_SIZE) ? len : SDIO_BLOCK_SIZE;
+
+ if (len > SDIO_BLOCK_SIZE || !priv->skb_cache) {
+ skb = dev_alloc_skb(alloc_len
+ + WSM_TX_EXTRA_HEADROOM
+ + 8 /* TKIP IV */
+ + 12 /* TKIP ICV + MIC */
+ - 2 /* Piggyback */);
+ /* In AP mode RXed SKB can be looped back as a broadcast.
+ * Here we reserve enough space for headers. */
+ skb_reserve(skb, WSM_TX_EXTRA_HEADROOM
+ + 8 /* TKIP IV */
+ - WSM_RX_EXTRA_HEADROOM);
+ } else {
+ skb = priv->skb_cache;
+ priv->skb_cache = NULL;
+ }
+ return skb;
+}
+
+static void cw1200_put_skb(struct cw1200_common *priv, struct sk_buff *skb)
+{
+ if (priv->skb_cache)
+ dev_kfree_skb(skb);
+ else
+ priv->skb_cache = skb;
+}
+
+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)
+ printk(KERN_ERR
+ "[BH] Failed to read control register.\n");
+ }
+
+ return ret;
+}
+
+static int cw1200_device_wakeup(struct cw1200_common *priv)
+{
+ u16 ctrl_reg;
+ int ret;
+
+ bh_printk(KERN_DEBUG "[BH] Device wakeup.\n");
+
+ /* 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) {
+ bh_printk(KERN_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)
+{
+ bh_printk(KERN_DEBUG "[BH] Powerave is %s.\n",
+ enable ? "enabled" : "disabled");
+ priv->powersave_enabled = enable;
+}
+
+static int cw1200_bh(void *arg)
+{
+ struct cw1200_common *priv = arg;
+ struct sk_buff *skb_rx = NULL;
+ size_t read_len = 0;
+ int rx, tx, term, suspend;
+ struct wsm_hdr *wsm;
+ size_t wsm_len;
+ int wsm_id;
+ u8 wsm_seq;
+ int rx_resync = 1;
+ u16 ctrl_reg = 0;
+ int tx_allowed;
+ int pending_tx = 0;
+ int tx_burst;
+ int rx_burst = 0;
+ long status;
+#if defined(CONFIG_CW1200_WSM_DUMPS)
+ size_t wsm_dump_max = -1;
+#endif
+ u32 dummy;
+
+ for (;;) {
+ if (!priv->hw_bufs_used
+ && priv->powersave_enabled
+ && !priv->device_can_sleep)
+ status = 1 * HZ;
+ else if (priv->hw_bufs_used)
+ /* Interrupt loss detection */
+ status = 1 * HZ;
+ else
+ status = MAX_SCHEDULE_TIMEOUT;
+
+ /* Dummy Read for SDIO retry mechanism*/
+ if (((atomic_read(&priv->bh_rx) == 0) &&
+ (atomic_read(&priv->bh_tx) == 0)))
+ cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID,
+ &dummy, sizeof(dummy));
+#if defined(CONFIG_CW1200_WSM_DUMPS_SHORT)
+ wsm_dump_max = priv->wsm_dump_max_size;
+#endif /* CONFIG_CW1200_WSM_DUMPS_SHORT */
+
+ 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);
+
+ if (status < 0 || term || priv->bh_error)
+ break;
+
+ if (!status && priv->hw_bufs_used) {
+ unsigned long timestamp = jiffies;
+ long timeout;
+ bool pending = false;
+ int i;
+
+ wiphy_warn(priv->hw->wiphy, "Missed interrupt?\n");
+ rx = 1;
+
+ /* Get a timestamp of "oldest" frame */
+ for (i = 0; i < 4; ++i)
+ pending |= cw1200_queue_get_xmit_timestamp(
+ &priv->tx_queue[i],
+ &timestamp);
+
+ /* 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 tread if the frame is "stuck" */
+ if (pending && timeout < 0) {
+ wiphy_warn(priv->hw->wiphy,
+ "Timeout waiting for TX confirm.\n");
+ break;
+ }
+
+#if defined(CONFIG_CW1200_DUMP_ON_ERROR)
+ BUG_ON(1);
+#endif /* CONFIG_CW1200_DUMP_ON_ERROR */
+ } else if (!status) {
+ bh_printk(KERN_DEBUG "[BH] Device wakedown.\n");
+ WARN_ON(cw1200_reg_write_16(priv,
+ ST90TDS_CONTROL_REG_ID, 0));
+ priv->device_can_sleep = true;
+ continue;
+ } else if (suspend) {
+ bh_printk(KERN_DEBUG "[BH] Device suspend.\n");
+ if (priv->powersave_enabled) {
+ WARN_ON(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,
+ "%s: Failed to wait for resume: %ld.\n",
+ __func__, status);
+ break;
+ }
+ bh_printk(KERN_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);
+ continue;
+ }
+
+ tx += pending_tx;
+ pending_tx = 0;
+
+ if (rx) {
+ size_t alloc_len;
+ u8 *data;
+
+ if (WARN_ON(cw1200_bh_read_ctrl_reg(
+ priv, &ctrl_reg)))
+ break;
+rx:
+ read_len = (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2;
+ if (!read_len) {
+ rx_burst = 0;
+ goto tx;
+ }
+
+ if (WARN_ON((read_len < sizeof(struct wsm_hdr)) ||
+ (read_len > EFFECTIVE_BUF_SIZE))) {
+ printk(KERN_DEBUG "Invalid read len: %ld",
+ read_len);
+ break;
+ }
+
+ /* Add SIZE of PIGGYBACK reg (CONTROL Reg)
+ * to the NEXT Message length + 2 Bytes for SKB */
+ read_len = read_len + 2;
+
+#if defined(CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES)
+ alloc_len = priv->sbus_ops->align_size(
+ priv->sbus_priv, read_len);
+#else /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */
+ /* Platform's SDIO workaround */
+ alloc_len = read_len & ~(SDIO_BLOCK_SIZE - 1);
+ if (read_len & (SDIO_BLOCK_SIZE - 1))
+ alloc_len += SDIO_BLOCK_SIZE;
+#endif /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */
+
+ /* Check if not exceeding CW1200 capabilities */
+ if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) {
+ printk(KERN_DEBUG "Read aligned len: %ld\n",
+ alloc_len);
+ }
+
+ skb_rx = cw1200_get_skb(priv, alloc_len);
+ if (WARN_ON(!skb_rx))
+ break;
+
+ skb_trim(skb_rx, 0);
+ skb_put(skb_rx, read_len);
+ data = skb_rx->data;
+ if (WARN_ON(!data))
+ break;
+
+ if (WARN_ON(cw1200_data_read(priv, data, alloc_len)))
+ break;
+
+ /* Piggyback */
+ ctrl_reg = __le16_to_cpu(
+ ((__le16 *)data)[alloc_len / 2 - 1]);
+
+ wsm = (struct wsm_hdr *)data;
+ wsm_len = __le32_to_cpu(wsm->len);
+ if (WARN_ON(wsm_len > read_len))
+ break;
+
+#if defined(CONFIG_CW1200_WSM_DUMPS)
+ if (unlikely(priv->wsm_enable_wsm_dumps))
+ print_hex_dump_bytes("<-- ",
+ DUMP_PREFIX_NONE,
+ data, min(wsm_len, wsm_dump_max));
+#endif /* CONFIG_CW1200_WSM_DUMPS */
+
+ wsm_id = __le32_to_cpu(wsm->id) & 0xFFF;
+ wsm_seq = (__le32_to_cpu(wsm->id) >> 13) & 7;
+
+ skb_trim(skb_rx, wsm_len);
+
+ if (unlikely(wsm_id == 0x0800)) {
+ wsm_handle_exception(priv,
+ &data[sizeof(*wsm)],
+ wsm_len - sizeof(*wsm));
+ break;
+ } else if (unlikely(!rx_resync)) {
+ if (WARN_ON(wsm_seq != priv->wsm_rx_seq)) {
+#if defined(CONFIG_CW1200_DUMP_ON_ERROR)
+ BUG_ON(1);
+#endif /* CONFIG_CW1200_DUMP_ON_ERROR */
+ break;
+ }
+ }
+ 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))
+ break;
+ 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)))
+ break;
+
+ if (skb_rx) {
+ cw1200_put_skb(priv, skb_rx);
+ skb_rx = NULL;
+ }
+
+ read_len = 0;
+
+ if (rx_burst) {
+ cw1200_debug_rx_burst(priv);
+ --rx_burst;
+ goto rx;
+ }
+ }
+
+tx:
+ BUG_ON(priv->hw_bufs_used > priv->wsm_caps.numInpChBufs);
+ tx_burst = priv->wsm_caps.numInpChBufs - priv->hw_bufs_used;
+ tx_allowed = tx_burst > 0;
+
+ if (tx && tx_allowed) {
+ size_t tx_len;
+ u8 *data;
+ int ret;
+
+ if (priv->device_can_sleep) {
+ ret = cw1200_device_wakeup(priv);
+ if (WARN_ON(ret < 0))
+ break;
+ else if (ret)
+ priv->device_can_sleep = false;
+ else {
+ /* Wait for "awake" interrupt */
+ pending_tx = tx;
+ continue;
+ }
+ }
+
+ 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))
+ break;
+ } else {
+ wsm = (struct wsm_hdr *)data;
+ BUG_ON(tx_len < sizeof(*wsm));
+ BUG_ON(__le32_to_cpu(wsm->len) != tx_len);
+
+#if 0 /* count is not implemented */
+ if (ret > 1)
+ atomic_add(1, &priv->bh_tx);
+#else
+ atomic_add(1, &priv->bh_tx);
+#endif
+
+
+#if defined(CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES)
+ tx_len = priv->sbus_ops->align_size(
+ priv->sbus_priv, tx_len);
+#else /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */
+ /* HACK!!! Platform limitation.
+ * It is also supported by upper layer:
+ * there is always enough space at the
+ * end of the buffer. */
+ if (tx_len & (SDIO_BLOCK_SIZE - 1)) {
+ tx_len &= ~(SDIO_BLOCK_SIZE - 1);
+ tx_len += SDIO_BLOCK_SIZE;
+ }
+#endif /* CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES */
+
+ /* Check if not exceeding CW1200
+ capabilities */
+ if (WARN_ON_ONCE(
+ tx_len > EFFECTIVE_BUF_SIZE)) {
+ printk(KERN_DEBUG "Write aligned len:"
+ " %ld\n", tx_len);
+ }
+
+ wsm->id &= __cpu_to_le32(
+ ~WSM_TX_SEQ(WSM_TX_SEQ_MAX));
+ wsm->id |= cpu_to_le32(
+ WSM_TX_SEQ(priv->wsm_tx_seq));
+
+ if (WARN_ON(cw1200_data_write(priv,
+ data, tx_len))) {
+ wsm_release_tx_buffer(priv, 1);
+ break;
+ }
+
+#if defined(CONFIG_CW1200_WSM_DUMPS)
+ if (unlikely(priv->wsm_enable_wsm_dumps))
+ print_hex_dump_bytes("--> ",
+ DUMP_PREFIX_NONE,
+ data,
+ min_t(size_t,
+ __le32_to_cpu(wsm->len),
+ wsm_dump_max));
+#endif /* CONFIG_CW1200_WSM_DUMPS */
+
+ 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);
+ ++rx_burst;
+ goto tx;
+ }
+ }
+ }
+
+ if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
+ goto rx;
+ }
+
+ if (skb_rx) {
+ cw1200_put_skb(priv, skb_rx);
+ skb_rx = NULL;
+ }
+
+
+ if (!term) {
+ cw1200_dbg(CW1200_DBG_ERROR, "[BH] Fatal error, exitting.\n");
+#if defined(CONFIG_CW1200_DUMP_ON_ERROR)
+ BUG_ON(1);
+#endif /* CONFIG_CW1200_DUMP_ON_ERROR */
+ priv->bh_error = 1;
+ /* TODO: schedule_work(recovery) */
+#ifndef HAS_PUT_TASK_STRUCT
+ /* The only reason of having this stupid code here is
+ * that __put_task_struct is not exported by kernel. */
+ for (;;) {
+ int status = wait_event_interruptible(priv->bh_wq, ({
+ term = atomic_xchg(&priv->bh_term, 0);
+ (term);
+ }));
+
+ if (status || term)
+ break;
+ }
+#endif
+ }
+ return 0;
+}
diff --git a/drivers/net/wireless/cw1200/bh.h b/drivers/net/wireless/cw1200/bh.h
new file mode 100644
index 0000000..ea4598a
--- /dev/null
+++ b/drivers/net/wireless/cw1200/bh.h
@@ -0,0 +1,30 @@
+/*
+ * Device handling thread interface for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_BH_H
+#define CW1200_BH_H
+
+/* extern */ struct cw1200_common;
+
+#define SDIO_BLOCK_SIZE (512)
+
+int cw1200_register_bh(struct cw1200_common *priv);
+void cw1200_unregister_bh(struct cw1200_common *priv);
+void cw1200_irq_handler(struct cw1200_common *priv);
+void cw1200_bh_wakeup(struct cw1200_common *priv);
+int cw1200_bh_suspend(struct cw1200_common *priv);
+int cw1200_bh_resume(struct cw1200_common *priv);
+/* Must be called from BH thread. */
+void cw1200_enable_powersave(struct cw1200_common *priv,
+ bool enable);
+int wsm_release_tx_buffer(struct cw1200_common *priv, int count);
+
+#endif /* CW1200_BH_H */
--
1.7.9


2012-03-02 20:26:54

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 05/21] cw1200: hwio.*, device reg/mem map and low-level i/o primitives.

The patch contains cw1200 registry and memory map, as well as low-level
memory and regestry access primitives.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/hwio.c | 286 ++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/hwio.h | 243 ++++++++++++++++++++++++++++++
2 files changed, 529 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/hwio.c
create mode 100644 drivers/net/wireless/cw1200/hwio.h

diff --git a/drivers/net/wireless/cw1200/hwio.c b/drivers/net/wireless/cw1200/hwio.c
new file mode 100644
index 0000000..c0b63ac
--- /dev/null
+++ b/drivers/net/wireless/cw1200/hwio.c
@@ -0,0 +1,286 @@
+/*
+ * Low-level device IO routines for ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on:
+ * ST-Ericsson UMAC CW1200 driver, which is
+ * Copyright (c) 2010, ST-Ericsson AB
+ * Author: Ajitpal Singh <[email protected]>
+ *
+ * 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/types.h>
+
+#include "cw1200.h"
+#include "hwio.h"
+#include "sbus.h"
+
+ /* Sdio addr is 4*spi_addr */
+#define SPI_REG_ADDR_TO_SDIO(spi_reg_addr) ((spi_reg_addr) << 2)
+#define SDIO_ADDR17BIT(buf_id, mpf, rfu, reg_id_ofs) \
+ ((((buf_id) & 0x1F) << 7) \
+ | (((mpf) & 1) << 6) \
+ | (((rfu) & 1) << 5) \
+ | (((reg_id_ofs) & 0x1F) << 0))
+#define MAX_RETRY 3
+
+
+static int __cw1200_reg_read(struct cw1200_common *priv, u16 addr,
+ void *buf, size_t buf_len, int buf_id)
+{
+ u16 addr_sdio;
+ u32 sdio_reg_addr_17bit ;
+
+ /* Check if buffer is aligned to 4 byte boundary */
+ if (WARN_ON(((unsigned long)buf & 3) && (buf_len > 4))) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: buffer is not aligned.\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Convert to SDIO Register Address */
+ addr_sdio = SPI_REG_ADDR_TO_SDIO(addr);
+ sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio);
+
+ BUG_ON(!priv->sbus_ops);
+ return priv->sbus_ops->sbus_memcpy_fromio(priv->sbus_priv,
+ sdio_reg_addr_17bit,
+ buf, buf_len);
+}
+
+static int __cw1200_reg_write(struct cw1200_common *priv, u16 addr,
+ const void *buf, size_t buf_len, int buf_id)
+{
+ u16 addr_sdio;
+ u32 sdio_reg_addr_17bit ;
+
+#if 0
+ /* Check if buffer is aligned to 4 byte boundary */
+ if (WARN_ON(((unsigned long)buf & 3) && (buf_len > 4))) {
+ cw1200_dbg(CW1200_DBG_ERROR, "%s: buffer is not aligned.\n",
+ __func__);
+ return -EINVAL;
+ }
+#endif
+
+ /* Convert to SDIO Register Address */
+ addr_sdio = SPI_REG_ADDR_TO_SDIO(addr);
+ sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio);
+
+ BUG_ON(!priv->sbus_ops);
+ return priv->sbus_ops->sbus_memcpy_toio(priv->sbus_priv,
+ sdio_reg_addr_17bit,
+ buf, buf_len);
+}
+
+static inline int __cw1200_reg_read_32(struct cw1200_common *priv,
+ u16 addr, u32 *val)
+{
+ return __cw1200_reg_read(priv, addr, val, sizeof(val), 0);
+}
+
+static inline int __cw1200_reg_write_32(struct cw1200_common *priv,
+ u16 addr, u32 val)
+{
+ return __cw1200_reg_write(priv, addr, &val, sizeof(val), 0);
+}
+
+int cw1200_reg_read(struct cw1200_common *priv, u16 addr, void *buf,
+ size_t buf_len)
+{
+ int ret;
+ BUG_ON(!priv->sbus_ops);
+ priv->sbus_ops->lock(priv->sbus_priv);
+ ret = __cw1200_reg_read(priv, addr, buf, buf_len, 0);
+ priv->sbus_ops->unlock(priv->sbus_priv);
+ return ret;
+}
+
+int cw1200_reg_write(struct cw1200_common *priv, u16 addr, const void *buf,
+ size_t buf_len)
+{
+ int ret;
+ BUG_ON(!priv->sbus_ops);
+ priv->sbus_ops->lock(priv->sbus_priv);
+ ret = __cw1200_reg_write(priv, addr, buf, buf_len, 0);
+ priv->sbus_ops->unlock(priv->sbus_priv);
+ return ret;
+}
+
+int cw1200_data_read(struct cw1200_common *priv, void *buf, size_t buf_len)
+{
+ int ret, retry = 1;
+ BUG_ON(!priv->sbus_ops);
+ priv->sbus_ops->lock(priv->sbus_priv);
+ {
+ int buf_id_rx = priv->buf_id_rx;
+ while (retry <= MAX_RETRY) {
+ ret = __cw1200_reg_read(priv,
+ ST90TDS_IN_OUT_QUEUE_REG_ID, buf,
+ buf_len, buf_id_rx + 1);
+ if (!ret) {
+ buf_id_rx = (buf_id_rx + 1) & 3;
+ priv->buf_id_rx = buf_id_rx;
+ break;
+ } else {
+ retry++;
+ mdelay(1);
+ cw1200_dbg(CW1200_DBG_ERROR, "%s,error :[%d]\n",
+ __func__, ret);
+ }
+ }
+ }
+ priv->sbus_ops->unlock(priv->sbus_priv);
+ return ret;
+}
+
+int cw1200_data_write(struct cw1200_common *priv, const void *buf,
+ size_t buf_len)
+{
+ int ret, retry = 1;
+ BUG_ON(!priv->sbus_ops);
+ priv->sbus_ops->lock(priv->sbus_priv);
+ {
+ int buf_id_tx = priv->buf_id_tx;
+ while (retry <= MAX_RETRY) {
+ ret = __cw1200_reg_write(priv,
+ ST90TDS_IN_OUT_QUEUE_REG_ID, buf,
+ buf_len, buf_id_tx);
+ if (!ret) {
+ buf_id_tx = (buf_id_tx + 1) & 31;
+ priv->buf_id_tx = buf_id_tx;
+ break;
+ } else {
+ retry++;
+ mdelay(1);
+ cw1200_dbg(CW1200_DBG_ERROR, "%s,error :[%d]\n",
+ __func__, ret);
+ }
+ }
+ }
+ priv->sbus_ops->unlock(priv->sbus_priv);
+ return ret;
+}
+
+int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf,
+ size_t buf_len, u32 prefetch, u16 port_addr)
+{
+ u32 val32 = 0;
+ int i, ret;
+
+ if ((buf_len / 2) >= 0x1000) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't read more than 0xfff words.\n",
+ __func__);
+ WARN_ON(1);
+ return -EINVAL;
+ goto out;
+ }
+
+ priv->sbus_ops->lock(priv->sbus_priv);
+ /* Write address */
+ ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't write address register.\n",
+ __func__);
+ goto out;
+ }
+
+ /* Read CONFIG Register Value - We will read 32 bits */
+ ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't read config register.\n",
+ __func__);
+ goto out;
+ }
+
+ /* Set PREFETCH bit */
+ ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID,
+ val32 | prefetch);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't write prefetch bit.\n",
+ __func__);
+ goto out;
+ }
+
+ /* Check for PRE-FETCH bit to be cleared */
+ for (i = 0; i < 20; i++) {
+ ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't check prefetch bit.\n",
+ __func__);
+ goto out;
+ }
+ if (!(val32 & prefetch))
+ break;
+
+ mdelay(i);
+ }
+
+ if (val32 & prefetch) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Prefetch bit is not cleared.\n",
+ __func__);
+ goto out;
+ }
+
+ /* Read data port */
+ ret = __cw1200_reg_read(priv, port_addr, buf, buf_len, 0);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't read data port.\n",
+ __func__);
+ goto out;
+ }
+
+out:
+ priv->sbus_ops->unlock(priv->sbus_priv);
+ return ret;
+}
+
+int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf,
+ size_t buf_len)
+{
+ int ret;
+
+ if ((buf_len / 2) >= 0x1000) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't wrire more than 0xfff words.\n",
+ __func__);
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ priv->sbus_ops->lock(priv->sbus_priv);
+
+ /* Write address */
+ ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Can't write address register.\n",
+ __func__);
+ goto out;
+ }
+
+ /* Write data port */
+ ret = __cw1200_reg_write(priv, ST90TDS_SRAM_DPORT_REG_ID,
+ buf, buf_len, 0);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR, "%s: Can't write data port.\n",
+ __func__);
+ goto out;
+ }
+
+out:
+ priv->sbus_ops->unlock(priv->sbus_priv);
+ return ret;
+}
diff --git a/drivers/net/wireless/cw1200/hwio.h b/drivers/net/wireless/cw1200/hwio.h
new file mode 100644
index 0000000..25c8f6b
--- /dev/null
+++ b/drivers/net/wireless/cw1200/hwio.h
@@ -0,0 +1,243 @@
+/*
+ * Low-level API for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on:
+ * ST-Ericsson UMAC CW1200 driver which is
+ * Copyright (c) 2010, ST-Ericsson AB
+ * Author: Ajitpal Singh <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_HWIO_H_INCLUDED
+#define CW1200_HWIO_H_INCLUDED
+
+/* extern */ struct cw1200_common;
+
+/* DPLL initial values */
+#define DPLL_INIT_VAL_9000 (0x00000191)
+#define DPLL_INIT_VAL_CW1200 (0x0EC4F121)
+
+/* Hardware Type Definitions */
+#define HIF_8601_VERSATILE (0)
+#define HIF_8601_SILICON (1)
+#define HIF_9000_SILICON_VERSTAILE (2)
+
+#define CW1200_CUT_11_ID_STR (0x302E3830)
+#define CW1200_CUT_22_ID_STR1 (0x302e3132)
+#define CW1200_CUT_22_ID_STR2 (0x32302e30)
+#define CW1200_CUT_22_ID_STR3 (0x3335)
+#define CW1200_CUT_ID_ADDR (0xFFF17F90)
+#define CW1200_CUT2_ID_ADDR (0xFFF1FF90)
+
+/* Download control area */
+/* boot loader start address in SRAM */
+#define DOWNLOAD_BOOT_LOADER_OFFSET (0x00000000)
+/* 32K, 0x4000 to 0xDFFF */
+#define DOWNLOAD_FIFO_OFFSET (0x00004000)
+/* 32K */
+#define DOWNLOAD_FIFO_SIZE (0x00008000)
+/* 128 bytes, 0xFF80 to 0xFFFF */
+#define DOWNLOAD_CTRL_OFFSET (0x0000FF80)
+#define DOWNLOAD_CTRL_DATA_DWORDS (32-6)
+
+struct download_cntl_t {
+ /* size of whole firmware file (including Cheksum), host init */
+ u32 ImageSize;
+ /* downloading flags */
+ u32 Flags;
+ /* No. of bytes put into the download, init & updated by host */
+ u32 Put;
+ /* last traced program counter, last ARM reg_pc */
+ u32 TracePc;
+ /* No. of bytes read from the download, host init, device updates */
+ u32 Get;
+ /* r0, boot losader status, host init to pending, device updates */
+ u32 Status;
+ /* Extra debug info, r1 to r14 if status=r0=DOWNLOAD_EXCEPTION */
+ u32 DebugData[DOWNLOAD_CTRL_DATA_DWORDS];
+};
+
+#define DOWNLOAD_IMAGE_SIZE_REG \
+ (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, ImageSize))
+#define DOWNLOAD_FLAGS_REG \
+ (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Flags))
+#define DOWNLOAD_PUT_REG \
+ (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Put))
+#define DOWNLOAD_TRACE_PC_REG \
+ (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, TracePc))
+#define DOWNLOAD_GET_REG \
+ (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Get))
+#define DOWNLOAD_STATUS_REG \
+ (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, Status))
+#define DOWNLOAD_DEBUG_DATA_REG \
+ (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, DebugData))
+#define DOWNLOAD_DEBUG_DATA_LEN (108)
+
+#define DOWNLOAD_BLOCK_SIZE (1024)
+
+/* For boot loader detection */
+#define DOWNLOAD_ARE_YOU_HERE (0x87654321)
+#define DOWNLOAD_I_AM_HERE (0x12345678)
+
+/* Download error code */
+#define DOWNLOAD_PENDING (0xFFFFFFFF)
+#define DOWNLOAD_SUCCESS (0)
+#define DOWNLOAD_EXCEPTION (1)
+#define DOWNLOAD_ERR_MEM_1 (2)
+#define DOWNLOAD_ERR_MEM_2 (3)
+#define DOWNLOAD_ERR_SOFTWARE (4)
+#define DOWNLOAD_ERR_FILE_SIZE (5)
+#define DOWNLOAD_ERR_CHECKSUM (6)
+#define DOWNLOAD_ERR_OVERFLOW (7)
+#define DOWNLOAD_ERR_IMAGE (8)
+#define DOWNLOAD_ERR_HOST (9)
+#define DOWNLOAD_ERR_ABORT (10)
+
+
+#define SYS_BASE_ADDR_SILICON (0)
+#define PAC_BASE_ADDRESS_SILICON (SYS_BASE_ADDR_SILICON + 0x09000000)
+#define PAC_SHARED_MEMORY_SILICON (PAC_BASE_ADDRESS_SILICON)
+
+#define CW12000_APB(addr) (PAC_SHARED_MEMORY_SILICON + (addr))
+
+/* ***************************************************************
+*Device register definitions
+*************************************************************** */
+/* WBF - SPI Register Addresses */
+#define ST90TDS_ADDR_ID_BASE (0x0000)
+/* 16/32 bits */
+#define ST90TDS_CONFIG_REG_ID (0x0000)
+/* 16/32 bits */
+#define ST90TDS_CONTROL_REG_ID (0x0001)
+/* 16 bits, Q mode W/R */
+#define ST90TDS_IN_OUT_QUEUE_REG_ID (0x0002)
+/* 32 bits, AHB bus R/W */
+#define ST90TDS_AHB_DPORT_REG_ID (0x0003)
+/* 16/32 bits */
+#define ST90TDS_SRAM_BASE_ADDR_REG_ID (0x0004)
+/* 32 bits, APB bus R/W */
+#define ST90TDS_SRAM_DPORT_REG_ID (0x0005)
+/* 32 bits, t_settle/general */
+#define ST90TDS_TSET_GEN_R_W_REG_ID (0x0006)
+/* 16 bits, Q mode read, no length */
+#define ST90TDS_FRAME_OUT_REG_ID (0x0007)
+#define ST90TDS_ADDR_ID_MAX (ST90TDS_FRAME_OUT_REG_ID)
+
+/* WBF - Control register bit set */
+/* next o/p length, bit 11 to 0 */
+#define ST90TDS_CONT_NEXT_LEN_MASK (0x0FFF)
+#define ST90TDS_CONT_WUP_BIT (BIT(12))
+#define ST90TDS_CONT_RDY_BIT (BIT(13))
+#define ST90TDS_CONT_IRQ_ENABLE (BIT(14))
+#define ST90TDS_CONT_RDY_ENABLE (BIT(15))
+#define ST90TDS_CONT_IRQ_RDY_ENABLE (BIT(14)|BIT(15))
+
+/* SPI Config register bit set */
+#define ST90TDS_CONFIG_FRAME_BIT (BIT(2))
+#define ST90TDS_CONFIG_WORD_MODE_BITS (BIT(3)|BIT(4))
+#define ST90TDS_CONFIG_WORD_MODE_1 (BIT(3))
+#define ST90TDS_CONFIG_WORD_MODE_2 (BIT(4))
+#define ST90TDS_CONFIG_ERROR_0_BIT (BIT(5))
+#define ST90TDS_CONFIG_ERROR_1_BIT (BIT(6))
+#define ST90TDS_CONFIG_ERROR_2_BIT (BIT(7))
+/* TBD: Sure??? */
+#define ST90TDS_CONFIG_CSN_FRAME_BIT (BIT(7))
+#define ST90TDS_CONFIG_ERROR_3_BIT (BIT(8))
+#define ST90TDS_CONFIG_ERROR_4_BIT (BIT(9))
+/* QueueM */
+#define ST90TDS_CONFIG_ACCESS_MODE_BIT (BIT(10))
+/* AHB bus */
+#define ST90TDS_CONFIG_AHB_PFETCH_BIT (BIT(11))
+#define ST90TDS_CONFIG_CPU_CLK_DIS_BIT (BIT(12))
+/* APB bus */
+#define ST90TDS_CONFIG_PFETCH_BIT (BIT(13))
+/* cpu reset */
+#define ST90TDS_CONFIG_CPU_RESET_BIT (BIT(14))
+#define ST90TDS_CONFIG_CLEAR_INT_BIT (BIT(15))
+
+/* For CW1200 the IRQ Enable and Ready Bits are in CONFIG register */
+#define ST90TDS_CONF_IRQ_RDY_ENABLE (BIT(16)|BIT(17))
+
+int cw1200_data_read(struct cw1200_common *priv,
+ void *buf, size_t buf_len);
+int cw1200_data_write(struct cw1200_common *priv,
+ const void *buf, size_t buf_len);
+
+int cw1200_reg_read(struct cw1200_common *priv, u16 addr,
+ void *buf, size_t buf_len);
+int cw1200_reg_write(struct cw1200_common *priv, u16 addr,
+ const void *buf, size_t buf_len);
+
+static inline int cw1200_reg_read_16(struct cw1200_common *priv,
+ u16 addr, u16 *val)
+{
+ u32 bigVal;
+ int ret;
+ ret = cw1200_reg_read(priv, addr, &bigVal, sizeof(bigVal));
+ *val = (u16)bigVal;
+ return ret;
+}
+
+static inline int cw1200_reg_write_16(struct cw1200_common *priv,
+ u16 addr, u16 val)
+{
+ u32 bigVal = (u32)val;
+ return cw1200_reg_write(priv, addr, &bigVal, sizeof(bigVal));
+}
+
+static inline int cw1200_reg_read_32(struct cw1200_common *priv,
+ u16 addr, u32 *val)
+{
+ return cw1200_reg_read(priv, addr, val, sizeof(val));
+}
+
+static inline int cw1200_reg_write_32(struct cw1200_common *priv,
+ u16 addr, u32 val)
+{
+ return cw1200_reg_write(priv, addr, &val, sizeof(val));
+}
+
+int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf,
+ size_t buf_len, u32 prefetch, u16 port_addr);
+int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf,
+ size_t buf_len);
+
+static inline int cw1200_apb_read(struct cw1200_common *priv, u32 addr,
+ void *buf, size_t buf_len)
+{
+ return cw1200_indirect_read(priv, addr, buf, buf_len,
+ ST90TDS_CONFIG_PFETCH_BIT, ST90TDS_SRAM_DPORT_REG_ID);
+}
+
+static inline int cw1200_ahb_read(struct cw1200_common *priv, u32 addr,
+ void *buf, size_t buf_len)
+{
+ return cw1200_indirect_read(priv, addr, buf, buf_len,
+ ST90TDS_CONFIG_AHB_PFETCH_BIT, ST90TDS_AHB_DPORT_REG_ID);
+}
+
+static inline int cw1200_apb_read_32(struct cw1200_common *priv,
+ u32 addr, u32 *val)
+{
+ return cw1200_apb_read(priv, addr, val, sizeof(val));
+}
+
+static inline int cw1200_apb_write_32(struct cw1200_common *priv,
+ u32 addr, u32 val)
+{
+ return cw1200_apb_write(priv, addr, &val, sizeof(val));
+}
+
+static inline int cw1200_ahb_read_32(struct cw1200_common *priv,
+ u32 addr, u32 *val)
+{
+ return cw1200_ahb_read(priv, addr, val, sizeof(val));
+}
+
+#endif /* CW1200_HWIO_H_INCLUDED */
--
1.7.9


2012-03-02 20:26:54

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 07/21] cw1200: queue.*, implementation of TX queues of the cw1200 driver.

The code implements cw1200 TX queues, used for holding TX frames pending
transmission. The code supports garbage collecting and accounting of the frames.

Note that link_id_map here and in the rest of the code is used to track
(ieee80211) power management state of connected stations in AP mode.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/queue.c | 584 +++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/queue.h | 116 +++++++
2 files changed, 700 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/queue.c
create mode 100644 drivers/net/wireless/cw1200/queue.h

diff --git a/drivers/net/wireless/cw1200/queue.c b/drivers/net/wireless/cw1200/queue.c
new file mode 100644
index 0000000..014e6b2
--- /dev/null
+++ b/drivers/net/wireless/cw1200/queue.c
@@ -0,0 +1,584 @@
+/*
+ * O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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 <net/mac80211.h>
+#include <linux/sched.h>
+#include "queue.h"
+#include "cw1200.h"
+#include "debug.h"
+
+/* private */ struct cw1200_queue_item
+{
+ struct list_head head;
+ struct sk_buff *skb;
+ u32 packetID;
+ unsigned long queue_timestamp;
+ unsigned long xmit_timestamp;
+ struct cw1200_txpriv txpriv;
+ u8 generation;
+};
+
+static inline void __cw1200_queue_lock(struct cw1200_queue *queue)
+{
+ struct cw1200_queue_stats *stats = queue->stats;
+ if (queue->tx_locked_cnt++ == 0) {
+ txrx_printk(KERN_DEBUG "[TX] Queue %d is locked.\n",
+ queue->queue_id);
+ ieee80211_stop_queue(stats->priv->hw, queue->queue_id);
+ }
+}
+
+static inline void __cw1200_queue_unlock(struct cw1200_queue *queue)
+{
+ struct cw1200_queue_stats *stats = queue->stats;
+ BUG_ON(!queue->tx_locked_cnt);
+ if (--queue->tx_locked_cnt == 0) {
+ txrx_printk(KERN_DEBUG "[TX] Queue %d is unlocked.\n",
+ queue->queue_id);
+ ieee80211_wake_queue(stats->priv->hw, queue->queue_id);
+ }
+}
+
+static inline void cw1200_queue_parse_id(u32 packetID, u8 *queue_generation,
+ u8 *queue_id,
+ u8 *item_generation,
+ u8 *item_id)
+{
+ *item_id = (packetID >> 0) & 0xFF;
+ *item_generation = (packetID >> 8) & 0xFF;
+ *queue_id = (packetID >> 16) & 0xFF;
+ *queue_generation = (packetID >> 24) & 0xFF;
+}
+
+static inline u32 cw1200_queue_make_packet_id(u8 queue_generation, u8 queue_id,
+ u8 item_generation, u8 item_id)
+{
+ return ((u32)item_id << 0) |
+ ((u32)item_generation << 8) |
+ ((u32)queue_id << 16) |
+ ((u32)queue_generation << 24);
+}
+
+static void cw1200_queue_post_gc(struct cw1200_queue_stats *stats,
+ struct list_head *gc_list)
+{
+ struct cw1200_queue_item *item;
+
+ while (!list_empty(gc_list)) {
+ item = list_first_entry(
+ gc_list, struct cw1200_queue_item, head);
+ list_del(&item->head);
+ stats->skb_dtor(stats->priv, item->skb, &item->txpriv);
+ kfree(item);
+ }
+}
+
+static void cw1200_queue_register_post_gc(struct list_head *gc_list,
+ struct cw1200_queue_item *item)
+{
+ struct cw1200_queue_item *gc_item;
+ gc_item = kmalloc(sizeof(struct cw1200_queue_item),
+ GFP_ATOMIC);
+ BUG_ON(!gc_item);
+ memcpy(gc_item, item, sizeof(struct cw1200_queue_item));
+ list_add_tail(&gc_item->head, gc_list);
+}
+
+static void __cw1200_queue_gc(struct cw1200_queue *queue,
+ struct list_head *head,
+ bool unlock)
+{
+ struct cw1200_queue_stats *stats = queue->stats;
+ struct cw1200_queue_item *item = NULL;
+ bool wakeup_stats = false;
+
+ while (!list_empty(&queue->queue)) {
+ item = list_first_entry(
+ &queue->queue, struct cw1200_queue_item, head);
+ if (jiffies - item->queue_timestamp < queue->ttl)
+ break;
+ --queue->num_queued;
+ --queue->link_map_cache[item->txpriv.link_id];
+ spin_lock_bh(&stats->lock);
+ --stats->num_queued;
+ if (!--stats->link_map_cache[item->txpriv.link_id])
+ wakeup_stats = true;
+ spin_unlock_bh(&stats->lock);
+ cw1200_debug_tx_ttl(stats->priv);
+ cw1200_queue_register_post_gc(head, item);
+ item->skb = NULL;
+ list_move_tail(&item->head, &queue->free_pool);
+ }
+
+ if (wakeup_stats)
+ wake_up(&stats->wait_link_id_empty);
+
+ if (queue->overfull) {
+ if (queue->num_queued <= (queue->capacity >> 1)) {
+ queue->overfull = false;
+ if (unlock)
+ __cw1200_queue_unlock(queue);
+ } else {
+ unsigned long tmo = item->queue_timestamp + queue->ttl;
+ mod_timer(&queue->gc, tmo);
+ cw1200_pm_stay_awake(&stats->priv->pm_state,
+ tmo - jiffies);
+ }
+ }
+}
+
+static void cw1200_queue_gc(unsigned long arg)
+{
+ LIST_HEAD(list);
+ struct cw1200_queue *queue =
+ (struct cw1200_queue *)arg;
+
+ spin_lock_bh(&queue->lock);
+ __cw1200_queue_gc(queue, &list, true);
+ spin_unlock_bh(&queue->lock);
+ cw1200_queue_post_gc(queue->stats, &list);
+}
+
+int cw1200_queue_stats_init(struct cw1200_queue_stats *stats,
+ size_t map_capacity,
+ cw1200_queue_skb_dtor_t skb_dtor,
+ struct cw1200_common *priv)
+{
+ memset(stats, 0, sizeof(*stats));
+ stats->map_capacity = map_capacity;
+ stats->skb_dtor = skb_dtor;
+ stats->priv = priv;
+ spin_lock_init(&stats->lock);
+ init_waitqueue_head(&stats->wait_link_id_empty);
+
+ stats->link_map_cache = kzalloc(sizeof(int[map_capacity]),
+ GFP_KERNEL);
+ if (!stats->link_map_cache)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int cw1200_queue_init(struct cw1200_queue *queue,
+ struct cw1200_queue_stats *stats,
+ u8 queue_id,
+ size_t capacity,
+ unsigned long ttl)
+{
+ size_t i;
+
+ memset(queue, 0, sizeof(*queue));
+ queue->stats = stats;
+ queue->capacity = capacity;
+ queue->queue_id = queue_id;
+ queue->ttl = ttl;
+ INIT_LIST_HEAD(&queue->queue);
+ INIT_LIST_HEAD(&queue->pending);
+ INIT_LIST_HEAD(&queue->free_pool);
+ spin_lock_init(&queue->lock);
+ init_timer(&queue->gc);
+ queue->gc.data = (unsigned long)queue;
+ queue->gc.function = cw1200_queue_gc;
+
+ queue->pool = kzalloc(sizeof(struct cw1200_queue_item) * capacity,
+ GFP_KERNEL);
+ if (!queue->pool)
+ return -ENOMEM;
+
+ queue->link_map_cache = kzalloc(sizeof(int[stats->map_capacity]),
+ GFP_KERNEL);
+ if (!queue->link_map_cache) {
+ kfree(queue->pool);
+ queue->pool = NULL;
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < capacity; ++i)
+ list_add_tail(&queue->pool[i].head, &queue->free_pool);
+
+ return 0;
+}
+
+int cw1200_queue_clear(struct cw1200_queue *queue)
+{
+ int i;
+ LIST_HEAD(gc_list);
+ struct cw1200_queue_stats *stats = queue->stats;
+
+ spin_lock_bh(&queue->lock);
+ queue->generation++;
+ list_splice_tail_init(&queue->queue, &queue->pending);
+ while (!list_empty(&queue->pending)) {
+ struct cw1200_queue_item *item = list_first_entry(
+ &queue->pending, struct cw1200_queue_item, head);
+ WARN_ON(!item->skb);
+ cw1200_queue_register_post_gc(&gc_list, item);
+ item->skb = NULL;
+ list_move_tail(&item->head, &queue->free_pool);
+ }
+ queue->num_queued = 0;
+ queue->num_pending = 0;
+
+ spin_lock_bh(&stats->lock);
+ for (i = 0; i < stats->map_capacity; ++i) {
+ stats->num_queued -= queue->link_map_cache[i];
+ stats->link_map_cache[i] -= queue->link_map_cache[i];
+ queue->link_map_cache[i] = 0;
+ }
+ spin_unlock_bh(&stats->lock);
+ if (unlikely(queue->overfull)) {
+ queue->overfull = false;
+ __cw1200_queue_unlock(queue);
+ }
+ spin_unlock_bh(&queue->lock);
+ wake_up(&stats->wait_link_id_empty);
+ cw1200_queue_post_gc(stats, &gc_list);
+ return 0;
+}
+
+void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats)
+{
+ kfree(stats->link_map_cache);
+ stats->link_map_cache = NULL;
+}
+
+void cw1200_queue_deinit(struct cw1200_queue *queue)
+{
+ cw1200_queue_clear(queue);
+ del_timer_sync(&queue->gc);
+ INIT_LIST_HEAD(&queue->free_pool);
+ kfree(queue->pool);
+ kfree(queue->link_map_cache);
+ queue->pool = NULL;
+ queue->link_map_cache = NULL;
+ queue->capacity = 0;
+}
+
+size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue,
+ u32 link_id_map)
+{
+ size_t ret;
+ int i, bit;
+ size_t map_capacity = queue->stats->map_capacity;
+
+ if (!link_id_map)
+ return 0;
+
+ spin_lock_bh(&queue->lock);
+ if (likely(link_id_map == (u32) -1))
+ ret = queue->num_queued - queue->num_pending;
+ else {
+ ret = 0;
+ for (i = 0, bit = 1; i < map_capacity; ++i, bit <<= 1) {
+ if (link_id_map & bit)
+ ret += queue->link_map_cache[i];
+ }
+ }
+ spin_unlock_bh(&queue->lock);
+ return ret;
+}
+
+int cw1200_queue_put(struct cw1200_queue *queue,
+ struct sk_buff *skb,
+ struct cw1200_txpriv *txpriv)
+{
+ int ret = 0;
+ LIST_HEAD(gc_list);
+ struct cw1200_queue_stats *stats = queue->stats;
+
+ if (txpriv->link_id >= queue->stats->map_capacity)
+ return -EINVAL;
+
+ spin_lock_bh(&queue->lock);
+ if (!WARN_ON(list_empty(&queue->free_pool))) {
+ struct cw1200_queue_item *item = list_first_entry(
+ &queue->free_pool, struct cw1200_queue_item, head);
+ BUG_ON(item->skb);
+
+ list_move_tail(&item->head, &queue->queue);
+ item->skb = skb;
+ item->txpriv = *txpriv;
+ item->generation = 0;
+ item->packetID = cw1200_queue_make_packet_id(
+ queue->generation, queue->queue_id,
+ item->generation, item - queue->pool);
+ item->queue_timestamp = jiffies;
+
+ ++queue->num_queued;
+ ++queue->link_map_cache[txpriv->link_id];
+
+ spin_lock_bh(&stats->lock);
+ ++stats->num_queued;
+ ++stats->link_map_cache[txpriv->link_id];
+ spin_unlock_bh(&stats->lock);
+
+ /*
+ * TX may happen in parallel sometimes.
+ * Leave extra queue slots so we don't overflow.
+ */
+ if (queue->overfull == false &&
+ queue->num_queued >=
+ (queue->capacity - (num_present_cpus() - 1))) {
+ queue->overfull = true;
+ __cw1200_queue_lock(queue);
+ mod_timer(&queue->gc, jiffies);
+ }
+ } else {
+ ret = -ENOENT;
+ }
+ spin_unlock_bh(&queue->lock);
+ return ret;
+}
+
+int cw1200_queue_get(struct cw1200_queue *queue,
+ u32 link_id_map,
+ struct wsm_tx **tx,
+ struct ieee80211_tx_info **tx_info,
+ const struct cw1200_txpriv **txpriv)
+{
+ int ret = -ENOENT;
+ struct cw1200_queue_item *item;
+ struct cw1200_queue_stats *stats = queue->stats;
+ bool wakeup_stats = false;
+
+ spin_lock_bh(&queue->lock);
+ list_for_each_entry(item, &queue->queue, head) {
+ if (link_id_map & BIT(item->txpriv.link_id)) {
+ ret = 0;
+ break;
+ }
+ }
+
+ if (!WARN_ON(ret)) {
+ *tx = (struct wsm_tx *)item->skb->data;
+ *tx_info = IEEE80211_SKB_CB(item->skb);
+ *txpriv = &item->txpriv;
+ (*tx)->packetID = __cpu_to_le32(item->packetID);
+ list_move_tail(&item->head, &queue->pending);
+ ++queue->num_pending;
+ --queue->link_map_cache[item->txpriv.link_id];
+ item->xmit_timestamp = jiffies;
+
+ spin_lock_bh(&stats->lock);
+ --stats->num_queued;
+ if (!--stats->link_map_cache[item->txpriv.link_id])
+ wakeup_stats = true;
+ spin_unlock_bh(&stats->lock);
+ }
+ spin_unlock_bh(&queue->lock);
+ if (wakeup_stats)
+ wake_up(&stats->wait_link_id_empty);
+ return ret;
+}
+
+int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packetID)
+{
+ int ret = 0;
+ u8 queue_generation, queue_id, item_generation, item_id;
+ struct cw1200_queue_item *item;
+ struct cw1200_queue_stats *stats = queue->stats;
+
+ cw1200_queue_parse_id(packetID, &queue_generation, &queue_id,
+ &item_generation, &item_id);
+
+ item = &queue->pool[item_id];
+
+ spin_lock_bh(&queue->lock);
+ BUG_ON(queue_id != queue->queue_id);
+ if (unlikely(queue_generation != queue->generation)) {
+ ret = -ENOENT;
+ } else if (unlikely(item_id >= (unsigned) queue->capacity)) {
+ WARN_ON(1);
+ ret = -EINVAL;
+ } else if (unlikely(item->generation != item_generation)) {
+ WARN_ON(1);
+ ret = -ENOENT;
+ } else {
+ --queue->num_pending;
+ ++queue->link_map_cache[item->txpriv.link_id];
+
+ spin_lock_bh(&stats->lock);
+ ++stats->num_queued;
+ ++stats->link_map_cache[item->txpriv.link_id];
+ spin_unlock_bh(&stats->lock);
+
+ item->generation = ++item_generation;
+ item->packetID = cw1200_queue_make_packet_id(
+ queue_generation, queue_id, item_generation, item_id);
+ list_move(&item->head, &queue->queue);
+ }
+ spin_unlock_bh(&queue->lock);
+ return ret;
+}
+
+int cw1200_queue_requeue_all(struct cw1200_queue *queue)
+{
+ struct cw1200_queue_stats *stats = queue->stats;
+ spin_lock_bh(&queue->lock);
+ while (!list_empty(&queue->pending)) {
+ struct cw1200_queue_item *item = list_entry(
+ queue->pending.prev, struct cw1200_queue_item, head);
+
+ --queue->num_pending;
+ ++queue->link_map_cache[item->txpriv.link_id];
+
+ spin_lock_bh(&stats->lock);
+ ++stats->num_queued;
+ ++stats->link_map_cache[item->txpriv.link_id];
+ spin_unlock_bh(&stats->lock);
+
+ ++item->generation;
+ item->packetID = cw1200_queue_make_packet_id(
+ queue->generation, queue->queue_id,
+ item->generation, item - queue->pool);
+ list_move(&item->head, &queue->queue);
+ }
+ spin_unlock_bh(&queue->lock);
+
+ return 0;
+}
+
+int cw1200_queue_remove(struct cw1200_queue *queue, u32 packetID)
+{
+ int ret = 0;
+ u8 queue_generation, queue_id, item_generation, item_id;
+ struct cw1200_queue_item *item;
+ struct cw1200_queue_stats *stats = queue->stats;
+ struct sk_buff *gc_skb = NULL;
+ struct cw1200_txpriv gc_txpriv;
+
+ cw1200_queue_parse_id(packetID, &queue_generation, &queue_id,
+ &item_generation, &item_id);
+
+ item = &queue->pool[item_id];
+
+ spin_lock_bh(&queue->lock);
+ BUG_ON(queue_id != queue->queue_id);
+ if (unlikely(queue_generation != queue->generation)) {
+ ret = -ENOENT;
+ } else if (unlikely(item_id >= (unsigned) queue->capacity)) {
+ WARN_ON(1);
+ ret = -EINVAL;
+ } else if (unlikely(item->generation != item_generation)) {
+ WARN_ON(1);
+ ret = -ENOENT;
+ } else {
+ gc_txpriv = item->txpriv;
+ gc_skb = item->skb;
+ item->skb = NULL;
+ --queue->num_pending;
+ --queue->num_queued;
+ ++queue->num_sent;
+ ++item->generation;
+ /* Do not use list_move_tail here, but list_move:
+ * try to utilize cache row.
+ */
+ list_move(&item->head, &queue->free_pool);
+
+ if (unlikely(queue->overfull) &&
+ (queue->num_queued <= (queue->capacity >> 1))) {
+ queue->overfull = false;
+ __cw1200_queue_unlock(queue);
+ }
+ }
+ spin_unlock_bh(&queue->lock);
+
+ if (gc_skb)
+ stats->skb_dtor(stats->priv, gc_skb, &gc_txpriv);
+
+ return ret;
+}
+
+int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packetID,
+ struct sk_buff **skb,
+ const struct cw1200_txpriv **txpriv)
+{
+ int ret = 0;
+ u8 queue_generation, queue_id, item_generation, item_id;
+ struct cw1200_queue_item *item;
+ cw1200_queue_parse_id(packetID, &queue_generation, &queue_id,
+ &item_generation, &item_id);
+
+ item = &queue->pool[item_id];
+
+ spin_lock_bh(&queue->lock);
+ BUG_ON(queue_id != queue->queue_id);
+ if (unlikely(queue_generation != queue->generation)) {
+ ret = -ENOENT;
+ } else if (unlikely(item_id >= (unsigned) queue->capacity)) {
+ WARN_ON(1);
+ ret = -EINVAL;
+ } else if (unlikely(item->generation != item_generation)) {
+ WARN_ON(1);
+ ret = -ENOENT;
+ } else {
+ *skb = item->skb;
+ *txpriv = &item->txpriv;
+ }
+ spin_unlock_bh(&queue->lock);
+ return ret;
+}
+
+void cw1200_queue_lock(struct cw1200_queue *queue)
+{
+ spin_lock_bh(&queue->lock);
+ __cw1200_queue_lock(queue);
+ spin_unlock_bh(&queue->lock);
+}
+
+void cw1200_queue_unlock(struct cw1200_queue *queue)
+{
+ spin_lock_bh(&queue->lock);
+ __cw1200_queue_unlock(queue);
+ spin_unlock_bh(&queue->lock);
+}
+
+bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue,
+ unsigned long *timestamp)
+{
+ struct cw1200_queue_item *item;
+ bool ret;
+
+ spin_lock_bh(&queue->lock);
+ ret = !list_empty(&queue->pending);
+ if (ret) {
+ list_for_each_entry(item, &queue->pending, head) {
+ if (time_before(item->xmit_timestamp, *timestamp))
+ *timestamp = item->xmit_timestamp;
+ }
+ }
+ spin_unlock_bh(&queue->lock);
+ return ret;
+}
+
+bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats,
+ u32 link_id_map)
+{
+ bool empty = true;
+
+ spin_lock_bh(&stats->lock);
+ if (link_id_map == (u32)-1)
+ empty = stats->num_queued == 0;
+ else {
+ int i;
+ for (i = 0; i < stats->map_capacity; ++i) {
+ if (link_id_map & BIT(i)) {
+ if (stats->link_map_cache[i]) {
+ empty = false;
+ break;
+ }
+ }
+ }
+ }
+ spin_unlock_bh(&stats->lock);
+
+ return empty;
+}
diff --git a/drivers/net/wireless/cw1200/queue.h b/drivers/net/wireless/cw1200/queue.h
new file mode 100644
index 0000000..aa9a7f9
--- /dev/null
+++ b/drivers/net/wireless/cw1200/queue.h
@@ -0,0 +1,116 @@
+/*
+ * O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_QUEUE_H_INCLUDED
+#define CW1200_QUEUE_H_INCLUDED
+
+/* private */ struct cw1200_queue_item;
+
+/* extern */ struct sk_buff;
+/* extern */ struct wsm_tx;
+/* extern */ struct cw1200_common;
+/* extern */ struct ieee80211_tx_queue_stats;
+/* extern */ struct cw1200_txpriv;
+
+/* forward */ struct cw1200_queue_stats;
+
+typedef void (*cw1200_queue_skb_dtor_t)(struct cw1200_common *priv,
+ struct sk_buff *skb,
+ const struct cw1200_txpriv *txpriv);
+
+struct cw1200_queue {
+ struct cw1200_queue_stats *stats;
+ size_t capacity;
+ size_t num_queued;
+ size_t num_pending;
+ size_t num_sent;
+ struct cw1200_queue_item *pool;
+ struct list_head queue;
+ struct list_head free_pool;
+ struct list_head pending;
+ int tx_locked_cnt;
+ int *link_map_cache;
+ bool overfull;
+ spinlock_t lock;
+ u8 queue_id;
+ u8 generation;
+ struct timer_list gc;
+ unsigned long ttl;
+};
+
+struct cw1200_queue_stats {
+ spinlock_t lock;
+ int *link_map_cache;
+ int num_queued;
+ size_t map_capacity;
+ wait_queue_head_t wait_link_id_empty;
+ cw1200_queue_skb_dtor_t skb_dtor;
+ struct cw1200_common *priv;
+};
+
+struct cw1200_txpriv {
+ u8 link_id;
+ u8 raw_link_id;
+ u8 tid;
+ u8 rate_id;
+ u8 offset;
+};
+
+int cw1200_queue_stats_init(struct cw1200_queue_stats *stats,
+ size_t map_capacity,
+ cw1200_queue_skb_dtor_t skb_dtor,
+ struct cw1200_common *priv);
+int cw1200_queue_init(struct cw1200_queue *queue,
+ struct cw1200_queue_stats *stats,
+ u8 queue_id,
+ size_t capacity,
+ unsigned long ttl);
+int cw1200_queue_clear(struct cw1200_queue *queue);
+void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats);
+void cw1200_queue_deinit(struct cw1200_queue *queue);
+
+size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue,
+ u32 link_id_map);
+int cw1200_queue_put(struct cw1200_queue *queue,
+ struct sk_buff *skb,
+ struct cw1200_txpriv *txpriv);
+int cw1200_queue_get(struct cw1200_queue *queue,
+ u32 link_id_map,
+ struct wsm_tx **tx,
+ struct ieee80211_tx_info **tx_info,
+ const struct cw1200_txpriv **txpriv);
+int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packetID);
+int cw1200_queue_requeue_all(struct cw1200_queue *queue);
+int cw1200_queue_remove(struct cw1200_queue *queue,
+ u32 packetID);
+int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packetID,
+ struct sk_buff **skb,
+ const struct cw1200_txpriv **txpriv);
+void cw1200_queue_lock(struct cw1200_queue *queue);
+void cw1200_queue_unlock(struct cw1200_queue *queue);
+bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue,
+ unsigned long *timestamp);
+
+
+bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats,
+ u32 link_id_map);
+
+static inline u8 cw1200_queue_get_queue_id(u32 packetID)
+{
+ return (packetID >> 16) & 0xFF;
+}
+
+static inline u8 cw1200_queue_get_generation(u32 packetID)
+{
+ return (packetID >> 8) & 0xFF;
+}
+
+#endif /* CW1200_QUEUE_H_INCLUDED */
--
1.7.9


2012-03-02 20:27:09

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 21/21] cw1200: Kconfig + Makefile for the driver.

The patch contains build system files for the cw1200 driver.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/Kconfig | 1 +
drivers/net/wireless/Makefile | 2 +
drivers/net/wireless/cw1200/Kconfig | 203 ++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/Makefile | 19 +++
4 files changed, 225 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/Kconfig
create mode 100644 drivers/net/wireless/cw1200/Makefile

diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index abd3b71..6fcda14 100644
--- a/drivers/net/wireless/Kconfig
+++ b/drivers/net/wireless/Kconfig
@@ -286,5 +286,6 @@ source "drivers/net/wireless/wl1251/Kconfig"
source "drivers/net/wireless/wl12xx/Kconfig"
source "drivers/net/wireless/zd1211rw/Kconfig"
source "drivers/net/wireless/mwifiex/Kconfig"
+source "drivers/net/wireless/cw1200/Kconfig"

endif # WLAN
diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile
index 98db761..d57fb4b 100644
--- a/drivers/net/wireless/Makefile
+++ b/drivers/net/wireless/Makefile
@@ -61,3 +61,5 @@ obj-$(CONFIG_MWIFIEX) += mwifiex/

obj-$(CONFIG_BRCMFMAC) += brcm80211/
obj-$(CONFIG_BRCMSMAC) += brcm80211/
+
+obj-$(CONFIG_CW1200) += cw1200/
diff --git a/drivers/net/wireless/cw1200/Kconfig b/drivers/net/wireless/cw1200/Kconfig
new file mode 100644
index 0000000..43b86dd
--- /dev/null
+++ b/drivers/net/wireless/cw1200/Kconfig
@@ -0,0 +1,203 @@
+menuconfig CW1200
+ tristate "ST-Ericsson cw1200 WiFi driver"
+ select MAC80211
+ select CFG80211
+ ---help---
+ This is a mac80211 driver for the ST-Ericsson cw1200 chip-set.
+ The driver supports also cw1100 chipset.
+
+ If you enable this option, two modules will be built: cw1200_core
+ module with core cw1200 support and cw1200_wlan, an SDIO glue layer
+ for the driver.
+
+config CW1200_STANDALONE
+ bool "Build a standalone cw1200 driver"
+ depends on CW1200
+ ---help---
+ Usually it is not enough just to build the driver modules, you need
+ to provide platform-specific data for the driver from your mach- code
+ (unless you device does not require any power, clock or reset control).
+
+ Say Y here if you would like to build a standalone cw1200 driver,
+ not associated with any platform. Likely you will not be able to use
+ it for any purposes.
+
+ If unsure, say N.
+
+config CW1200_USE_GPIO_IRQ
+ bool "Use GPIO interrupt"
+ depends on CW1200 && !CW1200_STANDALONE
+ ---help---
+ Say Y here if you want to include GPIO IRQ support instead of SDIO IRQ.
+
+ GPIO IRQ signalling is significantly faster than SDIO, but it requires
+ a dedicated GPIO line to be connected to the cw1200 device. It is safe
+ to use SDIO IRQ even if GPIO is connected, but it will lead to some
+ performance penalty.
+
+ Please refer to your hardware design to find out if SDIO IRQ is
+ supported on your platform.
+
+ If unsure, say N.
+
+config CW1200_U5500_SUPPORT
+ bool "Enable U5500 support"
+ depends on CW1200 && !CW1200_STANDALONE
+ ---help---
+ Say Y if you want to enable wlan support on u5500 STE platform.
+
+ u5500 uses a bit different method of enabling power end resetting of
+ the chip. The code under this configuration option uses a prcmu_ctrl
+ callback in the device platform data structure to enable or disable
+ power.
+
+ You can also create a custom prcmu_ctrl callback and use this option
+ if the generic power on / power off sequence does not fit your system
+ design.
+
+ If unsure, say N.
+
+config CW1200_NON_POWER_OF_TWO_BLOCKSIZES
+ bool "Platform supports non-power-of-two SDIO transfer"
+ depends on CW1200
+ ---help---
+ Say N here only if you are running the driver on a platform
+ which does not have support for non-power-of-two SDIO transfer.
+
+ Disabling this option is safe, but it will lead to some performance
+ penalty.
+
+ If unsure, say Y.
+
+config CW1200_5GHZ_SUPPORT
+ bool "5GHz band support"
+ depends on CW1200
+ ---help---
+ Say Y if your device supports 5GHz band. Usually a system based on cw1200
+ device supports 5GHz band, while a cw1100-based system does not. But it
+ might be some variations, please check your system design.
+
+ If unsure, say N.
+
+config CW1200_CERTIFICATION_TRICKS
+ bool "Enable WiFi certification tricks"
+ depends on CW1200
+ ---help---
+ Say Y here if you would like to build a system which can be WiFi-certified.
+ It is mostly related to WFD test case 6.1.10 of the WiFi Direct test plan,
+ but not only. It is safe to have this option enabled. Impact to the system
+ performance is minimal.
+
+ If unsure, say Y.
+
+config CW1200_DISABLE_BEACON_HINTS
+ bool "Disable 11d beacon hints"
+ depends on CW1200
+ ---help---
+ Say Y if you want to disable 11d beacon hints. Usually you don't need to
+ do it, because they are used for 11d support in the mac80211 code.
+
+ However, in some variants of design you might know for sure the regulatory
+ domain you are residing in. In that rare case using this option you can
+ disable unnecessary AI. One of examples of such kind of design is a mobile
+ phone, where positioning information is available directly from the mobile
+ network, and this information takes precedence over 11d hints.
+
+ If unsure, say N.
+
+config CW1200_DEBUGFS
+ bool "Expose driver internals to DebugFS (DEVELOPMENT)"
+ depends on CW1200
+ ---help---
+ Say Y if you want to expose driver internals to DebugFS.
+
+ If unsure, say N.
+
+config CW1200_ITP
+ bool "Enable ITP DebugFS (DEVELOPMENT)"
+ depends on CW1200_DEBUGFS
+ ---help---
+ Say Y if you want to include ITP (device calibration and "productization")
+ code.
+
+ If unsure, say N.
+
+menu "Driver debug features"
+ depends on CW1200
+
+config CW1200_BH_DEBUG
+ bool "Enable low-level device communication logs (DEVELOPMENT)"
+ ---help---
+ Say Y if you want to enable low-level communication logs: interrupts,
+ device wakeups, wake-downs, suspends, resumes etc.
+
+ If unsure, say N.
+
+config CW1200_WSM_DEBUG
+ bool "Enable WSM API debug messages (DEVELOPMENT)"
+ ---help---
+ Say Y if you want to enable WSM logs. The logs will help you to trace
+ the sequence of WSM commands sent over the device interface. No additional
+ information will be stored, like command payload, just WSM message ID.
+
+ Usually you would like to enable verbose WSM logging in addition.
+
+ If unsure, say N.
+
+config CW1200_WSM_DUMPS
+ bool "Verbose WSM API logging (DEVELOPMENT)"
+ ---help---
+ Say Y if you want to enable WSM dumps. The dumps will store in the kernel
+ log all data sent over the device interface.
+
+ Note: WSM dumps are quite time- and log space consuming.
+
+ If unsure, say N.
+
+config CW1200_WSM_DUMPS_SHORT
+ depends on CW1200_WSM_DUMPS
+ bool "Dump only first x bytes (default 20) (DEVELOPMENT)"
+ ---help---
+ Say Y if you want to limit amount of data printed in WSM dumps. It reduces
+ system performance impact, but reduces also ability to debug some tricky cases.
+
+ If unsure, say Y.
+
+config CW1200_TXRX_DEBUG
+ bool "Enable TX/RX debug messages (DEVELOPMENT)"
+ ---help---
+ Say Y if you want to enable TX and RX logs.
+
+ Note: TX logs consumes quite a lot of log space. Usually it's more natural
+ to enable WSM dumps instead.
+
+ If unsure, say N.
+
+config CW1200_TX_POLICY_DEBUG
+ bool "Enable TX policy debug (DEVELOPMENT)"
+ ---help---
+ Say Y if you want to enable TX rate control logs.
+
+ Usually you do not need this kind of logs. The only exception is if you are
+ going to debug rate control selection code in the driver.
+
+ If unsure, say N.
+
+config CW1200_STA_DEBUG
+ bool "Enable STA/AP debug (DEVELOPMENT)"
+ ---help---
+ Say Y if you want to enable STA and AP logs.
+
+ This kind of logs is often quite useful for generic debugging and tracing.
+ The logs are quite lightweight.
+
+ If unsure, say N.
+
+config CW1200_DUMP_ON_ERROR
+ bool "Dump kernel in case of critical error (DEVELOPMENT)"
+ ---help---
+ Say Y only if you really know what are you doing.
+
+ If unsure, say N.
+
+endmenu
diff --git a/drivers/net/wireless/cw1200/Makefile b/drivers/net/wireless/cw1200/Makefile
new file mode 100644
index 0000000..01a3290
--- /dev/null
+++ b/drivers/net/wireless/cw1200/Makefile
@@ -0,0 +1,19 @@
+cw1200_core-y := \
+ fwio.o \
+ txrx.o \
+ main.o \
+ queue.o \
+ hwio.o \
+ bh.o \
+ wsm.o \
+ sta.o \
+ ap.o \
+ scan.o
+cw1200_core-$(CONFIG_CW1200_DEBUGFS) += debug.o
+cw1200_core-$(CONFIG_CW1200_ITP) += itp.o
+cw1200_core-$(CONFIG_PM) += pm.o
+
+cw1200_wlan-y := cw1200_sdio.o
+
+obj-$(CONFIG_CW1200) += cw1200_core.o
+obj-$(CONFIG_CW1200) += cw1200_wlan.o
--
1.7.9


2012-03-02 20:27:02

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 14/21] cw1200: scan.*, mac80211 hw_scan callback.

The code implements mac80211 hw_scan callback.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/scan.c | 446 ++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/scan.h | 54 +++++
2 files changed, 500 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/scan.c
create mode 100644 drivers/net/wireless/cw1200/scan.h

diff --git a/drivers/net/wireless/cw1200/scan.c b/drivers/net/wireless/cw1200/scan.c
new file mode 100644
index 0000000..b12af9d
--- /dev/null
+++ b/drivers/net/wireless/cw1200/scan.c
@@ -0,0 +1,446 @@
+/*
+ * Scan implementation for ST-Ericsson CW1200 mac80211 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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/sched.h>
+#include "cw1200.h"
+#include "scan.h"
+#include "sta.h"
+#include "pm.h"
+
+static void cw1200_scan_restart_delayed(struct cw1200_common *priv);
+
+static int cw1200_scan_start(struct cw1200_common *priv, struct wsm_scan *scan)
+{
+ int ret, i;
+ int tmo = 2000;
+
+ for (i = 0; i < scan->numOfChannels; ++i)
+ tmo += scan->ch[i].maxChannelTime + 10;
+
+ atomic_set(&priv->scan.in_progress, 1);
+ cw1200_pm_stay_awake(&priv->pm_state, tmo * HZ / 1000);
+ queue_delayed_work(priv->workqueue, &priv->scan.timeout,
+ tmo * HZ / 1000);
+ ret = wsm_scan(priv, scan);
+ if (unlikely(ret)) {
+ atomic_set(&priv->scan.in_progress, 0);
+ cancel_delayed_work_sync(&priv->scan.timeout);
+ cw1200_scan_restart_delayed(priv);
+ }
+ return ret;
+}
+
+int cw1200_hw_scan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_scan_request *req)
+{
+ struct cw1200_common *priv = hw->priv;
+ struct wsm_template_frame frame = {
+ .frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
+ };
+ int i;
+
+ if (!priv->vif)
+ return -EINVAL;
+
+ /* Scan when P2P_GO corrupt firmware MiniAP mode */
+ if (priv->join_status == CW1200_JOIN_STATUS_AP)
+ return -EOPNOTSUPP;
+
+ if (req->n_ssids == 1 && !req->ssids[0].ssid_len)
+ req->n_ssids = 0;
+
+ wiphy_dbg(hw->wiphy, "[SCAN] Scan request for %d SSIDs.\n",
+ req->n_ssids);
+
+ if (req->n_ssids > WSM_SCAN_MAX_NUM_OF_SSIDS)
+ return -EINVAL;
+
+ frame.skb = ieee80211_probereq_get(hw, priv->vif, NULL, 0,
+ req->ie, req->ie_len);
+ if (!frame.skb)
+ return -ENOMEM;
+
+ /* will be unlocked in cw1200_scan_work() */
+ down(&priv->scan.lock);
+ mutex_lock(&priv->conf_mutex);
+ if (frame.skb) {
+ int ret = wsm_set_template_frame(priv, &frame);
+ if (0 == ret) {
+ /*
+ * set empty probe response template in order
+ * to receive probe requests from firmware
+ */
+ frame.frame_type = WSM_FRAME_TYPE_PROBE_RESPONSE;
+ frame.disable = true;
+ ret = wsm_set_template_frame(priv, &frame);
+ }
+ if (ret) {
+ mutex_unlock(&priv->conf_mutex);
+ up(&priv->scan.lock);
+ dev_kfree_skb(frame.skb);
+ return ret;
+ }
+ }
+
+ wsm_lock_tx(priv);
+
+ BUG_ON(priv->scan.req);
+ priv->scan.req = req;
+ priv->scan.n_ssids = 0;
+ priv->scan.status = 0;
+ priv->scan.begin = &req->channels[0];
+ priv->scan.curr = priv->scan.begin;
+ priv->scan.end = &req->channels[req->n_channels];
+ priv->scan.output_power = priv->output_power;
+
+ for (i = 0; i < req->n_ssids; ++i) {
+ struct wsm_ssid *dst =
+ &priv->scan.ssids[priv->scan.n_ssids];
+ BUG_ON(req->ssids[i].ssid_len > sizeof(dst->ssid));
+ memcpy(&dst->ssid[0], req->ssids[i].ssid,
+ sizeof(dst->ssid));
+ dst->length = req->ssids[i].ssid_len;
+ ++priv->scan.n_ssids;
+ }
+
+ mutex_unlock(&priv->conf_mutex);
+
+ if (frame.skb)
+ dev_kfree_skb(frame.skb);
+ queue_work(priv->workqueue, &priv->scan.work);
+ return 0;
+}
+
+void cw1200_scan_work(struct work_struct *work)
+{
+ struct cw1200_common *priv = container_of(work, struct cw1200_common,
+ scan.work);
+ struct ieee80211_channel **it;
+ struct wsm_scan scan = {
+ .scanType = WSM_SCAN_TYPE_FOREGROUND,
+ .scanFlags = WSM_SCAN_FLAG_SPLIT_METHOD,
+ };
+ bool first_run = priv->scan.begin == priv->scan.curr &&
+ priv->scan.begin != priv->scan.end;
+ int i;
+
+ if (first_run) {
+ /* Firmware gets crazy if scan request is sent
+ * when STA is joined but not yet associated.
+ * Force unjoin in this case. */
+ if (cancel_delayed_work_sync(&priv->join_timeout) > 0)
+ cw1200_join_timeout(&priv->join_timeout.work);
+ }
+
+ mutex_lock(&priv->conf_mutex);
+
+ if (first_run) {
+ if (priv->join_status == CW1200_JOIN_STATUS_STA &&
+ !(priv->powersave_mode.pmMode & WSM_PSM_PS)) {
+ struct wsm_set_pm pm = priv->powersave_mode;
+ pm.pmMode = WSM_PSM_PS;
+ cw1200_set_pm(priv, &pm);
+ } else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) {
+ /* FW bug: driver has to restart p2p-dev mode
+ * after scan */
+ cw1200_disable_listening(priv);
+ }
+ }
+
+ if (!priv->scan.req || (priv->scan.curr == priv->scan.end)) {
+ if (priv->scan.output_power != priv->output_power)
+ WARN_ON(wsm_set_output_power(priv,
+ priv->output_power * 10));
+ if (priv->join_status == CW1200_JOIN_STATUS_STA &&
+ !(priv->powersave_mode.pmMode & WSM_PSM_PS))
+ cw1200_set_pm(priv, &priv->powersave_mode);
+
+ if (priv->scan.status < 0)
+ wiphy_dbg(priv->hw->wiphy,
+ "[SCAN] Scan failed (%d).\n",
+ priv->scan.status);
+ else if (priv->scan.req)
+ wiphy_dbg(priv->hw->wiphy,
+ "[SCAN] Scan completed.\n");
+ else
+ wiphy_dbg(priv->hw->wiphy,
+ "[SCAN] Scan canceled.\n");
+
+ priv->scan.req = NULL;
+ cw1200_scan_restart_delayed(priv);
+ wsm_unlock_tx(priv);
+ mutex_unlock(&priv->conf_mutex);
+ ieee80211_scan_completed(priv->hw, priv->scan.status ? 1 : 0);
+ up(&priv->scan.lock);
+ return;
+ } else {
+ struct ieee80211_channel *first = *priv->scan.curr;
+ for (it = priv->scan.curr + 1, i = 1;
+ it != priv->scan.end && i < WSM_SCAN_MAX_NUM_OF_CHANNELS;
+ ++it, ++i) {
+ if ((*it)->band != first->band)
+ break;
+ if (((*it)->flags ^ first->flags) &
+ IEEE80211_CHAN_PASSIVE_SCAN)
+ break;
+ if (!(first->flags & IEEE80211_CHAN_PASSIVE_SCAN) &&
+ (*it)->max_power != first->max_power)
+ break;
+ }
+ scan.band = first->band;
+
+ if (priv->scan.req->no_cck)
+ scan.maxTransmitRate = WSM_TRANSMIT_RATE_6;
+ else
+ scan.maxTransmitRate = WSM_TRANSMIT_RATE_1;
+ /* TODO: Is it optimal? */
+ scan.numOfProbeRequests =
+ (first->flags & IEEE80211_CHAN_PASSIVE_SCAN) ? 0 : 2;
+ scan.numOfSSIDs = priv->scan.n_ssids;
+ scan.ssids = &priv->scan.ssids[0];
+ scan.numOfChannels = it - priv->scan.curr;
+ /* TODO: Is it optimal? */
+ scan.probeDelay = 100;
+ /* It is not stated in WSM specification, however
+ * FW team says that driver may not use FG scan
+ * when joined. */
+ if (priv->join_status == CW1200_JOIN_STATUS_STA) {
+ scan.scanType = WSM_SCAN_TYPE_BACKGROUND;
+ scan.scanFlags = WSM_SCAN_FLAG_FORCE_BACKGROUND;
+ }
+ scan.ch = kzalloc(
+ sizeof(struct wsm_scan_ch[it - priv->scan.curr]),
+ GFP_KERNEL);
+ if (!scan.ch) {
+ priv->scan.status = -ENOMEM;
+ goto fail;
+ }
+ for (i = 0; i < scan.numOfChannels; ++i) {
+ scan.ch[i].number = priv->scan.curr[i]->hw_value;
+ scan.ch[i].minChannelTime = 50;
+ scan.ch[i].maxChannelTime = 110;
+ }
+ if (!(first->flags & IEEE80211_CHAN_PASSIVE_SCAN) &&
+ priv->scan.output_power != first->max_power) {
+ priv->scan.output_power = first->max_power;
+ WARN_ON(wsm_set_output_power(priv,
+ priv->scan.output_power * 10));
+ }
+ priv->scan.status = cw1200_scan_start(priv, &scan);
+ kfree(scan.ch);
+ if (priv->scan.status)
+ goto fail;
+ priv->scan.curr = it;
+ }
+ mutex_unlock(&priv->conf_mutex);
+ return;
+
+fail:
+ priv->scan.curr = priv->scan.end;
+ mutex_unlock(&priv->conf_mutex);
+ queue_work(priv->workqueue, &priv->scan.work);
+ return;
+}
+
+static void cw1200_scan_restart_delayed(struct cw1200_common *priv)
+{
+ if (priv->delayed_link_loss) {
+ int tmo = priv->cqm_beacon_loss_count;
+
+ if (priv->scan.direct_probe)
+ tmo = 0;
+
+ priv->delayed_link_loss = 0;
+ /* Restart beacon loss timer and requeue
+ BSS loss work. */
+ wiphy_dbg(priv->hw->wiphy,
+ "[CQM] Requeue BSS loss in %d "
+ "beacons.\n", tmo);
+ spin_lock(&priv->bss_loss_lock);
+ priv->bss_loss_status = CW1200_BSS_LOSS_NONE;
+ spin_unlock(&priv->bss_loss_lock);
+ cancel_delayed_work_sync(&priv->bss_loss_work);
+ queue_delayed_work(priv->workqueue,
+ &priv->bss_loss_work,
+ tmo * HZ / 10);
+ }
+
+ /* FW bug: driver has to restart p2p-dev mode after scan. */
+ if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) {
+ cw1200_enable_listening(priv);
+ cw1200_update_filtering(priv);
+ }
+
+ if (priv->delayed_unjoin) {
+ priv->delayed_unjoin = false;
+ if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
+ wsm_unlock_tx(priv);
+ }
+}
+
+static void cw1200_scan_complete(struct cw1200_common *priv)
+{
+ if (priv->scan.direct_probe) {
+ wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe complete.\n");
+ cw1200_scan_restart_delayed(priv);
+ priv->scan.direct_probe = 0;
+ up(&priv->scan.lock);
+ wsm_unlock_tx(priv);
+ } else {
+ cw1200_scan_work(&priv->scan.work);
+ }
+}
+
+void cw1200_scan_complete_cb(struct cw1200_common *priv,
+ struct wsm_scan_complete *arg)
+{
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED))
+ /* STA is stopped. */
+ return;
+
+ if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) {
+ priv->scan.status = 1;
+ queue_delayed_work(priv->workqueue,
+ &priv->scan.timeout, 0);
+ }
+}
+
+void cw1200_scan_timeout(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, scan.timeout.work);
+ if (likely(atomic_xchg(&priv->scan.in_progress, 0))) {
+ if (priv->scan.status > 0)
+ priv->scan.status = 0;
+ else if (!priv->scan.status) {
+ wiphy_warn(priv->hw->wiphy,
+ "Timeout waiting for scan "
+ "complete notification.\n");
+ priv->scan.status = -ETIMEDOUT;
+ priv->scan.curr = priv->scan.end;
+ WARN_ON(wsm_stop_scan(priv));
+ }
+ cw1200_scan_complete(priv);
+ }
+}
+
+void cw1200_probe_work(struct work_struct *work)
+{
+ struct cw1200_common *priv =
+ container_of(work, struct cw1200_common, scan.probe_work.work);
+ u8 queueId = cw1200_queue_get_queue_id(priv->pending_frame_id);
+ struct cw1200_queue *queue = &priv->tx_queue[queueId];
+ const struct cw1200_txpriv *txpriv;
+ struct wsm_tx *wsm;
+ struct wsm_template_frame frame = {
+ .frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
+ };
+ struct wsm_ssid ssids[1] = {{
+ .length = 0,
+ } };
+ struct wsm_scan_ch ch[1] = {{
+ .minChannelTime = 0,
+ .maxChannelTime = 10,
+ } };
+ struct wsm_scan scan = {
+ .scanType = WSM_SCAN_TYPE_FOREGROUND,
+ .numOfProbeRequests = 1,
+ .probeDelay = 0,
+ .numOfChannels = 1,
+ .ssids = ssids,
+ .ch = ch,
+ };
+ u8 *ies;
+ size_t ies_len;
+ int ret;
+
+ wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe work.\n");
+
+ BUG_ON(queueId >= 4);
+ BUG_ON(!priv->channel);
+
+ mutex_lock(&priv->conf_mutex);
+ if (unlikely(down_trylock(&priv->scan.lock))) {
+ /* Scan is already in progress. Requeue self. */
+ schedule();
+ queue_delayed_work(priv->workqueue,
+ &priv->scan.probe_work, HZ / 10);
+ mutex_unlock(&priv->conf_mutex);
+ return;
+ }
+
+ if (cw1200_queue_get_skb(queue, priv->pending_frame_id,
+ &frame.skb, &txpriv)) {
+ up(&priv->scan.lock);
+ mutex_unlock(&priv->conf_mutex);
+ wsm_unlock_tx(priv);
+ return;
+ }
+ wsm = (struct wsm_tx *)frame.skb->data;
+ scan.maxTransmitRate = wsm->maxTxRate;
+ scan.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ?
+ WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G;
+ if (priv->join_status == CW1200_JOIN_STATUS_STA) {
+ scan.scanType = WSM_SCAN_TYPE_BACKGROUND;
+ scan.scanFlags = WSM_SCAN_FLAG_FORCE_BACKGROUND;
+ }
+ ch[0].number = priv->channel->hw_value;
+
+ skb_pull(frame.skb, txpriv->offset);
+
+ ies = &frame.skb->data[sizeof(struct ieee80211_hdr_3addr)];
+ ies_len = frame.skb->len - sizeof(struct ieee80211_hdr_3addr);
+
+ if (ies_len) {
+ u8 *ssidie =
+ (u8 *)cfg80211_find_ie(WLAN_EID_SSID, ies, ies_len);
+ if (ssidie && ssidie[1] && ssidie[1] <= sizeof(ssids[0].ssid)) {
+ u8 *nextie = &ssidie[2 + ssidie[1]];
+ /* Remove SSID from the IE list. It has to be provided
+ * as a separate argument in cw1200_scan_start call */
+
+ /* Store SSID localy */
+ ssids[0].length = ssidie[1];
+ memcpy(ssids[0].ssid, &ssidie[2], ssids[0].length);
+ scan.numOfSSIDs = 1;
+
+ /* Remove SSID from IE list */
+ ssidie[1] = 0;
+ memmove(&ssidie[2], nextie, &ies[ies_len] - nextie);
+ skb_trim(frame.skb, frame.skb->len - ssids[0].length);
+ }
+ }
+
+ /* FW bug: driver has to restart p2p-dev mode after scan */
+ if (priv->join_status == CW1200_JOIN_STATUS_MONITOR)
+ cw1200_disable_listening(priv);
+ ret = WARN_ON(wsm_set_template_frame(priv, &frame));
+ priv->scan.direct_probe = 1;
+ if (!ret) {
+ wsm_flush_tx(priv);
+ ret = WARN_ON(cw1200_scan_start(priv, &scan));
+ }
+ mutex_unlock(&priv->conf_mutex);
+
+ skb_push(frame.skb, txpriv->offset);
+ if (!ret)
+ IEEE80211_SKB_CB(frame.skb)->flags |= IEEE80211_TX_STAT_ACK;
+ BUG_ON(cw1200_queue_remove(queue, priv->pending_frame_id));
+
+ if (ret) {
+ priv->scan.direct_probe = 0;
+ up(&priv->scan.lock);
+ wsm_unlock_tx(priv);
+ }
+
+ return;
+}
diff --git a/drivers/net/wireless/cw1200/scan.h b/drivers/net/wireless/cw1200/scan.h
new file mode 100644
index 0000000..abffd1d
--- /dev/null
+++ b/drivers/net/wireless/cw1200/scan.h
@@ -0,0 +1,54 @@
+/*
+ * Scan interface for ST-Ericsson CW1200 mac80211 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef SCAN_H_INCLUDED
+#define SCAN_H_INCLUDED
+
+#include <linux/semaphore.h>
+#include "wsm.h"
+
+/* external */ struct sk_buff;
+/* external */ struct cfg80211_scan_request;
+/* external */ struct ieee80211_channel;
+/* external */ struct ieee80211_hw;
+/* external */ struct work_struct;
+
+struct cw1200_scan {
+ struct semaphore lock;
+ struct work_struct work;
+ struct delayed_work timeout;
+ struct cfg80211_scan_request *req;
+ struct ieee80211_channel **begin;
+ struct ieee80211_channel **curr;
+ struct ieee80211_channel **end;
+ struct wsm_ssid ssids[WSM_SCAN_MAX_NUM_OF_SSIDS];
+ int output_power;
+ int n_ssids;
+ int status;
+ atomic_t in_progress;
+ /* Direct probe requests workaround */
+ struct delayed_work probe_work;
+ int direct_probe;
+};
+
+int cw1200_hw_scan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_scan_request *req);
+void cw1200_scan_work(struct work_struct *work);
+void cw1200_scan_timeout(struct work_struct *work);
+void cw1200_scan_complete_cb(struct cw1200_common *priv,
+ struct wsm_scan_complete *arg);
+
+/* ******************************************************************** */
+/* Raw probe requests TX workaround */
+void cw1200_probe_work(struct work_struct *work);
+
+#endif
--
1.7.9


2012-03-02 20:27:02

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 06/21] cw1200: fwio.*, firmware downloading code for the cw1200 driver

The patch contains firmware downloading code for the cw1200 device driver.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/fwio.c | 593 ++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/fwio.h | 36 +++
2 files changed, 629 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/fwio.c
create mode 100644 drivers/net/wireless/cw1200/fwio.h

diff --git a/drivers/net/wireless/cw1200/fwio.c b/drivers/net/wireless/cw1200/fwio.c
new file mode 100644
index 0000000..9452125
--- /dev/null
+++ b/drivers/net/wireless/cw1200/fwio.c
@@ -0,0 +1,593 @@
+/*
+ * Firmware I/O code for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on:
+ * ST-Ericsson UMAC CW1200 driver which is
+ * Copyright (c) 2010, ST-Ericsson AB
+ * Author: Ajitpal Singh <[email protected]>
+ *
+ * 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/init.h>
+#include <linux/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/firmware.h>
+
+#include "cw1200.h"
+#include "fwio.h"
+#include "hwio.h"
+#include "sbus.h"
+#include "bh.h"
+
+static int cw1200_get_hw_type(u32 config_reg_val, int *major_revision)
+{
+ int hw_type = -1;
+ u32 silicon_type = (config_reg_val >> 24) & 0x3;
+ u32 silicon_vers = (config_reg_val >> 31) & 0x1;
+
+ /* Check if we have CW1200 or STLC9000 */
+ if ((silicon_type == 0x1) || (silicon_type == 0x2)) {
+ *major_revision = silicon_type;
+ if (silicon_vers)
+ hw_type = HIF_8601_VERSATILE;
+ else
+ hw_type = HIF_8601_SILICON;
+ } else {
+ *major_revision = 1;
+ hw_type = HIF_9000_SILICON_VERSTAILE;
+ }
+
+ return hw_type;
+}
+
+static int config_reg_read_stlc9000(struct cw1200_common *priv,
+ u16 reg, u32 *val)
+{
+ u16 val16;
+ int ret = cw1200_reg_read_16(priv, reg, &val16);
+ if (ret < 0)
+ return ret;
+ *val = val16;
+ return 0;
+}
+
+static int config_reg_write_stlc9000(struct cw1200_common *priv,
+ u16 reg, u32 val)
+{
+ return cw1200_reg_write_16(priv, reg, (u16)val);
+}
+
+static int cw1200_load_firmware_cw1200(struct cw1200_common *priv)
+{
+ int ret, block, num_blocks;
+ unsigned i;
+ u32 val32;
+ u32 put = 0, get = 0;
+ u8 *buf = NULL;
+ const char *fw_path;
+ const struct firmware *firmware = NULL;
+
+ /* Macroses are local. */
+#define APB_WRITE(reg, val) \
+ do { \
+ ret = cw1200_apb_write_32(priv, CW12000_APB(reg), (val)); \
+ if (ret < 0) { \
+ cw1200_dbg(CW1200_DBG_ERROR, \
+ "%s: can't write %s at line %d.\n", \
+ __func__, #reg, __LINE__); \
+ goto error; \
+ } \
+ } while (0)
+#define APB_READ(reg, val) \
+ do { \
+ ret = cw1200_apb_read_32(priv, CW12000_APB(reg), &(val)); \
+ if (ret < 0) { \
+ cw1200_dbg(CW1200_DBG_ERROR, \
+ "%s: can't read %s at line %d.\n", \
+ __func__, #reg, __LINE__); \
+ goto error; \
+ } \
+ } while (0)
+#define REG_WRITE(reg, val) \
+ do { \
+ ret = cw1200_reg_write_32(priv, (reg), (val)); \
+ if (ret < 0) { \
+ cw1200_dbg(CW1200_DBG_ERROR, \
+ "%s: can't write %s at line %d.\n", \
+ __func__, #reg, __LINE__); \
+ goto error; \
+ } \
+ } while (0)
+#define REG_READ(reg, val) \
+ do { \
+ ret = cw1200_reg_read_32(priv, (reg), &(val)); \
+ if (ret < 0) { \
+ cw1200_dbg(CW1200_DBG_ERROR, \
+ "%s: can't read %s at line %d.\n", \
+ __func__, #reg, __LINE__); \
+ goto error; \
+ } \
+ } while (0)
+
+ switch (priv->hw_revision) {
+ case CW1200_HW_REV_CUT10:
+ fw_path = FIRMWARE_CUT10;
+ break;
+ case CW1200_HW_REV_CUT11:
+ fw_path = FIRMWARE_CUT11;
+ break;
+ case CW1200_HW_REV_CUT20:
+ fw_path = FIRMWARE_CUT20;
+ break;
+ case CW1200_HW_REV_CUT22:
+ fw_path = FIRMWARE_CUT22;
+ break;
+ default:
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: invalid silicon revision %d.\n",
+ __func__, priv->hw_revision);
+ return -EINVAL;
+ }
+
+ /* Initialize common registers */
+ APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, DOWNLOAD_ARE_YOU_HERE);
+ APB_WRITE(DOWNLOAD_PUT_REG, 0);
+ APB_WRITE(DOWNLOAD_GET_REG, 0);
+ APB_WRITE(DOWNLOAD_STATUS_REG, DOWNLOAD_PENDING);
+ APB_WRITE(DOWNLOAD_FLAGS_REG, 0);
+
+ /* Write the NOP Instruction */
+ REG_WRITE(ST90TDS_SRAM_BASE_ADDR_REG_ID, 0xFFF20000);
+ REG_WRITE(ST90TDS_AHB_DPORT_REG_ID, 0xEAFFFFFE);
+
+ /* Release CPU from RESET */
+ REG_READ(ST90TDS_CONFIG_REG_ID, val32);
+ val32 &= ~ST90TDS_CONFIG_CPU_RESET_BIT;
+ REG_WRITE(ST90TDS_CONFIG_REG_ID, val32);
+
+ /* Enable Clock */
+ val32 &= ~ST90TDS_CONFIG_CPU_CLK_DIS_BIT;
+ REG_WRITE(ST90TDS_CONFIG_REG_ID, val32);
+
+ /* Load a firmware file */
+ ret = request_firmware(&firmware, fw_path, priv->pdev);
+ if (ret) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't load firmware file %s.\n",
+ __func__, fw_path);
+ goto error;
+ }
+ BUG_ON(!firmware->data);
+
+ buf = kmalloc(DOWNLOAD_BLOCK_SIZE, GFP_KERNEL | GFP_DMA);
+ if (!buf) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't allocate firmware buffer.\n", __func__);
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Check if the bootloader is ready */
+ for (i = 0; i < 100; i += 1 + i / 2) {
+ APB_READ(DOWNLOAD_IMAGE_SIZE_REG, val32);
+ if (val32 == DOWNLOAD_I_AM_HERE)
+ break;
+ mdelay(i);
+ } /* End of for loop */
+
+ if (val32 != DOWNLOAD_I_AM_HERE) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: bootloader is not ready.\n", __func__);
+ ret = -ETIMEDOUT;
+ goto error;
+ }
+
+ /* Calculcate number of download blocks */
+ num_blocks = (firmware->size - 1) / DOWNLOAD_BLOCK_SIZE + 1;
+
+ /* Updating the length in Download Ctrl Area */
+ val32 = firmware->size; /* Explicit cast from size_t to u32 */
+ APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, val32);
+
+ /* Firmware downloading loop */
+ for (block = 0; block < num_blocks ; block++) {
+ size_t tx_size;
+ size_t block_size;
+
+ /* check the download status */
+ APB_READ(DOWNLOAD_STATUS_REG, val32);
+ if (val32 != DOWNLOAD_PENDING) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: bootloader reported error %d.\n",
+ __func__, val32);
+ ret = -EIO;
+ goto error;
+ }
+
+ /* loop until put - get <= 24K */
+ for (i = 0; i < 100; i++) {
+ APB_READ(DOWNLOAD_GET_REG, get);
+ if ((put - get) <=
+ (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE))
+ break;
+ mdelay(i);
+ }
+
+ if ((put - get) > (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Timeout waiting for FIFO.\n",
+ __func__);
+ return -ETIMEDOUT;
+ }
+
+ /* calculate the block size */
+ tx_size = block_size = min((size_t)(firmware->size - put),
+ (size_t)DOWNLOAD_BLOCK_SIZE);
+
+ memcpy(buf, &firmware->data[put], block_size);
+ if (block_size < DOWNLOAD_BLOCK_SIZE) {
+ memset(&buf[block_size],
+ 0, DOWNLOAD_BLOCK_SIZE - block_size);
+ tx_size = DOWNLOAD_BLOCK_SIZE;
+ }
+
+ /* send the block to sram */
+ ret = cw1200_apb_write(priv,
+ CW12000_APB(DOWNLOAD_FIFO_OFFSET +
+ (put & (DOWNLOAD_FIFO_SIZE - 1))),
+ buf, tx_size);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't write block at line %d.\n",
+ __func__, __LINE__);
+ goto error;
+ }
+
+ /* update the put register */
+ put += block_size;
+ APB_WRITE(DOWNLOAD_PUT_REG, put);
+ } /* End of firmware download loop */
+
+ /* Wait for the download completion */
+ for (i = 0; i < 300; i += 1 + i / 2) {
+ APB_READ(DOWNLOAD_STATUS_REG, val32);
+ if (val32 != DOWNLOAD_PENDING)
+ break;
+ mdelay(i);
+ }
+ if (val32 != DOWNLOAD_SUCCESS) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: wait for download completion failed. " \
+ "Read: 0x%.8X\n", __func__, val32);
+ ret = -ETIMEDOUT;
+ goto error;
+ } else {
+ cw1200_dbg(CW1200_DBG_MSG,
+ "Firmware download completed.\n");
+ ret = 0;
+ }
+
+error:
+ kfree(buf);
+ if (firmware)
+ release_firmware(firmware);
+ return ret;
+
+#undef APB_WRITE
+#undef APB_READ
+#undef REG_WRITE
+#undef REG_READ
+}
+
+int cw1200_load_firmware(struct cw1200_common *priv)
+{
+ int ret;
+ int i;
+ u32 val32;
+ u16 val16;
+ u32 dpll = 0;
+ int major_revision;
+ int (*config_reg_read)(struct cw1200_common *priv, u16 reg, u32 *val);
+ int (*config_reg_write)(struct cw1200_common *priv, u16 reg, u32 val);
+
+ BUG_ON(!priv);
+
+ /* Read CONFIG Register Value - We will read 32 bits */
+ ret = cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't read config register.\n", __func__);
+ goto out;
+ }
+
+ priv->hw_type = cw1200_get_hw_type(val32, &major_revision);
+ if (priv->hw_type < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't deduct hardware type.\n", __func__);
+ ret = -ENOTSUPP;
+ goto out;
+ }
+
+ switch (priv->hw_type) {
+ case HIF_8601_VERSATILE:
+ case HIF_8601_SILICON:
+ dpll = DPLL_INIT_VAL_CW1200;
+ config_reg_read = cw1200_reg_read_32;
+ config_reg_write = cw1200_reg_write_32;
+ break;
+ case HIF_9000_SILICON_VERSTAILE:
+ dpll = DPLL_INIT_VAL_9000;
+ config_reg_read = config_reg_read_stlc9000;
+ config_reg_write = config_reg_write_stlc9000;
+ break;
+ default:
+ BUG_ON(1);
+ }
+
+ ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID, dpll);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't write DPLL register.\n", __func__);
+ goto out;
+ }
+
+ msleep(20);
+
+ /* Read DPLL Reg value and compare with value written */
+ ret = cw1200_reg_read_32(priv,
+ ST90TDS_TSET_GEN_R_W_REG_ID, &val32);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't read DPLL register.\n", __func__);
+ goto out;
+ }
+
+ if (val32 != dpll) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: unable to initialise " \
+ "DPLL register. Wrote 0x%.8X, read 0x%.8X.\n",
+ __func__, dpll, val32);
+ ret = -EIO;
+ goto out;
+ }
+
+ /* Set wakeup bit in device */
+ ret = cw1200_reg_read_16(priv, ST90TDS_CONTROL_REG_ID, &val16);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: set_wakeup: can't read " \
+ "control register.\n", __func__);
+ goto out;
+ }
+
+ ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
+ val16 | ST90TDS_CONT_WUP_BIT);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: set_wakeup: can't write " \
+ "control register.\n", __func__);
+ goto out;
+ }
+
+ /* Wait for wakeup */
+ for (i = 0 ; i < 300 ; i += 1 + i / 2) {
+ ret = cw1200_reg_read_16(priv,
+ ST90TDS_CONTROL_REG_ID, &val16);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: wait_for_wakeup: can't read " \
+ "control register.\n", __func__);
+ goto out;
+ }
+
+ if (val16 & ST90TDS_CONT_RDY_BIT) {
+ cw1200_dbg(CW1200_DBG_MSG,
+ "WLAN device is ready.\n");
+ break;
+ }
+ msleep(i);
+ }
+
+ if ((val16 & ST90TDS_CONT_RDY_BIT) == 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: wait_for_wakeup: device is not responding.\n",
+ __func__);
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ if (major_revision == 1) {
+ /* CW1200 Hardware detection logic : Check for CUT1.1 */
+ ret = cw1200_ahb_read_32(priv, CW1200_CUT_ID_ADDR, &val32);
+ if (ret) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: HW detection: can't read CUT ID.\n",
+ __func__);
+ goto out;
+ }
+
+ switch (val32) {
+ case CW1200_CUT_11_ID_STR:
+ cw1200_dbg(CW1200_DBG_MSG,
+ "Cut 1.1 silicon is detected.\n");
+ priv->hw_revision = CW1200_HW_REV_CUT11;
+ break;
+ default:
+ cw1200_dbg(CW1200_DBG_MSG,
+ "Cut 1.0 silicon is detected.\n");
+ priv->hw_revision = CW1200_HW_REV_CUT10;
+ break;
+ }
+ } else if (major_revision == 2) {
+ u32 ar1, ar2, ar3;
+ cw1200_dbg(CW1200_DBG_MSG, "Cut 2.x silicon is detected.\n");
+
+ ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR, &ar1);
+ if (ret) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: (1) HW detection: can't read CUT ID.\n",
+ __func__);
+ goto out;
+ }
+ ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 4, &ar2);
+ if (ret) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: (2) HW detection: can't read CUT ID.\n",
+ __func__);
+ goto out;
+ }
+
+ ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 8, &ar3);
+ if (ret) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: (3) HW detection: can't read CUT ID.\n",
+ __func__);
+ goto out;
+ }
+
+ if (ar1 == CW1200_CUT_22_ID_STR1 &&
+ ar2 == CW1200_CUT_22_ID_STR2 &&
+ ar3 == CW1200_CUT_22_ID_STR3) {
+ cw1200_dbg(CW1200_DBG_MSG, "Cut 2.2 detected.\n");
+ priv->hw_revision = CW1200_HW_REV_CUT22;
+ } else {
+ cw1200_dbg(CW1200_DBG_MSG, "Cut 2.0 detected.\n");
+ priv->hw_revision = CW1200_HW_REV_CUT20;
+ }
+ } else {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: unsupported silicon major revision %d.\n",
+ __func__, major_revision);
+ ret = -ENOTSUPP;
+ goto out;
+ }
+
+ /* Checking for access mode */
+ ret = config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: check_access_mode: can't read " \
+ "config register.\n", __func__);
+ goto out;
+ }
+
+ if (val32 & ST90TDS_CONFIG_ACCESS_MODE_BIT) {
+ switch (priv->hw_type) {
+ case HIF_8601_SILICON:
+ cw1200_dbg(CW1200_DBG_MSG,
+ "%s: CW1200 detected.\n", __func__);
+ ret = cw1200_load_firmware_cw1200(priv);
+ break;
+ case HIF_8601_VERSATILE:
+ /* TODO: Not implemented yet!
+ ret = cw1200_load_firmware_cw1100(priv);
+ */
+ ret = -ENOTSUPP;
+ goto out;
+ case HIF_9000_SILICON_VERSTAILE:
+ /* TODO: Not implemented yet!
+ ret = cw1200_load_firmware_stlc9000(priv);
+ */
+ ret = -ENOTSUPP;
+ goto out;
+ default:
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: Unknown hardware: %d.\n",
+ __func__, priv->hw_type);
+ }
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't download firmware.\n", __func__);
+ goto out;
+ }
+ } else {
+ cw1200_dbg(CW1200_DBG_MSG,
+ "%s: check_access_mode: device is already " \
+ "in QUEUE mode.\n", __func__);
+ /* TODO: verify this branch. Do we need something to do? */
+ }
+
+ /* Register Interrupt Handler */
+ ret = priv->sbus_ops->irq_subscribe(priv->sbus_priv,
+ (sbus_irq_handler)cw1200_irq_handler, priv);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: can't register IRQ handler.\n", __func__);
+ goto out;
+ }
+
+ if (HIF_8601_SILICON == priv->hw_type) {
+ /* If device is CW1200 the IRQ enable/disable bits
+ * are in CONFIG register */
+ ret = config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: enable_irq: can't read " \
+ "config register.\n", __func__);
+ goto unsubscribe;
+ }
+ ret = config_reg_write(priv, ST90TDS_CONFIG_REG_ID,
+ val32 | ST90TDS_CONF_IRQ_RDY_ENABLE);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: enable_irq: can't write " \
+ "config register.\n", __func__);
+ goto unsubscribe;
+ }
+ } else {
+ /* If device is STLC9000 the IRQ enable/disable bits
+ * are in CONTROL register */
+ /* Enable device interrupts - Both DATA_RDY and WLAN_RDY */
+ ret = cw1200_reg_read_16(priv, ST90TDS_CONFIG_REG_ID, &val16);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: enable_irq: can't read " \
+ "control register.\n", __func__);
+ goto unsubscribe;
+ }
+ ret = cw1200_reg_write_16(priv, ST90TDS_CONFIG_REG_ID,
+ val16 | ST90TDS_CONT_IRQ_RDY_ENABLE);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: enable_irq: can't write " \
+ "control register.\n", __func__);
+ goto unsubscribe;
+ }
+
+ }
+
+ /* Configure device for MESSSAGE MODE */
+ ret = config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: set_mode: can't read config register.\n",
+ __func__);
+ goto unsubscribe;
+ }
+ ret = config_reg_write(priv, ST90TDS_CONFIG_REG_ID,
+ val32 & ~ST90TDS_CONFIG_ACCESS_MODE_BIT);
+ if (ret < 0) {
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "%s: set_mode: can't write config register.\n",
+ __func__);
+ goto unsubscribe;
+ }
+
+ /* Unless we read the CONFIG Register we are
+ * not able to get an interrupt */
+ mdelay(10);
+ config_reg_read(priv, ST90TDS_CONFIG_REG_ID, &val32);
+
+out:
+ return ret;
+
+unsubscribe:
+ priv->sbus_ops->irq_unsubscribe(priv->sbus_priv);
+ return ret;
+}
diff --git a/drivers/net/wireless/cw1200/fwio.h b/drivers/net/wireless/cw1200/fwio.h
new file mode 100644
index 0000000..cb91b8d
--- /dev/null
+++ b/drivers/net/wireless/cw1200/fwio.h
@@ -0,0 +1,36 @@
+/*
+ * Firmware API for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on:
+ * ST-Ericsson UMAC CW1200 driver which is
+ * Copyright (c) 2010, ST-Ericsson AB
+ * Author: Ajitpal Singh <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef FWIO_H_INCLUDED
+#define FWIO_H_INCLUDED
+
+#define FIRMWARE_CUT22 ("cw1200/wsm_22.bin")
+#define FIRMWARE_CUT20 ("cw1200/wsm_20.bin")
+#define FIRMWARE_CUT11 ("cw1200/wsm_11.bin")
+#define FIRMWARE_CUT10 ("cw1200/wsm_10.bin")
+#define SDD_FILE_22 ("cw1200/sdd_22.bin")
+#define SDD_FILE_20 ("cw1200/sdd_20.bin")
+#define SDD_FILE_11 ("cw1200/sdd_11.bin")
+#define SDD_FILE_10 ("cw1200/sdd_10.bin")
+
+#define CW1200_HW_REV_CUT10 (10)
+#define CW1200_HW_REV_CUT11 (11)
+#define CW1200_HW_REV_CUT20 (20)
+#define CW1200_HW_REV_CUT22 (22)
+
+int cw1200_load_firmware(struct cw1200_common *priv);
+
+#endif
--
1.7.9


2012-03-02 20:26:51

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 04/21] cw1200: cw1200_sdio.c, implementation of SDIO wrapper for the driver.

The patch contains SDIO wrapper for the cw1200 wireless driver.
The wrapper instantiates driver's core and implements common sbus
interface for the core.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/cw1200_sdio.c | 493 +++++++++++++++++++++++++++++
1 files changed, 493 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/cw1200_sdio.c

diff --git a/drivers/net/wireless/cw1200/cw1200_sdio.c b/drivers/net/wireless/cw1200/cw1200_sdio.c
new file mode 100644
index 0000000..dfa7e64
--- /dev/null
+++ b/drivers/net/wireless/cw1200/cw1200_sdio.c
@@ -0,0 +1,493 @@
+/*
+ * Mac80211 SDIO driver for ST-Ericsson CW1200 device
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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/version.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/spinlock.h>
+#include <net/mac80211.h>
+
+#include "cw1200.h"
+#include "sbus.h"
+#include "cw1200_plat.h"
+
+MODULE_AUTHOR("Dmitry Tarnyagin <[email protected]>");
+MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SDIO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("cw1200_wlan");
+
+#if defined(CONFIG_CW1200_STANDALONE)
+static char cw1200_mmc_id[10];
+module_param_string(mmc_id, cw1200_mmc_id, sizeof(cw1200_mmc_id), 0);
+#endif /* CONFIG_CW1200_STANDALONE */
+
+struct sbus_priv {
+ struct sdio_func *func;
+ struct cw1200_common *core;
+ const struct cw1200_platform_data *pdata;
+ spinlock_t lock;
+ sbus_irq_handler irq_handler;
+ void *irq_priv;
+};
+
+static const struct sdio_device_id cw1200_sdio_ids[] = {
+ { SDIO_DEVICE(SDIO_ANY_ID, SDIO_ANY_ID) },
+ { /* end: all zeroes */ },
+};
+
+/* sbus_ops implemetation */
+
+static int cw1200_sdio_memcpy_fromio(struct sbus_priv *self,
+ unsigned int addr,
+ void *dst, int count)
+{
+ return sdio_memcpy_fromio(self->func, dst, addr, count);
+}
+
+static int cw1200_sdio_memcpy_toio(struct sbus_priv *self,
+ unsigned int addr,
+ const void *src, int count)
+{
+ return sdio_memcpy_toio(self->func, addr, (void *)src, count);
+}
+
+static void cw1200_sdio_lock(struct sbus_priv *self)
+{
+ sdio_claim_host(self->func);
+}
+
+static void cw1200_sdio_unlock(struct sbus_priv *self)
+{
+ sdio_release_host(self->func);
+}
+
+#ifndef CONFIG_CW1200_USE_GPIO_IRQ
+static void cw1200_sdio_irq_handler(struct sdio_func *func)
+{
+ struct sbus_priv *self = sdio_get_drvdata(func);
+ unsigned long flags;
+
+ BUG_ON(!self);
+ spin_lock_irqsave(&self->lock, flags);
+ if (self->irq_handler)
+ self->irq_handler(self->irq_priv);
+ spin_unlock_irqrestore(&self->lock, flags);
+}
+#else /* CONFIG_CW1200_USE_GPIO_IRQ */
+static irqreturn_t cw1200_gpio_irq_handler(int irq, void *dev_id)
+{
+ struct sbus_priv *self = dev_id;
+
+ BUG_ON(!self);
+ if (self->irq_handler)
+ self->irq_handler(self->irq_priv);
+ return IRQ_HANDLED;
+}
+
+static int cw1200_request_irq(struct sbus_priv *self,
+ irq_handler_t handler)
+{
+ int ret;
+ int func_num;
+ const struct resource *irq = self->pdata->irq;
+ u8 cccr;
+
+ if (!irq)
+ return -EINVAL;
+
+ ret = request_any_context_irq(irq->start, handler,
+ IRQF_TRIGGER_RISING, irq->name, self);
+ if (WARN_ON(ret < 0))
+ goto exit;
+
+ /* Hack to access Fuction-0 */
+ func_num = self->func->num;
+ self->func->num = 0;
+
+ cccr = sdio_readb(self->func, SDIO_CCCR_IENx, &ret);
+ if (WARN_ON(ret))
+ goto set_func;
+
+ /* Master interrupt enable ... */
+ cccr |= BIT(0);
+
+ /* ... for our function */
+ cccr |= BIT(func_num);
+
+ sdio_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret);
+ if (WARN_ON(ret))
+ goto set_func;
+
+ /* Restore the WLAN function number */
+ self->func->num = func_num;
+ return 0;
+
+set_func:
+ self->func->num = func_num;
+ free_irq(irq->start, self);
+exit:
+ return ret;
+}
+#endif /* CONFIG_CW1200_USE_GPIO_IRQ */
+
+static int cw1200_sdio_irq_subscribe(struct sbus_priv *self,
+ sbus_irq_handler handler,
+ void *priv)
+{
+ int ret;
+ unsigned long flags;
+
+ if (!handler)
+ return -EINVAL;
+
+ spin_lock_irqsave(&self->lock, flags);
+ self->irq_priv = priv;
+ self->irq_handler = handler;
+ spin_unlock_irqrestore(&self->lock, flags);
+
+ printk(KERN_DEBUG "SW IRQ subscribe\n");
+ sdio_claim_host(self->func);
+#ifndef CONFIG_CW1200_USE_GPIO_IRQ
+ ret = sdio_claim_irq(self->func, cw1200_sdio_irq_handler);
+#else
+ ret = cw1200_request_irq(self, cw1200_gpio_irq_handler);
+#endif
+ sdio_release_host(self->func);
+ return ret;
+}
+
+static int cw1200_sdio_irq_unsubscribe(struct sbus_priv *self)
+{
+ int ret = 0;
+ unsigned long flags;
+#ifdef CONFIG_CW1200_USE_GPIO_IRQ
+ const struct resource *irq = self->pdata->irq;
+
+ if (!irq)
+ return -EINVAL;
+#endif
+
+ WARN_ON(!self->irq_handler);
+ if (!self->irq_handler)
+ return 0;
+
+ printk(KERN_DEBUG "SW IRQ unsubscribe\n");
+#ifndef CONFIG_CW1200_USE_GPIO_IRQ
+ sdio_claim_host(self->func);
+ ret = sdio_release_irq(self->func);
+ sdio_release_host(self->func);
+#else
+ free_irq(irq->start, self);
+#endif
+
+ spin_lock_irqsave(&self->lock, flags);
+ self->irq_priv = NULL;
+ self->irq_handler = NULL;
+ spin_unlock_irqrestore(&self->lock, flags);
+
+ return ret;
+}
+
+static int cw1200_detect_card(const struct cw1200_platform_data *pdata)
+{
+ /* HACK!!!
+ * Rely on mmc->class_dev.class set in mmc_alloc_host
+ * Tricky part: a new mmc hook is being (temporary) created
+ * to discover mmc_host class.
+ * Do you know more elegant way how to enumerate mmc_hosts?
+ */
+
+ struct mmc_host *mmc = NULL;
+ struct class_dev_iter iter;
+ struct device *dev;
+
+ mmc = mmc_alloc_host(0, NULL);
+ if (!mmc)
+ return -ENOMEM;
+
+ BUG_ON(!mmc->class_dev.class);
+ class_dev_iter_init(&iter, mmc->class_dev.class, NULL, NULL);
+ for (;;) {
+ dev = class_dev_iter_next(&iter);
+ if (!dev) {
+ printk(KERN_ERR "cw1200: %s is not found.\n",
+ pdata->mmc_id);
+ break;
+ } else {
+ struct mmc_host *host = container_of(dev,
+ struct mmc_host, class_dev);
+
+ if (dev_name(&host->class_dev) &&
+ strcmp(dev_name(&host->class_dev),
+ pdata->mmc_id))
+ continue;
+
+ mmc_detect_change(host, 10);
+ break;
+ }
+ }
+ mmc_free_host(mmc);
+ return 0;
+}
+
+static int cw1200_sdio_off(const struct cw1200_platform_data *pdata)
+{
+ int ret = 0;
+#ifndef CONFIG_CW1200_U5500_SUPPORT
+ const struct resource *reset = pdata->reset;
+ if (reset) {
+ gpio_set_value(reset->start, 0);
+ gpio_free(reset->start);
+ }
+#else
+ if (pdata->prcmu_ctrl)
+ ret = pdata->prcmu_ctrl(pdata, false);
+ msleep(50);
+#endif
+ cw1200_detect_card(pdata);
+ return ret;
+}
+
+static int cw1200_sdio_on(const struct cw1200_platform_data *pdata)
+{
+ int ret = 0;
+#ifndef CONFIG_CW1200_U5500_SUPPORT
+ const struct resource *reset = pdata->reset;
+ if (reset) {
+ gpio_request(reset->start, reset->name);
+ gpio_direction_output(reset->start, 1);
+ /* It is not stated in the datasheet, but at least some
+ * of devices have problems with reset if this stage
+ * is omited. */
+ msleep(50);
+ gpio_direction_output(reset->start, 0);
+ /* A valid reset shall be obtained by maintaining WRESETN
+ * active (low) for at least two cycles of LP_CLK after VDDIO
+ * is stable within it operating range. */
+ usleep_range(1000, 20000);
+ gpio_set_value(reset->start, 1);
+ /* The host should wait 32 ms after the WRESETN release
+ * for the on-chip LDO to stabilize */
+ msleep(32);
+ }
+#else
+ if (pdata->prcmu_ctrl)
+ ret = pdata->prcmu_ctrl(pdata, true);
+ msleep(50);
+#endif
+ cw1200_detect_card(pdata);
+ return ret;
+}
+
+static int cw1200_sdio_reset(struct sbus_priv *self)
+{
+ cw1200_sdio_off(self->pdata);
+ msleep(1000);
+ cw1200_sdio_on(self->pdata);
+ return 0;
+}
+
+static size_t cw1200_sdio_align_size(struct sbus_priv *self, size_t size)
+{
+ size_t aligned = sdio_align_size(self->func, size);
+ return aligned;
+}
+
+int cw1200_sdio_set_block_size(struct sbus_priv *self, size_t size)
+{
+ return sdio_set_block_size(self->func, size);
+}
+
+static int cw1200_sdio_pm(struct sbus_priv *self, bool suspend)
+{
+ int ret = 0;
+ const struct resource *irq = self->pdata->irq;
+
+ if (irq)
+ ret = irq_set_irq_wake(irq->start, suspend);
+
+ return ret;
+}
+
+static struct sbus_ops cw1200_sdio_sbus_ops = {
+ .sbus_memcpy_fromio = cw1200_sdio_memcpy_fromio,
+ .sbus_memcpy_toio = cw1200_sdio_memcpy_toio,
+ .lock = cw1200_sdio_lock,
+ .unlock = cw1200_sdio_unlock,
+ .irq_subscribe = cw1200_sdio_irq_subscribe,
+ .irq_unsubscribe = cw1200_sdio_irq_unsubscribe,
+ .reset = cw1200_sdio_reset,
+ .align_size = cw1200_sdio_align_size,
+ .power_mgmt = cw1200_sdio_pm,
+ .set_block_size = cw1200_sdio_set_block_size,
+};
+
+/* Probe Function to be called by SDIO stack when device is discovered */
+static int cw1200_sdio_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ struct sbus_priv *self;
+ int status;
+
+ cw1200_dbg(CW1200_DBG_INIT, "Probe called\n");
+
+ self = kzalloc(sizeof(*self), GFP_KERNEL);
+ if (!self) {
+ cw1200_dbg(CW1200_DBG_ERROR, "Can't allocate SDIO sbus_priv.");
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&self->lock);
+ self->pdata = cw1200_get_platform_data();
+ self->func = func;
+ sdio_set_drvdata(func, self);
+ sdio_claim_host(func);
+ sdio_enable_func(func);
+ sdio_release_host(func);
+
+ status = cw1200_core_probe(&cw1200_sdio_sbus_ops,
+ self, &func->dev, &self->core);
+ if (status) {
+ sdio_claim_host(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+ sdio_set_drvdata(func, NULL);
+ kfree(self);
+ }
+
+ return status;
+}
+
+/* Disconnect Function to be called by SDIO stack when
+ * device is disconnected */
+static void cw1200_sdio_disconnect(struct sdio_func *func)
+{
+ struct sbus_priv *self = sdio_get_drvdata(func);
+
+ if (self) {
+ if (self->core) {
+ cw1200_core_release(self->core);
+ self->core = NULL;
+ }
+ sdio_claim_host(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+ sdio_set_drvdata(func, NULL);
+ kfree(self);
+ }
+}
+
+static int cw1200_suspend(struct device *dev)
+{
+ int ret;
+ struct sdio_func *func = dev_to_sdio_func(dev);
+
+ /* Notify SDIO that CW1200 will remain powered during suspend */
+ ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+ if (ret)
+ cw1200_dbg(CW1200_DBG_ERROR,
+ "Error setting SDIO pm flags: %i\n", ret);
+
+ return ret;
+}
+
+static int cw1200_resume(struct device *dev)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops cw1200_pm_ops = {
+ .suspend = cw1200_suspend,
+ .resume = cw1200_resume,
+};
+
+static struct sdio_driver sdio_driver = {
+ .name = "cw1200_wlan",
+ .id_table = cw1200_sdio_ids,
+ .probe = cw1200_sdio_probe,
+ .remove = cw1200_sdio_disconnect,
+ .drv = {
+ .pm = &cw1200_pm_ops,
+ }
+};
+
+/* Init Module function -> Called by insmod */
+static int __init cw1200_sdio_init(void)
+{
+ const struct cw1200_platform_data *pdata;
+ int ret;
+
+ pdata = cw1200_get_platform_data();
+
+ ret = sdio_register_driver(&sdio_driver);
+ if (ret)
+ goto err_reg;
+
+ if (pdata->clk_ctrl) {
+ ret = pdata->clk_ctrl(pdata, true);
+ if (ret)
+ goto err_clk;
+ }
+
+ if (pdata->power_ctrl) {
+ ret = pdata->power_ctrl(pdata, true);
+ if (ret)
+ goto err_power;
+ }
+
+ ret = cw1200_sdio_on(pdata);
+ if (ret)
+ goto err_on;
+
+ return 0;
+
+err_on:
+ if (pdata->power_ctrl)
+ pdata->power_ctrl(pdata, false);
+err_power:
+ if (pdata->clk_ctrl)
+ pdata->clk_ctrl(pdata, false);
+err_clk:
+ sdio_unregister_driver(&sdio_driver);
+err_reg:
+ return ret;
+}
+
+/* Called at Driver Unloading */
+static void __exit cw1200_sdio_exit(void)
+{
+ const struct cw1200_platform_data *pdata;
+ pdata = cw1200_get_platform_data();
+ sdio_unregister_driver(&sdio_driver);
+ cw1200_sdio_off(pdata);
+ if (pdata->power_ctrl)
+ pdata->power_ctrl(pdata, false);
+ if (pdata->clk_ctrl)
+ pdata->clk_ctrl(pdata, false);
+}
+
+#if defined(CONFIG_CW1200_STANDALONE)
+const struct cw1200_platform_data *cw1200_get_platform_data()
+{
+ static const struct cw1200_platform_data pdata = {
+ .mmc_id = cw1200_mmc_id,
+ };
+ return &pdata;
+}
+#endif /* CONFIG_CW1200_STANDALONE */
+
+module_init(cw1200_sdio_init);
+module_exit(cw1200_sdio_exit);
--
1.7.9


2012-03-02 20:26:48

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 02/21] cw1200: cw1200_plat.h, definition of the driver'ss platform data.

The cw1200 driver requires platform data to be provided by arch code.
The purpose of the platform data is to expose resources for out-of-band
(comparing to main interface, sdio etc) signaling, like OOB irq, clock,
power and reset control.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/cw1200_plat.h | 28 ++++++++++++++++++++++++++++
1 files changed, 28 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/cw1200_plat.h

diff --git a/drivers/net/wireless/cw1200/cw1200_plat.h b/drivers/net/wireless/cw1200/cw1200_plat.h
new file mode 100644
index 0000000..3a73183
--- /dev/null
+++ b/drivers/net/wireless/cw1200/cw1200_plat.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011-2012
+ *
+ * Author: Dmitry Tarnyagin <[email protected]>
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#ifndef CW1200_PLAT_H_INCLUDED
+#define CW1200_PLAT_H_INCLUDED
+
+#include <linux/ioport.h>
+
+struct cw1200_platform_data {
+ const char *mmc_id;
+ const struct resource *irq;
+ const struct resource *reset;
+ int (*power_ctrl)(const struct cw1200_platform_data *pdata,
+ bool enable);
+ int (*clk_ctrl)(const struct cw1200_platform_data *pdata,
+ bool enable);
+ int (*prcmu_ctrl)(const struct cw1200_platform_data *pdata,
+ bool enable);
+};
+
+/* Declaration only. Should be implemented in arch/xxx/mach-yyy */
+const struct cw1200_platform_data *cw1200_get_platform_data(void);
+
+#endif /* CW1200_PLAT_H_INCLUDED */
--
1.7.9


2012-03-02 20:27:05

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 17/21] cw1200: pm.*, power management code.

The code implements WoW mac80211 callbacks. The suspend strategy
is taken more form Android world, where reject of an ongoing suspend
is something quite normal. I understand that it is not a right
approach for vanilla Linux, where there is no retry in suspend.

TODO for differentiating suspend startegy is written. To be done
when bh is converted to a workqueue.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/pm.c | 421 ++++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/pm.h | 41 ++++
2 files changed, 462 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/pm.c
create mode 100644 drivers/net/wireless/cw1200/pm.h

diff --git a/drivers/net/wireless/cw1200/pm.c b/drivers/net/wireless/cw1200/pm.c
new file mode 100644
index 0000000..1613efa
--- /dev/null
+++ b/drivers/net/wireless/cw1200/pm.c
@@ -0,0 +1,421 @@
+/*
+ * Mac80211 power management API for ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2011-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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/platform_device.h>
+#include <linux/if_ether.h>
+#include "cw1200.h"
+#include "pm.h"
+#include "sta.h"
+#include "bh.h"
+#include "sbus.h"
+
+#define CW1200_BEACON_SKIPPING_MULTIPLIER 3
+
+struct cw1200_udp_port_filter {
+ struct wsm_udp_port_filter_hdr hdr;
+ struct wsm_udp_port_filter dhcp;
+ struct wsm_udp_port_filter upnp;
+} __packed;
+
+struct cw1200_ether_type_filter {
+ struct wsm_ether_type_filter_hdr hdr;
+ struct wsm_ether_type_filter ip;
+ struct wsm_ether_type_filter pae;
+ struct wsm_ether_type_filter wapi;
+} __packed;
+
+static struct cw1200_udp_port_filter cw1200_udp_port_filter_on = {
+ .hdr.nrFilters = 2,
+ .dhcp = {
+ .filterAction = WSM_FILTER_ACTION_FILTER_OUT,
+ .portType = WSM_FILTER_PORT_TYPE_DST,
+ .udpPort = __cpu_to_le16(67),
+ },
+ .upnp = {
+ .filterAction = WSM_FILTER_ACTION_FILTER_OUT,
+ .portType = WSM_FILTER_PORT_TYPE_DST,
+ .udpPort = __cpu_to_le16(1900),
+ },
+ /* Please add other known ports to be filtered out here and
+ * update nrFilters field in the header.
+ * Up to 4 filters are allowed. */
+};
+
+static struct wsm_udp_port_filter_hdr cw1200_udp_port_filter_off = {
+ .nrFilters = 0,
+};
+
+#ifndef ETH_P_WAPI
+#define ETH_P_WAPI 0x88B4
+#endif
+
+static struct cw1200_ether_type_filter cw1200_ether_type_filter_on = {
+ .hdr.nrFilters = 3,
+ .ip = {
+ .filterAction = WSM_FILTER_ACTION_FILTER_IN,
+ .etherType = __cpu_to_le16(ETH_P_IP),
+ },
+ .pae = {
+ .filterAction = WSM_FILTER_ACTION_FILTER_IN,
+ .etherType = __cpu_to_le16(ETH_P_PAE),
+ },
+ .wapi = {
+ .filterAction = WSM_FILTER_ACTION_FILTER_IN,
+ .etherType = __cpu_to_le16(ETH_P_WAPI),
+ },
+ /* Please add other known ether types to be filtered out here and
+ * update nrFilters field in the header.
+ * Up to 4 filters are allowed. */
+};
+
+static struct wsm_ether_type_filter_hdr cw1200_ether_type_filter_off = {
+ .nrFilters = 0,
+};
+
+static int cw1200_suspend_late(struct device *dev);
+static void cw1200_pm_release(struct device *dev);
+static int cw1200_pm_probe(struct platform_device *pdev);
+
+/* private */
+struct cw1200_suspend_state {
+ unsigned long bss_loss_tmo;
+ unsigned long connection_loss_tmo;
+ unsigned long join_tmo;
+ unsigned long direct_probe;
+ unsigned long link_id_gc;
+ bool beacon_skipping;
+};
+
+static const struct dev_pm_ops cw1200_pm_ops = {
+ .suspend_noirq = cw1200_suspend_late,
+};
+
+static struct platform_driver cw1200_power_driver = {
+ .probe = cw1200_pm_probe,
+ .driver = {
+ .name = "cw1200_power",
+ .pm = &cw1200_pm_ops,
+ },
+};
+
+static int cw1200_pm_init_common(struct cw1200_pm_state *pm,
+ struct cw1200_common *priv)
+{
+ int ret;
+
+ spin_lock_init(&pm->lock);
+ ret = platform_driver_register(&cw1200_power_driver);
+ if (ret)
+ return ret;
+ pm->pm_dev = platform_device_alloc("cw1200_power", 0);
+ if (!pm->pm_dev) {
+ platform_driver_unregister(&cw1200_power_driver);
+ return -ENOMEM;
+ }
+
+ pm->pm_dev->dev.platform_data = priv;
+ ret = platform_device_add(pm->pm_dev);
+ if (ret) {
+ kfree(pm->pm_dev);
+ pm->pm_dev = NULL;
+ }
+
+ return ret;
+}
+
+static void cw1200_pm_deinit_common(struct cw1200_pm_state *pm)
+{
+ platform_driver_unregister(&cw1200_power_driver);
+ if (pm->pm_dev) {
+ pm->pm_dev->dev.platform_data = NULL;
+ platform_device_unregister(pm->pm_dev);
+ pm->pm_dev = NULL;
+ }
+}
+
+static void cw1200_pm_stay_awake_tmo(unsigned long arg)
+{
+}
+
+int cw1200_pm_init(struct cw1200_pm_state *pm,
+ struct cw1200_common *priv)
+{
+ int ret = cw1200_pm_init_common(pm, priv);
+ if (!ret) {
+ init_timer(&pm->stay_awake);
+ pm->stay_awake.data = (unsigned long)pm;
+ pm->stay_awake.function = cw1200_pm_stay_awake_tmo;
+ }
+ return ret;
+}
+
+void cw1200_pm_deinit(struct cw1200_pm_state *pm)
+{
+ del_timer_sync(&pm->stay_awake);
+ cw1200_pm_deinit_common(pm);
+}
+
+void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
+ unsigned long tmo)
+{
+ long cur_tmo;
+ spin_lock_bh(&pm->lock);
+ cur_tmo = pm->stay_awake.expires - jiffies;
+ if (!timer_pending(&pm->stay_awake) ||
+ cur_tmo < (long)tmo)
+ mod_timer(&pm->stay_awake, jiffies + tmo);
+ spin_unlock_bh(&pm->lock);
+}
+
+static long cw1200_suspend_work(struct delayed_work *work)
+{
+ int ret = cancel_delayed_work(work);
+ long tmo;
+ if (ret > 0) {
+ /* Timer is pending */
+ tmo = work->timer.expires - jiffies;
+ if (tmo < 0)
+ tmo = 0;
+ } else {
+ tmo = -1;
+ }
+ return tmo;
+}
+
+static int cw1200_resume_work(struct cw1200_common *priv,
+ struct delayed_work *work,
+ unsigned long tmo)
+{
+ if ((long)tmo < 0)
+ return 1;
+
+ return queue_delayed_work(priv->workqueue, work, tmo);
+}
+
+static int cw1200_suspend_late(struct device *dev)
+{
+ struct cw1200_common *priv = dev->platform_data;
+ if (atomic_read(&priv->bh_rx)) {
+ wiphy_dbg(priv->hw->wiphy,
+ "%s: Suspend interrupted.\n",
+ __func__);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static void cw1200_pm_release(struct device *dev)
+{
+}
+
+static int cw1200_pm_probe(struct platform_device *pdev)
+{
+ pdev->dev.release = cw1200_pm_release;
+ return 0;
+}
+
+int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
+{
+ struct cw1200_common *priv = hw->priv;
+ struct cw1200_pm_state *pm_state = &priv->pm_state;
+ struct cw1200_suspend_state *state;
+ int ret;
+
+ spin_lock_bh(&pm_state->lock);
+ ret = timer_pending(&pm_state->stay_awake);
+ spin_unlock_bh(&pm_state->lock);
+ if (ret)
+ return -EAGAIN;
+
+ /* Do not suspend when datapath is not idle */
+ if (priv->tx_queue_stats.num_queued)
+ return -EBUSY;
+
+ /* Make sure there is no configuration requests in progress. */
+ if (!mutex_trylock(&priv->conf_mutex))
+ return -EBUSY;
+
+ /* Ensure pending operations are done.
+ * Note also that wow_suspend must return in ~2.5sec, before
+ * watchdog is triggered. */
+ if (priv->channel_switch_in_progress)
+ goto revert1;
+
+ /* Do not suspend when join work is scheduled */
+ if (work_pending(&priv->join_work))
+ goto revert1;
+
+ /* Do not suspend when scanning */
+ if (down_trylock(&priv->scan.lock))
+ goto revert1;
+
+ /* Lock TX. */
+ wsm_lock_tx_async(priv);
+
+ /* Wait to avoid possible race with bh code.
+ * But do not wait too long... */
+ if (wait_event_timeout(priv->bh_evt_wq,
+ !priv->hw_bufs_used, HZ / 10) <= 0)
+ goto revert2;
+
+ /* Set UDP filter */
+ wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_on.hdr);
+
+ /* Set ethernet frame type filter */
+ wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_on.hdr);
+
+ /* Allocate state */
+ state = kzalloc(sizeof(struct cw1200_suspend_state), GFP_KERNEL);
+ if (!state)
+ goto revert3;
+
+ /* Store delayed work states. */
+ state->bss_loss_tmo =
+ cw1200_suspend_work(&priv->bss_loss_work);
+ state->connection_loss_tmo =
+ cw1200_suspend_work(&priv->connection_loss_work);
+ state->join_tmo =
+ cw1200_suspend_work(&priv->join_timeout);
+ state->direct_probe =
+ cw1200_suspend_work(&priv->scan.probe_work);
+ state->link_id_gc =
+ cw1200_suspend_work(&priv->link_id_gc_work);
+
+ /* Enable beacon skipping */
+ if (priv->join_status == CW1200_JOIN_STATUS_STA
+ && priv->join_dtim_period
+ && !priv->has_multicast_subscription) {
+ state->beacon_skipping = true;
+ wsm_set_beacon_wakeup_period(priv,
+ priv->join_dtim_period,
+ CW1200_BEACON_SKIPPING_MULTIPLIER *
+ priv->join_dtim_period);
+ }
+
+ /* Stop serving thread */
+ if (cw1200_bh_suspend(priv))
+ goto revert4;
+
+ ret = timer_pending(&priv->mcast_timeout);
+ if (ret)
+ goto revert5;
+
+ /* Cancel block ack stat timer */
+ del_timer_sync(&priv->ba_timer);
+
+ /* Store suspend state */
+ pm_state->suspend_state = state;
+
+ /* Enable IRQ wake */
+ ret = priv->sbus_ops->power_mgmt(priv->sbus_priv, true);
+ if (ret) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: PM request failed: %d. WoW is disabled.\n",
+ __func__, ret);
+ cw1200_wow_resume(hw);
+ return -EBUSY;
+ }
+
+ /* Force resume if event is coming from the device. */
+ if (atomic_read(&priv->bh_rx)) {
+ cw1200_wow_resume(hw);
+ return -EAGAIN;
+ }
+
+ return 0;
+
+revert5:
+ WARN_ON(cw1200_bh_resume(priv));
+revert4:
+ cw1200_resume_work(priv, &priv->bss_loss_work,
+ state->bss_loss_tmo);
+ cw1200_resume_work(priv, &priv->connection_loss_work,
+ state->connection_loss_tmo);
+ cw1200_resume_work(priv, &priv->join_timeout,
+ state->join_tmo);
+ cw1200_resume_work(priv, &priv->scan.probe_work,
+ state->direct_probe);
+ cw1200_resume_work(priv, &priv->link_id_gc_work,
+ state->link_id_gc);
+ kfree(state);
+revert3:
+ wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
+ wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
+revert2:
+ wsm_unlock_tx(priv);
+ up(&priv->scan.lock);
+revert1:
+ mutex_unlock(&priv->conf_mutex);
+ return -EBUSY;
+}
+
+int cw1200_wow_resume(struct ieee80211_hw *hw)
+{
+ struct cw1200_common *priv = hw->priv;
+ struct cw1200_pm_state *pm_state = &priv->pm_state;
+ struct cw1200_suspend_state *state;
+
+ state = pm_state->suspend_state;
+ pm_state->suspend_state = NULL;
+
+ /* Disable IRQ wake */
+ priv->sbus_ops->power_mgmt(priv->sbus_priv, false);
+
+ /* Resume BH thread */
+ WARN_ON(cw1200_bh_resume(priv));
+
+ if (state->beacon_skipping) {
+ wsm_set_beacon_wakeup_period(priv, priv->beacon_int *
+ priv->join_dtim_period >
+ MAX_BEACON_SKIP_TIME_MS ? 1 :
+ priv->join_dtim_period, 0);
+ state->beacon_skipping = false;
+ }
+
+ /* Resume delayed work */
+ cw1200_resume_work(priv, &priv->bss_loss_work,
+ state->bss_loss_tmo);
+ cw1200_resume_work(priv, &priv->connection_loss_work,
+ state->connection_loss_tmo);
+ cw1200_resume_work(priv, &priv->join_timeout,
+ state->join_tmo);
+ cw1200_resume_work(priv, &priv->scan.probe_work,
+ state->direct_probe);
+ cw1200_resume_work(priv, &priv->link_id_gc_work,
+ state->link_id_gc);
+
+ /* Restart block ack stat */
+ spin_lock_bh(&priv->ba_lock);
+ if (priv->ba_cnt)
+ mod_timer(&priv->ba_timer,
+ jiffies + CW1200_BLOCK_ACK_INTERVAL);
+ spin_unlock_bh(&priv->ba_lock);
+
+ /* Remove UDP port filter */
+ wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
+
+ /* Remove ethernet frame type filter */
+ wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
+
+ /* Unlock datapath */
+ wsm_unlock_tx(priv);
+
+ /* Unlock scan */
+ up(&priv->scan.lock);
+
+ /* Unlock configuration mutex */
+ mutex_unlock(&priv->conf_mutex);
+
+ /* Free memory */
+ kfree(state);
+
+ return 0;
+}
diff --git a/drivers/net/wireless/cw1200/pm.h b/drivers/net/wireless/cw1200/pm.h
new file mode 100644
index 0000000..36afe63
--- /dev/null
+++ b/drivers/net/wireless/cw1200/pm.h
@@ -0,0 +1,41 @@
+/*
+ * Mac80211 power management interface for ST-Ericsson CW1200 mac80211 drivers
+ *
+ * Copyright (c) 2011-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef PM_H_INCLUDED
+#define PM_H_INCLUDED
+
+/* ******************************************************************** */
+/* mac80211 API */
+
+#ifdef CONFIG_PM
+
+/* extern */ struct cw1200_common;
+/* private */ struct cw1200_suspend_state;
+
+struct cw1200_pm_state {
+ struct cw1200_suspend_state *suspend_state;
+ struct timer_list stay_awake;
+ struct platform_device *pm_dev;
+ spinlock_t lock;
+};
+
+int cw1200_pm_init(struct cw1200_pm_state *pm,
+ struct cw1200_common *priv);
+void cw1200_pm_deinit(struct cw1200_pm_state *pm);
+void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
+ unsigned long tmo);
+int cw1200_wow_suspend(struct ieee80211_hw *hw,
+ struct cfg80211_wowlan *wowlan);
+int cw1200_wow_resume(struct ieee80211_hw *hw);
+
+#endif /* CONFIG_PM */
+
+#endif
--
1.7.9


2012-03-02 20:27:01

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 08/21] cw1200: wsm.*, implementation of device high-level interface.

WSM is a message interface between the host and the device.
The patch defines and implements WSM abstraction layer.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/wsm.c | 1828 ++++++++++++++++++++++++++++++++++++
drivers/net/wireless/cw1200/wsm.h | 1845 +++++++++++++++++++++++++++++++++++++
2 files changed, 3673 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/wsm.c
create mode 100644 drivers/net/wireless/cw1200/wsm.h

diff --git a/drivers/net/wireless/cw1200/wsm.c b/drivers/net/wireless/cw1200/wsm.c
new file mode 100644
index 0000000..7ee6e52
--- /dev/null
+++ b/drivers/net/wireless/cw1200/wsm.c
@@ -0,0 +1,1828 @@
+/*
+ * WSM host interface (HI) implementation for
+ * ST-Ericsson CW1200 mac80211 drivers.
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * 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/skbuff.h>
+#include <linux/wait.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/random.h>
+
+#include "cw1200.h"
+#include "wsm.h"
+#include "bh.h"
+#include "debug.h"
+#include "itp.h"
+
+#if defined(CONFIG_CW1200_WSM_DEBUG)
+#define wsm_printk(...) printk(__VA_ARGS__)
+#else
+#define wsm_printk(...)
+#endif
+
+#define WSM_CMD_TIMEOUT (2 * HZ) /* With respect to interrupt loss */
+#define WSM_CMD_JOIN_TIMEOUT (7 * HZ) /* Join timeout is 5 sec. in FW */
+#define WSM_CMD_START_TIMEOUT (7 * HZ)
+#define WSM_CMD_RESET_TIMEOUT (3 * HZ) /* 2 sec. timeout was observed. */
+
+#define WSM_SKIP(buf, size) \
+ do { \
+ if (unlikely((buf)->data + size > (buf)->end)) \
+ goto underflow; \
+ (buf)->data += size; \
+ } while (0)
+
+#define WSM_GET(buf, ptr, size) \
+ do { \
+ if (unlikely((buf)->data + size > (buf)->end)) \
+ goto underflow; \
+ memcpy(ptr, (buf)->data, size); \
+ (buf)->data += size; \
+ } while (0)
+
+#define __WSM_GET(buf, type, cvt) \
+ ({ \
+ type val; \
+ if (unlikely((buf)->data + sizeof(type) > (buf)->end)) \
+ goto underflow; \
+ val = cvt(*(type *)(buf)->data); \
+ (buf)->data += sizeof(type); \
+ val; \
+ })
+
+#define WSM_GET8(buf) __WSM_GET(buf, u8, (u8))
+#define WSM_GET16(buf) __WSM_GET(buf, u16, __le16_to_cpu)
+#define WSM_GET32(buf) __WSM_GET(buf, u32, __le32_to_cpu)
+
+#define WSM_PUT(buf, ptr, size) \
+ do { \
+ if (unlikely((buf)->data + size > (buf)->end)) \
+ if (unlikely(wsm_buf_reserve((buf), size))) \
+ goto nomem; \
+ memcpy((buf)->data, ptr, size); \
+ (buf)->data += size; \
+ } while (0)
+
+#define __WSM_PUT(buf, val, type, cvt) \
+ do { \
+ if (unlikely((buf)->data + sizeof(type) > (buf)->end)) \
+ if (unlikely(wsm_buf_reserve((buf), sizeof(type)))) \
+ goto nomem; \
+ *(type *)(buf)->data = cvt(val); \
+ (buf)->data += sizeof(type); \
+ } while (0)
+
+#define WSM_PUT8(buf, val) __WSM_PUT(buf, val, u8, (u8))
+#define WSM_PUT16(buf, val) __WSM_PUT(buf, val, u16, __cpu_to_le16)
+#define WSM_PUT32(buf, val) __WSM_PUT(buf, val, u32, __cpu_to_le32)
+
+static void wsm_buf_reset(struct wsm_buf *buf);
+static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size);
+
+static int wsm_cmd_send(struct cw1200_common *priv,
+ struct wsm_buf *buf,
+ void *arg, u16 cmd, long tmo);
+
+static inline void wsm_cmd_lock(struct cw1200_common *priv)
+{
+ mutex_lock(&priv->wsm_cmd_mux);
+}
+
+static inline void wsm_cmd_unlock(struct cw1200_common *priv)
+{
+ mutex_unlock(&priv->wsm_cmd_mux);
+}
+
+/* ******************************************************************** */
+/* WSM API implementation */
+
+static int wsm_generic_confirm(struct cw1200_common *priv,
+ void *arg,
+ struct wsm_buf *buf)
+{
+ u32 status = WSM_GET32(buf);
+ if (status != WSM_STATUS_SUCCESS)
+ return -EINVAL;
+ return 0;
+
+underflow:
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+int wsm_configuration(struct cw1200_common *priv, struct wsm_configuration *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT32(buf, arg->dot11MaxTransmitMsduLifeTime);
+ WSM_PUT32(buf, arg->dot11MaxReceiveLifeTime);
+ WSM_PUT32(buf, arg->dot11RtsThreshold);
+
+ /* DPD block. */
+ WSM_PUT16(buf, arg->dpdData_size + 12);
+ WSM_PUT16(buf, 1); /* DPD version */
+ WSM_PUT(buf, arg->dot11StationId, ETH_ALEN);
+ WSM_PUT16(buf, 5); /* DPD flags */
+ WSM_PUT(buf, arg->dpdData, arg->dpdData_size);
+
+ ret = wsm_cmd_send(priv, buf, arg, 0x0009, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+static int wsm_configuration_confirm(struct cw1200_common *priv,
+ struct wsm_configuration *arg,
+ struct wsm_buf *buf)
+{
+ int i;
+ int status;
+
+ status = WSM_GET32(buf);
+ if (WARN_ON(status != WSM_STATUS_SUCCESS))
+ return -EINVAL;
+
+ WSM_GET(buf, arg->dot11StationId, ETH_ALEN);
+ arg->dot11FrequencyBandsSupported = WSM_GET8(buf);
+ WSM_SKIP(buf, 1);
+ arg->supportedRateMask = WSM_GET32(buf);
+ for (i = 0; i < 2; ++i) {
+ arg->txPowerRange[i].min_power_level = WSM_GET32(buf);
+ arg->txPowerRange[i].max_power_level = WSM_GET32(buf);
+ arg->txPowerRange[i].stepping = WSM_GET32(buf);
+ }
+ return 0;
+
+underflow:
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+/* ******************************************************************** */
+
+int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+ u16 cmd = 0x000A | WSM_TX_LINK_ID(arg->link_id);
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT32(buf, arg->reset_statistics ? 0 : 1);
+ ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_RESET_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+struct wsm_mib {
+ u16 mibId;
+ void *buf;
+ size_t buf_size;
+};
+
+int wsm_read_mib(struct cw1200_common *priv, u16 mibId, void *_buf,
+ size_t buf_size)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+ struct wsm_mib mib_buf = {
+ .mibId = mibId,
+ .buf = _buf,
+ .buf_size = buf_size,
+ };
+ wsm_cmd_lock(priv);
+
+ WSM_PUT16(buf, mibId);
+ WSM_PUT16(buf, 0);
+
+ ret = wsm_cmd_send(priv, buf, &mib_buf, 0x0005, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+static int wsm_read_mib_confirm(struct cw1200_common *priv,
+ struct wsm_mib *arg,
+ struct wsm_buf *buf)
+{
+ u16 size;
+ if (WARN_ON(WSM_GET32(buf) != WSM_STATUS_SUCCESS))
+ return -EINVAL;
+
+ if (WARN_ON(WSM_GET16(buf) != arg->mibId))
+ return -EINVAL;
+
+ size = WSM_GET16(buf);
+ if (size > arg->buf_size)
+ size = arg->buf_size;
+
+ WSM_GET(buf, arg->buf, size);
+ arg->buf_size = size;
+ return 0;
+
+underflow:
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+/* ******************************************************************** */
+
+int wsm_write_mib(struct cw1200_common *priv, u16 mibId, void *_buf,
+ size_t buf_size)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+ struct wsm_mib mib_buf = {
+ .mibId = mibId,
+ .buf = _buf,
+ .buf_size = buf_size,
+ };
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT16(buf, mibId);
+ WSM_PUT16(buf, buf_size);
+ WSM_PUT(buf, _buf, buf_size);
+
+ ret = wsm_cmd_send(priv, buf, &mib_buf, 0x0006, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+static int wsm_write_mib_confirm(struct cw1200_common *priv,
+ struct wsm_mib *arg,
+ struct wsm_buf *buf)
+{
+ int ret;
+
+ ret = wsm_generic_confirm(priv, arg, buf);
+ if (ret)
+ return ret;
+
+ if (arg->mibId == 0x1006) {
+ /* OperationalMode: update PM status. */
+ const char *p = arg->buf;
+ cw1200_enable_powersave(priv,
+ (p[0] & 0x0F) ? true : false);
+ }
+ return 0;
+}
+
+/* ******************************************************************** */
+
+int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg)
+{
+ int i;
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ if (unlikely(arg->numOfChannels > 48))
+ return -EINVAL;
+
+ if (unlikely(arg->numOfSSIDs > 2))
+ return -EINVAL;
+
+ if (unlikely(arg->band > 1))
+ return -EINVAL;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, arg->band);
+ WSM_PUT8(buf, arg->scanType);
+ WSM_PUT8(buf, arg->scanFlags);
+ WSM_PUT8(buf, arg->maxTransmitRate);
+ WSM_PUT32(buf, arg->autoScanInterval);
+ WSM_PUT8(buf, arg->numOfProbeRequests);
+ WSM_PUT8(buf, arg->numOfChannels);
+ WSM_PUT8(buf, arg->numOfSSIDs);
+ WSM_PUT8(buf, arg->probeDelay);
+
+ for (i = 0; i < arg->numOfChannels; ++i) {
+ WSM_PUT16(buf, arg->ch[i].number);
+ WSM_PUT16(buf, 0);
+ WSM_PUT32(buf, arg->ch[i].minChannelTime);
+ WSM_PUT32(buf, arg->ch[i].maxChannelTime);
+ WSM_PUT32(buf, 0);
+ }
+
+ for (i = 0; i < arg->numOfSSIDs; ++i) {
+ WSM_PUT32(buf, arg->ssids[i].length);
+ WSM_PUT(buf, &arg->ssids[i].ssid[0],
+ sizeof(arg->ssids[i].ssid));
+ }
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0007, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_stop_scan(struct cw1200_common *priv)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+ wsm_cmd_lock(priv);
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0008, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+}
+
+
+static int wsm_tx_confirm(struct cw1200_common *priv,
+ struct wsm_buf *buf,
+ int link_id)
+{
+ struct wsm_tx_confirm tx_confirm;
+
+ tx_confirm.packetID = WSM_GET32(buf);
+ tx_confirm.status = WSM_GET32(buf);
+ tx_confirm.txedRate = WSM_GET8(buf);
+ tx_confirm.ackFailures = WSM_GET8(buf);
+ tx_confirm.flags = WSM_GET16(buf);
+ tx_confirm.mediaDelay = WSM_GET32(buf);
+ tx_confirm.txQueueDelay = WSM_GET32(buf);
+ tx_confirm.link_id = link_id;
+
+ if (priv->wsm_cbc.tx_confirm)
+ priv->wsm_cbc.tx_confirm(priv, &tx_confirm);
+ return 0;
+
+underflow:
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+static int wsm_multi_tx_confirm(struct cw1200_common *priv,
+ struct wsm_buf *buf, int link_id)
+{
+ int ret;
+ int count;
+ int i;
+
+ count = WSM_GET32(buf);
+ if (WARN_ON(count <= 0))
+ return -EINVAL;
+ else if (count > 1) {
+ ret = wsm_release_tx_buffer(priv, count - 1);
+ if (ret < 0)
+ return ret;
+ else if (ret > 0)
+ cw1200_bh_wakeup(priv);
+ }
+
+ cw1200_debug_txed_multi(priv, count);
+ for (i = 0; i < count; ++i) {
+ ret = wsm_tx_confirm(priv, buf, link_id);
+ if (ret)
+ return ret;
+ }
+ return ret;
+
+underflow:
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+/* ******************************************************************** */
+
+static int wsm_join_confirm(struct cw1200_common *priv,
+ struct wsm_join *arg,
+ struct wsm_buf *buf)
+{
+ if (WARN_ON(WSM_GET32(buf) != WSM_STATUS_SUCCESS))
+ return -EINVAL;
+
+ arg->minPowerLevel = WSM_GET32(buf);
+ arg->maxPowerLevel = WSM_GET32(buf);
+
+ return 0;
+
+underflow:
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+int wsm_join(struct cw1200_common *priv, struct wsm_join *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, arg->mode);
+ WSM_PUT8(buf, arg->band);
+ WSM_PUT16(buf, arg->channelNumber);
+ WSM_PUT(buf, &arg->bssid[0], sizeof(arg->bssid));
+ WSM_PUT16(buf, arg->atimWindow);
+ WSM_PUT8(buf, arg->preambleType);
+ WSM_PUT8(buf, arg->probeForJoin);
+ WSM_PUT8(buf, arg->dtimPeriod);
+ WSM_PUT8(buf, arg->flags);
+ WSM_PUT32(buf, arg->ssidLength);
+ WSM_PUT(buf, &arg->ssid[0], sizeof(arg->ssid));
+ WSM_PUT32(buf, arg->beaconInterval);
+ WSM_PUT32(buf, arg->basicRateSet);
+
+ priv->tx_burst_idx = -1;
+ ret = wsm_cmd_send(priv, buf, arg, 0x000B, WSM_CMD_JOIN_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_set_bss_params(struct cw1200_common *priv,
+ const struct wsm_set_bss_params *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, 0);
+ WSM_PUT8(buf, arg->beaconLostCount);
+ WSM_PUT16(buf, arg->aid);
+ WSM_PUT32(buf, arg->operationalRateSet);
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0011, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT(buf, arg, sizeof(*arg));
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x000C, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_remove_key(struct cw1200_common *priv, const struct wsm_remove_key *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, arg->entryIndex);
+ WSM_PUT8(buf, 0);
+ WSM_PUT16(buf, 0);
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x000D, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_set_tx_queue_params(struct cw1200_common *priv,
+ const struct wsm_set_tx_queue_params *arg, u8 id)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+ u8 queue_id_to_wmm_aci[] = {3, 2, 0, 1};
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, queue_id_to_wmm_aci[id]);
+ WSM_PUT8(buf, 0);
+ WSM_PUT8(buf, arg->ackPolicy);
+ WSM_PUT8(buf, 0);
+ WSM_PUT32(buf, arg->maxTransmitLifetime);
+ WSM_PUT16(buf, arg->allowedMediumTime);
+ WSM_PUT16(buf, 0);
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0012, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_set_edca_params(struct cw1200_common *priv,
+ const struct wsm_edca_params *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ /* Implemented according to specification. */
+
+ WSM_PUT16(buf, arg->params[3].cwMin);
+ WSM_PUT16(buf, arg->params[2].cwMin);
+ WSM_PUT16(buf, arg->params[1].cwMin);
+ WSM_PUT16(buf, arg->params[0].cwMin);
+
+ WSM_PUT16(buf, arg->params[3].cwMax);
+ WSM_PUT16(buf, arg->params[2].cwMax);
+ WSM_PUT16(buf, arg->params[1].cwMax);
+ WSM_PUT16(buf, arg->params[0].cwMax);
+
+ WSM_PUT8(buf, arg->params[3].aifns);
+ WSM_PUT8(buf, arg->params[2].aifns);
+ WSM_PUT8(buf, arg->params[1].aifns);
+ WSM_PUT8(buf, arg->params[0].aifns);
+
+ WSM_PUT16(buf, arg->params[3].txOpLimit);
+ WSM_PUT16(buf, arg->params[2].txOpLimit);
+ WSM_PUT16(buf, arg->params[1].txOpLimit);
+ WSM_PUT16(buf, arg->params[0].txOpLimit);
+
+ WSM_PUT32(buf, arg->params[3].maxReceiveLifetime);
+ WSM_PUT32(buf, arg->params[2].maxReceiveLifetime);
+ WSM_PUT32(buf, arg->params[1].maxReceiveLifetime);
+ WSM_PUT32(buf, arg->params[0].maxReceiveLifetime);
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0013, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_switch_channel(struct cw1200_common *priv,
+ const struct wsm_switch_channel *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_lock_tx(priv);
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, arg->channelMode);
+ WSM_PUT8(buf, arg->channelSwitchCount);
+ WSM_PUT16(buf, arg->newChannelNumber);
+
+ priv->channel_switch_in_progress = 1;
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0016, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ if (ret) {
+ wsm_unlock_tx(priv);
+ priv->channel_switch_in_progress = 0;
+ }
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ wsm_unlock_tx(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, arg->pmMode);
+ WSM_PUT8(buf, arg->fastPsmIdlePeriod);
+ WSM_PUT8(buf, arg->apPsmChangePeriod);
+ WSM_PUT8(buf, arg->minAutoPsPollPeriod);
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0010, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT8(buf, arg->mode);
+ WSM_PUT8(buf, arg->band);
+ WSM_PUT16(buf, arg->channelNumber);
+ WSM_PUT32(buf, arg->CTWindow);
+ WSM_PUT32(buf, arg->beaconInterval);
+ WSM_PUT8(buf, arg->DTIMPeriod);
+ WSM_PUT8(buf, arg->preambleType);
+ WSM_PUT8(buf, arg->probeDelay);
+ WSM_PUT8(buf, arg->ssidLength);
+ WSM_PUT(buf, arg->ssid, sizeof(arg->ssid));
+ WSM_PUT32(buf, arg->basicRateSet);
+
+ priv->tx_burst_idx = -1;
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0017, WSM_CMD_START_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_beacon_transmit(struct cw1200_common *priv,
+ const struct wsm_beacon_transmit *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT32(buf, arg->enableBeaconing ? 1 : 0);
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0018, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_start_find(struct cw1200_common *priv)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+ ret = wsm_cmd_send(priv, buf, NULL, 0x0019, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+}
+
+/* ******************************************************************** */
+
+int wsm_stop_find(struct cw1200_common *priv)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+ ret = wsm_cmd_send(priv, buf, NULL, 0x001A, WSM_CMD_TIMEOUT);
+ wsm_cmd_unlock(priv);
+ return ret;
+}
+
+/* ******************************************************************** */
+
+int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+ u16 cmd = 0x001C | WSM_TX_LINK_ID(arg->link_id);
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT(buf, &arg->mac_addr[0], sizeof(arg->mac_addr));
+ WSM_PUT16(buf, 0);
+
+ ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+}
+
+/* ******************************************************************** */
+
+int wsm_update_ie(struct cw1200_common *priv,
+ const struct wsm_update_ie *arg)
+{
+ int ret;
+ struct wsm_buf *buf = &priv->wsm_cmd_buf;
+
+ wsm_cmd_lock(priv);
+
+ WSM_PUT16(buf, arg->what);
+ WSM_PUT16(buf, arg->count);
+ WSM_PUT(buf, arg->ies, arg->length);
+
+ ret = wsm_cmd_send(priv, buf, NULL, 0x001B, WSM_CMD_TIMEOUT);
+
+ wsm_cmd_unlock(priv);
+ return ret;
+
+nomem:
+ wsm_cmd_unlock(priv);
+ return -ENOMEM;
+
+}
+
+/* ******************************************************************** */
+/* WSM indication events implementation */
+
+static int wsm_startup_indication(struct cw1200_common *priv,
+ struct wsm_buf *buf)
+{
+ u16 status;
+ char fw_label[129];
+ static const char * const fw_types[] = {
+ "ETF",
+ "WFM",
+ "WSM",
+ "HI test",
+ "Platform test"
+ };
+
+ priv->wsm_caps.numInpChBufs = WSM_GET16(buf);
+ priv->wsm_caps.sizeInpChBuf = WSM_GET16(buf);
+ priv->wsm_caps.hardwareId = WSM_GET16(buf);
+ priv->wsm_caps.hardwareSubId = WSM_GET16(buf);
+ status = WSM_GET16(buf);
+ priv->wsm_caps.firmwareCap = WSM_GET16(buf);
+ priv->wsm_caps.firmwareType = WSM_GET16(buf);
+ priv->wsm_caps.firmwareApiVer = WSM_GET16(buf);
+ priv->wsm_caps.firmwareBuildNumber = WSM_GET16(buf);
+ priv->wsm_caps.firmwareVersion = WSM_GET16(buf);
+ WSM_GET(buf, &fw_label[0], sizeof(fw_label) - 1);
+ fw_label[sizeof(fw_label) - 1] = 0; /* Do not trust FW too much. */
+
+ if (WARN_ON(status))
+ return -EINVAL;
+
+ if (WARN_ON(priv->wsm_caps.firmwareType > 4))
+ return -EINVAL;
+
+ printk(KERN_INFO "CW1200 WSM init done.\n"
+ " Input buffers: %d x %d bytes\n"
+ " Hardware: %d.%d\n"
+ " %s firmware [%s], ver: %d, build: %d,"
+ " api: %d, cap: 0x%.4X\n",
+ priv->wsm_caps.numInpChBufs, priv->wsm_caps.sizeInpChBuf,
+ priv->wsm_caps.hardwareId, priv->wsm_caps.hardwareSubId,
+ fw_types[priv->wsm_caps.firmwareType],
+ &fw_label[0], priv->wsm_caps.firmwareVersion,
+ priv->wsm_caps.firmwareBuildNumber,
+ priv->wsm_caps.firmwareApiVer, priv->wsm_caps.firmwareCap);
+
+ priv->wsm_caps.firmwareReady = 1;
+
+ wake_up(&priv->wsm_startup_done);
+ return 0;
+
+underflow:
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+static int wsm_receive_indication(struct cw1200_common *priv,
+ int link_id,
+ struct wsm_buf *buf,
+ struct sk_buff **skb_p)
+{
+ priv->rx_timestamp = jiffies;
+ if (priv->wsm_cbc.rx) {
+ struct wsm_rx rx;
+ struct ieee80211_hdr *hdr;
+ size_t hdr_len;
+ __le16 fctl;
+
+ rx.status = WSM_GET32(buf);
+ rx.channelNumber = WSM_GET16(buf);
+ rx.rxedRate = WSM_GET8(buf);
+ rx.rcpiRssi = WSM_GET8(buf);
+ rx.flags = WSM_GET32(buf);
+
+ /* FW Workaround: Drop probe resp or
+ beacon when RSSI is 0 */
+ hdr = (struct ieee80211_hdr *) (*skb_p)->data;
+
+ if (!rx.rcpiRssi &&
+ (ieee80211_is_probe_resp(hdr->frame_control) ||
+ ieee80211_is_beacon(hdr->frame_control)))
+ return 0;
+
+ /* If no RSSI subscription has been made,
+ * convert RCPI to RSSI here */
+ if (!priv->cqm_use_rssi)
+ rx.rcpiRssi = rx.rcpiRssi / 2 - 110;
+
+ rx.link_id = link_id;
+ fctl = *(__le16 *)buf->data;
+ hdr_len = buf->data - buf->begin;
+ skb_pull(*skb_p, hdr_len);
+ if (!rx.status && unlikely(ieee80211_is_deauth(fctl))) {
+ if (priv->join_status == CW1200_JOIN_STATUS_STA) {
+ /* Shedule unjoin work */
+ wsm_printk(KERN_DEBUG \
+ "[WSM] Issue unjoin command (RX).\n");
+ wsm_lock_tx_async(priv);
+ if (queue_work(priv->workqueue,
+ &priv->unjoin_work) <= 0)
+ wsm_unlock_tx(priv);
+ }
+ }
+ priv->wsm_cbc.rx(priv, &rx, skb_p);
+ if (*skb_p)
+ skb_push(*skb_p, hdr_len);
+ }
+ return 0;
+
+underflow:
+ return -EINVAL;
+}
+
+static int wsm_event_indication(struct cw1200_common *priv, struct wsm_buf *buf)
+{
+ int first;
+ struct cw1200_wsm_event *event;
+
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) {
+ /* STA is stopped. */
+ return 0;
+ }
+
+ event = kzalloc(sizeof(struct cw1200_wsm_event), GFP_KERNEL);
+
+ event->evt.eventId = __le32_to_cpu(WSM_GET32(buf));
+ event->evt.eventData = __le32_to_cpu(WSM_GET32(buf));
+
+ wsm_printk(KERN_DEBUG "[WSM] Event: %d(%d)\n",
+ event->evt.eventId, event->evt.eventData);
+
+ spin_lock(&priv->event_queue_lock);
+ first = list_empty(&priv->event_queue);
+ list_add_tail(&event->link, &priv->event_queue);
+ spin_unlock(&priv->event_queue_lock);
+
+ if (first)
+ queue_work(priv->workqueue, &priv->event_handler);
+
+ return 0;
+
+underflow:
+ kfree(event);
+ return -EINVAL;
+}
+
+static int wsm_channel_switch_indication(struct cw1200_common *priv,
+ struct wsm_buf *buf)
+{
+ wsm_unlock_tx(priv); /* Re-enable datapath */
+ WARN_ON(WSM_GET32(buf));
+
+ priv->channel_switch_in_progress = 0;
+ wake_up(&priv->channel_switch_done);
+
+ if (priv->wsm_cbc.channel_switch)
+ priv->wsm_cbc.channel_switch(priv);
+ return 0;
+
+underflow:
+ return -EINVAL;
+}
+
+static int wsm_set_pm_indication(struct cw1200_common *priv,
+ struct wsm_buf *buf)
+{
+ return 0;
+}
+
+static int wsm_scan_complete_indication(struct cw1200_common *priv,
+ struct wsm_buf *buf)
+{
+ if (priv->wsm_cbc.scan_complete) {
+ struct wsm_scan_complete arg;
+ arg.status = WSM_GET32(buf);
+ arg.psm = WSM_GET8(buf);
+ arg.numChannels = WSM_GET8(buf);
+ priv->wsm_cbc.scan_complete(priv, &arg);
+ }
+ return 0;
+
+underflow:
+ return -EINVAL;
+}
+
+static int wsm_find_complete_indication(struct cw1200_common *priv,
+ struct wsm_buf *buf)
+{
+ /* TODO: Implement me. */
+ STUB();
+ return 0;
+}
+
+static int wsm_suspend_resume_indication(struct cw1200_common *priv,
+ int link_id, struct wsm_buf *buf)
+{
+ if (priv->wsm_cbc.suspend_resume) {
+ u32 flags;
+ struct wsm_suspend_resume arg;
+
+ flags = WSM_GET32(buf);
+ arg.link_id = link_id;
+ arg.stop = !(flags & 1);
+ arg.multicast = !!(flags & 8);
+ arg.queue = (flags >> 1) & 3;
+
+ priv->wsm_cbc.suspend_resume(priv, &arg);
+ }
+ return 0;
+
+underflow:
+ return -EINVAL;
+}
+
+
+/* ******************************************************************** */
+/* WSM TX */
+
+int wsm_cmd_send(struct cw1200_common *priv,
+ struct wsm_buf *buf,
+ void *arg, u16 cmd, long tmo)
+{
+ size_t buf_len = buf->data - buf->begin;
+ int ret;
+
+ if (cmd == 0x0006) /* Write MIB */
+ wsm_printk(KERN_DEBUG "[WSM] >>> 0x%.4X [MIB: 0x%.4X] (%ld)\n",
+ cmd, __le16_to_cpu(((__le16 *)buf->begin)[2]),
+ buf_len);
+ else
+ wsm_printk(KERN_DEBUG "[WSM] >>> 0x%.4X (%ld)\n", cmd, buf_len);
+
+ /* Fill HI message header */
+ /* BH will add sequence number */
+ ((__le16 *)buf->begin)[0] = __cpu_to_le16(buf_len);
+ ((__le16 *)buf->begin)[1] = __cpu_to_le16(cmd);
+
+ spin_lock(&priv->wsm_cmd.lock);
+ BUG_ON(priv->wsm_cmd.ptr);
+ priv->wsm_cmd.done = 0;
+ priv->wsm_cmd.ptr = buf->begin;
+ priv->wsm_cmd.len = buf_len;
+ priv->wsm_cmd.arg = arg;
+ priv->wsm_cmd.cmd = cmd;
+ spin_unlock(&priv->wsm_cmd.lock);
+
+ cw1200_bh_wakeup(priv);
+
+ if (unlikely(priv->bh_error)) {
+ /* Do not wait for timeout if BH is dead. Exit immediately. */
+ ret = 0;
+ } else {
+ long rx_timestamp;
+ /* Firmware prioritizes data traffic over control confirm.
+ * Loop below checks if data was RXed and increases timeout
+ * accordingly. */
+ do {
+ /* It's safe to use unprotected access to
+ * wsm_cmd.done here */
+ ret = wait_event_timeout(
+ priv->wsm_cmd_wq,
+ priv->wsm_cmd.done, tmo);
+ rx_timestamp = jiffies - priv->rx_timestamp;
+ if (unlikely(rx_timestamp < 0))
+ rx_timestamp = tmo + 1;
+ } while (!ret && rx_timestamp <= tmo);
+ }
+
+ if (unlikely(ret == 0)) {
+ u16 raceCheck;
+
+ spin_lock(&priv->wsm_cmd.lock);
+ raceCheck = priv->wsm_cmd.cmd;
+ priv->wsm_cmd.arg = NULL;
+ priv->wsm_cmd.ptr = NULL;
+ spin_unlock(&priv->wsm_cmd.lock);
+
+ /* Race condition check to make sure _confirm is not called
+ * after exit of _send */
+ if (raceCheck == 0xFFFF) {
+ /* If wsm_handle_rx got stuck in _confirm we will hang
+ * system there. It's better than silently currupt
+ * stack or heap, isn't it? */
+ BUG_ON(wait_event_timeout(
+ priv->wsm_cmd_wq, priv->wsm_cmd.done,
+ WSM_CMD_LAST_CHANCE_TIMEOUT) <= 0);
+ }
+
+ /* Kill BH thread to report the error to the top layer. */
+ priv->bh_error = 1;
+ wake_up(&priv->bh_wq);
+ ret = -ETIMEDOUT;
+ } else {
+ spin_lock(&priv->wsm_cmd.lock);
+ BUG_ON(!priv->wsm_cmd.done);
+ ret = priv->wsm_cmd.ret;
+ spin_unlock(&priv->wsm_cmd.lock);
+ }
+ wsm_buf_reset(buf);
+ return ret;
+}
+
+/* ******************************************************************** */
+/* WSM TX port control */
+
+void wsm_lock_tx(struct cw1200_common *priv)
+{
+ wsm_cmd_lock(priv);
+ if (atomic_add_return(1, &priv->tx_lock) == 1) {
+ if (wsm_flush_tx(priv))
+ wsm_printk(KERN_DEBUG "[WSM] TX is locked.\n");
+ }
+ wsm_cmd_unlock(priv);
+}
+
+void wsm_lock_tx_async(struct cw1200_common *priv)
+{
+ if (atomic_add_return(1, &priv->tx_lock) == 1)
+ wsm_printk(KERN_DEBUG "[WSM] TX is locked (async).\n");
+}
+
+bool wsm_flush_tx(struct cw1200_common *priv)
+{
+ unsigned long timestamp = jiffies;
+ bool pending = false;
+ long timeout;
+ int i;
+
+ /* Flush must be called with TX lock held. */
+ BUG_ON(!atomic_read(&priv->tx_lock));
+
+ /* First check if we really need to do something.
+ * It is safe to use unprotected access, as hw_bufs_used
+ * can only decrements. */
+ if (!priv->hw_bufs_used)
+ return true;
+
+ if (priv->bh_error) {
+ /* In case of failure do not wait for magic. */
+ wsm_printk(KERN_ERR "[WSM] Fatal error occured, "
+ "will not flush TX.\n");
+ return false;
+ } else {
+ /* Get a timestamp of "oldest" frame */
+ for (i = 0; i < 4; ++i)
+ pending |= cw1200_queue_get_xmit_timestamp(
+ &priv->tx_queue[i],
+ &timestamp);
+ /* It is allowed to lock TX with only a command in the pipe. */
+ if (!pending)
+ return true;
+
+ timeout = timestamp + WSM_CMD_LAST_CHANCE_TIMEOUT - jiffies;
+ if (timeout < 0 || wait_event_timeout(priv->bh_evt_wq,
+ !priv->hw_bufs_used,
+ timeout) <= 0) {
+ /* Hmmm... Not good. Frame had stuck in firmware. */
+ priv->bh_error = 1;
+ wake_up(&priv->bh_wq);
+ return false;
+ }
+
+ /* Ok, everything is flushed. */
+ return true;
+ }
+}
+
+void wsm_unlock_tx(struct cw1200_common *priv)
+{
+ int tx_lock;
+ if (priv->bh_error)
+ wsm_printk(KERN_ERR "fatal error occured, unlock is unsafe\n");
+ else {
+ tx_lock = atomic_sub_return(1, &priv->tx_lock);
+ if (tx_lock < 0) {
+ BUG_ON(1);
+ } else if (tx_lock == 0) {
+ cw1200_bh_wakeup(priv);
+ wsm_printk(KERN_DEBUG "[WSM] TX is unlocked.\n");
+ }
+ }
+}
+
+/* ******************************************************************** */
+/* WSM RX */
+
+int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len)
+{
+ struct wsm_buf buf;
+ u32 reason;
+ u32 reg[18];
+ char fname[48];
+ size_t i;
+
+ static const char * const reason_str[] = {
+ "undefined instruction",
+ "prefetch abort",
+ "data abort",
+ "unknown error",
+ };
+
+ buf.begin = buf.data = data;
+ buf.end = &buf.begin[len];
+
+ reason = WSM_GET32(&buf);
+ for (i = 0; i < ARRAY_SIZE(reg); ++i)
+ reg[i] = WSM_GET32(&buf);
+ WSM_GET(&buf, fname, sizeof(fname));
+
+ if (reason < 4)
+ wiphy_err(priv->hw->wiphy,
+ "Firmware exception: %s.\n",
+ reason_str[reason]);
+ else
+ wiphy_err(priv->hw->wiphy,
+ "Firmware assert at %.*s, line %d\n",
+ (int)sizeof(fname), fname, reg[1]);
+
+ for (i = 0; i < 12; i += 4)
+ wiphy_err(priv->hw->wiphy,
+ "R%ld: 0x%.8X, R%ld: 0x%.8X, R%ld: 0x%.8X, R%ld: 0x%.8X,\n",
+ i + 0, reg[i + 0], i + 1, reg[i + 1],
+ i + 2, reg[i + 2], i + 3, reg[i + 3]);
+ wiphy_err(priv->hw->wiphy,
+ "R12: 0x%.8X, SP: 0x%.8X, LR: 0x%.8X, PC: 0x%.8X,\n",
+ reg[i + 0], reg[i + 1], reg[i + 2], reg[i + 3]);
+ i += 4;
+ wiphy_err(priv->hw->wiphy,
+ "CPSR: 0x%.8X, SPSR: 0x%.8X\n",
+ reg[i + 0], reg[i + 1]);
+
+ print_hex_dump_bytes("R1: ", DUMP_PREFIX_NONE,
+ fname, sizeof(fname));
+ return 0;
+
+underflow:
+ wiphy_err(priv->hw->wiphy,
+ "Firmware exception.\n");
+ print_hex_dump_bytes("Exception: ", DUMP_PREFIX_NONE,
+ data, len);
+ return -EINVAL;
+}
+
+int wsm_handle_rx(struct cw1200_common *priv, int id,
+ struct wsm_hdr *wsm, struct sk_buff **skb_p)
+{
+ int ret = 0;
+ struct wsm_buf wsm_buf;
+ int link_id = (id >> 6) & 0x0F;
+
+ /* Strip link id. */
+ id &= ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX);
+
+ wsm_buf.begin = (u8 *)&wsm[0];
+ wsm_buf.data = (u8 *)&wsm[1];
+ wsm_buf.end = &wsm_buf.begin[__le32_to_cpu(wsm->len)];
+
+ wsm_printk(KERN_DEBUG "[WSM] <<< 0x%.4X (%ld)\n", id,
+ wsm_buf.end - wsm_buf.begin);
+
+ if (id == 0x404) {
+ ret = wsm_tx_confirm(priv, &wsm_buf, link_id);
+ } else if (id == 0x41E) {
+ ret = wsm_multi_tx_confirm(priv, &wsm_buf, link_id);
+ } else if (id & 0x0400) {
+ void *wsm_arg;
+ u16 wsm_cmd;
+
+ /* Do not trust FW too much. Protection against repeated
+ * response and race condition removal (see above). */
+ spin_lock(&priv->wsm_cmd.lock);
+ wsm_arg = priv->wsm_cmd.arg;
+ wsm_cmd = priv->wsm_cmd.cmd &
+ ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX);
+ priv->wsm_cmd.cmd = 0xFFFF;
+ spin_unlock(&priv->wsm_cmd.lock);
+
+ if (WARN_ON((id & ~0x0400) != wsm_cmd)) {
+ /* Note that any non-zero is a fatal retcode. */
+ ret = -EINVAL;
+ goto out;
+ }
+
+ switch (id) {
+ case 0x0409:
+ /* Note that wsm_arg can be NULL in case of timeout in
+ * wsm_cmd_send(). */
+ if (likely(wsm_arg))
+ ret = wsm_configuration_confirm(priv, wsm_arg,
+ &wsm_buf);
+ break;
+ case 0x0405:
+ if (likely(wsm_arg))
+ ret = wsm_read_mib_confirm(priv, wsm_arg,
+ &wsm_buf);
+ break;
+ case 0x0406:
+ if (likely(wsm_arg))
+ ret = wsm_write_mib_confirm(priv, wsm_arg,
+ &wsm_buf);
+ break;
+ case 0x040B:
+ if (likely(wsm_arg))
+ ret = wsm_join_confirm(priv, wsm_arg, &wsm_buf);
+ break;
+ case 0x0407: /* start-scan */
+ case 0x0408: /* stop-scan */
+ case 0x040A: /* wsm_reset */
+ case 0x040C: /* add_key */
+ case 0x040D: /* remove_key */
+ case 0x0410: /* wsm_set_pm */
+ case 0x0411: /* set_bss_params */
+ case 0x0412: /* set_tx_queue_params */
+ case 0x0413: /* set_edca_params */
+ case 0x0416: /* switch_channel */
+ case 0x0417: /* start */
+ case 0x0418: /* beacon_transmit */
+ case 0x0419: /* start_find */
+ case 0x041A: /* stop_find */
+ case 0x041B: /* update_ie */
+ case 0x041C: /* map_link */
+ WARN_ON(wsm_arg != NULL);
+ ret = wsm_generic_confirm(priv, wsm_arg, &wsm_buf);
+ if (ret)
+ wiphy_warn(priv->hw->wiphy,
+ "wsm_generic_confirm "
+ "failed for request 0x%.4X.\n",
+ id & ~0x0400);
+ break;
+ default:
+ BUG_ON(1);
+ }
+
+ spin_lock(&priv->wsm_cmd.lock);
+ priv->wsm_cmd.ret = ret;
+ priv->wsm_cmd.done = 1;
+ spin_unlock(&priv->wsm_cmd.lock);
+ ret = 0; /* Error response from device should ne stop BH. */
+
+ wake_up(&priv->wsm_cmd_wq);
+ } else if (id & 0x0800) {
+ switch (id) {
+ case 0x0801:
+ ret = wsm_startup_indication(priv, &wsm_buf);
+ break;
+ case 0x0804:
+ ret = wsm_receive_indication(priv, link_id,
+ &wsm_buf, skb_p);
+ break;
+ case 0x0805:
+ ret = wsm_event_indication(priv, &wsm_buf);
+ break;
+ case 0x080A:
+ ret = wsm_channel_switch_indication(priv, &wsm_buf);
+ break;
+ case 0x0809:
+ ret = wsm_set_pm_indication(priv, &wsm_buf);
+ break;
+ case 0x0806:
+ ret = wsm_scan_complete_indication(priv, &wsm_buf);
+ break;
+ case 0x080B:
+ ret = wsm_find_complete_indication(priv, &wsm_buf);
+ break;
+ case 0x080C:
+ ret = wsm_suspend_resume_indication(priv,
+ link_id, &wsm_buf);
+ break;
+ default:
+ STUB();
+ }
+ } else {
+ WARN_ON(1);
+ ret = -EINVAL;
+ }
+out:
+ return ret;
+}
+
+static bool wsm_handle_tx_data(struct cw1200_common *priv,
+ const struct wsm_tx *wsm,
+ const struct ieee80211_tx_info *tx_info,
+ const struct cw1200_txpriv *txpriv,
+ struct cw1200_queue *queue)
+{
+ bool handled = false;
+ const struct ieee80211_hdr *frame =
+ (struct ieee80211_hdr *) &((u8 *)wsm)[txpriv->offset];
+ __le16 fctl = frame->frame_control;
+ enum {
+ doProbe,
+ doDrop,
+ doJoin,
+ doOffchannel,
+ doWep,
+ doTx,
+ } action = doTx;
+
+ switch (priv->mode) {
+ case NL80211_IFTYPE_STATION:
+ if (unlikely((priv->join_status == CW1200_JOIN_STATUS_STA) &&
+ ieee80211_is_nullfunc(fctl))) {
+ spin_lock(&priv->bss_loss_lock);
+ if (priv->bss_loss_status == CW1200_BSS_LOSS_CHECKING) {
+ priv->bss_loss_status =
+ CW1200_BSS_LOSS_CONFIRMING;
+ priv->bss_loss_confirm_id = wsm->packetID;
+ }
+ spin_unlock(&priv->bss_loss_lock);
+ } else if (unlikely(
+ (priv->join_status <= CW1200_JOIN_STATUS_MONITOR) ||
+ memcmp(frame->addr1, priv->join_bssid,
+ sizeof(priv->join_bssid)))) {
+ if (ieee80211_is_auth(fctl))
+ action = doJoin;
+ else if (ieee80211_is_probe_req(fctl))
+ action = doTx;
+ else if (priv->join_status >=
+ CW1200_JOIN_STATUS_MONITOR)
+ action = doTx;
+ else
+ action = doOffchannel;
+ }
+ break;
+ case NL80211_IFTYPE_AP:
+ if (unlikely(!priv->join_status))
+ action = doDrop;
+ else if (unlikely(!(BIT(txpriv->raw_link_id) &
+ (BIT(0) | priv->link_id_map)))) {
+ wiphy_warn(priv->hw->wiphy,
+ "A frame with expired link id "
+ "is dropped.\n");
+ action = doDrop;
+ }
+ if (cw1200_queue_get_generation(wsm->packetID) >
+ CW1200_MAX_REQUEUE_ATTEMPTS) {
+ /* HACK!!! WSM324 firmware has tendency to requeue
+ * multicast frames in a loop, causing performance
+ * drop and high power consumption of the driver.
+ * In this situation it is better just to drop
+ * the problematic frame. */
+ wiphy_warn(priv->hw->wiphy,
+ "Too many attempts "
+ "to requeue a frame. "
+ "Frame is dropped.\n");
+ action = doDrop;
+ }
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_MESH_POINT:
+ STUB();
+ case NL80211_IFTYPE_MONITOR:
+ default:
+ action = doDrop;
+ break;
+ }
+
+ if (action == doTx) {
+ if (unlikely(ieee80211_is_probe_req(fctl)))
+ action = doProbe;
+ else if ((fctl & __cpu_to_le32(IEEE80211_FCTL_PROTECTED)) &&
+ tx_info->control.hw_key &&
+ unlikely(tx_info->control.hw_key->keyidx !=
+ priv->wep_default_key_id) &&
+ (tx_info->control.hw_key->cipher ==
+ WLAN_CIPHER_SUITE_WEP40 ||
+ tx_info->control.hw_key->cipher ==
+ WLAN_CIPHER_SUITE_WEP104))
+ action = doWep;
+ }
+
+ switch (action) {
+ case doProbe:
+ {
+ /* An interesting FW "feature". Device filters
+ * probe responses.
+ * The easiest way to get it back is to convert
+ * probe request into WSM start_scan command. */
+ wsm_printk(KERN_DEBUG \
+ "[WSM] Convert probe request to scan.\n");
+ wsm_lock_tx_async(priv);
+ priv->pending_frame_id = __le32_to_cpu(wsm->packetID);
+ queue_delayed_work(priv->workqueue,
+ &priv->scan.probe_work, 0);
+ handled = true;
+ }
+ break;
+ case doDrop:
+ {
+ /* See detailed description of "join" below.
+ * We are dropping everything except AUTH in non-joined mode. */
+ wsm_printk(KERN_DEBUG "[WSM] Drop frame (0x%.4X).\n", fctl);
+ BUG_ON(cw1200_queue_remove(queue,
+ __le32_to_cpu(wsm->packetID)));
+ handled = true;
+ }
+ break;
+ case doJoin:
+ {
+ /* There is one more interesting "feature"
+ * in FW: it can't do RX/TX before "join".
+ * "Join" here is not an association,
+ * but just a syncronization between AP and STA.
+ * priv->join_status is used only in bh thread and does
+ * not require protection */
+ wsm_printk(KERN_DEBUG "[WSM] Issue join command.\n");
+ wsm_lock_tx_async(priv);
+ priv->pending_frame_id = __le32_to_cpu(wsm->packetID);
+ if (queue_work(priv->workqueue, &priv->join_work) <= 0)
+ wsm_unlock_tx(priv);
+ handled = true;
+ }
+ break;
+ case doOffchannel:
+ {
+ wsm_printk(KERN_DEBUG "[WSM] Offchannel TX request.\n");
+ wsm_lock_tx_async(priv);
+ priv->pending_frame_id = __le32_to_cpu(wsm->packetID);
+ if (queue_work(priv->workqueue, &priv->offchannel_work) <= 0)
+ wsm_unlock_tx(priv);
+ handled = true;
+ }
+ break;
+ case doWep:
+ {
+ wsm_printk(KERN_DEBUG "[WSM] Issue set_default_wep_key.\n");
+ wsm_lock_tx_async(priv);
+ priv->wep_default_key_id = tx_info->control.hw_key->keyidx;
+ priv->pending_frame_id = __le32_to_cpu(wsm->packetID);
+ if (queue_work(priv->workqueue, &priv->wep_key_work) <= 0)
+ wsm_unlock_tx(priv);
+ handled = true;
+ }
+ break;
+ case doTx:
+ {
+#if 0
+ /* Kept for history. If you want to implement wsm->more,
+ * make sure you are able to send a frame after that. */
+ wsm->more = (count > 1) ? 1 : 0;
+ if (wsm->more) {
+ /* HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * It's undocumented in WSM spec, but CW1200 hangs
+ * if 'more' is set and no TX is performed due to TX
+ * buffers limitation. */
+ if (priv->hw_bufs_used + 1 ==
+ priv->wsm_caps.numInpChBufs)
+ wsm->more = 0;
+ }
+
+ /* BUG!!! FIXME: we can't use 'more' at all: we don't know
+ * future. It could be a request from upper layer with TX lock
+ * requirements (scan, for example). If "more" is set device
+ * will not send data and wsm_tx_lock() will fail...
+ * It's not obvious how to fix this deadlock. Any ideas?
+ * As a workaround more is set to 0. */
+ wsm->more = 0;
+#endif /* 0 */
+
+ if (ieee80211_is_deauth(fctl) &&
+ priv->mode != NL80211_IFTYPE_AP) {
+ /* Shedule unjoin work */
+ wsm_printk(KERN_DEBUG "[WSM] Issue unjoin command"
+ " (TX).\n");
+#if 0
+ wsm->more = 0;
+#endif /* 0 */
+ wsm_lock_tx_async(priv);
+ if (queue_work(priv->workqueue,
+ &priv->unjoin_work) <= 0)
+ wsm_unlock_tx(priv);
+ }
+ }
+ break;
+ }
+ return handled;
+}
+
+static int cw1200_get_prio_queue(struct cw1200_common *priv,
+ u32 link_id_map, int *total)
+{
+ static const int urgent = BIT(CW1200_LINK_ID_AFTER_DTIM) |
+ BIT(CW1200_LINK_ID_UAPSD);
+ struct wsm_edca_queue_params *edca;
+ unsigned score, best = -1;
+ int winner = -1;
+ int queued;
+ int i;
+
+ /* search for a winner using edca params */
+ for (i = 0; i < 4; ++i) {
+ queued = cw1200_queue_get_num_queued(&priv->tx_queue[i],
+ link_id_map);
+ if (!queued)
+ continue;
+ *total += queued;
+ edca = &priv->edca.params[i];
+ score = ((edca->aifns + edca->cwMin) << 16) +
+ (edca->cwMax - edca->cwMin) *
+ (random32() & 0xFFFF);
+ if (score < best && (winner < 0 || i != 3)) {
+ best = score;
+ winner = i;
+ }
+ }
+
+ /* override winner if bursting */
+ if (winner >= 0 && priv->tx_burst_idx >= 0 &&
+ winner != priv->tx_burst_idx &&
+ !cw1200_queue_get_num_queued(
+ &priv->tx_queue[winner],
+ link_id_map & urgent) &&
+ cw1200_queue_get_num_queued(
+ &priv->tx_queue[priv->tx_burst_idx],
+ link_id_map))
+ winner = priv->tx_burst_idx;
+
+ return winner;
+}
+
+static int wsm_get_tx_queue_and_mask(struct cw1200_common *priv,
+ struct cw1200_queue **queue_p,
+ u32 *tx_allowed_mask_p,
+ bool *more)
+{
+ int idx;
+ u32 tx_allowed_mask;
+ int total = 0;
+
+ /* Search for a queue with multicast frames buffered */
+ if (priv->tx_multicast) {
+ tx_allowed_mask = BIT(CW1200_LINK_ID_AFTER_DTIM);
+ idx = cw1200_get_prio_queue(priv,
+ tx_allowed_mask, &total);
+ if (idx >= 0) {
+ *more = total > 1;
+ goto found;
+ }
+ }
+
+ /* Search for unicast traffic */
+ tx_allowed_mask = ~priv->sta_asleep_mask;
+ tx_allowed_mask |= BIT(CW1200_LINK_ID_UAPSD);
+ if (priv->sta_asleep_mask) {
+ tx_allowed_mask |= priv->pspoll_mask;
+ tx_allowed_mask &= ~BIT(CW1200_LINK_ID_AFTER_DTIM);
+ } else {
+ tx_allowed_mask |= BIT(CW1200_LINK_ID_AFTER_DTIM);
+ }
+ idx = cw1200_get_prio_queue(priv,
+ tx_allowed_mask, &total);
+ if (idx < 0)
+ return -ENOENT;
+
+found:
+ *queue_p = &priv->tx_queue[idx];
+ *tx_allowed_mask_p = tx_allowed_mask;
+ return 0;
+}
+
+int wsm_get_tx(struct cw1200_common *priv, u8 **data,
+ size_t *tx_len, int *burst)
+{
+ struct wsm_tx *wsm = NULL;
+ struct ieee80211_tx_info *tx_info;
+ struct cw1200_queue *queue = NULL;
+ int queue_num;
+ u32 tx_allowed_mask = 0;
+ const struct cw1200_txpriv *txpriv = NULL;
+ /*
+ * Count was intended as an input for wsm->more flag.
+ * During implementation it was found that wsm->more
+ * is not usable, see details above. It is kept just
+ * in case you would like to try to implement it again.
+ */
+ int count = 0;
+
+ /* More is used only for broadcasts. */
+ bool more = false;
+
+ count = cw1200_itp_get_tx(priv, data, tx_len, burst);
+ if (count)
+ return count;
+
+ if (priv->wsm_cmd.ptr) {
+ ++count;
+ spin_lock(&priv->wsm_cmd.lock);
+ BUG_ON(!priv->wsm_cmd.ptr);
+ *data = priv->wsm_cmd.ptr;
+ *tx_len = priv->wsm_cmd.len;
+ *burst = 1;
+ spin_unlock(&priv->wsm_cmd.lock);
+ } else {
+ for (;;) {
+ int ret;
+
+ if (atomic_add_return(0, &priv->tx_lock))
+ break;
+
+ spin_lock_bh(&priv->ps_state_lock);
+
+ ret = wsm_get_tx_queue_and_mask(priv, &queue,
+ &tx_allowed_mask, &more);
+ queue_num = queue - priv->tx_queue;
+
+ if (priv->buffered_multicasts &&
+ (ret || !more) &&
+ (priv->tx_multicast ||
+ !priv->sta_asleep_mask)) {
+ priv->buffered_multicasts = false;
+ if (priv->tx_multicast) {
+ priv->tx_multicast = false;
+ queue_work(priv->workqueue,
+ &priv->multicast_stop_work);
+ }
+ }
+
+ spin_unlock_bh(&priv->ps_state_lock);
+
+ if (ret)
+ break;
+
+ if (cw1200_queue_get(queue,
+ tx_allowed_mask,
+ &wsm, &tx_info, &txpriv))
+ continue;
+
+ if (wsm_handle_tx_data(priv, wsm,
+ tx_info, txpriv, queue))
+ continue; /* Handled by WSM */
+
+ wsm->hdr.id &= __cpu_to_le16(
+ ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX));
+ wsm->hdr.id |= cpu_to_le16(
+ WSM_TX_LINK_ID(txpriv->raw_link_id));
+ priv->pspoll_mask &= ~BIT(txpriv->raw_link_id);
+
+ *data = (u8 *)wsm;
+ *tx_len = __le16_to_cpu(wsm->hdr.len);
+
+ /* allow bursting if txop is set */
+ if (priv->edca.params[queue_num].txOpLimit)
+ *burst = min(*burst,
+ (int)cw1200_queue_get_num_queued(
+ queue, tx_allowed_mask) + 1);
+ else
+ *burst = 1;
+
+ /* store index of bursting queue */
+ if (*burst > 1)
+ priv->tx_burst_idx = queue_num;
+ else
+ priv->tx_burst_idx = -1;
+
+ if (more) {
+ struct ieee80211_hdr *hdr =
+ (struct ieee80211_hdr *)
+ &((u8 *)wsm)[txpriv->offset];
+ /* more buffered multicast/broadcast frames
+ * ==> set MoreData flag in IEEE 802.11 header
+ * to inform PS STAs */
+ hdr->frame_control |=
+ cpu_to_le16(IEEE80211_FCTL_MOREDATA);
+ }
+
+ wsm_printk(KERN_DEBUG "[WSM] >>> 0x%.4X (%ld) %p %c\n",
+ 0x0004, *tx_len, *data,
+ wsm->more ? 'M' : ' ');
+ ++count;
+ break;
+ }
+ }
+
+ return count;
+}
+
+void wsm_txed(struct cw1200_common *priv, u8 *data)
+{
+ if (data == priv->wsm_cmd.ptr) {
+ spin_lock(&priv->wsm_cmd.lock);
+ priv->wsm_cmd.ptr = NULL;
+ spin_unlock(&priv->wsm_cmd.lock);
+ }
+}
+
+/* ******************************************************************** */
+/* WSM buffer */
+
+void wsm_buf_init(struct wsm_buf *buf)
+{
+ BUG_ON(buf->begin);
+ buf->begin = kmalloc(SDIO_BLOCK_SIZE, GFP_KERNEL | GFP_DMA);
+ buf->end = buf->begin ? &buf->begin[SDIO_BLOCK_SIZE] : buf->begin;
+ wsm_buf_reset(buf);
+}
+
+void wsm_buf_deinit(struct wsm_buf *buf)
+{
+ kfree(buf->begin);
+ buf->begin = buf->data = buf->end = NULL;
+}
+
+static void wsm_buf_reset(struct wsm_buf *buf)
+{
+ if (buf->begin) {
+ buf->data = &buf->begin[4];
+ *(u32 *)buf->begin = 0;
+ } else
+ buf->data = buf->begin;
+}
+
+static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size)
+{
+ size_t pos = buf->data - buf->begin;
+ size_t size = pos + extra_size;
+
+
+ if (size & (SDIO_BLOCK_SIZE - 1)) {
+ size &= SDIO_BLOCK_SIZE;
+ size += SDIO_BLOCK_SIZE;
+ }
+
+ buf->begin = krealloc(buf->begin, size, GFP_KERNEL | GFP_DMA);
+ if (buf->begin) {
+ buf->data = &buf->begin[pos];
+ buf->end = &buf->begin[size];
+ return 0;
+ } else {
+ buf->end = buf->data = buf->begin;
+ return -ENOMEM;
+ }
+}
diff --git a/drivers/net/wireless/cw1200/wsm.h b/drivers/net/wireless/cw1200/wsm.h
new file mode 100644
index 0000000..9c3dcd9
--- /dev/null
+++ b/drivers/net/wireless/cw1200/wsm.h
@@ -0,0 +1,1845 @@
+/*
+ * WSM host interface (HI) interface for ST-Ericsson CW1200 mac80211 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on CW1200 UMAC WSM API, which is
+ * Copyright (C) ST-Ericsson SA 2010
+ * Author: Stewart Mathers <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef CW1200_WSM_H_INCLUDED
+#define CW1200_WSM_H_INCLUDED
+
+#include <linux/spinlock.h>
+
+struct cw1200_common;
+
+/* Bands */
+/* Radio band 2.412 -2.484 GHz. */
+#define WSM_PHY_BAND_2_4G (0)
+
+/* Radio band 4.9375-5.8250 GHz. */
+#define WSM_PHY_BAND_5G (1)
+
+/* Transmit rates */
+/* 1 Mbps ERP-DSSS */
+#define WSM_TRANSMIT_RATE_1 (0)
+
+/* 2 Mbps ERP-DSSS */
+#define WSM_TRANSMIT_RATE_2 (1)
+
+/* 5.5 Mbps ERP-CCK, ERP-PBCC (Not supported) */
+/* #define WSM_TRANSMIT_RATE_5 (2) */
+
+/* 11 Mbps ERP-CCK, ERP-PBCC (Not supported) */
+/* #define WSM_TRANSMIT_RATE_11 (3) */
+
+/* 22 Mbps ERP-PBCC (Not supported) */
+/* #define WSM_TRANSMIT_RATE_22 (4) */
+
+/* 33 Mbps ERP-PBCC (Not supported) */
+/* #define WSM_TRANSMIT_RATE_33 (5) */
+
+/* 6 Mbps (3 Mbps) ERP-OFDM, BPSK coding rate 1/2 */
+#define WSM_TRANSMIT_RATE_6 (6)
+
+/* 9 Mbps (4.5 Mbps) ERP-OFDM, BPSK coding rate 3/4 */
+#define WSM_TRANSMIT_RATE_9 (7)
+
+/* 12 Mbps (6 Mbps) ERP-OFDM, QPSK coding rate 1/2 */
+#define WSM_TRANSMIT_RATE_12 (8)
+
+/* 18 Mbps (9 Mbps) ERP-OFDM, QPSK coding rate 3/4 */
+#define WSM_TRANSMIT_RATE_18 (9)
+
+/* 24 Mbps (12 Mbps) ERP-OFDM, 16QAM coding rate 1/2 */
+#define WSM_TRANSMIT_RATE_24 (10)
+
+/* 36 Mbps (18 Mbps) ERP-OFDM, 16QAM coding rate 3/4 */
+#define WSM_TRANSMIT_RATE_36 (11)
+
+/* 48 Mbps (24 Mbps) ERP-OFDM, 64QAM coding rate 1/2 */
+#define WSM_TRANSMIT_RATE_48 (12)
+
+/* 54 Mbps (27 Mbps) ERP-OFDM, 64QAM coding rate 3/4 */
+#define WSM_TRANSMIT_RATE_54 (13)
+
+/* 6.5 Mbps HT-OFDM, BPSK coding rate 1/2 */
+#define WSM_TRANSMIT_RATE_HT_6 (14)
+
+/* 13 Mbps HT-OFDM, QPSK coding rate 1/2 */
+#define WSM_TRANSMIT_RATE_HT_13 (15)
+
+/* 19.5 Mbps HT-OFDM, QPSK coding rate 3/4 */
+#define WSM_TRANSMIT_RATE_HT_19 (16)
+
+/* 26 Mbps HT-OFDM, 16QAM coding rate 1/2 */
+#define WSM_TRANSMIT_RATE_HT_26 (17)
+
+/* 39 Mbps HT-OFDM, 16QAM coding rate 3/4 */
+#define WSM_TRANSMIT_RATE_HT_39 (18)
+
+/* 52 Mbps HT-OFDM, 64QAM coding rate 2/3 */
+#define WSM_TRANSMIT_RATE_HT_52 (19)
+
+/* 58.5 Mbps HT-OFDM, 64QAM coding rate 3/4 */
+#define WSM_TRANSMIT_RATE_HT_58 (20)
+
+/* 65 Mbps HT-OFDM, 64QAM coding rate 5/6 */
+#define WSM_TRANSMIT_RATE_HT_65 (21)
+
+/* Scan types */
+/* Foreground scan */
+#define WSM_SCAN_TYPE_FOREGROUND (0)
+
+/* Background scan */
+#define WSM_SCAN_TYPE_BACKGROUND (1)
+
+/* Auto scan */
+#define WSM_SCAN_TYPE_AUTO (2)
+
+/* Scan flags */
+/* Forced background scan means if the station cannot */
+/* enter the power-save mode, it shall force to perform a */
+/* background scan. Only valid when ScanType is */
+/* background scan. */
+#define WSM_SCAN_FLAG_FORCE_BACKGROUND (BIT(0))
+
+/* The WLAN device scans one channel at a time so */
+/* that disturbance to the data traffic is minimized. */
+#define WSM_SCAN_FLAG_SPLIT_METHOD (BIT(1))
+
+/* Preamble Type. Long if not set. */
+#define WSM_SCAN_FLAG_SHORT_PREAMBLE (BIT(2))
+
+/* 11n Tx Mode. Mixed if not set. */
+#define WSM_SCAN_FLAG_11N_GREENFIELD (BIT(3))
+
+/* Scan constraints */
+/* Maximum number of channels to be scanned. */
+#define WSM_SCAN_MAX_NUM_OF_CHANNELS (48)
+
+/* The maximum number of SSIDs that the device can scan for. */
+#define WSM_SCAN_MAX_NUM_OF_SSIDS (2)
+
+/* Power management modes */
+/* 802.11 Active mode */
+#define WSM_PSM_ACTIVE (0)
+
+/* 802.11 PS mode */
+#define WSM_PSM_PS BIT(0)
+
+/* Fast Power Save bit */
+#define WSM_PSM_FAST_PS_FLAG BIT(7)
+
+/* Dynamic aka Fast power save */
+#define WSM_PSM_FAST_PS (BIT(0) | BIT(7))
+
+/* Undetermined */
+/* Note : Undetermined status is reported when the */
+/* NULL data frame used to advertise the PM mode to */
+/* the AP at Pre or Post Background Scan is not Acknowledged */
+#define WSM_PSM_UNKNOWN BIT(1)
+
+/* Queue IDs */
+/* best effort/legacy */
+#define WSM_QUEUE_BEST_EFFORT (0)
+
+/* background */
+#define WSM_QUEUE_BACKGROUND (1)
+
+/* video */
+#define WSM_QUEUE_VIDEO (2)
+
+/* voice */
+#define WSM_QUEUE_VOICE (3)
+
+/* HT TX parameters */
+/* Non-HT */
+#define WSM_HT_TX_NON_HT (0)
+
+/* Mixed format */
+#define WSM_HT_TX_MIXED (1)
+
+/* Greenfield format */
+#define WSM_HT_TX_GREENFIELD (2)
+
+/* STBC allowed */
+#define WSM_HT_TX_STBC (BIT(7))
+
+/* EPTA prioirty flags for BT Coex */
+/* default epta priority */
+#define WSM_EPTA_PRIORITY_DEFAULT 4
+/* use for normal data */
+#define WSM_EPTA_PRIORITY_DATA 4
+/* use for connect/disconnect/roaming*/
+#define WSM_EPTA_PRIORITY_MGT 5
+/* use for action frames */
+#define WSM_EPTA_PRIORITY_ACTION 5
+/* use for AC_VI data */
+#define WSM_EPTA_PRIORITY_VIDEO 5
+/* use for AC_VO data */
+#define WSM_EPTA_PRIORITY_VOICE 6
+/* use for EAPOL exchange */
+#define WSM_EPTA_PRIORITY_EAPOL 7
+
+/* TX status */
+/* Frame was sent aggregated */
+/* Only valid for WSM_SUCCESS status. */
+#define WSM_TX_STATUS_AGGREGATION (BIT(0))
+
+/* Host should requeue this frame later. */
+/* Valid only when status is WSM_REQUEUE. */
+#define WSM_TX_STATUS_REQUEUE (BIT(1))
+
+/* Normal Ack */
+#define WSM_TX_STATUS_NORMAL_ACK (0<<2)
+
+/* No Ack */
+#define WSM_TX_STATUS_NO_ACK (1<<2)
+
+/* No explicit acknowledgement */
+#define WSM_TX_STATUS_NO_EXPLICIT_ACK (2<<2)
+
+/* Block Ack */
+/* Only valid for WSM_SUCCESS status. */
+#define WSM_TX_STATUS_BLOCK_ACK (3<<2)
+
+/* RX status */
+/* Unencrypted */
+#define WSM_RX_STATUS_UNENCRYPTED (0<<0)
+
+/* WEP */
+#define WSM_RX_STATUS_WEP (1<<0)
+
+/* TKIP */
+#define WSM_RX_STATUS_TKIP (2<<0)
+
+/* AES */
+#define WSM_RX_STATUS_AES (3<<0)
+
+/* WAPI */
+#define WSM_RX_STATUS_WAPI (4<<0)
+
+/* Macro to fetch encryption subfield. */
+#define WSM_RX_STATUS_ENCRYPTION(status) ((status) & 0x07)
+
+/* Frame was part of an aggregation */
+#define WSM_RX_STATUS_AGGREGATE (BIT(3))
+
+/* Frame was first in the aggregation */
+#define WSM_RX_STATUS_AGGREGATE_FIRST (BIT(4))
+
+/* Frame was last in the aggregation */
+#define WSM_RX_STATUS_AGGREGATE_LAST (BIT(5))
+
+/* Indicates a defragmented frame */
+#define WSM_RX_STATUS_DEFRAGMENTED (BIT(6))
+
+/* Indicates a Beacon frame */
+#define WSM_RX_STATUS_BEACON (BIT(7))
+
+/* Indicates STA bit beacon TIM field */
+#define WSM_RX_STATUS_TIM (BIT(8))
+
+/* Indicates Beacon frame's virtual bitmap contains multicast bit */
+#define WSM_RX_STATUS_MULTICAST (BIT(9))
+
+/* Indicates frame contains a matching SSID */
+#define WSM_RX_STATUS_MATCHING_SSID (BIT(10))
+
+/* Indicates frame contains a matching BSSI */
+#define WSM_RX_STATUS_MATCHING_BSSI (BIT(11))
+
+/* Indicates More bit set in Framectl field */
+#define WSM_RX_STATUS_MORE_DATA (BIT(12))
+
+/* Indicates frame received during a measurement process */
+#define WSM_RX_STATUS_MEASUREMENT (BIT(13))
+
+/* Indicates frame received as an HT packet */
+#define WSM_RX_STATUS_HT (BIT(14))
+
+/* Indicates frame received with STBC */
+#define WSM_RX_STATUS_STBC (BIT(15))
+
+/* Indicates Address 1 field matches dot11StationId */
+#define WSM_RX_STATUS_ADDRESS1 (BIT(16))
+
+/* Indicates Group address present in the Address 1 field */
+#define WSM_RX_STATUS_GROUP (BIT(17))
+
+/* Indicates Broadcast address present in the Address 1 field */
+#define WSM_RX_STATUS_BROADCAST (BIT(18))
+
+/* Indicates group key used with encrypted frames */
+#define WSM_RX_STATUS_GROUP_KEY (BIT(19))
+
+/* Macro to fetch encryption key index. */
+#define WSM_RX_STATUS_KEY_IDX(status) (((status >> 20)) & 0x0F)
+
+/* Frame Control field starts at Frame offset + 2 */
+#define WSM_TX_2BYTES_SHIFT (BIT(7))
+
+/* Join mode */
+/* IBSS */
+#define WSM_JOIN_MODE_IBSS (0)
+
+/* BSS */
+#define WSM_JOIN_MODE_BSS (1)
+
+/* PLCP preamble type */
+/* For long preamble */
+#define WSM_JOIN_PREAMBLE_LONG (0)
+
+/* For short preamble (Long for 1Mbps) */
+#define WSM_JOIN_PREAMBLE_SHORT (1)
+
+/* For short preamble (Long for 1 and 2Mbps) */
+#define WSM_JOIN_PREAMBLE_SHORT_2 (2)
+
+/* Join flags */
+/* Unsynchronized */
+#define WSM_JOIN_FLAGS_UNSYNCRONIZED BIT(0)
+/* The BSS owner is a P2P GO */
+#define WSM_JOIN_FLAGS_P2P_GO BIT(1)
+/* Force to join BSS with the BSSID and the
+ * SSID specified without waiting for beacons. The
+ * ProbeForJoin parameter is ignored. */
+#define WSM_JOIN_FLAGS_FORCE BIT(2)
+/* Give probe request/response higher
+ * priority over the BT traffic */
+#define WSM_JOIN_FLAGS_PRIO BIT(3)
+
+/* Key types */
+#define WSM_KEY_TYPE_WEP_DEFAULT (0)
+#define WSM_KEY_TYPE_WEP_PAIRWISE (1)
+#define WSM_KEY_TYPE_TKIP_GROUP (2)
+#define WSM_KEY_TYPE_TKIP_PAIRWISE (3)
+#define WSM_KEY_TYPE_AES_GROUP (4)
+#define WSM_KEY_TYPE_AES_PAIRWISE (5)
+#define WSM_KEY_TYPE_WAPI_GROUP (6)
+#define WSM_KEY_TYPE_WAPI_PAIRWISE (7)
+
+/* Key indexes */
+#define WSM_KEY_MAX_INDEX (10)
+
+/* ACK policy */
+#define WSM_ACK_POLICY_NORMAL (0)
+#define WSM_ACK_POLICY_NO_ACK (1)
+
+/* Start modes */
+#define WSM_START_MODE_AP (0) /* Mini AP */
+#define WSM_START_MODE_P2P_GO (1) /* P2P GO */
+#define WSM_START_MODE_P2P_DEV (2) /* P2P device */
+
+/* SetAssociationMode MIB flags */
+#define WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE (BIT(0))
+#define WSM_ASSOCIATION_MODE_USE_HT_MODE (BIT(1))
+#define WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET (BIT(2))
+#define WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING (BIT(3))
+#define WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES (BIT(4))
+
+/* RcpiRssiThreshold MIB flags */
+#define WSM_RCPI_RSSI_THRESHOLD_ENABLE (BIT(0))
+#define WSM_RCPI_RSSI_USE_RSSI (BIT(1))
+#define WSM_RCPI_RSSI_DONT_USE_UPPER (BIT(2))
+#define WSM_RCPI_RSSI_DONT_USE_LOWER (BIT(3))
+
+/* Update-ie constants */
+#define WSM_UPDATE_IE_BEACON (BIT(0))
+#define WSM_UPDATE_IE_PROBE_RESP (BIT(1))
+#define WSM_UPDATE_IE_PROBE_REQ (BIT(2))
+
+/* WSM events */
+/* Error */
+#define WSM_EVENT_ERROR (0)
+
+/* BSS lost */
+#define WSM_EVENT_BSS_LOST (1)
+
+/* BSS regained */
+#define WSM_EVENT_BSS_REGAINED (2)
+
+/* Radar detected */
+#define WSM_EVENT_RADAR_DETECTED (3)
+
+/* RCPI or RSSI threshold triggered */
+#define WSM_EVENT_RCPI_RSSI (4)
+
+/* BT inactive */
+#define WSM_EVENT_BT_INACTIVE (5)
+
+/* BT active */
+#define WSM_EVENT_BT_ACTIVE (6)
+
+/* MIB IDs */
+/* 4.1 dot11StationId */
+#define WSM_MIB_ID_DOT11_STATION_ID 0x0000
+
+/* 4.2 dot11MaxtransmitMsduLifeTime */
+#define WSM_MIB_ID_DOT11_MAX_TRANSMIT_LIFTIME 0x0001
+
+/* 4.3 dot11MaxReceiveLifeTime */
+#define WSM_MIB_ID_DOT11_MAX_RECEIVE_LIFETIME 0x0002
+
+/* 4.4 dot11SlotTime */
+#define WSM_MIB_ID_DOT11_SLOT_TIME 0x0003
+
+/* 4.5 dot11GroupAddressesTable */
+#define WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE 0x0004
+#define WSM_MAX_GRP_ADDRTABLE_ENTRIES 8
+
+/* 4.6 dot11WepDefaultKeyId */
+#define WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID 0x0005
+
+/* 4.7 dot11CurrentTxPowerLevel */
+#define WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL 0x0006
+
+/* 4.8 dot11RTSThreshold */
+#define WSM_MIB_ID_DOT11_RTS_THRESHOLD 0x0007
+
+/* 4.9 NonErpProtection */
+#define WSM_MIB_ID_NON_ERP_PROTECTION 0x1000
+
+/* 4.10 ArpIpAddressesTable */
+#define WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE 0x1001
+#define WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES 1
+
+/* 4.11 TemplateFrame */
+#define WSM_MIB_ID_TEMPLATE_FRAME 0x1002
+
+/* 4.12 RxFilter */
+#define WSM_MIB_ID_RX_FILTER 0x1003
+
+/* 4.13 BeaconFilterTable */
+#define WSM_MIB_ID_BEACON_FILTER_TABLE 0x1004
+
+/* 4.14 BeaconFilterEnable */
+#define WSM_MIB_ID_BEACON_FILTER_ENABLE 0x1005
+
+/* 4.15 OperationalPowerMode */
+#define WSM_MIB_ID_OPERATIONAL_POWER_MODE 0x1006
+
+/* 4.16 BeaconWakeUpPeriod */
+#define WSM_MIB_ID_BEACON_WAKEUP_PERIOD 0x1007
+
+/* 4.17 RcpiRssiThreshold */
+#define WSM_MIB_ID_RCPI_RSSI_THRESHOLD 0x1009
+
+/* 4.18 StatisticsTable */
+#define WSM_MIB_ID_STATISTICS_TABLE 0x100A
+
+/* 4.19 IbssPsConfig */
+#define WSM_MIB_ID_IBSS_PS_CONFIG 0x100B
+
+/* 4.20 CountersTable */
+#define WSM_MIB_ID_COUNTERS_TABLE 0x100C
+
+/* 4.21 BlockAckPolicy */
+#define WSM_MIB_ID_BLOCK_ACK_POLICY 0x100E
+
+/* 4.22 OverrideInternalTxRate */
+#define WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE 0x100F
+
+/* 4.23 SetAssociationMode */
+#define WSM_MIB_ID_SET_ASSOCIATION_MODE 0x1010
+
+/* 4.24 UpdateEptaConfigData */
+#define WSM_MIB_ID_UPDATE_EPTA_CONFIG_DATA 0x1011
+
+/* 4.25 SelectCcaMethod */
+#define WSM_MIB_ID_SELECT_CCA_METHOD 0x1012
+
+/* 4.26 SetUpasdInformation */
+#define WSM_MIB_ID_SET_UAPSD_INFORMATION 0x1013
+
+/* 4.27 SetAutoCalibrationMode WBF00004073 */
+#define WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE 0x1015
+
+/* 4.28 SetTxRateRetryPolicy */
+#define WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY 0x1016
+
+/* 4.29 SetHostMessageTypeFilter */
+#define WSM_MIB_ID_SET_HOST_MSG_TYPE_FILTER 0x1017
+
+/* 4.30 P2PFindInfo */
+#define WSM_MIB_ID_P2P_FIND_INFO 0x1018
+
+/* 4.31 P2PPsModeInfo */
+#define WSM_MIB_ID_P2P_PS_MODE_INFO 0x1019
+
+/* 4.32 SetEtherTypeDataFrameFilter */
+#define WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER 0x101A
+
+/* 4.33 SetUDPPortDataFrameFilter */
+#define WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER 0x101B
+
+/* 4.34 SetMagicDataFrameFilter */
+#define WSM_MIB_ID_SET_MAGIC_DATAFRAME_FILTER 0x101C
+
+/* This is the end of specification. */
+
+/* 4.35 P2PDeviceInfo */
+#define WSM_MIB_ID_P2P_DEVICE_INFO 0x101D
+
+/* 4.36 SetWCDMABand */
+#define WSM_MIB_ID_SET_WCDMA_BAND 0x101E
+
+/* 4.37 GroupTxSequenceCounter */
+#define WSM_MIB_ID_GRP_SEQ_COUNTER 0x101F
+
+/* 4.38 ProtectedMgmtPolicy */
+#define WSM_MIB_ID_PROTECTED_MGMT_POLICY 0x1020
+
+/* 4.39 SetHtProtection */
+#define WSM_MID_ID_SET_HT_PROTECTION 0x1021
+
+/* 4.40 GPIO Command */
+#define WSM_MIB_ID_GPIO_COMMAND 0x1022
+
+/* 4.41 TSF Counter Value */
+#define WSM_MIB_ID_TSF_COUNTER 0x1023
+
+/* Test Purposes Only */
+#define WSM_MIB_ID_BLOCK_ACK_INFO 0x100D
+
+/* 4.42 UseMultiTxConfMessage */
+#define WSM_MIB_USE_MULTI_TX_CONF 0x1024
+
+/* 4.43 Keep-alive period */
+#define WSM_MIB_ID_KEEP_ALIVE_PERIOD 0x1025
+
+/* 4.44 Disable BSSID filter */
+#define WSM_MIB_ID_DISABLE_BSSID_FILTER 0x1026
+
+/* Frame template types */
+#define WSM_FRAME_TYPE_PROBE_REQUEST (0)
+#define WSM_FRAME_TYPE_BEACON (1)
+#define WSM_FRAME_TYPE_NULL (2)
+#define WSM_FRAME_TYPE_QOS_NULL (3)
+#define WSM_FRAME_TYPE_PS_POLL (4)
+#define WSM_FRAME_TYPE_PROBE_RESPONSE (5)
+
+#define WSM_FRAME_GREENFIELD (0x80) /* See 4.11 */
+
+/* Status */
+/* The WSM firmware has completed a request */
+/* successfully. */
+#define WSM_STATUS_SUCCESS (0)
+
+/* This is a generic failure code if other error codes do */
+/* not apply. */
+#define WSM_STATUS_FAILURE (1)
+
+/* A request contains one or more invalid parameters. */
+#define WSM_INVALID_PARAMETER (2)
+
+/* The request cannot perform because the device is in */
+/* an inappropriate mode. */
+#define WSM_ACCESS_DENIED (3)
+
+/* The frame received includes a decryption error. */
+#define WSM_STATUS_DECRYPTFAILURE (4)
+
+/* A MIC failure is detected in the received packets. */
+#define WSM_STATUS_MICFAILURE (5)
+
+/* The transmit request failed due to retry limit being */
+/* exceeded. */
+#define WSM_STATUS_RETRY_EXCEEDED (6)
+
+/* The transmit request failed due to MSDU life time */
+/* being exceeded. */
+#define WSM_STATUS_TX_LIFETIME_EXCEEDED (7)
+
+/* The link to the AP is lost. */
+#define WSM_STATUS_LINK_LOST (8)
+
+/* No key was found for the encrypted frame */
+#define WSM_STATUS_NO_KEY_FOUND (9)
+
+/* Jammer was detected when transmitting this frame */
+#define WSM_STATUS_JAMMER_DETECTED (10)
+
+/* The message should be requeued later. */
+/* This is applicable only to Transmit */
+#define WSM_REQUEUE (11)
+
+/* Advanced filtering options */
+#define WSM_MAX_FILTER_ELEMENTS (4)
+
+#define WSM_FILTER_ACTION_IGNORE (0)
+#define WSM_FILTER_ACTION_FILTER_IN (1)
+#define WSM_FILTER_ACTION_FILTER_OUT (2)
+
+#define WSM_FILTER_PORT_TYPE_DST (0)
+#define WSM_FILTER_PORT_TYPE_SRC (1)
+
+
+
+struct wsm_hdr {
+ __le16 len;
+ __le16 id;
+};
+
+#define WSM_TX_SEQ_MAX (7)
+#define WSM_TX_SEQ(seq) \
+ ((seq & WSM_TX_SEQ_MAX) << 13)
+#define WSM_TX_LINK_ID_MAX (0x0F)
+#define WSM_TX_LINK_ID(link_id) \
+ ((link_id & WSM_TX_LINK_ID_MAX) << 6)
+
+#define MAX_BEACON_SKIP_TIME_MS 1000
+
+#define WSM_CMD_LAST_CHANCE_TIMEOUT (HZ * 3 / 2)
+
+/* ******************************************************************** */
+/* WSM capcbility */
+
+struct wsm_caps {
+ u16 numInpChBufs;
+ u16 sizeInpChBuf;
+ u16 hardwareId;
+ u16 hardwareSubId;
+ u16 firmwareCap;
+ u16 firmwareType;
+ u16 firmwareApiVer;
+ u16 firmwareBuildNumber;
+ u16 firmwareVersion;
+ int firmwareReady;
+};
+
+/* ******************************************************************** */
+/* WSM commands */
+
+struct wsm_tx_power_range {
+ int min_power_level;
+ int max_power_level;
+ u32 stepping;
+};
+
+/* 3.1 */
+struct wsm_configuration {
+ /* [in] */ u32 dot11MaxTransmitMsduLifeTime;
+ /* [in] */ u32 dot11MaxReceiveLifeTime;
+ /* [in] */ u32 dot11RtsThreshold;
+ /* [in, out] */ u8 *dot11StationId;
+ /* [in] */ const void *dpdData;
+ /* [in] */ size_t dpdData_size;
+ /* [out] */ u8 dot11FrequencyBandsSupported;
+ /* [out] */ u32 supportedRateMask;
+ /* [out] */ struct wsm_tx_power_range txPowerRange[2];
+};
+
+int wsm_configuration(struct cw1200_common *priv,
+ struct wsm_configuration *arg);
+
+/* 3.3 */
+struct wsm_reset {
+ /* [in] */ int link_id;
+ /* [in] */ bool reset_statistics;
+};
+
+int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg);
+
+/* 3.5 */
+int wsm_read_mib(struct cw1200_common *priv, u16 mibId, void *buf,
+ size_t buf_size);
+
+/* 3.7 */
+int wsm_write_mib(struct cw1200_common *priv, u16 mibId, void *buf,
+ size_t buf_size);
+
+/* 3.9 */
+struct wsm_ssid {
+ u8 ssid[32];
+ u32 length;
+};
+
+struct wsm_scan_ch {
+ u16 number;
+ u32 minChannelTime;
+ u32 maxChannelTime;
+ u32 txPowerLevel;
+};
+
+/* 3.13 */
+struct wsm_scan_complete {
+ /* WSM_STATUS_... */
+ u32 status;
+
+ /* WSM_PSM_... */
+ u8 psm;
+
+ /* Number of channels that the scan operation completed. */
+ u8 numChannels;
+};
+
+typedef void (*wsm_scan_complete_cb) (struct cw1200_common *priv,
+ struct wsm_scan_complete *arg);
+
+/* 3.9 */
+struct wsm_scan {
+ /* WSM_PHY_BAND_... */
+ /* [in] */ u8 band;
+
+ /* WSM_SCAN_TYPE_... */
+ /* [in] */ u8 scanType;
+
+ /* WSM_SCAN_FLAG_... */
+ /* [in] */ u8 scanFlags;
+
+ /* WSM_TRANSMIT_RATE_... */
+ /* [in] */ u8 maxTransmitRate;
+
+ /* Interval period in TUs that the device shall the re- */
+ /* execute the requested scan. Max value supported by the device */
+ /* is 256s. */
+ /* [in] */ u32 autoScanInterval;
+
+ /* Number of probe requests (per SSID) sent to one (1) */
+ /* channel. Zero (0) means that none is send, which */
+ /* means that a passive scan is to be done. Value */
+ /* greater than zero (0) means that an active scan is to */
+ /* be done. */
+ /* [in] */ u32 numOfProbeRequests;
+
+ /* Number of channels to be scanned. */
+ /* Maximum value is WSM_SCAN_MAX_NUM_OF_CHANNELS. */
+ /* [in] */ u8 numOfChannels;
+
+ /* Number of SSID provided in the scan command (this */
+ /* is zero (0) in broadcast scan) */
+ /* The maximum number of SSIDs is WSM_SCAN_MAX_NUM_OF_SSIDS. */
+ /* [in] */ u8 numOfSSIDs;
+
+ /* The delay time (in microseconds) period */
+ /* before sending a probe-request. */
+ /* [in] */ u8 probeDelay;
+
+ /* SSIDs to be scanned [numOfSSIDs]; */
+ /* [in] */ struct wsm_ssid *ssids;
+
+ /* Channels to be scanned [numOfChannels]; */
+ /* [in] */ struct wsm_scan_ch *ch;
+};
+
+int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg);
+
+/* 3.11 */
+int wsm_stop_scan(struct cw1200_common *priv);
+
+/* 3.14 */
+struct wsm_tx_confirm {
+ /* Packet identifier used in wsm_tx. */
+ /* [out] */ u32 packetID;
+
+ /* WSM_STATUS_... */
+ /* [out] */ u32 status;
+
+ /* WSM_TRANSMIT_RATE_... */
+ /* [out] */ u8 txedRate;
+
+ /* The number of times the frame was transmitted */
+ /* without receiving an acknowledgement. */
+ /* [out] */ u8 ackFailures;
+
+ /* WSM_TX_STATUS_... */
+ /* [out] */ u16 flags;
+
+ /* The total time in microseconds that the frame spent in */
+ /* the WLAN device before transmission as completed. */
+ /* [out] */ u32 mediaDelay;
+
+ /* The total time in microseconds that the frame spent in */
+ /* the WLAN device before transmission was started. */
+ /* [out] */ u32 txQueueDelay;
+
+ /* [out]*/ u32 link_id;
+};
+
+/* 3.15 */
+typedef void (*wsm_tx_confirm_cb) (struct cw1200_common *priv,
+ struct wsm_tx_confirm *arg);
+
+/* Note that ideology of wsm_tx struct is different against the rest of
+ * WSM API. wsm_hdr is /not/ a caller-adapted struct to be used as an input
+ * argument for WSM call, but a prepared bytestream to be sent to firmware.
+ * It is filled partly in cw1200_tx, partly in low-level WSM code.
+ * Please pay attention once again: ideology is different.
+ *
+ * Legend:
+ * - [in]: cw1200_tx must fill this field.
+ * - [wsm]: the field is filled by low-level WSM.
+ */
+struct wsm_tx {
+ /* common WSM header */
+ /* [in/wsm] */ struct wsm_hdr hdr;
+
+ /* Packet identifier that meant to be used in completion. */
+ /* [in] */ __le32 packetID;
+
+ /* WSM_TRANSMIT_RATE_... */
+ /* [in] */ u8 maxTxRate;
+
+ /* WSM_QUEUE_... */
+ /* [in] */ u8 queueId;
+
+ /* True: another packet is pending on the host for transmission. */
+ /* [wsm] */ u8 more;
+
+ /* Bit 0 = 0 - Start expiry time from first Tx attempt (default) */
+ /* Bit 0 = 1 - Start expiry time from receipt of Tx Request */
+ /* Bits 3:1 - PTA Priority */
+ /* Bits 6:4 - Tx Rate Retry Policy */
+ /* Bit 7 - Reserved */
+ /* [in] */ u8 flags;
+
+ /* Should be 0. */
+ /* [in] */ __le32 reserved;
+
+ /* The elapsed time in TUs, after the initial transmission */
+ /* of an MSDU, after which further attempts to transmit */
+ /* the MSDU shall be terminated. Overrides the global */
+ /* dot11MaxTransmitMsduLifeTime setting [optional] */
+ /* Device will set the default value if this is 0. */
+ /* [wsm] */ __le32 expireTime;
+
+ /* WSM_HT_TX_... */
+ /* [in] */ __le32 htTxParameters;
+};
+
+/* = sizeof(generic hi hdr) + sizeof(wsm hdr) + sizeof(alignment) */
+#define WSM_TX_EXTRA_HEADROOM (28)
+
+/* 3.16 */
+struct wsm_rx {
+ /* WSM_STATUS_... */
+ /* [out] */ u32 status;
+
+ /* Specifies the channel of the received packet. */
+ /* [out] */ u16 channelNumber;
+
+ /* WSM_TRANSMIT_RATE_... */
+ /* [out] */ u8 rxedRate;
+
+ /* This value is expressed in signed Q8.0 format for */
+ /* RSSI and unsigned Q7.1 format for RCPI. */
+ /* [out] */ u8 rcpiRssi;
+
+ /* WSM_RX_STATUS_... */
+ /* [out] */ u32 flags;
+
+ /* An 802.11 frame. */
+ /* [out] */ void *frame;
+
+ /* Size of the frame */
+ /* [out] */ size_t frame_size;
+
+ /* Link ID */
+ /* [out] */ int link_id;
+};
+
+/* = sizeof(generic hi hdr) + sizeof(wsm hdr) */
+#define WSM_RX_EXTRA_HEADROOM (16)
+
+typedef void (*wsm_rx_cb) (struct cw1200_common *priv, struct wsm_rx *arg,
+ struct sk_buff **skb_p);
+
+/* 3.17 */
+struct wsm_event {
+ /* WSM_STATUS_... */
+ /* [out] */ u32 eventId;
+
+ /* Indication parameters. */
+ /* For error indication, this shall be a 32-bit WSM status. */
+ /* For RCPI or RSSI indication, this should be an 8-bit */
+ /* RCPI or RSSI value. */
+ /* [out] */ u32 eventData;
+};
+
+struct cw1200_wsm_event {
+ struct list_head link;
+ struct wsm_event evt;
+};
+
+/* 3.18 - 3.22 */
+/* Measurement. Skipped for now. Irrelevent. */
+
+typedef void (*wsm_event_cb) (struct cw1200_common *priv,
+ struct wsm_event *arg);
+
+/* 3.23 */
+struct wsm_join {
+ /* WSM_JOIN_MODE_... */
+ /* [in] */ u8 mode;
+
+ /* WSM_PHY_BAND_... */
+ /* [in] */ u8 band;
+
+ /* Specifies the channel number to join. The channel */
+ /* number will be mapped to an actual frequency */
+ /* according to the band */
+ /* [in] */ u16 channelNumber;
+
+ /* Specifies the BSSID of the BSS or IBSS to be joined */
+ /* or the IBSS to be started. */
+ /* [in] */ u8 bssid[6];
+
+ /* ATIM window of IBSS */
+ /* When ATIM window is zero the initiated IBSS does */
+ /* not support power saving. */
+ /* [in] */ u16 atimWindow;
+
+ /* WSM_JOIN_PREAMBLE_... */
+ /* [in] */ u8 preambleType;
+
+ /* Specifies if a probe request should be send with the */
+ /* specified SSID when joining to the network. */
+ /* [in] */ u8 probeForJoin;
+
+ /* DTIM Period (In multiples of beacon interval) */
+ /* [in] */ u8 dtimPeriod;
+
+ /* WSM_JOIN_FLAGS_... */
+ /* [in] */ u8 flags;
+
+ /* Length of the SSID */
+ /* [in] */ u32 ssidLength;
+
+ /* Specifies the SSID of the IBSS to join or start */
+ /* [in] */ u8 ssid[32];
+
+ /* Specifies the time between TBTTs in TUs */
+ /* [in] */ u32 beaconInterval;
+
+ /* A bit mask that defines the BSS basic rate set. */
+ /* [in] */ u32 basicRateSet;
+
+ /* Minimum transmission power level in units of 0.1dBm */
+ /* [out] */ int minPowerLevel;
+
+ /* Maximum transmission power level in units of 0.1dBm */
+ /* [out] */ int maxPowerLevel;
+};
+
+int wsm_join(struct cw1200_common *priv, struct wsm_join *arg);
+
+/* 3.25 */
+struct wsm_set_pm {
+ /* WSM_PSM_... */
+ /* [in] */ u8 pmMode;
+
+ /* in unit of 500us; 0 to use default */
+ /* [in] */ u8 fastPsmIdlePeriod;
+
+ /* in unit of 500us; 0 to use default */
+ /* [in] */ u8 apPsmChangePeriod;
+
+ /* in unit of 500us; 0 to disable auto-pspoll */
+ /* [in] */ u8 minAutoPsPollPeriod;
+};
+
+int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg);
+
+/* 3.27 */
+struct wsm_set_pm_complete {
+ u8 psm; /* WSM_PSM_... */
+};
+
+typedef void (*wsm_set_pm_complete_cb) (struct cw1200_common *priv,
+ struct wsm_set_pm_complete *arg);
+
+/* 3.28 */
+struct wsm_set_bss_params {
+ /* The number of lost consecutive beacons after which */
+ /* the WLAN device should indicate the BSS-Lost event */
+ /* to the WLAN host driver. */
+ u8 beaconLostCount;
+
+ /* The AID received during the association process. */
+ u16 aid;
+
+ /* The operational rate set mask */
+ u32 operationalRateSet;
+};
+
+int wsm_set_bss_params(struct cw1200_common *priv,
+ const struct wsm_set_bss_params *arg);
+
+/* 3.30 */
+struct wsm_add_key {
+ u8 type; /* WSM_KEY_TYPE_... */
+ u8 entryIndex; /* Key entry index: 0 -- WSM_KEY_MAX_INDEX */
+ u16 reserved;
+ union {
+ struct {
+ u8 peerAddress[6]; /* MAC address of the
+ * peer station */
+ u8 reserved;
+ u8 keyLength; /* Key length in bytes */
+ u8 keyData[16]; /* Key data */
+ } __packed wepPairwiseKey;
+ struct {
+ u8 keyId; /* Unique per key identifier
+ * (0..3) */
+ u8 keyLength; /* Key length in bytes */
+ u16 reserved;
+ u8 keyData[16]; /* Key data */
+ } __packed wepGroupKey;
+ struct {
+ u8 peerAddress[6]; /* MAC address of the
+ * peer station */
+ u8 reserved[2];
+ u8 tkipKeyData[16]; /* TKIP key data */
+ u8 rxMicKey[8]; /* Rx MIC key */
+ u8 txMicKey[8]; /* Tx MIC key */
+ } __packed tkipPairwiseKey;
+ struct {
+ u8 tkipKeyData[16]; /* TKIP key data */
+ u8 rxMicKey[8]; /* Rx MIC key */
+ u8 keyId; /* Key ID */
+ u8 reserved[3];
+ u8 rxSeqCounter[8]; /* Receive Sequence Counter */
+ } __packed tkipGroupKey;
+ struct {
+ u8 peerAddress[6]; /* MAC address of the
+ * peer station */
+ u16 reserved;
+ u8 aesKeyData[16]; /* AES key data */
+ } __packed aesPairwiseKey;
+ struct {
+ u8 aesKeyData[16]; /* AES key data */
+ u8 keyId; /* Key ID */
+ u8 reserved[3];
+ u8 rxSeqCounter[8]; /* Receive Sequence Counter */
+ } __packed aesGroupKey;
+ struct {
+ u8 peerAddress[6]; /* MAC address of the
+ * peer station */
+ u8 keyId; /* Key ID */
+ u8 reserved;
+ u8 wapiKeyData[16]; /* WAPI key data */
+ u8 micKeyData[16]; /* MIC key data */
+ } __packed wapiPairwiseKey;
+ struct {
+ u8 wapiKeyData[16]; /* WAPI key data */
+ u8 micKeyData[16]; /* MIC key data */
+ u8 keyId; /* Key ID */
+ u8 reserved[3];
+ } __packed wapiGroupKey;
+ } __packed;
+} __packed;
+
+int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg);
+
+/* 3.32 */
+struct wsm_remove_key {
+ /* Key entry index : 0-10 */
+ u8 entryIndex;
+};
+
+int wsm_remove_key(struct cw1200_common *priv,
+ const struct wsm_remove_key *arg);
+
+/* 3.34 */
+struct wsm_set_tx_queue_params {
+ /* WSM_ACK_POLICY_... */
+ u8 ackPolicy;
+
+ /* Medium Time of TSPEC (in 32us units) allowed per */
+ /* One Second Averaging Period for this queue. */
+ u16 allowedMediumTime;
+
+ /* dot11MaxTransmitMsduLifetime to be used for the */
+ /* specified queue. */
+ u32 maxTransmitLifetime;
+};
+
+struct wsm_tx_queue_params {
+ /* NOTE: index is a linux queue id. */
+ struct wsm_set_tx_queue_params params[4];
+};
+
+
+#define WSM_TX_QUEUE_SET(queue_params, queue, ack_policy, allowed_time,\
+ max_life_time) \
+do { \
+ struct wsm_set_tx_queue_params *p = &(queue_params)->params[queue]; \
+ p->ackPolicy = (ack_policy); \
+ p->allowedMediumTime = (allowed_time); \
+ p->maxTransmitLifetime = (max_life_time); \
+} while (0)
+
+int wsm_set_tx_queue_params(struct cw1200_common *priv,
+ const struct wsm_set_tx_queue_params *arg, u8 id);
+
+/* 3.36 */
+struct wsm_edca_queue_params {
+ /* CWmin (in slots) for the access class. */
+ /* [in] */ u16 cwMin;
+
+ /* CWmax (in slots) for the access class. */
+ /* [in] */ u16 cwMax;
+
+ /* AIFS (in slots) for the access class. */
+ /* [in] */ u8 aifns;
+
+ /* TX OP Limit (in microseconds) for the access class. */
+ /* [in] */ u16 txOpLimit;
+
+ /* dot11MaxReceiveLifetime to be used for the specified */
+ /* the access class. Overrides the global */
+ /* dot11MaxReceiveLifetime value */
+ /* [in] */ u32 maxReceiveLifetime;
+
+ /* UAPSD trigger support for the access class. */
+ /* [in] */ bool uapsdEnable;
+};
+
+struct wsm_edca_params {
+ /* NOTE: index is a linux queue id. */
+ struct wsm_edca_queue_params params[4];
+};
+
+#define TXOP_UNIT 32
+#define WSM_EDCA_SET(edca, queue, aifs, cw_min, cw_max, txop, life_time,\
+ uapsd) \
+ do { \
+ struct wsm_edca_queue_params *p = &(edca)->params[queue]; \
+ p->cwMin = (cw_min); \
+ p->cwMax = (cw_max); \
+ p->aifns = (aifs); \
+ p->txOpLimit = ((txop) * TXOP_UNIT); \
+ p->maxReceiveLifetime = (life_time); \
+ p->uapsdEnable = (uapsd); \
+ } while (0)
+
+int wsm_set_edca_params(struct cw1200_common *priv,
+ const struct wsm_edca_params *arg);
+
+int wsm_set_uapsd_param(struct cw1200_common *priv,
+ const struct wsm_edca_params *arg);
+
+/* 3.38 */
+/* Set-System info. Skipped for now. Irrelevent. */
+
+/* 3.40 */
+struct wsm_switch_channel {
+ /* 1 - means the STA shall not transmit any further */
+ /* frames until the channel switch has completed */
+ /* [in] */ u8 channelMode;
+
+ /* Number of TBTTs until channel switch occurs. */
+ /* 0 - indicates switch shall occur at any time */
+ /* 1 - occurs immediately before the next TBTT */
+ /* [in] */ u8 channelSwitchCount;
+
+ /* The new channel number to switch to. */
+ /* Note this is defined as per section 2.7. */
+ /* [in] */ u16 newChannelNumber;
+};
+
+int wsm_switch_channel(struct cw1200_common *priv,
+ const struct wsm_switch_channel *arg);
+
+typedef void (*wsm_channel_switch_cb) (struct cw1200_common *priv);
+
+struct wsm_start {
+ /* WSM_START_MODE_... */
+ /* [in] */ u8 mode;
+
+ /* WSM_PHY_BAND_... */
+ /* [in] */ u8 band;
+
+ /* Channel number */
+ /* [in] */ u16 channelNumber;
+
+ /* Client Traffic window in units of TU */
+ /* Valid only when mode == ..._P2P */
+ /* [in] */ u32 CTWindow;
+
+ /* Interval between two consecutive */
+ /* beacon transmissions in TU. */
+ /* [in] */ u32 beaconInterval;
+
+ /* DTIM period in terms of beacon intervals */
+ /* [in] */ u8 DTIMPeriod;
+
+ /* WSM_JOIN_PREAMBLE_... */
+ /* [in] */ u8 preambleType;
+
+ /* The delay time (in microseconds) period */
+ /* before sending a probe-request. */
+ /* [in] */ u8 probeDelay;
+
+ /* Length of the SSID */
+ /* [in] */ u8 ssidLength;
+
+ /* SSID of the BSS or P2P_GO to be started now. */
+ /* [in] */ u8 ssid[32];
+
+ /* The basic supported rates for the MiniAP. */
+ /* [in] */ u32 basicRateSet;
+};
+
+int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg);
+
+struct wsm_beacon_transmit {
+ /* 1: enable; 0: disable */
+ /* [in] */ u8 enableBeaconing;
+};
+
+int wsm_beacon_transmit(struct cw1200_common *priv,
+ const struct wsm_beacon_transmit *arg);
+
+int wsm_start_find(struct cw1200_common *priv);
+
+int wsm_stop_find(struct cw1200_common *priv);
+
+typedef void (*wsm_find_complete_cb) (struct cw1200_common *priv, u32 status);
+
+struct wsm_suspend_resume {
+ /* See 3.52 */
+ /* Link ID */
+ /* [out] */ int link_id;
+ /* Stop sending further Tx requests down to device for this link */
+ /* [out] */ bool stop;
+ /* Transmit multicast Frames */
+ /* [out] */ bool multicast;
+ /* The AC on which Tx to be suspended /resumed. */
+ /* This is applicable only for U-APSD */
+ /* WSM_QUEUE_... */
+ /* [out] */ int queue;
+};
+
+typedef void (*wsm_suspend_resume_cb) (struct cw1200_common *priv,
+ struct wsm_suspend_resume *arg);
+
+/* 3.54 Update-IE request. */
+struct wsm_update_ie {
+ /* WSM_UPDATE_IE_... */
+ /* [in] */ u16 what;
+ /* [in] */ u16 count;
+ /* [in] */ u8 *ies;
+ /* [in] */ size_t length;
+};
+
+int wsm_update_ie(struct cw1200_common *priv,
+ const struct wsm_update_ie *arg);
+
+/* 3.56 */
+struct wsm_map_link {
+ /* MAC address of the remote device */
+ /* [in] */ u8 mac_addr[6];
+ /* [in] */ u8 link_id;
+};
+
+int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg);
+
+struct wsm_cbc {
+ wsm_scan_complete_cb scan_complete;
+ wsm_tx_confirm_cb tx_confirm;
+ wsm_rx_cb rx;
+ wsm_event_cb event;
+ wsm_set_pm_complete_cb set_pm_complete;
+ wsm_channel_switch_cb channel_switch;
+ wsm_find_complete_cb find_complete;
+ wsm_suspend_resume_cb suspend_resume;
+};
+
+/* ******************************************************************** */
+/* MIB shortcats */
+
+static inline int wsm_set_output_power(struct cw1200_common *priv,
+ int power_level)
+{
+ __le32 val = __cpu_to_le32(power_level);
+ return wsm_write_mib(priv, WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL,
+ &val, sizeof(val));
+}
+
+static inline int wsm_set_beacon_wakeup_period(struct cw1200_common *priv,
+ unsigned dtim_interval,
+ unsigned listen_interval)
+{
+ struct {
+ u8 numBeaconPeriods;
+ u8 reserved;
+ __le16 listenInterval;
+ } val = {
+ dtim_interval, 0, __cpu_to_le16(listen_interval)};
+ if (dtim_interval > 0xFF || listen_interval > 0xFFFF)
+ return -EINVAL;
+ else
+ return wsm_write_mib(priv, WSM_MIB_ID_BEACON_WAKEUP_PERIOD,
+ &val, sizeof(val));
+}
+
+struct wsm_rcpi_rssi_threshold {
+ u8 rssiRcpiMode; /* WSM_RCPI_RSSI_... */
+ u8 lowerThreshold;
+ u8 upperThreshold;
+ u8 rollingAverageCount;
+};
+
+static inline int wsm_set_rcpi_rssi_threshold(struct cw1200_common *priv,
+ struct wsm_rcpi_rssi_threshold *arg)
+{
+ return wsm_write_mib(priv, WSM_MIB_ID_RCPI_RSSI_THRESHOLD, arg,
+ sizeof(*arg));
+}
+
+struct wsm_counters_table {
+ __le32 countPlcpErrors;
+ __le32 countFcsErrors;
+ __le32 countTxPackets;
+ __le32 countRxPackets;
+ __le32 countRxPacketErrors;
+ __le32 countRxDecryptionFailures;
+ __le32 countRxMicFailures;
+ __le32 countRxNoKeyFailures;
+ __le32 countTxMulticastFrames;
+ __le32 countTxFramesSuccess;
+ __le32 countTxFrameFailures;
+ __le32 countTxFramesRetried;
+ __le32 countTxFramesMultiRetried;
+ __le32 countRxFrameDuplicates;
+ __le32 countRtsSuccess;
+ __le32 countRtsFailures;
+ __le32 countAckFailures;
+ __le32 countRxMulticastFrames;
+ __le32 countRxFramesSuccess;
+ __le32 countRxCMACICVErrors;
+ __le32 countRxCMACReplays;
+ __le32 countRxMgmtCCMPReplays;
+};
+
+static inline int wsm_get_counters_table(struct cw1200_common *priv,
+ struct wsm_counters_table *arg)
+{
+ return wsm_read_mib(priv, WSM_MIB_ID_COUNTERS_TABLE,
+ arg, sizeof(*arg));
+}
+
+static inline int wsm_get_station_id(struct cw1200_common *priv, u8 *mac)
+{
+ return wsm_read_mib(priv, WSM_MIB_ID_DOT11_STATION_ID, mac, ETH_ALEN);
+}
+
+struct wsm_rx_filter {
+ bool promiscuous;
+ bool bssid;
+ bool fcs;
+};
+
+static inline int wsm_set_rx_filter(struct cw1200_common *priv,
+ const struct wsm_rx_filter *arg)
+{
+ __le32 val = 0;
+ if (arg->promiscuous)
+ val |= __cpu_to_le32(BIT(0));
+ if (arg->bssid)
+ val |= __cpu_to_le32(BIT(1));
+ if (arg->fcs)
+ val |= __cpu_to_le32(BIT(2));
+ return wsm_write_mib(priv, WSM_MIB_ID_RX_FILTER, &val, sizeof(val));
+}
+
+#define WSM_BEACON_FILTER_IE_HAS_CHANGED BIT(0)
+#define WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT BIT(1)
+#define WSM_BEACON_FILTER_IE_HAS_APPEARED BIT(2)
+
+struct wsm_beacon_filter_table_entry {
+ u8 ieId;
+ u8 actionFlags;
+ u8 oui[3];
+ u8 matchData[3];
+} __packed;
+
+struct wsm_beacon_filter_table {
+ __le32 numOfIEs;
+ struct wsm_beacon_filter_table_entry entry[10];
+} __packed;
+
+static inline int wsm_set_beacon_filter_table(struct cw1200_common *priv,
+ struct wsm_beacon_filter_table *ft)
+{
+ size_t size = __le32_to_cpu(ft->numOfIEs) *
+ sizeof(struct wsm_beacon_filter_table_entry) +
+ sizeof(__le32);
+
+ return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_TABLE, ft, size);
+}
+
+struct wsm_beacon_filter_control {
+ int enabled;
+ int bcn_count;
+};
+
+static inline int wsm_beacon_filter_control(struct cw1200_common *priv,
+ struct wsm_beacon_filter_control *arg)
+{
+ struct {
+ __le32 enabled;
+ __le32 bcn_count;
+ } val;
+ val.enabled = __cpu_to_le32(arg->enabled);
+ val.bcn_count = __cpu_to_le32(arg->bcn_count);
+ return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_ENABLE, &val,
+ sizeof(val));
+}
+
+enum wsm_power_mode {
+ wsm_power_mode_active = 0,
+ wsm_power_mode_doze = 1,
+ wsm_power_mode_quiescent = 2,
+};
+
+struct wsm_operational_mode {
+ enum wsm_power_mode power_mode;
+ int disableMoreFlagUsage;
+ int performAntDiversity;
+};
+
+static inline int wsm_set_operational_mode(struct cw1200_common *priv,
+ const struct wsm_operational_mode *arg)
+{
+ u8 val = arg->power_mode;
+ if (arg->disableMoreFlagUsage)
+ val |= BIT(4);
+ if (arg->performAntDiversity)
+ val |= BIT(5);
+ return wsm_write_mib(priv, WSM_MIB_ID_OPERATIONAL_POWER_MODE, &val,
+ sizeof(val));
+}
+
+struct wsm_template_frame {
+ u8 frame_type;
+ u8 rate;
+ bool disable;
+ struct sk_buff *skb;
+};
+
+static inline int wsm_set_template_frame(struct cw1200_common *priv,
+ struct wsm_template_frame *arg)
+{
+ int ret;
+ u8 *p = skb_push(arg->skb, 4);
+ p[0] = arg->frame_type;
+ p[1] = arg->rate;
+ if (arg->disable)
+ ((u16 *) p)[1] = 0;
+ else
+ ((u16 *) p)[1] = __cpu_to_le16(arg->skb->len - 4);
+ ret = wsm_write_mib(priv, WSM_MIB_ID_TEMPLATE_FRAME, p, arg->skb->len);
+ skb_pull(arg->skb, 4);
+ return ret;
+}
+
+
+struct wsm_protected_mgmt_policy {
+ bool protectedMgmtEnable;
+ bool unprotectedMgmtFramesAllowed;
+ bool encryptionForAuthFrame;
+};
+
+static inline int wsm_set_protected_mgmt_policy(struct cw1200_common *priv,
+ struct wsm_protected_mgmt_policy *arg)
+{
+ __le32 val = 0;
+ int ret;
+ if (arg->protectedMgmtEnable)
+ val |= __cpu_to_le32(BIT(0));
+ if (arg->unprotectedMgmtFramesAllowed)
+ val |= __cpu_to_le32(BIT(1));
+ if (arg->encryptionForAuthFrame)
+ val |= __cpu_to_le32(BIT(2));
+ ret = wsm_write_mib(priv, WSM_MIB_ID_PROTECTED_MGMT_POLICY,
+ &val, sizeof(val));
+ return ret;
+}
+
+static inline int wsm_set_block_ack_policy(struct cw1200_common *priv,
+ u8 blockAckTxTidPolicy,
+ u8 blockAckRxTidPolicy)
+{
+ struct {
+ u8 blockAckTxTidPolicy;
+ u8 reserved1;
+ u8 blockAckRxTidPolicy;
+ u8 reserved2;
+ } val = {
+ .blockAckTxTidPolicy = blockAckTxTidPolicy,
+ .blockAckRxTidPolicy = blockAckRxTidPolicy,
+ };
+ return wsm_write_mib(priv, WSM_MIB_ID_BLOCK_ACK_POLICY, &val,
+ sizeof(val));
+}
+
+struct wsm_association_mode {
+ u8 flags; /* WSM_ASSOCIATION_MODE_... */
+ u8 preambleType; /* WSM_JOIN_PREAMBLE_... */
+ u8 greenfieldMode; /* 1 for greenfield */
+ u8 mpduStartSpacing;
+ __le32 basicRateSet;
+};
+
+static inline int wsm_set_association_mode(struct cw1200_common *priv,
+ struct wsm_association_mode *arg)
+{
+ return wsm_write_mib(priv, WSM_MIB_ID_SET_ASSOCIATION_MODE, arg,
+ sizeof(*arg));
+}
+
+struct wsm_set_tx_rate_retry_policy_header {
+ u8 numTxRatePolicies;
+ u8 reserved[3];
+} __packed;
+
+struct wsm_set_tx_rate_retry_policy_policy {
+ u8 policyIndex;
+ u8 shortRetryCount;
+ u8 longRetryCount;
+ u8 policyFlags;
+ u8 rateRecoveryCount;
+ u8 reserved[3];
+ __le32 rateCountIndices[3];
+} __packed;
+
+struct wsm_set_tx_rate_retry_policy {
+ struct wsm_set_tx_rate_retry_policy_header hdr;
+ struct wsm_set_tx_rate_retry_policy_policy tbl[8];
+} __packed;
+
+static inline int wsm_set_tx_rate_retry_policy(struct cw1200_common *priv,
+ struct wsm_set_tx_rate_retry_policy *arg)
+{
+ size_t size = sizeof(struct wsm_set_tx_rate_retry_policy_header) +
+ arg->hdr.numTxRatePolicies *
+ sizeof(struct wsm_set_tx_rate_retry_policy_policy);
+ return wsm_write_mib(priv, WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY, arg,
+ size);
+}
+
+/* 4.32 SetEtherTypeDataFrameFilter */
+struct wsm_ether_type_filter_hdr {
+ u8 nrFilters; /* Up to WSM_MAX_FILTER_ELEMENTS */
+ u8 reserved[3];
+} __packed;
+
+struct wsm_ether_type_filter {
+ u8 filterAction; /* WSM_FILTER_ACTION_XXX */
+ u8 reserved;
+ __le16 etherType; /* Type of ethernet frame */
+} __packed;
+
+static inline int wsm_set_ether_type_filter(struct cw1200_common *priv,
+ struct wsm_ether_type_filter_hdr *arg)
+{
+ size_t size = sizeof(struct wsm_ether_type_filter_hdr) +
+ arg->nrFilters * sizeof(struct wsm_ether_type_filter);
+ return wsm_write_mib(priv, WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER,
+ arg, size);
+}
+
+
+/* 4.33 SetUDPPortDataFrameFilter */
+struct wsm_udp_port_filter_hdr {
+ u8 nrFilters; /* Up to WSM_MAX_FILTER_ELEMENTS */
+ u8 reserved[3];
+} __packed;
+
+struct wsm_udp_port_filter {
+ u8 filterAction; /* WSM_FILTER_ACTION_XXX */
+ u8 portType; /* WSM_FILTER_PORT_TYPE_XXX */
+ __le16 udpPort; /* Port number */
+} __packed;
+
+static inline int wsm_set_udp_port_filter(struct cw1200_common *priv,
+ struct wsm_udp_port_filter_hdr *arg)
+{
+ size_t size = sizeof(struct wsm_udp_port_filter_hdr) +
+ arg->nrFilters * sizeof(struct wsm_udp_port_filter);
+ return wsm_write_mib(priv, WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER,
+ arg, size);
+}
+
+/* Undocumented MIBs: */
+/* 4.35 P2PDeviceInfo */
+#define D11_MAX_SSID_LEN (32)
+
+struct wsm_p2p_device_type {
+ __le16 categoryId;
+ u8 oui[4];
+ __le16 subCategoryId;
+} __packed;
+
+struct wsm_p2p_device_info {
+ struct wsm_p2p_device_type primaryDevice;
+ u8 reserved1[3];
+ u8 devNameSize;
+ u8 localDevName[D11_MAX_SSID_LEN];
+ u8 reserved2[3];
+ u8 numSecDevSupported;
+ struct wsm_p2p_device_type secondaryDevices[0];
+} __packed;
+
+/* 4.36 SetWCDMABand - WO */
+struct wsm_cdma_band {
+ u8 WCDMA_Band;
+ u8 reserved[3];
+} __packed;
+
+/* 4.37 GroupTxSequenceCounter - RO */
+struct wsm_group_tx_seq {
+ __le32 bits_47_16;
+ __le16 bits_15_00;
+ __le16 reserved;
+} __packed;
+
+/* 4.39 SetHtProtection - WO */
+#define WSM_DUAL_CTS_PROT_ENB (1 << 0)
+#define WSM_NON_GREENFIELD_STA PRESENT(1 << 1)
+#define WSM_HT_PROT_MODE__NO_PROT (0 << 2)
+#define WSM_HT_PROT_MODE__NON_MEMBER (1 << 2)
+#define WSM_HT_PROT_MODE__20_MHZ (2 << 2)
+#define WSM_HT_PROT_MODE__NON_HT_MIXED (3 << 2)
+#define WSM_LSIG_TXOP_PROT_FULL (1 << 4)
+#define WSM_LARGE_L_LENGTH_PROT (1 << 5)
+
+struct wsm_ht_protection {
+ __le32 flags;
+} __packed;
+
+/* 4.40 GPIO Command - R/W */
+#define WSM_GPIO_COMMAND_SETUP 0
+#define WSM_GPIO_COMMAND_READ 1
+#define WSM_GPIO_COMMAND_WRITE 2
+#define WSM_GPIO_COMMAND_RESET 3
+#define WSM_GPIO_ALL_PINS 0xFF
+
+struct wsm_gpio_command {
+ u8 GPIO_Command;
+ u8 pin;
+ __le16 config;
+} __packed;
+
+/* 4.41 TSFCounter - RO */
+struct wsm_tsf_counter {
+ __le64 TSF_Counter;
+} __packed;
+
+/* 4.43 Keep alive period */
+struct wsm_keep_alive_period {
+ __le16 keepAlivePeriod;
+ u8 reserved[2];
+} __packed;
+
+static inline int wsm_keep_alive_period(struct cw1200_common *priv,
+ int period)
+{
+ struct wsm_keep_alive_period arg = {
+ .keepAlivePeriod = __cpu_to_le16(period),
+ };
+ return wsm_write_mib(priv, WSM_MIB_ID_KEEP_ALIVE_PERIOD,
+ &arg, sizeof(arg));
+};
+
+/* BSSID filtering */
+struct wsm_set_bssid_filtering {
+ u8 filter;
+ u8 reserved[3];
+} __packed;
+
+static inline int wsm_set_bssid_filtering(struct cw1200_common *priv,
+ bool enabled)
+{
+ struct wsm_set_bssid_filtering arg = {
+ .filter = !enabled,
+ };
+ return wsm_write_mib(priv, WSM_MIB_ID_DISABLE_BSSID_FILTER,
+ &arg, sizeof(arg));
+}
+
+/* Multicat filtering - 4.5 */
+struct wsm_multicast_filter {
+ __le32 enable;
+ __le32 numOfAddresses;
+ u8 macAddress[WSM_MAX_GRP_ADDRTABLE_ENTRIES][ETH_ALEN];
+} __packed;
+
+static inline int wsm_set_multicast_filter(struct cw1200_common *priv,
+ struct wsm_multicast_filter *fp)
+{
+ return wsm_write_mib(priv, WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE,
+ fp, sizeof(*fp));
+}
+
+/* ARP IPv4 filtering - 4.10 */
+struct wsm_arp_ipv4_filter {
+ __le32 enable;
+ __be32 ipv4Address[WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES];
+} __packed;
+
+static inline int wsm_set_arp_ipv4_filter(struct cw1200_common *priv,
+ struct wsm_arp_ipv4_filter *fp)
+{
+ return wsm_write_mib(priv, WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE,
+ fp, sizeof(*fp));
+}
+
+/* P2P Power Save Mode Info - 4.31 */
+struct wsm_p2p_ps_modeinfo {
+ u8 oppPsCTWindow;
+ u8 count;
+ u8 reserved;
+ u8 dtimCount;
+ __le32 duration;
+ __le32 interval;
+ __le32 startTime;
+} __packed;
+
+static inline int wsm_set_p2p_ps_modeinfo(struct cw1200_common *priv,
+ struct wsm_p2p_ps_modeinfo *mi)
+{
+ return wsm_write_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO,
+ mi, sizeof(*mi));
+}
+
+static inline int wsm_get_p2p_ps_modeinfo(struct cw1200_common *priv,
+ struct wsm_p2p_ps_modeinfo *mi)
+{
+ return wsm_read_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO,
+ mi, sizeof(*mi));
+}
+
+/* UseMultiTxConfMessage */
+
+static inline int wsm_use_multi_tx_conf(struct cw1200_common *priv,
+ bool enabled)
+{
+ __le32 arg = enabled ? __cpu_to_le32(1) : 0;
+
+ return wsm_write_mib(priv, WSM_MIB_USE_MULTI_TX_CONF,
+ &arg, sizeof(arg));
+}
+
+
+/* 4.26 SetUpasdInformation */
+struct wsm_uapsd_info {
+ __le16 uapsdFlags;
+ __le16 minAutoTriggerInterval;
+ __le16 maxAutoTriggerInterval;
+ __le16 autoTriggerStep;
+};
+
+static inline int wsm_set_uapsd_info(struct cw1200_common *priv,
+ struct wsm_uapsd_info *arg)
+{
+ return wsm_write_mib(priv, WSM_MIB_ID_SET_UAPSD_INFORMATION,
+ arg, sizeof(*arg));
+}
+
+/* 4.22 OverrideInternalTxRate */
+struct wsm_override_internal_txrate {
+ u8 internalTxRate;
+ u8 nonErpInternalTxRate;
+ u8 reserved[2];
+} __packed;
+
+static inline int wsm_set_override_internal_txrate(struct cw1200_common *priv,
+ struct wsm_override_internal_txrate *arg)
+{
+ return wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE,
+ arg, sizeof(*arg));
+}
+
+/* ******************************************************************** */
+/* WSM TX port control */
+
+void wsm_lock_tx(struct cw1200_common *priv);
+void wsm_lock_tx_async(struct cw1200_common *priv);
+bool wsm_flush_tx(struct cw1200_common *priv);
+void wsm_unlock_tx(struct cw1200_common *priv);
+
+/* ******************************************************************** */
+/* WSM / BH API */
+
+int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len);
+int wsm_handle_rx(struct cw1200_common *priv, int id, struct wsm_hdr *wsm,
+ struct sk_buff **skb_p);
+
+/* ******************************************************************** */
+/* wsm_buf API */
+
+struct wsm_buf {
+ u8 *begin;
+ u8 *data;
+ u8 *end;
+};
+
+void wsm_buf_init(struct wsm_buf *buf);
+void wsm_buf_deinit(struct wsm_buf *buf);
+
+/* ******************************************************************** */
+/* wsm_cmd API */
+
+struct wsm_cmd {
+ spinlock_t lock;
+ int done;
+ u8 *ptr;
+ size_t len;
+ void *arg;
+ int ret;
+ u16 cmd;
+};
+
+/* ******************************************************************** */
+/* WSM TX buffer access */
+
+int wsm_get_tx(struct cw1200_common *priv, u8 **data,
+ size_t *tx_len, int *burst);
+void wsm_txed(struct cw1200_common *priv, u8 *data);
+
+/* ******************************************************************** */
+/* Queue mapping: WSM <---> linux */
+/* Linux: VO VI BE BK */
+/* WSM: BE BK VI VO */
+
+static inline u8 wsm_queue_id_to_linux(u8 queueId)
+{
+ static const u8 queue_mapping[] = {
+ IEEE80211_AC_BE,
+ IEEE80211_AC_BK,
+ IEEE80211_AC_VI,
+ IEEE80211_AC_VO,
+ };
+ if (WARN_ON(queueId >= ARRAY_SIZE(queue_mapping)))
+ return IEEE80211_AC_BE;
+ else
+ return queue_mapping[queueId];
+}
+
+static inline u8 wsm_queue_id_to_wsm(u8 queueId)
+{
+ static const u8 queue_mapping[] = {
+ WSM_QUEUE_VOICE,
+ WSM_QUEUE_VIDEO,
+ WSM_QUEUE_BEST_EFFORT,
+ WSM_QUEUE_BACKGROUND,
+ };
+ if (WARN_ON(queueId >= ARRAY_SIZE(queue_mapping)))
+ return WSM_QUEUE_BEST_EFFORT;
+ else
+ return queue_mapping[queueId];
+}
+
+#endif /* CW1200_HWIO_H_INCLUDED */
--
1.7.9


2012-03-02 20:27:06

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 20/21] cw1200: Credits

I just want to say thank you to all the people contributing to the driver code.
- Lukasz Kucharczyk <[email protected]>
- Janusz Dziedzic <[email protected]>
- Bartosz Markowski <[email protected]>
- Amit Shakya <[email protected]>
- Ajitpal Singh <[email protected]>
- Vitaly Wool <[email protected]>
- Marek Kwaczynski <[email protected]>
- Piotr Nakraszewicz <[email protected]>
- Stefan Nilsson <[email protected]>
- Szymon Bigos <[email protected]>
- Ulf Hansson <[email protected]>
- Michal Kazior <[email protected]>
- Philippe Langlais <[email protected]>
- Tomasz Hliwiak <[email protected]>
- Pawel Kulakowski <[email protected]>

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/CREDITS | 20 ++++++++++++++++++++
1 files changed, 20 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/CREDITS

diff --git a/drivers/net/wireless/cw1200/CREDITS b/drivers/net/wireless/cw1200/CREDITS
new file mode 100644
index 0000000..d554e49
--- /dev/null
+++ b/drivers/net/wireless/cw1200/CREDITS
@@ -0,0 +1,20 @@
+History of the cw1200 driver can be found in Igloo kernel git:
+ http://www.igloocommunity.org/gitweb/?p=kernel/igloo-kernel.git;a=summary
+
+Thank you all who have contributed to the driver code:
+ - Dmitry Tarnyagin <[email protected]>
+ - Lukasz Kucharczyk <[email protected]>
+ - Janusz Dziedzic <[email protected]>
+ - Bartosz Markowski <[email protected]>
+ - Amit Shakya <[email protected]>
+ - Ajitpal Singh <[email protected]>
+ - Vitaly Wool <[email protected]>
+ - Marek Kwaczynski <[email protected]>
+ - Piotr Nakraszewicz <[email protected]>
+ - Stefan Nilsson <[email protected]>
+ - Szymon Bigos <[email protected]>
+ - Ulf Hansson <[email protected]>
+ - Michal Kazior <[email protected]>
+ - Philippe Langlais <[email protected]>
+ - Tomasz Hliwiak <[email protected]>
+ - Pawel Kulakowski <[email protected]>
--
1.7.9


2012-03-02 20:26:48

by Dmitry Tarnyagin

[permalink] [raw]
Subject: [PATCHv2 01/21] cw1200: cw1200.h, private driver data.

The patch contains definition of driver private for cw1200 WLAN driver.

Signed-off-by: Dmitry Tarnyagin <[email protected]>
---
drivers/net/wireless/cw1200/cw1200.h | 313 ++++++++++++++++++++++++++++++++++
1 files changed, 313 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/wireless/cw1200/cw1200.h

diff --git a/drivers/net/wireless/cw1200/cw1200.h b/drivers/net/wireless/cw1200/cw1200.h
new file mode 100644
index 0000000..0b8adc1
--- /dev/null
+++ b/drivers/net/wireless/cw1200/cw1200.h
@@ -0,0 +1,313 @@
+/*
+ * Common private data for ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010-2012, ST-Ericsson AB
+ * Author: Dmitry Tarnyagin <[email protected]>
+ *
+ * Based on the mac80211 Prism54 code, which is
+ * Copyright (c) 2006, Michael Wu <[email protected]>
+ *
+ * Based on the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <[email protected]>, et al.
+ *
+ * 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.
+ */
+
+#ifndef CW1200_H
+#define CW1200_H
+
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <net/mac80211.h>
+
+#include "queue.h"
+#include "wsm.h"
+#include "scan.h"
+#include "txrx.h"
+#include "ht.h"
+#include "pm.h"
+
+/* extern */ struct sbus_ops;
+/* extern */ struct task_struct;
+/* extern */ struct cw1200_debug_priv;
+/* extern */ struct firmware;
+
+#if defined(CONFIG_CW1200_TXRX_DEBUG)
+#define txrx_printk(...) printk(__VA_ARGS__)
+#else
+#define txrx_printk(...)
+#endif
+
+#define CW1200_MAX_CTRL_FRAME_LEN (0x1000)
+
+#define CW1200_MAX_STA_IN_AP_MODE (5)
+#define CW1200_LINK_ID_AFTER_DTIM (CW1200_MAX_STA_IN_AP_MODE + 1)
+#define CW1200_LINK_ID_UAPSD (CW1200_MAX_STA_IN_AP_MODE + 2)
+#define CW1200_LINK_ID_MAX (CW1200_MAX_STA_IN_AP_MODE + 3)
+#define CW1200_MAX_REQUEUE_ATTEMPTS (5)
+
+#define CW1200_MAX_TID (8)
+
+#define CW1200_BLOCK_ACK_CNT (30)
+#define CW1200_BLOCK_ACK_THLD (800)
+#define CW1200_BLOCK_ACK_HIST (3)
+#define CW1200_BLOCK_ACK_INTERVAL (1 * HZ / CW1200_BLOCK_ACK_HIST)
+
+/* Please keep order */
+enum cw1200_join_status {
+ CW1200_JOIN_STATUS_PASSIVE = 0,
+ CW1200_JOIN_STATUS_MONITOR,
+ CW1200_JOIN_STATUS_STA,
+ CW1200_JOIN_STATUS_AP,
+};
+
+enum cw1200_link_status {
+ CW1200_LINK_OFF,
+ CW1200_LINK_RESERVE,
+ CW1200_LINK_SOFT,
+ CW1200_LINK_HARD,
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+ CW1200_LINK_RESET,
+ CW1200_LINK_RESET_REMAP,
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+};
+
+enum cw1200_bss_loss_status {
+ CW1200_BSS_LOSS_NONE,
+ CW1200_BSS_LOSS_CHECKING,
+ CW1200_BSS_LOSS_CONFIRMING,
+ CW1200_BSS_LOSS_CONFIRMED,
+};
+
+struct cw1200_link_entry {
+ unsigned long timestamp;
+ enum cw1200_link_status status;
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+ enum cw1200_link_status prev_status;
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+ u8 mac[ETH_ALEN];
+ u8 buffered[CW1200_MAX_TID];
+ struct sk_buff_head rx_queue;
+};
+
+struct cw1200_common {
+ struct cw1200_queue tx_queue[4];
+ struct cw1200_queue_stats tx_queue_stats;
+ int tx_burst_idx;
+ struct cw1200_debug_priv *debug;
+
+ struct ieee80211_hw *hw;
+ struct ieee80211_vif *vif;
+ struct device *pdev;
+ struct workqueue_struct *workqueue;
+
+ struct mutex conf_mutex;
+
+ const struct sbus_ops *sbus_ops;
+ struct sbus_priv *sbus_priv;
+
+ /* HW type (HIF_...) */
+ int hw_type;
+ int hw_revision;
+
+ /* firmware/hardware info */
+ unsigned int tx_hdr_len;
+
+ /* Radio data */
+ int output_power;
+ int noise;
+
+ /* calibration, output power limit and rssi<->dBm conversation data */
+
+ /* BBP/MAC state */
+ const struct firmware *sdd;
+ struct ieee80211_rate *rates;
+ struct ieee80211_rate *mcs_rates;
+ u8 mac_addr[ETH_ALEN];
+ struct ieee80211_channel *channel;
+ u8 bssid[ETH_ALEN];
+ struct wsm_edca_params edca;
+ struct wsm_tx_queue_params tx_queue_params;
+ struct wsm_association_mode association_mode;
+ struct wsm_set_bss_params bss_params;
+ struct cw1200_ht_info ht_info;
+ struct wsm_set_pm powersave_mode;
+ struct wsm_set_pm firmware_ps_mode;
+ int cqm_rssi_thold;
+ unsigned cqm_rssi_hyst;
+ unsigned cqm_tx_failure_thold;
+ unsigned cqm_tx_failure_count;
+ bool cqm_use_rssi;
+ int cqm_link_loss_count;
+ int cqm_beacon_loss_count;
+ int channel_switch_in_progress;
+ wait_queue_head_t channel_switch_done;
+ u8 long_frame_max_tx_count;
+ u8 short_frame_max_tx_count;
+ int mode;
+ bool enable_beacon;
+ int beacon_int;
+ size_t ssid_length;
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ bool listening;
+ struct wsm_rx_filter rx_filter;
+ struct wsm_beacon_filter_table bf_table;
+ struct wsm_beacon_filter_control bf_control;
+ struct wsm_multicast_filter multicast_filter;
+ bool has_multicast_subscription;
+ bool disable_beacon_filter;
+ struct work_struct update_filtering_work;
+ u8 ba_tid_mask;
+ int ba_acc;
+ int ba_cnt;
+ int ba_hist;
+ struct timer_list ba_timer;
+ spinlock_t ba_lock;
+ bool ba_ena;
+ struct work_struct ba_work;
+ struct cw1200_pm_state pm_state;
+ struct wsm_p2p_ps_modeinfo p2p_ps_modeinfo;
+ struct wsm_uapsd_info uapsd_info;
+ bool setbssparams_done;
+ bool is_BT_Present;
+ u8 conf_listen_interval;
+ u32 listen_interval;
+ u32 erp_info;
+
+ /* BH */
+ atomic_t bh_rx;
+ atomic_t bh_tx;
+ atomic_t bh_term;
+ atomic_t bh_suspend;
+ struct task_struct *bh_thread;
+ int bh_error;
+ wait_queue_head_t bh_wq;
+ wait_queue_head_t bh_evt_wq;
+ int buf_id_tx; /* byte */
+ int buf_id_rx; /* byte */
+ int wsm_rx_seq; /* byte */
+ int wsm_tx_seq; /* byte */
+ int hw_bufs_used;
+ struct sk_buff *skb_cache;
+ bool powersave_enabled;
+ bool device_can_sleep;
+
+ /* WSM */
+ struct wsm_caps wsm_caps;
+ struct mutex wsm_cmd_mux;
+ struct wsm_buf wsm_cmd_buf;
+ struct wsm_cmd wsm_cmd;
+ wait_queue_head_t wsm_cmd_wq;
+ wait_queue_head_t wsm_startup_done;
+ struct wsm_cbc wsm_cbc;
+ atomic_t tx_lock;
+
+ /* WSM debug */
+ int wsm_enable_wsm_dumps;
+ u32 wsm_dump_max_size;
+
+ /* Scan status */
+ struct cw1200_scan scan;
+
+ /* WSM Join */
+ enum cw1200_join_status join_status;
+ u8 join_bssid[ETH_ALEN];
+ u32 pending_frame_id;
+ struct work_struct join_work;
+ struct delayed_work join_timeout;
+ struct work_struct unjoin_work;
+ struct work_struct offchannel_work;
+ int join_dtim_period;
+ bool delayed_unjoin;
+
+ /* TX/RX and security */
+ s8 wep_default_key_id;
+ struct work_struct wep_key_work;
+ u32 key_map;
+ struct wsm_add_key keys[WSM_KEY_MAX_INDEX + 1];
+ unsigned long rx_timestamp;
+
+ /* AP powersave */
+ u32 link_id_map;
+ struct cw1200_link_entry link_id_db[CW1200_MAX_STA_IN_AP_MODE];
+ struct work_struct link_id_work;
+ struct delayed_work link_id_gc_work;
+ u32 sta_asleep_mask;
+ u32 pspoll_mask;
+ bool aid0_bit_set;
+ spinlock_t ps_state_lock;
+ bool buffered_multicasts;
+ bool tx_multicast;
+ struct work_struct set_tim_work;
+ struct delayed_work set_cts_work;
+ struct work_struct multicast_start_work;
+ struct work_struct multicast_stop_work;
+ struct timer_list mcast_timeout;
+
+
+ /* WSM events and CQM implementation */
+ spinlock_t event_queue_lock;
+ struct list_head event_queue;
+ struct work_struct event_handler;
+ struct delayed_work bss_loss_work;
+ struct delayed_work connection_loss_work;
+ struct work_struct tx_failure_work;
+ int delayed_link_loss;
+ spinlock_t bss_loss_lock;
+ int bss_loss_status;
+ int bss_loss_confirm_id;
+
+ /* TX rate policy cache */
+ struct tx_policy_cache tx_policy_cache;
+ struct work_struct tx_policy_upload_work;
+
+ /* cryptographic engine information */
+
+ /* bit field of glowing LEDs */
+ u16 softled_state;
+
+ /* statistics */
+ struct ieee80211_low_level_stats stats;
+#if defined(CONFIG_CW1200_CERTIFICATION_TRICKS)
+ /* Workaround for WFD testcase 6.1.10*/
+ struct work_struct linkid_reset_work;
+ u8 action_frame_sa[ETH_ALEN];
+ u8 action_linkid;
+#endif /* CONFIG_CW1200_CERTIFICATION_TRICKS */
+};
+
+struct cw1200_sta_priv {
+ int link_id;
+};
+
+/* interfaces for the drivers */
+int cw1200_core_probe(const struct sbus_ops *sbus_ops,
+ struct sbus_priv *sbus,
+ struct device *pdev,
+ struct cw1200_common **pself);
+void cw1200_core_release(struct cw1200_common *self);
+
+#define CW1200_DBG_MSG 0x00000001
+#define CW1200_DBG_NIY 0x00000002
+#define CW1200_DBG_SBUS 0x00000004
+#define CW1200_DBG_INIT 0x00000008
+#define CW1200_DBG_ERROR 0x00000010
+#define CW1200_DBG_LEVEL 0xFFFFFFFF
+
+#define cw1200_dbg(level, ...) \
+ do { \
+ if ((level) & CW1200_DBG_LEVEL) \
+ printk(KERN_DEBUG __VA_ARGS__); \
+ } while (0)
+
+#define STUB() \
+ do { \
+ cw1200_dbg(CW1200_DBG_NIY, "%s: STUB at line %d.\n", \
+ __func__, __LINE__); \
+ } while (0)
+
+#endif /* CW1200_H */
--
1.7.9


2012-04-26 13:30:18

by Bob Copeland

[permalink] [raw]
Subject: Re: [PATCHv2 08/21] cw1200: wsm.*, implementation of device high-level interface.

On Fri, Mar 02, 2012 at 09:26:30PM +0100, Dmitry Tarnyagin wrote:
> WSM is a message interface between the host and the device.
> The patch defines and implements WSM abstraction layer.

Hi!

I just started reading through this driver and had a few comments/
questions; apologies in advance if they've been changed in more
recent versions.

> +void wsm_lock_tx(struct cw1200_common *priv)
> +{
> + wsm_cmd_lock(priv);
> + if (atomic_add_return(1, &priv->tx_lock) == 1) {
> + if (wsm_flush_tx(priv))
> + wsm_printk(KERN_DEBUG "[WSM] TX is locked.\n");
> + }
> + wsm_cmd_unlock(priv);
> +}

Can you explain how tx_lock works and how it's different from existing
locking primitives? As a newcomer I find it surprising that two different
callers could call wsm_lock_tx{_async}() and neither of them would wait,
but maybe that's just a naming thing.

> +void wsm_buf_init(struct wsm_buf *buf)
> +{
> + BUG_ON(buf->begin);
> + buf->begin = kmalloc(SDIO_BLOCK_SIZE, GFP_KERNEL | GFP_DMA);

Does your platform really need GFP_DMA? It's a no-op on many platforms
and on x86 it will restrict this to low 16 MB of physical memory.

> + if (size & (SDIO_BLOCK_SIZE - 1)) {
> + size &= SDIO_BLOCK_SIZE;
> + size += SDIO_BLOCK_SIZE;
> + }

This looks odd, should you use round_up(size, SDIO_BLOCK_SIZE) instead?
For example, if size = 2 * SDIO_BLOCK_SIZE + 1, it'll silently truncate
to SDIO_BLOCK_SIZE. This code is in a couple of places.

--
Bob Copeland %% http://www.bobcopeland.com

2012-04-29 03:51:16

by Bob Copeland

[permalink] [raw]
Subject: Re: [PATCHv2 12/21] cw1200: sta.*, mac80211 STA callbacks.

Hello,

A couple of other quick notes as I read this code:

On Fri, Mar 02, 2012 at 09:26:34PM +0100, Dmitry Tarnyagin wrote:
> +static inline void __cw1200_free_event_queue(struct list_head *list)
> +{
> + while (!list_empty(list)) {
> + struct cw1200_wsm_event *event =
> + list_first_entry(list, struct cw1200_wsm_event,
> + link);
> + list_del(&event->link);
> + kfree(event);
> + }

I think perhaps you can use list_for_each_entry_safe() here...

> + LIST_HEAD(list);
[...]
> + spin_lock(&priv->event_queue_lock);
> + list_splice_init(&priv->event_queue, &list);
> + spin_unlock(&priv->event_queue_lock);
> + __cw1200_free_event_queue(&list);

...and then you won't need to do the above?

> + if (priv->listening ^ listening) {

That's a unusual (to me anyway) way to spell "a != b". On my
version of gcc, generated code is exactly the same.

--
Bob Copeland %% http://www.bobcopeland.com

2012-05-03 18:57:24

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Dmitry,

> It looks like mac80211 tries to monitor beacons and they are filtered by
> cw1200 device.
>
> IEEE80211_HW_CONNECTION_MONITOR and / or IEEE80211_HW_BEACON_FILTER flags
> (I don't remember, but both of them should be set) should prevent mac80211
> from
> monitoring. Please check if they are set in
> drivers/net/wireless/cw1200/main.c

main.c contains the following as in the patches that you posted:

hw->flags = IEEE80211_HW_SIGNAL_DBM |
IEEE80211_HW_SUPPORTS_PS |
IEEE80211_HW_SUPPORTS_DYNAMIC_PS |
IEEE80211_HW_REPORTS_TX_ACK_STATUS |
IEEE80211_HW_SUPPORTS_UAPSD |
IEEE80211_HW_CONNECTION_MONITOR |
IEEE80211_HW_NEED_DTIM_PERIOD;

IEEE80211_HW_BEACON_FILTER does not seem to be defined anywhere in the
header files. Do I need this?

>>> Also I see unexpectedly high rate of IRQs, they should not come every now
>>> and then..
>>
>> Any ideas what could cause this or how I should debug it?
>
> Are you running with SDIO or GPIO IRQ?

I am using SDIO IRQ.


Thanks,

Kevin

2012-05-07 09:25:40

by Janusz Dziedzic

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

W dniu 5 maja 2012 09:20 u?ytkownik Janusz Dziedzic
<[email protected]> napisa?:
> 2012/5/4 Kevin Groeneveld <[email protected]>:
>>>> So, you should configure this in cw1200_add_interface() for new
>>>> compat-wireless:
>>>>
>>>> ? ? ? ?vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
>>>> ? ? ? ? ? ? ? ? ? ? ? ? ? ? IEEE80211_VIF_SUPPORTS_CQM_RSSI;
>>
>> I tried adding the above code today and I can now connect in station mode.
>>
>> While connected in station mode I get a lot of the following warnings
>> in the log:
>>
>> ieee80211 phy1: Slowpath: tailroom is not big enough. Req: 8, got: 0.
>>
>> Is that anything to worry about?
>
> Yes we have patch for this in mac80211. I can send you on monday.
> cw1200 firmware required pre-allocated IVs.
> Without that performance will be very poor.
>
> There is similar patch in community code - but seems not fullfill all
> our requirements.

Please check such patch, some modification added to:
mac80211: support adding IV-room in the skb for CCMP keys

diff --git a/drivers/staging/cw1200/sta.c b/drivers/staging/cw1200/sta.c
index db05b84..2011fda 100644
--- a/drivers/staging/cw1200/sta.c
+++ b/drivers/staging/cw1200/sta.c
@@ -701,6 +701,8 @@ int cw1200_set_key(struct ieee80211_hw *dev, enum
set_key_cmd cmd,
if (sta)
peer_addr = sta->addr;

+ key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
+
switch (key->cipher) {
case WLAN_CIPHER_SUITE_WEP40:
case WLAN_CIPHER_SUITE_WEP104:
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 965eca8..0da1806 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -939,7 +939,7 @@ static inline bool ieee80211_vif_is_mesh(struct
ieee80211_vif *vif)
* CCMP key if it requires CCMP encryption of management frames (MFP) to
* be done in software.
* @IEEE80211_KEY_FLAG_PUT_IV_SPACE: This flag should be set by the driver
- * for a CCMP key if space should be prepared for the IV, but the IV
+ * for a CCMP key <why only ccmp?> if space should be prepared for
the IV, but the IV
* itself should not be generated. Do not set together with
* @IEEE80211_KEY_FLAG_GENERATE_IV on the same key.
*/
diff --git a/net/mac80211/wpa.c b/net/mac80211/wpa.c
index 0ae23c6..8c9ae77 100644
--- a/net/mac80211/wpa.c
+++ b/net/mac80211/wpa.c
@@ -399,8 +399,7 @@ static int ccmp_encrypt_skb(struct
ieee80211_tx_data *tx, struct sk_buff *skb)
u8 scratch[6 * AES_BLOCK_SIZE];

if (info->control.hw_key &&
- !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_GENERATE_IV) &&
- !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE)) {
+ !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_GENERATE_IV)) {
/*
* hwaccel has no need for preallocated room for CCMP
* header or MIC fields


BR
Janusz

>
>>
>> The driver did seem to crash on me once today spewing out a lot of
>> WARNINGs and trace info to the log. ?Unfortunately I didn't save the
>> log. ?I will try to pay more attention to it if it happens again.
>>
>> I also did a bit more testing in AP mode. ?After changing hostapd to
>> 802.11g mode I got much better throughput. ?However, I did see quite a
>> few warnings in the log such as the following:
>>
>> [ ?296.854219] ieee80211 phy2: Missed interrupt?
>
> This is critical and this is firmware issue.
> After that driver can do recovery ...
> So, logs after that are not important much ...
>
>
>> [ ?297.104219] ieee80211 phy2: Multicast delivery timeout.
>> [ ?254.194450] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000002
>> [ ? 34.731942] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000002
>> [ ?462.159614] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000002
>> [ ?482.560642] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000002
>> [ ?482.568372] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000002
>> [ ?482.601447] ieee80211 phy2: Multicast delivery timeout.
>> [ ?482.736948] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000006
>> [ ?482.879955] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000006
>> [ ?483.052676] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000006
>> [ ?483.570836] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000006
>> [ ?483.578685] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000006
>> [ ?483.765740] ieee80211 phy2: Requeue for link_id 1 (try 2). STAs
>> asleep: 0x00000006
>> [ ?483.773462] ieee80211 phy2: Requeue for link_id 1 (try 2). STAs
>> asleep: 0x00000006
>> [ ?484.340904] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000006
>> [ ?484.348626] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000006
>> [ ?484.356344] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000006
>> [ ?484.670846] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
>> asleep: 0x00000006
>> [ ?484.903498] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000006
>> [ ? ?2.101774] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000000
>> [ ? ?2.109569] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000000
>> [ ? ?2.119271] ieee80211 phy2: Requeue for link_id 0 (try 2). STAs
>> asleep: 0x00000000
>> [ ? ?2.128483] ieee80211 phy2: Requeue for link_id 0 (try 2). STAs
>> asleep: 0x00000004
>> [ ? ?2.137894] ieee80211 phy2: Requeue for link_id 0 (try 3). STAs
>> asleep: 0x00000004
>> [ ? ?5.629055] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000004
>> [ ? 81.020541] ieee80211 phy2: Missed interrupt?
>> [ ? 85.050556] ieee80211 phy2: Missed interrupt?
>> [ ? 96.410571] ieee80211 phy2: Missed interrupt?
>> [ ?100.410560] ieee80211 phy2: Missed interrupt?
>> [ ?105.700565] ieee80211 phy2: Missed interrupt?
>> [ ?119.410573] ieee80211 phy2: Missed interrupt?
>> [ ?140.050548] ieee80211 phy2: Missed interrupt?
>> [ ?156.993680] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000004
>> [ ?157.001679] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
>> asleep: 0x00000004
>> [ ?157.240546] ieee80211 phy2: Multicast delivery timeout.
>> [ ?167.400528] ieee80211 phy2: Missed interrupt?
>>
>> Should I be worried about these?
>>
> Yes :)
>
>
> --
> Janusz Dziedzic



--
Janusz Dziedzic

2012-05-02 16:02:09

by Dmitry Tarnyagin

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Kevin,

>> Please find below in the patchset implementation of a new driver for
>> the ST-Ericsson cw1200 wlan device.
> Hello Dmitry,
>
> What firmware version is this driver tested with? Where can I get the
> firmware files?
The driver was tested with WSM_A30.02.0380 (and lot of other releases:)).
The firmware was available at igloocommunity.org, I will try to find a link.

>
> I have build 328 and 380 but I cannot get the driver to work with
> either of these. Everything looks like it is working okay but when I
> run "iw wlan0 scan" it does not find anything. The debugfs counters
> show I am not receiving any valid packets:
>
Could you enable following options and send kernel log:
CW1200_DEBUGFS=y
CW1200_WSM_DEBUG=y
CW1200_WSM_DUMPS=y
CW1200_BH_DEBUG=y

and also please send content of
/sys/kernel/debug/ieee80211/phy0/cw1200/status

Best regards,
Dmitry

2012-05-09 17:03:35

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Dmitry,

> The driver was tested with WSM_A30.02.0380 (and lot of other releases:)).
> The firmware was available at igloocommunity.org, I will try to find a link.

I found cw1200 firmware files within the following:
http://releases.linaro.org/12.04/ubuntu/leb-snowball/hwpack_linaro-lt-snowball-x11-base_20120424-15_armhf_supported.tar.gz

The wsm_xx.bin files from that tarball are identical to the ones I
already had for build 380. However, the sdd_xx.bin files are
different. When I use the sdd files from snowball nothing seems to
work. I cannot scan or make any connections.

What are the sdd files for and why would they be different?

I noticed the driver parses some information directly from the sdd
file in the cw1200_parse_SDD_file function. Does the file get used
for anything else?

With both sdd files I have the log shows:

PTA element found.
Listen Interval 5

If a PTA element is found the code always sets priv->is_BT_Present =
true. If I do not have Blue Tooth in my system should this still be
set?


Thanks,

Kevin

2012-05-07 19:54:42

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

> Please check such patch, some modification added to:
> mac80211: support adding IV-room in the skb for CCMP keys

Thanks for the patch. Applying it does get rid of the "Slowpath:
tailroom is not big enough" message I was seeing.


Kevin

2012-05-02 16:51:22

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Dmitry,

Thanks for the response.

> Could you enable following options and send kernel log:
> CW1200_DEBUGFS=y
> CW1200_WSM_DEBUG=y
> CW1200_WSM_DUMPS=y
> CW1200_BH_DEBUG=y

I already had these options on. ?The following is the debugfs status
after attempting a scan:

CW1200 Wireless LAN driver status
Hardware: ? 5.5
Firmware: ? WSM 3020.380
FW API: ? ? 1042
FW caps: ? ?0x000B
Mode: ? ? ? station
Assoc: ? ? ?passive
Channel: ? ?1
EDCA(0): ? ?7, 15, 2, 3264, 200
EDCA(1): ? ?15, 31, 2, 6016, 200
EDCA(2): ? ?31, 1023, 3, 0, 200
EDCA(3): ? ?31, 1023, 7, 0, 200
HT: ? ? ? ? off
RSSI thold: 0
RSSI hyst: ?0
TXFL thold: 0
Linkloss: ? 60
Bcnloss: ? ?20
Long retr: ?4
Short retr: 7
RC in use: ?0
BA stat: ? ?0, 0 (0)
Block ACK: ?off

Queue ? ? ? 0:
?capacity: 16
?queued: ? 0
?pending: ?0
?sent: ? ? 0
?locked: ? no
?overfull: no
?link map: 0-> 00 00 00 00 00 00 00 00 <-8

Queue ? ? ? 1:
?capacity: 16
?queued: ? 0
?pending: ?0
?sent: ? ? 0
?locked: ? no
?overfull: no
?link map: 0-> 00 00 00 00 00 00 00 00 <-8

Queue ? ? ? 2:
?capacity: 16
?queued: ? 0
?pending: ?0
?sent: ? ? 0
?locked: ? no
?overfull: no
?link map: 0-> 00 00 00 00 00 00 00 00 <-8

Queue ? ? ? 3:
?capacity: 16
?queued: ? 0
?pending: ?0
?sent: ? ? 0
?locked: ? no
?overfull: no
?link map: 0-> 00 00 00 00 00 00 00 00 <-8

Link map: ? 0-> .. .. .. .. .. .. .. .. <-7
Asleep map: 0-> .. .. .. .. .. .. .. .. <-7
PSPOLL map: 0-> .. .. .. .. .. .. .. .. <-7


BH status: ?alive
Pending RX: 0
Pending TX: 0
TX bufs: ? ?8 x 1632 bytes
Used bufs: ?0
Powermgmt: ?on
Device: ? ? alseep
WSM status: idle
WSM cmd: ? ?0xFFFF (9 bytes)
WSM retval: 0
Datapath: ? unlocked
TXed: ? ? ? 0
AGG TXed: ? 0
MULTI TXed: 0 (0)
RXed: ? ? ? 0
AGG RXed: ? 0
TX miss: ? ?0
TX align: ? 0
TX burst: ? 0
RX burst: ? 0
TX TTL: ? ? 0
Scan: ? ? ? idle
Led state: ?0x00


The following is from the kernel log:

[ ? ?8.101627] Compat-wireless backport release: compat-wireless-2012-04-24-1
[ ? ?8.108519] Backport based on linux-next.git next-20120424
[ ? ?8.192370] cfg80211: Calling CRDA to update world regulatory domain
[ ? ?8.215057] cfg80211: World regulatory domain updated:
[ ? ?8.220311] cfg80211: ? (start_freq - end_freq @ bandwidth),
(max_antenna_gain, max_eirp)
[ ? ?8.236156] cfg80211: ? (2402000 KHz - 2472000 KHz @ 40000 KHz),
(300 mBi, 2000 mBm)
[ ? ?8.245162] cfg80211: ? (2457000 KHz - 2482000 KHz @ 20000 KHz),
(300 mBi, 2000 mBm)
[ ? ?8.254101] cfg80211: ? (2474000 KHz - 2494000 KHz @ 20000 KHz),
(300 mBi, 2000 mBm)
[ ? ?8.271167] cfg80211: ? (5170000 KHz - 5250000 KHz @ 40000 KHz),
(300 mBi, 2000 mBm)
[ ? ?8.278927] cfg80211: ? (5735000 KHz - 5835000 KHz @ 40000 KHz),
(300 mBi, 2000 mBm)
[ ? 80.672423] Probe called
[ ? 80.674493] [BH] register.
[ ? 80.731090] WLAN device is ready.
[ ? 80.731108] Cut 2.x silicon is detected.
[ ? 80.731533] Cut 2.2 detected.
[ ? 80.731580] cw1200_load_firmware: CW1200 detected.
[ ? 80.816222] Firmware download completed.
[ ? 80.816289] SW IRQ subscribe
[ ? 80.821093] [BH] irq.
[ ? 80.821140] [BH] irq.
[ ? 80.821177] [BH] irq.
[ ? 80.821212] [BH] irq.
[ ? 80.821246] [BH] irq.
[ ? 80.821375] [WSM] <<< 0x0801 (168)
[ ? 80.821398] CW1200 WSM init done.
[ ? 80.821402] ? ?Input buffers: 8 x 1632 bytes
[ ? 80.821407] ? ?Hardware: 5.5
[ ? 80.821411] ? ?WSM firmware [WSM_A30.02.0380_PTA4W Feb 17 2012
10:43:10], ver: 3020, build: 380, api: 1042, cap: 0x000B
[ ? 80.848338] [WSM] >>> 0x0006 [MIB: 0x1006] (9)
[ ? 80.848348] [BH] wakeup.
[ ? 80.848502] [BH] irq.
[ ? 80.848536] [BH] irq.
[ ? 80.848570] [BH] irq.
[ ? 80.848692] [WSM] <<< 0x0406 (8)
[ ? 80.848701] [BH] Powerave is enabled.
[ ? 80.848793] [WSM] >>> 0x0006 [MIB: 0x1024] (12)
[ ? 80.848801] [BH] wakeup.
[ ? 80.848942] [BH] irq.
[ ? 80.848975] [BH] irq.
[ ? 80.849095] [WSM] <<< 0x0406 (8)
[ ? 80.859122] ieee80211 phy0: Selected rate control algorithm 'minstrel_ht'
[ ? 80.861178] is registered as 'phy0'
[ ? 81.841078] [BH] Device wakedown.
[ ?123.215695] [WSM] >>> 0x0013 (48)
[ ?123.215711] [BH] wakeup.
[ ?123.215758] [BH] Device wakeup.
[ ?123.233368] [BH] irq.
[ ?123.233418] [BH] irq.
[ ?123.233456] [BH] irq.
[ ?123.233490] [BH] irq.
[ ?123.233537] [BH] Device wakeup.
[ ?123.233597] [BH] Device awake.
[ ?123.233794] [WSM] <<< 0x0413 (8)
[ ?123.233887] [WSM] >>> 0x0006 [MIB: 0x1013] (16)
[ ?123.233896] [BH] wakeup.
[ ?123.234042] [BH] irq.
[ ?123.234075] [BH] irq.
[ ?123.234109] [BH] irq.
[ ?123.234142] [BH] irq.
[ ?123.234264] [WSM] <<< 0x0406 (8)
[ ?123.254241] [WSM] >>> 0x0009 (840)
[ ?123.254257] [BH] wakeup.
[ ?123.254590] [BH] irq.
[ ?123.254630] [BH] irq.
[ ?123.254667] [BH] irq.
[ ?123.254703] [BH] irq.
[ ?123.254738] [BH] irq.
[ ?123.254872] [WSM] <<< 0x0409 (44)
[ ?123.254947] [WSM] >>> 0x0006 [MIB: 0x1009] (12)
[ ?123.254956] [BH] wakeup.
[ ?123.255109] [BH] irq.
[ ?123.255144] [BH] irq.
[ ?123.255180] [BH] irq.
[ ?123.255314] [WSM] <<< 0x0406 (8)
[ ?123.255385] [WSM] >>> 0x0006 [MIB: 0x1009] (12)
[ ?123.255394] [BH] wakeup.
[ ?123.255545] [BH] irq.
[ ?123.255579] [BH] irq.
[ ?123.255616] [BH] irq.
[ ?123.255743] [WSM] <<< 0x0406 (8)
[ ?123.255816] [WSM] >>> 0x0006 [MIB: 0x0003] (12)
[ ?123.255824] [BH] wakeup.
[ ?123.255975] [BH] irq.
[ ?123.256008] [BH] irq.
[ ?123.256045] [BH] irq.
[ ?123.256080] [BH] irq.
[ ?123.256208] [WSM] <<< 0x0406 (8)
[ ?123.256284] [WSM] >>> 0x0012 (16)
[ ?123.256292] [BH] wakeup.
[ ?123.256460] [BH] irq.
[ ?123.256494] [BH] irq.
[ ?123.256617] [WSM] <<< 0x0412 (8)
[ ?123.256683] [WSM] >>> 0x0013 (48)
[ ?123.256690] [BH] wakeup.
[ ?123.256855] [BH] irq.
[ ?123.256890] [BH] irq.
[ ?123.257017] [WSM] <<< 0x0413 (8)
[ ?123.257080] [WSM] >>> 0x0006 [MIB: 0x1013] (16)
[ ?123.257087] [BH] wakeup.
[ ?123.257271] [BH] irq.
[ ?123.257307] [BH] irq.
[ ?123.257346] [BH] irq.
[ ?123.257470] [WSM] <<< 0x0406 (8)
[ ?123.257533] [WSM] >>> 0x0012 (16)
[ ?123.257539] [BH] wakeup.
[ ?123.257687] [BH] irq.
[ ?123.257722] [BH] irq.
[ ?123.258940] [WSM] <<< 0x0412 (8)
[ ?123.259080] [WSM] >>> 0x0013 (48)
[ ?123.259088] [BH] wakeup.
[ ?123.259272] [BH] irq.
[ ?123.259307] [BH] irq.
[ ?123.259438] [WSM] <<< 0x0413 (8)
[ ?123.259477] [WSM] >>> 0x0006 [MIB: 0x1013] (16)
[ ?123.259485] [BH] wakeup.
[ ?123.259637] [BH] irq.
[ ?123.259671] [BH] irq.
[ ?123.259706] [BH] irq.
[ ?123.259740] [BH] irq.
[ ?123.259866] [WSM] <<< 0x0406 (8)
[ ?123.259905] [WSM] >>> 0x0012 (16)
[ ?123.259912] [BH] wakeup.
[ ?123.260058] [BH] irq.
[ ?123.260092] [BH] irq.
[ ?123.260127] [BH] irq.
[ ?123.260161] [BH] irq.
[ ?123.260195] [BH] irq.
[ ?123.260321] [WSM] <<< 0x0412 (8)
[ ?123.260356] [WSM] >>> 0x0013 (48)
[ ?123.260363] [BH] wakeup.
[ ?123.260508] [BH] irq.
[ ?123.260542] [BH] irq.
[ ?123.260667] [WSM] <<< 0x0413 (8)
[ ?123.260703] [WSM] >>> 0x0006 [MIB: 0x1013] (16)
[ ?123.260711] [BH] wakeup.
[ ?123.260860] [BH] irq.
[ ?123.260894] [BH] irq.
[ ?123.260929] [BH] irq.
[ ?123.260963] [BH] irq.
[ ?123.261125] [WSM] <<< 0x0406 (8)
[ ?123.261164] [WSM] >>> 0x0012 (16)
[ ?123.261171] [BH] wakeup.
[ ?123.261321] [BH] irq.
[ ?123.261355] [BH] irq.
[ ?123.261390] [BH] irq.
[ ?123.261519] [WSM] <<< 0x0412 (8)
[ ?123.261555] [WSM] >>> 0x0013 (48)
[ ?123.261562] [BH] wakeup.
[ ?123.261710] [BH] irq.
[ ?123.261743] [BH] irq.
[ ?123.261778] [BH] irq.
[ ?123.261905] [WSM] <<< 0x0413 (8)
[ ?123.261941] [WSM] >>> 0x0006 [MIB: 0x1013] (16)
[ ?123.261950] [BH] wakeup.
[ ?123.262099] [BH] irq.
[ ?123.262133] [BH] irq.
[ ?123.262256] [WSM] <<< 0x0406 (8)
[ ?123.262303] [WSM] TX is locked.
[ ?123.262312] [BH] wakeup.
[ ?123.262319] [WSM] TX is unlocked.
[ ?123.262332] [WSM] >>> 0x0006 [MIB: 0x0006] (12)
[ ?123.262339] [BH] wakeup.
[ ?123.262495] [BH] irq.
[ ?123.262529] [BH] irq.
[ ?123.262658] [WSM] <<< 0x0406 (8)
[ ?123.262691] [WSM] TX is locked.
[ ?123.262702] [WSM] >>> 0x0016 (8)
[ ?123.262708] [BH] wakeup.
[ ?123.262874] [BH] irq.
[ ?123.262909] [BH] irq.
[ ?123.263039] [WSM] <<< 0x0416 (8)
[ ?123.284588] [BH] irq.
[ ?123.284640] [BH] irq.
[ ?123.284783] [WSM] <<< 0x080A (8)
[ ?123.284796] [BH] wakeup.
[ ?123.284803] [WSM] TX is unlocked.
[ ?123.284836] [WSM] TX is locked.
[ ?123.284851] [WSM] >>> 0x0006 [MIB: 0x1006] (9)
[ ?123.284858] [BH] wakeup.
[ ?123.285007] [BH] irq.
[ ?123.285040] [BH] irq.
[ ?123.285161] [WSM] <<< 0x0406 (8)
[ ?123.285173] [BH] Powerave is enabled.
[ ?123.285201] [BH] wakeup.
[ ?123.285207] [WSM] TX is unlocked.
[ ?124.281096] [BH] Device wakedown.
[ ?153.599285] [WSM] TX is locked.
[ ?153.599309] [WSM] >>> 0x0006 [MIB: 0x1006] (9)
[ ?153.599319] [BH] wakeup.
[ ?153.599367] [BH] Device wakeup.
[ ?153.607490] [BH] irq.
[ ?153.607539] [BH] irq.
[ ?153.607575] [BH] irq.
[ ?153.607609] [BH] irq.
[ ?153.607642] [BH] irq.
[ ?153.607691] [BH] Device wakeup.
[ ?153.607749] [BH] Device awake.
[ ?153.607945] [WSM] <<< 0x0406 (8)
[ ?153.607957] [BH] Powerave is enabled.
[ ?153.608030] [BH] wakeup.
[ ?153.608059] [WSM] TX is unlocked.
[ ?153.608109] [WSM] >>> 0x0006 [MIB: 0x1002] (82)
[ ?153.608116] [BH] wakeup.
[ ?153.608255] [BH] irq.
[ ?153.608288] [BH] irq.
[ ?153.608408] [WSM] <<< 0x0406 (8)
[ ?153.608499] [WSM] >>> 0x0006 [MIB: 0x1002] (82)
[ ?153.608507] [BH] wakeup.
[ ?153.608649] [BH] irq.
[ ?153.608681] [BH] irq.
[ ?153.608715] [BH] irq.
[ ?153.608835] [WSM] <<< 0x0406 (8)
[ ?153.608925] [WSM] TX is locked.
[ ?153.608963] [WSM] >>> 0x0007 (192)
[ ?153.608970] [BH] wakeup.
[ ?153.609203] [BH] irq.
[ ?153.609240] [BH] irq.
[ ?153.609369] [WSM] <<< 0x0407 (8)
[ ?154.557453] [BH] irq.
[ ?154.557501] [BH] irq.
[ ?154.557638] [WSM] <<< 0x0806 (12)
[ ?154.557688] [WSM] >>> 0x0007 (64)
[ ?154.557696] [BH] wakeup.
[ ?154.557935] [BH] irq.
[ ?154.557970] [BH] irq.
[ ?154.558098] [WSM] <<< 0x0407 (8)
[ ?154.831678] [BH] irq.
[ ?154.831714] [BH] irq.
[ ?154.831842] [WSM] <<< 0x0806 (12)
[ ?154.831873] [BH] wakeup.
[ ?154.831880] [WSM] TX is unlocked.
[ ?154.831925] [WSM] TX is locked.
[ ?154.831934] [BH] wakeup.
[ ?154.831939] [WSM] TX is unlocked.
[ ?154.831948] [WSM] TX is locked.
[ ?154.831957] [WSM] >>> 0x0006 [MIB: 0x1006] (9)
[ ?154.831964] [BH] wakeup.
[ ?154.832172] [BH] irq.
[ ?154.832208] [BH] irq.
[ ?154.832334] [WSM] <<< 0x0406 (8)
[ ?154.832345] [BH] Powerave is enabled.
[ ?154.832378] [BH] wakeup.
[ ?154.832385] [WSM] TX is unlocked.
[ ?155.831063] [BH] Device wakedown.
[ ?217.456101] [WSM] >>> 0x0005 (8)
[ ?217.456118] [BH] wakeup.
[ ?217.456166] [BH] Device wakeup.
[ ?217.464294] [BH] irq.
[ ?217.464343] [BH] irq.
[ ?217.464379] [BH] irq.
[ ?217.464413] [BH] irq.
[ ?217.464446] [BH] irq.
[ ?217.464496] [BH] Device wakeup.
[ ?217.464555] [BH] Device awake.
[ ?217.464750] [WSM] <<< 0x0405 (104)
[ ?218.461064] [BH] Device wakedown.

I ran "ifconfig wlan0 up" at about time stamp 123 and "iw wlan0 scan"
at about 153.

As you can see in the log I am compiling the driver patched into
compat-wireless-2012-04-24. ?I am using a kernel based on 2.6.35.3.
Maybe this is a bad combination or not even a valid way of doing
things. ?I don't have much experience with wireless drivers in Linux.

Thanks,

Kevin

2012-05-03 12:43:53

by Bob Copeland

[permalink] [raw]
Subject: Re: [PATCHv2 12/21] cw1200: sta.*, mac80211 STA callbacks.

On Thu, May 03, 2012 at 11:59:14AM +0200, Dmitry Tarnyagin wrote:
> >[...]
> >>+ spin_lock(&priv->event_queue_lock);
> >>+ list_splice_init(&priv->event_queue,&list);
> >>+ spin_unlock(&priv->event_queue_lock);
> >>+ __cw1200_free_event_queue(&list);
> >...and then you won't need to do the above?
> I'm not sure I understand the question :(.

On second reading this code is fine, I just mis-understood it the first
time around. Sorry for the noise.

> >>+ if (priv->listening ^ listening) {
> >That's a unusual (to me anyway) way to spell "a != b". On my
> >version of gcc, generated code is exactly the same.
> Yes, true. It looked quite natural for me at 00:16. Pach is welcome.

Ok, I'll send on a couple of patches for these minor changes -- thanks!

--
Bob Copeland %% http://www.bobcopeland.com

2012-05-10 13:07:17

by Solomon Peachy

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Wed, May 09, 2012 at 04:59:21PM -0400, Kevin Groeneveld wrote:
> As I mentioned in a previous post I am using the SG901-1091 module
> from Sagrad. The only SDD file I have tried that works is also from
> Sagrad. It makes perfect sense now that SDD files from other sources
> do not work.
>
> I still wonder why there is a PTA record in the SDD file if the
> SG901-1091 module does not have Bluetooth. Could this negatively
> effect performance if it is trying to play nice with Bluetooth when
> Bluetooth is not being used?

I don't believe it will cause any problems, as the bluetooth pins aren't
hooked up on the SG901-1091 module. That said, the PTA record on the
current Sagrad SDD files for the 1091/1098 modules specifies that BT
coexistence is disabled (ePTA mode = 0), so it should be functionally
equivalent to no PTA record being present.

- Solomon
--
Solomon Peachy pizza at shaftnet dot org
Melbourne, FL ^^ (mail/jabber/gtalk) ^^
Quidquid latine dictum sit, altum viditur.


Attachments:
(No filename) (1.02 kB)
(No filename) (190.00 B)
Download all attachments

2012-05-03 17:23:36

by Dmitry Tarnyagin

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Kevin,


> However, I have not been able to actually connect
> to any networks. :(
You actually connected :)

AUTH request:
[ 17.207870] [WSM] >>> 0x0004 (54) 8e49b458
[ 17.207969] --> 36 00 04 40 00 00 00 02 00 03 00 0a 00 00 00 00
6..@............
[ 17.207983] --> 00 00 00 00 01 00 00 00 b0 00 3a 01 c0 3f 0e 87
..........:..?..
[ 17.207994] --> f3 0e 00 80 e1 3d 80 24 c0 3f 0e 87 f3 0e 90 04
.....=.$.?......
[ 17.208005] --> 00 00 01 00 00 00 ......

AUTH response:
[ 17.209535] <-- 2e 00 04 48 00 00 00 00 03 00 00 76 00 08 01 00
...H.......v....
[ 17.209548] <-- b0 00 3a 01 00 80 e1 3d 80 24 c0 3f 0e 87 f3 0e
..:....=.$.?....
[ 17.209559] <-- c0 3f 0e 87 f3 0e a0 00 00 00 02 00 00 00
.?............
[ 17.209570] [WSM] <<< 0x0804 (46)

ASSOC request:
[ 17.216524] [WSM] >>> 0x0004 (86) 8e49b418
[ 17.216622] --> 56 00 04 60 00 00 00 02 00 03 00 0a 00 00 00 00
V..`............
[ 17.216635] --> 00 00 00 00 01 00 00 00 00 00 3a 01 c0 3f 0e 87
..........:..?..
[ 17.216647] --> f3 0e 00 80 e1 3d 80 24 c0 3f 0e 87 f3 0e a0 04
.....=.$.?......
[ 17.216658] --> 21 04 05 00 00 07 63 77 5f 74 65 73 74 01 08 02
!.....cw_test...
[ 17.216669] --> 04 0b 16 0c 12 18 24 32 04 30 48 60 6c dd 07 00
......$2.0H`l...
[ 17.216680] --> 50 f2 02 00 01 0f P.....

ASSOC response:
[ 17.223949] <-- 6f 00 04 88 00 00 00 00 03 00 00 78 00 08 01 00
o..........x....
[ 17.223962] <-- 10 00 3a 01 00 80 e1 3d 80 24 c0 3f 0e 87 f3 0e
..:....=.$.?....
[ 17.223974] <-- c0 3f 0e 87 f3 0e b0 00 21 04 00 00 01 c0 01 08
.?......!.......
[ 17.223985] <-- 82 84 8b 0c 12 96 18 24 32 04 30 48 60 6c dd 18
.......$2.0H`l..
[ 17.223997] <-- 00 50 f2 02 01 01 82 00 03 a4 00 00 27 a4 00 00
.P..........'...
[ 17.224008] <-- 42 43 5e 00 62 32 2f 00 dd 09 00 03 7f 01 01 00
BC^.b2/.........
[ 17.224019] <-- 00 ff 7f dd 0a 00 03 7f 04 01 00 02 00 40 00
.............@.
[ 17.224030] [WSM] <<< 0x0804 (111)
,

but then you've got almost immediately
[ 18.926444] wlan0: Connection to AP c0:3f:0e:87:f3:0e lost.
It looks like mac80211 tries to monitor beacons and they are filtered by
cw1200 device.

IEEE80211_HW_CONNECTION_MONITOR and / or IEEE80211_HW_BEACON_FILTER flags
(I don't remember, but both of them should be set) should prevent
mac80211 from
monitoring. Please check if they are set in
drivers/net/wireless/cw1200/main.c

>
>> Also I see unexpectedly high rate of IRQs, they should not come every now
>> and then..
> Any ideas what could cause this or how I should debug it?
Are you running with SDIO or GPIO IRQ?


With best regards,
Dmitry


2012-05-09 18:56:35

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Dmitry,

Thanks for the information.

>> If a PTA element is found the code always sets priv->is_BT_Present =
>> true. ?If I do not have Blue Tooth in my system should this still be
>> set?
>>
> Yes if you share antenna and use PTA (Bluetooth arbitrage) in your design.
> But in this case you should have SDD with PTA entry in.

My system does not have Bluetooth at all. Will the existence of a PTA
record in my SDD file have any negative impact?

Can I just delete the PTA record? (Easier said than done since it is
a binary file... but it can be done.)

Are there any other records in the SDD file that could cause problems?


Thanks,

Kevin

2012-05-09 18:33:35

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Wed, 2012-05-09 at 20:24 +0200, Dmitry Tarnyagin wrote:

> SDD files contains some kind of radio calibration data and Bluetooth
> arbitrage tables.
> They must be aligned with particular RF design and presence of Bluetooth PTA.
> If you are using PTA is your design, you should have SDD with PTA tables in.

Just to note, this is pretty ugly from a distro point of view, it
essentially means that no distro can possibly ship the firmware and you
can't get it included into linux-firmware.git either ...

Is there any way you could differentiate those to be able to ship them?

johannes


2012-05-02 15:22:41

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

>Please find below in the patchset implementation of a new driver for
>the ST-Ericsson cw1200 wlan device.

Hello Dmitry,

What firmware version is this driver tested with? Where can I get the
firmware files?

I have build 328 and 380 but I cannot get the driver to work with
either of these. Everything looks like it is working okay but when I
run "iw wlan0 scan" it does not find anything. The debugfs counters
show I am not receiving any valid packets:

PlcpErrors:???????????? 6
FcsErrors:????????????? 0
TxPackets:????????????? 22
RxPackets:????????????? 0
RxPacketErrors:???????? 6
RxDecryptionFailures:?? 0
RxMicFailures:????????? 0
RxNoKeyFailures:??????? 0
TxMulticastFrames:????? 44
TxFramesSuccess:??????? 0
TxFrameFailures:??????? 0
TxFramesRetried:??????? 0
TxFramesMultiRetried:?? 0
RxFrameDuplicates:????? 0
RtsSuccess:???????????? 0
RtsFailures:??????????? 0
AckFailures:??????????? 0
RxMulticastFrames:????? 0
RxFramesSuccess:??????? 0
RxCMACICVErrors:??????? 0
RxCMACReplays:????????? 0
RxMgmtCCMPReplays:????? 0


Thanks,

Kevin

2012-05-09 20:37:34

by Solomon Peachy

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Wed, May 09, 2012 at 02:56:34PM -0400, Kevin Groeneveld wrote:
> Can I just delete the PTA record? (Easier said than done since it is
> a binary file... but it can be done.)
>
> Are there any other records in the SDD file that could cause problems?

For any given design with the cw1200, you're likely to need an SDD file
specific to that design. That's because the SDD file includes GPIO
mappings to control the RF front end, plus things like bluetooth
coexistence parameters, reference clocking information, and some
calibration data and output power limitations. Oh, and a macaddr too,
but that's generally overridden by the device driver anyway (eg via
platform data)

Most wifi chipsets requiring this sort of information have an embedded
or attached eeprom, but the cw1200 doesn't (presumably for cost
reasons), as it was intended to be used in cellphones that already
maintained per-device platform data.

So in short, there's no way to include a generic SDD file that will work
on all cw1200-based modules, and there's no way to detect which
particular cw1200-based device has been plugged in without the
information in the SDD file. It's a right kerfuffle.

- Solomon [who has some experience with this chipset...]
--
Solomon Peachy pizza at shaftnet dot org
Melbourne, FL ^^ (mail/jabber/gtalk) ^^
Quidquid latine dictum sit, altum viditur.


Attachments:
(No filename) (1.38 kB)
(No filename) (190.00 B)
Download all attachments

2012-05-03 16:42:03

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Dmitry,

> Thank you for logs!
> As I can see the device itself is alive, but nothing is coming back as a
> response to the "scan" command. Something happened with RF part or (likely)
> wrong PLL settings?
> What kind of platform you are running on? What frequency you are providing
> as a master clock for cw1200?

I am running on an iMX51 platform very similar to the Freescale
babbage board. The cw1200 is on a sg901_1091 module from Sagrad.

I measured the clock on the sg901_1091 module and it is 38.4MHz.

Based on some other cw1200 driver source I have changed the PLL init
value from 0x0EC4F121 to 0x100010C1. After that change "iw wlan0
scan" succeeds! :) However, I have not been able to actually connect
to any networks. :(

"iw --debug wlan0 connect cw_test" returns:

-- Debug: Sent Message:
-------------------------- BEGIN NETLINK MESSAGE ---------------------------
[HEADER] 16 octets
.nlmsg_len = 40
.nlmsg_type = 17 <0x11>
.nlmsg_flags = 5 <REQUEST,ACK>
.nlmsg_seq = 1336063010
.nlmsg_pid = 2087
[PAYLOAD] 24 octets
2e 00 00 00 08 00 03 00 04 00 00 00 0b 00 34 00 63 77 ..............4.cw
5f 74 65 73 74 00 _test.
--------------------------- END NETLINK MESSAGE ---------------------------
-- Debug: Received Message:
-------------------------- BEGIN NETLINK MESSAGE ---------------------------
[HEADER] 16 octets
.nlmsg_len = 168
.nlmsg_type = 17 <0x11>
.nlmsg_flags = 0 <>
.nlmsg_seq = 0
.nlmsg_pid = 0
[PAYLOAD] 152 octets
21 01 00 00 08 00 01 00 01 00 00 00 08 00 03 00 04 00 !.................
00 00 10 00 2d 00 0b 00 00 00 63 77 5f 74 65 73 74 00 ....-.....cw_test.
74 00 2c 00 08 00 00 00 6c 09 00 00 08 00 01 00 71 09 t.,.....l.......q.
00 00 08 00 02 00 76 09 00 00 08 00 03 00 7b 09 00 00 ......v.......{...
08 00 04 00 80 09 00 00 08 00 05 00 85 09 00 00 08 00 ..................
06 00 8a 09 00 00 08 00 07 00 8f 09 00 00 08 00 08 00 ..................
94 09 00 00 08 00 09 00 99 09 00 00 08 00 0a 00 9e 09 ..................
00 00 08 00 0b 00 a3 09 00 00 08 00 0c 00 a8 09 00 00 ..................
08 00 0d 00 b4 09 00 00 ........
--------------------------- END NETLINK MESSAGE ---------------------------
-- Error: Invalid message: type=0x11 length=168 flags=<> sequence-nr=0 pid=0
-- Debug: Received Message:
-------------------------- BEGIN NETLINK MESSAGE ---------------------------
[HEADER] 16 octets
.nlmsg_len = 36
.nlmsg_type = 2 <ERROR>
.nlmsg_flags = 0 <>
.nlmsg_seq = 1336063010
.nlmsg_pid = 2087
[ERRORMSG] 20 octets
.error = 0 "Success"
[ORIGINAL MESSAGE] 16 octets
.nlmsg_len = 16
.nlmsg_type = 17 <0x11>
.nlmsg_flags = 5 <REQUEST,ACK>
.nlmsg_seq = 1336063010
.nlmsg_pid = 2087
--------------------------- END NETLINK MESSAGE ---------------------------

I have also attached a kernel log.

> Also I see unexpectedly high rate of IRQs, they should not come every now
> and then..

Any ideas what could cause this or how I should debug it?


Thanks,

Kevin


Attachments:
kernel.log (32.97 kB)

2012-05-09 18:13:19

by Dmitry Tarnyagin

[permalink] [raw]
Subject: Re: [PATCHv2 12/21] cw1200: sta.*, mac80211 STA callbacks.

Hi Kevin,

>>+ ? ? ? ? ? ? ? ? ? ? ?priv->conf_listen_interval =
>>+ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(*((u16 *)pElement->data+1) >> 7) & 0x1F;
>
> The above code in the cw1200_parse_SDD_file function does not appear
> to take into account the endianess of the host. ?Is this a problem?

Yepp, thank you for pointing out :).

Dmitry

2012-05-03 21:57:19

by Janusz Dziedzic

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

2012/5/3 Kevin Groeneveld <[email protected]>:
> Hi Dmitry,
>
>> It looks like mac80211 tries to monitor beacons and they are filtered by
>> cw1200 device.
>>
>> IEEE80211_HW_CONNECTION_MONITOR and / or IEEE80211_HW_BEACON_FILTER flags
>> (I don't remember, but both of them should be set) should prevent mac80211
>> from
>> monitoring. Please check if they are set in
>> drivers/net/wireless/cw1200/main.c
>
> main.c contains the following as in the patches that you posted:
>
> hw->flags = IEEE80211_HW_SIGNAL_DBM |
> ? ? ? ? ? ? ? ? ? ?IEEE80211_HW_SUPPORTS_PS |
> ? ? ? ? ? ? ? ? ? ?IEEE80211_HW_SUPPORTS_DYNAMIC_PS |
> ? ? ? ? ? ? ? ? ? ?IEEE80211_HW_REPORTS_TX_ACK_STATUS |
> ? ? ? ? ? ? ? ? ? ?IEEE80211_HW_SUPPORTS_UAPSD |
> ? ? ? ? ? ? ? ? ? ?IEEE80211_HW_CONNECTION_MONITOR |
> ? ? ? ? ? ? ? ? ? ?IEEE80211_HW_NEED_DTIM_PERIOD;
>
> IEEE80211_HW_BEACON_FILTER does not seem to be defined anywhere in the
> header files. ?Do I need this?
>
>>>> Also I see unexpectedly high rate of IRQs, they should not come every now
>>>> and then..
>>>
>>> Any ideas what could cause this or how I should debug it?
>>
>> Are you running with SDIO or GPIO IRQ?
>
> I am using SDIO IRQ.
>
>

Seems this flag is per vif in new compat now.
IEEE80211_VIF_BEACON_FILTER
IEEE80211_VIF_SUPPORTS_CQM_RSSI

So, you should configure this in cw1200_add_interface() for new compat-wireless:

vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
IEEE80211_VIF_SUPPORTS_CQM_RSSI;


BR
Janusz

2012-05-10 20:03:54

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Solomon,

> I don't believe it will cause any problems, as the bluetooth pins aren't
> hooked up on the SG901-1091 module. ?That said, the PTA record on the
> current Sagrad SDD files for the 1091/1098 modules specifies that BT
> coexistence is disabled (ePTA mode = 0), so it should be functionally
> equivalent to no PTA record being present.

It is good to know the Sagrad SDD file is disabling PTA in the
hardware even though there is a PTA record present.

Although, at least with the driver Dmitry posted on this mailing list,
the driver is taking action simply on the presence of the PTA record.
I can easily disable this for my testing.

I wonder if the parsing of the SDD file in the driver could be a
little smarter to detect that the PTA record is actually disabling PTA
and not assume the existence of a PTA record means PTA is being used.


Kevin

2012-05-04 21:40:57

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

>> So, you should configure this in cw1200_add_interface() for new
>> compat-wireless:
>>
>> ? ? ? ?vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
>> ? ? ? ? ? ? ? ? ? ? ? ? ? ? IEEE80211_VIF_SUPPORTS_CQM_RSSI;

I tried adding the above code today and I can now connect in station mode.

While connected in station mode I get a lot of the following warnings
in the log:

ieee80211 phy1: Slowpath: tailroom is not big enough. Req: 8, got: 0.

Is that anything to worry about?

The driver did seem to crash on me once today spewing out a lot of
WARNINGs and trace info to the log. Unfortunately I didn't save the
log. I will try to pay more attention to it if it happens again.

I also did a bit more testing in AP mode. After changing hostapd to
802.11g mode I got much better throughput. However, I did see quite a
few warnings in the log such as the following:

[ 296.854219] ieee80211 phy2: Missed interrupt?
[ 297.104219] ieee80211 phy2: Multicast delivery timeout.
[ 254.194450] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000002
[ 34.731942] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000002
[ 462.159614] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000002
[ 482.560642] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000002
[ 482.568372] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000002
[ 482.601447] ieee80211 phy2: Multicast delivery timeout.
[ 482.736948] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000006
[ 482.879955] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000006
[ 483.052676] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000006
[ 483.570836] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000006
[ 483.578685] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000006
[ 483.765740] ieee80211 phy2: Requeue for link_id 1 (try 2). STAs
asleep: 0x00000006
[ 483.773462] ieee80211 phy2: Requeue for link_id 1 (try 2). STAs
asleep: 0x00000006
[ 484.340904] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000006
[ 484.348626] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000006
[ 484.356344] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000006
[ 484.670846] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
asleep: 0x00000006
[ 484.903498] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000006
[ 2.101774] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000000
[ 2.109569] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000000
[ 2.119271] ieee80211 phy2: Requeue for link_id 0 (try 2). STAs
asleep: 0x00000000
[ 2.128483] ieee80211 phy2: Requeue for link_id 0 (try 2). STAs
asleep: 0x00000004
[ 2.137894] ieee80211 phy2: Requeue for link_id 0 (try 3). STAs
asleep: 0x00000004
[ 5.629055] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000004
[ 81.020541] ieee80211 phy2: Missed interrupt?
[ 85.050556] ieee80211 phy2: Missed interrupt?
[ 96.410571] ieee80211 phy2: Missed interrupt?
[ 100.410560] ieee80211 phy2: Missed interrupt?
[ 105.700565] ieee80211 phy2: Missed interrupt?
[ 119.410573] ieee80211 phy2: Missed interrupt?
[ 140.050548] ieee80211 phy2: Missed interrupt?
[ 156.993680] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000004
[ 157.001679] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
asleep: 0x00000004
[ 157.240546] ieee80211 phy2: Multicast delivery timeout.
[ 167.400528] ieee80211 phy2: Missed interrupt?

Should I be worried about these?


Thanks,

Kevin

2012-05-09 06:58:33

by Dmitry Tarnyagin

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi,

Could you please check status of this submission? It looks like review
was passed, but patchset was not taken in. Have I done something
wrong?

Thank you and with best regards,
Dmitry Tarnyagin

On Fri, Mar 2, 2012 at 9:26 PM, Dmitry Tarnyagin
<[email protected]> wrote:
> Please find below in the patchset implementation of a new driver for
> the ST-Ericsson cw1200 wlan device.
>
> Signed-off-by: Dmitry Tarnyagin <[email protected]>
> --

2012-05-03 09:59:23

by Dmitry Tarnyagin

[permalink] [raw]
Subject: Re: [PATCHv2 12/21] cw1200: sta.*, mac80211 STA callbacks.

Hi Bob,

On Sun, Apr 29, 2012, Bob Copeland wrote:

>
> + while (!list_empty(list)) {
[...]

> I think perhaps you can use list_for_each_entry_safe() here...
It's the same. Yes, list_for_each_entry_safe() can be used here as well.

>> + LIST_HEAD(list);
> [...]
>> + spin_lock(&priv->event_queue_lock);
>> + list_splice_init(&priv->event_queue,&list);
>> + spin_unlock(&priv->event_queue_lock);
>> + __cw1200_free_event_queue(&list);
> ...and then you won't need to do the above?
I'm not sure I understand the question :(.

>> + if (priv->listening ^ listening) {
> That's a unusual (to me anyway) way to spell "a != b". On my
> version of gcc, generated code is exactly the same.
Yes, true. It looked quite natural for me at 00:16. Pach is welcome.

With best regards,
Dmitry

2012-05-05 07:20:44

by Janusz Dziedzic

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

2012/5/4 Kevin Groeneveld <[email protected]>:
>>> So, you should configure this in cw1200_add_interface() for new
>>> compat-wireless:
>>>
>>> ? ? ? ?vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
>>> ? ? ? ? ? ? ? ? ? ? ? ? ? ? IEEE80211_VIF_SUPPORTS_CQM_RSSI;
>
> I tried adding the above code today and I can now connect in station mode.
>
> While connected in station mode I get a lot of the following warnings
> in the log:
>
> ieee80211 phy1: Slowpath: tailroom is not big enough. Req: 8, got: 0.
>
> Is that anything to worry about?

Yes we have patch for this in mac80211. I can send you on monday.
cw1200 firmware required pre-allocated IVs.
Without that performance will be very poor.

There is similar patch in community code - but seems not fullfill all
our requirements.

>
> The driver did seem to crash on me once today spewing out a lot of
> WARNINGs and trace info to the log. ?Unfortunately I didn't save the
> log. ?I will try to pay more attention to it if it happens again.
>
> I also did a bit more testing in AP mode. ?After changing hostapd to
> 802.11g mode I got much better throughput. ?However, I did see quite a
> few warnings in the log such as the following:
>
> [ ?296.854219] ieee80211 phy2: Missed interrupt?

This is critical and this is firmware issue.
After that driver can do recovery ...
So, logs after that are not important much ...


> [ ?297.104219] ieee80211 phy2: Multicast delivery timeout.
> [ ?254.194450] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000002
> [ ? 34.731942] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000002
> [ ?462.159614] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000002
> [ ?482.560642] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000002
> [ ?482.568372] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000002
> [ ?482.601447] ieee80211 phy2: Multicast delivery timeout.
> [ ?482.736948] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000006
> [ ?482.879955] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000006
> [ ?483.052676] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000006
> [ ?483.570836] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000006
> [ ?483.578685] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000006
> [ ?483.765740] ieee80211 phy2: Requeue for link_id 1 (try 2). STAs
> asleep: 0x00000006
> [ ?483.773462] ieee80211 phy2: Requeue for link_id 1 (try 2). STAs
> asleep: 0x00000006
> [ ?484.340904] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000006
> [ ?484.348626] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000006
> [ ?484.356344] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000006
> [ ?484.670846] ieee80211 phy2: Requeue for link_id 1 (try 1). STAs
> asleep: 0x00000006
> [ ?484.903498] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000006
> [ ? ?2.101774] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000000
> [ ? ?2.109569] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000000
> [ ? ?2.119271] ieee80211 phy2: Requeue for link_id 0 (try 2). STAs
> asleep: 0x00000000
> [ ? ?2.128483] ieee80211 phy2: Requeue for link_id 0 (try 2). STAs
> asleep: 0x00000004
> [ ? ?2.137894] ieee80211 phy2: Requeue for link_id 0 (try 3). STAs
> asleep: 0x00000004
> [ ? ?5.629055] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000004
> [ ? 81.020541] ieee80211 phy2: Missed interrupt?
> [ ? 85.050556] ieee80211 phy2: Missed interrupt?
> [ ? 96.410571] ieee80211 phy2: Missed interrupt?
> [ ?100.410560] ieee80211 phy2: Missed interrupt?
> [ ?105.700565] ieee80211 phy2: Missed interrupt?
> [ ?119.410573] ieee80211 phy2: Missed interrupt?
> [ ?140.050548] ieee80211 phy2: Missed interrupt?
> [ ?156.993680] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000004
> [ ?157.001679] ieee80211 phy2: Requeue for link_id 0 (try 1). STAs
> asleep: 0x00000004
> [ ?157.240546] ieee80211 phy2: Multicast delivery timeout.
> [ ?167.400528] ieee80211 phy2: Missed interrupt?
>
> Should I be worried about these?
>
Yes :)


--
Janusz Dziedzic

2012-05-03 20:08:33

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 15/21] cw1200: debug.*, implementation of the driver's debugfs.

Hi Dmitry,

There seems to be a small problem in this patch. If
CONFIG_CW1200_DEBUGFS is not defined then compilation fails with
"multiple definition of `cw1200_print_fw_version'".

>+int cw1200_print_fw_version(struct cw1200_common *priv, u8 *buf, size_t len)
>+{
>+}

The above code near the end of debug.h should probably just be a
function prototype and not include a function body.


Best regards,

Kevin

2012-05-09 20:59:23

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

> For any given design with the cw1200, you're likely to need an SDD file
> specific to that design. That's because the SDD file includes GPIO
> ...

Thanks for the further info on the SDD files Solomon.

As I mentioned in a previous post I am using the SG901-1091 module
from Sagrad. The only SDD file I have tried that works is also from
Sagrad. It makes perfect sense now that SDD files from other sources
do not work.

I still wonder why there is a PTA record in the SDD file if the
SG901-1091 module does not have Bluetooth. Could this negatively
effect performance if it is trying to play nice with Bluetooth when
Bluetooth is not being used?


Kevin

2012-05-09 18:24:39

by Dmitry Tarnyagin

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Kevin,

> What are the sdd files for and why would they be different?
>
SDD files contains some kind of radio calibration data and Bluetooth
arbitrage tables.
They must be aligned with particular RF design and presence of Bluetooth PTA.
If you are using PTA is your design, you should have SDD with PTA tables in.

> I noticed the driver parses some information directly from the sdd
> file in the cw1200_parse_SDD_file function. ?Does the file get used
> for anything else?
>
It is downloaded to firmware and the firmware uses data from it.

> If a PTA element is found the code always sets priv->is_BT_Present =
> true. ?If I do not have Blue Tooth in my system should this still be
> set?
>
Yes if you share antenna and use PTA (Bluetooth arbitrage) in your design.
But in this case you should have SDD with PTA entry in.

Dmitry

2012-05-09 19:37:39

by Dmitry Tarnyagin

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hi Kevin,

> My system does not have Bluetooth at all. ?Will the existence of a PTA
> record in my SDD file have any negative impact?
>
It will not have any valuable impact to the driver, just listen interval
will be limited a bit. And you can disable check for PTA record in the
driver code. Maybe it would be smart to make it configurable (?).

More interesting question is if presence of PTA entry affects firmware
and arbiter. I do not have a good answer for that, but I can guess it depends
on actual logical values on the PTA interface.

> Can I just delete the PTA record? ?(Easier said than done since it is
> a binary file... but it can be done.)
>
But why can't you use SDD from your distribution, calibrated for your RF design?
It can be really huge difference in calibration parameters with different FEMs,
antennas etc. SDDs are independent from the firmware and you can use an SDD
with any firmware version.

Dmitry

2012-05-04 00:10:50

by Kevin Groeneveld

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Hello Janusz,

> Seems this flag is per vif in new compat now.
> IEEE80211_VIF_BEACON_FILTER
> IEEE80211_VIF_SUPPORTS_CQM_RSSI
>
> So, you should configure this in cw1200_add_interface() for new
> compat-wireless:
>
> ? ? ? ?vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? IEEE80211_VIF_SUPPORTS_CQM_RSSI;

Thanks for the suggestion. I will take a look at this tomorrow.

On a positive note, just before leaving for the day I fired up hostapd
and was able to connect a couple clients to the cw1200 in AP mode! I
didn't test it very much but it seemed to be working okay. I
transferred a couple hundred MB of data without any issue. I was only
getting about 500kB per second but I suspect all the logging I have
turned on may be slowing it down. I am not even sure what rate I was
connected at as I was using an old MacBook for the test and not being
a Mac guy I couldn't figure out how to see what rate the WiFi was
connected at.

So if AP mode is working station mode can't be that far off...


Thanks,

Kevin

2012-05-31 05:45:37

by Janusz Dziedzic

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

2012/5/9 Dmitry Tarnyagin <[email protected]>:
> Hi,
>
> Could you please check status of this submission? It looks like review
> was passed, but patchset was not taken in. Have I done something
> wrong?
>

Any news?


BR
Janusz

2012-07-05 18:16:32

by Steev Klimaszewski

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Thu, May 31, 2012 at 12:45 AM, Janusz Dziedzic
<[email protected]> wrote:
> 2012/5/9 Dmitry Tarnyagin <[email protected]>:
>> Hi,
>>
>> Could you please check status of this submission? It looks like review
>> was passed, but patchset was not taken in. Have I done something
>> wrong?
>>
>
> Any news?
>
>
> BR
> Janusz
> --

Any news on this?



--
Best Regards,
Steev

Steev Klimaszewski <[email protected]>
Senior Software Engineer, Genesi USA, Inc.

2012-07-06 20:33:28

by John W. Linville

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Thu, Jul 05, 2012 at 01:16:31PM -0500, Steev Klimaszewski wrote:
> On Thu, May 31, 2012 at 12:45 AM, Janusz Dziedzic
> <[email protected]> wrote:
> > 2012/5/9 Dmitry Tarnyagin <[email protected]>:
> >> Hi,
> >>
> >> Could you please check status of this submission? It looks like review
> >> was passed, but patchset was not taken in. Have I done something
> >> wrong?
> >>
> >
> > Any news?
> >
> >
> > BR
> > Janusz
> > --
>
> Any news on this?

Sorry, I kinda lost track of this. I guess I was expecting a repost,
including Bob Copeland's patches and something to address the endian
issues mentioned by Kevin Groenevel. Was anyone planning to address
those concerns? Also, I'm not sure that bit about needing custom
SDD files was fully addressed?

On top of that, there were some style quirks that I was hoping
someone could address. There were "if 0" blocks in the code, which
is rather questionable. Was anyone planning to remove them? Also,
the block comment formats seemed a little random. In particular, the
single-line "/* ********* */" thing looks a bit funny. Perhaps that
isn't the worst thing ever but if you are going to respin anyway than
I'd prefer if you just deleted them.

There was also talk of a mac80211 patch needed to fix a bug observed
with the driver as posted? Has that mac80211 fix been posted and
merged? If not, when will we see it?

So anyway, it seems like a repost of the current version (i.e. with
Bob's fixes, etc) is in order?

John
--
John W. Linville Someday the world will need a hero, and you
[email protected] might be all we have. Be ready.

2012-12-11 23:23:15

by Solomon Peachy

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Fri, Jul 06, 2012 at 04:18:40PM -0400, John W. Linville wrote:
> Sorry, I kinda lost track of this. I guess I was expecting a repost,
> including Bob Copeland's patches and something to address the endian
> issues mentioned by Kevin Groenevel. Was anyone planning to address
> those concerns? Also, I'm not sure that bit about needing custom
> SDD files was fully addressed?

Unfortunately, the SDD needs to be supplied by the module vendor, as
it's specific to the design. Even more troubling is that there's a
register value we have to program that also depends on the design, and
there's no way to know that generically. I've solved this by resorting
to platform data.

Anyway. Onto my main point.

ST-E hasn't publically done any work on this driver since around April
2012, and they haven't given me an answer on whether or not it's been
abandoned. In light of recent announcements relating to ST-E's future,
I'm not hopeful that it'll see any more official support.

That said, I've done a ton of work on this driver for my employer. It
has far fewer bugs than what ST-E has previously released; including
fixing up major endian problems (I brought it up on a uClinux m68k
target for another client). It's also vastly more stable, now has SPI
support, and has also managed to pass WFA testing. That's not to say
it's bug-free, but it at least can be counted on to not HACF randomly.

I've been maintaining it on top of compat-wireless, and I'm wanting to
give it another upstream push, and be listed as the maintainer. What's
the best way for me to do that? The patch series they posted originally
(one file per patch) seems rather silly, but at the same time, doing it
all at once is a rather substantial chunk to digest/review.

> On top of that, there were some style quirks that I was hoping
> someone could address. There were "if 0" blocks in the code, which
> is rather questionable. Was anyone planning to remove them? Also,
> the block comment formats seemed a little random. In particular, the
> single-line "/* ********* */" thing looks a bit funny. Perhaps that
> isn't the worst thing ever but if you are going to respin anyway than
> I'd prefer if you just deleted them.

The code I have has a lot of changes there, but I imagine there are more
remaining. I'll have to go back and audit things, as I've since given
up on keeping my code close to ST-E's "upstream"

> There was also talk of a mac80211 patch needed to fix a bug observed
> with the driver as posted? Has that mac80211 fix been posted and
> merged? If not, when will we see it?

IIRC it's because mac80211 doesn't provide a way of pre-allocating MIC
space at the tail end of the frame -- the hardware performs the work but
still needs the space in the payload. The code I'm maintaining has
workarounds for this; not the most efficient solution but I'd rather see
the code merged as-is, and then we can incrementally improve things
(perhaps by enhancing mac80211 as needed)

> So anyway, it seems like a repost of the current version (i.e. with
> Bob's fixes, etc) is in order?

I'll have to go back and see if any of those fixes are still relevant.

Anyway, let me know how I should proceed from here..

- Solomon
--
Solomon Peachy pizza at shaftnet dot org
Melbourne, FL ^^ (mail/jabber/gtalk) ^^
Quidquid latine dictum sit, altum viditur.


Attachments:
(No filename) (3.34 kB)
(No filename) (190.00 B)
Download all attachments

2012-12-12 16:20:42

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Wed, 2012-12-12 at 11:18 -0500, Solomon Peachy wrote:
> On Wed, Dec 12, 2012 at 04:13:12PM +0100, Christian Lamparter wrote:
> > Mac80211 already reserves at least 18 bytes tailroom [which can be
> > allocated by a simple skb_put]. In fact p54 uses this tailroom to
> > pass the MIC (8 bytes) + ICV (4 bytes) to the firmware. Do you need
> > more than 18 bytes?
>
> I just went back and had a look at the code -- There were two related
> workarounds: One made room for the MIC, but as you said, that hasn't
> been necessary for a while so the code was disabled.
>
> The other workaround is still in place, however -- cw1200 also needs a
> gap in the frame payload for the IV, even though it's generated by the
> firmware. I worked around this by telling setting the
> IEEE80211_KEY_FLAG_GENERATE_IV flag on the mac80211 keys, and then
> zeroing that part of the payload when the frame went out to the harware.
>
> I figured it was a less costly approach than memmoving the whole payload
> over by sizeof(ICV).
>
> Is there a better way to handle this now?

There's IEEE80211_KEY_FLAG_PUT_IV_SPACE now which does exactly that.

johannes


2012-12-13 14:53:24

by Solomon Peachy

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Wed, Dec 12, 2012 at 05:20:48PM +0100, Johannes Berg wrote:
> > Is there a better way to handle this now?
> There's IEEE80211_KEY_FLAG_PUT_IV_SPACE now which does exactly that.

Excellent! I just cut all of the old crap out in favor of this.

I'm working on a revamp of the platform_data to be a little more
generic/useful/sane, but once I'm done with that, I'll put it through
checkpatch and then I think I'll be in shape to submit a patch series
for review. This weekend sometime, I think.

- Solomon
--
Solomon Peachy pizza at shaftnet dot org
Melbourne, FL ^^ (mail/jabber/gtalk) ^^
Quidquid latine dictum sit, altum viditur.


Attachments:
(No filename) (685.00 B)
(No filename) (190.00 B)
Download all attachments

2012-12-12 18:31:52

by John W. Linville

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Tue, Dec 11, 2012 at 06:23:11PM -0500, Solomon Peachy wrote:

> > So anyway, it seems like a repost of the current version (i.e. with
> > Bob's fixes, etc) is in order?
>
> I'll have to go back and see if any of those fixes are still relevant.
>
> Anyway, let me know how I should proceed from here..

It looks like others are interacting with you on this, and you
are getting good feedback. I have no problem with you being the
maintainer for this driver -- just get it into shape and post it.
As Kalle suggested, posting it in pieces for review is usually a
good idea. A final merge patch could be posted as one single patch,
or in many cases I just combine them into a single patch myself at
the final merge.

Hth!

John
--
John W. Linville Someday the world will need a hero, and you
[email protected] might be all we have. Be ready.

2012-12-12 14:40:36

by Kalle Valo

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

Solomon Peachy <[email protected]> writes:

> I've been maintaining it on top of compat-wireless, and I'm wanting to
> give it another upstream push, and be listed as the maintainer. What's
> the best way for me to do that? The patch series they posted originally
> (one file per patch) seems rather silly, but at the same time, doing it
> all at once is a rather substantial chunk to digest/review.

With ath6kl I did so that for review I sent multiple patches (one file
per patch) but the final submission, after the review, was one huge
patch containing everything. I don't know if John still prefers that but
at least I would recommend to do so.

--
Kalle Valo

2012-12-12 16:18:49

by Solomon Peachy

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Wed, Dec 12, 2012 at 04:13:12PM +0100, Christian Lamparter wrote:
> Mac80211 already reserves at least 18 bytes tailroom [which can be
> allocated by a simple skb_put]. In fact p54 uses this tailroom to
> pass the MIC (8 bytes) + ICV (4 bytes) to the firmware. Do you need
> more than 18 bytes?

I just went back and had a look at the code -- There were two related
workarounds: One made room for the MIC, but as you said, that hasn't
been necessary for a while so the code was disabled.

The other workaround is still in place, however -- cw1200 also needs a
gap in the frame payload for the IV, even though it's generated by the
firmware. I worked around this by telling setting the
IEEE80211_KEY_FLAG_GENERATE_IV flag on the mac80211 keys, and then
zeroing that part of the payload when the frame went out to the harware.

I figured it was a less costly approach than memmoving the whole payload
over by sizeof(ICV).

Is there a better way to handle this now?

- Solomon
--
Solomon Peachy pizza at shaftnet dot org
Melbourne, FL ^^ (mail/jabber/gtalk) ^^
Quidquid latine dictum sit, altum viditur.


Attachments:
(No filename) (1.13 kB)
(No filename) (190.00 B)
Download all attachments

2012-12-12 15:13:28

by Christian Lamparter

[permalink] [raw]
Subject: Re: [PATCHv2 00/21] cw1200: ST-Ericsson cw1200 wlan device driver

On Wednesday, December 12, 2012 12:23:11 AM Solomon Peachy wrote:
> On Fri, Jul 06, 2012 at 04:18:40PM -0400, John W. Linville wrote:
>
> IIRC it's because mac80211 doesn't provide a way of pre-allocating MIC
> space at the tail end of the frame -- the hardware performs the work but
> still needs the space in the payload. The code I'm maintaining has
> workarounds for this; not the most efficient solution but I'd rather see
> the code merged as-is, and then we can incrementally improve things
> (perhaps by enhancing mac80211 as needed)

Mac80211 already reserves at least 18 bytes tailroom [which can be
allocated by a simple skb_put]. In fact p54 uses this tailroom to
pass the MIC (8 bytes) + ICV (4 bytes) to the firmware. Do you need
more than 18 bytes?

Regards,
Chr