#include "myri10ge_var.h"
#define IP_OFFMASK 0x1fff
#define TCPOPT_TIMESTAMP 8
#define TCPOLEN_TIMESTAMP 10
#define TCPOLEN_TSTAMP_APPA 12
uint16_t
myri10ge_csum_generic(uint16_t *raw, int len)
{
uint32_t csum;
csum = 0;
while (len > 0) {
csum += *raw;
raw++;
csum += *raw;
raw++;
len -= 4;
}
csum = (csum >> 16) + (csum & 0xffff);
csum = (csum >> 16) + (csum & 0xffff);
return ((uint16_t)csum);
}
static uint16_t
myri10ge_in_pseudo(unsigned int a, unsigned int b,
unsigned int c)
{
uint64_t csum;
csum = (uint64_t)a + b + c;
csum = (csum >> 16) + (csum & 0xffff);
csum = (csum >> 16) + (csum & 0xffff);
return ((uint16_t)csum);
}
void
myri10ge_lro_flush(struct myri10ge_slice_state *ss, struct lro_entry *lro,
struct myri10ge_mblk_list *mbl)
{
struct ip *ip;
struct tcphdr *tcp;
uint32_t *ts_ptr;
uint32_t tcplen, tcp_csum;
if (lro->append_cnt) {
ip = lro->ip;
ip->ip_len = htons(lro->len - ETHERNET_HEADER_SIZE);
ip->ip_sum = 0;
ip->ip_sum = 0xffff ^
myri10ge_csum_generic((uint16_t *)ip, sizeof (*ip));
tcp = (struct tcphdr *)(ip + 1);
tcp->th_ack = lro->ack_seq;
tcp->th_win = lro->window;
tcp->th_flags = lro->flags;
if (lro->timestamp) {
ts_ptr = (uint32_t *)(tcp + 1);
ts_ptr[1] = htonl(lro->tsval);
ts_ptr[2] = lro->tsecr;
}
tcp->th_sum = 0;
tcplen = lro->len - sizeof (*ip) - ETHERNET_HEADER_SIZE;
tcp_csum = lro->data_csum;
tcp_csum += myri10ge_in_pseudo(ip->ip_src.s_addr,
ip->ip_dst.s_addr, htons(tcplen + IPPROTO_TCP));
tcp_csum += myri10ge_csum_generic((uint16_t *)tcp,
tcp->th_off << 2);
tcp_csum = (tcp_csum & 0xffff) + (tcp_csum >> 16);
tcp_csum = (tcp_csum & 0xffff) + (tcp_csum >> 16);
tcp->th_sum = 0xffff ^ tcp_csum;
}
mac_hcksum_set(lro->m_head, 0, 0, 0,
0, HCK_IPV4_HDRCKSUM_OK | HCK_FULLCKSUM_OK);
mbl->cnt += lro->append_cnt;
myri10ge_mbl_append(ss, mbl, lro->m_head);
MYRI10GE_SLICE_STAT_INC(lro_flushed);
MYRI10GE_SLICE_STAT_ADD(lro_queued, lro->append_cnt + 1);
lro->m_head = NULL;
lro->timestamp = 0;
lro->append_cnt = 0;
lro->next = ss->lro_free;
ss->lro_free = lro;
}
int
myri10ge_lro_rx(struct myri10ge_slice_state *ss, mblk_t *m_head,
uint32_t csum, struct myri10ge_mblk_list *mbl)
{
struct ether_header *eh;
struct ip *ip;
struct tcphdr *tcp;
uint32_t *ts_ptr;
struct lro_entry *lro, *curr;
int hlen, ip_len, tcp_hdr_len, tcp_data_len;
int opt_bytes, trim;
int tot_len = MBLKL(m_head);
uint32_t seq, tmp_csum;
eh = (struct ether_header *)(void *)m_head->b_rptr;
if (eh->ether_type != htons(ETHERTYPE_IP))
return (EINVAL);
ip = (struct ip *)(void *)(eh + 1);
if (ip->ip_p != IPPROTO_TCP)
return (EINVAL);
if ((ip->ip_hl << 2) != sizeof (*ip))
return (EINVAL);
if (ip->ip_off & htons(IP_MF|IP_OFFMASK))
return (EINVAL);
tmp_csum = myri10ge_csum_generic((uint16_t *)ip, sizeof (*ip));
if (unlikely((tmp_csum ^ 0xffff) != 0)) {
MYRI10GE_SLICE_STAT_INC(lro_bad_csum);
return (EINVAL);
}
tcp = (struct tcphdr *)(ip + 1);
if ((tcp->th_flags & ~(TH_ACK | TH_PUSH)) != 0)
return (EINVAL);
opt_bytes = (tcp->th_off << 2) - sizeof (*tcp);
tcp_hdr_len = sizeof (*tcp) + opt_bytes;
ts_ptr = (uint32_t *)(tcp + 1);
if (opt_bytes != 0) {
if (unlikely(opt_bytes != TCPOLEN_TSTAMP_APPA) ||
(*ts_ptr != ntohl(TCPOPT_NOP<<24|TCPOPT_NOP<<16|
TCPOPT_TIMESTAMP<<8|TCPOLEN_TIMESTAMP)))
return (EINVAL);
}
ip_len = ntohs(ip->ip_len);
tcp_data_len = ip_len - (tcp->th_off << 2) - sizeof (*ip);
trim = tot_len - (ip_len + ETHERNET_HEADER_SIZE);
if (trim != 0) {
if (trim < 0) {
return (EINVAL);
}
m_head->b_wptr -= trim;
tot_len -= trim;
}
csum = ntohs((uint16_t)csum);
tmp_csum = csum + myri10ge_in_pseudo(ip->ip_src.s_addr,
ip->ip_dst.s_addr, htons(tcp_hdr_len + tcp_data_len + IPPROTO_TCP));
tmp_csum = (tmp_csum & 0xffff) + (tmp_csum >> 16);
tmp_csum = (tmp_csum & 0xffff) + (tmp_csum >> 16);
if (tmp_csum != 0xffff) {
MYRI10GE_SLICE_STAT_INC(lro_bad_csum);
return (EINVAL);
}
hlen = ip_len + ETHERNET_HEADER_SIZE - tcp_data_len;
seq = ntohl(tcp->th_seq);
for (lro = ss->lro_active; lro != NULL; lro = lro->next) {
if (lro->source_port == tcp->th_sport &&
lro->dest_port == tcp->th_dport &&
lro->source_ip == ip->ip_src.s_addr &&
lro->dest_ip == ip->ip_dst.s_addr) {
if (unlikely(seq != lro->next_seq)) {
if (ss->lro_active == lro) {
ss->lro_active = lro->next;
} else {
curr = ss->lro_active;
while (curr->next != lro)
curr = curr->next;
curr->next = lro->next;
}
myri10ge_lro_flush(ss, lro, mbl);
return (EINVAL);
}
if (opt_bytes) {
uint32_t tsval = ntohl(*(ts_ptr + 1));
if (unlikely(lro->tsval > tsval ||
*(ts_ptr + 2) == 0)) {
return (-8);
}
lro->tsval = tsval;
lro->tsecr = *(ts_ptr + 2);
}
lro->next_seq += tcp_data_len;
lro->ack_seq = tcp->th_ack;
lro->window = tcp->th_win;
lro->flags |= tcp->th_flags;
lro->append_cnt++;
if (tcp_data_len == 0) {
freeb(m_head);
return (0);
}
tmp_csum = myri10ge_csum_generic((uint16_t *)tcp,
tcp_hdr_len);
csum = csum + (tmp_csum ^ 0xffff);
csum = (csum & 0xffff) + (csum >> 16);
csum = (csum & 0xffff) + (csum >> 16);
if (lro->len & 0x1) {
csum = ((csum << 8) | (csum >> 8)) & 0xffff;
}
csum = csum + lro->data_csum;
csum = (csum & 0xffff) + (csum >> 16);
csum = (csum & 0xffff) + (csum >> 16);
lro->data_csum = csum;
lro->len += tcp_data_len;
m_head->b_rptr += hlen;
lro->m_tail->b_cont = m_head;
lro->m_tail = m_head;
if (lro->len > (65535 - myri10ge_mtu) ||
(lro->append_cnt + 1) == myri10ge_lro_max_aggr) {
if (ss->lro_active == lro) {
ss->lro_active = lro->next;
} else {
curr = ss->lro_active;
while (curr->next != lro)
curr = curr->next;
curr->next = lro->next;
}
myri10ge_lro_flush(ss, lro, mbl);
}
return (0);
}
}
if (ss->lro_free == NULL)
return (ENOMEM);
lro = ss->lro_free;
ss->lro_free = lro->next;
lro->next = ss->lro_active;
ss->lro_active = lro;
lro->source_port = tcp->th_sport;
lro->dest_port = tcp->th_dport;
lro->source_ip = ip->ip_src.s_addr;
lro->dest_ip = ip->ip_dst.s_addr;
lro->next_seq = seq + tcp_data_len;
lro->mss = (uint16_t)tcp_data_len;
lro->ack_seq = tcp->th_ack;
lro->window = tcp->th_win;
lro->flags = tcp->th_flags;
tmp_csum = myri10ge_csum_generic((uint16_t *)tcp, tcp_hdr_len);
csum = csum + (tmp_csum ^ 0xffff);
csum = (csum & 0xffff) + (csum >> 16);
csum = (csum & 0xffff) + (csum >> 16);
lro->data_csum = csum;
lro->ip = ip;
if (opt_bytes) {
lro->timestamp = 1;
lro->tsval = ntohl(*(ts_ptr + 1));
lro->tsecr = *(ts_ptr + 2);
}
lro->len = tot_len;
lro->m_head = m_head;
lro->m_tail = m_head;
return (0);
}