[PATCH/RFC net-next 2/2] nfp: add support for assigning VFs to different physical ports

Simon Horman
2023-02-06T15:40:46Z

From: Fei Qin <fei.qin@corigine.com>

Currently, all the VFs are bridged to port 0 for the device.
Assigning VFs to different ports is required to realize
traffic load balancing.

Add implement for "devlink port function set vf_count <VAL>"
command to support assigning vfs to different physical ports.

e.g.
$ devlink port show pci/0000:82:00.0/0
pci/0000:82:00.0/0: type eth netdev enp130s0np0 flavour physical
port 0 splittable true lanes 4
function:
vf_count 0

$ devlink port function set pci/0000:82:00.0/0 vf_count 3

$ devlink port show pci/0000:82:00.0/0
pci/0000:82:00.0/0: type eth netdev enp130s0np0 flavour physical
port 0 splittable true lanes 4
function:
vf_count 3

Signed-off-by: Fei Qin <fei.qin@corigine.com>
Signed-off-by: Simon Horman <simon.horman@corigine.com>
---
.../net/ethernet/netronome/nfp/nfp_devlink.c | 71 +++++++++
drivers/net/ethernet/netronome/nfp/nfp_main.c | 5 +
drivers/net/ethernet/netronome/nfp/nfp_main.h | 4 +
.../ethernet/netronome/nfp/nfp_net_sriov.c | 147 +++++++++++++++++-
.../ethernet/netronome/nfp/nfp_net_sriov.h | 6 +
5 files changed, 225 insertions(+), 8 deletions(-)

diff --git a/drivers/net/ethernet/netronome/nfp/nfp_devlink.c b/drivers/net/ethernet/netronome/nfp/nfp_devlink.c
index bf6bae557158..9a6b4d6c28ca 100644
--- a/drivers/net/ethernet/netronome/nfp/nfp_devlink.c
+++ b/drivers/net/ethernet/netronome/nfp/nfp_devlink.c
@@ -9,6 +9,7 @@
#include "nfp_app.h"
#include "nfp_main.h"
#include "nfp_port.h"
+#include "nfp_net_sriov.h"

static int
nfp_devlink_fill_eth_port(struct nfp_port *port,
@@ -310,6 +311,74 @@ nfp_devlink_flash_update(struct devlink *devlink,
return nfp_flash_update_common(devlink_priv(devlink), params->fw, extack);
}

+static int
+nfp_devlink_vf_count_get_port_id(struct devlink_port *dl_port)
+{
+ struct nfp_pf *pf = devlink_priv(dl_port->devlink);
+ struct nfp_eth_table_port *eth_port;
+ struct nfp_port *port;
+ int err;
+
+ err = nfp_net_sriov_check(pf->app, 0, NFP_NET_VF_CFG_MB_CAP_VF_ASSIGNMENT,
+ "vf_assignment", false);
+ if (err)
+ return err;
+
+ port = container_of(dl_port, struct nfp_port, dl_port);
+ eth_port = __nfp_port_get_eth_port(port);
+ if (!eth_port)
+ return -EOPNOTSUPP;
+
+ return eth_port - pf->eth_tbl->ports;
+}
+
+static int
+nfp_devlink_vf_count_get(struct devlink_port *dl_port, u16 *vf_count,
+ struct netlink_ext_ack *extack)
+{
+ int port_id = nfp_devlink_vf_count_get_port_id(dl_port);
+ struct nfp_pf *pf = devlink_priv(dl_port->devlink);
+
+ if (port_id < 0 || port_id >= NFP_VF_ASSIGNMENT_PORT_COUNT)
+ return -EOPNOTSUPP;
+
+ *vf_count = pf->num_assigned_vfs[port_id];
+
+ return 0;
+}
+
+static int
+nfp_devlink_vf_count_set(struct devlink_port *dl_port, u16 vf_count,
+ struct netlink_ext_ack *extack)
+{
+ int port_id = nfp_devlink_vf_count_get_port_id(dl_port);
+ struct nfp_pf *pf = devlink_priv(dl_port->devlink);
+ int total_num_ports = pf->eth_tbl->count;
+ int total_num_vfs = 0;
+ unsigned int i;
+
+ if (port_id < 0 || port_id >= NFP_VF_ASSIGNMENT_PORT_COUNT)
+ return -EOPNOTSUPP;
+
+ for (i = 0; i < total_num_ports; i++) {
+ if (i != port_id)
+ total_num_vfs += pf->num_assigned_vfs[i];
+ else
+ total_num_vfs += vf_count;
+
+ if (total_num_vfs > pf->limit_vfs)
+ goto exit_out_of_range;
+ }
+
+ pf->num_assigned_vfs[port_id] = vf_count;
+
+ return 0;
+
+exit_out_of_range:
+ NL_SET_ERR_MSG_MOD(extack, "Parameter out of range");
+ return -EINVAL;
+}
+
const struct devlink_ops nfp_devlink_ops = {
.port_split = nfp_devlink_port_split,
.port_unsplit = nfp_devlink_port_unsplit,
@@ -319,6 +388,8 @@ const struct devlink_ops nfp_devlink_ops = {
.eswitch_mode_set = nfp_devlink_eswitch_mode_set,
.info_get = nfp_devlink_info_get,
.flash_update = nfp_devlink_flash_update,
+ .port_fn_vf_count_get = nfp_devlink_vf_count_get,
+ .port_fn_vf_count_set = nfp_devlink_vf_count_set,
};

int nfp_devlink_port_register(struct nfp_app *app, struct nfp_port *port)
diff --git a/drivers/net/ethernet/netronome/nfp/nfp_main.c b/drivers/net/ethernet/netronome/nfp/nfp_main.c
index 71301dbd8fb5..29663d2562aa 100644
--- a/drivers/net/ethernet/netronome/nfp/nfp_main.c
+++ b/drivers/net/ethernet/netronome/nfp/nfp_main.c
@@ -29,6 +29,7 @@
#include "nfp_app.h"
#include "nfp_main.h"
#include "nfp_net.h"
+#include "nfp_net_sriov.h"

static const char nfp_driver_name[] = "nfp";

@@ -252,6 +253,10 @@ static int nfp_pcie_sriov_enable(struct pci_dev *pdev, int num_vfs)
return -EINVAL;
}

+ err = nfp_configure_assign_vf(pdev, num_vfs);
+ if (err)
+ return err;
+
err = pci_enable_sriov(pdev, num_vfs);
if (err) {
dev_warn(&pdev->dev, "Failed to enable PCI SR-IOV: %d\n", err);
diff --git a/drivers/net/ethernet/netronome/nfp/nfp_main.h b/drivers/net/ethernet/netronome/nfp/nfp_main.h
index 14a751bfe1fe..67692fcf1201 100644
--- a/drivers/net/ethernet/netronome/nfp/nfp_main.h
+++ b/drivers/net/ethernet/netronome/nfp/nfp_main.h
@@ -16,6 +16,8 @@
#include <linux/workqueue.h>
#include <net/devlink.h>

+#define NFP_VF_ASSIGNMENT_PORT_COUNT 8
+
struct dentry;
struct device;
struct pci_dev;
@@ -63,6 +65,7 @@ struct nfp_dumpspec {
* @irq_entries: Array of MSI-X entries for all vNICs
* @limit_vfs: Number of VFs supported by firmware (~0 for PCI limit)
* @num_vfs: Number of SR-IOV VFs enabled
+ * @num_assigned_vfs: Number of VFs assigned to different physical ports
* @fw_loaded: Is the firmware loaded?
* @unload_fw_on_remove:Do we need to unload firmware on driver removal?
* @ctrl_vnic: Pointer to the control vNIC if available
@@ -111,6 +114,7 @@ struct nfp_pf {

unsigned int limit_vfs;
unsigned int num_vfs;
+ u8 num_assigned_vfs[NFP_VF_ASSIGNMENT_PORT_COUNT];

bool fw_loaded;
bool unload_fw_on_remove;
diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.c b/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.c
index 6eeeb0fda91f..873c6d707c0e 100644
--- a/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.c
+++ b/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.c
@@ -13,9 +13,13 @@
#include "nfp_net_ctrl.h"
#include "nfp_net.h"
#include "nfp_net_sriov.h"
+#include "nfp_port.h"
+#include "nfpcore/nfp_nsp.h"

-static int
-nfp_net_sriov_check(struct nfp_app *app, int vf, u16 cap, const char *msg, bool warn)
+/* Capability of VF pre-configuration */
+#define NFP_NET_VF_PRE_CONFIG NFP_NET_VF_CFG_MB_CAP_VF_ASSIGNMENT
+
+int nfp_net_sriov_check(struct nfp_app *app, int vf, u16 cap, const char *msg, bool warn)
{
u16 cap_vf;

@@ -29,6 +33,9 @@ nfp_net_sriov_check(struct nfp_app *app, int vf, u16 cap, const char *msg, bool
return -EOPNOTSUPP;
}

+ if (cap & NFP_NET_VF_PRE_CONFIG)
+ return 0;
+
if (vf < 0 || vf >= app->pf->num_vfs) {
if (warn)
nfp_warn(app->pf->cpp, "invalid VF id %d\n", vf);
@@ -38,17 +45,125 @@ nfp_net_sriov_check(struct nfp_app *app, int vf, u16 cap, const char *msg, bool
return 0;
}

+/* VFs can be shown and configured through each physical port even with VF
+ * assignment enabled. FW requires configurations to be sent through the port
+ * that the VF assigned to. Driver may need to get the port id and judge if the
+ * current netdev is the one that the VF assigned to.
+ */
+static int nfp_vf_assignment_get_port_id(struct nfp_app *app, int vf)
+{
+ struct nfp_pf *pf = app->pf;
+ unsigned int i, start_vf;
+
+ for (start_vf = 0, i = 0; i < ARRAY_SIZE(pf->num_assigned_vfs); i++) {
+ if (vf >= start_vf && vf < (start_vf + pf->num_assigned_vfs[i]))
+ return i;
+ start_vf += pf->num_assigned_vfs[i];
+ }
+
+ /* If VF assignment is disabled, all the VFs are assigned to port 0 */
+ return 0;
+}
+
+static bool nfp_vf_assignment_assigned_to_cur_port(struct net_device *netdev, int vf)
+{
+ struct nfp_app *app = nfp_app_from_netdev(netdev);
+ struct nfp_eth_table_port *eth_port;
+ struct nfp_pf *pf = app->pf;
+ int assigned_port_id, err;
+ struct nfp_port *port;
+
+ /* If firmware doesn't support vf_assignment, each VF should be shown under
+ * each physical port or PF normally.
+ */
+ err = nfp_net_sriov_check(pf->app, 0, NFP_NET_VF_CFG_MB_CAP_VF_ASSIGNMENT,
+ "vf_assignment", false);
+ if (err < 0)
+ return true;
+
+ port = nfp_port_from_netdev(netdev);
+ eth_port = nfp_port_get_eth_port(port);
+ if (!eth_port)
+ return true;
+
+ assigned_port_id = nfp_vf_assignment_get_port_id(app, vf);
+ return eth_port == &pf->eth_tbl->ports[assigned_port_id];
+}
+
+int nfp_configure_assign_vf(struct pci_dev *pdev, int num_vfs)
+{
+ struct nfp_pf *pf = pci_get_drvdata(pdev);
+ int err, total_num_vfs = 0, idx = 0;
+ struct nfp_net *nn;
+ unsigned int i;
+
+ err = nfp_net_sriov_check(pf->app, 0, NFP_NET_VF_CFG_MB_CAP_VF_ASSIGNMENT,
+ "vf_assignment", false);
+ if (err)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(pf->num_assigned_vfs); i++)
+ total_num_vfs += pf->num_assigned_vfs[i];
+
+ /* When the total_num_vfs is nonzero, the VF assignment is enabled.
+ * The total number of created VFs is required to be consistent with
+ * the one set in VF assignment.
+ */
+ if (total_num_vfs && num_vfs != total_num_vfs) {
+ dev_err(&pdev->dev,
+ "Trying to create %d VFs not satisfy the configuration of VF assignment\n",
+ num_vfs);
+ return -EINVAL;
+ }
+
+ /* When VF assignment is disabled, all the VFs are allocated to port 0 */
+ list_for_each_entry(nn, &pf->vnics, vnic_list) {
+ u8 num_assigned_vfs = ((idx == 0) && !total_num_vfs) ?
+ pf->limit_vfs : pf->num_assigned_vfs[idx];
+ idx++;
+
+ writeb(num_assigned_vfs, pf->vfcfg_tbl2 + NFP_NET_VF_CFG_VF_ASSIGNMENT);
+ writew(NFP_NET_VF_CFG_MB_UPD_VF_ASSIGNMENT, pf->vfcfg_tbl2 + NFP_NET_VF_CFG_MB_UPD);
+
+ err = nfp_net_reconfig(nn, NFP_NET_CFG_UPDATE_VF);
+ if (err)
+ return err;
+
+ err = readw(pf->vfcfg_tbl2 + NFP_NET_VF_CFG_MB_RET);
+ if (err) {
+ dev_info(&pdev->dev,
+ "FW refused VF assignment update with errno: %d\n", err);
+ return -err;
+ }
+ }
+
+ return 0;
+}
+
static int
nfp_net_sriov_update(struct nfp_app *app, int vf, u16 update, const char *msg)
{
+ int ret, assigned_port_id, cnt = 0;
+ struct nfp_net *nn_iter;
struct nfp_net *nn;
- int ret;

/* Write update info to mailbox in VF config symbol */
writeb(vf, app->pf->vfcfg_tbl2 + NFP_NET_VF_CFG_MB_VF_NUM);
writew(update, app->pf->vfcfg_tbl2 + NFP_NET_VF_CFG_MB_UPD);

nn = list_first_entry(&app->pf->vnics, struct nfp_net, vnic_list);
+
+ /* If VF assignment is enabled, reconfig of VF should be set through "nn"
+ * of corresponding physical port
+ */
+ assigned_port_id = nfp_vf_assignment_get_port_id(app, vf);
+ list_for_each_entry(nn_iter, &app->pf->vnics, vnic_list) {
+ if (cnt++ == assigned_port_id) {
+ nn = nn_iter;
+ break;
+ }
+ }
+
/* Signal VF reconfiguration */
ret = nfp_net_reconfig(nn, NFP_NET_CFG_UPDATE_VF);
if (ret)
@@ -257,11 +372,18 @@ int nfp_app_set_vf_link_state(struct net_device *netdev, int vf,
"link state");
}

+/* VFs can be shown under each physical port. When the VF is
+ * not assigned to the physical port, hardcode its mac to
+ * ff:ff:ff:ff:ff:ff to distinguish. The changes of VFs'
+ * configurations can be only seen under the corresponding
+ * physical ports.
+ */
int nfp_app_get_vf_config(struct net_device *netdev, int vf,
struct ifla_vf_info *ivi)
{
struct nfp_app *app = nfp_app_from_netdev(netdev);
u32 vf_offset, mac_hi, rate;
+ bool is_assigned = true;
u32 vlan_tag;
u16 mac_lo;
u8 flags;
@@ -271,13 +393,19 @@ int nfp_app_get_vf_config(struct net_device *netdev, int vf,
if (err)
return err;

- vf_offset = NFP_NET_VF_CFG_MB_SZ + vf * NFP_NET_VF_CFG_SZ;
+ is_assigned = nfp_vf_assignment_assigned_to_cur_port(netdev, vf);

- mac_hi = readl(app->pf->vfcfg_tbl2 + vf_offset);
- mac_lo = readw(app->pf->vfcfg_tbl2 + vf_offset + NFP_NET_VF_CFG_MAC_LO);
+ vf_offset = NFP_NET_VF_CFG_MB_SZ + vf * NFP_NET_VF_CFG_SZ;

- flags = readb(app->pf->vfcfg_tbl2 + vf_offset + NFP_NET_VF_CFG_CTRL);
- vlan_tag = readl(app->pf->vfcfg_tbl2 + vf_offset + NFP_NET_VF_CFG_VLAN);
+ if (is_assigned) {
+ mac_hi = readl(app->pf->vfcfg_tbl2 + vf_offset);
+ mac_lo = readw(app->pf->vfcfg_tbl2 + vf_offset + NFP_NET_VF_CFG_MAC_LO);
+ flags = readb(app->pf->vfcfg_tbl2 + vf_offset + NFP_NET_VF_CFG_CTRL);
+ vlan_tag = readl(app->pf->vfcfg_tbl2 + vf_offset + NFP_NET_VF_CFG_VLAN);
+ } else {
+ mac_hi = 0xffffffff;
+ mac_lo = 0xffff;
+ }

memset(ivi, 0, sizeof(*ivi));
ivi->vf = vf;
@@ -285,6 +413,9 @@ int nfp_app_get_vf_config(struct net_device *netdev, int vf,
put_unaligned_be32(mac_hi, &ivi->mac[0]);
put_unaligned_be16(mac_lo, &ivi->mac[4]);

+ if (!is_assigned)
+ return 0;
+
ivi->vlan = FIELD_GET(NFP_NET_VF_CFG_VLAN_VID, vlan_tag);
ivi->qos = FIELD_GET(NFP_NET_VF_CFG_VLAN_QOS, vlan_tag);
if (!nfp_net_sriov_check(app, vf, NFP_NET_VF_CFG_MB_CAP_VLAN_PROTO, "vlan_proto", false))
diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.h b/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.h
index 2d445fa199dc..fdf429f60d34 100644
--- a/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.h
+++ b/drivers/net/ethernet/netronome/nfp/nfp_net_sriov.h
@@ -21,6 +21,7 @@
#define NFP_NET_VF_CFG_MB_CAP_TRUST (0x1 << 4)
#define NFP_NET_VF_CFG_MB_CAP_VLAN_PROTO (0x1 << 5)
#define NFP_NET_VF_CFG_MB_CAP_RATE (0x1 << 6)
+#define NFP_NET_VF_CFG_MB_CAP_VF_ASSIGNMENT (0x1 << 8)
#define NFP_NET_VF_CFG_MB_RET 0x2
#define NFP_NET_VF_CFG_MB_UPD 0x4
#define NFP_NET_VF_CFG_MB_UPD_MAC (0x1 << 0)
@@ -30,6 +31,8 @@
#define NFP_NET_VF_CFG_MB_UPD_TRUST (0x1 << 4)
#define NFP_NET_VF_CFG_MB_UPD_VLAN_PROTO (0x1 << 5)
#define NFP_NET_VF_CFG_MB_UPD_RATE (0x1 << 6)
+#define NFP_NET_VF_CFG_MB_UPD_VF_ASSIGNMENT (0x1 << 8)
+#define NFP_NET_VF_CFG_VF_ASSIGNMENT 0x6
#define NFP_NET_VF_CFG_MB_VF_NUM 0x7

/* VF config entry
@@ -67,5 +70,8 @@ int nfp_app_set_vf_link_state(struct net_device *netdev, int vf,
int link_state);
int nfp_app_get_vf_config(struct net_device *netdev, int vf,
struct ifla_vf_info *ivi);
+int nfp_configure_assign_vf(struct pci_dev *pdev, int num_vfs);
+int nfp_net_sriov_check(struct nfp_app *app, int vf, u16 cap, const char *msg,
+ bool warn);

#endif /* _NFP_NET_SRIOV_H_ */
--
2.30.2