diff options
author | sjur.brandeland@stericsson.com <sjur.brandeland@stericsson.com> | 2011-12-04 06:22:54 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2011-12-05 18:27:56 -0500 |
commit | 0e4c7d85d5e522d5839bdc5745235428faf38d44 (patch) | |
tree | 99506bac6e5cbefffc2358a5c85c76950b265efe /net/caif | |
parent | 7ad65bf68d705b445ef10b77ab50dab22be185ee (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.c | 85 |
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 | ||
39 | struct caif_device_entry_list { | 42 | struct caif_device_entry_list { |
@@ -48,6 +51,7 @@ struct caif_net { | |||
48 | }; | 51 | }; |
49 | 52 | ||
50 | static int caif_net_id; | 53 | static int caif_net_id; |
54 | static int q_high = 50; /* Percent */ | ||
51 | 55 | ||
52 | struct cfcnfg *get_cfcnfg(struct net *net) | 56 | struct 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 | ||
133 | void 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 | |||
129 | static int transmit(struct cflayer *layer, struct cfpkt *pkt) | 158 | static 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); | ||
219 | noxoff: | ||
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 | ||