Update to 3.4-final.
[linux-flexiantxendom0-3.2.10.git] / drivers / xen / sfc_netback / accel_fwd.c
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  */
24
25 #include "accel.h"
26 #include "accel_cuckoo_hash.h"
27 #include "accel_util.h"
28 #include "accel_solarflare.h"
29
30 #include "driverlink_api.h"
31
32 #include <linux/if_arp.h>
33 #include <linux/skbuff.h>
34 #include <linux/list.h>
35
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 };
43
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)
48
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 };
63
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;
70
71
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;
76
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);
86
87         return -ENOMEM;
88 }
89
90
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);
97
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         }
105
106         return NULL;
107 }
108
109
110 /* Initialise each nic port's fowarding table */
111 void *netback_accel_init_fwd_port(void) 
112 {       
113         struct port_fwd *fwd_set;
114
115         fwd_set = kzalloc(sizeof(struct port_fwd), GFP_KERNEL);
116         if (fwd_set == NULL) {
117                 return NULL;
118         }
119
120         spin_lock_init(&fwd_set->fwd_lock);
121         
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         }
128         
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         }
134         
135         INIT_LIST_HEAD(&fwd_set->fwd_list);
136         
137         list_add(&fwd_set->link, &port_fwds);
138
139         return fwd_set;
140 }
141
142
143 void netback_accel_shutdown_fwd_port(void *fwd_priv)
144 {
145         struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
146
147         BUG_ON(fwd_priv == NULL);
148         
149         BUG_ON(list_empty(&port_fwds));
150         list_del(&fwd_set->link);
151
152         BUG_ON(!list_empty(&fwd_set->fwd_list));
153
154         cuckoo_hash_destroy(&fwd_set->fwd_hash_table);
155         kfree(fwd_set->fwd_array);
156         kfree(fwd_set);
157 }
158
159
160 int netback_accel_init_fwd()
161 {
162         INIT_LIST_HEAD(&port_fwds);
163         return 0;
164 }
165
166
167 void netback_accel_shutdown_fwd()
168 {
169         BUG_ON(!list_empty(&port_fwds));
170 }
171
172
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;
184
185         BUG_ON(fwd_priv == NULL);
186
187         DPRINTK("Adding mac %pM\n", mac);
188        
189         spin_lock_irqsave(&fwd_set->fwd_lock, flags);
190         
191         if ((rc = fwd_find_free_entry(fwd_set)) < 0 ) {
192                 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
193                 return rc;
194         }
195
196         index = rc;
197
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);
203                 return -EEXIST;
204         }
205
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];
209                 fwd->valid = 1;
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++);
214         }
215
216         spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
217
218         /*
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
223          * bridge)
224          */
225
226         return rc;
227 }
228
229
230 /* remove an entry from the forwarding tables. */
231 void netback_accel_fwd_remove(const __u8 *mac, void *fwd_priv)
232 {
233         struct fwd_struct *fwd;
234         unsigned long flags;
235         cuckoo_hash_mac_key key = cuckoo_mac_to_key(mac);
236         struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
237
238         DPRINTK("Removing mac %pM\n", mac);
239
240         BUG_ON(fwd_priv == NULL);
241
242         spin_lock_irqsave(&fwd_set->fwd_lock, flags);
243
244         fwd = fwd_find_entry(mac, fwd_set);
245         if (fwd != NULL) {
246                 BUG_ON(list_empty(&fwd_set->fwd_list));
247                 list_del(&fwd->link);
248
249                 fwd->valid = 0;
250                 cuckoo_hash_remove(&fwd_set->fwd_hash_table, 
251                                    (cuckoo_hash_key *)(&key));
252                 NETBACK_ACCEL_STATS_OP(global_stats.num_fwds--);
253         }
254         spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
255
256         /*
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
260          */
261 }
262
263
264 /* Set the context pointer for a hash table entry. */
265 int netback_accel_fwd_set_context(const __u8 *mac, void *context, 
266                                   void *fwd_priv)
267 {
268         struct fwd_struct *fwd;
269         unsigned long flags;
270         int rc = -ENOENT;
271         struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
272
273         BUG_ON(fwd_priv == NULL);
274
275         spin_lock_irqsave(&fwd_set->fwd_lock, flags);
276         fwd = fwd_find_entry(mac, fwd_set);
277         if (fwd != NULL) {
278                 fwd->context = context;
279                 rc = 0;
280         }
281         spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
282         return rc;
283 }
284
285
286 /**************************************************************************
287  * Process a received packet
288  **************************************************************************/
289
290 /*
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
293  */
294 static struct netback_accel *for_a_vnic(struct netback_pkt_buf *skb, 
295                                         struct port_fwd *fwd_set)
296 {
297         struct fwd_struct *fwd;
298         struct netback_accel *retval = NULL;
299
300         fwd = fwd_find_entry(skb->mac.raw, fwd_set);
301         if (fwd != NULL)
302                 retval = fwd->context;
303         return retval;
304 }
305
306
307 static inline int packet_is_arp_reply(struct sk_buff *skb)
308 {
309         return skb->protocol == ntohs(ETH_P_ARP) 
310                 && arp_hdr(skb)->ar_op == ntohs(ARPOP_REPLY);
311 }
312
313
314 static inline void hdr_to_filt(struct ethhdr *ethhdr, struct iphdr *ip,
315                                struct netback_accel_filter_spec *spec)
316 {
317         spec->proto = ip->protocol;
318         spec->destip_be = ip->daddr;
319         memcpy(spec->mac, ethhdr->h_source, ETH_ALEN);
320
321         if (ip->protocol == IPPROTO_TCP) {
322                 struct tcphdr *tcp = (struct tcphdr *)((char *)ip + 4 * ip->ihl);
323                 spec->destport_be = tcp->dest;
324         } else {
325                 struct udphdr *udp = (struct udphdr *)((char *)ip + 4 * ip->ihl);
326                 EPRINTK_ON(ip->protocol != IPPROTO_UDP);
327                 spec->destport_be = udp->dest;
328         }
329 }
330
331
332 static inline int netback_accel_can_filter(struct netback_pkt_buf *skb) 
333 {
334         return (skb->protocol == htons(ETH_P_IP) && 
335                 ((skb->nh.iph->protocol == IPPROTO_TCP) ||
336                  (skb->nh.iph->protocol == IPPROTO_UDP)));
337 }
338
339
340 static inline void netback_accel_filter_packet(struct netback_accel *bend,
341                                                struct netback_pkt_buf *skb)
342 {
343         struct netback_accel_filter_spec fs;
344         struct ethhdr *eh = (struct ethhdr *)(skb->mac.raw);
345
346         hdr_to_filt(eh, skb->nh.iph, &fs);
347         
348         netback_accel_filter_check_add(bend, &fs);
349 }
350
351
352 /*
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
356  */
357 void netback_accel_rx_packet(struct netback_pkt_buf *skb, void *fwd_priv)
358 {
359         struct netback_accel *bend;
360         struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
361         unsigned long flags;
362
363         BUG_ON(fwd_priv == NULL);
364
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 */
368                 return;
369         } else if (is_multicast_ether_addr(skb->mac.raw)) {
370                 /* pass through the slow path by not claiming ownership */
371                 return;
372         } else {
373                 /* It is unicast */
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);
380         }
381         return;
382 }
383
384
385 void netback_accel_tx_packet(struct sk_buff *skb, void *fwd_priv) 
386 {
387         __u8 *mac;
388         unsigned long flags;
389         struct port_fwd *fwd_set = (struct port_fwd *)fwd_priv;
390         struct fwd_struct *fwd;
391
392         BUG_ON(fwd_priv == NULL);
393
394         if (is_broadcast_ether_addr(skb_mac_header(skb))
395             && packet_is_arp_reply(skb)) {
396                 /*
397                  * update our fast path forwarding to reflect this
398                  * gratuitous ARP
399                  */ 
400                 mac = skb_mac_header(skb)+ETH_ALEN;
401
402                 DPRINTK("%s: found gratuitous ARP for %pM\n",
403                         __FUNCTION__, mac);
404
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                 }
416
417                 spin_unlock_irqrestore(&fwd_set->fwd_lock, flags);
418         }
419         return;
420 }