Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/virtio/network/virtio_net.h
Show First 20 Lines • Show All 195 Lines • ▼ Show 20 Lines | struct virtio_net_ctrl_mq { | ||||
uint16_t virtqueue_pairs; | uint16_t virtqueue_pairs; | ||||
} __packed; | } __packed; | ||||
#define VIRTIO_NET_CTRL_MQ 4 | #define VIRTIO_NET_CTRL_MQ 4 | ||||
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 0 | #define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 0 | ||||
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN 1 | #define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN 1 | ||||
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX 0x8000 | #define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX 0x8000 | ||||
/* | |||||
* Use the checksum offset in the VirtIO header to set the | |||||
* correct CSUM_* flags. | |||||
*/ | |||||
static inline int | |||||
virtio_net_rx_csum_by_offset(struct mbuf *m, uint16_t eth_type, int ip_start, | |||||
struct virtio_net_hdr *hdr) | |||||
{ | |||||
#if defined(INET) || defined(INET6) | |||||
int offset = hdr->csum_start + hdr->csum_offset; | |||||
#endif | |||||
/* Only do a basic sanity check on the offset. */ | |||||
switch (eth_type) { | |||||
#if defined(INET) | |||||
case ETHERTYPE_IP: | |||||
if (__predict_false(offset < ip_start + sizeof(struct ip))) | |||||
return (1); | |||||
break; | |||||
#endif | |||||
#if defined(INET6) | |||||
case ETHERTYPE_IPV6: | |||||
if (__predict_false(offset < ip_start + sizeof(struct ip6_hdr))) | |||||
return (1); | |||||
break; | |||||
#endif | |||||
default: | |||||
/* Here we should increment the rx_csum_bad_ethtype counter. */ | |||||
return (1); | |||||
} | |||||
/* | |||||
* Use the offset to determine the appropriate CSUM_* flags. This is | |||||
* a bit dirty, but we can get by with it since the checksum offsets | |||||
* happen to be different. We assume the host host does not do IPv4 | |||||
* header checksum offloading. | |||||
*/ | |||||
switch (hdr->csum_offset) { | |||||
case offsetof(struct udphdr, uh_sum): | |||||
case offsetof(struct tcphdr, th_sum): | |||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; | |||||
m->m_pkthdr.csum_data = 0xFFFF; | |||||
break; | |||||
default: | |||||
/* Here we should increment the rx_csum_bad_offset counter. */ | |||||
return (1); | |||||
} | |||||
return (0); | |||||
} | |||||
static inline int | |||||
virtio_net_rx_csum_by_parse(struct mbuf *m, uint16_t eth_type, int ip_start, | |||||
struct virtio_net_hdr *hdr) | |||||
{ | |||||
int offset, proto; | |||||
switch (eth_type) { | |||||
#if defined(INET) | |||||
case ETHERTYPE_IP: { | |||||
struct ip *ip; | |||||
if (__predict_false(m->m_len < ip_start + sizeof(struct ip))) | |||||
return (1); | |||||
ip = (struct ip *)(m->m_data + ip_start); | |||||
proto = ip->ip_p; | |||||
offset = ip_start + (ip->ip_hl << 2); | |||||
break; | |||||
} | |||||
#endif | |||||
#if defined(INET6) | |||||
case ETHERTYPE_IPV6: | |||||
if (__predict_false(m->m_len < ip_start + | |||||
sizeof(struct ip6_hdr))) | |||||
return (1); | |||||
offset = ip6_lasthdr(m, ip_start, IPPROTO_IPV6, &proto); | |||||
if (__predict_false(offset < 0)) | |||||
return (1); | |||||
break; | |||||
#endif | |||||
default: | |||||
/* Here we should increment the rx_csum_bad_ethtype counter. */ | |||||
return (1); | |||||
} | |||||
switch (proto) { | |||||
case IPPROTO_TCP: | |||||
if (__predict_false(m->m_len < offset + sizeof(struct tcphdr))) | |||||
return (1); | |||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; | |||||
m->m_pkthdr.csum_data = 0xFFFF; | |||||
break; | |||||
case IPPROTO_UDP: | |||||
if (__predict_false(m->m_len < offset + sizeof(struct udphdr))) | |||||
return (1); | |||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; | |||||
m->m_pkthdr.csum_data = 0xFFFF; | |||||
break; | |||||
default: | |||||
/* | |||||
* For the remaining protocols, FreeBSD does not support | |||||
* checksum offloading, so the checksum will be recomputed. | |||||
*/ | |||||
#if 0 | |||||
if_printf(ifp, "cksum offload of unsupported " | |||||
"protocol eth_type=%#x proto=%d csum_start=%d " | |||||
"csum_offset=%d\n", __func__, eth_type, proto, | |||||
hdr->csum_start, hdr->csum_offset); | |||||
#endif | |||||
break; | |||||
} | |||||
return (0); | |||||
} | |||||
/* | |||||
* Set the appropriate CSUM_* flags. Unfortunately, the information | |||||
* provided is not directly useful to us. The VirtIO header gives the | |||||
* offset of the checksum, which is all Linux needs, but this is not | |||||
* how FreeBSD does things. We are forced to peek inside the packet | |||||
* a bit. | |||||
* | |||||
* It would be nice if VirtIO gave us the L4 protocol or if FreeBSD | |||||
* could accept the offsets and let the stack figure it out. | |||||
*/ | |||||
static inline int | |||||
virtio_net_rx_csum(struct mbuf *m, struct virtio_net_hdr *hdr) | |||||
{ | |||||
struct ether_header *eh; | |||||
struct ether_vlan_header *evh; | |||||
uint16_t eth_type; | |||||
int offset, error; | |||||
if ((hdr->flags & (VIRTIO_NET_HDR_F_NEEDS_CSUM | | |||||
VIRTIO_NET_HDR_F_DATA_VALID)) == 0) { | |||||
return (0); | |||||
} | |||||
eh = mtod(m, struct ether_header *); | |||||
eth_type = ntohs(eh->ether_type); | |||||
if (eth_type == ETHERTYPE_VLAN) { | |||||
/* BMV: We should handle nested VLAN tags too. */ | |||||
evh = mtod(m, struct ether_vlan_header *); | |||||
eth_type = ntohs(evh->evl_proto); | |||||
offset = sizeof(struct ether_vlan_header); | |||||
} else | |||||
offset = sizeof(struct ether_header); | |||||
if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) | |||||
error = virtio_net_rx_csum_by_offset(m, eth_type, offset, hdr); | |||||
else | |||||
error = virtio_net_rx_csum_by_parse(m, eth_type, offset, hdr); | |||||
return (error); | |||||
} | |||||
static inline int | |||||
virtio_net_tx_offload_ctx(struct mbuf *m, int *etype, int *proto, int *start) | |||||
{ | |||||
struct ether_vlan_header *evh; | |||||
int offset; | |||||
evh = mtod(m, struct ether_vlan_header *); | |||||
if (evh->evl_encap_proto == htons(ETHERTYPE_VLAN)) { | |||||
/* BMV: We should handle nested VLAN tags too. */ | |||||
*etype = ntohs(evh->evl_proto); | |||||
offset = sizeof(struct ether_vlan_header); | |||||
} else { | |||||
*etype = ntohs(evh->evl_encap_proto); | |||||
offset = sizeof(struct ether_header); | |||||
} | |||||
switch (*etype) { | |||||
#if defined(INET) | |||||
case ETHERTYPE_IP: { | |||||
struct ip *ip, iphdr; | |||||
if (__predict_false(m->m_len < offset + sizeof(struct ip))) { | |||||
m_copydata(m, offset, sizeof(struct ip), | |||||
(caddr_t) &iphdr); | |||||
ip = &iphdr; | |||||
} else | |||||
ip = (struct ip *)(m->m_data + offset); | |||||
*proto = ip->ip_p; | |||||
*start = offset + (ip->ip_hl << 2); | |||||
break; | |||||
} | |||||
#endif | |||||
#if defined(INET6) | |||||
case ETHERTYPE_IPV6: | |||||
*proto = -1; | |||||
*start = ip6_lasthdr(m, offset, IPPROTO_IPV6, proto); | |||||
/* Assert the network stack sent us a valid packet. */ | |||||
KASSERT(*start > offset, | |||||
("%s: mbuf %p start %d offset %d proto %d", __func__, m, | |||||
*start, offset, *proto)); | |||||
break; | |||||
#endif | |||||
default: | |||||
/* Here we should increment the tx_csum_bad_ethtype counter. */ | |||||
return (EINVAL); | |||||
} | |||||
return (0); | |||||
} | |||||
static inline int | |||||
virtio_net_tx_offload_tso(if_t ifp, struct mbuf *m, int eth_type, | |||||
int offset, bool allow_ecn, struct virtio_net_hdr *hdr) | |||||
{ | |||||
static struct timeval lastecn; | |||||
static int curecn; | |||||
struct tcphdr *tcp, tcphdr; | |||||
if (__predict_false(m->m_len < offset + sizeof(struct tcphdr))) { | |||||
m_copydata(m, offset, sizeof(struct tcphdr), (caddr_t) &tcphdr); | |||||
tcp = &tcphdr; | |||||
} else | |||||
tcp = (struct tcphdr *)(m->m_data + offset); | |||||
hdr->hdr_len = offset + (tcp->th_off << 2); | |||||
hdr->gso_size = m->m_pkthdr.tso_segsz; | |||||
hdr->gso_type = eth_type == ETHERTYPE_IP ? VIRTIO_NET_HDR_GSO_TCPV4 : | |||||
VIRTIO_NET_HDR_GSO_TCPV6; | |||||
if (tcp->th_flags & TH_CWR) { | |||||
/* | |||||
* Drop if VIRTIO_NET_F_HOST_ECN was not negotiated. In FreeBSD, | |||||
* ECN support is not on a per-interface basis, but globally via | |||||
* the net.inet.tcp.ecn.enable sysctl knob. The default is off. | |||||
*/ | |||||
if (!allow_ecn) { | |||||
if (ppsratecheck(&lastecn, &curecn, 1)) | |||||
if_printf(ifp, | |||||
"TSO with ECN not negotiated with host\n"); | |||||
return (ENOTSUP); | |||||
} | |||||
hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN; | |||||
} | |||||
/* Here we should increment tx_tso counter. */ | |||||
return (0); | |||||
} | |||||
static inline struct mbuf * | |||||
virtio_net_tx_offload(if_t ifp, struct mbuf *m, bool allow_ecn, | |||||
struct virtio_net_hdr *hdr) | |||||
{ | |||||
int flags, etype, csum_start, proto, error; | |||||
flags = m->m_pkthdr.csum_flags; | |||||
error = virtio_net_tx_offload_ctx(m, &etype, &proto, &csum_start); | |||||
if (error) | |||||
goto drop; | |||||
if ((etype == ETHERTYPE_IP && (flags & (CSUM_TCP | CSUM_UDP))) || | |||||
(etype == ETHERTYPE_IPV6 && | |||||
(flags & (CSUM_TCP_IPV6 | CSUM_UDP_IPV6)))) { | |||||
/* | |||||
* We could compare the IP protocol vs the CSUM_ flag too, | |||||
* but that really should not be necessary. | |||||
*/ | |||||
hdr->flags |= VIRTIO_NET_HDR_F_NEEDS_CSUM; | |||||
hdr->csum_start = csum_start; | |||||
hdr->csum_offset = m->m_pkthdr.csum_data; | |||||
/* Here we should increment the tx_csum counter. */ | |||||
} | |||||
if (flags & CSUM_TSO) { | |||||
if (__predict_false(proto != IPPROTO_TCP)) { | |||||
/* Likely failed to correctly parse the mbuf. | |||||
* Here we should increment the tx_tso_not_tcp | |||||
* counter. */ | |||||
goto drop; | |||||
} | |||||
KASSERT(hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM, | |||||
("%s: mbuf %p TSO without checksum offload %#x", | |||||
__func__, m, flags)); | |||||
error = virtio_net_tx_offload_tso(ifp, m, etype, csum_start, | |||||
allow_ecn, hdr); | |||||
if (error) | |||||
goto drop; | |||||
} | |||||
return (m); | |||||
drop: | |||||
m_freem(m); | |||||
return (NULL); | |||||
} | |||||
#endif /* _VIRTIO_NET_H */ | #endif /* _VIRTIO_NET_H */ |