diff options
Diffstat (limited to 'drivers/net/ethernet/sfc/ef100_netdev.c')
-rw-r--r-- | drivers/net/ethernet/sfc/ef100_netdev.c | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/drivers/net/ethernet/sfc/ef100_netdev.c b/drivers/net/ethernet/sfc/ef100_netdev.c new file mode 100644 index 000000000000..63c311ba28b9 --- /dev/null +++ b/drivers/net/ethernet/sfc/ef100_netdev.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + * Driver for Solarflare network controllers and boards + * Copyright 2018 Solarflare Communications Inc. + * Copyright 2019-2020 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 "net_driver.h" +#include "mcdi_port_common.h" +#include "mcdi_functions.h" +#include "efx_common.h" +#include "efx_channels.h" +#include "tx_common.h" +#include "ef100_netdev.h" +#include "ef100_ethtool.h" +#include "nic_common.h" +#include "ef100_nic.h" +#include "ef100_tx.h" +#include "ef100_regs.h" +#include "mcdi_filters.h" +#include "rx_common.h" + +static void ef100_update_name(struct efx_nic *efx) +{ + strcpy(efx->name, efx->net_dev->name); +} + +static int ef100_alloc_vis(struct efx_nic *efx, unsigned int *allocated_vis) +{ + /* EF100 uses a single TXQ per channel, as all checksum offloading + * is configured in the TX descriptor, and there is no TX Pacer for + * HIGHPRI queues. + */ + unsigned int tx_vis = efx->n_tx_channels + efx->n_extra_tx_channels; + unsigned int rx_vis = efx->n_rx_channels; + unsigned int min_vis, max_vis; + + EFX_WARN_ON_PARANOID(efx->tx_queues_per_channel != 1); + + tx_vis += efx->n_xdp_channels * efx->xdp_tx_per_channel; + + max_vis = max(rx_vis, tx_vis); + /* Currently don't handle resource starvation and only accept + * our maximum needs and no less. + */ + min_vis = max_vis; + + return efx_mcdi_alloc_vis(efx, min_vis, max_vis, + NULL, allocated_vis); +} + +static int ef100_remap_bar(struct efx_nic *efx, int max_vis) +{ + unsigned int uc_mem_map_size; + void __iomem *membase; + + efx->max_vis = max_vis; + uc_mem_map_size = PAGE_ALIGN(max_vis * efx->vi_stride); + + /* Extend the original UC mapping of the memory BAR */ + membase = ioremap(efx->membase_phys, uc_mem_map_size); + if (!membase) { + netif_err(efx, probe, efx->net_dev, + "could not extend memory BAR to %x\n", + uc_mem_map_size); + return -ENOMEM; + } + iounmap(efx->membase); + efx->membase = membase; + return 0; +} + +/* Context: process, rtnl_lock() held. + * Note that the kernel will ignore our return code; this method + * should really be a void. + */ +static int ef100_net_stop(struct net_device *net_dev) +{ + struct efx_nic *efx = netdev_priv(net_dev); + + netif_dbg(efx, ifdown, efx->net_dev, "closing on CPU %d\n", + raw_smp_processor_id()); + + netif_stop_queue(net_dev); + efx_stop_all(efx); + efx_mcdi_mac_fini_stats(efx); + efx_disable_interrupts(efx); + efx_clear_interrupt_affinity(efx); + efx_nic_fini_interrupt(efx); + efx_remove_filters(efx); + efx_fini_napi(efx); + efx_remove_channels(efx); + efx_mcdi_free_vis(efx); + efx_remove_interrupts(efx); + + return 0; +} + +/* Context: process, rtnl_lock() held. */ +static int ef100_net_open(struct net_device *net_dev) +{ + struct efx_nic *efx = netdev_priv(net_dev); + unsigned int allocated_vis; + int rc; + + ef100_update_name(efx); + netif_dbg(efx, ifup, net_dev, "opening device on CPU %d\n", + raw_smp_processor_id()); + + rc = efx_check_disabled(efx); + if (rc) + goto fail; + + rc = efx_probe_interrupts(efx); + if (rc) + goto fail; + + rc = efx_set_channels(efx); + if (rc) + goto fail; + + rc = efx_mcdi_free_vis(efx); + if (rc) + goto fail; + + rc = ef100_alloc_vis(efx, &allocated_vis); + if (rc) + goto fail; + + rc = efx_probe_channels(efx); + if (rc) + return rc; + + rc = ef100_remap_bar(efx, allocated_vis); + if (rc) + goto fail; + + efx_init_napi(efx); + + rc = efx_probe_filters(efx); + if (rc) + goto fail; + + rc = efx_nic_init_interrupt(efx); + if (rc) + goto fail; + efx_set_interrupt_affinity(efx); + + rc = efx_enable_interrupts(efx); + if (rc) + goto fail; + + /* in case the MC rebooted while we were stopped, consume the change + * to the warm reboot count + */ + (void) efx_mcdi_poll_reboot(efx); + + rc = efx_mcdi_mac_init_stats(efx); + if (rc) + goto fail; + + efx_start_all(efx); + + /* Link state detection is normally event-driven; we have + * to poll now because we could have missed a change + */ + mutex_lock(&efx->mac_lock); + if (efx_mcdi_phy_poll(efx)) + efx_link_status_changed(efx); + mutex_unlock(&efx->mac_lock); + + return 0; + +fail: + ef100_net_stop(net_dev); + return rc; +} + +/* Initiate a packet transmission. We use one channel per CPU + * (sharing when we have more CPUs than channels). + * + * Context: non-blocking. + * Note that returning anything other than NETDEV_TX_OK will cause the + * OS to free the skb. + */ +static netdev_tx_t ef100_hard_start_xmit(struct sk_buff *skb, + struct net_device *net_dev) +{ + struct efx_nic *efx = netdev_priv(net_dev); + struct efx_tx_queue *tx_queue; + struct efx_channel *channel; + int rc; + + channel = efx_get_tx_channel(efx, skb_get_queue_mapping(skb)); + netif_vdbg(efx, tx_queued, efx->net_dev, + "%s len %d data %d channel %d\n", __func__, + skb->len, skb->data_len, channel->channel); + if (!efx->n_channels || !efx->n_tx_channels || !channel) { + netif_stop_queue(net_dev); + goto err; + } + + tx_queue = &channel->tx_queue[0]; + rc = ef100_enqueue_skb(tx_queue, skb); + if (rc == 0) + return NETDEV_TX_OK; + +err: + net_dev->stats.tx_dropped++; + return NETDEV_TX_OK; +} + +static const struct net_device_ops ef100_netdev_ops = { + .ndo_open = ef100_net_open, + .ndo_stop = ef100_net_stop, + .ndo_start_xmit = ef100_hard_start_xmit, + .ndo_get_stats64 = efx_net_stats, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_rx_mode = efx_set_rx_mode, /* Lookout */ + .ndo_get_phys_port_id = efx_get_phys_port_id, + .ndo_get_phys_port_name = efx_get_phys_port_name, +#ifdef CONFIG_RFS_ACCEL + .ndo_rx_flow_steer = efx_filter_rfs, +#endif +}; + +/* Netdev registration + */ +int ef100_netdev_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct efx_nic *efx = container_of(this, struct efx_nic, netdev_notifier); + struct net_device *net_dev = netdev_notifier_info_to_dev(ptr); + + if (netdev_priv(net_dev) == efx && event == NETDEV_CHANGENAME) + ef100_update_name(efx); + + return NOTIFY_DONE; +} + +int ef100_register_netdev(struct efx_nic *efx) +{ + struct net_device *net_dev = efx->net_dev; + int rc; + + net_dev->watchdog_timeo = 5 * HZ; + net_dev->irq = efx->pci_dev->irq; + net_dev->netdev_ops = &ef100_netdev_ops; + net_dev->min_mtu = EFX_MIN_MTU; + net_dev->max_mtu = EFX_MAX_MTU; + net_dev->ethtool_ops = &ef100_ethtool_ops; + + rtnl_lock(); + + rc = dev_alloc_name(net_dev, net_dev->name); + if (rc < 0) + goto fail_locked; + ef100_update_name(efx); + + rc = register_netdevice(net_dev); + if (rc) + goto fail_locked; + + /* Always start with carrier off; PHY events will detect the link */ + netif_carrier_off(net_dev); + + efx->state = STATE_READY; + rtnl_unlock(); + efx_init_mcdi_logging(efx); + + return 0; + +fail_locked: + rtnl_unlock(); + netif_err(efx, drv, efx->net_dev, "could not register net dev\n"); + return rc; +} + +void ef100_unregister_netdev(struct efx_nic *efx) +{ + if (efx_dev_registered(efx)) { + efx_fini_mcdi_logging(efx); + efx->state = STATE_UNINIT; + unregister_netdev(efx->net_dev); + } +} |