1 /****************************************************************************
2 * Solarflare driver for Xen network acceleration
4 * Copyright 2006-2008: Solarflare Communications Inc,
5 * 9501 Jeronimo Road, Suite 250,
6 * Irvine, CA 92618, USA
8 * Maintained by Solarflare Communications <linux-xen-drivers@solarflare.com>
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.
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.
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 ****************************************************************************
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 */
38 struct list_head link; /* Forms list */
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)
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. */
60 /* Make find_free_entry() a bit faster by caching this */
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
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)
75 int index = fwd_set->last_free_index;
78 if (!fwd_set->fwd_array[index].valid) {
79 fwd_set->last_free_index = index;
83 if (index >= NUM_FWDS)
85 } while (index != fwd_set->last_free_index);
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)
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),
101 struct fwd_struct *fwd = &fwd_set->fwd_array[value];
102 DPRINTK_ON(memcmp(fwd->mac, mac, ETH_ALEN) != 0);
110 /* Initialise each nic port's fowarding table */
111 void *netback_accel_init_fwd_port(void)
113 struct port_fwd *fwd_set;
115 fwd_set = kzalloc(sizeof(struct port_fwd), GFP_KERNEL);
116 if (fwd_set == NULL) {
120 spin_lock_init(&fwd_set->fwd_lock);
122 fwd_set->fwd_array = kzalloc(sizeof (struct fwd_struct) * NUM_FWDS,
124 if (fwd_set->fwd_array == NULL) {
129 if (cuckoo_hash_init(&fwd_set->fwd_hash_table, NUM_FWDS_BITS, 8) != 0) {
130 kfree(fwd_set->fwd_array);
135 INIT_LIST_HEAD(&fwd_set->fwd_list);
137 list_add(&fwd_set->link, &port_fwds);
143 void netback_accel_shutdown_fwd_port(void *fwd_priv)
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);
160 int netback_accel_init_fwd()
162 INIT_LIST_HEAD(&port_fwds);
167 void netback_accel_shutdown_fwd()
169 BUG_ON(!list_empty(&port_fwds));
174 * Add an entry to the forwarding table. Returns -ENOMEM if no
177 int netback_accel_fwd_add(const __u8 *mac, void *context, void *fwd_priv)
179 struct fwd_struct *fwd;
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 %pM\n", 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);
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 %pM already accelerated.\n", mac);
206 if ((rc = cuckoo_hash_add(&fwd_set->fwd_hash_table,
207 (cuckoo_hash_key *)(&key), index, 1)) == 0) {
208 fwd = &fwd_set->fwd_array[index];
210 fwd->context = context;
211 memcpy(fwd->mac, mac, ETH_ALEN);
212 list_add(&fwd->link, &fwd_set->fwd_list);
213 NETBACK_ACCEL_STATS_OP(global_stats.num_fwds++);
216 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
219 * No need to tell frontend that this mac address is local -
220 * it should auto-discover through packets on fastpath what is
221 * local and what is not, and just being on same server
222 * doesn't make it local (it could be on a different
230 /* remove an entry from the forwarding tables. */
231 void netback_accel_fwd_remove(const __u8 *mac, void *fwd_priv)
233 struct fwd_struct *fwd;
235 cuckoo_hash_mac_key key = cuckoo_mac_to_key(mac);
236 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
238 DPRINTK("Removing mac %pM\n", mac);
240 BUG_ON(fwd_priv == NULL);
242 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
244 fwd = fwd_find_entry(mac, fwd_set);
246 BUG_ON(list_empty(&fwd_set->fwd_list));
247 list_del(&fwd->link);
250 cuckoo_hash_remove(&fwd_set->fwd_hash_table,
251 (cuckoo_hash_key *)(&key));
252 NETBACK_ACCEL_STATS_OP(global_stats.num_fwds--);
254 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
257 * No need to tell frontend that this is no longer present -
258 * the frontend is currently only interested in remote
259 * addresses and it works these out (mostly) by itself
264 /* Set the context pointer for a hash table entry. */
265 int netback_accel_fwd_set_context(const __u8 *mac, void *context,
268 struct fwd_struct *fwd;
271 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
273 BUG_ON(fwd_priv == NULL);
275 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
276 fwd = fwd_find_entry(mac, fwd_set);
278 fwd->context = context;
281 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
286 /**************************************************************************
287 * Process a received packet
288 **************************************************************************/
291 * Returns whether or not we have a match in our forward table for the
292 * this skb. Must be called with appropriate fwd_lock already held
294 static struct netback_accel *for_a_vnic(struct netback_pkt_buf *skb,
295 struct port_fwd *fwd_set)
297 struct fwd_struct *fwd;
298 struct netback_accel *retval = NULL;
300 fwd = fwd_find_entry(skb->mac.raw, fwd_set);
302 retval = fwd->context;
307 static inline int packet_is_arp_reply(struct sk_buff *skb)
309 return skb->protocol == ntohs(ETH_P_ARP)
310 && arp_hdr(skb)->ar_op == ntohs(ARPOP_REPLY);
314 static inline void hdr_to_filt(struct ethhdr *ethhdr, struct iphdr *ip,
315 struct netback_accel_filter_spec *spec)
317 spec->proto = ip->protocol;
318 spec->destip_be = ip->daddr;
319 memcpy(spec->mac, ethhdr->h_source, ETH_ALEN);
321 if (ip->protocol == IPPROTO_TCP) {
322 struct tcphdr *tcp = (struct tcphdr *)((char *)ip + 4 * ip->ihl);
323 spec->destport_be = tcp->dest;
325 struct udphdr *udp = (struct udphdr *)((char *)ip + 4 * ip->ihl);
326 EPRINTK_ON(ip->protocol != IPPROTO_UDP);
327 spec->destport_be = udp->dest;
332 static inline int netback_accel_can_filter(struct netback_pkt_buf *skb)
334 return (skb->protocol == htons(ETH_P_IP) &&
335 ((skb->nh.iph->protocol == IPPROTO_TCP) ||
336 (skb->nh.iph->protocol == IPPROTO_UDP)));
340 static inline void netback_accel_filter_packet(struct netback_accel *bend,
341 struct netback_pkt_buf *skb)
343 struct netback_accel_filter_spec fs;
344 struct ethhdr *eh = (struct ethhdr *)(skb->mac.raw);
346 hdr_to_filt(eh, skb->nh.iph, &fs);
348 netback_accel_filter_check_add(bend, &fs);
353 * Receive a packet and do something appropriate with it. Return true
354 * to take exclusive ownership of the packet. This is verging on
355 * solarflare specific
357 void netback_accel_rx_packet(struct netback_pkt_buf *skb, void *fwd_priv)
359 struct netback_accel *bend;
360 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
363 BUG_ON(fwd_priv == NULL);
365 /* Checking for bcast is cheaper so do that first */
366 if (is_broadcast_ether_addr(skb->mac.raw)) {
367 /* pass through the slow path by not claiming ownership */
369 } else if (is_multicast_ether_addr(skb->mac.raw)) {
370 /* pass through the slow path by not claiming ownership */
374 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
375 /* We insert filter to pass it off to a VNIC */
376 if ((bend = for_a_vnic(skb, fwd_set)) != NULL)
377 if (netback_accel_can_filter(skb))
378 netback_accel_filter_packet(bend, skb);
379 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
385 void netback_accel_tx_packet(struct sk_buff *skb, void *fwd_priv)
389 struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
390 struct fwd_struct *fwd;
392 BUG_ON(fwd_priv == NULL);
394 if (is_broadcast_ether_addr(skb_mac_header(skb))
395 && packet_is_arp_reply(skb)) {
397 * update our fast path forwarding to reflect this
400 mac = skb_mac_header(skb)+ETH_ALEN;
402 DPRINTK("%s: found gratuitous ARP for %pM\n",
405 spin_lock_irqsave(&fwd_set->fwd_lock, flags);
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
411 list_for_each_entry(fwd, &fwd_set->fwd_list, link) {
412 struct netback_accel *bend = fwd->context;
414 netback_accel_msg_tx_new_localmac(bend, mac);
417 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);