| // SPDX-License-Identifier: GPL-2.0-only | |
| /**************************************************************************** | |
| * Driver for Solarflare network controllers and boards | |
| * Copyright 2019 Solarflare Communications Inc. | |
| * Copyright 2020-2022 Xilinx Inc. | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 as published | |
| * by the Free Software Foundation, incorporated herein by reference. | |
| */ | |
| #include <linux/rhashtable.h> | |
| #include "ef100_rep.h" | |
| #include "ef100_netdev.h" | |
| #include "ef100_nic.h" | |
| #include "mae.h" | |
| #include "rx_common.h" | |
| #include "tc_bindings.h" | |
| #include "efx_devlink.h" | |
| #define EFX_EF100_REP_DRIVER "efx_ef100_rep" | |
| #define EFX_REP_DEFAULT_PSEUDO_RING_SIZE 64 | |
| static int efx_ef100_rep_poll(struct napi_struct *napi, int weight); | |
| static int efx_ef100_rep_init_struct(struct efx_nic *efx, struct efx_rep *efv, | |
| unsigned int i) | |
| { | |
| efv->parent = efx; | |
| efv->idx = i; | |
| INIT_LIST_HEAD(&efv->list); | |
| efv->dflt.fw_id = MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL; | |
| INIT_LIST_HEAD(&efv->dflt.acts.list); | |
| INIT_LIST_HEAD(&efv->rx_list); | |
| spin_lock_init(&efv->rx_lock); | |
| efv->msg_enable = NETIF_MSG_DRV | NETIF_MSG_PROBE | | |
| NETIF_MSG_LINK | NETIF_MSG_IFDOWN | | |
| NETIF_MSG_IFUP | NETIF_MSG_RX_ERR | | |
| NETIF_MSG_TX_ERR | NETIF_MSG_HW; | |
| return 0; | |
| } | |
| static int efx_ef100_rep_open(struct net_device *net_dev) | |
| { | |
| struct efx_rep *efv = netdev_priv(net_dev); | |
| netif_napi_add(net_dev, &efv->napi, efx_ef100_rep_poll); | |
| napi_enable(&efv->napi); | |
| return 0; | |
| } | |
| static int efx_ef100_rep_close(struct net_device *net_dev) | |
| { | |
| struct efx_rep *efv = netdev_priv(net_dev); | |
| napi_disable(&efv->napi); | |
| netif_napi_del(&efv->napi); | |
| return 0; | |
| } | |
| static netdev_tx_t efx_ef100_rep_xmit(struct sk_buff *skb, | |
| struct net_device *dev) | |
| { | |
| struct efx_rep *efv = netdev_priv(dev); | |
| struct efx_nic *efx = efv->parent; | |
| netdev_tx_t rc; | |
| /* __ef100_hard_start_xmit() will always return success even in the | |
| * case of TX drops, where it will increment efx's tx_dropped. The | |
| * efv stats really only count attempted TX, not success/failure. | |
| */ | |
| atomic64_inc(&efv->stats.tx_packets); | |
| atomic64_add(skb->len, &efv->stats.tx_bytes); | |
| netif_tx_lock(efx->net_dev); | |
| rc = __ef100_hard_start_xmit(skb, efx, dev, efv); | |
| netif_tx_unlock(efx->net_dev); | |
| return rc; | |
| } | |
| static int efx_ef100_rep_get_port_parent_id(struct net_device *dev, | |
| struct netdev_phys_item_id *ppid) | |
| { | |
| struct efx_rep *efv = netdev_priv(dev); | |
| struct efx_nic *efx = efv->parent; | |
| struct ef100_nic_data *nic_data; | |
| nic_data = efx->nic_data; | |
| /* nic_data->port_id is a u8[] */ | |
| ppid->id_len = sizeof(nic_data->port_id); | |
| memcpy(ppid->id, nic_data->port_id, sizeof(nic_data->port_id)); | |
| return 0; | |
| } | |
| static int efx_ef100_rep_get_phys_port_name(struct net_device *dev, | |
| char *buf, size_t len) | |
| { | |
| struct efx_rep *efv = netdev_priv(dev); | |
| struct efx_nic *efx = efv->parent; | |
| struct ef100_nic_data *nic_data; | |
| int ret; | |
| nic_data = efx->nic_data; | |
| ret = snprintf(buf, len, "p%upf%uvf%u", efx->port_num, | |
| nic_data->pf_index, efv->idx); | |
| if (ret >= len) | |
| return -EOPNOTSUPP; | |
| return 0; | |
| } | |
| static int efx_ef100_rep_setup_tc(struct net_device *net_dev, | |
| enum tc_setup_type type, void *type_data) | |
| { | |
| struct efx_rep *efv = netdev_priv(net_dev); | |
| struct efx_nic *efx = efv->parent; | |
| if (type == TC_SETUP_CLSFLOWER) | |
| return efx_tc_flower(efx, net_dev, type_data, efv); | |
| if (type == TC_SETUP_BLOCK) | |
| return efx_tc_setup_block(net_dev, efx, type_data, efv); | |
| return -EOPNOTSUPP; | |
| } | |
| static void efx_ef100_rep_get_stats64(struct net_device *dev, | |
| struct rtnl_link_stats64 *stats) | |
| { | |
| struct efx_rep *efv = netdev_priv(dev); | |
| stats->rx_packets = atomic64_read(&efv->stats.rx_packets); | |
| stats->tx_packets = atomic64_read(&efv->stats.tx_packets); | |
| stats->rx_bytes = atomic64_read(&efv->stats.rx_bytes); | |
| stats->tx_bytes = atomic64_read(&efv->stats.tx_bytes); | |
| stats->rx_dropped = atomic64_read(&efv->stats.rx_dropped); | |
| stats->tx_errors = atomic64_read(&efv->stats.tx_errors); | |
| } | |
| const struct net_device_ops efx_ef100_rep_netdev_ops = { | |
| .ndo_open = efx_ef100_rep_open, | |
| .ndo_stop = efx_ef100_rep_close, | |
| .ndo_start_xmit = efx_ef100_rep_xmit, | |
| .ndo_get_port_parent_id = efx_ef100_rep_get_port_parent_id, | |
| .ndo_get_phys_port_name = efx_ef100_rep_get_phys_port_name, | |
| .ndo_get_stats64 = efx_ef100_rep_get_stats64, | |
| .ndo_setup_tc = efx_ef100_rep_setup_tc, | |
| }; | |
| static void efx_ef100_rep_get_drvinfo(struct net_device *dev, | |
| struct ethtool_drvinfo *drvinfo) | |
| { | |
| strscpy(drvinfo->driver, EFX_EF100_REP_DRIVER, sizeof(drvinfo->driver)); | |
| } | |
| static u32 efx_ef100_rep_ethtool_get_msglevel(struct net_device *net_dev) | |
| { | |
| struct efx_rep *efv = netdev_priv(net_dev); | |
| return efv->msg_enable; | |
| } | |
| static void efx_ef100_rep_ethtool_set_msglevel(struct net_device *net_dev, | |
| u32 msg_enable) | |
| { | |
| struct efx_rep *efv = netdev_priv(net_dev); | |
| efv->msg_enable = msg_enable; | |
| } | |
| static void efx_ef100_rep_ethtool_get_ringparam(struct net_device *net_dev, | |
| struct ethtool_ringparam *ring, | |
| struct kernel_ethtool_ringparam *kring, | |
| struct netlink_ext_ack *ext_ack) | |
| { | |
| struct efx_rep *efv = netdev_priv(net_dev); | |
| ring->rx_max_pending = U32_MAX; | |
| ring->rx_pending = efv->rx_pring_size; | |
| } | |
| static int efx_ef100_rep_ethtool_set_ringparam(struct net_device *net_dev, | |
| struct ethtool_ringparam *ring, | |
| struct kernel_ethtool_ringparam *kring, | |
| struct netlink_ext_ack *ext_ack) | |
| { | |
| struct efx_rep *efv = netdev_priv(net_dev); | |
| if (ring->rx_mini_pending || ring->rx_jumbo_pending || ring->tx_pending) | |
| return -EINVAL; | |
| efv->rx_pring_size = ring->rx_pending; | |
| return 0; | |
| } | |
| static const struct ethtool_ops efx_ef100_rep_ethtool_ops = { | |
| .get_drvinfo = efx_ef100_rep_get_drvinfo, | |
| .get_msglevel = efx_ef100_rep_ethtool_get_msglevel, | |
| .set_msglevel = efx_ef100_rep_ethtool_set_msglevel, | |
| .get_ringparam = efx_ef100_rep_ethtool_get_ringparam, | |
| .set_ringparam = efx_ef100_rep_ethtool_set_ringparam, | |
| }; | |
| static struct efx_rep *efx_ef100_rep_create_netdev(struct efx_nic *efx, | |
| unsigned int i) | |
| { | |
| struct net_device *net_dev; | |
| struct efx_rep *efv; | |
| int rc; | |
| net_dev = alloc_etherdev_mq(sizeof(*efv), 1); | |
| if (!net_dev) | |
| return ERR_PTR(-ENOMEM); | |
| efv = netdev_priv(net_dev); | |
| rc = efx_ef100_rep_init_struct(efx, efv, i); | |
| if (rc) | |
| goto fail1; | |
| efv->net_dev = net_dev; | |
| rtnl_lock(); | |
| spin_lock_bh(&efx->vf_reps_lock); | |
| list_add_tail(&efv->list, &efx->vf_reps); | |
| spin_unlock_bh(&efx->vf_reps_lock); | |
| if (netif_running(efx->net_dev) && efx->state == STATE_NET_UP) { | |
| netif_device_attach(net_dev); | |
| netif_carrier_on(net_dev); | |
| } else { | |
| netif_carrier_off(net_dev); | |
| netif_tx_stop_all_queues(net_dev); | |
| } | |
| rtnl_unlock(); | |
| net_dev->netdev_ops = &efx_ef100_rep_netdev_ops; | |
| net_dev->ethtool_ops = &efx_ef100_rep_ethtool_ops; | |
| net_dev->min_mtu = EFX_MIN_MTU; | |
| net_dev->max_mtu = EFX_MAX_MTU; | |
| net_dev->lltx = true; | |
| return efv; | |
| fail1: | |
| free_netdev(net_dev); | |
| return ERR_PTR(rc); | |
| } | |
| static int efx_ef100_configure_rep(struct efx_rep *efv) | |
| { | |
| struct efx_nic *efx = efv->parent; | |
| int rc; | |
| efv->rx_pring_size = EFX_REP_DEFAULT_PSEUDO_RING_SIZE; | |
| /* Look up actual mport ID */ | |
| rc = efx_mae_lookup_mport(efx, efv->idx, &efv->mport); | |
| if (rc) | |
| return rc; | |
| pci_dbg(efx->pci_dev, "VF %u has mport ID %#x\n", efv->idx, efv->mport); | |
| /* mport label should fit in 16 bits */ | |
| WARN_ON(efv->mport >> 16); | |
| return efx_tc_configure_default_rule_rep(efv); | |
| } | |
| static void efx_ef100_deconfigure_rep(struct efx_rep *efv) | |
| { | |
| struct efx_nic *efx = efv->parent; | |
| efx_tc_deconfigure_default_rule(efx, &efv->dflt); | |
| } | |
| static void efx_ef100_rep_destroy_netdev(struct efx_rep *efv) | |
| { | |
| struct efx_nic *efx = efv->parent; | |
| rtnl_lock(); | |
| spin_lock_bh(&efx->vf_reps_lock); | |
| list_del(&efv->list); | |
| spin_unlock_bh(&efx->vf_reps_lock); | |
| rtnl_unlock(); | |
| synchronize_rcu(); | |
| free_netdev(efv->net_dev); | |
| } | |
| int efx_ef100_vfrep_create(struct efx_nic *efx, unsigned int i) | |
| { | |
| struct efx_rep *efv; | |
| int rc; | |
| efv = efx_ef100_rep_create_netdev(efx, i); | |
| if (IS_ERR(efv)) { | |
| rc = PTR_ERR(efv); | |
| pci_err(efx->pci_dev, | |
| "Failed to create representor for VF %d, rc %d\n", i, | |
| rc); | |
| return rc; | |
| } | |
| rc = efx_ef100_configure_rep(efv); | |
| if (rc) { | |
| pci_err(efx->pci_dev, | |
| "Failed to configure representor for VF %d, rc %d\n", | |
| i, rc); | |
| goto fail1; | |
| } | |
| ef100_rep_set_devlink_port(efv); | |
| rc = register_netdev(efv->net_dev); | |
| if (rc) { | |
| pci_err(efx->pci_dev, | |
| "Failed to register representor for VF %d, rc %d\n", | |
| i, rc); | |
| goto fail2; | |
| } | |
| pci_dbg(efx->pci_dev, "Representor for VF %d is %s\n", i, | |
| efv->net_dev->name); | |
| return 0; | |
| fail2: | |
| ef100_rep_unset_devlink_port(efv); | |
| efx_ef100_deconfigure_rep(efv); | |
| fail1: | |
| efx_ef100_rep_destroy_netdev(efv); | |
| return rc; | |
| } | |
| void efx_ef100_vfrep_destroy(struct efx_nic *efx, struct efx_rep *efv) | |
| { | |
| struct net_device *rep_dev; | |
| rep_dev = efv->net_dev; | |
| if (!rep_dev) | |
| return; | |
| netif_dbg(efx, drv, rep_dev, "Removing VF representor\n"); | |
| unregister_netdev(rep_dev); | |
| ef100_rep_unset_devlink_port(efv); | |
| efx_ef100_deconfigure_rep(efv); | |
| efx_ef100_rep_destroy_netdev(efv); | |
| } | |
| void efx_ef100_fini_vfreps(struct efx_nic *efx) | |
| { | |
| struct ef100_nic_data *nic_data = efx->nic_data; | |
| struct efx_rep *efv, *next; | |
| if (!nic_data->grp_mae) | |
| return; | |
| list_for_each_entry_safe(efv, next, &efx->vf_reps, list) | |
| efx_ef100_vfrep_destroy(efx, efv); | |
| } | |
| static bool ef100_mport_is_pcie_vnic(struct mae_mport_desc *mport_desc) | |
| { | |
| return mport_desc->mport_type == MAE_MPORT_DESC_MPORT_TYPE_VNIC && | |
| mport_desc->vnic_client_type == MAE_MPORT_DESC_VNIC_CLIENT_TYPE_FUNCTION; | |
| } | |
| bool ef100_mport_on_local_intf(struct efx_nic *efx, | |
| struct mae_mport_desc *mport_desc) | |
| { | |
| struct ef100_nic_data *nic_data = efx->nic_data; | |
| bool pcie_func; | |
| pcie_func = ef100_mport_is_pcie_vnic(mport_desc); | |
| return nic_data->have_local_intf && pcie_func && | |
| mport_desc->interface_idx == nic_data->local_mae_intf; | |
| } | |
| bool ef100_mport_is_vf(struct mae_mport_desc *mport_desc) | |
| { | |
| bool pcie_func; | |
| pcie_func = ef100_mport_is_pcie_vnic(mport_desc); | |
| return pcie_func && (mport_desc->vf_idx != MAE_MPORT_DESC_VF_IDX_NULL); | |
| } | |
| void efx_ef100_init_reps(struct efx_nic *efx) | |
| { | |
| struct ef100_nic_data *nic_data = efx->nic_data; | |
| int rc; | |
| nic_data->have_local_intf = false; | |
| rc = efx_mae_enumerate_mports(efx); | |
| if (rc) | |
| pci_warn(efx->pci_dev, | |
| "Could not enumerate mports (rc=%d), are we admin?", | |
| rc); | |
| } | |
| void efx_ef100_fini_reps(struct efx_nic *efx) | |
| { | |
| struct efx_mae *mae = efx->mae; | |
| rhashtable_free_and_destroy(&mae->mports_ht, efx_mae_remove_mport, | |
| NULL); | |
| } | |
| static int efx_ef100_rep_poll(struct napi_struct *napi, int weight) | |
| { | |
| struct efx_rep *efv = container_of(napi, struct efx_rep, napi); | |
| unsigned int read_index; | |
| struct list_head head; | |
| struct sk_buff *skb; | |
| bool need_resched; | |
| int spent = 0; | |
| INIT_LIST_HEAD(&head); | |
| /* Grab up to 'weight' pending SKBs */ | |
| spin_lock_bh(&efv->rx_lock); | |
| read_index = efv->write_index; | |
| while (spent < weight && !list_empty(&efv->rx_list)) { | |
| skb = list_first_entry(&efv->rx_list, struct sk_buff, list); | |
| list_del(&skb->list); | |
| list_add_tail(&skb->list, &head); | |
| spent++; | |
| } | |
| spin_unlock_bh(&efv->rx_lock); | |
| /* Receive them */ | |
| netif_receive_skb_list(&head); | |
| if (spent < weight) | |
| if (napi_complete_done(napi, spent)) { | |
| spin_lock_bh(&efv->rx_lock); | |
| efv->read_index = read_index; | |
| /* If write_index advanced while we were doing the | |
| * RX, then storing our read_index won't re-prime the | |
| * fake-interrupt. In that case, we need to schedule | |
| * NAPI again to consume the additional packet(s). | |
| */ | |
| need_resched = efv->write_index != read_index; | |
| spin_unlock_bh(&efv->rx_lock); | |
| if (need_resched) | |
| napi_schedule(&efv->napi); | |
| } | |
| return spent; | |
| } | |
| void efx_ef100_rep_rx_packet(struct efx_rep *efv, struct efx_rx_buffer *rx_buf) | |
| { | |
| u8 *eh = efx_rx_buf_va(rx_buf); | |
| struct sk_buff *skb; | |
| bool primed; | |
| /* Don't allow too many queued SKBs to build up, as they consume | |
| * GFP_ATOMIC memory. If we overrun, just start dropping. | |
| */ | |
| if (efv->write_index - READ_ONCE(efv->read_index) > efv->rx_pring_size) { | |
| atomic64_inc(&efv->stats.rx_dropped); | |
| if (net_ratelimit()) | |
| netif_dbg(efv->parent, rx_err, efv->net_dev, | |
| "nodesc-dropped packet of length %u\n", | |
| rx_buf->len); | |
| return; | |
| } | |
| skb = netdev_alloc_skb(efv->net_dev, rx_buf->len); | |
| if (!skb) { | |
| atomic64_inc(&efv->stats.rx_dropped); | |
| if (net_ratelimit()) | |
| netif_dbg(efv->parent, rx_err, efv->net_dev, | |
| "noskb-dropped packet of length %u\n", | |
| rx_buf->len); | |
| return; | |
| } | |
| memcpy(skb->data, eh, rx_buf->len); | |
| __skb_put(skb, rx_buf->len); | |
| skb_record_rx_queue(skb, 0); /* rep is single-queue */ | |
| /* Move past the ethernet header */ | |
| skb->protocol = eth_type_trans(skb, efv->net_dev); | |
| skb_checksum_none_assert(skb); | |
| atomic64_inc(&efv->stats.rx_packets); | |
| atomic64_add(rx_buf->len, &efv->stats.rx_bytes); | |
| /* Add it to the rx list */ | |
| spin_lock_bh(&efv->rx_lock); | |
| primed = efv->read_index == efv->write_index; | |
| list_add_tail(&skb->list, &efv->rx_list); | |
| efv->write_index++; | |
| spin_unlock_bh(&efv->rx_lock); | |
| /* Trigger rx work */ | |
| if (primed) | |
| napi_schedule(&efv->napi); | |
| } | |
| struct efx_rep *efx_ef100_find_rep_by_mport(struct efx_nic *efx, u16 mport) | |
| { | |
| struct efx_rep *efv, *out = NULL; | |
| /* spinlock guards against list mutation while we're walking it; | |
| * but caller must also hold rcu_read_lock() to ensure the netdev | |
| * isn't freed after we drop the spinlock. | |
| */ | |
| spin_lock_bh(&efx->vf_reps_lock); | |
| list_for_each_entry(efv, &efx->vf_reps, list) | |
| if (efv->mport == mport) { | |
| out = efv; | |
| break; | |
| } | |
| spin_unlock_bh(&efx->vf_reps_lock); | |
| return out; | |
| } |