1219820Sjeff/*
2219820Sjeff * Copyright (c) 2007 Mellanox Technologies. All rights reserved.
3219820Sjeff *
4219820Sjeff * This software is available to you under a choice of one of two
5219820Sjeff * licenses.  You may choose to be licensed under the terms of the GNU
6219820Sjeff * General Public License (GPL) Version 2, available from the file
7219820Sjeff * COPYING in the main directory of this source tree, or the
8219820Sjeff * OpenIB.org BSD license below:
9219820Sjeff *
10219820Sjeff *     Redistribution and use in source and binary forms, with or
11219820Sjeff *     without modification, are permitted provided that the following
12219820Sjeff *     conditions are met:
13219820Sjeff *
14219820Sjeff *      - Redistributions of source code must retain the above
15219820Sjeff *        copyright notice, this list of conditions and the following
16219820Sjeff *        disclaimer.
17219820Sjeff *
18219820Sjeff *      - Redistributions in binary form must reproduce the above
19219820Sjeff *        copyright notice, this list of conditions and the following
20219820Sjeff *        disclaimer in the documentation and/or other materials
21219820Sjeff *        provided with the distribution.
22219820Sjeff *
23219820Sjeff * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24219820Sjeff * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25219820Sjeff * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26219820Sjeff * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
27219820Sjeff * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
28219820Sjeff * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29219820Sjeff * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30219820Sjeff * SOFTWARE.
31219820Sjeff *
32219820Sjeff */
33219820Sjeff
34234183Sjhb#include "opt_inet.h"
35219820Sjeff#include "mlx4_en.h"
36219820Sjeff
37234183Sjhb#ifdef INET
38234183Sjhb
39219820Sjeff#include <net/ethernet.h>
40219820Sjeff#include <netinet/ip.h>
41219820Sjeff#include <machine/in_cksum.h>
42219820Sjeff
43219820Sjeffstatic struct mlx4_en_ipfrag *find_session(struct mlx4_en_rx_ring *ring,
44219820Sjeff					   struct ip *iph)
45219820Sjeff{
46219820Sjeff	struct mlx4_en_ipfrag *session;
47219820Sjeff	int i;
48219820Sjeff
49219820Sjeff	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
50219820Sjeff		session = &ring->ipfrag[i];
51219820Sjeff		if (session->fragments == NULL)
52219820Sjeff			continue;
53219820Sjeff		if (session->daddr == iph->ip_dst.s_addr &&
54219820Sjeff		    session->saddr == iph->ip_src.s_addr &&
55219820Sjeff		    session->id == iph->ip_id &&
56219820Sjeff		    session->protocol == iph->ip_p) {
57219820Sjeff			return session;
58219820Sjeff		}
59219820Sjeff	}
60219820Sjeff	return NULL;
61219820Sjeff}
62219820Sjeff
63219820Sjeffstatic struct mlx4_en_ipfrag *start_session(struct mlx4_en_rx_ring *ring,
64219820Sjeff					    struct ip *iph)
65219820Sjeff{
66219820Sjeff	struct mlx4_en_ipfrag *session;
67219820Sjeff	int index = -1;
68219820Sjeff	int i;
69219820Sjeff
70219820Sjeff	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
71219820Sjeff		if (ring->ipfrag[i].fragments == NULL) {
72219820Sjeff			index = i;
73219820Sjeff			break;
74219820Sjeff		}
75219820Sjeff	}
76219820Sjeff	if (index < 0)
77219820Sjeff		return NULL;
78219820Sjeff
79219820Sjeff	session = &ring->ipfrag[index];
80219820Sjeff
81219820Sjeff	return session;
82219820Sjeff}
83219820Sjeff
84219820Sjeff
85219820Sjeffstatic void flush_session(struct mlx4_en_priv *priv,
86219820Sjeff			  struct mlx4_en_ipfrag *session,
87219820Sjeff			  u16 more)
88219820Sjeff{
89219820Sjeff	struct mbuf *mb = session->fragments;
90254832Sandre	struct ip *iph = mb->m_pkthdr.PH_loc.ptr;
91219820Sjeff	struct net_device *dev = mb->m_pkthdr.rcvif;
92219820Sjeff
93219820Sjeff	/* Update IP length and checksum */
94219820Sjeff	iph->ip_len = htons(session->total_len);
95219820Sjeff	iph->ip_off = htons(more | (session->offset >> 3));
96219820Sjeff	iph->ip_sum = 0;
97219820Sjeff	iph->ip_sum = in_cksum_skip(mb, iph->ip_hl * 4,
98219820Sjeff	    (char *)iph - mb->m_data);
99219820Sjeff
100219820Sjeff	dev->if_input(dev, mb);
101219820Sjeff	session->fragments = NULL;
102219820Sjeff	session->last = NULL;
103219820Sjeff}
104219820Sjeff
105219820Sjeff
106219820Sjeffstatic inline void frag_append(struct mlx4_en_priv *priv,
107219820Sjeff			       struct mlx4_en_ipfrag *session,
108219820Sjeff			       struct mbuf *mb,
109219820Sjeff			       unsigned int data_len)
110219820Sjeff{
111219820Sjeff	struct mbuf *parent = session->fragments;
112219820Sjeff
113219820Sjeff	/* Update mb bookkeeping */
114219820Sjeff	parent->m_pkthdr.len += data_len;
115219820Sjeff	session->total_len += data_len;
116219820Sjeff
117219820Sjeff	m_adj(mb, mb->m_pkthdr.len - data_len);
118219820Sjeff
119219820Sjeff	session->last->m_next = mb;
120219820Sjeff	for (; mb->m_next != NULL; mb = mb->m_next);
121219820Sjeff	session->last = mb;
122219820Sjeff}
123219820Sjeff
124219820Sjeffint mlx4_en_rx_frags(struct mlx4_en_priv *priv, struct mlx4_en_rx_ring *ring,
125219820Sjeff		     struct mbuf *mb, struct mlx4_cqe *cqe)
126219820Sjeff{
127219820Sjeff	struct mlx4_en_ipfrag *session;
128219820Sjeff	struct ip *iph;
129219820Sjeff	u16 ip_len;
130219820Sjeff	u16 ip_hlen;
131219820Sjeff	int data_len;
132219820Sjeff	u16 offset;
133219820Sjeff
134219820Sjeff	iph = (struct ip *)(mtod(mb, char *) + ETHER_HDR_LEN);
135254832Sandre	mb->m_pkthdr.PH_loc.ptr = iph;
136219820Sjeff	ip_len = ntohs(iph->ip_len);
137219820Sjeff	ip_hlen = iph->ip_hl * 4;
138219820Sjeff	data_len = ip_len - ip_hlen;
139219820Sjeff	offset = ntohs(iph->ip_off);
140219820Sjeff	offset &= IP_OFFMASK;
141219820Sjeff	offset <<= 3;
142219820Sjeff
143219820Sjeff	session = find_session(ring, iph);
144219820Sjeff	if (unlikely(in_cksum_skip(mb, ip_hlen, (char *)iph - mb->m_data))) {
145219820Sjeff		if (session)
146219820Sjeff			flush_session(priv, session, IP_MF);
147219820Sjeff		return -EINVAL;
148219820Sjeff	}
149219820Sjeff	if (session) {
150219820Sjeff		if (unlikely(session->offset + session->total_len !=
151219820Sjeff		    offset + ip_hlen ||
152219820Sjeff		    session->total_len + mb->m_pkthdr.len > 65536)) {
153219820Sjeff			flush_session(priv, session, IP_MF);
154219820Sjeff			goto new_session;
155219820Sjeff		}
156219820Sjeff		frag_append(priv, session, mb, data_len);
157219820Sjeff	} else {
158219820Sjeffnew_session:
159219820Sjeff		session = start_session(ring, iph);
160219820Sjeff		if (unlikely(!session))
161219820Sjeff			return -ENOSPC;
162219820Sjeff
163219820Sjeff		session->fragments = mb;
164219820Sjeff		session->daddr = iph->ip_dst.s_addr;
165219820Sjeff		session->saddr = iph->ip_src.s_addr;
166219820Sjeff		session->id = iph->ip_id;
167219820Sjeff		session->protocol = iph->ip_p;
168219820Sjeff		session->total_len = ip_len;
169219820Sjeff		session->offset = offset;
170219820Sjeff		for (; mb->m_next != NULL; mb = mb->m_next);
171219820Sjeff		session->last = mb;
172219820Sjeff	}
173219820Sjeff	if (!(ntohs(iph->ip_off) & IP_MF))
174219820Sjeff		flush_session(priv, session, 0);
175219820Sjeff
176219820Sjeff	return 0;
177219820Sjeff}
178219820Sjeff
179219820Sjeff
180219820Sjeffvoid mlx4_en_flush_frags(struct mlx4_en_priv *priv,
181219820Sjeff			 struct mlx4_en_rx_ring *ring)
182219820Sjeff{
183219820Sjeff	struct mlx4_en_ipfrag *session;
184219820Sjeff	int i;
185219820Sjeff
186219820Sjeff	for (i = 0; i < MLX4_EN_NUM_IPFRAG_SESSIONS; i++) {
187219820Sjeff		session = &ring->ipfrag[i];
188219820Sjeff		if (session->fragments)
189219820Sjeff			flush_session(priv, session, IP_MF);
190219820Sjeff	}
191219820Sjeff}
192234183Sjhb#endif
193