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 "opt_inet.h"
35#include "mlx4_en.h"
36
37#ifdef INET
38
39#include <net/ethernet.h>
40#include <netinet/ip.h>
41#include <machine/in_cksum.h>
42
43static struct mlx4_en_ipfrag *find_session(struct mlx4_en_rx_ring *ring,
44					   struct ip *iph)
45{
46	struct mlx4_en_ipfrag *session;
47	int i;
48
49	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
50		session = &ring->ipfrag[i];
51		if (session->fragments == NULL)
52			continue;
53		if (session->daddr == iph->ip_dst.s_addr &&
54		    session->saddr == iph->ip_src.s_addr &&
55		    session->id == iph->ip_id &&
56		    session->protocol == iph->ip_p) {
57			return session;
58		}
59	}
60	return NULL;
61}
62
63static struct mlx4_en_ipfrag *start_session(struct mlx4_en_rx_ring *ring,
64					    struct ip *iph)
65{
66	struct mlx4_en_ipfrag *session;
67	int index = -1;
68	int i;
69
70	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
71		if (ring->ipfrag[i].fragments == NULL) {
72			index = i;
73			break;
74		}
75	}
76	if (index < 0)
77		return NULL;
78
79	session = &ring->ipfrag[index];
80
81	return session;
82}
83
84
85static void flush_session(struct mlx4_en_priv *priv,
86			  struct mlx4_en_ipfrag *session,
87			  u16 more)
88{
89	struct mbuf *mb = session->fragments;
90	struct ip *iph = mb->m_pkthdr.PH_loc.ptr;
91	struct net_device *dev = mb->m_pkthdr.rcvif;
92
93	/* Update IP length and checksum */
94	iph->ip_len = htons(session->total_len);
95	iph->ip_off = htons(more | (session->offset >> 3));
96	iph->ip_sum = 0;
97	iph->ip_sum = in_cksum_skip(mb, iph->ip_hl * 4,
98	    (char *)iph - mb->m_data);
99
100	dev->if_input(dev, mb);
101	session->fragments = NULL;
102	session->last = NULL;
103}
104
105
106static inline void frag_append(struct mlx4_en_priv *priv,
107			       struct mlx4_en_ipfrag *session,
108			       struct mbuf *mb,
109			       unsigned int data_len)
110{
111	struct mbuf *parent = session->fragments;
112
113	/* Update mb bookkeeping */
114	parent->m_pkthdr.len += data_len;
115	session->total_len += data_len;
116
117	m_adj(mb, mb->m_pkthdr.len - data_len);
118
119	session->last->m_next = mb;
120	for (; mb->m_next != NULL; mb = mb->m_next);
121	session->last = mb;
122}
123
124int mlx4_en_rx_frags(struct mlx4_en_priv *priv, struct mlx4_en_rx_ring *ring,
125		     struct mbuf *mb, struct mlx4_cqe *cqe)
126{
127	struct mlx4_en_ipfrag *session;
128	struct ip *iph;
129	u16 ip_len;
130	u16 ip_hlen;
131	int data_len;
132	u16 offset;
133
134	iph = (struct ip *)(mtod(mb, char *) + ETHER_HDR_LEN);
135	mb->m_pkthdr.PH_loc.ptr = iph;
136	ip_len = ntohs(iph->ip_len);
137	ip_hlen = iph->ip_hl * 4;
138	data_len = ip_len - ip_hlen;
139	offset = ntohs(iph->ip_off);
140	offset &= IP_OFFMASK;
141	offset <<= 3;
142
143	session = find_session(ring, iph);
144	if (unlikely(in_cksum_skip(mb, ip_hlen, (char *)iph - mb->m_data))) {
145		if (session)
146			flush_session(priv, session, IP_MF);
147		return -EINVAL;
148	}
149	if (session) {
150		if (unlikely(session->offset + session->total_len !=
151		    offset + ip_hlen ||
152		    session->total_len + mb->m_pkthdr.len > 65536)) {
153			flush_session(priv, session, IP_MF);
154			goto new_session;
155		}
156		frag_append(priv, session, mb, data_len);
157	} else {
158new_session:
159		session = start_session(ring, iph);
160		if (unlikely(!session))
161			return -ENOSPC;
162
163		session->fragments = mb;
164		session->daddr = iph->ip_dst.s_addr;
165		session->saddr = iph->ip_src.s_addr;
166		session->id = iph->ip_id;
167		session->protocol = iph->ip_p;
168		session->total_len = ip_len;
169		session->offset = offset;
170		for (; mb->m_next != NULL; mb = mb->m_next);
171		session->last = mb;
172	}
173	if (!(ntohs(iph->ip_off) & IP_MF))
174		flush_session(priv, session, 0);
175
176	return 0;
177}
178
179
180void mlx4_en_flush_frags(struct mlx4_en_priv *priv,
181			 struct mlx4_en_rx_ring *ring)
182{
183	struct mlx4_en_ipfrag *session;
184	int i;
185
186	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
187		session = &ring->ipfrag[i];
188		if (session->fragments)
189			flush_session(priv, session, IP_MF);
190	}
191}
192#endif
193