aboutsummaryrefslogtreecommitdiffstats
path: root/net/caif
diff options
context:
space:
mode:
authorsjur.brandeland@stericsson.com <sjur.brandeland@stericsson.com>2011-12-04 06:22:54 -0500
committerDavid S. Miller <davem@davemloft.net>2011-12-05 18:27:56 -0500
commit0e4c7d85d5e522d5839bdc5745235428faf38d44 (patch)
tree99506bac6e5cbefffc2358a5c85c76950b265efe /net/caif
parent7ad65bf68d705b445ef10b77ab50dab22be185ee (diff)
caif: Add support for flow-control on device's tx-queue
Flow control is implemented by inspecting the qdisc queue length in order to detect potential overflow on the TX queue. When a threshold is reached flow-off is sent upwards in the CAIF stack. At the same time the skb->destructor is hi-jacked by orphaning the SKB and the original destructor is replaced with a "flow-on" callback. When the "hi-jacked" SKB is consumed the queue should be empty, and the "flow-on" callback is called and xon is sent upwards in the CAIF stack. Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/caif')
-rw-r--r--net/caif/caif_dev.c85
1 files changed, 84 insertions, 1 deletions
diff --git a/net/caif/caif_dev.c b/net/caif/caif_dev.c
index 6acec1921e7f..74c12734db74 100644
--- a/net/caif/caif_dev.c
+++ b/net/caif/caif_dev.c
@@ -17,6 +17,7 @@
17#include <linux/netdevice.h> 17#include <linux/netdevice.h>
18#include <linux/mutex.h> 18#include <linux/mutex.h>
19#include <linux/module.h> 19#include <linux/module.h>
20#include <linux/spinlock.h>
20#include <net/netns/generic.h> 21#include <net/netns/generic.h>
21#include <net/net_namespace.h> 22#include <net/net_namespace.h>
22#include <net/pkt_sched.h> 23#include <net/pkt_sched.h>
@@ -34,6 +35,8 @@ struct caif_device_entry {
34 struct list_head list; 35 struct list_head list;
35 struct net_device *netdev; 36 struct net_device *netdev;
36 int __percpu *pcpu_refcnt; 37 int __percpu *pcpu_refcnt;
38 spinlock_t flow_lock;
39 bool xoff;
37}; 40};
38 41
39struct caif_device_entry_list { 42struct caif_device_entry_list {
@@ -48,6 +51,7 @@ struct caif_net {
48}; 51};
49 52
50static int caif_net_id; 53static int caif_net_id;
54static int q_high = 50; /* Percent */
51 55
52struct cfcnfg *get_cfcnfg(struct net *net) 56struct cfcnfg *get_cfcnfg(struct net *net)
53{ 57{
@@ -126,17 +130,94 @@ static struct caif_device_entry *caif_get(struct net_device *dev)
126 return NULL; 130 return NULL;
127} 131}
128 132
133void caif_flow_cb(struct sk_buff *skb)
134{
135 struct caif_device_entry *caifd;
136 bool send_xoff;
137
138 WARN_ON(skb->dev == NULL);
139
140 rcu_read_lock();
141 caifd = caif_get(skb->dev);
142 caifd_hold(caifd);
143 rcu_read_unlock();
144
145 spin_lock_bh(&caifd->flow_lock);
146 send_xoff = caifd->xoff;
147 caifd->xoff = 0;
148 spin_unlock_bh(&caifd->flow_lock);
149
150 if (send_xoff)
151 caifd->layer.up->
152 ctrlcmd(caifd->layer.up,
153 _CAIF_CTRLCMD_PHYIF_FLOW_ON_IND,
154 caifd->layer.id);
155 caifd_put(caifd);
156}
157
129static int transmit(struct cflayer *layer, struct cfpkt *pkt) 158static int transmit(struct cflayer *layer, struct cfpkt *pkt)
130{ 159{
131 int err; 160 int err, high = 0, qlen = 0;
161 struct caif_dev_common *caifdev;
132 struct caif_device_entry *caifd = 162 struct caif_device_entry *caifd =
133 container_of(layer, struct caif_device_entry, layer); 163 container_of(layer, struct caif_device_entry, layer);
134 struct sk_buff *skb; 164 struct sk_buff *skb;
165 struct netdev_queue *txq;
166
167 rcu_read_lock_bh();
135 168
136 skb = cfpkt_tonative(pkt); 169 skb = cfpkt_tonative(pkt);
137 skb->dev = caifd->netdev; 170 skb->dev = caifd->netdev;
138 skb_reset_network_header(skb); 171 skb_reset_network_header(skb);
139 skb->protocol = htons(ETH_P_CAIF); 172 skb->protocol = htons(ETH_P_CAIF);
173 caifdev = netdev_priv(caifd->netdev);
174
175 /* Check if we need to handle xoff */
176 if (likely(caifd->netdev->tx_queue_len == 0))
177 goto noxoff;
178
179 if (unlikely(caifd->xoff))
180 goto noxoff;
181
182 if (likely(!netif_queue_stopped(caifd->netdev))) {
183 /* If we run with a TX queue, check if the queue is too long*/
184 txq = netdev_get_tx_queue(skb->dev, 0);
185 qlen = qdisc_qlen(rcu_dereference_bh(txq->qdisc));
186
187 if (likely(qlen == 0))
188 goto noxoff;
189
190 high = (caifd->netdev->tx_queue_len * q_high) / 100;
191 if (likely(qlen < high))
192 goto noxoff;
193 }
194
195 /* Hold lock while accessing xoff */
196 spin_lock_bh(&caifd->flow_lock);
197 if (caifd->xoff) {
198 spin_unlock_bh(&caifd->flow_lock);
199 goto noxoff;
200 }
201
202 /*
203 * Handle flow off, we do this by temporary hi-jacking this
204 * skb's destructor function, and replace it with our own
205 * flow-on callback. The callback will set flow-on and call
206 * the original destructor.
207 */
208
209 pr_debug("queue has stopped(%d) or is full (%d > %d)\n",
210 netif_queue_stopped(caifd->netdev),
211 qlen, high);
212 caifd->xoff = 1;
213 spin_unlock_bh(&caifd->flow_lock);
214 skb_orphan(skb);
215
216 caifd->layer.up->ctrlcmd(caifd->layer.up,
217 _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND,
218 caifd->layer.id);
219noxoff:
220 rcu_read_unlock_bh();
140 221
141 err = dev_queue_xmit(skb); 222 err = dev_queue_xmit(skb);
142 if (err > 0) 223 if (err > 0)
@@ -232,6 +313,7 @@ void caif_enroll_dev(struct net_device *dev, struct caif_dev_common *caifdev,
232 if (!caifd) 313 if (!caifd)
233 return; 314 return;
234 *layer = &caifd->layer; 315 *layer = &caifd->layer;
316 spin_lock_init(&caifd->flow_lock);
235 317
236 switch (caifdev->link_select) { 318 switch (caifdev->link_select) {
237 case CAIF_LINK_HIGH_BANDW: 319 case CAIF_LINK_HIGH_BANDW:
@@ -316,6 +398,7 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what,
316 break; 398 break;
317 } 399 }
318 400
401 caifd->xoff = 0;
319 cfcnfg_set_phy_state(cfg, &caifd->layer, true); 402 cfcnfg_set_phy_state(cfg, &caifd->layer, true);
320 rcu_read_unlock(); 403 rcu_read_unlock();
321 404