Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/ethernet/fungible/funeth/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 51 kB image not shown  

Quelle  funeth_main.c   Sprache: C

 
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)

#include <linux/bpf.h>
#include <linux/crash_dump.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/filter.h>
#include <linux/idr.h>
#include <linux/if_vlan.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/rtnetlink.h>
#include <linux/inetdevice.h>

#include "funeth.h"
#include "funeth_devlink.h"
#include "funeth_ktls.h"
#include "fun_port.h"
#include "fun_queue.h"
#include "funeth_txrx.h"

#define ADMIN_SQ_DEPTH 32
#define ADMIN_CQ_DEPTH 64
#define ADMIN_RQ_DEPTH 16

/* Default number of Tx/Rx queues. */
#define FUN_DFLT_QUEUES 16U

enum {
 FUN_SERV_RES_CHANGE = FUN_SERV_FIRST_AVAIL,
 FUN_SERV_DEL_PORTS,
};

static const struct pci_device_id funeth_id_table[] = {
 { PCI_VDEVICE(FUNGIBLE, 0x0101) },
 { PCI_VDEVICE(FUNGIBLE, 0x0181) },
 { 0, }
};

/* Issue a port write admin command with @n key/value pairs. */
static int fun_port_write_cmds(struct funeth_priv *fp, unsigned int n,
          const int *keys, const u64 *data)
{
 unsigned int cmd_size, i;
 union {
  struct fun_admin_port_req req;
  struct fun_admin_port_rsp rsp;
  u8 v[ADMIN_SQE_SIZE];
 } cmd;

 cmd_size = offsetof(struct fun_admin_port_req, u.write.write48) +
  n * sizeof(struct fun_admin_write48_req);
 if (cmd_size > sizeof(cmd) || cmd_size > ADMIN_RSP_MAX_LEN)
  return -EINVAL;

 cmd.req.common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_PORT,
          cmd_size);
 cmd.req.u.write =
  FUN_ADMIN_PORT_WRITE_REQ_INIT(FUN_ADMIN_SUBOP_WRITE, 0,
           fp->netdev->dev_port);
 for (i = 0; i < n; i++)
  cmd.req.u.write.write48[i] =
   FUN_ADMIN_WRITE48_REQ_INIT(keys[i], data[i]);

 return fun_submit_admin_sync_cmd(fp->fdev, &cmd.req.common,
      &cmd.rsp, cmd_size, 0);
}

int fun_port_write_cmd(struct funeth_priv *fp, int key, u64 data)
{
 return fun_port_write_cmds(fp, 1, &key, &data);
}

/* Issue a port read admin command with @n key/value pairs. */
static int fun_port_read_cmds(struct funeth_priv *fp, unsigned int n,
         const int *keys, u64 *data)
{
 const struct fun_admin_read48_rsp *r48rsp;
 unsigned int cmd_size, i;
 int rc;
 union {
  struct fun_admin_port_req req;
  struct fun_admin_port_rsp rsp;
  u8 v[ADMIN_SQE_SIZE];
 } cmd;

 cmd_size = offsetof(struct fun_admin_port_req, u.read.read48) +
  n * sizeof(struct fun_admin_read48_req);
 if (cmd_size > sizeof(cmd) || cmd_size > ADMIN_RSP_MAX_LEN)
  return -EINVAL;

 cmd.req.common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_PORT,
          cmd_size);
 cmd.req.u.read =
  FUN_ADMIN_PORT_READ_REQ_INIT(FUN_ADMIN_SUBOP_READ, 0,
          fp->netdev->dev_port);
 for (i = 0; i < n; i++)
  cmd.req.u.read.read48[i] = FUN_ADMIN_READ48_REQ_INIT(keys[i]);

 rc = fun_submit_admin_sync_cmd(fp->fdev, &cmd.req.common,
           &cmd.rsp, cmd_size, 0);
 if (rc)
  return rc;

 for (r48rsp = cmd.rsp.u.read.read48, i = 0; i < n; i++, r48rsp++) {
  data[i] = FUN_ADMIN_READ48_RSP_DATA_G(r48rsp->key_to_data);
  dev_dbg(fp->fdev->dev,
   "port_read_rsp lport=%u (key_to_data=0x%llx) key=%d data:%lld retval:%lld",
   fp->lport, r48rsp->key_to_data, keys[i], data[i],
   FUN_ADMIN_READ48_RSP_RET_G(r48rsp->key_to_data));
 }
 return 0;
}

int fun_port_read_cmd(struct funeth_priv *fp, int key, u64 *data)
{
 return fun_port_read_cmds(fp, 1, &key, data);
}

static void fun_report_link(struct net_device *netdev)
{
 if (netif_carrier_ok(netdev)) {
  const struct funeth_priv *fp = netdev_priv(netdev);
  const char *fec = "", *pause = "";
  int speed = fp->link_speed;
  char unit = 'M';

  if (fp->link_speed >= SPEED_1000) {
   speed /= 1000;
   unit = 'G';
  }

  if (fp->active_fec & FUN_PORT_FEC_RS)
   fec = ", RS-FEC";
  else if (fp->active_fec & FUN_PORT_FEC_FC)
   fec = ", BASER-FEC";

  if ((fp->active_fc & FUN_PORT_CAP_PAUSE_MASK) == FUN_PORT_CAP_PAUSE_MASK)
   pause = ", Tx/Rx PAUSE";
  else if (fp->active_fc & FUN_PORT_CAP_RX_PAUSE)
   pause = ", Rx PAUSE";
  else if (fp->active_fc & FUN_PORT_CAP_TX_PAUSE)
   pause = ", Tx PAUSE";

  netdev_info(netdev, "Link up at %d %cb/s full-duplex%s%s\n",
       speed, unit, pause, fec);
 } else {
  netdev_info(netdev, "Link down\n");
 }
}

static int fun_adi_write(struct fun_dev *fdev, enum fun_admin_adi_attr attr,
    unsigned int adi_id, const struct fun_adi_param *param)
{
 struct fun_admin_adi_req req = {
  .common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_ADI,
           sizeof(req)),
  .u.write.subop = FUN_ADMIN_SUBOP_WRITE,
  .u.write.attribute = attr,
  .u.write.id = cpu_to_be32(adi_id),
  .u.write.param = *param
 };

 return fun_submit_admin_sync_cmd(fdev, &req.common, NULL, 0, 0);
}

/* Configure RSS for the given port. @op determines whether a new RSS context
 * is to be created or whether an existing one should be reconfigured. The
 * remaining parameters specify the hashing algorithm, key, and indirection
 * table.
 *
 * This initiates packet delivery to the Rx queues set in the indirection
 * table.
 */

int fun_config_rss(struct net_device *dev, int algo, const u8 *key,
     const u32 *qtable, u8 op)
{
 struct funeth_priv *fp = netdev_priv(dev);
 unsigned int table_len = fp->indir_table_nentries;
 unsigned int len = FUN_ETH_RSS_MAX_KEY_SIZE + sizeof(u32) * table_len;
 struct funeth_rxq **rxqs = rtnl_dereference(fp->rxqs);
 union {
  struct {
   struct fun_admin_rss_req req;
   struct fun_dataop_gl gl;
  };
  struct fun_admin_generic_create_rsp rsp;
 } cmd;
 __be32 *indir_tab;
 u16 flags;
 int rc;

 if (op != FUN_ADMIN_SUBOP_CREATE && fp->rss_hw_id == FUN_HCI_ID_INVALID)
  return -EINVAL;

 flags = op == FUN_ADMIN_SUBOP_CREATE ?
   FUN_ADMIN_RES_CREATE_FLAG_ALLOCATOR : 0;
 cmd.req.common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_RSS,
          sizeof(cmd));
 cmd.req.u.create =
  FUN_ADMIN_RSS_CREATE_REQ_INIT(op, flags, fp->rss_hw_id,
           dev->dev_port, algo,
           FUN_ETH_RSS_MAX_KEY_SIZE,
           table_len, 0,
           FUN_ETH_RSS_MAX_KEY_SIZE);
 cmd.req.u.create.dataop = FUN_DATAOP_HDR_INIT(1, 0, 1, 0, len);
 fun_dataop_gl_init(&cmd.gl, 0, 0, len, fp->rss_dma_addr);

 /* write the key and indirection table into the RSS DMA area */
 memcpy(fp->rss_cfg, key, FUN_ETH_RSS_MAX_KEY_SIZE);
 indir_tab = fp->rss_cfg + FUN_ETH_RSS_MAX_KEY_SIZE;
 for (rc = 0; rc < table_len; rc++)
  *indir_tab++ = cpu_to_be32(rxqs[*qtable++]->hw_cqid);

 rc = fun_submit_admin_sync_cmd(fp->fdev, &cmd.req.common,
           &cmd.rsp, sizeof(cmd.rsp), 0);
 if (!rc && op == FUN_ADMIN_SUBOP_CREATE)
  fp->rss_hw_id = be32_to_cpu(cmd.rsp.id);
 return rc;
}

/* Destroy the HW RSS conntext associated with the given port. This also stops
 * all packet delivery to our Rx queues.
 */

static void fun_destroy_rss(struct funeth_priv *fp)
{
 if (fp->rss_hw_id != FUN_HCI_ID_INVALID) {
  fun_res_destroy(fp->fdev, FUN_ADMIN_OP_RSS, 0, fp->rss_hw_id);
  fp->rss_hw_id = FUN_HCI_ID_INVALID;
 }
}

static void fun_irq_aff_notify(struct irq_affinity_notify *notify,
          const cpumask_t *mask)
{
 struct fun_irq *p = container_of(notify, struct fun_irq, aff_notify);

 cpumask_copy(&p->affinity_mask, mask);
}

static void fun_irq_aff_release(struct kref __always_unused *ref)
{
}

/* Allocate an IRQ structure, assign an MSI-X index and initial affinity to it,
 * and add it to the IRQ XArray.
 */

static struct fun_irq *fun_alloc_qirq(struct funeth_priv *fp, unsigned int idx,
          int node, unsigned int xa_idx_offset)
{
 struct fun_irq *irq;
 int cpu, res;

 cpu = cpumask_local_spread(idx, node);
 node = cpu_to_mem(cpu);

 irq = kzalloc_node(sizeof(*irq), GFP_KERNEL, node);
 if (!irq)
  return ERR_PTR(-ENOMEM);

 res = fun_reserve_irqs(fp->fdev, 1, &irq->irq_idx);
 if (res != 1)
  goto free_irq;

 res = xa_insert(&fp->irqs, idx + xa_idx_offset, irq, GFP_KERNEL);
 if (res)
  goto release_irq;

 irq->irq = pci_irq_vector(fp->pdev, irq->irq_idx);
 cpumask_set_cpu(cpu, &irq->affinity_mask);
 irq->aff_notify.notify = fun_irq_aff_notify;
 irq->aff_notify.release = fun_irq_aff_release;
 irq->state = FUN_IRQ_INIT;
 return irq;

release_irq:
 fun_release_irqs(fp->fdev, 1, &irq->irq_idx);
free_irq:
 kfree(irq);
 return ERR_PTR(res);
}

static void fun_free_qirq(struct funeth_priv *fp, struct fun_irq *irq)
{
 netif_napi_del(&irq->napi);
 fun_release_irqs(fp->fdev, 1, &irq->irq_idx);
 kfree(irq);
}

/* Release the IRQs reserved for Tx/Rx queues that aren't being used. */
static void fun_prune_queue_irqs(struct net_device *dev)
{
 struct funeth_priv *fp = netdev_priv(dev);
 unsigned int nreleased = 0;
 struct fun_irq *irq;
 unsigned long idx;

 xa_for_each(&fp->irqs, idx, irq) {
  if (irq->txq || irq->rxq)  /* skip those in use */
   continue;

  xa_erase(&fp->irqs, idx);
  fun_free_qirq(fp, irq);
  nreleased++;
  if (idx < fp->rx_irq_ofst)
   fp->num_tx_irqs--;
  else
   fp->num_rx_irqs--;
 }
 netif_info(fp, intr, dev, "Released %u queue IRQs\n", nreleased);
}

/* Reserve IRQs, one per queue, to acommodate the requested queue numbers @ntx
 * and @nrx. IRQs are added incrementally to those we already have.
 * We hold on to allocated IRQs until garbage collection of unused IRQs is
 * separately requested.
 */

static int fun_alloc_queue_irqs(struct net_device *dev, unsigned int ntx,
    unsigned int nrx)
{
 struct funeth_priv *fp = netdev_priv(dev);
 int node = dev_to_node(&fp->pdev->dev);
 struct fun_irq *irq;
 unsigned int i;

 for (i = fp->num_tx_irqs; i < ntx; i++) {
  irq = fun_alloc_qirq(fp, i, node, 0);
  if (IS_ERR(irq))
   return PTR_ERR(irq);

  fp->num_tx_irqs++;
  netif_napi_add_tx(dev, &irq->napi, fun_txq_napi_poll);
 }

 for (i = fp->num_rx_irqs; i < nrx; i++) {
  irq = fun_alloc_qirq(fp, i, node, fp->rx_irq_ofst);
  if (IS_ERR(irq))
   return PTR_ERR(irq);

  fp->num_rx_irqs++;
  netif_napi_add(dev, &irq->napi, fun_rxq_napi_poll);
 }

 netif_info(fp, intr, dev, "Reserved %u/%u IRQs for Tx/Rx queues\n",
     ntx, nrx);
 return 0;
}

static void free_txqs(struct funeth_txq **txqs, unsigned int nqs,
        unsigned int start, int state)
{
 unsigned int i;

 for (i = start; i < nqs && txqs[i]; i++)
  txqs[i] = funeth_txq_free(txqs[i], state);
}

static int alloc_txqs(struct net_device *dev, struct funeth_txq **txqs,
        unsigned int nqs, unsigned int depth, unsigned int start,
        int state)
{
 struct funeth_priv *fp = netdev_priv(dev);
 unsigned int i;
 int err;

 for (i = start; i < nqs; i++) {
  err = funeth_txq_create(dev, i, depth, xa_load(&fp->irqs, i),
     state, &txqs[i]);
  if (err) {
   free_txqs(txqs, nqs, start, FUN_QSTATE_DESTROYED);
   return err;
  }
 }
 return 0;
}

static void free_rxqs(struct funeth_rxq **rxqs, unsigned int nqs,
        unsigned int start, int state)
{
 unsigned int i;

 for (i = start; i < nqs && rxqs[i]; i++)
  rxqs[i] = funeth_rxq_free(rxqs[i], state);
}

static int alloc_rxqs(struct net_device *dev, struct funeth_rxq **rxqs,
        unsigned int nqs, unsigned int ncqe, unsigned int nrqe,
        unsigned int start, int state)
{
 struct funeth_priv *fp = netdev_priv(dev);
 unsigned int i;
 int err;

 for (i = start; i < nqs; i++) {
  err = funeth_rxq_create(dev, i, ncqe, nrqe,
     xa_load(&fp->irqs, i + fp->rx_irq_ofst),
     state, &rxqs[i]);
  if (err) {
   free_rxqs(rxqs, nqs, start, FUN_QSTATE_DESTROYED);
   return err;
  }
 }
 return 0;
}

static void free_xdpqs(struct funeth_txq **xdpqs, unsigned int nqs,
         unsigned int start, int state)
{
 unsigned int i;

 for (i = start; i < nqs && xdpqs[i]; i++)
  xdpqs[i] = funeth_txq_free(xdpqs[i], state);

 if (state == FUN_QSTATE_DESTROYED)
  kfree(xdpqs);
}

static struct funeth_txq **alloc_xdpqs(struct net_device *dev, unsigned int nqs,
           unsigned int depth, unsigned int start,
           int state)
{
 struct funeth_txq **xdpqs;
 unsigned int i;
 int err;

 xdpqs = kcalloc(nqs, sizeof(*xdpqs), GFP_KERNEL);
 if (!xdpqs)
  return ERR_PTR(-ENOMEM);

 for (i = start; i < nqs; i++) {
  err = funeth_txq_create(dev, i, depth, NULL, state, &xdpqs[i]);
  if (err) {
   free_xdpqs(xdpqs, nqs, start, FUN_QSTATE_DESTROYED);
   return ERR_PTR(err);
  }
 }
 return xdpqs;
}

static void fun_free_rings(struct net_device *netdev, struct fun_qset *qset)
{
 struct funeth_priv *fp = netdev_priv(netdev);
 struct funeth_txq **xdpqs = qset->xdpqs;
 struct funeth_rxq **rxqs = qset->rxqs;

 /* qset may not specify any queues to operate on. In that case the
 * currently installed queues are implied.
 */

 if (!rxqs) {
  rxqs = rtnl_dereference(fp->rxqs);
  xdpqs = rtnl_dereference(fp->xdpqs);
  qset->txqs = fp->txqs;
  qset->nrxqs = netdev->real_num_rx_queues;
  qset->ntxqs = netdev->real_num_tx_queues;
  qset->nxdpqs = fp->num_xdpqs;
 }
 if (!rxqs)
  return;

 if (rxqs == rtnl_dereference(fp->rxqs)) {
  rcu_assign_pointer(fp->rxqs, NULL);
  rcu_assign_pointer(fp->xdpqs, NULL);
  synchronize_net();
  fp->txqs = NULL;
 }

 free_rxqs(rxqs, qset->nrxqs, qset->rxq_start, qset->state);
 free_txqs(qset->txqs, qset->ntxqs, qset->txq_start, qset->state);
 free_xdpqs(xdpqs, qset->nxdpqs, qset->xdpq_start, qset->state);
 if (qset->state == FUN_QSTATE_DESTROYED)
  kfree(rxqs);

 /* Tell the caller which queues were operated on. */
 qset->rxqs = rxqs;
 qset->xdpqs = xdpqs;
}

static int fun_alloc_rings(struct net_device *netdev, struct fun_qset *qset)
{
 struct funeth_txq **xdpqs = NULL, **txqs;
 struct funeth_rxq **rxqs;
 int err;

 err = fun_alloc_queue_irqs(netdev, qset->ntxqs, qset->nrxqs);
 if (err)
  return err;

 rxqs = kcalloc(qset->ntxqs + qset->nrxqs, sizeof(*rxqs), GFP_KERNEL);
 if (!rxqs)
  return -ENOMEM;

 if (qset->nxdpqs) {
  xdpqs = alloc_xdpqs(netdev, qset->nxdpqs, qset->sq_depth,
        qset->xdpq_start, qset->state);
  if (IS_ERR(xdpqs)) {
   err = PTR_ERR(xdpqs);
   goto free_qvec;
  }
 }

 txqs = (struct funeth_txq **)&rxqs[qset->nrxqs];
 err = alloc_txqs(netdev, txqs, qset->ntxqs, qset->sq_depth,
    qset->txq_start, qset->state);
 if (err)
  goto free_xdpqs;

 err = alloc_rxqs(netdev, rxqs, qset->nrxqs, qset->cq_depth,
    qset->rq_depth, qset->rxq_start, qset->state);
 if (err)
  goto free_txqs;

 qset->rxqs = rxqs;
 qset->txqs = txqs;
 qset->xdpqs = xdpqs;
 return 0;

free_txqs:
 free_txqs(txqs, qset->ntxqs, qset->txq_start, FUN_QSTATE_DESTROYED);
free_xdpqs:
 free_xdpqs(xdpqs, qset->nxdpqs, qset->xdpq_start, FUN_QSTATE_DESTROYED);
free_qvec:
 kfree(rxqs);
 return err;
}

/* Take queues to the next level. Presently this means creating them on the
 * device.
 */

static int fun_advance_ring_state(struct net_device *dev, struct fun_qset *qset)
{
 struct funeth_priv *fp = netdev_priv(dev);
 int i, err;

 for (i = 0; i < qset->nrxqs; i++) {
  err = fun_rxq_create_dev(qset->rxqs[i],
      xa_load(&fp->irqs,
       i + fp->rx_irq_ofst));
  if (err)
   goto out;
 }

 for (i = 0; i < qset->ntxqs; i++) {
  err = fun_txq_create_dev(qset->txqs[i], xa_load(&fp->irqs, i));
  if (err)
   goto out;
 }

 for (i = 0; i < qset->nxdpqs; i++) {
  err = fun_txq_create_dev(qset->xdpqs[i], NULL);
  if (err)
   goto out;
 }

 return 0;

out:
 fun_free_rings(dev, qset);
 return err;
}

static int fun_port_create(struct net_device *netdev)
{
 struct funeth_priv *fp = netdev_priv(netdev);
 union {
  struct fun_admin_port_req req;
  struct fun_admin_port_rsp rsp;
 } cmd;
 int rc;

 if (fp->lport != INVALID_LPORT)
  return 0;

 cmd.req.common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_PORT,
          sizeof(cmd.req));
 cmd.req.u.create =
  FUN_ADMIN_PORT_CREATE_REQ_INIT(FUN_ADMIN_SUBOP_CREATE, 0,
            netdev->dev_port);

 rc = fun_submit_admin_sync_cmd(fp->fdev, &cmd.req.common, &cmd.rsp,
           sizeof(cmd.rsp), 0);

 if (!rc)
  fp->lport = be16_to_cpu(cmd.rsp.u.create.lport);
 return rc;
}

static int fun_port_destroy(struct net_device *netdev)
{
 struct funeth_priv *fp = netdev_priv(netdev);

 if (fp->lport == INVALID_LPORT)
  return 0;

 fp->lport = INVALID_LPORT;
 return fun_res_destroy(fp->fdev, FUN_ADMIN_OP_PORT, 0,
          netdev->dev_port);
}

static int fun_eth_create(struct funeth_priv *fp)
{
 union {
  struct fun_admin_eth_req req;
  struct fun_admin_generic_create_rsp rsp;
 } cmd;
 int rc;

 cmd.req.common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_ETH,
          sizeof(cmd.req));
 cmd.req.u.create = FUN_ADMIN_ETH_CREATE_REQ_INIT(
    FUN_ADMIN_SUBOP_CREATE,
    FUN_ADMIN_RES_CREATE_FLAG_ALLOCATOR,
    0, fp->netdev->dev_port);

 rc = fun_submit_admin_sync_cmd(fp->fdev, &cmd.req.common, &cmd.rsp,
           sizeof(cmd.rsp), 0);
 return rc ? rc : be32_to_cpu(cmd.rsp.id);
}

static int fun_vi_create(struct funeth_priv *fp)
{
 struct fun_admin_vi_req req = {
  .common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_VI,
           sizeof(req)),
  .u.create = FUN_ADMIN_VI_CREATE_REQ_INIT(FUN_ADMIN_SUBOP_CREATE,
        0,
        fp->netdev->dev_port,
        fp->netdev->dev_port)
 };

 return fun_submit_admin_sync_cmd(fp->fdev, &req.common, NULL, 0, 0);
}

/* Helper to create an ETH flow and bind an SQ to it.
 * Returns the ETH id (>= 0) on success or a negative error.
 */

int fun_create_and_bind_tx(struct funeth_priv *fp, u32 sqid)
{
 int rc, ethid;

 ethid = fun_eth_create(fp);
 if (ethid >= 0) {
  rc = fun_bind(fp->fdev, FUN_ADMIN_BIND_TYPE_EPSQ, sqid,
         FUN_ADMIN_BIND_TYPE_ETH, ethid);
  if (rc) {
   fun_res_destroy(fp->fdev, FUN_ADMIN_OP_ETH, 0, ethid);
   ethid = rc;
  }
 }
 return ethid;
}

static irqreturn_t fun_queue_irq_handler(int irq, void *data)
{
 struct fun_irq *p = data;

 if (p->rxq) {
  prefetch(p->rxq->next_cqe_info);
  p->rxq->irq_cnt++;
 }
 napi_schedule_irqoff(&p->napi);
 return IRQ_HANDLED;
}

static int fun_enable_irqs(struct net_device *dev)
{
 struct funeth_priv *fp = netdev_priv(dev);
 unsigned long idx, last;
 unsigned int qidx;
 struct fun_irq *p;
 const char *qtype;
 int err;

 xa_for_each(&fp->irqs, idx, p) {
  if (p->txq) {
   qtype = "tx";
   qidx = p->txq->qidx;
  } else if (p->rxq) {
   qtype = "rx";
   qidx = p->rxq->qidx;
  } else {
   continue;
  }

  if (p->state != FUN_IRQ_INIT)
   continue;

  snprintf(p->name, sizeof(p->name) - 1, "%s-%s-%u", dev->name,
    qtype, qidx);
  err = request_irq(p->irq, fun_queue_irq_handler, 0, p->name, p);
  if (err) {
   netdev_err(dev, "Failed to allocate IRQ %u, err %d\n",
       p->irq, err);
   goto unroll;
  }
  p->state = FUN_IRQ_REQUESTED;
 }

 xa_for_each(&fp->irqs, idx, p) {
  if (p->state != FUN_IRQ_REQUESTED)
   continue;
  irq_set_affinity_notifier(p->irq, &p->aff_notify);
  irq_set_affinity_and_hint(p->irq, &p->affinity_mask);
  napi_enable(&p->napi);
  p->state = FUN_IRQ_ENABLED;
 }

 return 0;

unroll:
 last = idx - 1;
 xa_for_each_range(&fp->irqs, idx, p, 0, last)
  if (p->state == FUN_IRQ_REQUESTED) {
   free_irq(p->irq, p);
   p->state = FUN_IRQ_INIT;
  }

 return err;
}

static void fun_disable_one_irq(struct fun_irq *irq)
{
 napi_disable(&irq->napi);
 irq_set_affinity_notifier(irq->irq, NULL);
 irq_update_affinity_hint(irq->irq, NULL);
 free_irq(irq->irq, irq);
 irq->state = FUN_IRQ_INIT;
}

static void fun_disable_irqs(struct net_device *dev)
{
 struct funeth_priv *fp = netdev_priv(dev);
 struct fun_irq *p;
 unsigned long idx;

 xa_for_each(&fp->irqs, idx, p)
  if (p->state == FUN_IRQ_ENABLED)
   fun_disable_one_irq(p);
}

static void fun_down(struct net_device *dev, struct fun_qset *qset)
{
 struct funeth_priv *fp = netdev_priv(dev);

 /* If we don't have queues the data path is already down.
 * Note netif_running(dev) may be true.
 */

 if (!rcu_access_pointer(fp->rxqs))
  return;

 /* It is also down if the queues aren't on the device. */
 if (fp->txqs[0]->init_state >= FUN_QSTATE_INIT_FULL) {
  netif_info(fp, ifdown, dev,
      "Tearing down data path on device\n");
  fun_port_write_cmd(fp, FUN_ADMIN_PORT_KEY_DISABLE, 0);

  netif_carrier_off(dev);
  netif_tx_disable(dev);

  fun_destroy_rss(fp);
  fun_res_destroy(fp->fdev, FUN_ADMIN_OP_VI, 0, dev->dev_port);
  fun_disable_irqs(dev);
 }

 fun_free_rings(dev, qset);
}

static int fun_up(struct net_device *dev, struct fun_qset *qset)
{
 static const int port_keys[] = {
  FUN_ADMIN_PORT_KEY_STATS_DMA_LOW,
  FUN_ADMIN_PORT_KEY_STATS_DMA_HIGH,
  FUN_ADMIN_PORT_KEY_ENABLE
 };

 struct funeth_priv *fp = netdev_priv(dev);
 u64 vals[] = {
  lower_32_bits(fp->stats_dma_addr),
  upper_32_bits(fp->stats_dma_addr),
  FUN_PORT_FLAG_ENABLE_NOTIFY
 };
 int err;

 netif_info(fp, ifup, dev, "Setting up data path on device\n");

 if (qset->rxqs[0]->init_state < FUN_QSTATE_INIT_FULL) {
  err = fun_advance_ring_state(dev, qset);
  if (err)
   return err;
 }

 err = fun_vi_create(fp);
 if (err)
  goto free_queues;

 fp->txqs = qset->txqs;
 rcu_assign_pointer(fp->rxqs, qset->rxqs);
 rcu_assign_pointer(fp->xdpqs, qset->xdpqs);

 err = fun_enable_irqs(dev);
 if (err)
  goto destroy_vi;

 if (fp->rss_cfg) {
  err = fun_config_rss(dev, fp->hash_algo, fp->rss_key,
         fp->indir_table, FUN_ADMIN_SUBOP_CREATE);
 } else {
  /* The non-RSS case has only 1 queue. */
  err = fun_bind(fp->fdev, FUN_ADMIN_BIND_TYPE_VI, dev->dev_port,
          FUN_ADMIN_BIND_TYPE_EPCQ,
          qset->rxqs[0]->hw_cqid);
 }
 if (err)
  goto disable_irqs;

 err = fun_port_write_cmds(fp, 3, port_keys, vals);
 if (err)
  goto free_rss;

 netif_tx_start_all_queues(dev);
 return 0;

free_rss:
 fun_destroy_rss(fp);
disable_irqs:
 fun_disable_irqs(dev);
destroy_vi:
 fun_res_destroy(fp->fdev, FUN_ADMIN_OP_VI, 0, dev->dev_port);
free_queues:
 fun_free_rings(dev, qset);
 return err;
}

static int funeth_open(struct net_device *netdev)
{
 struct funeth_priv *fp = netdev_priv(netdev);
 struct fun_qset qset = {
  .nrxqs = netdev->real_num_rx_queues,
  .ntxqs = netdev->real_num_tx_queues,
  .nxdpqs = fp->num_xdpqs,
  .cq_depth = fp->cq_depth,
  .rq_depth = fp->rq_depth,
  .sq_depth = fp->sq_depth,
  .state = FUN_QSTATE_INIT_FULL,
 };
 int rc;

 rc = fun_alloc_rings(netdev, &qset);
 if (rc)
  return rc;

 rc = fun_up(netdev, &qset);
 if (rc) {
  qset.state = FUN_QSTATE_DESTROYED;
  fun_free_rings(netdev, &qset);
 }

 return rc;
}

static int funeth_close(struct net_device *netdev)
{
 struct fun_qset qset = { .state = FUN_QSTATE_DESTROYED };

 fun_down(netdev, &qset);
 return 0;
}

static void fun_get_stats64(struct net_device *netdev,
       struct rtnl_link_stats64 *stats)
{
 struct funeth_priv *fp = netdev_priv(netdev);
 struct funeth_txq **xdpqs;
 struct funeth_rxq **rxqs;
 unsigned int i, start;

 stats->tx_packets = fp->tx_packets;
 stats->tx_bytes   = fp->tx_bytes;
 stats->tx_dropped = fp->tx_dropped;

 stats->rx_packets = fp->rx_packets;
 stats->rx_bytes   = fp->rx_bytes;
 stats->rx_dropped = fp->rx_dropped;

 rcu_read_lock();
 rxqs = rcu_dereference(fp->rxqs);
 if (!rxqs)
  goto unlock;

 for (i = 0; i < netdev->real_num_tx_queues; i++) {
  struct funeth_txq_stats txs;

  FUN_QSTAT_READ(fp->txqs[i], start, txs);
  stats->tx_packets += txs.tx_pkts;
  stats->tx_bytes   += txs.tx_bytes;
  stats->tx_dropped += txs.tx_map_err;
 }

 for (i = 0; i < netdev->real_num_rx_queues; i++) {
  struct funeth_rxq_stats rxs;

  FUN_QSTAT_READ(rxqs[i], start, rxs);
  stats->rx_packets += rxs.rx_pkts;
  stats->rx_bytes   += rxs.rx_bytes;
  stats->rx_dropped += rxs.rx_map_err + rxs.rx_mem_drops;
 }

 xdpqs = rcu_dereference(fp->xdpqs);
 if (!xdpqs)
  goto unlock;

 for (i = 0; i < fp->num_xdpqs; i++) {
  struct funeth_txq_stats txs;

  FUN_QSTAT_READ(xdpqs[i], start, txs);
  stats->tx_packets += txs.tx_pkts;
  stats->tx_bytes   += txs.tx_bytes;
 }
unlock:
 rcu_read_unlock();
}

static int fun_change_mtu(struct net_device *netdev, int new_mtu)
{
 struct funeth_priv *fp = netdev_priv(netdev);
 int rc;

 rc = fun_port_write_cmd(fp, FUN_ADMIN_PORT_KEY_MTU, new_mtu);
 if (!rc)
  WRITE_ONCE(netdev->mtu, new_mtu);
 return rc;
}

static int fun_set_macaddr(struct net_device *netdev, void *addr)
{
 struct funeth_priv *fp = netdev_priv(netdev);
 struct sockaddr *saddr = addr;
 int rc;

 if (!is_valid_ether_addr(saddr->sa_data))
  return -EADDRNOTAVAIL;

 if (ether_addr_equal(netdev->dev_addr, saddr->sa_data))
  return 0;

 rc = fun_port_write_cmd(fp, FUN_ADMIN_PORT_KEY_MACADDR,
    ether_addr_to_u64(saddr->sa_data));
 if (!rc)
  eth_hw_addr_set(netdev, saddr->sa_data);
 return rc;
}

static int fun_get_port_attributes(struct net_device *netdev)
{
 static const int keys[] = {
  FUN_ADMIN_PORT_KEY_MACADDR, FUN_ADMIN_PORT_KEY_CAPABILITIES,
  FUN_ADMIN_PORT_KEY_ADVERT, FUN_ADMIN_PORT_KEY_MTU
 };
 static const int phys_keys[] = {
  FUN_ADMIN_PORT_KEY_LANE_ATTRS,
 };

 struct funeth_priv *fp = netdev_priv(netdev);
 u64 data[ARRAY_SIZE(keys)];
 u8 mac[ETH_ALEN];
 int i, rc;

 rc = fun_port_read_cmds(fp, ARRAY_SIZE(keys), keys, data);
 if (rc)
  return rc;

 for (i = 0; i < ARRAY_SIZE(keys); i++) {
  switch (keys[i]) {
  case FUN_ADMIN_PORT_KEY_MACADDR:
   u64_to_ether_addr(data[i], mac);
   if (is_zero_ether_addr(mac)) {
    eth_hw_addr_random(netdev);
   } else if (is_valid_ether_addr(mac)) {
    eth_hw_addr_set(netdev, mac);
   } else {
    netdev_err(netdev,
        "device provided a bad MAC address %pM\n",
        mac);
    return -EINVAL;
   }
   break;

  case FUN_ADMIN_PORT_KEY_CAPABILITIES:
   fp->port_caps = data[i];
   break;

  case FUN_ADMIN_PORT_KEY_ADVERT:
   fp->advertising = data[i];
   break;

  case FUN_ADMIN_PORT_KEY_MTU:
   netdev->mtu = data[i];
   break;
  }
 }

 if (!(fp->port_caps & FUN_PORT_CAP_VPORT)) {
  rc = fun_port_read_cmds(fp, ARRAY_SIZE(phys_keys), phys_keys,
     data);
  if (rc)
   return rc;

  fp->lane_attrs = data[0];
 }

 if (netdev->addr_assign_type == NET_ADDR_RANDOM)
  return fun_port_write_cmd(fp, FUN_ADMIN_PORT_KEY_MACADDR,
       ether_addr_to_u64(netdev->dev_addr));
 return 0;
}

static int fun_hwtstamp_get(struct net_device *dev, struct ifreq *ifr)
{
 const struct funeth_priv *fp = netdev_priv(dev);

 return copy_to_user(ifr->ifr_data, &fp->hwtstamp_cfg,
       sizeof(fp->hwtstamp_cfg)) ? -EFAULT : 0;
}

static int fun_hwtstamp_set(struct net_device *dev, struct ifreq *ifr)
{
 struct funeth_priv *fp = netdev_priv(dev);
 struct hwtstamp_config cfg;

 if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
  return -EFAULT;

 /* no TX HW timestamps */
 cfg.tx_type = HWTSTAMP_TX_OFF;

 switch (cfg.rx_filter) {
 case HWTSTAMP_FILTER_NONE:
  break;
 case HWTSTAMP_FILTER_ALL:
 case HWTSTAMP_FILTER_SOME:
 case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
 case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
 case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
 case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
 case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
 case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
 case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
 case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
 case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
 case HWTSTAMP_FILTER_PTP_V2_EVENT:
 case HWTSTAMP_FILTER_PTP_V2_SYNC:
 case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
 case HWTSTAMP_FILTER_NTP_ALL:
  cfg.rx_filter = HWTSTAMP_FILTER_ALL;
  break;
 default:
  return -ERANGE;
 }

 fp->hwtstamp_cfg = cfg;
 return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
}

static int fun_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
 switch (cmd) {
 case SIOCSHWTSTAMP:
  return fun_hwtstamp_set(dev, ifr);
 case SIOCGHWTSTAMP:
  return fun_hwtstamp_get(dev, ifr);
 default:
  return -EOPNOTSUPP;
 }
}

/* Prepare the queues for XDP. */
static int fun_enter_xdp(struct net_device *dev, struct bpf_prog *prog)
{
 struct funeth_priv *fp = netdev_priv(dev);
 unsigned int i, nqs = num_online_cpus();
 struct funeth_txq **xdpqs;
 struct funeth_rxq **rxqs;
 int err;

 xdpqs = alloc_xdpqs(dev, nqs, fp->sq_depth, 0, FUN_QSTATE_INIT_FULL);
 if (IS_ERR(xdpqs))
  return PTR_ERR(xdpqs);

 rxqs = rtnl_dereference(fp->rxqs);
 for (i = 0; i < dev->real_num_rx_queues; i++) {
  err = fun_rxq_set_bpf(rxqs[i], prog);
  if (err)
   goto out;
 }

 fp->num_xdpqs = nqs;
 rcu_assign_pointer(fp->xdpqs, xdpqs);
 return 0;
out:
 while (i--)
  fun_rxq_set_bpf(rxqs[i], NULL);

 free_xdpqs(xdpqs, nqs, 0, FUN_QSTATE_DESTROYED);
 return err;
}

/* Set the queues for non-XDP operation. */
static void fun_end_xdp(struct net_device *dev)
{
 struct funeth_priv *fp = netdev_priv(dev);
 struct funeth_txq **xdpqs;
 struct funeth_rxq **rxqs;
 unsigned int i;

 xdpqs = rtnl_dereference(fp->xdpqs);
 rcu_assign_pointer(fp->xdpqs, NULL);
 synchronize_net();
 /* at this point both Rx and Tx XDP processing has ended */

 free_xdpqs(xdpqs, fp->num_xdpqs, 0, FUN_QSTATE_DESTROYED);
 fp->num_xdpqs = 0;

 rxqs = rtnl_dereference(fp->rxqs);
 for (i = 0; i < dev->real_num_rx_queues; i++)
  fun_rxq_set_bpf(rxqs[i], NULL);
}

#define XDP_MAX_MTU \
 (PAGE_SIZE - FUN_XDP_HEADROOM - VLAN_ETH_HLEN - FUN_RX_TAILROOM)

static int fun_xdp_setup(struct net_device *dev, struct netdev_bpf *xdp)
{
 struct bpf_prog *old_prog, *prog = xdp->prog;
 struct funeth_priv *fp = netdev_priv(dev);
 int i, err;

 /* XDP uses at most one buffer */
 if (prog && dev->mtu > XDP_MAX_MTU) {
  netdev_err(dev, "device MTU %u too large for XDP\n", dev->mtu);
  NL_SET_ERR_MSG_MOD(xdp->extack,
       "Device MTU too large for XDP");
  return -EINVAL;
 }

 if (!netif_running(dev)) {
  fp->num_xdpqs = prog ? num_online_cpus() : 0;
 } else if (prog && !fp->xdp_prog) {
  err = fun_enter_xdp(dev, prog);
  if (err) {
   NL_SET_ERR_MSG_MOD(xdp->extack,
        "Failed to set queues for XDP.");
   return err;
  }
 } else if (!prog && fp->xdp_prog) {
  fun_end_xdp(dev);
 } else {
  struct funeth_rxq **rxqs = rtnl_dereference(fp->rxqs);

  for (i = 0; i < dev->real_num_rx_queues; i++)
   WRITE_ONCE(rxqs[i]->xdp_prog, prog);
 }

 if (prog)
  xdp_features_set_redirect_target(dev, true);
 else
  xdp_features_clear_redirect_target(dev);

 dev->max_mtu = prog ? XDP_MAX_MTU : FUN_MAX_MTU;
 old_prog = xchg(&fp->xdp_prog, prog);
 if (old_prog)
  bpf_prog_put(old_prog);

 return 0;
}

static int fun_xdp(struct net_device *dev, struct netdev_bpf *xdp)
{
 switch (xdp->command) {
 case XDP_SETUP_PROG:
  return fun_xdp_setup(dev, xdp);
 default:
  return -EINVAL;
 }
}

static int fun_init_vports(struct fun_ethdev *ed, unsigned int n)
{
 if (ed->num_vports)
  return -EINVAL;

 ed->vport_info = kvcalloc(n, sizeof(*ed->vport_info), GFP_KERNEL);
 if (!ed->vport_info)
  return -ENOMEM;
 ed->num_vports = n;
 return 0;
}

static void fun_free_vports(struct fun_ethdev *ed)
{
 kvfree(ed->vport_info);
 ed->vport_info = NULL;
 ed->num_vports = 0;
}

static struct fun_vport_info *fun_get_vport(struct fun_ethdev *ed,
         unsigned int vport)
{
 if (!ed->vport_info || vport >= ed->num_vports)
  return NULL;

 return ed->vport_info + vport;
}

static int fun_set_vf_mac(struct net_device *dev, int vf, u8 *mac)
{
 struct funeth_priv *fp = netdev_priv(dev);
 struct fun_adi_param mac_param = {};
 struct fun_dev *fdev = fp->fdev;
 struct fun_ethdev *ed = to_fun_ethdev(fdev);
 struct fun_vport_info *vi;
 int rc = -EINVAL;

 if (is_multicast_ether_addr(mac))
  return -EINVAL;

 mutex_lock(&ed->state_mutex);
 vi = fun_get_vport(ed, vf);
 if (!vi)
  goto unlock;

 mac_param.u.mac = FUN_ADI_MAC_INIT(ether_addr_to_u64(mac));
 rc = fun_adi_write(fdev, FUN_ADMIN_ADI_ATTR_MACADDR, vf + 1,
      &mac_param);
 if (!rc)
  ether_addr_copy(vi->mac, mac);
unlock:
 mutex_unlock(&ed->state_mutex);
 return rc;
}

static int fun_set_vf_vlan(struct net_device *dev, int vf, u16 vlan, u8 qos,
      __be16 vlan_proto)
{
 struct funeth_priv *fp = netdev_priv(dev);
 struct fun_adi_param vlan_param = {};
 struct fun_dev *fdev = fp->fdev;
 struct fun_ethdev *ed = to_fun_ethdev(fdev);
 struct fun_vport_info *vi;
 int rc = -EINVAL;

 if (vlan > 4095 || qos > 7)
  return -EINVAL;
 if (vlan_proto && vlan_proto != htons(ETH_P_8021Q) &&
     vlan_proto != htons(ETH_P_8021AD))
  return -EINVAL;

 mutex_lock(&ed->state_mutex);
 vi = fun_get_vport(ed, vf);
 if (!vi)
  goto unlock;

 vlan_param.u.vlan = FUN_ADI_VLAN_INIT(be16_to_cpu(vlan_proto),
           ((u16)qos << VLAN_PRIO_SHIFT) | vlan);
 rc = fun_adi_write(fdev, FUN_ADMIN_ADI_ATTR_VLAN, vf + 1, &vlan_param);
 if (!rc) {
  vi->vlan = vlan;
  vi->qos = qos;
  vi->vlan_proto = vlan_proto;
 }
unlock:
 mutex_unlock(&ed->state_mutex);
 return rc;
}

static int fun_set_vf_rate(struct net_device *dev, int vf, int min_tx_rate,
      int max_tx_rate)
{
 struct funeth_priv *fp = netdev_priv(dev);
 struct fun_adi_param rate_param = {};
 struct fun_dev *fdev = fp->fdev;
 struct fun_ethdev *ed = to_fun_ethdev(fdev);
 struct fun_vport_info *vi;
 int rc = -EINVAL;

 if (min_tx_rate)
  return -EINVAL;

 mutex_lock(&ed->state_mutex);
 vi = fun_get_vport(ed, vf);
 if (!vi)
  goto unlock;

 rate_param.u.rate = FUN_ADI_RATE_INIT(max_tx_rate);
 rc = fun_adi_write(fdev, FUN_ADMIN_ADI_ATTR_RATE, vf + 1, &rate_param);
 if (!rc)
  vi->max_rate = max_tx_rate;
unlock:
 mutex_unlock(&ed->state_mutex);
 return rc;
}

static int fun_get_vf_config(struct net_device *dev, int vf,
        struct ifla_vf_info *ivi)
{
 struct funeth_priv *fp = netdev_priv(dev);
 struct fun_ethdev *ed = to_fun_ethdev(fp->fdev);
 const struct fun_vport_info *vi;

 mutex_lock(&ed->state_mutex);
 vi = fun_get_vport(ed, vf);
 if (!vi)
  goto unlock;

 memset(ivi, 0, sizeof(*ivi));
 ivi->vf = vf;
 ether_addr_copy(ivi->mac, vi->mac);
 ivi->vlan = vi->vlan;
 ivi->qos = vi->qos;
 ivi->vlan_proto = vi->vlan_proto;
 ivi->max_tx_rate = vi->max_rate;
 ivi->spoofchk = vi->spoofchk;
unlock:
 mutex_unlock(&ed->state_mutex);
 return vi ? 0 : -EINVAL;
}

static void fun_uninit(struct net_device *dev)
{
 struct funeth_priv *fp = netdev_priv(dev);

 fun_prune_queue_irqs(dev);
 xa_destroy(&fp->irqs);
}

static const struct net_device_ops fun_netdev_ops = {
 .ndo_open  = funeth_open,
 .ndo_stop  = funeth_close,
 .ndo_start_xmit  = fun_start_xmit,
 .ndo_get_stats64 = fun_get_stats64,
 .ndo_change_mtu  = fun_change_mtu,
 .ndo_set_mac_address = fun_set_macaddr,
 .ndo_validate_addr = eth_validate_addr,
 .ndo_eth_ioctl  = fun_ioctl,
 .ndo_uninit  = fun_uninit,
 .ndo_bpf  = fun_xdp,
 .ndo_xdp_xmit  = fun_xdp_xmit_frames,
 .ndo_set_vf_mac  = fun_set_vf_mac,
 .ndo_set_vf_vlan = fun_set_vf_vlan,
 .ndo_set_vf_rate = fun_set_vf_rate,
 .ndo_get_vf_config = fun_get_vf_config,
};

#define GSO_ENCAP_FLAGS (NETIF_F_GSO_GRE | NETIF_F_GSO_IPXIP4 | \
    NETIF_F_GSO_IPXIP6 | NETIF_F_GSO_UDP_TUNNEL | \
    NETIF_F_GSO_UDP_TUNNEL_CSUM)
#define TSO_FLAGS (NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_TSO_ECN | \
     NETIF_F_GSO_UDP_L4)
#define VLAN_FEAT (NETIF_F_SG | NETIF_F_HW_CSUM | TSO_FLAGS | \
     GSO_ENCAP_FLAGS | NETIF_F_HIGHDMA)

static void fun_dflt_rss_indir(struct funeth_priv *fp, unsigned int nrx)
{
 unsigned int i;

 for (i = 0; i < fp->indir_table_nentries; i++)
  fp->indir_table[i] = ethtool_rxfh_indir_default(i, nrx);
}

/* Reset the RSS indirection table to equal distribution across the current
 * number of Rx queues. Called at init time and whenever the number of Rx
 * queues changes subsequently. Note that this may also resize the indirection
 * table.
 */

static void fun_reset_rss_indir(struct net_device *dev, unsigned int nrx)
{
 struct funeth_priv *fp = netdev_priv(dev);

 if (!fp->rss_cfg)
  return;

 /* Set the table size to the max possible that allows an equal number
 * of occurrences of each CQ.
 */

 fp->indir_table_nentries = rounddown(FUN_ETH_RSS_MAX_INDIR_ENT, nrx);
 fun_dflt_rss_indir(fp, nrx);
}

/* Update the RSS LUT to contain only queues in [0, nrx). Normally this will
 * update the LUT to an equal distribution among nrx queues, If @only_if_needed
 * is set the LUT is left unchanged if it already does not reference any queues
 * >= nrx.
 */

static int fun_rss_set_qnum(struct net_device *dev, unsigned int nrx,
       bool only_if_needed)
{
 struct funeth_priv *fp = netdev_priv(dev);
 u32 old_lut[FUN_ETH_RSS_MAX_INDIR_ENT];
 unsigned int i, oldsz;
 int err;

 if (!fp->rss_cfg)
  return 0;

 if (only_if_needed) {
  for (i = 0; i < fp->indir_table_nentries; i++)
   if (fp->indir_table[i] >= nrx)
    break;

  if (i >= fp->indir_table_nentries)
   return 0;
 }

 memcpy(old_lut, fp->indir_table, sizeof(old_lut));
 oldsz = fp->indir_table_nentries;
 fun_reset_rss_indir(dev, nrx);

 err = fun_config_rss(dev, fp->hash_algo, fp->rss_key,
        fp->indir_table, FUN_ADMIN_SUBOP_MODIFY);
 if (!err)
  return 0;

 memcpy(fp->indir_table, old_lut, sizeof(old_lut));
 fp->indir_table_nentries = oldsz;
 return err;
}

/* Allocate the DMA area for the RSS configuration commands to the device, and
 * initialize the hash, hash key, indirection table size and its entries to
 * their defaults. The indirection table defaults to equal distribution across
 * the Rx queues.
 */

static int fun_init_rss(struct net_device *dev)
{
 struct funeth_priv *fp = netdev_priv(dev);
 size_t size = sizeof(fp->rss_key) + sizeof(fp->indir_table);

 fp->rss_hw_id = FUN_HCI_ID_INVALID;
 if (!(fp->port_caps & FUN_PORT_CAP_OFFLOADS))
  return 0;

 fp->rss_cfg = dma_alloc_coherent(&fp->pdev->dev, size,
      &fp->rss_dma_addr, GFP_KERNEL);
 if (!fp->rss_cfg)
  return -ENOMEM;

 fp->hash_algo = FUN_ETH_RSS_ALG_TOEPLITZ;
 netdev_rss_key_fill(fp->rss_key, sizeof(fp->rss_key));
 fun_reset_rss_indir(dev, dev->real_num_rx_queues);
 return 0;
}

static void fun_free_rss(struct funeth_priv *fp)
{
 if (fp->rss_cfg) {
  dma_free_coherent(&fp->pdev->dev,
      sizeof(fp->rss_key) + sizeof(fp->indir_table),
      fp->rss_cfg, fp->rss_dma_addr);
  fp->rss_cfg = NULL;
 }
}

void fun_set_ring_count(struct net_device *netdev, unsigned int ntx,
   unsigned int nrx)
{
 netif_set_real_num_tx_queues(netdev, ntx);
 if (nrx != netdev->real_num_rx_queues) {
  netif_set_real_num_rx_queues(netdev, nrx);
  fun_reset_rss_indir(netdev, nrx);
 }
}

static int fun_init_stats_area(struct funeth_priv *fp)
{
 unsigned int nstats;

 if (!(fp->port_caps & FUN_PORT_CAP_STATS))
  return 0;

 nstats = PORT_MAC_RX_STATS_MAX + PORT_MAC_TX_STATS_MAX +
   PORT_MAC_FEC_STATS_MAX;

 fp->stats = dma_alloc_coherent(&fp->pdev->dev, nstats * sizeof(u64),
           &fp->stats_dma_addr, GFP_KERNEL);
 if (!fp->stats)
  return -ENOMEM;
 return 0;
}

static void fun_free_stats_area(struct funeth_priv *fp)
{
 unsigned int nstats;

 if (fp->stats) {
  nstats = PORT_MAC_RX_STATS_MAX + PORT_MAC_TX_STATS_MAX;
  dma_free_coherent(&fp->pdev->dev, nstats * sizeof(u64),
      fp->stats, fp->stats_dma_addr);
  fp->stats = NULL;
 }
}

static int fun_dl_port_register(struct net_device *netdev)
{
 struct funeth_priv *fp = netdev_priv(netdev);
 struct devlink *dl = priv_to_devlink(fp->fdev);
 struct devlink_port_attrs attrs = {};
 unsigned int idx;

 if (fp->port_caps & FUN_PORT_CAP_VPORT) {
  attrs.flavour = DEVLINK_PORT_FLAVOUR_VIRTUAL;
  idx = fp->lport;
 } else {
  idx = netdev->dev_port;
  attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
  attrs.lanes = fp->lane_attrs & 7;
  if (fp->lane_attrs & FUN_PORT_LANE_SPLIT) {
   attrs.split = 1;
   attrs.phys.port_number = fp->lport & ~3;
   attrs.phys.split_subport_number = fp->lport & 3;
  } else {
   attrs.phys.port_number = fp->lport;
  }
 }

 devlink_port_attrs_set(&fp->dl_port, &attrs);

 return devlink_port_register(dl, &fp->dl_port, idx);
}

/* Determine the max Tx/Rx queues for a port. */
static int fun_max_qs(struct fun_ethdev *ed, unsigned int *ntx,
        unsigned int *nrx)
{
 int neth;

 if (ed->num_ports > 1 || is_kdump_kernel()) {
  *ntx = 1;
  *nrx = 1;
  return 0;
 }

 neth = fun_get_res_count(&ed->fdev, FUN_ADMIN_OP_ETH);
 if (neth < 0)
  return neth;

 /* We determine the max number of queues based on the CPU
 * cores, device interrupts and queues, RSS size, and device Tx flows.
 *
 * - At least 1 Rx and 1 Tx queues.
 * - At most 1 Rx/Tx queue per core.
 * - Each Rx/Tx queue needs 1 SQ.
 */

 *ntx = min(ed->nsqs_per_port - 1, num_online_cpus());
 *nrx = *ntx;
 if (*ntx > neth)
  *ntx = neth;
 if (*nrx > FUN_ETH_RSS_MAX_INDIR_ENT)
  *nrx = FUN_ETH_RSS_MAX_INDIR_ENT;
 return 0;
}

static void fun_queue_defaults(struct net_device *dev, unsigned int nsqs)
{
 unsigned int ntx, nrx;

 ntx = min(dev->num_tx_queues, FUN_DFLT_QUEUES);
 nrx = min(dev->num_rx_queues, FUN_DFLT_QUEUES);
 if (ntx <= nrx) {
  ntx = min(ntx, nsqs / 2);
  nrx = min(nrx, nsqs - ntx);
 } else {
  nrx = min(nrx, nsqs / 2);
  ntx = min(ntx, nsqs - nrx);
 }

 netif_set_real_num_tx_queues(dev, ntx);
 netif_set_real_num_rx_queues(dev, nrx);
}

/* Replace the existing Rx/Tx/XDP queues with equal number of queues with
 * different settings, e.g. depth. This is a disruptive replacement that
 * temporarily shuts down the data path and should be limited to changes that
 * can't be applied to live queues. The old queues are always discarded.
 */

int fun_replace_queues(struct net_device *dev, struct fun_qset *newqs,
         struct netlink_ext_ack *extack)
{
 struct fun_qset oldqs = { .state = FUN_QSTATE_DESTROYED };
 struct funeth_priv *fp = netdev_priv(dev);
 int err;

 newqs->nrxqs = dev->real_num_rx_queues;
 newqs->ntxqs = dev->real_num_tx_queues;
 newqs->nxdpqs = fp->num_xdpqs;
 newqs->state = FUN_QSTATE_INIT_SW;
 err = fun_alloc_rings(dev, newqs);
 if (err) {
  NL_SET_ERR_MSG_MOD(extack,
       "Unable to allocate memory for new queues, keeping current settings");
  return err;
 }

 fun_down(dev, &oldqs);

 err = fun_up(dev, newqs);
 if (!err)
  return 0;

 /* The new queues couldn't be installed. We do not retry the old queues
 * as they are the same to the device as the new queues and would
 * similarly fail.
 */

 newqs->state = FUN_QSTATE_DESTROYED;
 fun_free_rings(dev, newqs);
 NL_SET_ERR_MSG_MOD(extack, "Unable to restore the data path with the new queues.");
 return err;
}

/* Change the number of Rx/Tx queues of a device while it is up. This is done
 * by incrementally adding/removing queues to meet the new requirements while
 * handling ongoing traffic.
 */

int fun_change_num_queues(struct net_device *dev, unsigned int ntx,
     unsigned int nrx)
{
 unsigned int keep_tx = min(dev->real_num_tx_queues, ntx);
 unsigned int keep_rx = min(dev->real_num_rx_queues, nrx);
 struct funeth_priv *fp = netdev_priv(dev);
 struct fun_qset oldqs = {
  .rxqs = rtnl_dereference(fp->rxqs),
  .txqs = fp->txqs,
  .nrxqs = dev->real_num_rx_queues,
  .ntxqs = dev->real_num_tx_queues,
  .rxq_start = keep_rx,
  .txq_start = keep_tx,
  .state = FUN_QSTATE_DESTROYED
 };
 struct fun_qset newqs = {
  .nrxqs = nrx,
  .ntxqs = ntx,
  .rxq_start = keep_rx,
  .txq_start = keep_tx,
  .cq_depth = fp->cq_depth,
  .rq_depth = fp->rq_depth,
  .sq_depth = fp->sq_depth,
  .state = FUN_QSTATE_INIT_FULL
 };
 int i, err;

 err = fun_alloc_rings(dev, &newqs);
 if (err)
  goto free_irqs;

 err = fun_enable_irqs(dev); /* of any newly added queues */
 if (err)
  goto free_rings;

 /* copy the queues we are keeping to the new set */
 memcpy(newqs.rxqs, oldqs.rxqs, keep_rx * sizeof(*oldqs.rxqs));
 memcpy(newqs.txqs, fp->txqs, keep_tx * sizeof(*fp->txqs));

 if (nrx < dev->real_num_rx_queues) {
  err = fun_rss_set_qnum(dev, nrx, true);
  if (err)
   goto disable_tx_irqs;

  for (i = nrx; i < dev->real_num_rx_queues; i++)
   fun_disable_one_irq(container_of(oldqs.rxqs[i]->napi,
        struct fun_irq, napi));

  netif_set_real_num_rx_queues(dev, nrx);
 }

 if (ntx < dev->real_num_tx_queues)
  netif_set_real_num_tx_queues(dev, ntx);

 rcu_assign_pointer(fp->rxqs, newqs.rxqs);
 fp->txqs = newqs.txqs;
 synchronize_net();

 if (ntx > dev->real_num_tx_queues)
  netif_set_real_num_tx_queues(dev, ntx);

 if (nrx > dev->real_num_rx_queues) {
  netif_set_real_num_rx_queues(dev, nrx);
  fun_rss_set_qnum(dev, nrx, false);
 }

 /* disable interrupts of any excess Tx queues */
 for (i = keep_tx; i < oldqs.ntxqs; i++)
  fun_disable_one_irq(oldqs.txqs[i]->irq);

 fun_free_rings(dev, &oldqs);
 fun_prune_queue_irqs(dev);
 return 0;

disable_tx_irqs:
 for (i = oldqs.ntxqs; i < ntx; i++)
  fun_disable_one_irq(newqs.txqs[i]->irq);
free_rings:
 newqs.state = FUN_QSTATE_DESTROYED;
 fun_free_rings(dev, &newqs);
free_irqs:
 fun_prune_queue_irqs(dev);
 return err;
}

static int fun_create_netdev(struct fun_ethdev *ed, unsigned int portid)
{
 struct fun_dev *fdev = &ed->fdev;
 struct net_device *netdev;
 struct funeth_priv *fp;
 unsigned int ntx, nrx;
 int rc;

 rc = fun_max_qs(ed, &ntx, &nrx);
 if (rc)
  return rc;

 netdev = alloc_etherdev_mqs(sizeof(*fp), ntx, nrx);
 if (!netdev) {
  rc = -ENOMEM;
  goto done;
 }

 netdev->dev_port = portid;
 fun_queue_defaults(netdev, ed->nsqs_per_port);

 fp = netdev_priv(netdev);
 fp->fdev = fdev;
 fp->pdev = to_pci_dev(fdev->dev);
 fp->netdev = netdev;
 xa_init(&fp->irqs);
 fp->rx_irq_ofst = ntx;
 seqcount_init(&fp->link_seq);

 fp->lport = INVALID_LPORT;
 rc = fun_port_create(netdev);
 if (rc)
  goto free_netdev;

 /* bind port to admin CQ for async events */
 rc = fun_bind(fdev, FUN_ADMIN_BIND_TYPE_PORT, portid,
        FUN_ADMIN_BIND_TYPE_EPCQ, 0);
 if (rc)
  goto destroy_port;

 rc = fun_get_port_attributes(netdev);
 if (rc)
  goto destroy_port;

 rc = fun_init_rss(netdev);
 if (rc)
  goto destroy_port;

 rc = fun_init_stats_area(fp);
 if (rc)
  goto free_rss;

 SET_NETDEV_DEV(netdev, fdev->dev);
 SET_NETDEV_DEVLINK_PORT(netdev, &fp->dl_port);
 netdev->netdev_ops = &fun_netdev_ops;

 netdev->hw_features = NETIF_F_SG | NETIF_F_RXHASH | NETIF_F_RXCSUM;
 if (fp->port_caps & FUN_PORT_CAP_OFFLOADS)
  netdev->hw_features |= NETIF_F_HW_CSUM | TSO_FLAGS;
 if (fp->port_caps & FUN_PORT_CAP_ENCAP_OFFLOADS)
  netdev->hw_features |= GSO_ENCAP_FLAGS;

 netdev->features |= netdev->hw_features | NETIF_F_HIGHDMA;
 netdev->vlan_features = netdev->features & VLAN_FEAT;
 netdev->mpls_features = netdev->vlan_features;
 netdev->hw_enc_features = netdev->hw_features;
 netdev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT;

 netdev->min_mtu = ETH_MIN_MTU;
 netdev->max_mtu = FUN_MAX_MTU;

 fun_set_ethtool_ops(netdev);

 /* configurable parameters */
 fp->sq_depth = min(SQ_DEPTH, fdev->q_depth);
 fp->cq_depth = min(CQ_DEPTH, fdev->q_depth);
 fp->rq_depth = min_t(unsigned int, RQ_DEPTH, fdev->q_depth);
 fp->rx_coal_usec  = CQ_INTCOAL_USEC;
 fp->rx_coal_count = CQ_INTCOAL_NPKT;
 fp->tx_coal_usec  = SQ_INTCOAL_USEC;
 fp->tx_coal_count = SQ_INTCOAL_NPKT;
 fp->cq_irq_db = FUN_IRQ_CQ_DB(fp->rx_coal_usec, fp->rx_coal_count);

 rc = fun_dl_port_register(netdev);
 if (rc)
  goto free_stats;

 fp->ktls_id = FUN_HCI_ID_INVALID;
 fun_ktls_init(netdev);            /* optional, failure OK */

 netif_carrier_off(netdev);
 ed->netdevs[portid] = netdev;
 rc = register_netdev(netdev);
 if (rc)
  goto unreg_devlink;
 return 0;

unreg_devlink:
 ed->netdevs[portid] = NULL;
 fun_ktls_cleanup(fp);
 devlink_port_unregister(&fp->dl_port);
free_stats:
 fun_free_stats_area(fp);
free_rss:
 fun_free_rss(fp);
destroy_port:
 fun_port_destroy(netdev);
free_netdev:
 free_netdev(netdev);
done:
 dev_err(fdev->dev, "couldn't allocate port %u, error %d", portid, rc);
 return rc;
}

static void fun_destroy_netdev(struct net_device *netdev)
{
 struct funeth_priv *fp;

 fp = netdev_priv(netdev);
 unregister_netdev(netdev);
 devlink_port_unregister(&fp->dl_port);
 fun_ktls_cleanup(fp);
 fun_free_stats_area(fp);
 fun_free_rss(fp);
 fun_port_destroy(netdev);
 free_netdev(netdev);
}

static int fun_create_ports(struct fun_ethdev *ed, unsigned int nports)
{
 struct fun_dev *fd = &ed->fdev;
 int i, rc;

 /* The admin queue takes 1 IRQ and 2 SQs. */
 ed->nsqs_per_port = min(fd->num_irqs - 1,
    fd->kern_end_qid - 2) / nports;
 if (ed->nsqs_per_port < 2) {
  dev_err(fd->dev, "Too few SQs for %u ports", nports);
  return -EINVAL;
 }

 ed->netdevs = kcalloc(nports, sizeof(*ed->netdevs), GFP_KERNEL);
 if (!ed->netdevs)
  return -ENOMEM;

 ed->num_ports = nports;
 for (i = 0; i < nports; i++) {
  rc = fun_create_netdev(ed, i);
  if (rc)
   goto free_netdevs;
 }

 return 0;

free_netdevs:
 while (i)
  fun_destroy_netdev(ed->netdevs[--i]);
 kfree(ed->netdevs);
 ed->netdevs = NULL;
 ed->num_ports = 0;
 return rc;
}

static void fun_destroy_ports(struct fun_ethdev *ed)
{
 unsigned int i;

 for (i = 0; i < ed->num_ports; i++)
  fun_destroy_netdev(ed->netdevs[i]);

 kfree(ed->netdevs);
 ed->netdevs = NULL;
 ed->num_ports = 0;
}

static void fun_update_link_state(const struct fun_ethdev *ed,
      const struct fun_admin_port_notif *notif)
{
 unsigned int port_idx = be16_to_cpu(notif->id);
 struct net_device *netdev;
 struct funeth_priv *fp;

 if (port_idx >= ed->num_ports)
  return;

 netdev = ed->netdevs[port_idx];
 fp = netdev_priv(netdev);

 write_seqcount_begin(&fp->link_seq);
 fp->link_speed = be32_to_cpu(notif->speed) * 10;  /* 10 Mbps->Mbps */
 fp->active_fc = notif->flow_ctrl;
 fp->active_fec = notif->fec;
 fp->xcvr_type = notif->xcvr_type;
 fp->link_down_reason = notif->link_down_reason;
 fp->lp_advertising = be64_to_cpu(notif->lp_advertising);

 if ((notif->link_state | notif->missed_events) & FUN_PORT_FLAG_MAC_DOWN)
  netif_carrier_off(netdev);
 if (notif->link_state & FUN_PORT_FLAG_MAC_UP)
  netif_carrier_on(netdev);

 write_seqcount_end(&fp->link_seq);
 fun_report_link(netdev);
}

/* handler for async events delivered through the admin CQ */
static void fun_event_cb(struct fun_dev *fdev, void *entry)
{
 u8 op = ((struct fun_admin_rsp_common *)entry)->op;

 if (op == FUN_ADMIN_OP_PORT) {
  const struct fun_admin_port_notif *rsp = entry;

  if (rsp->subop == FUN_ADMIN_SUBOP_NOTIFY) {
   fun_update_link_state(to_fun_ethdev(fdev), rsp);
  } else if (rsp->subop == FUN_ADMIN_SUBOP_RES_COUNT) {
   const struct fun_admin_res_count_rsp *r = entry;

   if (r->count.data)
    set_bit(FUN_SERV_RES_CHANGE, &fdev->service_flags);
   else
    set_bit(FUN_SERV_DEL_PORTS, &fdev->service_flags);
   fun_serv_sched(fdev);
  } else {
   dev_info(fdev->dev, "adminq event unexpected op %u subop %u",
     op, rsp->subop);
  }
 } else {
  dev_info(fdev->dev, "adminq event unexpected op %u", op);
 }
}

/* handler for pending work managed by the service task */
static void fun_service_cb(struct fun_dev *fdev)
{
 struct fun_ethdev *ed = to_fun_ethdev(fdev);
 int rc;

 if (test_and_clear_bit(FUN_SERV_DEL_PORTS, &fdev->service_flags))
  fun_destroy_ports(ed);

 if (!test_and_clear_bit(FUN_SERV_RES_CHANGE, &fdev->service_flags))
  return;

 rc = fun_get_res_count(fdev, FUN_ADMIN_OP_PORT);
 if (rc < 0 || rc == ed->num_ports)
  return;

 if (ed->num_ports)
  fun_destroy_ports(ed);
 if (rc)
  fun_create_ports(ed, rc);
}

static int funeth_sriov_configure(struct pci_dev *pdev, int nvfs)
{
 struct fun_dev *fdev = pci_get_drvdata(pdev);
 struct fun_ethdev *ed = to_fun_ethdev(fdev);
 int rc;

 if (nvfs == 0) {
  if (pci_vfs_assigned(pdev)) {
   dev_warn(&pdev->dev,
     "Cannot disable SR-IOV while VFs are assigned\n");
   return -EPERM;
  }

  mutex_lock(&ed->state_mutex);
  fun_free_vports(ed);
  mutex_unlock(&ed->state_mutex);
  pci_disable_sriov(pdev);
  return 0;
 }

 rc = pci_enable_sriov(pdev, nvfs);
 if (rc)
  return rc;

 mutex_lock(&ed->state_mutex);
 rc = fun_init_vports(ed, nvfs);
 mutex_unlock(&ed->state_mutex);
 if (rc) {
  pci_disable_sriov(pdev);
  return rc;
 }

 return nvfs;
}

static int funeth_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
 struct fun_dev_params aqreq = {
  .cqe_size_log2 = ilog2(ADMIN_CQE_SIZE),
  .sqe_size_log2 = ilog2(ADMIN_SQE_SIZE),
  .cq_depth      = ADMIN_CQ_DEPTH,
  .sq_depth      = ADMIN_SQ_DEPTH,
  .rq_depth      = ADMIN_RQ_DEPTH,
  .min_msix      = 2,              /* 1 Rx + 1 Tx */
  .event_cb      = fun_event_cb,
  .serv_cb       = fun_service_cb,
 };
 struct devlink *devlink;
 struct fun_ethdev *ed;
 struct fun_dev *fdev;
 int rc;

 devlink = fun_devlink_alloc(&pdev->dev);
 if (!devlink) {
  dev_err(&pdev->dev, "devlink alloc failed\n");
  return -ENOMEM;
 }

 ed = devlink_priv(devlink);
 mutex_init(&ed->state_mutex);

 fdev = &ed->fdev;
 rc = fun_dev_enable(fdev, pdev, &aqreq, KBUILD_MODNAME);
 if (rc)
  goto free_devlink;

 rc = fun_get_res_count(fdev, FUN_ADMIN_OP_PORT);
 if (rc > 0)
  rc = fun_create_ports(ed, rc);
 if (rc < 0)
  goto disable_dev;

 fun_serv_restart(fdev);
 fun_devlink_register(devlink);
 return 0;

disable_dev:
 fun_dev_disable(fdev);
free_devlink:
 mutex_destroy(&ed->state_mutex);
 fun_devlink_free(devlink);
 return rc;
}

static void funeth_remove(struct pci_dev *pdev)
{
 struct fun_dev *fdev = pci_get_drvdata(pdev);
 struct devlink *devlink;
 struct fun_ethdev *ed;

 ed = to_fun_ethdev(fdev);
 devlink = priv_to_devlink(ed);
 fun_devlink_unregister(devlink);

#ifdef CONFIG_PCI_IOV
 funeth_sriov_configure(pdev, 0);
#endif

 fun_serv_stop(fdev);
 fun_destroy_ports(ed);
 fun_dev_disable(fdev);
 mutex_destroy(&ed->state_mutex);

 fun_devlink_free(devlink);
}

static struct pci_driver funeth_driver = {
 .name   = KBUILD_MODNAME,
 .id_table  = funeth_id_table,
 .probe   = funeth_probe,
 .remove   = funeth_remove,
 .shutdown  = funeth_remove,
 .sriov_configure = funeth_sriov_configure,
};

module_pci_driver(funeth_driver);

MODULE_AUTHOR("Dimitris Michailidis ");
MODULE_DESCRIPTION("Fungible Ethernet Network Driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DEVICE_TABLE(pci, funeth_id_table);

Messung V0.5
C=98 H=88 G=93

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.