diff options
Diffstat (limited to 'drivers/net/wireless/eswin/ecrnx_debugfs.c')
-rw-r--r-- | drivers/net/wireless/eswin/ecrnx_debugfs.c | 2576 |
1 files changed, 2576 insertions, 0 deletions
diff --git a/drivers/net/wireless/eswin/ecrnx_debugfs.c b/drivers/net/wireless/eswin/ecrnx_debugfs.c new file mode 100644 index 000000000000..a7b4232ed6b7 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_debugfs.c @@ -0,0 +1,2576 @@ +/** + ****************************************************************************** + * + * @file ecrnx_debugfs.c + * + * @brief Definition of debugfs entries + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + + +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/debugfs.h> +#include <linux/string.h> +#include <linux/sort.h> + +#include "ecrnx_debugfs.h" +#include "ecrnx_msg_tx.h" +#include "ecrnx_radar.h" +#include "ecrnx_tx.h" + +#define CONFIG_ECRNX_DBGFS_FW_TRACE 0 + +#ifdef CONFIG_ECRNX_SOFTMAC +static ssize_t ecrnx_dbgfs_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int per; + int ret; + int i, skipped; + ssize_t read; + int bufsz = (10 + NX_TX_PAYLOAD_MAX + NX_TXQ_CNT + IEEE80211_MAX_AMPDU_BUF) * 50; + + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + if (priv->stats.agg_done) + per = DIV_ROUND_UP((priv->stats.agg_retries + priv->stats.agg_died) * + 100, priv->stats.agg_done); + else + per = 0; + + ret = scnprintf(buf, min_t(size_t, bufsz - 1, count), + "agg_done %10d\n" + "agg_retries %10d\n" + "agg_retries_last %10d\n" + "agg_died %10d\n" + "ampdu_all_ko %10d\n" + "agg_PER (%%) %10d\n" + "queues_stops %10d\n\n", + priv->stats.agg_done, + priv->stats.agg_retries, + priv->stats.agg_retries_last, + priv->stats.agg_died, + priv->stats.ampdu_all_ko, + per, + priv->stats.queues_stops); + + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + "TXQs CFM balances "); + for (i = 0; i < NX_TXQ_CNT; i++) + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " [%1d]:%3d", i, + priv->stats.cfm_balance[i]); + +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + "\n\nAMSDU[len] done failed(%%)\n"); + for (i = skipped = 0; i < NX_TX_PAYLOAD_MAX; i++) { + if (priv->stats.amsdus[i].done) { + per = DIV_ROUND_UP((priv->stats.amsdus[i].failed) * + 100, priv->stats.amsdus[i].done); + } else { + per = 0; + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " * * * %10d %10d\n", 0, 0); + skipped = 0; + } + + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " [%1d] %10d %10d\n", i + 1, + priv->stats.amsdus[i].done, per); + } + if (skipped) + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " * * * %10d %10d\n", 0, 0); +#endif + + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - ret - 1, count - ret), + "\nIn-AMPDU TX failures(%%) RX counts\n"); + for (i = skipped = 0; i < IEEE80211_MAX_AMPDU_BUF; i++) { + int failed; + + if (priv->stats.in_ampdu[i].done) { + failed = DIV_ROUND_UP(priv->stats.in_ampdu[i].failed * + 100, priv->stats.in_ampdu[i].done); + } else { + if (!priv->stats.rx_in_ampdu[i].cnt) { + skipped = 1; + continue; + } + failed = 0; + } + if (skipped) { + ret += scnprintf(&buf[ret], + min_t(size_t, bufsz - ret - 1, count - ret), + " * * * %10d %10d\n", 0, 0); + skipped = 0; + } + ret += scnprintf(&buf[ret], + min_t(size_t, bufsz - ret - 1, count - ret), + " mpdu#%2d %10d %10d\n", i, failed, + priv->stats.rx_in_ampdu[i].cnt); + + } + if (skipped) + ret += scnprintf(&buf[ret], + min_t(size_t, bufsz - ret - 1, count - ret), + " * * * %10d %10d\n", 0, 0); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + + return read; +} + +#else + +static ssize_t ecrnx_dbgfs_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int ret; + int i, skipped; + ssize_t read; + int bufsz = (NX_TXQ_CNT) * 20 + (ARRAY_SIZE(priv->stats.amsdus_rx) + 1) * 40 + + (ARRAY_SIZE(priv->stats.ampdus_tx) * 30); + + if (*ppos) + return 0; + + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + ret = scnprintf(buf, bufsz, "TXQs CFM balances "); + for (i = 0; i < NX_TXQ_CNT; i++) + ret += scnprintf(&buf[ret], bufsz - ret, + " [%1d]:%3d", i, + priv->stats.cfm_balance[i]); + + ret += scnprintf(&buf[ret], bufsz - ret, "\n"); + +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + int per = 0; + ret += scnprintf(&buf[ret], bufsz - ret, + "\nAMSDU[len] done failed received\n"); + for (i = skipped = 0; i < NX_TX_PAYLOAD_MAX; i++) { + if (priv->stats.amsdus[i].done) { + per = DIV_ROUND_UP((priv->stats.amsdus[i].failed) * + 100, priv->stats.amsdus[i].done); + } else if (priv->stats.amsdus_rx[i]) { + per = 0; + } else { + per = 0; + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %10d %8d(%3d%%) %10d\n", i ? i + 1 : i, + priv->stats.amsdus[i].done, + priv->stats.amsdus[i].failed, per, + priv->stats.amsdus_rx[i]); + } + + for (; i < ARRAY_SIZE(priv->stats.amsdus_rx); i++) { + if (!priv->stats.amsdus_rx[i]) { + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %10d\n", + i + 1, priv->stats.amsdus_rx[i]); + } +#else + ret += scnprintf(&buf[ret], bufsz - ret, + "\nAMSDU[len] received\n"); + for (i = skipped = 0; i < ARRAY_SIZE(priv->stats.amsdus_rx); i++) { + if (!priv->stats.amsdus_rx[i]) { + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, + " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %10d\n", + i + 1, priv->stats.amsdus_rx[i]); + } + +#endif /* CONFIG_ECRNX_SPLIT_TX_BUF */ + + ret += scnprintf(&buf[ret], bufsz - ret, + "\nAMPDU[len] done received\n"); + for (i = skipped = 0; i < ARRAY_SIZE(priv->stats.ampdus_tx); i++) { + if (!priv->stats.ampdus_tx[i] && !priv->stats.ampdus_rx[i]) { + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, + " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %9d %9d\n", i ? i + 1 : i, + priv->stats.ampdus_tx[i], priv->stats.ampdus_rx[i]); + } + + ret += scnprintf(&buf[ret], bufsz - ret, + "#mpdu missed %9d\n", + priv->stats.ampdus_rx_miss); + + ret += scnprintf(&buf[ret], bufsz - ret, + "\nmsg_tx:%d,%d; data_tx:%d,%d\n", + priv->msg_tx, priv->msg_tx_done, priv->data_tx, priv->data_tx_done); + ret += scnprintf(&buf[ret], bufsz - ret, + "usb_rx:%d, data_rx:%d, msg_rx:%d\n", + priv->usb_rx, priv->data_rx, priv->msg_rx); + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + + return read; +} +#endif /* CONFIG_ECRNX_SOFTMAC */ + +static ssize_t ecrnx_dbgfs_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + + /* Prevent from interrupt preemption as these statistics are updated under + * interrupt */ + spin_lock_bh(&priv->tx_lock); + + memset(&priv->stats, 0, sizeof(priv->stats)); + + spin_unlock_bh(&priv->tx_lock); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(stats); + +#define TXQ_STA_PREF "tid|" +#define TXQ_STA_PREF_FMT "%3d|" + +#ifdef CONFIG_ECRNX_FULLMAC +#define TXQ_VIF_PREF "type|" +#define TXQ_VIF_PREF_FMT "%4s|" +#else +#define TXQ_VIF_PREF "AC|" +#define TXQ_VIF_PREF_FMT "%2s|" +#endif /* CONFIG_ECRNX_FULLMAC */ + +#define TXQ_HDR "idx| status|credit|ready|retry|pushed" +#define TXQ_HDR_FMT "%3d|%s%s%s%s%s%s%s%s|%6d|%5d|%5d|%6d" + +#ifdef CONFIG_ECRNX_AMSDUS_TX +#ifdef CONFIG_ECRNX_FULLMAC +#define TXQ_HDR_SUFF "|amsdu" +#define TXQ_HDR_SUFF_FMT "|%5d" +#else +#define TXQ_HDR_SUFF "|amsdu-ht|amdsu-vht" +#define TXQ_HDR_SUFF_FMT "|%8d|%9d" +#endif /* CONFIG_ECRNX_FULLMAC */ +#else +#define TXQ_HDR_SUFF "" +#define TXQ_HDR_SUF_FMT "" +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + +#define TXQ_HDR_MAX_LEN (sizeof(TXQ_STA_PREF) + sizeof(TXQ_HDR) + sizeof(TXQ_HDR_SUFF) + 1) + +#ifdef CONFIG_ECRNX_FULLMAC +#define PS_HDR "Legacy PS: ready=%d, sp=%d / UAPSD: ready=%d, sp=%d" +#define PS_HDR_LEGACY "Legacy PS: ready=%d, sp=%d" +#define PS_HDR_UAPSD "UAPSD: ready=%d, sp=%d" +#define PS_HDR_MAX_LEN sizeof("Legacy PS: ready=xxx, sp=xxx / UAPSD: ready=xxx, sp=xxx\n") +#else +#define PS_HDR "" +#define PS_HDR_MAX_LEN 0 +#endif /* CONFIG_ECRNX_FULLMAC */ + +#define STA_HDR "** STA %d (%pM)\n" +#define STA_HDR_MAX_LEN sizeof("- STA xx (xx:xx:xx:xx:xx:xx)\n") + PS_HDR_MAX_LEN + +#ifdef CONFIG_ECRNX_FULLMAC +#define VIF_HDR "* VIF [%d] %s\n" +#define VIF_HDR_MAX_LEN sizeof(VIF_HDR) + IFNAMSIZ +#else +#define VIF_HDR "* VIF [%d]\n" +#define VIF_HDR_MAX_LEN sizeof(VIF_HDR) +#endif + + +#ifdef CONFIG_ECRNX_AMSDUS_TX + +#ifdef CONFIG_ECRNX_FULLMAC +#define VIF_SEP "---------------------------------------\n" +#else +#define VIF_SEP "----------------------------------------------------\n" +#endif /* CONFIG_ECRNX_FULLMAC */ + +#else /* ! CONFIG_ECRNX_AMSDUS_TX */ +#define VIF_SEP "---------------------------------\n" +#endif /* CONFIG_ECRNX_AMSDUS_TX*/ + +#define VIF_SEP_LEN sizeof(VIF_SEP) + +#define CAPTION "status: L=in hwq list, F=stop full, P=stop sta PS, V=stop vif PS,\ + C=stop channel, S=stop CSA, M=stop MU, N=Ndev queue stopped" +#define CAPTION_LEN sizeof(CAPTION) + +#define STA_TXQ 0 +#define VIF_TXQ 1 + +static int ecrnx_dbgfs_txq(char *buf, size_t size, struct ecrnx_txq *txq, int type, int tid, char *name) +{ + int res, idx = 0; + int i, pushed = 0; + + if (type == STA_TXQ) { + res = scnprintf(&buf[idx], size, TXQ_STA_PREF_FMT, tid); + idx += res; + size -= res; + } else { + res = scnprintf(&buf[idx], size, TXQ_VIF_PREF_FMT, name); + idx += res; + size -= res; + } + + for (i = 0; i < CONFIG_USER_MAX; i++) { + pushed += txq->pkt_pushed[i]; + } + + res = scnprintf(&buf[idx], size, TXQ_HDR_FMT, txq->idx, + (txq->status & ECRNX_TXQ_IN_HWQ_LIST) ? "L" : " ", + (txq->status & ECRNX_TXQ_STOP_FULL) ? "F" : " ", + (txq->status & ECRNX_TXQ_STOP_STA_PS) ? "P" : " ", + (txq->status & ECRNX_TXQ_STOP_VIF_PS) ? "V" : " ", + (txq->status & ECRNX_TXQ_STOP_CHAN) ? "C" : " ", + (txq->status & ECRNX_TXQ_STOP_CSA) ? "S" : " ", + (txq->status & ECRNX_TXQ_STOP_MU_POS) ? "M" : " ", + (txq->status & ECRNX_TXQ_NDEV_FLOW_CTRL) ? "N" : " ", + txq->credits, skb_queue_len(&txq->sk_list), + txq->nb_retry, pushed); + idx += res; + size -= res; + +#ifdef CONFIG_ECRNX_AMSDUS_TX + if (type == STA_TXQ) { + res = scnprintf(&buf[idx], size, TXQ_HDR_SUFF_FMT, +#ifdef CONFIG_ECRNX_FULLMAC + txq->amsdu_len +#else + txq->amsdu_ht_len_cap, txq->amsdu_vht_len_cap +#endif /* CONFIG_ECRNX_FULLMAC */ + ); + idx += res; + size -= res; + } +#endif + + res = scnprintf(&buf[idx], size, "\n"); + idx += res; + size -= res; + + return idx; +} + +static int ecrnx_dbgfs_txq_sta(char *buf, size_t size, struct ecrnx_sta *ecrnx_sta, + struct ecrnx_hw *ecrnx_hw) +{ + int tid, res, idx = 0; + struct ecrnx_txq *txq; +#ifdef CONFIG_ECRNX_SOFTMAC + struct ieee80211_sta *sta = ecrnx_to_ieee80211_sta(ecrnx_sta); +#endif /* CONFIG_ECRNX_SOFTMAC */ + + res = scnprintf(&buf[idx], size, "\n" STA_HDR, + ecrnx_sta->sta_idx, +#ifdef CONFIG_ECRNX_SOFTMAC + sta->addr +#else + ecrnx_sta->mac_addr +#endif /* CONFIG_ECRNX_SOFTMAC */ + ); + idx += res; + size -= res; + +#ifdef CONFIG_ECRNX_FULLMAC + if (ecrnx_sta->ps.active) { + if (ecrnx_sta->uapsd_tids && + (ecrnx_sta->uapsd_tids == ((1 << NX_NB_TXQ_PER_STA) - 1))) + res = scnprintf(&buf[idx], size, PS_HDR_UAPSD "\n", + ecrnx_sta->ps.pkt_ready[UAPSD_ID], + ecrnx_sta->ps.sp_cnt[UAPSD_ID]); + else if (ecrnx_sta->uapsd_tids) + res = scnprintf(&buf[idx], size, PS_HDR "\n", + ecrnx_sta->ps.pkt_ready[LEGACY_PS_ID], + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID], + ecrnx_sta->ps.pkt_ready[UAPSD_ID], + ecrnx_sta->ps.sp_cnt[UAPSD_ID]); + else + res = scnprintf(&buf[idx], size, PS_HDR_LEGACY "\n", + ecrnx_sta->ps.pkt_ready[LEGACY_PS_ID], + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID]); + idx += res; + size -= res; + } else { + res = scnprintf(&buf[idx], size, "\n"); + idx += res; + size -= res; + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + + res = scnprintf(&buf[idx], size, TXQ_STA_PREF TXQ_HDR TXQ_HDR_SUFF "\n"); + idx += res; + size -= res; + + + foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) { + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, STA_TXQ, tid, NULL); + idx += res; + size -= res; + } + + return idx; +} + +static int ecrnx_dbgfs_txq_vif(char *buf, size_t size, struct ecrnx_vif *ecrnx_vif, + struct ecrnx_hw *ecrnx_hw) +{ + int res, idx = 0; + struct ecrnx_txq *txq; + struct ecrnx_sta *ecrnx_sta; + +#ifdef CONFIG_ECRNX_FULLMAC + res = scnprintf(&buf[idx], size, VIF_HDR, ecrnx_vif->vif_index, ecrnx_vif->ndev->name); + idx += res; + size -= res; + if (!ecrnx_vif->up || ecrnx_vif->ndev == NULL) + return idx; + +#else + int ac; + char ac_name[2] = {'0', '\0'}; + + res = scnprintf(&buf[idx], size, VIF_HDR, ecrnx_vif->vif_index); + idx += res; + size -= res; +#endif /* CONFIG_ECRNX_FULLMAC */ + +#ifdef CONFIG_ECRNX_FULLMAC + if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP || + ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO || + ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MESH_POINT) { + res = scnprintf(&buf[idx], size, TXQ_VIF_PREF TXQ_HDR "\n"); + idx += res; + size -= res; + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, "UNK"); + idx += res; + size -= res; + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE); + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, "BCMC"); + idx += res; + size -= res; + ecrnx_sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; + if (ecrnx_sta->ps.active) { + res = scnprintf(&buf[idx], size, PS_HDR_LEGACY "\n", + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID], + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID]); + idx += res; + size -= res; + } else { + res = scnprintf(&buf[idx], size, "\n"); + idx += res; + size -= res; + } + + list_for_each_entry(ecrnx_sta, &ecrnx_vif->ap.sta_list, list) { + res = ecrnx_dbgfs_txq_sta(&buf[idx], size, ecrnx_sta, ecrnx_hw); + idx += res; + size -= res; + } + } else if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_STATION || + ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_CLIENT) { + if (ecrnx_vif->sta.ap) { + res = ecrnx_dbgfs_txq_sta(&buf[idx], size, ecrnx_vif->sta.ap, ecrnx_hw); + idx += res; + size -= res; + } + } + +#else + res = scnprintf(&buf[idx], size, TXQ_VIF_PREF TXQ_HDR "\n"); + idx += res; + size -= res; + + foreach_vif_txq(ecrnx_vif, txq, ac) { + ac_name[0]++; + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, ac_name); + idx += res; + size -= res; + } + + list_for_each_entry(ecrnx_sta, &ecrnx_vif->stations, list) { + res = ecrnx_dbgfs_txq_sta(&buf[idx], size, ecrnx_sta, ecrnx_hw); + idx += res; + size -= res; + } +#endif /* CONFIG_ECRNX_FULLMAC */ + return idx; +} + +static ssize_t ecrnx_dbgfs_txq_read(struct file *file , + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *ecrnx_hw = file->private_data; + struct ecrnx_vif *vif; + char *buf; + int idx, res; + ssize_t read; + size_t bufsz = ((NX_VIRT_DEV_MAX * (VIF_HDR_MAX_LEN + 2 * VIF_SEP_LEN)) + + (NX_REMOTE_STA_MAX * STA_HDR_MAX_LEN) + + ((NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX + NX_NB_TXQ) * + TXQ_HDR_MAX_LEN) + CAPTION_LEN); + + /* everything is read in one go */ + if (*ppos) + return 0; + + bufsz = min_t(size_t, bufsz, count); + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + bufsz--; + idx = 0; + + res = scnprintf(&buf[idx], bufsz, CAPTION); + idx += res; + bufsz -= res; + + //spin_lock_bh(&ecrnx_hw->tx_lock); + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + res = scnprintf(&buf[idx], bufsz, "\n"VIF_SEP); + idx += res; + bufsz -= res; + res = ecrnx_dbgfs_txq_vif(&buf[idx], bufsz, vif, ecrnx_hw); + idx += res; + bufsz -= res; + res = scnprintf(&buf[idx], bufsz, VIF_SEP); + idx += res; + bufsz -= res; + } + //spin_unlock_bh(&ecrnx_hw->tx_lock); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, idx); + kfree(buf); + + return read; +} +DEBUGFS_READ_FILE_OPS(txq); + +static ssize_t ecrnx_dbgfs_acsinfo_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + #ifdef CONFIG_ECRNX_SOFTMAC + struct wiphy *wiphy = priv->hw->wiphy; + #else //CONFIG_ECRNX_SOFTMAC + struct wiphy *wiphy = priv->wiphy; + #endif //CONFIG_ECRNX_SOFTMAC + ssize_t read; + char *buf = kmalloc((SCAN_CHANNEL_MAX + 1) * 43, GFP_ATOMIC); + + //char buf[(SCAN_CHANNEL_MAX + 1) * 43]; + int survey_cnt = 0; + int len = 0; + int band, chan_cnt; + + if(!buf){ + return 0; + } + + mutex_lock(&priv->dbgdump_elem.mutex); + + len += scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "FREQ TIME(ms) BUSY(ms) NOISE(dBm)\n"); + +#ifdef CONFIG_ECRNX_5G + for (band = NL80211_BAND_2GHZ; band <= NL80211_BAND_5GHZ; band++) { +#else + for (band = NL80211_BAND_2GHZ; band <= NL80211_BAND_2GHZ; band++) { +#endif + for (chan_cnt = 0; chan_cnt < wiphy->bands[band]->n_channels; chan_cnt++) { + struct ecrnx_survey_info *p_survey_info = &priv->survey[survey_cnt]; + struct ieee80211_channel *p_chan = &wiphy->bands[band]->channels[chan_cnt]; + + if (p_survey_info->filled) { + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - len - 1, count), + "%d %03d %03d %d\n", + p_chan->center_freq, + p_survey_info->chan_time_ms, + p_survey_info->chan_time_busy_ms, + p_survey_info->noise_dbm); + } else { + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) -len -1, count), + "%d NOT AVAILABLE\n", + p_chan->center_freq); + } + + survey_cnt++; + } + } + + mutex_unlock(&priv->dbgdump_elem.mutex); + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return read; +} + +DEBUGFS_READ_FILE_OPS(acsinfo); + +static ssize_t ecrnx_dbgfs_fw_dbg_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + char help[]="usage: [MOD:<ALL|KE|DBG|IPC|DMA|MM|TX|RX|PHY>]* " + "[DBG:<NONE|CRT|ERR|WRN|INF|VRB>]\n"; + + return simple_read_from_buffer(user_buf, count, ppos, help, sizeof(help)); +} + + +static ssize_t ecrnx_dbgfs_fw_dbg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int idx = 0; + u32 mod = 0; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + +#define ECRNX_MOD_TOKEN(str, val) \ + if (strncmp(&buf[idx], str, sizeof(str) - 1 ) == 0) { \ + idx += sizeof(str) - 1; \ + mod |= val; \ + continue; \ + } + +#define ECRNX_DBG_TOKEN(str, val) \ + if (strncmp(&buf[idx], str, sizeof(str) - 1) == 0) { \ + idx += sizeof(str) - 1; \ + dbg = val; \ + goto dbg_done; \ + } + + while ((idx + 4) < len) { + if (strncmp(&buf[idx], "MOD:", 4) == 0) { + idx += 4; + ECRNX_MOD_TOKEN("ALL", 0xffffffff); + ECRNX_MOD_TOKEN("KE", BIT(0)); + ECRNX_MOD_TOKEN("DBG", BIT(1)); + ECRNX_MOD_TOKEN("IPC", BIT(2)); + ECRNX_MOD_TOKEN("DMA", BIT(3)); + ECRNX_MOD_TOKEN("MM", BIT(4)); + ECRNX_MOD_TOKEN("TX", BIT(5)); + ECRNX_MOD_TOKEN("RX", BIT(6)); + ECRNX_MOD_TOKEN("PHY", BIT(7)); + idx++; + } else if (strncmp(&buf[idx], "DBG:", 4) == 0) { + u32 dbg = 0; + idx += 4; + ECRNX_DBG_TOKEN("NONE", 0); + ECRNX_DBG_TOKEN("CRT", 1); + ECRNX_DBG_TOKEN("ERR", 2); + ECRNX_DBG_TOKEN("WRN", 3); + ECRNX_DBG_TOKEN("INF", 4); + ECRNX_DBG_TOKEN("VRB", 5); + idx++; + continue; + dbg_done: + ecrnx_send_dbg_set_sev_filter_req(priv, dbg); + } else { + idx++; + } + } + + if (mod) { + ecrnx_send_dbg_set_mod_filter_req(priv, mod); + } + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(fw_dbg); + +static ssize_t ecrnx_dbgfs_sys_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[3*64]; + int len = 0; + ssize_t read; + int error = 0; + struct dbg_get_sys_stat_cfm cfm; + u32 sleep_int, sleep_frac, doze_int, doze_frac; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Get the information from the FW */ + if ((error = ecrnx_send_dbg_get_sys_stat_req(priv, &cfm))) + return error; + + if (cfm.stats_time == 0) + return 0; + + sleep_int = ((cfm.cpu_sleep_time * 100) / cfm.stats_time); + sleep_frac = (((cfm.cpu_sleep_time * 100) % cfm.stats_time) * 10) / cfm.stats_time; + doze_int = ((cfm.doze_time * 100) / cfm.stats_time); + doze_frac = (((cfm.doze_time * 100) % cfm.stats_time) * 10) / cfm.stats_time; + + len += scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "\nSystem statistics:\n"); + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - 1, count), + " CPU sleep [%%]: %d.%d\n", sleep_int, sleep_frac); + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - 1, count), + " Doze [%%]: %d.%d\n", doze_int, doze_frac); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + return read; +} + +DEBUGFS_READ_FILE_OPS(sys_stats); + +#ifdef CONFIG_ECRNX_MUMIMO_TX +static ssize_t ecrnx_dbgfs_mu_group_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *ecrnx_hw = file->private_data; + struct ecrnx_mu_info *mu = &ecrnx_hw->mu; + struct ecrnx_mu_group *group; + size_t bufsz = NX_MU_GROUP_MAX * sizeof("xx = (xx - xx - xx - xx)\n") + 50; + char *buf; + int j, res, idx = 0; + + if (*ppos) + return 0; + + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + res = scnprintf(&buf[idx], bufsz, "MU Group list (%d groups, %d users max)\n", + NX_MU_GROUP_MAX, CONFIG_USER_MAX); + idx += res; + bufsz -= res; + + list_for_each_entry(group, &mu->active_groups, list) { + if (group->user_cnt) { + res = scnprintf(&buf[idx], bufsz, "%2d = (", group->group_id); + idx += res; + bufsz -= res; + for (j = 0; j < (CONFIG_USER_MAX - 1) ; j++) { + if (group->users[j]) + res = scnprintf(&buf[idx], bufsz, "%2d - ", + group->users[j]->sta_idx); + else + res = scnprintf(&buf[idx], bufsz, ".. - "); + + idx += res; + bufsz -= res; + } + + if (group->users[j]) + res = scnprintf(&buf[idx], bufsz, "%2d)\n", + group->users[j]->sta_idx); + else + res = scnprintf(&buf[idx], bufsz, "..)\n"); + + idx += res; + bufsz -= res; + } + } + + res = simple_read_from_buffer(user_buf, count, ppos, buf, idx); + kfree(buf); + + return res; +} + +DEBUGFS_READ_FILE_OPS(mu_group); +#endif + +#ifdef CONFIG_ECRNX_P2P_DEBUGFS +static ssize_t ecrnx_dbgfs_oppps_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *rw_hw = file->private_data; + struct ecrnx_vif *rw_vif; + char buf[32]; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + int ctw; + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + /* Read the written CT Window (provided in ms) value */ + if (sscanf(buf, "ctw=%d", &ctw) > 0) { + /* Check if at least one VIF is configured as P2P GO */ + list_for_each_entry(rw_vif, &rw_hw->vifs, list) { +#ifdef CONFIG_ECRNX_SOFTMAC + if ((ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_AP) && rw_vif->vif->p2p) { +#else /* CONFIG_ECRNX_FULLMAC */ + if (ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_P2P_GO) { +#endif /* CONFIG_ECRNX_SOFTMAC */ + struct mm_set_p2p_oppps_cfm cfm; + + /* Forward request to the embedded and wait for confirmation */ + ecrnx_send_p2p_oppps_req(rw_hw, rw_vif, (u8)ctw, &cfm); + + break; + } + } + } + + return count; +} + +DEBUGFS_WRITE_FILE_OPS(oppps); + +static ssize_t ecrnx_dbgfs_noa_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *rw_hw = file->private_data; + struct ecrnx_vif *rw_vif; + char buf[64]; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + int noa_count, interval, duration, dyn_noa; + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + /* Read the written NOA information */ + if (sscanf(buf, "count=%d interval=%d duration=%d dyn=%d", + &noa_count, &interval, &duration, &dyn_noa) > 0) { + /* Check if at least one VIF is configured as P2P GO */ + list_for_each_entry(rw_vif, &rw_hw->vifs, list) { +#ifdef CONFIG_ECRNX_SOFTMAC + if ((ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_AP) && rw_vif->vif->p2p) { +#else /* CONFIG_ECRNX_FULLMAC */ + if (ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_P2P_GO) { +#endif /* CONFIG_ECRNX_SOFTMAC */ + struct mm_set_p2p_noa_cfm cfm; + + /* Forward request to the embedded and wait for confirmation */ + ecrnx_send_p2p_noa_req(rw_hw, rw_vif, noa_count, interval, + duration, (dyn_noa > 0), &cfm); + + break; + } + } + } + + return count; +} + +DEBUGFS_WRITE_FILE_OPS(noa); +#endif /* CONFIG_ECRNX_P2P_DEBUGFS */ + +struct ecrnx_dbgfs_fw_trace { + struct ecrnx_fw_trace_local_buf lbuf; + struct ecrnx_fw_trace *trace; + struct ecrnx_hw *ecrnx_hw; +}; + +static int ecrnx_dbgfs_fw_trace_open(struct inode *inode, struct file *file) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = kmalloc(sizeof(*ltrace), GFP_KERNEL); + struct ecrnx_hw *priv = inode->i_private; + + if (!ltrace) + return -ENOMEM; + + if (ecrnx_fw_trace_alloc_local(<race->lbuf, 5120)) { + kfree(ltrace); + } + + ltrace->trace = &priv->debugfs.fw_trace; + ltrace->ecrnx_hw = priv; + file->private_data = ltrace; + return 0; +} + +static int ecrnx_dbgfs_fw_trace_release(struct inode *inode, struct file *file) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = file->private_data; + + if (ltrace) { + ecrnx_fw_trace_free_local(<race->lbuf); + kfree(ltrace); + } + + return 0; +} + +static ssize_t ecrnx_dbgfs_fw_trace_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = file->private_data; + bool dont_wait = ((file->f_flags & O_NONBLOCK) || + ltrace->ecrnx_hw->debugfs.unregistering); + + return ecrnx_fw_trace_read(ltrace->trace, <race->lbuf, + dont_wait, user_buf, count); +} + +static ssize_t ecrnx_dbgfs_fw_trace_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = file->private_data; + int ret; + + ret = _ecrnx_fw_trace_reset(ltrace->trace, true); + if (ret) + return ret; + + return count; +} + +DEBUGFS_READ_WRITE_OPEN_RELEASE_FILE_OPS(fw_trace); + +static ssize_t ecrnx_dbgfs_fw_trace_level_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + return ecrnx_fw_trace_level_read(&priv->debugfs.fw_trace, user_buf, + count, ppos); +} + +static ssize_t ecrnx_dbgfs_fw_trace_level_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + return ecrnx_fw_trace_level_write(&priv->debugfs.fw_trace, user_buf, count); +} +DEBUGFS_READ_WRITE_FILE_OPS(fw_trace_level); + + +#ifdef CONFIG_ECRNX_RADAR +static ssize_t ecrnx_dbgfs_pulses_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos, + int rd_idx) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int len = 0; + int bufsz; + int i; + int index; + struct ecrnx_radar_pulses *p = &priv->radar.pulses[rd_idx]; + ssize_t read; + + if (*ppos != 0) + return 0; + + /* Prevent from interrupt preemption */ + spin_lock_bh(&priv->radar.lock); + bufsz = p->count * 34 + 51; + bufsz += ecrnx_radar_dump_pattern_detector(NULL, 0, &priv->radar, rd_idx); + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) { + spin_unlock_bh(&priv->radar.lock); + return 0; + } + + if (p->count) { + len += scnprintf(&buf[len], bufsz - len, + " PRI WIDTH FOM FREQ\n"); + index = p->index; + for (i = 0; i < p->count; i++) { + struct radar_pulse *pulse; + + if (index > 0) + index--; + else + index = ECRNX_RADAR_PULSE_MAX - 1; + + pulse = (struct radar_pulse *) &p->buffer[index]; + + len += scnprintf(&buf[len], bufsz - len, + "%05dus %03dus %2d%% %+3dMHz\n", pulse->rep, + 2 * pulse->len, 6 * pulse->fom, 2*pulse->freq); + } + } + + len += ecrnx_radar_dump_pattern_detector(&buf[len], bufsz - len, + &priv->radar, rd_idx); + + spin_unlock_bh(&priv->radar.lock); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + + return read; +} + +static ssize_t ecrnx_dbgfs_pulses_prim_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_pulses_read(file, user_buf, count, ppos, 0); +} + +DEBUGFS_READ_FILE_OPS(pulses_prim); + +static ssize_t ecrnx_dbgfs_pulses_sec_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_pulses_read(file, user_buf, count, ppos, 1); +} + +DEBUGFS_READ_FILE_OPS(pulses_sec); + +static ssize_t ecrnx_dbgfs_detected_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int bufsz,len = 0; + ssize_t read; + + if (*ppos != 0) + return 0; + + bufsz = 5; // RIU:\n + bufsz += ecrnx_radar_dump_radar_detected(NULL, 0, &priv->radar, + ECRNX_RADAR_RIU); + + if (priv->phy.cnt > 1) { + bufsz += 5; // FCU:\n + bufsz += ecrnx_radar_dump_radar_detected(NULL, 0, &priv->radar, + ECRNX_RADAR_FCU); + } + + buf = kmalloc(bufsz, GFP_KERNEL); + if (buf == NULL) { + return 0; + } + + len = scnprintf(&buf[len], bufsz, "RIU:\n"); + len += ecrnx_radar_dump_radar_detected(&buf[len], bufsz - len, &priv->radar, + ECRNX_RADAR_RIU); + + if (priv->phy.cnt > 1) { + len += scnprintf(&buf[len], bufsz - len, "FCU:\n"); + len += ecrnx_radar_dump_radar_detected(&buf[len], bufsz - len, + &priv->radar, ECRNX_RADAR_FCU); + } + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + + return read; +} + +DEBUGFS_READ_FILE_OPS(detected); + +static ssize_t ecrnx_dbgfs_enable_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "RIU=%d FCU=%d\n", priv->radar.dpd[ECRNX_RADAR_RIU]->enabled, + priv->radar.dpd[ECRNX_RADAR_FCU]->enabled); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_enable_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "RIU=%d", &val) > 0) + ecrnx_radar_detection_enable(&priv->radar, val, ECRNX_RADAR_RIU); + + if (sscanf(buf, "FCU=%d", &val) > 0) + ecrnx_radar_detection_enable(&priv->radar, val, ECRNX_RADAR_FCU); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(enable); + +static ssize_t ecrnx_dbgfs_band_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "BAND=%d\n", priv->phy.sec_chan.band); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_band_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + +#ifdef CONFIG_ECRNX_5G + if ((sscanf(buf, "%d", &val) > 0) && (val >= 0) && (val <= NL80211_BAND_5GHZ)) +#else + if ((sscanf(buf, "%d", &val) > 0) && (val >= 0) && (val <= NL80211_BAND_2GHZ)) +#endif + priv->phy.sec_chan.band = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(band); + +static ssize_t ecrnx_dbgfs_type_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "TYPE=%d\n", priv->phy.sec_chan.type); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_type_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if ((sscanf(buf, "%d", &val) > 0) && (val >= PHY_CHNL_BW_20) && + (val <= PHY_CHNL_BW_80P80)) + priv->phy.sec_chan.type = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(type); + +static ssize_t ecrnx_dbgfs_prim20_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "PRIM20=%dMHz\n", priv->phy.sec_chan.prim20_freq); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_prim20_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "%d", &val) > 0) + priv->phy.sec_chan.prim20_freq = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(prim20); + +static ssize_t ecrnx_dbgfs_center1_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "CENTER1=%dMHz\n", priv->phy.sec_chan.center1_freq); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_center1_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "%d", &val) > 0) + priv->phy.sec_chan.center1_freq = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(center1); + +static ssize_t ecrnx_dbgfs_center2_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "CENTER2=%dMHz\n", priv->phy.sec_chan.center2_freq); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_center2_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "%d", &val) > 0) + priv->phy.sec_chan.center2_freq = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(center2); + + +static ssize_t ecrnx_dbgfs_set_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t ecrnx_dbgfs_set_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + + ecrnx_send_set_channel(priv, 1, NULL); + ecrnx_radar_detection_enable(&priv->radar, ECRNX_RADAR_DETECT_ENABLE, + ECRNX_RADAR_FCU); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(set); +#endif /* CONFIG_ECRNX_RADAR */ + +#ifdef CONFIG_ECRNX_FULLMAC + +#define LINE_MAX_SZ 150 + +struct st { + char line[LINE_MAX_SZ + 1]; + unsigned int r_idx; +}; + +static int compare_idx(const void *st1, const void *st2) +{ + int index1 = ((struct st *)st1)->r_idx; + int index2 = ((struct st *)st2)->r_idx; + + if (index1 > index2) return 1; + if (index1 < index2) return -1; + + return 0; +} + +static const int ru_size[] = +{ + 26, + 52, + 106, + 242, + 484, + 996 +}; + +static int print_rate(char *buf, int size, int format, int nss, int mcs, int bw, + int sgi, int pre, int dcm, int *r_idx) +{ + int res = 0; + int bitrates_cck[4] = { 10, 20, 55, 110 }; + int bitrates_ofdm[8] = { 6, 9, 12, 18, 24, 36, 48, 54}; + char he_gi[3][4] = {"0.8", "1.6", "3.2"}; + + if (format < FORMATMOD_HT_MF) { + if (mcs < 4) { + if (r_idx) { + *r_idx = (mcs * 2) + pre; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "L-CCK/%cP %2u.%1uM ", + pre > 0 ? 'L' : 'S', + bitrates_cck[mcs] / 10, + bitrates_cck[mcs] % 10); + } else { + mcs -= 4; + if (r_idx) { + *r_idx = N_CCK + mcs; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "L-OFDM %2u.0M ", + bitrates_ofdm[mcs]); + } + } else if (format < FORMATMOD_VHT) { + if (r_idx) { + *r_idx = N_CCK + N_OFDM + nss * 32 + mcs * 4 + bw * 2 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + mcs += nss * 8; + res += scnprintf(&buf[res], size - res, "HT%d/%cGI MCS%-2d ", + 20 * (1 << bw), sgi ? 'S' : 'L', mcs); + } else if (format == FORMATMOD_VHT){ + if (r_idx) { + *r_idx = N_CCK + N_OFDM + N_HT + nss * 80 + mcs * 8 + bw * 2 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "VHT%d/%cGI%*cMCS%d/%1d ", + 20 * (1 << bw), sgi ? 'S' : 'L', bw > 2 ? 9 : 10, ' ', + mcs, nss + 1); + } else if (format == FORMATMOD_HE_SU){ + if (r_idx) { + *r_idx = N_CCK + N_OFDM + N_HT + N_VHT + nss * 144 + mcs * 12 + bw * 3 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "HE%d/GI%s%4s%*cMCS%d/%1d%*c", + 20 * (1 << bw), he_gi[sgi], dcm ? "/DCM" : "", + bw > 2 ? 4 : 5, ' ', mcs, nss + 1, mcs > 9 ? 1 : 2, ' '); + } else { + if (r_idx) { + *r_idx = N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU + nss * 216 + mcs * 18 + bw * 3 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "HEMU-%d/GI%s%*cMCS%d/%1d%*c", + ru_size[bw], he_gi[sgi], bw > 1 ? 1 : 2, ' ', + mcs, nss + 1, mcs > 9 ? 1 : 2, ' '); + + } + + return res; +} + +static int print_rate_from_cfg(char *buf, int size, u32 rate_config, int *r_idx, int ru_size) +{ + union ecrnx_rate_ctrl_info *r_cfg = (union ecrnx_rate_ctrl_info *)&rate_config; + union ecrnx_mcs_index *mcs_index = (union ecrnx_mcs_index *)&rate_config; + unsigned int ft, pre, gi, bw, nss, mcs, dcm, len; + + ft = r_cfg->formatModTx; + pre = r_cfg->giAndPreTypeTx >> 1; + gi = r_cfg->giAndPreTypeTx; + bw = r_cfg->bwTx; + dcm = 0; + if (ft == FORMATMOD_HE_MU) { + mcs = mcs_index->he.mcs; + nss = mcs_index->he.nss; + bw = ru_size; + dcm = r_cfg->dcmTx; + } else if (ft == FORMATMOD_HE_SU) { + mcs = mcs_index->he.mcs; + nss = mcs_index->he.nss; + dcm = r_cfg->dcmTx; + } else if (ft == FORMATMOD_VHT) { + mcs = mcs_index->vht.mcs; + nss = mcs_index->vht.nss; + } else if (ft >= FORMATMOD_HT_MF) { + mcs = mcs_index->ht.mcs; + nss = mcs_index->ht.nss; + } else { + mcs = mcs_index->legacy; + nss = 0; + } + + len = print_rate(buf, size, ft, nss, mcs, bw, gi, pre, dcm, r_idx); + return len; +} + +static void idx_to_rate_cfg(int idx, union ecrnx_rate_ctrl_info *r_cfg, int *ru_size) +{ + r_cfg->value = 0; + if (idx < N_CCK) + { + r_cfg->formatModTx = FORMATMOD_NON_HT; + r_cfg->giAndPreTypeTx = (idx & 1) << 1; + r_cfg->mcsIndexTx = idx / 2; + } + else if (idx < (N_CCK + N_OFDM)) + { + r_cfg->formatModTx = FORMATMOD_NON_HT; + r_cfg->mcsIndexTx = idx - N_CCK + 4; + } + else if (idx < (N_CCK + N_OFDM + N_HT)) + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + idx -= (N_CCK + N_OFDM); + r_cfg->formatModTx = FORMATMOD_HT_MF; + r->ht.nss = idx / (8*2*2); + r->ht.mcs = (idx % (8*2*2)) / (2*2); + r_cfg->bwTx = ((idx % (8*2*2)) % (2*2)) / 2; + r_cfg->giAndPreTypeTx = idx & 1; + } + else if (idx < (N_CCK + N_OFDM + N_HT + N_VHT)) + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + idx -= (N_CCK + N_OFDM + N_HT); + r_cfg->formatModTx = FORMATMOD_VHT; + r->vht.nss = idx / (10*4*2); + r->vht.mcs = (idx % (10*4*2)) / (4*2); + r_cfg->bwTx = ((idx % (10*4*2)) % (4*2)) / 2; + r_cfg->giAndPreTypeTx = idx & 1; + } + else if (idx < (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU)) + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + idx -= (N_CCK + N_OFDM + N_HT + N_VHT); + r_cfg->formatModTx = FORMATMOD_HE_SU; + r->vht.nss = idx / (12*4*3); + r->vht.mcs = (idx % (12*4*3)) / (4*3); + r_cfg->bwTx = ((idx % (12*4*3)) % (4*3)) / 3; + r_cfg->giAndPreTypeTx = idx % 3; + } + else + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + BUG_ON(ru_size == NULL); + + idx -= (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU); + r_cfg->formatModTx = FORMATMOD_HE_MU; + r->vht.nss = idx / (12*6*3); + r->vht.mcs = (idx % (12*6*3)) / (6*3); + *ru_size = ((idx % (12*6*3)) % (6*3)) / 3; + r_cfg->giAndPreTypeTx = idx % 3; + r_cfg->bwTx = 0; + } +} + +static struct ecrnx_sta* ecrnx_dbgfs_get_sta(struct ecrnx_hw *ecrnx_hw, + char* mac_addr) +{ + u8 mac[6]; + + if (sscanf(mac_addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) + return NULL; + return ecrnx_get_sta(ecrnx_hw, mac); +} + +static ssize_t ecrnx_dbgfs_twt_request_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + char buf[750]; + ssize_t read; + struct ecrnx_hw *priv = file->private_data; + struct ecrnx_sta *sta = NULL; + int len; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + if (sta->twt_ind.sta_idx != ECRNX_INVALID_STA) + { + struct twt_conf_tag *conf = &sta->twt_ind.conf; + if (sta->twt_ind.resp_type == MAC_TWT_SETUP_ACCEPT) + len = scnprintf(buf, sizeof(buf) - 1, "Accepted configuration"); + else if (sta->twt_ind.resp_type == MAC_TWT_SETUP_ALTERNATE) + len = scnprintf(buf, sizeof(buf) - 1, "Alternate configuration proposed by AP"); + else if (sta->twt_ind.resp_type == MAC_TWT_SETUP_DICTATE) + len = scnprintf(buf, sizeof(buf) - 1, "AP dictates the following configuration"); + else if (sta->twt_ind.resp_type == MAC_TWT_SETUP_REJECT) + len = scnprintf(buf, sizeof(buf) - 1, "AP rejects the following configuration"); + else + { + len = scnprintf(buf, sizeof(buf) - 1, "Invalid response from the peer"); + goto end; + } + len += scnprintf(&buf[len], sizeof(buf) - 1 - len,":\n" + "flow_type = %d\n" + "wake interval mantissa = %d\n" + "wake interval exponent = %d\n" + "wake interval = %d us\n" + "nominal minimum wake duration = %d us\n", + conf->flow_type, conf->wake_int_mantissa, + conf->wake_int_exp, + conf->wake_int_mantissa << conf->wake_int_exp, + conf->wake_dur_unit ? + conf->min_twt_wake_dur * 1024: + conf->min_twt_wake_dur * 256); + } + else + { + len = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "setup_command = <0: request, 1: suggest, 2: demand>," + "flow_type = <0: announced, 1: unannounced>," + "wake_interval_mantissa = <0 if setup request and no constraints>," + "wake_interval_exp = <0 if setup request and no constraints>," + "nominal_min_wake_dur = <0 if setup request and no constraints>," + "wake_dur_unit = <0: 256us, 1: tu>"); + } + end: + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + return read; +} + +static ssize_t ecrnx_dbgfs_twt_request_write(struct file *file, + const char __user *user_buf, + size_t count, + loff_t *ppos) +{ + char *accepted_params[] = {"setup_command", + "flow_type", + "wake_interval_mantissa", + "wake_interval_exp", + "nominal_min_wake_dur", + "wake_dur_unit", + 0 + }; + struct twt_conf_tag twt_conf; + struct twt_setup_cfm twt_setup_cfm; + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char param[30]; + char *line; + int error = 1, i, val, setup_command = -1; + bool_l found; + char *buf = kmalloc(1024, GFP_ATOMIC); + size_t len = 1024 - 1; + + if(!buf){ + return 0; + } + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL){ + kfree(buf); + return -EINVAL; + } + + /* Get the content of the file */ + if (copy_from_user(buf, user_buf, len)){ + kfree(buf); + return -EFAULT; + } + + buf[len] = '\0'; + memset(&twt_conf, 0, sizeof(twt_conf)); + + line = buf; + /* Get the content of the file */ + while (line != NULL) + { + if (sscanf(line, "%s = %d", param, &val) == 2) + { + i = 0; + found = false; + // Check if parameter is valid + while(accepted_params[i]) + { + if (strcmp(accepted_params[i], param) == 0) + { + found = true; + break; + } + i++; + } + + if (!found) + { + dev_err(priv->dev, "%s: parameter %s is not valid\n", __func__, param); + kfree(buf); + return -EINVAL; + } + + if (!strcmp(param, "setup_command")) + { + setup_command = val; + } + else if (!strcmp(param, "flow_type")) + { + twt_conf.flow_type = val; + } + else if (!strcmp(param, "wake_interval_mantissa")) + { + twt_conf.wake_int_mantissa = val; + } + else if (!strcmp(param, "wake_interval_exp")) + { + twt_conf.wake_int_exp = val; + } + else if (!strcmp(param, "nominal_min_wake_dur")) + { + twt_conf.min_twt_wake_dur = val; + } + else if (!strcmp(param, "wake_dur_unit")) + { + twt_conf.wake_dur_unit = val; + } + } + else + { + dev_err(priv->dev, "%s: Impossible to read TWT configuration option\n", __func__); + kfree(buf); + return -EFAULT; + } + line = strchr(line, ','); + if(line == NULL) + break; + line++; + } + + if (setup_command == -1) + { + dev_err(priv->dev, "%s: TWT missing setup command\n", __func__); + kfree(buf); + return -EFAULT; + } + + // Forward the request to the LMAC + if ((error = ecrnx_send_twt_request(priv, setup_command, sta->vif_idx, + &twt_conf, &twt_setup_cfm)) != 0){ + kfree(buf); + return error; + } + + // Check the status + if (twt_setup_cfm.status != CO_OK){ + kfree(buf); + return -EIO; + } + + kfree(buf); + return count; +} +DEBUGFS_READ_WRITE_FILE_OPS(twt_request); + +static ssize_t ecrnx_dbgfs_twt_teardown_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + char buf[512]; + int ret; + ssize_t read; + + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "TWT teardown format:\n\n" + "flow_id = <ID>\n"); + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_twt_teardown_write(struct file *file, + const char __user *user_buf, + size_t count, + loff_t *ppos) +{ + struct twt_teardown_req twt_teardown; + struct twt_teardown_cfm twt_teardown_cfm; + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char buf[256]; + char *line; + int error = 1; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Get the content of the file */ + if (copy_from_user(buf, user_buf, len)) + return -EINVAL; + + buf[len] = '\0'; + memset(&twt_teardown, 0, sizeof(twt_teardown)); + + /* Get the content of the file */ + line = buf; + + if (sscanf(line, "flow_id = %d", (int *) &twt_teardown.id) != 1) + { + dev_err(priv->dev, "%s: Invalid TWT configuration\n", __func__); + return -EINVAL; + } + + twt_teardown.neg_type = 0; + twt_teardown.all_twt = 0; + twt_teardown.vif_idx = sta->vif_idx; + + // Forward the request to the LMAC + if ((error = ecrnx_send_twt_teardown(priv, &twt_teardown, &twt_teardown_cfm)) != 0) + return error; + + // Check the status + if (twt_teardown_cfm.status != CO_OK) + return -EIO; + + return count; +} +DEBUGFS_READ_WRITE_FILE_OPS(twt_teardown); + +static ssize_t ecrnx_dbgfs_rc_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char *buf; + int bufsz, len = 0; + ssize_t read; + int i = 0; + int error = 0; + struct me_rc_stats_cfm me_rc_stats_cfm; + unsigned int no_samples; + struct st *st; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* everything should fit in one call */ + if (*ppos) + return 0; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Forward the information to the LMAC */ + if ((error = ecrnx_send_me_rc_stats(priv, sta->sta_idx, &me_rc_stats_cfm))) + return error; + + no_samples = me_rc_stats_cfm.no_samples; + if (no_samples == 0) + return 0; + + bufsz = no_samples * LINE_MAX_SZ + 500; + + buf = kmalloc(bufsz + 1, GFP_ATOMIC); + if (buf == NULL) + return 0; + + st = kmalloc(sizeof(struct st) * no_samples, GFP_ATOMIC); + if (st == NULL) + { + kfree(buf); + return 0; + } + + for (i = 0; i < no_samples; i++) + { + unsigned int tp, eprob; + len = print_rate_from_cfg(st[i].line, LINE_MAX_SZ, + me_rc_stats_cfm.rate_stats[i].rate_config, + &st[i].r_idx, 0); + + if (me_rc_stats_cfm.sw_retry_step != 0) + { + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c", + me_rc_stats_cfm.retry_step_idx[me_rc_stats_cfm.sw_retry_step] == i ? '*' : ' '); + } + else + { + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, " "); + } + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c", + me_rc_stats_cfm.retry_step_idx[0] == i ? 'T' : ' '); + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c", + me_rc_stats_cfm.retry_step_idx[1] == i ? 't' : ' '); + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c ", + me_rc_stats_cfm.retry_step_idx[2] == i ? 'P' : ' '); + + tp = me_rc_stats_cfm.tp[i] / 10; + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, " %4u.%1u", + tp / 10, tp % 10); + + eprob = ((me_rc_stats_cfm.rate_stats[i].probability * 1000) >> 16) + 1; + len += scnprintf(&st[i].line[len],LINE_MAX_SZ - len, + " %4u.%1u %5u(%6u) %6u", + eprob / 10, eprob % 10, + me_rc_stats_cfm.rate_stats[i].success, + me_rc_stats_cfm.rate_stats[i].attempts, + me_rc_stats_cfm.rate_stats[i].sample_skipped); + } + len = scnprintf(buf, bufsz , + "\nTX rate info for %02X:%02X:%02X:%02X:%02X:%02X:\n", + sta->mac_addr[0], sta->mac_addr[1], sta->mac_addr[2], + sta->mac_addr[3], sta->mac_addr[4], sta->mac_addr[5]); + + len += scnprintf(&buf[len], bufsz - len, + " # type rate tpt eprob ok( tot) skipped\n"); + + // add sorted statistics to the buffer + sort(st, no_samples, sizeof(st[0]), compare_idx, NULL); + for (i = 0; i < no_samples; i++) + { + len += scnprintf(&buf[len], bufsz - len, "%s\n", st[i].line); + } + + // display HE TB statistics if any + if (me_rc_stats_cfm.rate_stats[RC_HE_STATS_IDX].rate_config != 0) { + unsigned int tp, eprob; + struct rc_rate_stats *rate_stats = &me_rc_stats_cfm.rate_stats[RC_HE_STATS_IDX]; + int ru_index = rate_stats->ru_and_length & 0x07; + int ul_length = rate_stats->ru_and_length >> 3; + + len += scnprintf(&buf[len], bufsz - len, + "\nHE TB rate info:\n"); + + len += scnprintf(&buf[len], bufsz - len, + " type rate tpt eprob ok( tot) ul_length\n "); + len += print_rate_from_cfg(&buf[len], bufsz - len, rate_stats->rate_config, + NULL, ru_index); + + tp = me_rc_stats_cfm.tp[RC_HE_STATS_IDX] / 10; + len += scnprintf(&buf[len], bufsz - len, " %4u.%1u", + tp / 10, tp % 10); + + eprob = ((rate_stats->probability * 1000) >> 16) + 1; + len += scnprintf(&buf[len],bufsz - len, + " %4u.%1u %5u(%6u) %6u\n", + eprob / 10, eprob % 10, + rate_stats->success, + rate_stats->attempts, + ul_length); + } + + len += scnprintf(&buf[len], bufsz - len, "\n MPDUs AMPDUs AvLen trialP"); + len += scnprintf(&buf[len], bufsz - len, "\n%6u %6u %3d.%1d %6u\n", + me_rc_stats_cfm.ampdu_len, + me_rc_stats_cfm.ampdu_packets, + me_rc_stats_cfm.avg_ampdu_len >> 16, + ((me_rc_stats_cfm.avg_ampdu_len * 10) >> 16) % 10, + me_rc_stats_cfm.sample_wait); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + kfree(st); + + return read; +} + +DEBUGFS_READ_FILE_OPS(rc_stats); + +static ssize_t ecrnx_dbgfs_rc_fixed_rate_idx_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char buf[10]; + int fixed_rate_idx = -1; + union ecrnx_rate_ctrl_info rate_config; + int error = 0; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Get the content of the file */ + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + sscanf(buf, "%i\n", &fixed_rate_idx); + + /* Convert rate index into rate configuration */ + if ((fixed_rate_idx < 0) || (fixed_rate_idx >= (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU))) + { + // disable fixed rate + rate_config.value = (u32)-1; + } + else + { + idx_to_rate_cfg(fixed_rate_idx, &rate_config, NULL); + } + + // Forward the request to the LMAC + if ((error = ecrnx_send_me_rc_set_rate(priv, sta->sta_idx, + (u16)rate_config.value)) != 0) + { + return error; + } + + priv->debugfs.rc_config[sta->sta_idx] = (int)rate_config.value; + return len; +} + +DEBUGFS_WRITE_FILE_OPS(rc_fixed_rate_idx); + +static ssize_t ecrnx_dbgfs_last_rx_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + struct ecrnx_rx_rate_stats *rate_stats; + char *buf; + int bufsz, i, len = 0; + ssize_t read; + unsigned int fmt, pre, bw, nss, mcs, gi, dcm = 0; + struct rx_vector_1 *last_rx; + char hist[] = "##################################################"; + int hist_len = sizeof(hist) - 1; + u8 nrx; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* everything should fit in one call */ + if (*ppos) + return 0; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + rate_stats = &sta->stats.rx_rate; + bufsz = (rate_stats->rate_cnt * ( 50 + hist_len) + 200); + buf = kmalloc(bufsz + 1, GFP_ATOMIC); + if (buf == NULL) + return 0; + + // Get number of RX paths + nrx = (priv->version_cfm.version_phy_1 & MDM_NRX_MASK) >> MDM_NRX_LSB; + + len += scnprintf(buf, bufsz, + "\nRX rate info for %02X:%02X:%02X:%02X:%02X:%02X:\n", + sta->mac_addr[0], sta->mac_addr[1], sta->mac_addr[2], + sta->mac_addr[3], sta->mac_addr[4], sta->mac_addr[5]); + + // Display Statistics + for (i = 0 ; i < rate_stats->size ; i++ ) + { + if (rate_stats->table[i]) { + union ecrnx_rate_ctrl_info rate_config; + int percent = ((/*(u64)*/rate_stats->table[i]) * 1000) / rate_stats->cpt; + int p; + int ru_size; + + idx_to_rate_cfg(i, &rate_config, &ru_size); + len += print_rate_from_cfg(&buf[len], bufsz - len, + rate_config.value, NULL, ru_size); + p = (percent * hist_len) / 1000; + len += scnprintf(&buf[len], bufsz - len, ": %9d(%2d.%1d%%)%.*s\n", + rate_stats->table[i], + percent / 10, percent % 10, p, hist); + } + } + + // Display detailed info of the last received rate + last_rx = &sta->stats.last_rx.rx_vect1; + + len += scnprintf(&buf[len], bufsz - len,"\nLast received rate\n" + " type rate LDPC STBC BEAMFM DCM DOPPLER %s\n", + (nrx > 1) ? "rssi1(dBm) rssi2(dBm)" : "rssi(dBm)"); + + fmt = last_rx->format_mod; + bw = last_rx->ch_bw; + pre = last_rx->pre_type; + if (fmt >= FORMATMOD_HE_SU) { + mcs = last_rx->he.mcs; + nss = last_rx->he.nss; + gi = last_rx->he.gi_type; + if (fmt == FORMATMOD_HE_MU) + bw = last_rx->he.ru_size; + dcm = last_rx->he.dcm; + } else if (fmt == FORMATMOD_VHT) { + mcs = last_rx->vht.mcs; + nss = last_rx->vht.nss; + gi = last_rx->vht.short_gi; + } else if (fmt >= FORMATMOD_HT_MF) { + mcs = last_rx->ht.mcs % 8; + nss = last_rx->ht.mcs / 8;; + gi = last_rx->ht.short_gi; + } else { + BUG_ON((mcs = legrates_lut[last_rx->leg_rate].idx) == -1); + nss = 0; + gi = 0; + } + + len += print_rate(&buf[len], bufsz - len, fmt, nss, mcs, bw, gi, pre, dcm, NULL); + + /* flags for HT/VHT/HE */ + if (fmt >= FORMATMOD_HE_SU) { + len += scnprintf(&buf[len], bufsz - len, " %c %c %c %c %c", + last_rx->he.fec ? 'L' : ' ', + last_rx->he.stbc ? 'S' : ' ', + last_rx->he.beamformed ? 'B' : ' ', + last_rx->he.dcm ? 'D' : ' ', + last_rx->he.doppler ? 'D' : ' '); + } else if (fmt == FORMATMOD_VHT) { + len += scnprintf(&buf[len], bufsz - len, " %c %c %c ", + last_rx->vht.fec ? 'L' : ' ', + last_rx->vht.stbc ? 'S' : ' ', + last_rx->vht.beamformed ? 'B' : ' '); + } else if (fmt >= FORMATMOD_HT_MF) { + len += scnprintf(&buf[len], bufsz - len, " %c %c ", + last_rx->ht.fec ? 'L' : ' ', + last_rx->ht.stbc ? 'S' : ' '); + } else { + len += scnprintf(&buf[len], bufsz - len, " "); + } + if (nrx > 1) { + len += scnprintf(&buf[len], bufsz - len, " %-4d %d\n", + last_rx->rssi1, last_rx->rssi1); + } else { + len += scnprintf(&buf[len], bufsz - len, " %d\n", last_rx->rssi1); + } + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + return read; +} + +static ssize_t ecrnx_dbgfs_last_rx_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Prevent from interrupt preemption as these statistics are updated under + * interrupt */ + spin_lock_bh(&priv->tx_lock); + memset(sta->stats.rx_rate.table, 0, + sta->stats.rx_rate.size * sizeof(sta->stats.rx_rate.table[0])); + sta->stats.rx_rate.cpt = 0; + sta->stats.rx_rate.rate_cnt = 0; + spin_unlock_bh(&priv->tx_lock); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(last_rx); + +#endif /* CONFIG_ECRNX_FULLMAC */ + +/* + * trace helper + */ +void ecrnx_fw_trace_dump(struct ecrnx_hw *ecrnx_hw) +{ +#ifndef CONFIG_ECRNX_ESWIN + /* may be called before ecrnx_dbgfs_register */ + if (ecrnx_hw->plat->enabled && !ecrnx_hw->debugfs.fw_trace.buf.data) { + ecrnx_fw_trace_buf_init(&ecrnx_hw->debugfs.fw_trace.buf, + ecrnx_ipc_fw_trace_desc_get(ecrnx_hw)); + } + + if (!ecrnx_hw->debugfs.fw_trace.buf.data) + return; + + _ecrnx_fw_trace_dump(&ecrnx_hw->debugfs.fw_trace.buf); +#endif +} + +void ecrnx_fw_trace_reset(struct ecrnx_hw *ecrnx_hw) +{ + _ecrnx_fw_trace_reset(&ecrnx_hw->debugfs.fw_trace, true); +} + +void ecrnx_dbgfs_trigger_fw_dump(struct ecrnx_hw *ecrnx_hw, char *reason) +{ + ecrnx_send_dbg_trigger_req(ecrnx_hw, reason); +} + +#ifdef CONFIG_ECRNX_FULLMAC +static void _ecrnx_dbgfs_register_sta(struct ecrnx_debugfs *ecrnx_debugfs, struct ecrnx_sta *sta) +{ + struct ecrnx_hw *ecrnx_hw = container_of(ecrnx_debugfs, struct ecrnx_hw, debugfs); + struct dentry *dir_sta; + char sta_name[18]; + struct dentry *dir_rc; + struct dentry *file; + struct ecrnx_rx_rate_stats *rate_stats = &sta->stats.rx_rate; + int nb_rx_rate = N_CCK + N_OFDM; + struct ecrnx_rc_config_save *rc_cfg, *next; + + if (sta->sta_idx >= NX_REMOTE_STA_MAX) { + scnprintf(sta_name, sizeof(sta_name), "bc_mc"); + } else { + scnprintf(sta_name, sizeof(sta_name), "%pM", sta->mac_addr); + } + + if (!(dir_sta = debugfs_create_dir(sta_name, ecrnx_debugfs->dir_stas))) + goto error; + ecrnx_debugfs->dir_sta[sta->sta_idx] = dir_sta; + + if (!(dir_rc = debugfs_create_dir("rc", ecrnx_debugfs->dir_sta[sta->sta_idx]))) + goto error_after_dir; + + ecrnx_debugfs->dir_rc_sta[sta->sta_idx] = dir_rc; + + file = debugfs_create_file("stats", S_IRUSR, dir_rc, ecrnx_hw, + &ecrnx_dbgfs_rc_stats_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + file = debugfs_create_file("fixed_rate_idx", S_IWUSR , dir_rc, ecrnx_hw, + &ecrnx_dbgfs_rc_fixed_rate_idx_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + file = debugfs_create_file("rx_rate", S_IRUSR | S_IWUSR, dir_rc, ecrnx_hw, + &ecrnx_dbgfs_last_rx_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + if (ecrnx_hw->mod_params->ht_on) + nb_rx_rate += N_HT; + + if (ecrnx_hw->mod_params->vht_on) + nb_rx_rate += N_VHT; + + if (ecrnx_hw->mod_params->he_on) + nb_rx_rate += N_HE_SU + N_HE_MU; + + rate_stats->table = kzalloc(nb_rx_rate * sizeof(rate_stats->table[0]), + GFP_ATOMIC); + if (!rate_stats->table) + goto error_after_dir; + + rate_stats->size = nb_rx_rate; + rate_stats->cpt = 0; + rate_stats->rate_cnt = 0; + + /* By default enable rate contoller */ + ecrnx_debugfs->rc_config[sta->sta_idx] = -1; + + /* Unless we already fix the rate for this station */ + list_for_each_entry_safe(rc_cfg, next, &ecrnx_debugfs->rc_config_save, list) { + if (jiffies_to_msecs(jiffies - rc_cfg->timestamp) > RC_CONFIG_DUR) { + list_del(&rc_cfg->list); + kfree(rc_cfg); + } else if (!memcmp(rc_cfg->mac_addr, sta->mac_addr, ETH_ALEN)) { + ecrnx_debugfs->rc_config[sta->sta_idx] = rc_cfg->rate; + list_del(&rc_cfg->list); + kfree(rc_cfg); + break; + } + } + + if ((ecrnx_debugfs->rc_config[sta->sta_idx] >= 0) && + ecrnx_send_me_rc_set_rate(ecrnx_hw, sta->sta_idx, + (u16)ecrnx_debugfs->rc_config[sta->sta_idx])) + ecrnx_debugfs->rc_config[sta->sta_idx] = -1; + + if (ECRNX_VIF_TYPE(ecrnx_hw->vif_table[sta->vif_idx]) == NL80211_IFTYPE_STATION) + { + /* register the sta */ + struct dentry *dir_twt; + struct dentry *file; + + if (!(dir_twt = debugfs_create_dir("twt", ecrnx_debugfs->dir_sta[sta->sta_idx]))) + goto error_after_dir; + + ecrnx_debugfs->dir_twt_sta[sta->sta_idx] = dir_twt; + + file = debugfs_create_file("request", S_IRUSR | S_IWUSR, dir_twt, ecrnx_hw, + &ecrnx_dbgfs_twt_request_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + file = debugfs_create_file("teardown", S_IRUSR | S_IWUSR, dir_twt, ecrnx_hw, + &ecrnx_dbgfs_twt_teardown_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + sta->twt_ind.sta_idx = ECRNX_INVALID_STA; + } + return; + + error_after_dir: + debugfs_remove_recursive(ecrnx_debugfs->dir_sta[sta->sta_idx]); + ecrnx_debugfs->dir_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_rc_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_twt_sta[sta->sta_idx] = NULL; + error: + dev_err(ecrnx_hw->dev, + "Error while registering debug entry for sta %d\n", sta->sta_idx); +} + +static void _ecrnx_dbgfs_unregister_sta(struct ecrnx_debugfs *ecrnx_debugfs, struct ecrnx_sta *sta) +{ + debugfs_remove_recursive(ecrnx_debugfs->dir_sta[sta->sta_idx]); + /* unregister the sta */ + if (sta->stats.rx_rate.table) { + kfree(sta->stats.rx_rate.table); + sta->stats.rx_rate.table = NULL; + } + sta->stats.rx_rate.size = 0; + sta->stats.rx_rate.cpt = 0; + sta->stats.rx_rate.rate_cnt = 0; + + /* If fix rate was set for this station, save the configuration in case + we reconnect to this station within RC_CONFIG_DUR msec */ + if (ecrnx_debugfs->rc_config[sta->sta_idx] >= 0) { + struct ecrnx_rc_config_save *rc_cfg; + rc_cfg = kmalloc(sizeof(*rc_cfg), GFP_ATOMIC); + if (rc_cfg) { + rc_cfg->rate = ecrnx_debugfs->rc_config[sta->sta_idx]; + rc_cfg->timestamp = jiffies; + memcpy(rc_cfg->mac_addr, sta->mac_addr, ETH_ALEN); + list_add_tail(&rc_cfg->list, &ecrnx_debugfs->rc_config_save); + } + } + + ecrnx_debugfs->dir_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_rc_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_twt_sta[sta->sta_idx] = NULL; + sta->twt_ind.sta_idx = ECRNX_INVALID_STA; +} + +static void ecrnx_sta_work(struct work_struct *ws) +{ + struct ecrnx_debugfs *ecrnx_debugfs = container_of(ws, struct ecrnx_debugfs, sta_work); + struct ecrnx_hw *ecrnx_hw = container_of(ecrnx_debugfs, struct ecrnx_hw, debugfs); + struct ecrnx_sta *sta; + uint8_t sta_idx; + + sta_idx = ecrnx_debugfs->sta_idx; + if (sta_idx > (NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)) { + WARN(1, "Invalid sta index %d", sta_idx); + return; + } + + ecrnx_debugfs->sta_idx = ECRNX_INVALID_STA; + sta = &ecrnx_hw->sta_table[sta_idx]; + if (!sta) { + WARN(1, "Invalid sta %d", sta_idx); + return; + } + + if (ecrnx_debugfs->dir_sta[sta_idx] == NULL) + _ecrnx_dbgfs_register_sta(ecrnx_debugfs, sta); + else + _ecrnx_dbgfs_unregister_sta(ecrnx_debugfs, sta); + + return; +} + +void _ecrnx_dbgfs_sta_write(struct ecrnx_debugfs *ecrnx_debugfs, uint8_t sta_idx) +{ + if (ecrnx_debugfs->unregistering) + return; + + ecrnx_debugfs->sta_idx = sta_idx; + schedule_work(&ecrnx_debugfs->sta_work); +} + +void ecrnx_dbgfs_unregister_sta(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta) +{ + _ecrnx_dbgfs_sta_write(&ecrnx_hw->debugfs, sta->sta_idx); +} + +void ecrnx_dbgfs_register_sta(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta) +{ + _ecrnx_dbgfs_sta_write(&ecrnx_hw->debugfs, sta->sta_idx); +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +int ecrnx_dbgfs_register(struct ecrnx_hw *ecrnx_hw, const char *name) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct dentry *phyd = ecrnx_hw->hw->wiphy->debugfsdir; +#else + struct dentry *phyd = ecrnx_hw->wiphy->debugfsdir; +#endif /* CONFIG_ECRNX_SOFTMAC */ + struct ecrnx_debugfs *ecrnx_debugfs = &ecrnx_hw->debugfs; + struct dentry *dir_drv, *dir_diags, *dir_stas; + + if (!(dir_drv = debugfs_create_dir(name, phyd))) + return -ENOMEM; + + ecrnx_debugfs->dir = dir_drv; + + if (!(dir_stas = debugfs_create_dir("stations", dir_drv))) + return -ENOMEM; + + ecrnx_debugfs->dir_stas = dir_stas; + ecrnx_debugfs->unregistering = false; + + if (!(dir_diags = debugfs_create_dir("diags", dir_drv))) + goto err; + +#ifdef CONFIG_ECRNX_FULLMAC + INIT_WORK(&ecrnx_debugfs->sta_work, ecrnx_sta_work); + INIT_LIST_HEAD(&ecrnx_debugfs->rc_config_save); + ecrnx_debugfs->sta_idx = ECRNX_INVALID_STA; +#endif + + DEBUGFS_ADD_U32(tcp_pacing_shift, dir_drv, &ecrnx_hw->tcp_pacing_shift, + S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(stats, dir_drv, S_IWUSR | S_IRUSR); +#if 0 + DEBUGFS_ADD_FILE(sys_stats, dir_drv, S_IRUSR); +#endif +#ifdef CONFIG_ECRNX_SOFTMAC + DEBUGFS_ADD_X64(rateidx, dir_drv, &ecrnx_hw->debugfs.rateidx); +#endif + DEBUGFS_ADD_FILE(txq, dir_drv, S_IRUSR); + DEBUGFS_ADD_FILE(acsinfo, dir_drv, S_IRUSR); +#ifdef CONFIG_ECRNX_MUMIMO_TX + DEBUGFS_ADD_FILE(mu_group, dir_drv, S_IRUSR); +#endif + +#ifdef CONFIG_ECRNX_P2P_DEBUGFS + { + /* Create a p2p directory */ + struct dentry *dir_p2p; + if (!(dir_p2p = debugfs_create_dir("p2p", dir_drv))) + goto err; + + /* Add file allowing to control Opportunistic PS */ + DEBUGFS_ADD_FILE(oppps, dir_p2p, S_IRUSR); + /* Add file allowing to control Notice of Absence */ + DEBUGFS_ADD_FILE(noa, dir_p2p, S_IRUSR); + } +#endif /* CONFIG_ECRNX_P2P_DEBUGFS */ + +#if CONFIG_ECRNX_DBGFS_FW_TRACE + if (ecrnx_dbgfs_register_fw_dump(ecrnx_hw, dir_drv, dir_diags)) + goto err; + DEBUGFS_ADD_FILE(fw_dbg, dir_diags, S_IWUSR | S_IRUSR); + + if (!ecrnx_fw_trace_init(&ecrnx_hw->debugfs.fw_trace, + ecrnx_ipc_fw_trace_desc_get(ecrnx_hw))) { + DEBUGFS_ADD_FILE(fw_trace, dir_diags, S_IWUSR | S_IRUSR); + if (ecrnx_hw->debugfs.fw_trace.buf.nb_compo) + DEBUGFS_ADD_FILE(fw_trace_level, dir_diags, S_IWUSR | S_IRUSR); + } else { + ecrnx_debugfs->fw_trace.buf.data = NULL; + } +#endif + +#ifdef CONFIG_ECRNX_RADAR + { + struct dentry *dir_radar, *dir_sec; + if (!(dir_radar = debugfs_create_dir("radar", dir_drv))) + goto err; + + DEBUGFS_ADD_FILE(pulses_prim, dir_radar, S_IRUSR); + DEBUGFS_ADD_FILE(detected, dir_radar, S_IRUSR); + DEBUGFS_ADD_FILE(enable, dir_radar, S_IRUSR); + + if (ecrnx_hw->phy.cnt == 2) { + DEBUGFS_ADD_FILE(pulses_sec, dir_radar, S_IRUSR); + + if (!(dir_sec = debugfs_create_dir("sec", dir_radar))) + goto err; + + DEBUGFS_ADD_FILE(band, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(type, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(prim20, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(center1, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(center2, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(set, dir_sec, S_IWUSR | S_IRUSR); + } + } +#endif /* CONFIG_ECRNX_RADAR */ + return 0; + +err: + ecrnx_dbgfs_unregister(ecrnx_hw); + return -ENOMEM; +} + +void ecrnx_dbgfs_unregister(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_debugfs *ecrnx_debugfs = &ecrnx_hw->debugfs; + +#ifdef CONFIG_ECRNX_FULLMAC + struct ecrnx_rc_config_save *cfg, *next; + list_for_each_entry_safe(cfg, next, &ecrnx_debugfs->rc_config_save, list) { + list_del(&cfg->list); + kfree(cfg); + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + if (!ecrnx_hw->debugfs.dir) + return; + + spin_lock_bh(&ecrnx_debugfs->umh_lock); + ecrnx_debugfs->unregistering = true; + spin_unlock_bh(&ecrnx_debugfs->umh_lock); + ecrnx_wait_um_helper(ecrnx_hw); +#if CONFIG_ECRNX_DBGFS_FW_TRACE + ecrnx_fw_trace_deinit(&ecrnx_hw->debugfs.fw_trace); +#endif +#ifdef CONFIG_ECRNX_FULLMAC + flush_work(&ecrnx_debugfs->sta_work); +#endif + debugfs_remove_recursive(ecrnx_hw->debugfs.dir); + ecrnx_hw->debugfs.dir = NULL; +} + |