ia64/linux-2.6.18-xen.hg

view drivers/xen/sfc_netback/accel_fwd.c @ 490:14b987774233

netback accel: Avoid BUG_ON when vifs have conflicting MAC addresses

Signed-off-by: Kieran Mansley <kmansley@solarflare.com>
author Keir Fraser <keir.fraser@citrix.com>
date Tue Mar 25 17:54:59 2008 +0000 (2008-03-25)
parents af0d925ba938
children
line source
1 /****************************************************************************
2 * Solarflare driver for Xen network acceleration
3 *
4 * Copyright 2006-2008: Solarflare Communications Inc,
5 * 9501 Jeronimo Road, Suite 250,
6 * Irvine, CA 92618, USA
7 *
8 * Maintained by Solarflare Communications <linux-xen-drivers@solarflare.com>
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License version 2 as published
12 * by the Free Software Foundation, incorporated herein by reference.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 ****************************************************************************
23 */
25 #include "accel.h"
26 #include "accel_cuckoo_hash.h"
27 #include "accel_util.h"
28 #include "accel_solarflare.h"
30 #include "driverlink_api.h"
32 #include <linux/if_arp.h>
33 #include <linux/skbuff.h>
34 #include <linux/list.h>
36 /* State stored in the forward table */
37 struct fwd_struct {
38 struct list_head link; /* Forms list */
39 void * context;
40 __u8 valid;
41 __u8 mac[ETH_ALEN];
42 };
44 /* Max value we support */
45 #define NUM_FWDS_BITS 8
46 #define NUM_FWDS (1 << NUM_FWDS_BITS)
47 #define FWD_MASK (NUM_FWDS - 1)
49 struct port_fwd {
50 /* Make a list */
51 struct list_head link;
52 /* Hash table to store the fwd_structs */
53 cuckoo_hash_table fwd_hash_table;
54 /* The array of fwd_structs */
55 struct fwd_struct *fwd_array;
56 /* Linked list of entries in use. */
57 struct list_head fwd_list;
58 /* Could do something clever with a reader/writer lock. */
59 spinlock_t fwd_lock;
60 /* Make find_free_entry() a bit faster by caching this */
61 int last_free_index;
62 };
64 /*
65 * This is unlocked as it's only called from dl probe and remove,
66 * which are themselves synchronised. Could get rid of it entirely as
67 * it's never iterated, but useful for debug
68 */
69 static struct list_head port_fwds;
72 /* Search the fwd_array for an unused entry */
73 static int fwd_find_free_entry(struct port_fwd *fwd_set)
74 {
75 int index = fwd_set->last_free_index;
77 do {
78 if (!fwd_set->fwd_array[index].valid) {
79 fwd_set->last_free_index = index;
80 return index;
81 }
82 index++;
83 if (index >= NUM_FWDS)
84 index = 0;
85 } while (index != fwd_set->last_free_index);
87 return -ENOMEM;
88 }
91 /* Look up a MAC in the hash table. Caller should hold table lock. */
92 static inline struct fwd_struct *fwd_find_entry(const __u8 *mac,
93 struct port_fwd *fwd_set)
94 {
95 cuckoo_hash_value value;
96 cuckoo_hash_mac_key key = cuckoo_mac_to_key(mac);
98 if (cuckoo_hash_lookup(&fwd_set->fwd_hash_table,
99 (cuckoo_hash_key *)(&key),
100 &value)) {
101 struct fwd_struct *fwd = &fwd_set->fwd_array[value];
102 DPRINTK_ON(memcmp(fwd->mac, mac, ETH_ALEN) != 0);
103 return fwd;
104 }
106 return NULL;
107 }
110 /* Initialise each nic port's fowarding table */
111 void *netback_accel_init_fwd_port(void)
112 {
113 struct port_fwd *fwd_set;
115 fwd_set = kzalloc(sizeof(struct port_fwd), GFP_KERNEL);
116 if (fwd_set == NULL) {
117 return NULL;
118 }
120 spin_lock_init(&fwd_set->fwd_lock);
122 fwd_set->fwd_array = kzalloc(sizeof (struct fwd_struct) * NUM_FWDS,
123 GFP_KERNEL);
124 if (fwd_set->fwd_array == NULL) {
125 kfree(fwd_set);
126 return NULL;
127 }
129 if (cuckoo_hash_init(&fwd_set->fwd_hash_table, NUM_FWDS_BITS, 8) != 0) {
130 kfree(fwd_set->fwd_array);
131 kfree(fwd_set);
132 return NULL;
133 }
135 INIT_LIST_HEAD(&fwd_set->fwd_list);
137 list_add(&fwd_set->link, &port_fwds);
139 return fwd_set;
140 }
143 void netback_accel_shutdown_fwd_port(void *fwd_priv)
144 {
145 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
147 BUG_ON(fwd_priv == NULL);
149 BUG_ON(list_empty(&port_fwds));
150 list_del(&fwd_set->link);
152 BUG_ON(!list_empty(&fwd_set->fwd_list));
154 cuckoo_hash_destroy(&fwd_set->fwd_hash_table);
155 kfree(fwd_set->fwd_array);
156 kfree(fwd_set);
157 }
160 int netback_accel_init_fwd()
161 {
162 INIT_LIST_HEAD(&port_fwds);
163 return 0;
164 }
167 void netback_accel_shutdown_fwd()
168 {
169 BUG_ON(!list_empty(&port_fwds));
170 }
173 /*
174 * Add an entry to the forwarding table. Returns -ENOMEM if no
175 * space.
176 */
177 int netback_accel_fwd_add(const __u8 *mac, void *context, void *fwd_priv)
178 {
179 struct fwd_struct *fwd;
180 int rc = 0, index;
181 unsigned long flags;
182 cuckoo_hash_mac_key key = cuckoo_mac_to_key(mac);
183 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
185 BUG_ON(fwd_priv == NULL);
187 DPRINTK("Adding mac " MAC_FMT "\n", MAC_ARG(mac));
189 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
191 if ((rc = fwd_find_free_entry(fwd_set)) < 0 ) {
192 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
193 return rc;
194 }
196 index = rc;
198 /* Shouldn't already be in the table */
199 if (cuckoo_hash_lookup(&fwd_set->fwd_hash_table,
200 (cuckoo_hash_key *)(&key), &rc) != 0) {
201 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
202 EPRINTK("MAC address " MAC_FMT " already accelerated.\n",
203 MAC_ARG(mac));
204 return -EEXIST;
205 }
207 if ((rc = cuckoo_hash_add(&fwd_set->fwd_hash_table,
208 (cuckoo_hash_key *)(&key), index, 1)) == 0) {
209 fwd = &fwd_set->fwd_array[index];
210 fwd->valid = 1;
211 fwd->context = context;
212 memcpy(fwd->mac, mac, ETH_ALEN);
213 list_add(&fwd->link, &fwd_set->fwd_list);
214 NETBACK_ACCEL_STATS_OP(global_stats.num_fwds++);
215 }
217 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
219 /*
220 * No need to tell frontend that this mac address is local -
221 * it should auto-discover through packets on fastpath what is
222 * local and what is not, and just being on same server
223 * doesn't make it local (it could be on a different
224 * bridge)
225 */
227 return rc;
228 }
231 /* remove an entry from the forwarding tables. */
232 void netback_accel_fwd_remove(const __u8 *mac, void *fwd_priv)
233 {
234 struct fwd_struct *fwd;
235 unsigned long flags;
236 cuckoo_hash_mac_key key = cuckoo_mac_to_key(mac);
237 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
239 DPRINTK("Removing mac " MAC_FMT "\n", MAC_ARG(mac));
241 BUG_ON(fwd_priv == NULL);
243 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
245 fwd = fwd_find_entry(mac, fwd_set);
246 if (fwd != NULL) {
247 BUG_ON(list_empty(&fwd_set->fwd_list));
248 list_del(&fwd->link);
250 fwd->valid = 0;
251 cuckoo_hash_remove(&fwd_set->fwd_hash_table,
252 (cuckoo_hash_key *)(&key));
253 NETBACK_ACCEL_STATS_OP(global_stats.num_fwds--);
254 }
255 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
257 /*
258 * No need to tell frontend that this is no longer present -
259 * the frontend is currently only interested in remote
260 * addresses and it works these out (mostly) by itself
261 */
262 }
265 /* Set the context pointer for a hash table entry. */
266 int netback_accel_fwd_set_context(const __u8 *mac, void *context,
267 void *fwd_priv)
268 {
269 struct fwd_struct *fwd;
270 unsigned long flags;
271 int rc = -ENOENT;
272 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
274 BUG_ON(fwd_priv == NULL);
276 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
277 fwd = fwd_find_entry(mac, fwd_set);
278 if (fwd != NULL) {
279 fwd->context = context;
280 rc = 0;
281 }
282 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
283 return rc;
284 }
287 /**************************************************************************
288 * Process a received packet
289 **************************************************************************/
291 /*
292 * Returns whether or not we have a match in our forward table for the
293 * this skb. Must be called with appropriate fwd_lock already held
294 */
295 static struct netback_accel *for_a_vnic(struct netback_pkt_buf *skb,
296 struct port_fwd *fwd_set)
297 {
298 struct fwd_struct *fwd;
299 struct netback_accel *retval = NULL;
301 fwd = fwd_find_entry(skb->mac.raw, fwd_set);
302 if (fwd != NULL)
303 retval = fwd->context;
304 return retval;
305 }
308 static inline int packet_is_arp_reply(struct sk_buff *skb)
309 {
310 return skb->protocol == ntohs(ETH_P_ARP)
311 && skb->nh.arph->ar_op == ntohs(ARPOP_REPLY);
312 }
315 static inline void hdr_to_filt(struct ethhdr *ethhdr, struct iphdr *ip,
316 struct netback_accel_filter_spec *spec)
317 {
318 spec->proto = ip->protocol;
319 spec->destip_be = ip->daddr;
320 memcpy(spec->mac, ethhdr->h_source, ETH_ALEN);
322 if (ip->protocol == IPPROTO_TCP) {
323 struct tcphdr *tcp = (struct tcphdr *)((char *)ip + 4 * ip->ihl);
324 spec->destport_be = tcp->dest;
325 } else {
326 struct udphdr *udp = (struct udphdr *)((char *)ip + 4 * ip->ihl);
327 EPRINTK_ON(ip->protocol != IPPROTO_UDP);
328 spec->destport_be = udp->dest;
329 }
330 }
333 static inline int netback_accel_can_filter(struct netback_pkt_buf *skb)
334 {
335 return (skb->protocol == htons(ETH_P_IP) &&
336 ((skb->nh.iph->protocol == IPPROTO_TCP) ||
337 (skb->nh.iph->protocol == IPPROTO_UDP)));
338 }
341 static inline void netback_accel_filter_packet(struct netback_accel *bend,
342 struct netback_pkt_buf *skb)
343 {
344 struct netback_accel_filter_spec fs;
345 struct ethhdr *eh = (struct ethhdr *)(skb->mac.raw);
347 hdr_to_filt(eh, skb->nh.iph, &fs);
349 netback_accel_filter_check_add(bend, &fs);
350 }
353 /*
354 * Receive a packet and do something appropriate with it. Return true
355 * to take exclusive ownership of the packet. This is verging on
356 * solarflare specific
357 */
358 void netback_accel_rx_packet(struct netback_pkt_buf *skb, void *fwd_priv)
359 {
360 struct netback_accel *bend;
361 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
362 unsigned long flags;
364 BUG_ON(fwd_priv == NULL);
366 /* Checking for bcast is cheaper so do that first */
367 if (is_broadcast_ether_addr(skb->mac.raw)) {
368 /* pass through the slow path by not claiming ownership */
369 return;
370 } else if (is_multicast_ether_addr(skb->mac.raw)) {
371 /* pass through the slow path by not claiming ownership */
372 return;
373 } else {
374 /* It is unicast */
375 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
376 /* We insert filter to pass it off to a VNIC */
377 if ((bend = for_a_vnic(skb, fwd_set)) != NULL)
378 if (netback_accel_can_filter(skb))
379 netback_accel_filter_packet(bend, skb);
380 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
381 }
382 return;
383 }
386 void netback_accel_tx_packet(struct sk_buff *skb, void *fwd_priv)
387 {
388 __u8 *mac;
389 unsigned long flags;
390 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
391 struct fwd_struct *fwd;
393 BUG_ON(fwd_priv == NULL);
395 if (is_broadcast_ether_addr(skb->mac.raw) && packet_is_arp_reply(skb)) {
396 /*
397 * update our fast path forwarding to reflect this
398 * gratuitous ARP
399 */
400 mac = skb->mac.raw+ETH_ALEN;
402 DPRINTK("%s: found gratuitous ARP for " MAC_FMT "\n",
403 __FUNCTION__, MAC_ARG(mac));
405 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
406 /*
407 * Might not be local, but let's tell them all it is,
408 * and they can restore the fastpath if they continue
409 * to get packets that way
410 */
411 list_for_each_entry(fwd, &fwd_set->fwd_list, link) {
412 struct netback_accel *bend = fwd->context;
413 if (bend != NULL)
414 netback_accel_msg_tx_new_localmac(bend, mac);
415 }
417 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
418 }
419 return;
420 }