TSN features on the ENETC (taprio, cbs, gate, police) are configured
through a mix of command BD ring messages and port registers:
enetc_port_rd(), enetc_port_wr().
Port registers are a region of the ENETC memory map which are only
accessible from the PCIe Physical Function. They are not accessible from
the Virtual Functions.
Moreover, attempting to access these registers crashes the kernel:
$ echo 1 > /sys/bus/pci/devices/0000\:00\:00.0/sriov_numvfs
pci 0000:00:01.0: [1957:ef00] type 00 class 0x020001
fsl_enetc_vf 0000:00:01.0: Adding to iommu group 15
fsl_enetc_vf 0000:00:01.0: enabling device (0000 -> 0002)
fsl_enetc_vf 0000:00:01.0 eno0vf0: renamed from eth0
$ tc qdisc replace dev eno0vf0 root taprio num_tc 8 map 0 1 2 3 4 5 6 7 \
queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 base-time 0 \
sched-entry S 0x7f 900000 sched-entry S 0x80 100000 flags 0x2
Unable to handle kernel paging request at virtual address ffff800009551a08
Internal error: Oops: 96000007 [#1] PREEMPT SMP
pc : enetc_setup_tc_taprio+0x170/0x47c
lr : enetc_setup_tc_taprio+0x16c/0x47c
Call trace:
enetc_setup_tc_taprio+0x170/0x47c
enetc_setup_tc+0x38/0x2dc
taprio_change+0x43c/0x970
taprio_init+0x188/0x1e0
qdisc_create+0x114/0x470
tc_modify_qdisc+0x1fc/0x6c0
rtnetlink_rcv_msg+0x12c/0x390
Split enetc_setup_tc() into separate functions for the PF and for the
VF drivers. Also remove enetc_qos.o from being included into
enetc-vf.ko, since it serves absolutely no purpose there.
Fixes: 34c6adf197 ("enetc: Configure the Time-Aware Scheduler via tc-taprio offload")
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://lore.kernel.org/r/20220916133209.3351399-2-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
264 lines
6.1 KiB
C
264 lines
6.1 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
|
|
/* Copyright 2017-2019 NXP */
|
|
|
|
#include <linux/module.h>
|
|
#include "enetc.h"
|
|
|
|
#define ENETC_DRV_NAME_STR "ENETC VF driver"
|
|
|
|
/* Messaging */
|
|
static void enetc_msg_vsi_write_msg(struct enetc_hw *hw,
|
|
struct enetc_msg_swbd *msg)
|
|
{
|
|
u32 val;
|
|
|
|
val = enetc_vsi_set_msize(msg->size) | lower_32_bits(msg->dma);
|
|
enetc_wr(hw, ENETC_VSIMSGSNDAR1, upper_32_bits(msg->dma));
|
|
enetc_wr(hw, ENETC_VSIMSGSNDAR0, val);
|
|
}
|
|
|
|
static int enetc_msg_vsi_send(struct enetc_si *si, struct enetc_msg_swbd *msg)
|
|
{
|
|
int timeout = 100;
|
|
u32 vsimsgsr;
|
|
|
|
enetc_msg_vsi_write_msg(&si->hw, msg);
|
|
|
|
do {
|
|
vsimsgsr = enetc_rd(&si->hw, ENETC_VSIMSGSR);
|
|
if (!(vsimsgsr & ENETC_VSIMSGSR_MB))
|
|
break;
|
|
|
|
usleep_range(1000, 2000);
|
|
} while (--timeout);
|
|
|
|
if (!timeout)
|
|
return -ETIMEDOUT;
|
|
|
|
/* check for message delivery error */
|
|
if (vsimsgsr & ENETC_VSIMSGSR_MS) {
|
|
dev_err(&si->pdev->dev, "VSI command execute error: %d\n",
|
|
ENETC_SIMSGSR_GET_MC(vsimsgsr));
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int enetc_msg_vsi_set_primary_mac_addr(struct enetc_ndev_priv *priv,
|
|
struct sockaddr *saddr)
|
|
{
|
|
struct enetc_msg_cmd_set_primary_mac *cmd;
|
|
struct enetc_msg_swbd msg;
|
|
int err;
|
|
|
|
msg.size = ALIGN(sizeof(struct enetc_msg_cmd_set_primary_mac), 64);
|
|
msg.vaddr = dma_alloc_coherent(priv->dev, msg.size, &msg.dma,
|
|
GFP_KERNEL);
|
|
if (!msg.vaddr) {
|
|
dev_err(priv->dev, "Failed to alloc Tx msg (size: %d)\n",
|
|
msg.size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cmd = (struct enetc_msg_cmd_set_primary_mac *)msg.vaddr;
|
|
cmd->header.type = ENETC_MSG_CMD_MNG_MAC;
|
|
cmd->header.id = ENETC_MSG_CMD_MNG_ADD;
|
|
memcpy(&cmd->mac, saddr, sizeof(struct sockaddr));
|
|
|
|
/* send the command and wait */
|
|
err = enetc_msg_vsi_send(priv->si, &msg);
|
|
|
|
dma_free_coherent(priv->dev, msg.size, msg.vaddr, msg.dma);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int enetc_vf_set_mac_addr(struct net_device *ndev, void *addr)
|
|
{
|
|
struct enetc_ndev_priv *priv = netdev_priv(ndev);
|
|
struct sockaddr *saddr = addr;
|
|
|
|
if (!is_valid_ether_addr(saddr->sa_data))
|
|
return -EADDRNOTAVAIL;
|
|
|
|
return enetc_msg_vsi_set_primary_mac_addr(priv, saddr);
|
|
}
|
|
|
|
static int enetc_vf_set_features(struct net_device *ndev,
|
|
netdev_features_t features)
|
|
{
|
|
enetc_set_features(ndev, features);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int enetc_vf_setup_tc(struct net_device *ndev, enum tc_setup_type type,
|
|
void *type_data)
|
|
{
|
|
switch (type) {
|
|
case TC_SETUP_QDISC_MQPRIO:
|
|
return enetc_setup_tc_mqprio(ndev, type_data);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
/* Probing/ Init */
|
|
static const struct net_device_ops enetc_ndev_ops = {
|
|
.ndo_open = enetc_open,
|
|
.ndo_stop = enetc_close,
|
|
.ndo_start_xmit = enetc_xmit,
|
|
.ndo_get_stats = enetc_get_stats,
|
|
.ndo_set_mac_address = enetc_vf_set_mac_addr,
|
|
.ndo_set_features = enetc_vf_set_features,
|
|
.ndo_eth_ioctl = enetc_ioctl,
|
|
.ndo_setup_tc = enetc_vf_setup_tc,
|
|
};
|
|
|
|
static void enetc_vf_netdev_setup(struct enetc_si *si, struct net_device *ndev,
|
|
const struct net_device_ops *ndev_ops)
|
|
{
|
|
struct enetc_ndev_priv *priv = netdev_priv(ndev);
|
|
|
|
SET_NETDEV_DEV(ndev, &si->pdev->dev);
|
|
priv->ndev = ndev;
|
|
priv->si = si;
|
|
priv->dev = &si->pdev->dev;
|
|
si->ndev = ndev;
|
|
|
|
priv->msg_enable = (NETIF_MSG_IFUP << 1) - 1;
|
|
ndev->netdev_ops = ndev_ops;
|
|
enetc_set_ethtool_ops(ndev);
|
|
ndev->watchdog_timeo = 5 * HZ;
|
|
ndev->max_mtu = ENETC_MAX_MTU;
|
|
|
|
ndev->hw_features = NETIF_F_SG | NETIF_F_RXCSUM |
|
|
NETIF_F_HW_VLAN_CTAG_TX |
|
|
NETIF_F_HW_VLAN_CTAG_RX |
|
|
NETIF_F_HW_CSUM | NETIF_F_TSO | NETIF_F_TSO6;
|
|
ndev->features = NETIF_F_HIGHDMA | NETIF_F_SG | NETIF_F_RXCSUM |
|
|
NETIF_F_HW_VLAN_CTAG_TX |
|
|
NETIF_F_HW_VLAN_CTAG_RX |
|
|
NETIF_F_HW_CSUM | NETIF_F_TSO | NETIF_F_TSO6;
|
|
ndev->vlan_features = NETIF_F_SG | NETIF_F_HW_CSUM |
|
|
NETIF_F_TSO | NETIF_F_TSO6;
|
|
|
|
if (si->num_rss)
|
|
ndev->hw_features |= NETIF_F_RXHASH;
|
|
|
|
/* pick up primary MAC address from SI */
|
|
enetc_load_primary_mac_addr(&si->hw, ndev);
|
|
}
|
|
|
|
static int enetc_vf_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *ent)
|
|
{
|
|
struct enetc_ndev_priv *priv;
|
|
struct net_device *ndev;
|
|
struct enetc_si *si;
|
|
int err;
|
|
|
|
err = enetc_pci_probe(pdev, KBUILD_MODNAME, 0);
|
|
if (err)
|
|
return dev_err_probe(&pdev->dev, err, "PCI probing failed\n");
|
|
|
|
si = pci_get_drvdata(pdev);
|
|
|
|
enetc_get_si_caps(si);
|
|
|
|
ndev = alloc_etherdev_mq(sizeof(*priv), ENETC_MAX_NUM_TXQS);
|
|
if (!ndev) {
|
|
err = -ENOMEM;
|
|
dev_err(&pdev->dev, "netdev creation failed\n");
|
|
goto err_alloc_netdev;
|
|
}
|
|
|
|
enetc_vf_netdev_setup(si, ndev, &enetc_ndev_ops);
|
|
|
|
priv = netdev_priv(ndev);
|
|
|
|
enetc_init_si_rings_params(priv);
|
|
|
|
err = enetc_setup_cbdr(priv->dev, &si->hw, ENETC_CBDR_DEFAULT_SIZE,
|
|
&si->cbd_ring);
|
|
if (err)
|
|
goto err_setup_cbdr;
|
|
|
|
err = enetc_alloc_si_resources(priv);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "SI resource alloc failed\n");
|
|
goto err_alloc_si_res;
|
|
}
|
|
|
|
err = enetc_configure_si(priv);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "Failed to configure SI\n");
|
|
goto err_config_si;
|
|
}
|
|
|
|
err = enetc_alloc_msix(priv);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "MSIX alloc failed\n");
|
|
goto err_alloc_msix;
|
|
}
|
|
|
|
err = register_netdev(ndev);
|
|
if (err)
|
|
goto err_reg_netdev;
|
|
|
|
netif_carrier_off(ndev);
|
|
|
|
return 0;
|
|
|
|
err_reg_netdev:
|
|
enetc_free_msix(priv);
|
|
err_config_si:
|
|
err_alloc_msix:
|
|
enetc_free_si_resources(priv);
|
|
err_alloc_si_res:
|
|
enetc_teardown_cbdr(&si->cbd_ring);
|
|
err_setup_cbdr:
|
|
si->ndev = NULL;
|
|
free_netdev(ndev);
|
|
err_alloc_netdev:
|
|
enetc_pci_remove(pdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void enetc_vf_remove(struct pci_dev *pdev)
|
|
{
|
|
struct enetc_si *si = pci_get_drvdata(pdev);
|
|
struct enetc_ndev_priv *priv;
|
|
|
|
priv = netdev_priv(si->ndev);
|
|
unregister_netdev(si->ndev);
|
|
|
|
enetc_free_msix(priv);
|
|
|
|
enetc_free_si_resources(priv);
|
|
enetc_teardown_cbdr(&si->cbd_ring);
|
|
|
|
free_netdev(si->ndev);
|
|
|
|
enetc_pci_remove(pdev);
|
|
}
|
|
|
|
static const struct pci_device_id enetc_vf_id_table[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, ENETC_DEV_ID_VF) },
|
|
{ 0, } /* End of table. */
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, enetc_vf_id_table);
|
|
|
|
static struct pci_driver enetc_vf_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.id_table = enetc_vf_id_table,
|
|
.probe = enetc_vf_probe,
|
|
.remove = enetc_vf_remove,
|
|
};
|
|
module_pci_driver(enetc_vf_driver);
|
|
|
|
MODULE_DESCRIPTION(ENETC_DRV_NAME_STR);
|
|
MODULE_LICENSE("Dual BSD/GPL");
|