en_frag.c revision 219820
1/*
2 * Copyright (c) 2007 Mellanox Technologies. All rights reserved.
3 *
4 * This software is available to you under a choice of one of two
5 * licenses.  You may choose to be licensed under the terms of the GNU
6 * General Public License (GPL) Version 2, available from the file
7 * COPYING in the main directory of this source tree, or the
8 * OpenIB.org BSD license below:
9 *
10 *     Redistribution and use in source and binary forms, with or
11 *     without modification, are permitted provided that the following
12 *     conditions are met:
13 *
14 *      - Redistributions of source code must retain the above
15 *        copyright notice, this list of conditions and the following
16 *        disclaimer.
17 *
18 *      - Redistributions in binary form must reproduce the above
19 *        copyright notice, this list of conditions and the following
20 *        disclaimer in the documentation and/or other materials
21 *        provided with the distribution.
22 *
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
27 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
28 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 * SOFTWARE.
31 *
32 */
33
34#include "mlx4_en.h"
35
36#include <net/ethernet.h>
37#include <netinet/ip.h>
38#include <machine/in_cksum.h>
39
40static struct mlx4_en_ipfrag *find_session(struct mlx4_en_rx_ring *ring,
41					   struct ip *iph)
42{
43	struct mlx4_en_ipfrag *session;
44	int i;
45
46	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
47		session = &ring->ipfrag[i];
48		if (session->fragments == NULL)
49			continue;
50		if (session->daddr == iph->ip_dst.s_addr &&
51		    session->saddr == iph->ip_src.s_addr &&
52		    session->id == iph->ip_id &&
53		    session->protocol == iph->ip_p) {
54			return session;
55		}
56	}
57	return NULL;
58}
59
60static struct mlx4_en_ipfrag *start_session(struct mlx4_en_rx_ring *ring,
61					    struct ip *iph)
62{
63	struct mlx4_en_ipfrag *session;
64	int index = -1;
65	int i;
66
67	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
68		if (ring->ipfrag[i].fragments == NULL) {
69			index = i;
70			break;
71		}
72	}
73	if (index < 0)
74		return NULL;
75
76	session = &ring->ipfrag[index];
77
78	return session;
79}
80
81
82static void flush_session(struct mlx4_en_priv *priv,
83			  struct mlx4_en_ipfrag *session,
84			  u16 more)
85{
86	struct mbuf *mb = session->fragments;
87	struct ip *iph = mb->m_pkthdr.header;
88	struct net_device *dev = mb->m_pkthdr.rcvif;
89
90	/* Update IP length and checksum */
91	iph->ip_len = htons(session->total_len);
92	iph->ip_off = htons(more | (session->offset >> 3));
93	iph->ip_sum = 0;
94	iph->ip_sum = in_cksum_skip(mb, iph->ip_hl * 4,
95	    (char *)iph - mb->m_data);
96
97	dev->if_input(dev, mb);
98	session->fragments = NULL;
99	session->last = NULL;
100}
101
102
103static inline void frag_append(struct mlx4_en_priv *priv,
104			       struct mlx4_en_ipfrag *session,
105			       struct mbuf *mb,
106			       unsigned int data_len)
107{
108	struct mbuf *parent = session->fragments;
109
110	/* Update mb bookkeeping */
111	parent->m_pkthdr.len += data_len;
112	session->total_len += data_len;
113
114	m_adj(mb, mb->m_pkthdr.len - data_len);
115
116	session->last->m_next = mb;
117	for (; mb->m_next != NULL; mb = mb->m_next);
118	session->last = mb;
119}
120
121int mlx4_en_rx_frags(struct mlx4_en_priv *priv, struct mlx4_en_rx_ring *ring,
122		     struct mbuf *mb, struct mlx4_cqe *cqe)
123{
124	struct mlx4_en_ipfrag *session;
125	struct ip *iph;
126	u16 ip_len;
127	u16 ip_hlen;
128	int data_len;
129	u16 offset;
130
131	iph = (struct ip *)(mtod(mb, char *) + ETHER_HDR_LEN);
132	mb->m_pkthdr.header = iph;
133	ip_len = ntohs(iph->ip_len);
134	ip_hlen = iph->ip_hl * 4;
135	data_len = ip_len - ip_hlen;
136	offset = ntohs(iph->ip_off);
137	offset &= IP_OFFMASK;
138	offset <<= 3;
139
140	session = find_session(ring, iph);
141	if (unlikely(in_cksum_skip(mb, ip_hlen, (char *)iph - mb->m_data))) {
142		if (session)
143			flush_session(priv, session, IP_MF);
144		return -EINVAL;
145	}
146	if (session) {
147		if (unlikely(session->offset + session->total_len !=
148		    offset + ip_hlen ||
149		    session->total_len + mb->m_pkthdr.len > 65536)) {
150			flush_session(priv, session, IP_MF);
151			goto new_session;
152		}
153		frag_append(priv, session, mb, data_len);
154	} else {
155new_session:
156		session = start_session(ring, iph);
157		if (unlikely(!session))
158			return -ENOSPC;
159
160		session->fragments = mb;
161		session->daddr = iph->ip_dst.s_addr;
162		session->saddr = iph->ip_src.s_addr;
163		session->id = iph->ip_id;
164		session->protocol = iph->ip_p;
165		session->total_len = ip_len;
166		session->offset = offset;
167		for (; mb->m_next != NULL; mb = mb->m_next);
168		session->last = mb;
169	}
170	if (!(ntohs(iph->ip_off) & IP_MF))
171		flush_session(priv, session, 0);
172
173	return 0;
174}
175
176
177void mlx4_en_flush_frags(struct mlx4_en_priv *priv,
178			 struct mlx4_en_rx_ring *ring)
179{
180	struct mlx4_en_ipfrag *session;
181	int i;
182
183	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
184		session = &ring->ipfrag[i];
185		if (session->fragments)
186			flush_session(priv, session, IP_MF);
187	}
188}
189