@@ -640,6 +640,7 @@ static void ath9k_hif_usb_rx_cb(struct urb *urb)
struct hif_device_usb *hif_dev =
usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
int ret;
+ int delay;
if (!skb)
return;
@@ -658,7 +659,6 @@ static void ath9k_hif_usb_rx_cb(struct urb *urb)
default:
goto resubmit;
}
-
if (likely(urb->actual_length != 0)) {
skb_put(skb, urb->actual_length);
ath9k_hif_usb_rx_stream(hif_dev, skb);
@@ -667,12 +667,18 @@ static void ath9k_hif_usb_rx_cb(struct urb *urb)
resubmit:
skb_reset_tail_pointer(skb);
skb_trim(skb, 0);
-
- usb_anchor_urb(urb, &hif_dev->rx_submitted);
- ret = usb_submit_urb(urb, GFP_ATOMIC);
- if (ret) {
- usb_unanchor_urb(urb);
- goto free;
+ if (atomic_read(&hif_dev->rx_urb_submit_delay) > 0) {
+ usb_anchor_urb(urb, &hif_dev->rx_delayed_submitted);
+ delay = atomic_read(&hif_dev->rx_urb_submit_delay);
+ schedule_delayed_work(&hif_dev->rx_submit_delayed_work,
+ usecs_to_jiffies(delay));
+ } else {
+ usb_anchor_urb(urb, &hif_dev->rx_submitted);
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret) {
+ usb_unanchor_urb(urb);
+ goto free;
+ }
}
return;
@@ -818,9 +824,43 @@ err:
return -ENOMEM;
}
+static void rx_urb_delayed_submit_handler(struct work_struct *work)
+{
+ struct hif_device_usb *hif_dev =
+ container_of(work,
+ struct hif_device_usb,
+ rx_submit_delayed_work.work);
+
+ struct urb *urb = NULL;
+ struct sk_buff *skb = NULL;
+ int ret;
+ int loop_times = 0;
+
+ while (true) {
+ loop_times++;
+ if (loop_times > (MAX_RX_URB_NUM))
+ atomic_add(ARC_RX_STEP, &hif_dev->rx_urb_submit_delay);
+
+ urb = usb_get_from_anchor(&hif_dev->rx_delayed_submitted);
+ if (urb) {
+ skb = (struct sk_buff *)urb->context;
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret != 0) {
+ usb_unanchor_urb(urb);
+ dev_kfree_skb_any(skb);
+ urb->context = NULL;
+ }
+ } else {
+ break;
+ }
+ }
+}
+
static void ath9k_hif_usb_dealloc_rx_urbs(struct hif_device_usb *hif_dev)
{
usb_kill_anchored_urbs(&hif_dev->rx_submitted);
+ usb_kill_anchored_urbs(&hif_dev->rx_delayed_submitted);
+ flush_delayed_work(&hif_dev->rx_submit_delayed_work);
}
static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
@@ -830,6 +870,8 @@ static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
int i, ret;
init_usb_anchor(&hif_dev->rx_submitted);
+ init_usb_anchor(&hif_dev->rx_delayed_submitted);
+
spin_lock_init(&hif_dev->rx_lock);
for (i = 0; i < MAX_RX_URB_NUM; i++) {
@@ -871,6 +913,11 @@ static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
usb_free_urb(urb);
}
+ /* add for adaptive rate control*/
+ atomic_set(&hif_dev->rx_urb_submit_delay, 0);
+ INIT_DELAYED_WORK(&hif_dev->rx_submit_delayed_work,
+ rx_urb_delayed_submit_handler);
+
return 0;
err_submit:
@@ -41,6 +41,7 @@
#define MAX_RX_URB_NUM 8
#define MAX_RX_BUF_SIZE 16384
#define MAX_PKT_NUM_IN_TRANSFER 10
+#define ARC_RX_STEP 100 /* 100us */
#define MAX_REG_OUT_URB_NUM 1
#define MAX_REG_IN_URB_NUM 64
@@ -98,9 +99,14 @@ struct hif_device_usb {
struct hif_usb_tx tx;
struct usb_anchor regout_submitted;
struct usb_anchor rx_submitted;
+ struct usb_anchor rx_delayed_submitted; /* delayed submit anchor */
struct usb_anchor reg_in_submitted;
struct usb_anchor mgmt_submitted;
struct sk_buff *remain_skb;
+
+ struct delayed_work rx_submit_delayed_work;
+ atomic_t rx_urb_submit_delay; /*us*/
+
const char *fw_name;
int rx_remain_len;
int rx_pkt_len;
@@ -331,6 +331,13 @@ static inline struct ath9k_htc_tx_ctl *HTC_SKB_CB(struct sk_buff *skb)
#define TX_QSTAT_INC(q) (priv->debug.tx_stats.queue_stats[q]++)
+#define ARCRX_STAT_INC(c) \
+ (hif_dev->htc_handle->drv_priv->debug.arcrx_stats.c++)
+#define ARCRX_STAT_ADD(c, a) \
+ (hif_dev->htc_handle->drv_priv->debug.arcrx_stats.c += a)
+#define ARCRX_STAT_SET(c, a) \
+ (hif_dev->htc_handle->drv_priv->debug.arcrx_stats.c = a)
+
void ath9k_htc_err_stat_rx(struct ath9k_htc_priv *priv,
struct ath_rx_status *rs);
@@ -352,11 +359,19 @@ struct ath_skbrx_stats {
u32 skb_dropped;
};
+struct ath_arcrx_stats {
+ u32 arcrx_highwater;
+ u32 arcrx_lowwater;
+ u32 arcrx_watermark_triggered;
+ u32 arcrx_urb_submit_delay;
+};
+
struct ath9k_debug {
struct dentry *debugfs_phy;
struct ath_tx_stats tx_stats;
struct ath_rx_stats rx_stats;
struct ath_skbrx_stats skbrx_stats;
+ struct ath_arcrx_stats arcrx_stats;
};
void ath9k_htc_get_et_strings(struct ieee80211_hw *hw,
@@ -377,6 +392,9 @@ void ath9k_htc_get_et_stats(struct ieee80211_hw *hw,
#define TX_QSTAT_INC(c) do { } while (0)
+#define ARCRX_STAT_INC(c) do {} while (0)
+#define ARCRX_STAT_ADD(c, a) do {} while (0)
+#define ARCRX_STAT_SET(c, a) do {} while (0)
static inline void ath9k_htc_err_stat_rx(struct ath9k_htc_priv *priv,
struct ath_rx_status *rs)
{
@@ -286,6 +286,48 @@ static const struct file_operations fops_skb_rx = {
.llseek = default_llseek,
};
+static ssize_t read_file_arc_rx(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct ath9k_htc_priv *priv = file->private_data;
+ char *buf;
+ unsigned int len = 0, size = 1500;
+ ssize_t retval = 0;
+
+ buf = kzalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ len += scnprintf(buf + len, size - len,
+ "%20s : %10u\n", "High watermark",
+ priv->debug.arcrx_stats.arcrx_highwater);
+ len += scnprintf(buf + len, size - len,
+ "%20s : %10u\n", "Low watermark",
+ priv->debug.arcrx_stats.arcrx_lowwater);
+
+ len += scnprintf(buf + len, size - len,
+ "%20s : %10u\n", "WM triggered",
+ priv->debug.arcrx_stats.arcrx_watermark_triggered);
+
+ len += scnprintf(buf + len, size - len,
+ "%20s : %10u\n", "URB delay",
+ priv->debug.arcrx_stats.arcrx_urb_submit_delay);
+ if (len > size)
+ len = size;
+
+ retval = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+ kfree(buf);
+
+ return retval;
+}
+
+static const struct file_operations fops_arc_rx = {
+ .read = read_file_arc_rx,
+ .open = simple_open,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
static ssize_t read_file_slot(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
@@ -518,7 +560,11 @@ int ath9k_htc_init_debug(struct ath_hw *ah)
debugfs_create_file("skb_rx", S_IRUSR, priv->debug.debugfs_phy,
priv, &fops_skb_rx);
+ debugfs_create_file("arc_rx", S_IRUSR, priv->debug.debugfs_phy,
+ priv, &fops_arc_rx);
+
ath9k_cmn_debug_recv(priv->debug.debugfs_phy, &priv->debug.rx_stats);
+
ath9k_cmn_debug_phy_err(priv->debug.debugfs_phy, &priv->debug.rx_stats);
debugfs_create_file("slot", S_IRUSR, priv->debug.debugfs_phy,
@@ -1061,7 +1061,27 @@ void ath9k_rx_tasklet(unsigned long data)
unsigned long flags;
struct ieee80211_hdr *hdr;
+ /* add for adaptive flow control*/
+ int looptimes = 0;
+ int highwatermark = ATH9K_HTC_RXBUF*3/4;
+ int lowwatermark = ATH9K_HTC_RXBUF/32;
+ unsigned int delay = 0;
+
+ struct htc_target *htc = priv->htc;
+ struct hif_device_usb *hif_dev = htc->hif_dev;
+
+ ARCRX_STAT_SET(arcrx_highwater, highwatermark);
+ ARCRX_STAT_SET(arcrx_lowwater, lowwatermark);
+
do {
+ looptimes++;
+ if (looptimes > highwatermark) {
+ delay = looptimes*ARC_RX_STEP;
+ atomic_set(&hif_dev->rx_urb_submit_delay, delay);
+ ARCRX_STAT_INC(arcrx_watermark_triggered);
+ ARCRX_STAT_SET(arcrx_urb_submit_delay, delay);
+ }
+
spin_lock_irqsave(&priv->rx.rxbuflock, flags);
list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) {
if (tmp_buf->in_process) {
@@ -1072,6 +1092,11 @@ void ath9k_rx_tasklet(unsigned long data)
if (rxbuf == NULL) {
spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
+ if (looptimes < lowwatermark) {
+ atomic_set(&hif_dev->rx_urb_submit_delay, 0);
+ ARCRX_STAT_SET(arcrx_urb_submit_delay, 0);
+ }
+
break;
}