162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* sunvnet.c: Sun LDOM Virtual Network Driver. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net> 562306a36Sopenharmony_ci * Copyright (C) 2016-2017 Oracle. All rights reserved. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/types.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <linux/delay.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/netdevice.h> 1562306a36Sopenharmony_ci#include <linux/ethtool.h> 1662306a36Sopenharmony_ci#include <linux/etherdevice.h> 1762306a36Sopenharmony_ci#include <linux/mutex.h> 1862306a36Sopenharmony_ci#include <linux/highmem.h> 1962306a36Sopenharmony_ci#include <linux/if_vlan.h> 2062306a36Sopenharmony_ci#define CREATE_TRACE_POINTS 2162306a36Sopenharmony_ci#include <trace/events/sunvnet.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 2462306a36Sopenharmony_ci#include <linux/icmpv6.h> 2562306a36Sopenharmony_ci#endif 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include <net/ip.h> 2862306a36Sopenharmony_ci#include <net/gso.h> 2962306a36Sopenharmony_ci#include <net/icmp.h> 3062306a36Sopenharmony_ci#include <net/route.h> 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#include <asm/vio.h> 3362306a36Sopenharmony_ci#include <asm/ldc.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include "sunvnet_common.h" 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/* Heuristic for the number of times to exponentially backoff and 3862306a36Sopenharmony_ci * retry sending an LDC trigger when EAGAIN is encountered 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_ci#define VNET_MAX_RETRIES 10 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ciMODULE_AUTHOR("David S. Miller (davem@davemloft.net)"); 4362306a36Sopenharmony_ciMODULE_DESCRIPTION("Sun LDOM virtual network support library"); 4462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 4562306a36Sopenharmony_ciMODULE_VERSION("1.1"); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic int __vnet_tx_trigger(struct vnet_port *port, u32 start); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic inline u32 vnet_tx_dring_avail(struct vio_dring_state *dr) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci return vio_dring_avail(dr, VNET_TX_RING_SIZE); 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic int vnet_handle_unknown(struct vnet_port *port, void *arg) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci struct vio_msg_tag *pkt = arg; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci pr_err("Received unknown msg [%02x:%02x:%04x:%08x]\n", 5962306a36Sopenharmony_ci pkt->type, pkt->stype, pkt->stype_env, pkt->sid); 6062306a36Sopenharmony_ci pr_err("Resetting connection\n"); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci ldc_disconnect(port->vio.lp); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return -ECONNRESET; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int vnet_port_alloc_tx_ring(struct vnet_port *port); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ciint sunvnet_send_attr_common(struct vio_driver_state *vio) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci struct vnet_port *port = to_vnet_port(vio); 7262306a36Sopenharmony_ci struct net_device *dev = VNET_PORT_TO_NET_DEVICE(port); 7362306a36Sopenharmony_ci struct vio_net_attr_info pkt; 7462306a36Sopenharmony_ci int framelen = ETH_FRAME_LEN; 7562306a36Sopenharmony_ci int i, err; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci err = vnet_port_alloc_tx_ring(to_vnet_port(vio)); 7862306a36Sopenharmony_ci if (err) 7962306a36Sopenharmony_ci return err; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci memset(&pkt, 0, sizeof(pkt)); 8262306a36Sopenharmony_ci pkt.tag.type = VIO_TYPE_CTRL; 8362306a36Sopenharmony_ci pkt.tag.stype = VIO_SUBTYPE_INFO; 8462306a36Sopenharmony_ci pkt.tag.stype_env = VIO_ATTR_INFO; 8562306a36Sopenharmony_ci pkt.tag.sid = vio_send_sid(vio); 8662306a36Sopenharmony_ci if (vio_version_before(vio, 1, 2)) 8762306a36Sopenharmony_ci pkt.xfer_mode = VIO_DRING_MODE; 8862306a36Sopenharmony_ci else 8962306a36Sopenharmony_ci pkt.xfer_mode = VIO_NEW_DRING_MODE; 9062306a36Sopenharmony_ci pkt.addr_type = VNET_ADDR_ETHERMAC; 9162306a36Sopenharmony_ci pkt.ack_freq = 0; 9262306a36Sopenharmony_ci for (i = 0; i < 6; i++) 9362306a36Sopenharmony_ci pkt.addr |= (u64)dev->dev_addr[i] << ((5 - i) * 8); 9462306a36Sopenharmony_ci if (vio_version_after(vio, 1, 3)) { 9562306a36Sopenharmony_ci if (port->rmtu) { 9662306a36Sopenharmony_ci port->rmtu = min(VNET_MAXPACKET, port->rmtu); 9762306a36Sopenharmony_ci pkt.mtu = port->rmtu; 9862306a36Sopenharmony_ci } else { 9962306a36Sopenharmony_ci port->rmtu = VNET_MAXPACKET; 10062306a36Sopenharmony_ci pkt.mtu = port->rmtu; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci if (vio_version_after_eq(vio, 1, 6)) 10362306a36Sopenharmony_ci pkt.options = VIO_TX_DRING; 10462306a36Sopenharmony_ci } else if (vio_version_before(vio, 1, 3)) { 10562306a36Sopenharmony_ci pkt.mtu = framelen; 10662306a36Sopenharmony_ci } else { /* v1.3 */ 10762306a36Sopenharmony_ci pkt.mtu = framelen + VLAN_HLEN; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci pkt.cflags = 0; 11162306a36Sopenharmony_ci if (vio_version_after_eq(vio, 1, 7) && port->tso) { 11262306a36Sopenharmony_ci pkt.cflags |= VNET_LSO_IPV4_CAPAB; 11362306a36Sopenharmony_ci if (!port->tsolen) 11462306a36Sopenharmony_ci port->tsolen = VNET_MAXTSO; 11562306a36Sopenharmony_ci pkt.ipv4_lso_maxlen = port->tsolen; 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci pkt.plnk_updt = PHYSLINK_UPDATE_NONE; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci viodbg(HS, "SEND NET ATTR xmode[0x%x] atype[0x%x] addr[%llx] " 12162306a36Sopenharmony_ci "ackfreq[%u] plnk_updt[0x%02x] opts[0x%02x] mtu[%llu] " 12262306a36Sopenharmony_ci "cflags[0x%04x] lso_max[%u]\n", 12362306a36Sopenharmony_ci pkt.xfer_mode, pkt.addr_type, 12462306a36Sopenharmony_ci (unsigned long long)pkt.addr, 12562306a36Sopenharmony_ci pkt.ack_freq, pkt.plnk_updt, pkt.options, 12662306a36Sopenharmony_ci (unsigned long long)pkt.mtu, pkt.cflags, pkt.ipv4_lso_maxlen); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return vio_ldc_send(vio, &pkt, sizeof(pkt)); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_send_attr_common); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic int handle_attr_info(struct vio_driver_state *vio, 13362306a36Sopenharmony_ci struct vio_net_attr_info *pkt) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct vnet_port *port = to_vnet_port(vio); 13662306a36Sopenharmony_ci u64 localmtu; 13762306a36Sopenharmony_ci u8 xfer_mode; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci viodbg(HS, "GOT NET ATTR xmode[0x%x] atype[0x%x] addr[%llx] " 14062306a36Sopenharmony_ci "ackfreq[%u] plnk_updt[0x%02x] opts[0x%02x] mtu[%llu] " 14162306a36Sopenharmony_ci " (rmtu[%llu]) cflags[0x%04x] lso_max[%u]\n", 14262306a36Sopenharmony_ci pkt->xfer_mode, pkt->addr_type, 14362306a36Sopenharmony_ci (unsigned long long)pkt->addr, 14462306a36Sopenharmony_ci pkt->ack_freq, pkt->plnk_updt, pkt->options, 14562306a36Sopenharmony_ci (unsigned long long)pkt->mtu, port->rmtu, pkt->cflags, 14662306a36Sopenharmony_ci pkt->ipv4_lso_maxlen); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci pkt->tag.sid = vio_send_sid(vio); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci xfer_mode = pkt->xfer_mode; 15162306a36Sopenharmony_ci /* for version < 1.2, VIO_DRING_MODE = 0x3 and no bitmask */ 15262306a36Sopenharmony_ci if (vio_version_before(vio, 1, 2) && xfer_mode == VIO_DRING_MODE) 15362306a36Sopenharmony_ci xfer_mode = VIO_NEW_DRING_MODE; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* MTU negotiation: 15662306a36Sopenharmony_ci * < v1.3 - ETH_FRAME_LEN exactly 15762306a36Sopenharmony_ci * > v1.3 - MIN(pkt.mtu, VNET_MAXPACKET, port->rmtu) and change 15862306a36Sopenharmony_ci * pkt->mtu for ACK 15962306a36Sopenharmony_ci * = v1.3 - ETH_FRAME_LEN + VLAN_HLEN exactly 16062306a36Sopenharmony_ci */ 16162306a36Sopenharmony_ci if (vio_version_before(vio, 1, 3)) { 16262306a36Sopenharmony_ci localmtu = ETH_FRAME_LEN; 16362306a36Sopenharmony_ci } else if (vio_version_after(vio, 1, 3)) { 16462306a36Sopenharmony_ci localmtu = port->rmtu ? port->rmtu : VNET_MAXPACKET; 16562306a36Sopenharmony_ci localmtu = min(pkt->mtu, localmtu); 16662306a36Sopenharmony_ci pkt->mtu = localmtu; 16762306a36Sopenharmony_ci } else { /* v1.3 */ 16862306a36Sopenharmony_ci localmtu = ETH_FRAME_LEN + VLAN_HLEN; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci port->rmtu = localmtu; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* LSO negotiation */ 17362306a36Sopenharmony_ci if (vio_version_after_eq(vio, 1, 7)) 17462306a36Sopenharmony_ci port->tso &= !!(pkt->cflags & VNET_LSO_IPV4_CAPAB); 17562306a36Sopenharmony_ci else 17662306a36Sopenharmony_ci port->tso = false; 17762306a36Sopenharmony_ci if (port->tso) { 17862306a36Sopenharmony_ci if (!port->tsolen) 17962306a36Sopenharmony_ci port->tsolen = VNET_MAXTSO; 18062306a36Sopenharmony_ci port->tsolen = min(port->tsolen, pkt->ipv4_lso_maxlen); 18162306a36Sopenharmony_ci if (port->tsolen < VNET_MINTSO) { 18262306a36Sopenharmony_ci port->tso = false; 18362306a36Sopenharmony_ci port->tsolen = 0; 18462306a36Sopenharmony_ci pkt->cflags &= ~VNET_LSO_IPV4_CAPAB; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci pkt->ipv4_lso_maxlen = port->tsolen; 18762306a36Sopenharmony_ci } else { 18862306a36Sopenharmony_ci pkt->cflags &= ~VNET_LSO_IPV4_CAPAB; 18962306a36Sopenharmony_ci pkt->ipv4_lso_maxlen = 0; 19062306a36Sopenharmony_ci port->tsolen = 0; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* for version >= 1.6, ACK packet mode we support */ 19462306a36Sopenharmony_ci if (vio_version_after_eq(vio, 1, 6)) { 19562306a36Sopenharmony_ci pkt->xfer_mode = VIO_NEW_DRING_MODE; 19662306a36Sopenharmony_ci pkt->options = VIO_TX_DRING; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci if (!(xfer_mode | VIO_NEW_DRING_MODE) || 20062306a36Sopenharmony_ci pkt->addr_type != VNET_ADDR_ETHERMAC || 20162306a36Sopenharmony_ci pkt->mtu != localmtu) { 20262306a36Sopenharmony_ci viodbg(HS, "SEND NET ATTR NACK\n"); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci pkt->tag.stype = VIO_SUBTYPE_NACK; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci (void)vio_ldc_send(vio, pkt, sizeof(*pkt)); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci return -ECONNRESET; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci viodbg(HS, "SEND NET ATTR ACK xmode[0x%x] atype[0x%x] " 21262306a36Sopenharmony_ci "addr[%llx] ackfreq[%u] plnk_updt[0x%02x] opts[0x%02x] " 21362306a36Sopenharmony_ci "mtu[%llu] (rmtu[%llu]) cflags[0x%04x] lso_max[%u]\n", 21462306a36Sopenharmony_ci pkt->xfer_mode, pkt->addr_type, 21562306a36Sopenharmony_ci (unsigned long long)pkt->addr, 21662306a36Sopenharmony_ci pkt->ack_freq, pkt->plnk_updt, pkt->options, 21762306a36Sopenharmony_ci (unsigned long long)pkt->mtu, port->rmtu, pkt->cflags, 21862306a36Sopenharmony_ci pkt->ipv4_lso_maxlen); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci pkt->tag.stype = VIO_SUBTYPE_ACK; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return vio_ldc_send(vio, pkt, sizeof(*pkt)); 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic int handle_attr_ack(struct vio_driver_state *vio, 22662306a36Sopenharmony_ci struct vio_net_attr_info *pkt) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci viodbg(HS, "GOT NET ATTR ACK\n"); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci return 0; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic int handle_attr_nack(struct vio_driver_state *vio, 23462306a36Sopenharmony_ci struct vio_net_attr_info *pkt) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci viodbg(HS, "GOT NET ATTR NACK\n"); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci return -ECONNRESET; 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ciint sunvnet_handle_attr_common(struct vio_driver_state *vio, void *arg) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci struct vio_net_attr_info *pkt = arg; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci switch (pkt->tag.stype) { 24662306a36Sopenharmony_ci case VIO_SUBTYPE_INFO: 24762306a36Sopenharmony_ci return handle_attr_info(vio, pkt); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci case VIO_SUBTYPE_ACK: 25062306a36Sopenharmony_ci return handle_attr_ack(vio, pkt); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci case VIO_SUBTYPE_NACK: 25362306a36Sopenharmony_ci return handle_attr_nack(vio, pkt); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci default: 25662306a36Sopenharmony_ci return -ECONNRESET; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_handle_attr_common); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_civoid sunvnet_handshake_complete_common(struct vio_driver_state *vio) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci struct vio_dring_state *dr; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci dr = &vio->drings[VIO_DRIVER_RX_RING]; 26662306a36Sopenharmony_ci dr->rcv_nxt = 1; 26762306a36Sopenharmony_ci dr->snd_nxt = 1; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci dr = &vio->drings[VIO_DRIVER_TX_RING]; 27062306a36Sopenharmony_ci dr->rcv_nxt = 1; 27162306a36Sopenharmony_ci dr->snd_nxt = 1; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_handshake_complete_common); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci/* The hypervisor interface that implements copying to/from imported 27662306a36Sopenharmony_ci * memory from another domain requires that copies are done to 8-byte 27762306a36Sopenharmony_ci * aligned buffers, and that the lengths of such copies are also 8-byte 27862306a36Sopenharmony_ci * multiples. 27962306a36Sopenharmony_ci * 28062306a36Sopenharmony_ci * So we align skb->data to an 8-byte multiple and pad-out the data 28162306a36Sopenharmony_ci * area so we can round the copy length up to the next multiple of 28262306a36Sopenharmony_ci * 8 for the copy. 28362306a36Sopenharmony_ci * 28462306a36Sopenharmony_ci * The transmitter puts the actual start of the packet 6 bytes into 28562306a36Sopenharmony_ci * the buffer it sends over, so that the IP headers after the ethernet 28662306a36Sopenharmony_ci * header are aligned properly. These 6 bytes are not in the descriptor 28762306a36Sopenharmony_ci * length, they are simply implied. This offset is represented using 28862306a36Sopenharmony_ci * the VNET_PACKET_SKIP macro. 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_cistatic struct sk_buff *alloc_and_align_skb(struct net_device *dev, 29162306a36Sopenharmony_ci unsigned int len) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci struct sk_buff *skb; 29462306a36Sopenharmony_ci unsigned long addr, off; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci skb = netdev_alloc_skb(dev, len + VNET_PACKET_SKIP + 8 + 8); 29762306a36Sopenharmony_ci if (unlikely(!skb)) 29862306a36Sopenharmony_ci return NULL; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci addr = (unsigned long)skb->data; 30162306a36Sopenharmony_ci off = ((addr + 7UL) & ~7UL) - addr; 30262306a36Sopenharmony_ci if (off) 30362306a36Sopenharmony_ci skb_reserve(skb, off); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci return skb; 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cistatic inline void vnet_fullcsum_ipv4(struct sk_buff *skb) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci struct iphdr *iph = ip_hdr(skb); 31162306a36Sopenharmony_ci int offset = skb_transport_offset(skb); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (skb->protocol != htons(ETH_P_IP)) 31462306a36Sopenharmony_ci return; 31562306a36Sopenharmony_ci if (iph->protocol != IPPROTO_TCP && 31662306a36Sopenharmony_ci iph->protocol != IPPROTO_UDP) 31762306a36Sopenharmony_ci return; 31862306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 31962306a36Sopenharmony_ci skb->csum_level = 1; 32062306a36Sopenharmony_ci skb->csum = 0; 32162306a36Sopenharmony_ci if (iph->protocol == IPPROTO_TCP) { 32262306a36Sopenharmony_ci struct tcphdr *ptcp = tcp_hdr(skb); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci ptcp->check = 0; 32562306a36Sopenharmony_ci skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); 32662306a36Sopenharmony_ci ptcp->check = csum_tcpudp_magic(iph->saddr, iph->daddr, 32762306a36Sopenharmony_ci skb->len - offset, IPPROTO_TCP, 32862306a36Sopenharmony_ci skb->csum); 32962306a36Sopenharmony_ci } else if (iph->protocol == IPPROTO_UDP) { 33062306a36Sopenharmony_ci struct udphdr *pudp = udp_hdr(skb); 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci pudp->check = 0; 33362306a36Sopenharmony_ci skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); 33462306a36Sopenharmony_ci pudp->check = csum_tcpudp_magic(iph->saddr, iph->daddr, 33562306a36Sopenharmony_ci skb->len - offset, IPPROTO_UDP, 33662306a36Sopenharmony_ci skb->csum); 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 34162306a36Sopenharmony_cistatic inline void vnet_fullcsum_ipv6(struct sk_buff *skb) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct ipv6hdr *ip6h = ipv6_hdr(skb); 34462306a36Sopenharmony_ci int offset = skb_transport_offset(skb); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci if (skb->protocol != htons(ETH_P_IPV6)) 34762306a36Sopenharmony_ci return; 34862306a36Sopenharmony_ci if (ip6h->nexthdr != IPPROTO_TCP && 34962306a36Sopenharmony_ci ip6h->nexthdr != IPPROTO_UDP) 35062306a36Sopenharmony_ci return; 35162306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 35262306a36Sopenharmony_ci skb->csum_level = 1; 35362306a36Sopenharmony_ci skb->csum = 0; 35462306a36Sopenharmony_ci if (ip6h->nexthdr == IPPROTO_TCP) { 35562306a36Sopenharmony_ci struct tcphdr *ptcp = tcp_hdr(skb); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci ptcp->check = 0; 35862306a36Sopenharmony_ci skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); 35962306a36Sopenharmony_ci ptcp->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, 36062306a36Sopenharmony_ci skb->len - offset, IPPROTO_TCP, 36162306a36Sopenharmony_ci skb->csum); 36262306a36Sopenharmony_ci } else if (ip6h->nexthdr == IPPROTO_UDP) { 36362306a36Sopenharmony_ci struct udphdr *pudp = udp_hdr(skb); 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci pudp->check = 0; 36662306a36Sopenharmony_ci skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); 36762306a36Sopenharmony_ci pudp->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, 36862306a36Sopenharmony_ci skb->len - offset, IPPROTO_UDP, 36962306a36Sopenharmony_ci skb->csum); 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci} 37262306a36Sopenharmony_ci#endif 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_cistatic int vnet_rx_one(struct vnet_port *port, struct vio_net_desc *desc) 37562306a36Sopenharmony_ci{ 37662306a36Sopenharmony_ci struct net_device *dev = VNET_PORT_TO_NET_DEVICE(port); 37762306a36Sopenharmony_ci unsigned int len = desc->size; 37862306a36Sopenharmony_ci unsigned int copy_len; 37962306a36Sopenharmony_ci struct sk_buff *skb; 38062306a36Sopenharmony_ci int maxlen; 38162306a36Sopenharmony_ci int err; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci err = -EMSGSIZE; 38462306a36Sopenharmony_ci if (port->tso && port->tsolen > port->rmtu) 38562306a36Sopenharmony_ci maxlen = port->tsolen; 38662306a36Sopenharmony_ci else 38762306a36Sopenharmony_ci maxlen = port->rmtu; 38862306a36Sopenharmony_ci if (unlikely(len < ETH_ZLEN || len > maxlen)) { 38962306a36Sopenharmony_ci dev->stats.rx_length_errors++; 39062306a36Sopenharmony_ci goto out_dropped; 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci skb = alloc_and_align_skb(dev, len); 39462306a36Sopenharmony_ci err = -ENOMEM; 39562306a36Sopenharmony_ci if (unlikely(!skb)) { 39662306a36Sopenharmony_ci dev->stats.rx_missed_errors++; 39762306a36Sopenharmony_ci goto out_dropped; 39862306a36Sopenharmony_ci } 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci copy_len = (len + VNET_PACKET_SKIP + 7U) & ~7U; 40162306a36Sopenharmony_ci skb_put(skb, copy_len); 40262306a36Sopenharmony_ci err = ldc_copy(port->vio.lp, LDC_COPY_IN, 40362306a36Sopenharmony_ci skb->data, copy_len, 0, 40462306a36Sopenharmony_ci desc->cookies, desc->ncookies); 40562306a36Sopenharmony_ci if (unlikely(err < 0)) { 40662306a36Sopenharmony_ci dev->stats.rx_frame_errors++; 40762306a36Sopenharmony_ci goto out_free_skb; 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci skb_pull(skb, VNET_PACKET_SKIP); 41162306a36Sopenharmony_ci skb_trim(skb, len); 41262306a36Sopenharmony_ci skb->protocol = eth_type_trans(skb, dev); 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci if (vio_version_after_eq(&port->vio, 1, 8)) { 41562306a36Sopenharmony_ci struct vio_net_dext *dext = vio_net_ext(desc); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci skb_reset_network_header(skb); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci if (dext->flags & VNET_PKT_HCK_IPV4_HDRCKSUM) { 42062306a36Sopenharmony_ci if (skb->protocol == ETH_P_IP) { 42162306a36Sopenharmony_ci struct iphdr *iph = ip_hdr(skb); 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci iph->check = 0; 42462306a36Sopenharmony_ci ip_send_check(iph); 42562306a36Sopenharmony_ci } 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci if ((dext->flags & VNET_PKT_HCK_FULLCKSUM) && 42862306a36Sopenharmony_ci skb->ip_summed == CHECKSUM_NONE) { 42962306a36Sopenharmony_ci if (skb->protocol == htons(ETH_P_IP)) { 43062306a36Sopenharmony_ci struct iphdr *iph = ip_hdr(skb); 43162306a36Sopenharmony_ci int ihl = iph->ihl * 4; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci skb_set_transport_header(skb, ihl); 43462306a36Sopenharmony_ci vnet_fullcsum_ipv4(skb); 43562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 43662306a36Sopenharmony_ci } else if (skb->protocol == htons(ETH_P_IPV6)) { 43762306a36Sopenharmony_ci skb_set_transport_header(skb, 43862306a36Sopenharmony_ci sizeof(struct ipv6hdr)); 43962306a36Sopenharmony_ci vnet_fullcsum_ipv6(skb); 44062306a36Sopenharmony_ci#endif 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci } 44362306a36Sopenharmony_ci if (dext->flags & VNET_PKT_HCK_IPV4_HDRCKSUM_OK) { 44462306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_PARTIAL; 44562306a36Sopenharmony_ci skb->csum_level = 0; 44662306a36Sopenharmony_ci if (dext->flags & VNET_PKT_HCK_FULLCKSUM_OK) 44762306a36Sopenharmony_ci skb->csum_level = 1; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci skb->ip_summed = port->switch_port ? CHECKSUM_NONE : CHECKSUM_PARTIAL; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci if (unlikely(is_multicast_ether_addr(eth_hdr(skb)->h_dest))) 45462306a36Sopenharmony_ci dev->stats.multicast++; 45562306a36Sopenharmony_ci dev->stats.rx_packets++; 45662306a36Sopenharmony_ci dev->stats.rx_bytes += len; 45762306a36Sopenharmony_ci port->stats.rx_packets++; 45862306a36Sopenharmony_ci port->stats.rx_bytes += len; 45962306a36Sopenharmony_ci napi_gro_receive(&port->napi, skb); 46062306a36Sopenharmony_ci return 0; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ciout_free_skb: 46362306a36Sopenharmony_ci kfree_skb(skb); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ciout_dropped: 46662306a36Sopenharmony_ci dev->stats.rx_dropped++; 46762306a36Sopenharmony_ci return err; 46862306a36Sopenharmony_ci} 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_cistatic int vnet_send_ack(struct vnet_port *port, struct vio_dring_state *dr, 47162306a36Sopenharmony_ci u32 start, u32 end, u8 vio_dring_state) 47262306a36Sopenharmony_ci{ 47362306a36Sopenharmony_ci struct vio_dring_data hdr = { 47462306a36Sopenharmony_ci .tag = { 47562306a36Sopenharmony_ci .type = VIO_TYPE_DATA, 47662306a36Sopenharmony_ci .stype = VIO_SUBTYPE_ACK, 47762306a36Sopenharmony_ci .stype_env = VIO_DRING_DATA, 47862306a36Sopenharmony_ci .sid = vio_send_sid(&port->vio), 47962306a36Sopenharmony_ci }, 48062306a36Sopenharmony_ci .dring_ident = dr->ident, 48162306a36Sopenharmony_ci .start_idx = start, 48262306a36Sopenharmony_ci .end_idx = end, 48362306a36Sopenharmony_ci .state = vio_dring_state, 48462306a36Sopenharmony_ci }; 48562306a36Sopenharmony_ci int err, delay; 48662306a36Sopenharmony_ci int retries = 0; 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci hdr.seq = dr->snd_nxt; 48962306a36Sopenharmony_ci delay = 1; 49062306a36Sopenharmony_ci do { 49162306a36Sopenharmony_ci err = vio_ldc_send(&port->vio, &hdr, sizeof(hdr)); 49262306a36Sopenharmony_ci if (err > 0) { 49362306a36Sopenharmony_ci dr->snd_nxt++; 49462306a36Sopenharmony_ci break; 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci udelay(delay); 49762306a36Sopenharmony_ci if ((delay <<= 1) > 128) 49862306a36Sopenharmony_ci delay = 128; 49962306a36Sopenharmony_ci if (retries++ > VNET_MAX_RETRIES) { 50062306a36Sopenharmony_ci pr_info("ECONNRESET %x:%x:%x:%x:%x:%x\n", 50162306a36Sopenharmony_ci port->raddr[0], port->raddr[1], 50262306a36Sopenharmony_ci port->raddr[2], port->raddr[3], 50362306a36Sopenharmony_ci port->raddr[4], port->raddr[5]); 50462306a36Sopenharmony_ci break; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci } while (err == -EAGAIN); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci if (err <= 0 && vio_dring_state == VIO_DRING_STOPPED) { 50962306a36Sopenharmony_ci port->stop_rx_idx = end; 51062306a36Sopenharmony_ci port->stop_rx = true; 51162306a36Sopenharmony_ci } else { 51262306a36Sopenharmony_ci port->stop_rx_idx = 0; 51362306a36Sopenharmony_ci port->stop_rx = false; 51462306a36Sopenharmony_ci } 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci return err; 51762306a36Sopenharmony_ci} 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_cistatic struct vio_net_desc *get_rx_desc(struct vnet_port *port, 52062306a36Sopenharmony_ci struct vio_dring_state *dr, 52162306a36Sopenharmony_ci u32 index) 52262306a36Sopenharmony_ci{ 52362306a36Sopenharmony_ci struct vio_net_desc *desc = port->vio.desc_buf; 52462306a36Sopenharmony_ci int err; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci err = ldc_get_dring_entry(port->vio.lp, desc, dr->entry_size, 52762306a36Sopenharmony_ci (index * dr->entry_size), 52862306a36Sopenharmony_ci dr->cookies, dr->ncookies); 52962306a36Sopenharmony_ci if (err < 0) 53062306a36Sopenharmony_ci return ERR_PTR(err); 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci return desc; 53362306a36Sopenharmony_ci} 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_cistatic int put_rx_desc(struct vnet_port *port, 53662306a36Sopenharmony_ci struct vio_dring_state *dr, 53762306a36Sopenharmony_ci struct vio_net_desc *desc, 53862306a36Sopenharmony_ci u32 index) 53962306a36Sopenharmony_ci{ 54062306a36Sopenharmony_ci int err; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci err = ldc_put_dring_entry(port->vio.lp, desc, dr->entry_size, 54362306a36Sopenharmony_ci (index * dr->entry_size), 54462306a36Sopenharmony_ci dr->cookies, dr->ncookies); 54562306a36Sopenharmony_ci if (err < 0) 54662306a36Sopenharmony_ci return err; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci return 0; 54962306a36Sopenharmony_ci} 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_cistatic int vnet_walk_rx_one(struct vnet_port *port, 55262306a36Sopenharmony_ci struct vio_dring_state *dr, 55362306a36Sopenharmony_ci u32 index, int *needs_ack) 55462306a36Sopenharmony_ci{ 55562306a36Sopenharmony_ci struct vio_net_desc *desc = get_rx_desc(port, dr, index); 55662306a36Sopenharmony_ci struct vio_driver_state *vio = &port->vio; 55762306a36Sopenharmony_ci int err; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci BUG_ON(!desc); 56062306a36Sopenharmony_ci if (IS_ERR(desc)) 56162306a36Sopenharmony_ci return PTR_ERR(desc); 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci if (desc->hdr.state != VIO_DESC_READY) 56462306a36Sopenharmony_ci return 1; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci dma_rmb(); 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci viodbg(DATA, "vio_walk_rx_one desc[%02x:%02x:%08x:%08x:%llx:%llx]\n", 56962306a36Sopenharmony_ci desc->hdr.state, desc->hdr.ack, 57062306a36Sopenharmony_ci desc->size, desc->ncookies, 57162306a36Sopenharmony_ci desc->cookies[0].cookie_addr, 57262306a36Sopenharmony_ci desc->cookies[0].cookie_size); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci err = vnet_rx_one(port, desc); 57562306a36Sopenharmony_ci if (err == -ECONNRESET) 57662306a36Sopenharmony_ci return err; 57762306a36Sopenharmony_ci trace_vnet_rx_one(port->vio._local_sid, port->vio._peer_sid, 57862306a36Sopenharmony_ci index, desc->hdr.ack); 57962306a36Sopenharmony_ci desc->hdr.state = VIO_DESC_DONE; 58062306a36Sopenharmony_ci err = put_rx_desc(port, dr, desc, index); 58162306a36Sopenharmony_ci if (err < 0) 58262306a36Sopenharmony_ci return err; 58362306a36Sopenharmony_ci *needs_ack = desc->hdr.ack; 58462306a36Sopenharmony_ci return 0; 58562306a36Sopenharmony_ci} 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_cistatic int vnet_walk_rx(struct vnet_port *port, struct vio_dring_state *dr, 58862306a36Sopenharmony_ci u32 start, u32 end, int *npkts, int budget) 58962306a36Sopenharmony_ci{ 59062306a36Sopenharmony_ci struct vio_driver_state *vio = &port->vio; 59162306a36Sopenharmony_ci int ack_start = -1, ack_end = -1; 59262306a36Sopenharmony_ci bool send_ack = true; 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci end = (end == (u32)-1) ? vio_dring_prev(dr, start) 59562306a36Sopenharmony_ci : vio_dring_next(dr, end); 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci viodbg(DATA, "vnet_walk_rx start[%08x] end[%08x]\n", start, end); 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci while (start != end) { 60062306a36Sopenharmony_ci int ack = 0, err = vnet_walk_rx_one(port, dr, start, &ack); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci if (err == -ECONNRESET) 60362306a36Sopenharmony_ci return err; 60462306a36Sopenharmony_ci if (err != 0) 60562306a36Sopenharmony_ci break; 60662306a36Sopenharmony_ci (*npkts)++; 60762306a36Sopenharmony_ci if (ack_start == -1) 60862306a36Sopenharmony_ci ack_start = start; 60962306a36Sopenharmony_ci ack_end = start; 61062306a36Sopenharmony_ci start = vio_dring_next(dr, start); 61162306a36Sopenharmony_ci if (ack && start != end) { 61262306a36Sopenharmony_ci err = vnet_send_ack(port, dr, ack_start, ack_end, 61362306a36Sopenharmony_ci VIO_DRING_ACTIVE); 61462306a36Sopenharmony_ci if (err == -ECONNRESET) 61562306a36Sopenharmony_ci return err; 61662306a36Sopenharmony_ci ack_start = -1; 61762306a36Sopenharmony_ci } 61862306a36Sopenharmony_ci if ((*npkts) >= budget) { 61962306a36Sopenharmony_ci send_ack = false; 62062306a36Sopenharmony_ci break; 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci } 62362306a36Sopenharmony_ci if (unlikely(ack_start == -1)) { 62462306a36Sopenharmony_ci ack_end = vio_dring_prev(dr, start); 62562306a36Sopenharmony_ci ack_start = ack_end; 62662306a36Sopenharmony_ci } 62762306a36Sopenharmony_ci if (send_ack) { 62862306a36Sopenharmony_ci port->napi_resume = false; 62962306a36Sopenharmony_ci trace_vnet_tx_send_stopped_ack(port->vio._local_sid, 63062306a36Sopenharmony_ci port->vio._peer_sid, 63162306a36Sopenharmony_ci ack_end, *npkts); 63262306a36Sopenharmony_ci return vnet_send_ack(port, dr, ack_start, ack_end, 63362306a36Sopenharmony_ci VIO_DRING_STOPPED); 63462306a36Sopenharmony_ci } else { 63562306a36Sopenharmony_ci trace_vnet_tx_defer_stopped_ack(port->vio._local_sid, 63662306a36Sopenharmony_ci port->vio._peer_sid, 63762306a36Sopenharmony_ci ack_end, *npkts); 63862306a36Sopenharmony_ci port->napi_resume = true; 63962306a36Sopenharmony_ci port->napi_stop_idx = ack_end; 64062306a36Sopenharmony_ci return 1; 64162306a36Sopenharmony_ci } 64262306a36Sopenharmony_ci} 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cistatic int vnet_rx(struct vnet_port *port, void *msgbuf, int *npkts, 64562306a36Sopenharmony_ci int budget) 64662306a36Sopenharmony_ci{ 64762306a36Sopenharmony_ci struct vio_dring_data *pkt = msgbuf; 64862306a36Sopenharmony_ci struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_RX_RING]; 64962306a36Sopenharmony_ci struct vio_driver_state *vio = &port->vio; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci viodbg(DATA, "vnet_rx stype_env[%04x] seq[%016llx] rcv_nxt[%016llx]\n", 65262306a36Sopenharmony_ci pkt->tag.stype_env, pkt->seq, dr->rcv_nxt); 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci if (unlikely(pkt->tag.stype_env != VIO_DRING_DATA)) 65562306a36Sopenharmony_ci return 0; 65662306a36Sopenharmony_ci if (unlikely(pkt->seq != dr->rcv_nxt)) { 65762306a36Sopenharmony_ci pr_err("RX out of sequence seq[0x%llx] rcv_nxt[0x%llx]\n", 65862306a36Sopenharmony_ci pkt->seq, dr->rcv_nxt); 65962306a36Sopenharmony_ci return 0; 66062306a36Sopenharmony_ci } 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci if (!port->napi_resume) 66362306a36Sopenharmony_ci dr->rcv_nxt++; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci /* XXX Validate pkt->start_idx and pkt->end_idx XXX */ 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci return vnet_walk_rx(port, dr, pkt->start_idx, pkt->end_idx, 66862306a36Sopenharmony_ci npkts, budget); 66962306a36Sopenharmony_ci} 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_cistatic int idx_is_pending(struct vio_dring_state *dr, u32 end) 67262306a36Sopenharmony_ci{ 67362306a36Sopenharmony_ci u32 idx = dr->cons; 67462306a36Sopenharmony_ci int found = 0; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci while (idx != dr->prod) { 67762306a36Sopenharmony_ci if (idx == end) { 67862306a36Sopenharmony_ci found = 1; 67962306a36Sopenharmony_ci break; 68062306a36Sopenharmony_ci } 68162306a36Sopenharmony_ci idx = vio_dring_next(dr, idx); 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci return found; 68462306a36Sopenharmony_ci} 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_cistatic int vnet_ack(struct vnet_port *port, void *msgbuf) 68762306a36Sopenharmony_ci{ 68862306a36Sopenharmony_ci struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 68962306a36Sopenharmony_ci struct vio_dring_data *pkt = msgbuf; 69062306a36Sopenharmony_ci struct net_device *dev; 69162306a36Sopenharmony_ci u32 end; 69262306a36Sopenharmony_ci struct vio_net_desc *desc; 69362306a36Sopenharmony_ci struct netdev_queue *txq; 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci if (unlikely(pkt->tag.stype_env != VIO_DRING_DATA)) 69662306a36Sopenharmony_ci return 0; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci end = pkt->end_idx; 69962306a36Sopenharmony_ci dev = VNET_PORT_TO_NET_DEVICE(port); 70062306a36Sopenharmony_ci netif_tx_lock(dev); 70162306a36Sopenharmony_ci if (unlikely(!idx_is_pending(dr, end))) { 70262306a36Sopenharmony_ci netif_tx_unlock(dev); 70362306a36Sopenharmony_ci return 0; 70462306a36Sopenharmony_ci } 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci /* sync for race conditions with vnet_start_xmit() and tell xmit it 70762306a36Sopenharmony_ci * is time to send a trigger. 70862306a36Sopenharmony_ci */ 70962306a36Sopenharmony_ci trace_vnet_rx_stopped_ack(port->vio._local_sid, 71062306a36Sopenharmony_ci port->vio._peer_sid, end); 71162306a36Sopenharmony_ci dr->cons = vio_dring_next(dr, end); 71262306a36Sopenharmony_ci desc = vio_dring_entry(dr, dr->cons); 71362306a36Sopenharmony_ci if (desc->hdr.state == VIO_DESC_READY && !port->start_cons) { 71462306a36Sopenharmony_ci /* vnet_start_xmit() just populated this dring but missed 71562306a36Sopenharmony_ci * sending the "start" LDC message to the consumer. 71662306a36Sopenharmony_ci * Send a "start" trigger on its behalf. 71762306a36Sopenharmony_ci */ 71862306a36Sopenharmony_ci if (__vnet_tx_trigger(port, dr->cons) > 0) 71962306a36Sopenharmony_ci port->start_cons = false; 72062306a36Sopenharmony_ci else 72162306a36Sopenharmony_ci port->start_cons = true; 72262306a36Sopenharmony_ci } else { 72362306a36Sopenharmony_ci port->start_cons = true; 72462306a36Sopenharmony_ci } 72562306a36Sopenharmony_ci netif_tx_unlock(dev); 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci txq = netdev_get_tx_queue(dev, port->q_index); 72862306a36Sopenharmony_ci if (unlikely(netif_tx_queue_stopped(txq) && 72962306a36Sopenharmony_ci vnet_tx_dring_avail(dr) >= VNET_TX_WAKEUP_THRESH(dr))) 73062306a36Sopenharmony_ci return 1; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci return 0; 73362306a36Sopenharmony_ci} 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_cistatic int vnet_nack(struct vnet_port *port, void *msgbuf) 73662306a36Sopenharmony_ci{ 73762306a36Sopenharmony_ci /* XXX just reset or similar XXX */ 73862306a36Sopenharmony_ci return 0; 73962306a36Sopenharmony_ci} 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_cistatic int handle_mcast(struct vnet_port *port, void *msgbuf) 74262306a36Sopenharmony_ci{ 74362306a36Sopenharmony_ci struct vio_net_mcast_info *pkt = msgbuf; 74462306a36Sopenharmony_ci struct net_device *dev = VNET_PORT_TO_NET_DEVICE(port); 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci if (pkt->tag.stype != VIO_SUBTYPE_ACK) 74762306a36Sopenharmony_ci pr_err("%s: Got unexpected MCAST reply [%02x:%02x:%04x:%08x]\n", 74862306a36Sopenharmony_ci dev->name, 74962306a36Sopenharmony_ci pkt->tag.type, 75062306a36Sopenharmony_ci pkt->tag.stype, 75162306a36Sopenharmony_ci pkt->tag.stype_env, 75262306a36Sopenharmony_ci pkt->tag.sid); 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci return 0; 75562306a36Sopenharmony_ci} 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci/* If the queue is stopped, wake it up so that we'll 75862306a36Sopenharmony_ci * send out another START message at the next TX. 75962306a36Sopenharmony_ci */ 76062306a36Sopenharmony_cistatic void maybe_tx_wakeup(struct vnet_port *port) 76162306a36Sopenharmony_ci{ 76262306a36Sopenharmony_ci struct netdev_queue *txq; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci txq = netdev_get_tx_queue(VNET_PORT_TO_NET_DEVICE(port), 76562306a36Sopenharmony_ci port->q_index); 76662306a36Sopenharmony_ci __netif_tx_lock(txq, smp_processor_id()); 76762306a36Sopenharmony_ci if (likely(netif_tx_queue_stopped(txq))) 76862306a36Sopenharmony_ci netif_tx_wake_queue(txq); 76962306a36Sopenharmony_ci __netif_tx_unlock(txq); 77062306a36Sopenharmony_ci} 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_cibool sunvnet_port_is_up_common(struct vnet_port *vnet) 77362306a36Sopenharmony_ci{ 77462306a36Sopenharmony_ci struct vio_driver_state *vio = &vnet->vio; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci return !!(vio->hs_state & VIO_HS_COMPLETE); 77762306a36Sopenharmony_ci} 77862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_port_is_up_common); 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_cistatic int vnet_event_napi(struct vnet_port *port, int budget) 78162306a36Sopenharmony_ci{ 78262306a36Sopenharmony_ci struct net_device *dev = VNET_PORT_TO_NET_DEVICE(port); 78362306a36Sopenharmony_ci struct vio_driver_state *vio = &port->vio; 78462306a36Sopenharmony_ci int tx_wakeup, err; 78562306a36Sopenharmony_ci int npkts = 0; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci /* we don't expect any other bits */ 78862306a36Sopenharmony_ci BUG_ON(port->rx_event & ~(LDC_EVENT_DATA_READY | 78962306a36Sopenharmony_ci LDC_EVENT_RESET | 79062306a36Sopenharmony_ci LDC_EVENT_UP)); 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci /* RESET takes precedent over any other event */ 79362306a36Sopenharmony_ci if (port->rx_event & LDC_EVENT_RESET) { 79462306a36Sopenharmony_ci /* a link went down */ 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci if (port->vsw == 1) { 79762306a36Sopenharmony_ci netif_tx_stop_all_queues(dev); 79862306a36Sopenharmony_ci netif_carrier_off(dev); 79962306a36Sopenharmony_ci } 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_ci vio_link_state_change(vio, LDC_EVENT_RESET); 80262306a36Sopenharmony_ci vnet_port_reset(port); 80362306a36Sopenharmony_ci vio_port_up(vio); 80462306a36Sopenharmony_ci 80562306a36Sopenharmony_ci /* If the device is running but its tx queue was 80662306a36Sopenharmony_ci * stopped (due to flow control), restart it. 80762306a36Sopenharmony_ci * This is necessary since vnet_port_reset() 80862306a36Sopenharmony_ci * clears the tx drings and thus we may never get 80962306a36Sopenharmony_ci * back a VIO_TYPE_DATA ACK packet - which is 81062306a36Sopenharmony_ci * the normal mechanism to restart the tx queue. 81162306a36Sopenharmony_ci */ 81262306a36Sopenharmony_ci if (netif_running(dev)) 81362306a36Sopenharmony_ci maybe_tx_wakeup(port); 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci port->rx_event = 0; 81662306a36Sopenharmony_ci port->stats.event_reset++; 81762306a36Sopenharmony_ci return 0; 81862306a36Sopenharmony_ci } 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ci if (port->rx_event & LDC_EVENT_UP) { 82162306a36Sopenharmony_ci /* a link came up */ 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci if (port->vsw == 1) { 82462306a36Sopenharmony_ci netif_carrier_on(port->dev); 82562306a36Sopenharmony_ci netif_tx_start_all_queues(port->dev); 82662306a36Sopenharmony_ci } 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci vio_link_state_change(vio, LDC_EVENT_UP); 82962306a36Sopenharmony_ci port->rx_event = 0; 83062306a36Sopenharmony_ci port->stats.event_up++; 83162306a36Sopenharmony_ci return 0; 83262306a36Sopenharmony_ci } 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci err = 0; 83562306a36Sopenharmony_ci tx_wakeup = 0; 83662306a36Sopenharmony_ci while (1) { 83762306a36Sopenharmony_ci union { 83862306a36Sopenharmony_ci struct vio_msg_tag tag; 83962306a36Sopenharmony_ci u64 raw[8]; 84062306a36Sopenharmony_ci } msgbuf; 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci if (port->napi_resume) { 84362306a36Sopenharmony_ci struct vio_dring_data *pkt = 84462306a36Sopenharmony_ci (struct vio_dring_data *)&msgbuf; 84562306a36Sopenharmony_ci struct vio_dring_state *dr = 84662306a36Sopenharmony_ci &port->vio.drings[VIO_DRIVER_RX_RING]; 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci pkt->tag.type = VIO_TYPE_DATA; 84962306a36Sopenharmony_ci pkt->tag.stype = VIO_SUBTYPE_INFO; 85062306a36Sopenharmony_ci pkt->tag.stype_env = VIO_DRING_DATA; 85162306a36Sopenharmony_ci pkt->seq = dr->rcv_nxt; 85262306a36Sopenharmony_ci pkt->start_idx = vio_dring_next(dr, 85362306a36Sopenharmony_ci port->napi_stop_idx); 85462306a36Sopenharmony_ci pkt->end_idx = -1; 85562306a36Sopenharmony_ci } else { 85662306a36Sopenharmony_ci err = ldc_read(vio->lp, &msgbuf, sizeof(msgbuf)); 85762306a36Sopenharmony_ci if (unlikely(err < 0)) { 85862306a36Sopenharmony_ci if (err == -ECONNRESET) 85962306a36Sopenharmony_ci vio_conn_reset(vio); 86062306a36Sopenharmony_ci break; 86162306a36Sopenharmony_ci } 86262306a36Sopenharmony_ci if (err == 0) 86362306a36Sopenharmony_ci break; 86462306a36Sopenharmony_ci viodbg(DATA, "TAG [%02x:%02x:%04x:%08x]\n", 86562306a36Sopenharmony_ci msgbuf.tag.type, 86662306a36Sopenharmony_ci msgbuf.tag.stype, 86762306a36Sopenharmony_ci msgbuf.tag.stype_env, 86862306a36Sopenharmony_ci msgbuf.tag.sid); 86962306a36Sopenharmony_ci err = vio_validate_sid(vio, &msgbuf.tag); 87062306a36Sopenharmony_ci if (err < 0) 87162306a36Sopenharmony_ci break; 87262306a36Sopenharmony_ci } 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci if (likely(msgbuf.tag.type == VIO_TYPE_DATA)) { 87562306a36Sopenharmony_ci if (msgbuf.tag.stype == VIO_SUBTYPE_INFO) { 87662306a36Sopenharmony_ci if (!sunvnet_port_is_up_common(port)) { 87762306a36Sopenharmony_ci /* failures like handshake_failure() 87862306a36Sopenharmony_ci * may have cleaned up dring, but 87962306a36Sopenharmony_ci * NAPI polling may bring us here. 88062306a36Sopenharmony_ci */ 88162306a36Sopenharmony_ci err = -ECONNRESET; 88262306a36Sopenharmony_ci break; 88362306a36Sopenharmony_ci } 88462306a36Sopenharmony_ci err = vnet_rx(port, &msgbuf, &npkts, budget); 88562306a36Sopenharmony_ci if (npkts >= budget) 88662306a36Sopenharmony_ci break; 88762306a36Sopenharmony_ci if (npkts == 0) 88862306a36Sopenharmony_ci break; 88962306a36Sopenharmony_ci } else if (msgbuf.tag.stype == VIO_SUBTYPE_ACK) { 89062306a36Sopenharmony_ci err = vnet_ack(port, &msgbuf); 89162306a36Sopenharmony_ci if (err > 0) 89262306a36Sopenharmony_ci tx_wakeup |= err; 89362306a36Sopenharmony_ci } else if (msgbuf.tag.stype == VIO_SUBTYPE_NACK) { 89462306a36Sopenharmony_ci err = vnet_nack(port, &msgbuf); 89562306a36Sopenharmony_ci } 89662306a36Sopenharmony_ci } else if (msgbuf.tag.type == VIO_TYPE_CTRL) { 89762306a36Sopenharmony_ci if (msgbuf.tag.stype_env == VNET_MCAST_INFO) 89862306a36Sopenharmony_ci err = handle_mcast(port, &msgbuf); 89962306a36Sopenharmony_ci else 90062306a36Sopenharmony_ci err = vio_control_pkt_engine(vio, &msgbuf); 90162306a36Sopenharmony_ci if (err) 90262306a36Sopenharmony_ci break; 90362306a36Sopenharmony_ci } else { 90462306a36Sopenharmony_ci err = vnet_handle_unknown(port, &msgbuf); 90562306a36Sopenharmony_ci } 90662306a36Sopenharmony_ci if (err == -ECONNRESET) 90762306a36Sopenharmony_ci break; 90862306a36Sopenharmony_ci } 90962306a36Sopenharmony_ci if (unlikely(tx_wakeup && err != -ECONNRESET)) 91062306a36Sopenharmony_ci maybe_tx_wakeup(port); 91162306a36Sopenharmony_ci return npkts; 91262306a36Sopenharmony_ci} 91362306a36Sopenharmony_ci 91462306a36Sopenharmony_ciint sunvnet_poll_common(struct napi_struct *napi, int budget) 91562306a36Sopenharmony_ci{ 91662306a36Sopenharmony_ci struct vnet_port *port = container_of(napi, struct vnet_port, napi); 91762306a36Sopenharmony_ci struct vio_driver_state *vio = &port->vio; 91862306a36Sopenharmony_ci int processed = vnet_event_napi(port, budget); 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci if (processed < budget) { 92162306a36Sopenharmony_ci napi_complete_done(napi, processed); 92262306a36Sopenharmony_ci port->rx_event &= ~LDC_EVENT_DATA_READY; 92362306a36Sopenharmony_ci vio_set_intr(vio->vdev->rx_ino, HV_INTR_ENABLED); 92462306a36Sopenharmony_ci } 92562306a36Sopenharmony_ci return processed; 92662306a36Sopenharmony_ci} 92762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_poll_common); 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_civoid sunvnet_event_common(void *arg, int event) 93062306a36Sopenharmony_ci{ 93162306a36Sopenharmony_ci struct vnet_port *port = arg; 93262306a36Sopenharmony_ci struct vio_driver_state *vio = &port->vio; 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci port->rx_event |= event; 93562306a36Sopenharmony_ci vio_set_intr(vio->vdev->rx_ino, HV_INTR_DISABLED); 93662306a36Sopenharmony_ci napi_schedule(&port->napi); 93762306a36Sopenharmony_ci} 93862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_event_common); 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_cistatic int __vnet_tx_trigger(struct vnet_port *port, u32 start) 94162306a36Sopenharmony_ci{ 94262306a36Sopenharmony_ci struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 94362306a36Sopenharmony_ci struct vio_dring_data hdr = { 94462306a36Sopenharmony_ci .tag = { 94562306a36Sopenharmony_ci .type = VIO_TYPE_DATA, 94662306a36Sopenharmony_ci .stype = VIO_SUBTYPE_INFO, 94762306a36Sopenharmony_ci .stype_env = VIO_DRING_DATA, 94862306a36Sopenharmony_ci .sid = vio_send_sid(&port->vio), 94962306a36Sopenharmony_ci }, 95062306a36Sopenharmony_ci .dring_ident = dr->ident, 95162306a36Sopenharmony_ci .start_idx = start, 95262306a36Sopenharmony_ci .end_idx = (u32)-1, 95362306a36Sopenharmony_ci }; 95462306a36Sopenharmony_ci int err, delay; 95562306a36Sopenharmony_ci int retries = 0; 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci if (port->stop_rx) { 95862306a36Sopenharmony_ci trace_vnet_tx_pending_stopped_ack(port->vio._local_sid, 95962306a36Sopenharmony_ci port->vio._peer_sid, 96062306a36Sopenharmony_ci port->stop_rx_idx, -1); 96162306a36Sopenharmony_ci err = vnet_send_ack(port, 96262306a36Sopenharmony_ci &port->vio.drings[VIO_DRIVER_RX_RING], 96362306a36Sopenharmony_ci port->stop_rx_idx, -1, 96462306a36Sopenharmony_ci VIO_DRING_STOPPED); 96562306a36Sopenharmony_ci if (err <= 0) 96662306a36Sopenharmony_ci return err; 96762306a36Sopenharmony_ci } 96862306a36Sopenharmony_ci 96962306a36Sopenharmony_ci hdr.seq = dr->snd_nxt; 97062306a36Sopenharmony_ci delay = 1; 97162306a36Sopenharmony_ci do { 97262306a36Sopenharmony_ci err = vio_ldc_send(&port->vio, &hdr, sizeof(hdr)); 97362306a36Sopenharmony_ci if (err > 0) { 97462306a36Sopenharmony_ci dr->snd_nxt++; 97562306a36Sopenharmony_ci break; 97662306a36Sopenharmony_ci } 97762306a36Sopenharmony_ci udelay(delay); 97862306a36Sopenharmony_ci if ((delay <<= 1) > 128) 97962306a36Sopenharmony_ci delay = 128; 98062306a36Sopenharmony_ci if (retries++ > VNET_MAX_RETRIES) 98162306a36Sopenharmony_ci break; 98262306a36Sopenharmony_ci } while (err == -EAGAIN); 98362306a36Sopenharmony_ci trace_vnet_tx_trigger(port->vio._local_sid, 98462306a36Sopenharmony_ci port->vio._peer_sid, start, err); 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci return err; 98762306a36Sopenharmony_ci} 98862306a36Sopenharmony_ci 98962306a36Sopenharmony_cistatic struct sk_buff *vnet_clean_tx_ring(struct vnet_port *port, 99062306a36Sopenharmony_ci unsigned *pending) 99162306a36Sopenharmony_ci{ 99262306a36Sopenharmony_ci struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 99362306a36Sopenharmony_ci struct sk_buff *skb = NULL; 99462306a36Sopenharmony_ci int i, txi; 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci *pending = 0; 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci txi = dr->prod; 99962306a36Sopenharmony_ci for (i = 0; i < VNET_TX_RING_SIZE; ++i) { 100062306a36Sopenharmony_ci struct vio_net_desc *d; 100162306a36Sopenharmony_ci 100262306a36Sopenharmony_ci --txi; 100362306a36Sopenharmony_ci if (txi < 0) 100462306a36Sopenharmony_ci txi = VNET_TX_RING_SIZE - 1; 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci d = vio_dring_entry(dr, txi); 100762306a36Sopenharmony_ci 100862306a36Sopenharmony_ci if (d->hdr.state == VIO_DESC_READY) { 100962306a36Sopenharmony_ci (*pending)++; 101062306a36Sopenharmony_ci continue; 101162306a36Sopenharmony_ci } 101262306a36Sopenharmony_ci if (port->tx_bufs[txi].skb) { 101362306a36Sopenharmony_ci if (d->hdr.state != VIO_DESC_DONE) 101462306a36Sopenharmony_ci pr_notice("invalid ring buffer state %d\n", 101562306a36Sopenharmony_ci d->hdr.state); 101662306a36Sopenharmony_ci BUG_ON(port->tx_bufs[txi].skb->next); 101762306a36Sopenharmony_ci 101862306a36Sopenharmony_ci port->tx_bufs[txi].skb->next = skb; 101962306a36Sopenharmony_ci skb = port->tx_bufs[txi].skb; 102062306a36Sopenharmony_ci port->tx_bufs[txi].skb = NULL; 102162306a36Sopenharmony_ci 102262306a36Sopenharmony_ci ldc_unmap(port->vio.lp, 102362306a36Sopenharmony_ci port->tx_bufs[txi].cookies, 102462306a36Sopenharmony_ci port->tx_bufs[txi].ncookies); 102562306a36Sopenharmony_ci } else if (d->hdr.state == VIO_DESC_FREE) { 102662306a36Sopenharmony_ci break; 102762306a36Sopenharmony_ci } 102862306a36Sopenharmony_ci d->hdr.state = VIO_DESC_FREE; 102962306a36Sopenharmony_ci } 103062306a36Sopenharmony_ci return skb; 103162306a36Sopenharmony_ci} 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_cistatic inline void vnet_free_skbs(struct sk_buff *skb) 103462306a36Sopenharmony_ci{ 103562306a36Sopenharmony_ci struct sk_buff *next; 103662306a36Sopenharmony_ci 103762306a36Sopenharmony_ci while (skb) { 103862306a36Sopenharmony_ci next = skb->next; 103962306a36Sopenharmony_ci skb->next = NULL; 104062306a36Sopenharmony_ci dev_kfree_skb(skb); 104162306a36Sopenharmony_ci skb = next; 104262306a36Sopenharmony_ci } 104362306a36Sopenharmony_ci} 104462306a36Sopenharmony_ci 104562306a36Sopenharmony_civoid sunvnet_clean_timer_expire_common(struct timer_list *t) 104662306a36Sopenharmony_ci{ 104762306a36Sopenharmony_ci struct vnet_port *port = from_timer(port, t, clean_timer); 104862306a36Sopenharmony_ci struct sk_buff *freeskbs; 104962306a36Sopenharmony_ci unsigned pending; 105062306a36Sopenharmony_ci 105162306a36Sopenharmony_ci netif_tx_lock(VNET_PORT_TO_NET_DEVICE(port)); 105262306a36Sopenharmony_ci freeskbs = vnet_clean_tx_ring(port, &pending); 105362306a36Sopenharmony_ci netif_tx_unlock(VNET_PORT_TO_NET_DEVICE(port)); 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_ci vnet_free_skbs(freeskbs); 105662306a36Sopenharmony_ci 105762306a36Sopenharmony_ci if (pending) 105862306a36Sopenharmony_ci (void)mod_timer(&port->clean_timer, 105962306a36Sopenharmony_ci jiffies + VNET_CLEAN_TIMEOUT); 106062306a36Sopenharmony_ci else 106162306a36Sopenharmony_ci del_timer(&port->clean_timer); 106262306a36Sopenharmony_ci} 106362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_clean_timer_expire_common); 106462306a36Sopenharmony_ci 106562306a36Sopenharmony_cistatic inline int vnet_skb_map(struct ldc_channel *lp, struct sk_buff *skb, 106662306a36Sopenharmony_ci struct ldc_trans_cookie *cookies, int ncookies, 106762306a36Sopenharmony_ci unsigned int map_perm) 106862306a36Sopenharmony_ci{ 106962306a36Sopenharmony_ci int i, nc, err, blen; 107062306a36Sopenharmony_ci 107162306a36Sopenharmony_ci /* header */ 107262306a36Sopenharmony_ci blen = skb_headlen(skb); 107362306a36Sopenharmony_ci if (blen < ETH_ZLEN) 107462306a36Sopenharmony_ci blen = ETH_ZLEN; 107562306a36Sopenharmony_ci blen += VNET_PACKET_SKIP; 107662306a36Sopenharmony_ci blen += 8 - (blen & 7); 107762306a36Sopenharmony_ci 107862306a36Sopenharmony_ci err = ldc_map_single(lp, skb->data - VNET_PACKET_SKIP, blen, cookies, 107962306a36Sopenharmony_ci ncookies, map_perm); 108062306a36Sopenharmony_ci if (err < 0) 108162306a36Sopenharmony_ci return err; 108262306a36Sopenharmony_ci nc = err; 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_ci for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { 108562306a36Sopenharmony_ci skb_frag_t *f = &skb_shinfo(skb)->frags[i]; 108662306a36Sopenharmony_ci u8 *vaddr; 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_ci if (nc < ncookies) { 108962306a36Sopenharmony_ci vaddr = kmap_local_page(skb_frag_page(f)); 109062306a36Sopenharmony_ci blen = skb_frag_size(f); 109162306a36Sopenharmony_ci blen += 8 - (blen & 7); 109262306a36Sopenharmony_ci err = ldc_map_single(lp, vaddr + skb_frag_off(f), 109362306a36Sopenharmony_ci blen, cookies + nc, ncookies - nc, 109462306a36Sopenharmony_ci map_perm); 109562306a36Sopenharmony_ci kunmap_local(vaddr); 109662306a36Sopenharmony_ci } else { 109762306a36Sopenharmony_ci err = -EMSGSIZE; 109862306a36Sopenharmony_ci } 109962306a36Sopenharmony_ci 110062306a36Sopenharmony_ci if (err < 0) { 110162306a36Sopenharmony_ci ldc_unmap(lp, cookies, nc); 110262306a36Sopenharmony_ci return err; 110362306a36Sopenharmony_ci } 110462306a36Sopenharmony_ci nc += err; 110562306a36Sopenharmony_ci } 110662306a36Sopenharmony_ci return nc; 110762306a36Sopenharmony_ci} 110862306a36Sopenharmony_ci 110962306a36Sopenharmony_cistatic inline struct sk_buff *vnet_skb_shape(struct sk_buff *skb, int ncookies) 111062306a36Sopenharmony_ci{ 111162306a36Sopenharmony_ci struct sk_buff *nskb; 111262306a36Sopenharmony_ci int i, len, pad, docopy; 111362306a36Sopenharmony_ci 111462306a36Sopenharmony_ci len = skb->len; 111562306a36Sopenharmony_ci pad = 0; 111662306a36Sopenharmony_ci if (len < ETH_ZLEN) { 111762306a36Sopenharmony_ci pad += ETH_ZLEN - skb->len; 111862306a36Sopenharmony_ci len += pad; 111962306a36Sopenharmony_ci } 112062306a36Sopenharmony_ci len += VNET_PACKET_SKIP; 112162306a36Sopenharmony_ci pad += 8 - (len & 7); 112262306a36Sopenharmony_ci 112362306a36Sopenharmony_ci /* make sure we have enough cookies and alignment in every frag */ 112462306a36Sopenharmony_ci docopy = skb_shinfo(skb)->nr_frags >= ncookies; 112562306a36Sopenharmony_ci for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { 112662306a36Sopenharmony_ci skb_frag_t *f = &skb_shinfo(skb)->frags[i]; 112762306a36Sopenharmony_ci 112862306a36Sopenharmony_ci docopy |= skb_frag_off(f) & 7; 112962306a36Sopenharmony_ci } 113062306a36Sopenharmony_ci if (((unsigned long)skb->data & 7) != VNET_PACKET_SKIP || 113162306a36Sopenharmony_ci skb_tailroom(skb) < pad || 113262306a36Sopenharmony_ci skb_headroom(skb) < VNET_PACKET_SKIP || docopy) { 113362306a36Sopenharmony_ci int start = 0, offset; 113462306a36Sopenharmony_ci __wsum csum; 113562306a36Sopenharmony_ci 113662306a36Sopenharmony_ci len = skb->len > ETH_ZLEN ? skb->len : ETH_ZLEN; 113762306a36Sopenharmony_ci nskb = alloc_and_align_skb(skb->dev, len); 113862306a36Sopenharmony_ci if (!nskb) { 113962306a36Sopenharmony_ci dev_kfree_skb(skb); 114062306a36Sopenharmony_ci return NULL; 114162306a36Sopenharmony_ci } 114262306a36Sopenharmony_ci skb_reserve(nskb, VNET_PACKET_SKIP); 114362306a36Sopenharmony_ci 114462306a36Sopenharmony_ci nskb->protocol = skb->protocol; 114562306a36Sopenharmony_ci offset = skb_mac_header(skb) - skb->data; 114662306a36Sopenharmony_ci skb_set_mac_header(nskb, offset); 114762306a36Sopenharmony_ci offset = skb_network_header(skb) - skb->data; 114862306a36Sopenharmony_ci skb_set_network_header(nskb, offset); 114962306a36Sopenharmony_ci offset = skb_transport_header(skb) - skb->data; 115062306a36Sopenharmony_ci skb_set_transport_header(nskb, offset); 115162306a36Sopenharmony_ci 115262306a36Sopenharmony_ci offset = 0; 115362306a36Sopenharmony_ci nskb->csum_offset = skb->csum_offset; 115462306a36Sopenharmony_ci nskb->ip_summed = skb->ip_summed; 115562306a36Sopenharmony_ci 115662306a36Sopenharmony_ci if (skb->ip_summed == CHECKSUM_PARTIAL) 115762306a36Sopenharmony_ci start = skb_checksum_start_offset(skb); 115862306a36Sopenharmony_ci if (start) { 115962306a36Sopenharmony_ci int offset = start + nskb->csum_offset; 116062306a36Sopenharmony_ci 116162306a36Sopenharmony_ci /* copy the headers, no csum here */ 116262306a36Sopenharmony_ci if (skb_copy_bits(skb, 0, nskb->data, start)) { 116362306a36Sopenharmony_ci dev_kfree_skb(nskb); 116462306a36Sopenharmony_ci dev_kfree_skb(skb); 116562306a36Sopenharmony_ci return NULL; 116662306a36Sopenharmony_ci } 116762306a36Sopenharmony_ci 116862306a36Sopenharmony_ci /* copy the rest, with csum calculation */ 116962306a36Sopenharmony_ci *(__sum16 *)(skb->data + offset) = 0; 117062306a36Sopenharmony_ci csum = skb_copy_and_csum_bits(skb, start, 117162306a36Sopenharmony_ci nskb->data + start, 117262306a36Sopenharmony_ci skb->len - start); 117362306a36Sopenharmony_ci 117462306a36Sopenharmony_ci /* add in the header checksums */ 117562306a36Sopenharmony_ci if (skb->protocol == htons(ETH_P_IP)) { 117662306a36Sopenharmony_ci struct iphdr *iph = ip_hdr(nskb); 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci if (iph->protocol == IPPROTO_TCP || 117962306a36Sopenharmony_ci iph->protocol == IPPROTO_UDP) { 118062306a36Sopenharmony_ci csum = csum_tcpudp_magic(iph->saddr, 118162306a36Sopenharmony_ci iph->daddr, 118262306a36Sopenharmony_ci skb->len - start, 118362306a36Sopenharmony_ci iph->protocol, 118462306a36Sopenharmony_ci csum); 118562306a36Sopenharmony_ci } 118662306a36Sopenharmony_ci } else if (skb->protocol == htons(ETH_P_IPV6)) { 118762306a36Sopenharmony_ci struct ipv6hdr *ip6h = ipv6_hdr(nskb); 118862306a36Sopenharmony_ci 118962306a36Sopenharmony_ci if (ip6h->nexthdr == IPPROTO_TCP || 119062306a36Sopenharmony_ci ip6h->nexthdr == IPPROTO_UDP) { 119162306a36Sopenharmony_ci csum = csum_ipv6_magic(&ip6h->saddr, 119262306a36Sopenharmony_ci &ip6h->daddr, 119362306a36Sopenharmony_ci skb->len - start, 119462306a36Sopenharmony_ci ip6h->nexthdr, 119562306a36Sopenharmony_ci csum); 119662306a36Sopenharmony_ci } 119762306a36Sopenharmony_ci } 119862306a36Sopenharmony_ci 119962306a36Sopenharmony_ci /* save the final result */ 120062306a36Sopenharmony_ci *(__sum16 *)(nskb->data + offset) = csum; 120162306a36Sopenharmony_ci 120262306a36Sopenharmony_ci nskb->ip_summed = CHECKSUM_NONE; 120362306a36Sopenharmony_ci } else if (skb_copy_bits(skb, 0, nskb->data, skb->len)) { 120462306a36Sopenharmony_ci dev_kfree_skb(nskb); 120562306a36Sopenharmony_ci dev_kfree_skb(skb); 120662306a36Sopenharmony_ci return NULL; 120762306a36Sopenharmony_ci } 120862306a36Sopenharmony_ci (void)skb_put(nskb, skb->len); 120962306a36Sopenharmony_ci if (skb_is_gso(skb)) { 121062306a36Sopenharmony_ci skb_shinfo(nskb)->gso_size = skb_shinfo(skb)->gso_size; 121162306a36Sopenharmony_ci skb_shinfo(nskb)->gso_type = skb_shinfo(skb)->gso_type; 121262306a36Sopenharmony_ci } 121362306a36Sopenharmony_ci nskb->queue_mapping = skb->queue_mapping; 121462306a36Sopenharmony_ci dev_kfree_skb(skb); 121562306a36Sopenharmony_ci skb = nskb; 121662306a36Sopenharmony_ci } 121762306a36Sopenharmony_ci return skb; 121862306a36Sopenharmony_ci} 121962306a36Sopenharmony_ci 122062306a36Sopenharmony_cistatic netdev_tx_t 122162306a36Sopenharmony_civnet_handle_offloads(struct vnet_port *port, struct sk_buff *skb, 122262306a36Sopenharmony_ci struct vnet_port *(*vnet_tx_port) 122362306a36Sopenharmony_ci (struct sk_buff *, struct net_device *)) 122462306a36Sopenharmony_ci{ 122562306a36Sopenharmony_ci struct net_device *dev = VNET_PORT_TO_NET_DEVICE(port); 122662306a36Sopenharmony_ci struct vio_dring_state *dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 122762306a36Sopenharmony_ci struct sk_buff *segs, *curr, *next; 122862306a36Sopenharmony_ci int maclen, datalen; 122962306a36Sopenharmony_ci int status; 123062306a36Sopenharmony_ci int gso_size, gso_type, gso_segs; 123162306a36Sopenharmony_ci int hlen = skb_transport_header(skb) - skb_mac_header(skb); 123262306a36Sopenharmony_ci int proto = IPPROTO_IP; 123362306a36Sopenharmony_ci 123462306a36Sopenharmony_ci if (skb->protocol == htons(ETH_P_IP)) 123562306a36Sopenharmony_ci proto = ip_hdr(skb)->protocol; 123662306a36Sopenharmony_ci else if (skb->protocol == htons(ETH_P_IPV6)) 123762306a36Sopenharmony_ci proto = ipv6_hdr(skb)->nexthdr; 123862306a36Sopenharmony_ci 123962306a36Sopenharmony_ci if (proto == IPPROTO_TCP) { 124062306a36Sopenharmony_ci hlen += tcp_hdr(skb)->doff * 4; 124162306a36Sopenharmony_ci } else if (proto == IPPROTO_UDP) { 124262306a36Sopenharmony_ci hlen += sizeof(struct udphdr); 124362306a36Sopenharmony_ci } else { 124462306a36Sopenharmony_ci pr_err("vnet_handle_offloads GSO with unknown transport " 124562306a36Sopenharmony_ci "protocol %d tproto %d\n", skb->protocol, proto); 124662306a36Sopenharmony_ci hlen = 128; /* XXX */ 124762306a36Sopenharmony_ci } 124862306a36Sopenharmony_ci datalen = port->tsolen - hlen; 124962306a36Sopenharmony_ci 125062306a36Sopenharmony_ci gso_size = skb_shinfo(skb)->gso_size; 125162306a36Sopenharmony_ci gso_type = skb_shinfo(skb)->gso_type; 125262306a36Sopenharmony_ci gso_segs = skb_shinfo(skb)->gso_segs; 125362306a36Sopenharmony_ci 125462306a36Sopenharmony_ci if (port->tso && gso_size < datalen) 125562306a36Sopenharmony_ci gso_segs = DIV_ROUND_UP(skb->len - hlen, datalen); 125662306a36Sopenharmony_ci 125762306a36Sopenharmony_ci if (unlikely(vnet_tx_dring_avail(dr) < gso_segs)) { 125862306a36Sopenharmony_ci struct netdev_queue *txq; 125962306a36Sopenharmony_ci 126062306a36Sopenharmony_ci txq = netdev_get_tx_queue(dev, port->q_index); 126162306a36Sopenharmony_ci netif_tx_stop_queue(txq); 126262306a36Sopenharmony_ci if (vnet_tx_dring_avail(dr) < skb_shinfo(skb)->gso_segs) 126362306a36Sopenharmony_ci return NETDEV_TX_BUSY; 126462306a36Sopenharmony_ci netif_tx_wake_queue(txq); 126562306a36Sopenharmony_ci } 126662306a36Sopenharmony_ci 126762306a36Sopenharmony_ci maclen = skb_network_header(skb) - skb_mac_header(skb); 126862306a36Sopenharmony_ci skb_pull(skb, maclen); 126962306a36Sopenharmony_ci 127062306a36Sopenharmony_ci if (port->tso && gso_size < datalen) { 127162306a36Sopenharmony_ci if (skb_unclone(skb, GFP_ATOMIC)) 127262306a36Sopenharmony_ci goto out_dropped; 127362306a36Sopenharmony_ci 127462306a36Sopenharmony_ci /* segment to TSO size */ 127562306a36Sopenharmony_ci skb_shinfo(skb)->gso_size = datalen; 127662306a36Sopenharmony_ci skb_shinfo(skb)->gso_segs = gso_segs; 127762306a36Sopenharmony_ci } 127862306a36Sopenharmony_ci segs = skb_gso_segment(skb, dev->features & ~NETIF_F_TSO); 127962306a36Sopenharmony_ci if (IS_ERR(segs)) 128062306a36Sopenharmony_ci goto out_dropped; 128162306a36Sopenharmony_ci 128262306a36Sopenharmony_ci skb_push(skb, maclen); 128362306a36Sopenharmony_ci skb_reset_mac_header(skb); 128462306a36Sopenharmony_ci 128562306a36Sopenharmony_ci status = 0; 128662306a36Sopenharmony_ci skb_list_walk_safe(segs, curr, next) { 128762306a36Sopenharmony_ci skb_mark_not_on_list(curr); 128862306a36Sopenharmony_ci if (port->tso && curr->len > dev->mtu) { 128962306a36Sopenharmony_ci skb_shinfo(curr)->gso_size = gso_size; 129062306a36Sopenharmony_ci skb_shinfo(curr)->gso_type = gso_type; 129162306a36Sopenharmony_ci skb_shinfo(curr)->gso_segs = 129262306a36Sopenharmony_ci DIV_ROUND_UP(curr->len - hlen, gso_size); 129362306a36Sopenharmony_ci } else { 129462306a36Sopenharmony_ci skb_shinfo(curr)->gso_size = 0; 129562306a36Sopenharmony_ci } 129662306a36Sopenharmony_ci 129762306a36Sopenharmony_ci skb_push(curr, maclen); 129862306a36Sopenharmony_ci skb_reset_mac_header(curr); 129962306a36Sopenharmony_ci memcpy(skb_mac_header(curr), skb_mac_header(skb), 130062306a36Sopenharmony_ci maclen); 130162306a36Sopenharmony_ci curr->csum_start = skb_transport_header(curr) - curr->head; 130262306a36Sopenharmony_ci if (ip_hdr(curr)->protocol == IPPROTO_TCP) 130362306a36Sopenharmony_ci curr->csum_offset = offsetof(struct tcphdr, check); 130462306a36Sopenharmony_ci else if (ip_hdr(curr)->protocol == IPPROTO_UDP) 130562306a36Sopenharmony_ci curr->csum_offset = offsetof(struct udphdr, check); 130662306a36Sopenharmony_ci 130762306a36Sopenharmony_ci if (!(status & NETDEV_TX_MASK)) 130862306a36Sopenharmony_ci status = sunvnet_start_xmit_common(curr, dev, 130962306a36Sopenharmony_ci vnet_tx_port); 131062306a36Sopenharmony_ci if (status & NETDEV_TX_MASK) 131162306a36Sopenharmony_ci dev_kfree_skb_any(curr); 131262306a36Sopenharmony_ci } 131362306a36Sopenharmony_ci 131462306a36Sopenharmony_ci if (!(status & NETDEV_TX_MASK)) 131562306a36Sopenharmony_ci dev_kfree_skb_any(skb); 131662306a36Sopenharmony_ci return status; 131762306a36Sopenharmony_ciout_dropped: 131862306a36Sopenharmony_ci dev->stats.tx_dropped++; 131962306a36Sopenharmony_ci dev_kfree_skb_any(skb); 132062306a36Sopenharmony_ci return NETDEV_TX_OK; 132162306a36Sopenharmony_ci} 132262306a36Sopenharmony_ci 132362306a36Sopenharmony_cinetdev_tx_t 132462306a36Sopenharmony_cisunvnet_start_xmit_common(struct sk_buff *skb, struct net_device *dev, 132562306a36Sopenharmony_ci struct vnet_port *(*vnet_tx_port) 132662306a36Sopenharmony_ci (struct sk_buff *, struct net_device *)) 132762306a36Sopenharmony_ci{ 132862306a36Sopenharmony_ci struct vnet_port *port = NULL; 132962306a36Sopenharmony_ci struct vio_dring_state *dr; 133062306a36Sopenharmony_ci struct vio_net_desc *d; 133162306a36Sopenharmony_ci unsigned int len; 133262306a36Sopenharmony_ci struct sk_buff *freeskbs = NULL; 133362306a36Sopenharmony_ci int i, err, txi; 133462306a36Sopenharmony_ci unsigned pending = 0; 133562306a36Sopenharmony_ci struct netdev_queue *txq; 133662306a36Sopenharmony_ci 133762306a36Sopenharmony_ci rcu_read_lock(); 133862306a36Sopenharmony_ci port = vnet_tx_port(skb, dev); 133962306a36Sopenharmony_ci if (unlikely(!port)) 134062306a36Sopenharmony_ci goto out_dropped; 134162306a36Sopenharmony_ci 134262306a36Sopenharmony_ci if (skb_is_gso(skb) && skb->len > port->tsolen) { 134362306a36Sopenharmony_ci err = vnet_handle_offloads(port, skb, vnet_tx_port); 134462306a36Sopenharmony_ci rcu_read_unlock(); 134562306a36Sopenharmony_ci return err; 134662306a36Sopenharmony_ci } 134762306a36Sopenharmony_ci 134862306a36Sopenharmony_ci if (!skb_is_gso(skb) && skb->len > port->rmtu) { 134962306a36Sopenharmony_ci unsigned long localmtu = port->rmtu - ETH_HLEN; 135062306a36Sopenharmony_ci 135162306a36Sopenharmony_ci if (vio_version_after_eq(&port->vio, 1, 3)) 135262306a36Sopenharmony_ci localmtu -= VLAN_HLEN; 135362306a36Sopenharmony_ci 135462306a36Sopenharmony_ci if (skb->protocol == htons(ETH_P_IP)) 135562306a36Sopenharmony_ci icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, 135662306a36Sopenharmony_ci htonl(localmtu)); 135762306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 135862306a36Sopenharmony_ci else if (skb->protocol == htons(ETH_P_IPV6)) 135962306a36Sopenharmony_ci icmpv6_ndo_send(skb, ICMPV6_PKT_TOOBIG, 0, localmtu); 136062306a36Sopenharmony_ci#endif 136162306a36Sopenharmony_ci goto out_dropped; 136262306a36Sopenharmony_ci } 136362306a36Sopenharmony_ci 136462306a36Sopenharmony_ci skb = vnet_skb_shape(skb, 2); 136562306a36Sopenharmony_ci 136662306a36Sopenharmony_ci if (unlikely(!skb)) 136762306a36Sopenharmony_ci goto out_dropped; 136862306a36Sopenharmony_ci 136962306a36Sopenharmony_ci if (skb->ip_summed == CHECKSUM_PARTIAL) { 137062306a36Sopenharmony_ci if (skb->protocol == htons(ETH_P_IP)) 137162306a36Sopenharmony_ci vnet_fullcsum_ipv4(skb); 137262306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 137362306a36Sopenharmony_ci else if (skb->protocol == htons(ETH_P_IPV6)) 137462306a36Sopenharmony_ci vnet_fullcsum_ipv6(skb); 137562306a36Sopenharmony_ci#endif 137662306a36Sopenharmony_ci } 137762306a36Sopenharmony_ci 137862306a36Sopenharmony_ci dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 137962306a36Sopenharmony_ci i = skb_get_queue_mapping(skb); 138062306a36Sopenharmony_ci txq = netdev_get_tx_queue(dev, i); 138162306a36Sopenharmony_ci if (unlikely(vnet_tx_dring_avail(dr) < 1)) { 138262306a36Sopenharmony_ci if (!netif_tx_queue_stopped(txq)) { 138362306a36Sopenharmony_ci netif_tx_stop_queue(txq); 138462306a36Sopenharmony_ci 138562306a36Sopenharmony_ci /* This is a hard error, log it. */ 138662306a36Sopenharmony_ci netdev_err(dev, "BUG! Tx Ring full when queue awake!\n"); 138762306a36Sopenharmony_ci dev->stats.tx_errors++; 138862306a36Sopenharmony_ci } 138962306a36Sopenharmony_ci rcu_read_unlock(); 139062306a36Sopenharmony_ci return NETDEV_TX_BUSY; 139162306a36Sopenharmony_ci } 139262306a36Sopenharmony_ci 139362306a36Sopenharmony_ci d = vio_dring_cur(dr); 139462306a36Sopenharmony_ci 139562306a36Sopenharmony_ci txi = dr->prod; 139662306a36Sopenharmony_ci 139762306a36Sopenharmony_ci freeskbs = vnet_clean_tx_ring(port, &pending); 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_ci BUG_ON(port->tx_bufs[txi].skb); 140062306a36Sopenharmony_ci 140162306a36Sopenharmony_ci len = skb->len; 140262306a36Sopenharmony_ci if (len < ETH_ZLEN) 140362306a36Sopenharmony_ci len = ETH_ZLEN; 140462306a36Sopenharmony_ci 140562306a36Sopenharmony_ci err = vnet_skb_map(port->vio.lp, skb, port->tx_bufs[txi].cookies, 2, 140662306a36Sopenharmony_ci (LDC_MAP_SHADOW | LDC_MAP_DIRECT | LDC_MAP_RW)); 140762306a36Sopenharmony_ci if (err < 0) { 140862306a36Sopenharmony_ci netdev_info(dev, "tx buffer map error %d\n", err); 140962306a36Sopenharmony_ci goto out_dropped; 141062306a36Sopenharmony_ci } 141162306a36Sopenharmony_ci 141262306a36Sopenharmony_ci port->tx_bufs[txi].skb = skb; 141362306a36Sopenharmony_ci skb = NULL; 141462306a36Sopenharmony_ci port->tx_bufs[txi].ncookies = err; 141562306a36Sopenharmony_ci 141662306a36Sopenharmony_ci /* We don't rely on the ACKs to free the skb in vnet_start_xmit(), 141762306a36Sopenharmony_ci * thus it is safe to not set VIO_ACK_ENABLE for each transmission: 141862306a36Sopenharmony_ci * the protocol itself does not require it as long as the peer 141962306a36Sopenharmony_ci * sends a VIO_SUBTYPE_ACK for VIO_DRING_STOPPED. 142062306a36Sopenharmony_ci * 142162306a36Sopenharmony_ci * An ACK for every packet in the ring is expensive as the 142262306a36Sopenharmony_ci * sending of LDC messages is slow and affects performance. 142362306a36Sopenharmony_ci */ 142462306a36Sopenharmony_ci d->hdr.ack = VIO_ACK_DISABLE; 142562306a36Sopenharmony_ci d->size = len; 142662306a36Sopenharmony_ci d->ncookies = port->tx_bufs[txi].ncookies; 142762306a36Sopenharmony_ci for (i = 0; i < d->ncookies; i++) 142862306a36Sopenharmony_ci d->cookies[i] = port->tx_bufs[txi].cookies[i]; 142962306a36Sopenharmony_ci if (vio_version_after_eq(&port->vio, 1, 7)) { 143062306a36Sopenharmony_ci struct vio_net_dext *dext = vio_net_ext(d); 143162306a36Sopenharmony_ci 143262306a36Sopenharmony_ci memset(dext, 0, sizeof(*dext)); 143362306a36Sopenharmony_ci if (skb_is_gso(port->tx_bufs[txi].skb)) { 143462306a36Sopenharmony_ci dext->ipv4_lso_mss = skb_shinfo(port->tx_bufs[txi].skb) 143562306a36Sopenharmony_ci ->gso_size; 143662306a36Sopenharmony_ci dext->flags |= VNET_PKT_IPV4_LSO; 143762306a36Sopenharmony_ci } 143862306a36Sopenharmony_ci if (vio_version_after_eq(&port->vio, 1, 8) && 143962306a36Sopenharmony_ci !port->switch_port) { 144062306a36Sopenharmony_ci dext->flags |= VNET_PKT_HCK_IPV4_HDRCKSUM_OK; 144162306a36Sopenharmony_ci dext->flags |= VNET_PKT_HCK_FULLCKSUM_OK; 144262306a36Sopenharmony_ci } 144362306a36Sopenharmony_ci } 144462306a36Sopenharmony_ci 144562306a36Sopenharmony_ci /* This has to be a non-SMP write barrier because we are writing 144662306a36Sopenharmony_ci * to memory which is shared with the peer LDOM. 144762306a36Sopenharmony_ci */ 144862306a36Sopenharmony_ci dma_wmb(); 144962306a36Sopenharmony_ci 145062306a36Sopenharmony_ci d->hdr.state = VIO_DESC_READY; 145162306a36Sopenharmony_ci 145262306a36Sopenharmony_ci /* Exactly one ldc "start" trigger (for dr->cons) needs to be sent 145362306a36Sopenharmony_ci * to notify the consumer that some descriptors are READY. 145462306a36Sopenharmony_ci * After that "start" trigger, no additional triggers are needed until 145562306a36Sopenharmony_ci * a DRING_STOPPED is received from the consumer. The dr->cons field 145662306a36Sopenharmony_ci * (set up by vnet_ack()) has the value of the next dring index 145762306a36Sopenharmony_ci * that has not yet been ack-ed. We send a "start" trigger here 145862306a36Sopenharmony_ci * if, and only if, start_cons is true (reset it afterward). Conversely, 145962306a36Sopenharmony_ci * vnet_ack() should check if the dring corresponding to cons 146062306a36Sopenharmony_ci * is marked READY, but start_cons was false. 146162306a36Sopenharmony_ci * If so, vnet_ack() should send out the missed "start" trigger. 146262306a36Sopenharmony_ci * 146362306a36Sopenharmony_ci * Note that the dma_wmb() above makes sure the cookies et al. are 146462306a36Sopenharmony_ci * not globally visible before the VIO_DESC_READY, and that the 146562306a36Sopenharmony_ci * stores are ordered correctly by the compiler. The consumer will 146662306a36Sopenharmony_ci * not proceed until the VIO_DESC_READY is visible assuring that 146762306a36Sopenharmony_ci * the consumer does not observe anything related to descriptors 146862306a36Sopenharmony_ci * out of order. The HV trap from the LDC start trigger is the 146962306a36Sopenharmony_ci * producer to consumer announcement that work is available to the 147062306a36Sopenharmony_ci * consumer 147162306a36Sopenharmony_ci */ 147262306a36Sopenharmony_ci if (!port->start_cons) { /* previous trigger suffices */ 147362306a36Sopenharmony_ci trace_vnet_skip_tx_trigger(port->vio._local_sid, 147462306a36Sopenharmony_ci port->vio._peer_sid, dr->cons); 147562306a36Sopenharmony_ci goto ldc_start_done; 147662306a36Sopenharmony_ci } 147762306a36Sopenharmony_ci 147862306a36Sopenharmony_ci err = __vnet_tx_trigger(port, dr->cons); 147962306a36Sopenharmony_ci if (unlikely(err < 0)) { 148062306a36Sopenharmony_ci netdev_info(dev, "TX trigger error %d\n", err); 148162306a36Sopenharmony_ci d->hdr.state = VIO_DESC_FREE; 148262306a36Sopenharmony_ci skb = port->tx_bufs[txi].skb; 148362306a36Sopenharmony_ci port->tx_bufs[txi].skb = NULL; 148462306a36Sopenharmony_ci dev->stats.tx_carrier_errors++; 148562306a36Sopenharmony_ci goto out_dropped; 148662306a36Sopenharmony_ci } 148762306a36Sopenharmony_ci 148862306a36Sopenharmony_cildc_start_done: 148962306a36Sopenharmony_ci port->start_cons = false; 149062306a36Sopenharmony_ci 149162306a36Sopenharmony_ci dev->stats.tx_packets++; 149262306a36Sopenharmony_ci dev->stats.tx_bytes += port->tx_bufs[txi].skb->len; 149362306a36Sopenharmony_ci port->stats.tx_packets++; 149462306a36Sopenharmony_ci port->stats.tx_bytes += port->tx_bufs[txi].skb->len; 149562306a36Sopenharmony_ci 149662306a36Sopenharmony_ci dr->prod = (dr->prod + 1) & (VNET_TX_RING_SIZE - 1); 149762306a36Sopenharmony_ci if (unlikely(vnet_tx_dring_avail(dr) < 1)) { 149862306a36Sopenharmony_ci netif_tx_stop_queue(txq); 149962306a36Sopenharmony_ci smp_rmb(); 150062306a36Sopenharmony_ci if (vnet_tx_dring_avail(dr) > VNET_TX_WAKEUP_THRESH(dr)) 150162306a36Sopenharmony_ci netif_tx_wake_queue(txq); 150262306a36Sopenharmony_ci } 150362306a36Sopenharmony_ci 150462306a36Sopenharmony_ci (void)mod_timer(&port->clean_timer, jiffies + VNET_CLEAN_TIMEOUT); 150562306a36Sopenharmony_ci rcu_read_unlock(); 150662306a36Sopenharmony_ci 150762306a36Sopenharmony_ci vnet_free_skbs(freeskbs); 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_ci return NETDEV_TX_OK; 151062306a36Sopenharmony_ci 151162306a36Sopenharmony_ciout_dropped: 151262306a36Sopenharmony_ci if (pending) 151362306a36Sopenharmony_ci (void)mod_timer(&port->clean_timer, 151462306a36Sopenharmony_ci jiffies + VNET_CLEAN_TIMEOUT); 151562306a36Sopenharmony_ci else if (port) 151662306a36Sopenharmony_ci del_timer(&port->clean_timer); 151762306a36Sopenharmony_ci rcu_read_unlock(); 151862306a36Sopenharmony_ci dev_kfree_skb(skb); 151962306a36Sopenharmony_ci vnet_free_skbs(freeskbs); 152062306a36Sopenharmony_ci dev->stats.tx_dropped++; 152162306a36Sopenharmony_ci return NETDEV_TX_OK; 152262306a36Sopenharmony_ci} 152362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_start_xmit_common); 152462306a36Sopenharmony_ci 152562306a36Sopenharmony_civoid sunvnet_tx_timeout_common(struct net_device *dev, unsigned int txqueue) 152662306a36Sopenharmony_ci{ 152762306a36Sopenharmony_ci /* XXX Implement me XXX */ 152862306a36Sopenharmony_ci} 152962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_tx_timeout_common); 153062306a36Sopenharmony_ci 153162306a36Sopenharmony_ciint sunvnet_open_common(struct net_device *dev) 153262306a36Sopenharmony_ci{ 153362306a36Sopenharmony_ci netif_carrier_on(dev); 153462306a36Sopenharmony_ci netif_tx_start_all_queues(dev); 153562306a36Sopenharmony_ci 153662306a36Sopenharmony_ci return 0; 153762306a36Sopenharmony_ci} 153862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_open_common); 153962306a36Sopenharmony_ci 154062306a36Sopenharmony_ciint sunvnet_close_common(struct net_device *dev) 154162306a36Sopenharmony_ci{ 154262306a36Sopenharmony_ci netif_tx_stop_all_queues(dev); 154362306a36Sopenharmony_ci netif_carrier_off(dev); 154462306a36Sopenharmony_ci 154562306a36Sopenharmony_ci return 0; 154662306a36Sopenharmony_ci} 154762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_close_common); 154862306a36Sopenharmony_ci 154962306a36Sopenharmony_cistatic struct vnet_mcast_entry *__vnet_mc_find(struct vnet *vp, u8 *addr) 155062306a36Sopenharmony_ci{ 155162306a36Sopenharmony_ci struct vnet_mcast_entry *m; 155262306a36Sopenharmony_ci 155362306a36Sopenharmony_ci for (m = vp->mcast_list; m; m = m->next) { 155462306a36Sopenharmony_ci if (ether_addr_equal(m->addr, addr)) 155562306a36Sopenharmony_ci return m; 155662306a36Sopenharmony_ci } 155762306a36Sopenharmony_ci return NULL; 155862306a36Sopenharmony_ci} 155962306a36Sopenharmony_ci 156062306a36Sopenharmony_cistatic void __update_mc_list(struct vnet *vp, struct net_device *dev) 156162306a36Sopenharmony_ci{ 156262306a36Sopenharmony_ci struct netdev_hw_addr *ha; 156362306a36Sopenharmony_ci 156462306a36Sopenharmony_ci netdev_for_each_mc_addr(ha, dev) { 156562306a36Sopenharmony_ci struct vnet_mcast_entry *m; 156662306a36Sopenharmony_ci 156762306a36Sopenharmony_ci m = __vnet_mc_find(vp, ha->addr); 156862306a36Sopenharmony_ci if (m) { 156962306a36Sopenharmony_ci m->hit = 1; 157062306a36Sopenharmony_ci continue; 157162306a36Sopenharmony_ci } 157262306a36Sopenharmony_ci 157362306a36Sopenharmony_ci if (!m) { 157462306a36Sopenharmony_ci m = kzalloc(sizeof(*m), GFP_ATOMIC); 157562306a36Sopenharmony_ci if (!m) 157662306a36Sopenharmony_ci continue; 157762306a36Sopenharmony_ci memcpy(m->addr, ha->addr, ETH_ALEN); 157862306a36Sopenharmony_ci m->hit = 1; 157962306a36Sopenharmony_ci 158062306a36Sopenharmony_ci m->next = vp->mcast_list; 158162306a36Sopenharmony_ci vp->mcast_list = m; 158262306a36Sopenharmony_ci } 158362306a36Sopenharmony_ci } 158462306a36Sopenharmony_ci} 158562306a36Sopenharmony_ci 158662306a36Sopenharmony_cistatic void __send_mc_list(struct vnet *vp, struct vnet_port *port) 158762306a36Sopenharmony_ci{ 158862306a36Sopenharmony_ci struct vio_net_mcast_info info; 158962306a36Sopenharmony_ci struct vnet_mcast_entry *m, **pp; 159062306a36Sopenharmony_ci int n_addrs; 159162306a36Sopenharmony_ci 159262306a36Sopenharmony_ci memset(&info, 0, sizeof(info)); 159362306a36Sopenharmony_ci 159462306a36Sopenharmony_ci info.tag.type = VIO_TYPE_CTRL; 159562306a36Sopenharmony_ci info.tag.stype = VIO_SUBTYPE_INFO; 159662306a36Sopenharmony_ci info.tag.stype_env = VNET_MCAST_INFO; 159762306a36Sopenharmony_ci info.tag.sid = vio_send_sid(&port->vio); 159862306a36Sopenharmony_ci info.set = 1; 159962306a36Sopenharmony_ci 160062306a36Sopenharmony_ci n_addrs = 0; 160162306a36Sopenharmony_ci for (m = vp->mcast_list; m; m = m->next) { 160262306a36Sopenharmony_ci if (m->sent) 160362306a36Sopenharmony_ci continue; 160462306a36Sopenharmony_ci m->sent = 1; 160562306a36Sopenharmony_ci memcpy(&info.mcast_addr[n_addrs * ETH_ALEN], 160662306a36Sopenharmony_ci m->addr, ETH_ALEN); 160762306a36Sopenharmony_ci if (++n_addrs == VNET_NUM_MCAST) { 160862306a36Sopenharmony_ci info.count = n_addrs; 160962306a36Sopenharmony_ci 161062306a36Sopenharmony_ci (void)vio_ldc_send(&port->vio, &info, 161162306a36Sopenharmony_ci sizeof(info)); 161262306a36Sopenharmony_ci n_addrs = 0; 161362306a36Sopenharmony_ci } 161462306a36Sopenharmony_ci } 161562306a36Sopenharmony_ci if (n_addrs) { 161662306a36Sopenharmony_ci info.count = n_addrs; 161762306a36Sopenharmony_ci (void)vio_ldc_send(&port->vio, &info, sizeof(info)); 161862306a36Sopenharmony_ci } 161962306a36Sopenharmony_ci 162062306a36Sopenharmony_ci info.set = 0; 162162306a36Sopenharmony_ci 162262306a36Sopenharmony_ci n_addrs = 0; 162362306a36Sopenharmony_ci pp = &vp->mcast_list; 162462306a36Sopenharmony_ci while ((m = *pp) != NULL) { 162562306a36Sopenharmony_ci if (m->hit) { 162662306a36Sopenharmony_ci m->hit = 0; 162762306a36Sopenharmony_ci pp = &m->next; 162862306a36Sopenharmony_ci continue; 162962306a36Sopenharmony_ci } 163062306a36Sopenharmony_ci 163162306a36Sopenharmony_ci memcpy(&info.mcast_addr[n_addrs * ETH_ALEN], 163262306a36Sopenharmony_ci m->addr, ETH_ALEN); 163362306a36Sopenharmony_ci if (++n_addrs == VNET_NUM_MCAST) { 163462306a36Sopenharmony_ci info.count = n_addrs; 163562306a36Sopenharmony_ci (void)vio_ldc_send(&port->vio, &info, 163662306a36Sopenharmony_ci sizeof(info)); 163762306a36Sopenharmony_ci n_addrs = 0; 163862306a36Sopenharmony_ci } 163962306a36Sopenharmony_ci 164062306a36Sopenharmony_ci *pp = m->next; 164162306a36Sopenharmony_ci kfree(m); 164262306a36Sopenharmony_ci } 164362306a36Sopenharmony_ci if (n_addrs) { 164462306a36Sopenharmony_ci info.count = n_addrs; 164562306a36Sopenharmony_ci (void)vio_ldc_send(&port->vio, &info, sizeof(info)); 164662306a36Sopenharmony_ci } 164762306a36Sopenharmony_ci} 164862306a36Sopenharmony_ci 164962306a36Sopenharmony_civoid sunvnet_set_rx_mode_common(struct net_device *dev, struct vnet *vp) 165062306a36Sopenharmony_ci{ 165162306a36Sopenharmony_ci struct vnet_port *port; 165262306a36Sopenharmony_ci 165362306a36Sopenharmony_ci rcu_read_lock(); 165462306a36Sopenharmony_ci list_for_each_entry_rcu(port, &vp->port_list, list) { 165562306a36Sopenharmony_ci if (port->switch_port) { 165662306a36Sopenharmony_ci __update_mc_list(vp, dev); 165762306a36Sopenharmony_ci __send_mc_list(vp, port); 165862306a36Sopenharmony_ci break; 165962306a36Sopenharmony_ci } 166062306a36Sopenharmony_ci } 166162306a36Sopenharmony_ci rcu_read_unlock(); 166262306a36Sopenharmony_ci} 166362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_set_rx_mode_common); 166462306a36Sopenharmony_ci 166562306a36Sopenharmony_ciint sunvnet_set_mac_addr_common(struct net_device *dev, void *p) 166662306a36Sopenharmony_ci{ 166762306a36Sopenharmony_ci return -EINVAL; 166862306a36Sopenharmony_ci} 166962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_set_mac_addr_common); 167062306a36Sopenharmony_ci 167162306a36Sopenharmony_civoid sunvnet_port_free_tx_bufs_common(struct vnet_port *port) 167262306a36Sopenharmony_ci{ 167362306a36Sopenharmony_ci struct vio_dring_state *dr; 167462306a36Sopenharmony_ci int i; 167562306a36Sopenharmony_ci 167662306a36Sopenharmony_ci dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 167762306a36Sopenharmony_ci 167862306a36Sopenharmony_ci if (!dr->base) 167962306a36Sopenharmony_ci return; 168062306a36Sopenharmony_ci 168162306a36Sopenharmony_ci for (i = 0; i < VNET_TX_RING_SIZE; i++) { 168262306a36Sopenharmony_ci struct vio_net_desc *d; 168362306a36Sopenharmony_ci void *skb = port->tx_bufs[i].skb; 168462306a36Sopenharmony_ci 168562306a36Sopenharmony_ci if (!skb) 168662306a36Sopenharmony_ci continue; 168762306a36Sopenharmony_ci 168862306a36Sopenharmony_ci d = vio_dring_entry(dr, i); 168962306a36Sopenharmony_ci 169062306a36Sopenharmony_ci ldc_unmap(port->vio.lp, 169162306a36Sopenharmony_ci port->tx_bufs[i].cookies, 169262306a36Sopenharmony_ci port->tx_bufs[i].ncookies); 169362306a36Sopenharmony_ci dev_kfree_skb(skb); 169462306a36Sopenharmony_ci port->tx_bufs[i].skb = NULL; 169562306a36Sopenharmony_ci d->hdr.state = VIO_DESC_FREE; 169662306a36Sopenharmony_ci } 169762306a36Sopenharmony_ci ldc_free_exp_dring(port->vio.lp, dr->base, 169862306a36Sopenharmony_ci (dr->entry_size * dr->num_entries), 169962306a36Sopenharmony_ci dr->cookies, dr->ncookies); 170062306a36Sopenharmony_ci dr->base = NULL; 170162306a36Sopenharmony_ci dr->entry_size = 0; 170262306a36Sopenharmony_ci dr->num_entries = 0; 170362306a36Sopenharmony_ci dr->pending = 0; 170462306a36Sopenharmony_ci dr->ncookies = 0; 170562306a36Sopenharmony_ci} 170662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_port_free_tx_bufs_common); 170762306a36Sopenharmony_ci 170862306a36Sopenharmony_civoid vnet_port_reset(struct vnet_port *port) 170962306a36Sopenharmony_ci{ 171062306a36Sopenharmony_ci del_timer(&port->clean_timer); 171162306a36Sopenharmony_ci sunvnet_port_free_tx_bufs_common(port); 171262306a36Sopenharmony_ci port->rmtu = 0; 171362306a36Sopenharmony_ci port->tso = (port->vsw == 0); /* no tso in vsw, misbehaves in bridge */ 171462306a36Sopenharmony_ci port->tsolen = 0; 171562306a36Sopenharmony_ci} 171662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vnet_port_reset); 171762306a36Sopenharmony_ci 171862306a36Sopenharmony_cistatic int vnet_port_alloc_tx_ring(struct vnet_port *port) 171962306a36Sopenharmony_ci{ 172062306a36Sopenharmony_ci struct vio_dring_state *dr; 172162306a36Sopenharmony_ci unsigned long len, elen; 172262306a36Sopenharmony_ci int i, err, ncookies; 172362306a36Sopenharmony_ci void *dring; 172462306a36Sopenharmony_ci 172562306a36Sopenharmony_ci dr = &port->vio.drings[VIO_DRIVER_TX_RING]; 172662306a36Sopenharmony_ci 172762306a36Sopenharmony_ci elen = sizeof(struct vio_net_desc) + 172862306a36Sopenharmony_ci sizeof(struct ldc_trans_cookie) * 2; 172962306a36Sopenharmony_ci if (vio_version_after_eq(&port->vio, 1, 7)) 173062306a36Sopenharmony_ci elen += sizeof(struct vio_net_dext); 173162306a36Sopenharmony_ci len = VNET_TX_RING_SIZE * elen; 173262306a36Sopenharmony_ci 173362306a36Sopenharmony_ci ncookies = VIO_MAX_RING_COOKIES; 173462306a36Sopenharmony_ci dring = ldc_alloc_exp_dring(port->vio.lp, len, 173562306a36Sopenharmony_ci dr->cookies, &ncookies, 173662306a36Sopenharmony_ci (LDC_MAP_SHADOW | 173762306a36Sopenharmony_ci LDC_MAP_DIRECT | 173862306a36Sopenharmony_ci LDC_MAP_RW)); 173962306a36Sopenharmony_ci if (IS_ERR(dring)) { 174062306a36Sopenharmony_ci err = PTR_ERR(dring); 174162306a36Sopenharmony_ci goto err_out; 174262306a36Sopenharmony_ci } 174362306a36Sopenharmony_ci 174462306a36Sopenharmony_ci dr->base = dring; 174562306a36Sopenharmony_ci dr->entry_size = elen; 174662306a36Sopenharmony_ci dr->num_entries = VNET_TX_RING_SIZE; 174762306a36Sopenharmony_ci dr->prod = 0; 174862306a36Sopenharmony_ci dr->cons = 0; 174962306a36Sopenharmony_ci port->start_cons = true; /* need an initial trigger */ 175062306a36Sopenharmony_ci dr->pending = VNET_TX_RING_SIZE; 175162306a36Sopenharmony_ci dr->ncookies = ncookies; 175262306a36Sopenharmony_ci 175362306a36Sopenharmony_ci for (i = 0; i < VNET_TX_RING_SIZE; ++i) { 175462306a36Sopenharmony_ci struct vio_net_desc *d; 175562306a36Sopenharmony_ci 175662306a36Sopenharmony_ci d = vio_dring_entry(dr, i); 175762306a36Sopenharmony_ci d->hdr.state = VIO_DESC_FREE; 175862306a36Sopenharmony_ci } 175962306a36Sopenharmony_ci return 0; 176062306a36Sopenharmony_ci 176162306a36Sopenharmony_cierr_out: 176262306a36Sopenharmony_ci sunvnet_port_free_tx_bufs_common(port); 176362306a36Sopenharmony_ci 176462306a36Sopenharmony_ci return err; 176562306a36Sopenharmony_ci} 176662306a36Sopenharmony_ci 176762306a36Sopenharmony_ci#ifdef CONFIG_NET_POLL_CONTROLLER 176862306a36Sopenharmony_civoid sunvnet_poll_controller_common(struct net_device *dev, struct vnet *vp) 176962306a36Sopenharmony_ci{ 177062306a36Sopenharmony_ci struct vnet_port *port; 177162306a36Sopenharmony_ci unsigned long flags; 177262306a36Sopenharmony_ci 177362306a36Sopenharmony_ci spin_lock_irqsave(&vp->lock, flags); 177462306a36Sopenharmony_ci if (!list_empty(&vp->port_list)) { 177562306a36Sopenharmony_ci port = list_entry(vp->port_list.next, struct vnet_port, list); 177662306a36Sopenharmony_ci napi_schedule(&port->napi); 177762306a36Sopenharmony_ci } 177862306a36Sopenharmony_ci spin_unlock_irqrestore(&vp->lock, flags); 177962306a36Sopenharmony_ci} 178062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_poll_controller_common); 178162306a36Sopenharmony_ci#endif 178262306a36Sopenharmony_ci 178362306a36Sopenharmony_civoid sunvnet_port_add_txq_common(struct vnet_port *port) 178462306a36Sopenharmony_ci{ 178562306a36Sopenharmony_ci struct vnet *vp = port->vp; 178662306a36Sopenharmony_ci int smallest = 0; 178762306a36Sopenharmony_ci int i; 178862306a36Sopenharmony_ci 178962306a36Sopenharmony_ci /* find the first least-used q 179062306a36Sopenharmony_ci * When there are more ldoms than q's, we start to 179162306a36Sopenharmony_ci * double up on ports per queue. 179262306a36Sopenharmony_ci */ 179362306a36Sopenharmony_ci for (i = 0; i < VNET_MAX_TXQS; i++) { 179462306a36Sopenharmony_ci if (vp->q_used[i] == 0) { 179562306a36Sopenharmony_ci smallest = i; 179662306a36Sopenharmony_ci break; 179762306a36Sopenharmony_ci } 179862306a36Sopenharmony_ci if (vp->q_used[i] < vp->q_used[smallest]) 179962306a36Sopenharmony_ci smallest = i; 180062306a36Sopenharmony_ci } 180162306a36Sopenharmony_ci 180262306a36Sopenharmony_ci vp->nports++; 180362306a36Sopenharmony_ci vp->q_used[smallest]++; 180462306a36Sopenharmony_ci port->q_index = smallest; 180562306a36Sopenharmony_ci} 180662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_port_add_txq_common); 180762306a36Sopenharmony_ci 180862306a36Sopenharmony_civoid sunvnet_port_rm_txq_common(struct vnet_port *port) 180962306a36Sopenharmony_ci{ 181062306a36Sopenharmony_ci port->vp->nports--; 181162306a36Sopenharmony_ci port->vp->q_used[port->q_index]--; 181262306a36Sopenharmony_ci port->q_index = 0; 181362306a36Sopenharmony_ci} 181462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sunvnet_port_rm_txq_common); 1815