diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 15:58:00 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 15:58:00 -1000 |
| commit | 734bef548a96e8774d6eef5dfa2026c6edc4e80b (patch) | |
| tree | ce2520b2602ce3b82f9842490d21836f9838d441 /drivers/net/ethernet/microchip | |
| parent | 864c07de0c7dcbca58b61625dfa2e461ae63b242 (diff) | |
| parent | 1a2c35c49cc7c6be860038b8442b52424a8f123c (diff) | |
| download | linux-next-stable.tar.gz linux-next-stable.zip | |
Merge tag 'net-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextstable
Pull networking updates from Paolo Abeni:
"This includes a significant number of low-priority fixes - especially
in the NIC drivers area - that we deferred from the last few RCs in
the previous cycle, which can't be easily outlined in the synopsis
below.
Core & protocols:
- Continue the work on removing rtnl_lock dependencies:
- per-netns ARP and ND neighbour tables, with the default settings
now configurable per netns (inheritance from init_net is
controlled by the new net.core.neigh_inherit_init_net sysctl)
- RCU-protected configuration and rtnl_lock-less fill_info() for
hsr, netkit, sit, vlan and vxlan devices
- per-netns device unregistration for ip tunnel and gre devices
- use the netdev instance lock instead of rtnl_lock when reading
speed and duplex via sysfs
- batch the flushing of uncached routes at device unregistration
time, relieving rtnl_lock contention on netns dismantle.
- Add hardware pacing offload support, admin-controlled via a new
rtnetlink attribute, with sch_fq integration (implemented in idpf).
- Re-segment BIG TCP GSO packets into GSO packets fitting the egress
device limits instead of plain MSS-sized segments, improving
throughput for mixed BIG TCP / non-BIG TCP paths by up to 70%.
- Generate RSS keys that spread flows over all the RX queues; the
previous random keys could leave some queues unused.
- Optimize the bridge VLAN forwarding paths: cache the port-VLAN in
fdb entries and consider only VLAN members when flooding, for ~10%
more Mpps in the fdb forwarding path.
- A few TCP/IP performance improvements aimed at efficient cache
usage and reducing contention.
- Add the TCP ROCCET congestion control, a CUBIC variant aimed at
avoiding bufferbloat in cellular networks.
- Continue the conversion of getsockopt callbacks to sockopt_t.
- Add an ethtool command to list the front-facing ports of an
interface, including SFP cages and modules, using the phy_port
representation.
- Enable BIG TCP support for bareudp tunnels.
- Remove the IPV6_ADDRFORM socket option and disallow buggy TCP state
transitions (listen() + shutdown() + connect() and connect() +
connect(AF_UNSPEC) + listen()).
- Remove most BUG_ON()s from core packet processing, replacing them
with error paths.
Wireless:
- Latency improvements, including lockless station get/dump and
beacon updates, plus RTNL reduction and locking cleanups.
- Infrastructure for non-station TX/RX with MLO.
- NAN (Wi-Fi Aware) Instant Communication support and other NAN
improvements.
- Control frame Integrity Protocol (CIP) and protected TWT support.
- Improvements for AP regulatory checks.
Netfilter:
- Disable br_netfilter in user namespaces and schedule it for removal
in 2027.
- Restrict the raw table in nft_compat to the specific family, hook
and priority.
- Prepare the flowtable for mixed IPv4-over-IPv6 tunneling (and vice
versa).
- Use GFP_KERNEL_ACCOUNT for most netfilter allocations, for better
integration with memcg.
- Allow discovering conntrack zone filtering support in ctnetlink
dumps, and fix nfnetlink_hook handling of large dumps.
Deletions:
- Move the LLC type 2 sockets out of tree. There are no known users
and the code attracts a never-ending stream of LLM-driven fixes.
Device drivers:
- New drivers:
- Meta Platforms NIC (mpnic), the second-generation NIC from Meta
- initial skeleton of the Nebula-matrix NICs driver (nbl)
- X-Powers AC200/AC300 Fast Ethernet PHYs
- Ethernet high-speed NICs:
- Intel:
- i40e: XDP metadata support for RX hash and VLAN tag
- ice: expose the E825 TSPLL state via the DPLL subsystem
- nVidia/Mellanox:
- move the data direct infrastructure from IB to core, in
preparation for netdev support.
- reduce the memory used per offloaded TC rule
- Meta (fbnic):
- support large io_uring zero-copy RX buffers
- Microsoft vNIC (mana):
- add BQL support
- Ethernet NICs consumer, and embedded:
- Airoha:
- LRO offload support
- Cadence (macb):
- context swapping for ringparam and MTU changes, avoiding
buffer loss under memory pressure
- Mucse (rnpgbe):
- TX/RX and link status support
- NXP (enetc):
- SR-IOV improvements and VF support for ENETC v4
- Qualcomm (ppe):
- IPQ5424 support
- Realtek PCI (r8169):
- phylink support
- RSS support for RTL8127
- Renesas (ravb):
- gPTP support for R-Car Gen4
- Synopsys (stmmac):
- XDP multi-buffer support on the TX side
- tc mqprio hw queue priority offload
- support for RK3568 SGMII, Allwinner H616 EMAC1, UltraRISC
DP1000 and Qualcomm sa8255p
- Ethernet switches:
- Mediatek (mt7530):
- fast age and change conduit support
- Microchip (ksz):
- support for KSZ8995XA/KS8995XA
- Microchip (lan966x):
- PCIe FDMA support, ~20x throughput increase in PCIe endpoint
- Microchip (sparx5):
- Time Aware Shaper (taprio) offload
- Motorcomm (yt921x):
- LED and SerDes PCS support
- Realtek (rtl8365mb):
- EEE support
- Ethernet PHYs:
- Broadcom:
- enable jumbo frames on BCM54xx
- Realtek:
- support for RTL8261CE_CG and RTL8261D_VM
- MDIO support for RTL838x and RTL839x
- WiFi drivers:
- Broadcom (brcmfmac):
- FT/OKC roaming offload
- Intel (iwlwifi):
- UHR sniffer support
- support for newer UEFI/BIOS tables
- MediaTek (mt76):
- mt7925 NAN improvements
- offchannel/scan improvements and MLO fixes
- Morse Micro (mm81x):
- monitor mode support
- Qualcomm (ath12k):
- IPQ5332 platform infrastructure
- Realtek (rtw88):
- RTL8723BS support
- SDIO TX performance improvements
- Realtek (rtw89):
- P2P_DEVICE support
- BT-coex updates for RTL8922D
- Bluetooth:
- move H:4 reassembly into the Bluetooth core
- add generic support for vendor packets
- MGMT: add security level changed event
- Bluetooth drivers:
- btusb: support Bulk Serialization Mode
- Intel: support Draco and Titan Lake, allow unsigned firmware
- Mediatek: MT7928 support
- Qualcomm: QCC2072 support
- Broadcom: Synaptics 4384 support"
* tag 'net-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (2126 commits)
Bluetooth: btusb: disable by default Qualcomm multi-subsystem
tipc: fix broadcast sender hang on last acker departure
tipc: fix use-after-free of broadcast reception link at node release
r8169: clear TX LPI on RTL8168h at link up after phylink conversion
tun: Require CAP_NET_ADMIN to attach to an ownerless device
net/sched/sch_cake: drop dead nla_nest_end() error check
net/sched/sch_cake: widen buffer_used to u64
net/sched/sch_cake: reduce parent backlog when clearing a tin
net/sched/sch_cake: serialize reconfiguration with the datapath
tg3: Add Datto SIRIS 5 1U Server to restart AER quirk table
net/mlx5e: Stop asserting for RTNL when closing channels
net: shrink the payload-only part of skb dataref to one bit
net: wwan: iosm: validate msg_head before indexing msg_ring
net: bgmac: rate limit RX error messages
selftests: net: veth: test XDP_TX to a peer with GRO enabled
veth: schedule the peer's NAPI after XDP_TX when only GRO is enabled
pppoe: delete session hash entries by pointer match
net: lan969x: don't disable hardirqs in fdma tx clear
net: sparx5/lan969x: do not clobber the PSFP cycle timer update period
net: lan969x: fix DSM calendar for empty taxis
...
Diffstat (limited to 'drivers/net/ethernet/microchip')
29 files changed, 2464 insertions, 138 deletions
diff --git a/drivers/net/ethernet/microchip/fdma/Makefile b/drivers/net/ethernet/microchip/fdma/Makefile index cc9a736be357..910a10b33fb9 100644 --- a/drivers/net/ethernet/microchip/fdma/Makefile +++ b/drivers/net/ethernet/microchip/fdma/Makefile @@ -5,3 +5,7 @@ obj-$(CONFIG_FDMA) += fdma.o fdma-y += fdma_api.o + +ifneq ($(CONFIG_MCHP_LAN966X_PCI),) +fdma-y += fdma_pci.o +endif diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.c b/drivers/net/ethernet/microchip/fdma/fdma_api.c index e78c3590da9e..f7a42348e932 100644 --- a/drivers/net/ethernet/microchip/fdma/fdma_api.c +++ b/drivers/net/ethernet/microchip/fdma/fdma_api.c @@ -12,10 +12,18 @@ static int __fdma_db_add(struct fdma *fdma, int dcb_idx, int db_idx, u64 status, int db_idx, u64 *dataptr)) { struct fdma_db *db = fdma_db_get(fdma, dcb_idx, db_idx); + u64 dataptr; + int err; - db->status = status; + db->status = cpu_to_le64(status); - return cb(fdma, dcb_idx, db_idx, &db->dataptr); + err = cb(fdma, dcb_idx, db_idx, &dataptr); + if (unlikely(err)) + return err; + + db->dataptr = cpu_to_le64(dataptr); + + return 0; } /* Add a DB to a DCB, using the callback set in the fdma_ops struct. */ @@ -35,6 +43,7 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status, u64 *dataptr)) { struct fdma_dcb *dcb = fdma_dcb_get(fdma, dcb_idx); + u64 nextptr; int i, err; for (i = 0; i < fdma->n_dbs; i++) { @@ -43,14 +52,16 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status, return err; } - err = dcb_cb(fdma, dcb_idx, &fdma->last_dcb->nextptr); + err = dcb_cb(fdma, dcb_idx, &nextptr); if (unlikely(err)) return err; + fdma->last_dcb->nextptr = cpu_to_le64(nextptr); + fdma->last_dcb = dcb; - dcb->nextptr = FDMA_DCB_INVALID_DATA; - dcb->info = info; + dcb->nextptr = cpu_to_le64(FDMA_DCB_INVALID_DATA); + dcb->info = cpu_to_le64(info); return 0; } @@ -127,6 +138,50 @@ void fdma_free_phys(struct fdma *fdma) } EXPORT_SYMBOL_GPL(fdma_free_phys); +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) +/* Allocate coherent DMA memory and map it in the ATU. */ +int fdma_alloc_coherent_and_map(struct device *dev, struct fdma *fdma, + struct fdma_pci_atu *atu) +{ + struct fdma_pci_atu_region *region; + int err; + + if (WARN_ON(fdma->atu_region)) + return -EBUSY; + + /* The ATU cannot express a limit finer than the region granularity, so + * the hardware widens the programmed limit to that boundary. Pad the + * allocation to match, or the outbound window would extend past the + * memory we own. + */ + fdma->size = ALIGN(fdma->size, FDMA_PCI_ATU_REGION_ALIGN); + + err = fdma_alloc_coherent(dev, fdma); + if (err) + return err; + + region = fdma_pci_atu_region_map(atu, fdma->dma, fdma->size); + if (IS_ERR(region)) { + fdma_free_coherent(dev, fdma); + return PTR_ERR(region); + } + + fdma->atu_region = region; + + return 0; +} +EXPORT_SYMBOL_GPL(fdma_alloc_coherent_and_map); + +/* Free coherent DMA memory and unmap the memory in the ATU. */ +void fdma_free_coherent_and_unmap(struct device *dev, struct fdma *fdma) +{ + fdma_pci_atu_region_unmap(fdma->atu_region); + fdma->atu_region = NULL; + fdma_free_coherent(dev, fdma); +} +EXPORT_SYMBOL_GPL(fdma_free_coherent_and_unmap); +#endif + /* Get the size of the FDMA memory */ u32 fdma_get_size(struct fdma *fdma) { diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.h b/drivers/net/ethernet/microchip/fdma/fdma_api.h index d91affe8bd98..4e4f009b77cb 100644 --- a/drivers/net/ethernet/microchip/fdma/fdma_api.h +++ b/drivers/net/ethernet/microchip/fdma/fdma_api.h @@ -7,6 +7,8 @@ #include <linux/etherdevice.h> #include <linux/types.h> +#include "fdma_pci.h" + /* This provides a common set of functions and data structures for interacting * with the Frame DMA engine on multiple Microchip switchcores. * @@ -64,13 +66,13 @@ struct fdma; struct fdma_db { - u64 dataptr; - u64 status; + __le64 dataptr; + __le64 status; }; struct fdma_dcb { - u64 nextptr; - u64 info; + __le64 nextptr; + __le64 info; struct fdma_db db[FDMA_DB_MAX]; }; @@ -109,6 +111,11 @@ struct fdma { u32 channel_id; struct fdma_ops ops; + +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) + /* PCI ATU region for this FDMA instance. */ + struct fdma_pci_atu_region *atu_region; +#endif }; /* Advance the DCB index and wrap if required. */ @@ -140,19 +147,25 @@ static inline bool fdma_dcb_is_reusable(struct fdma *fdma) /* Check if the FDMA has marked this DB as done. */ static inline bool fdma_db_is_done(struct fdma_db *db) { - return db->status & FDMA_DCB_STATUS_DONE; + return le64_to_cpu(db->status) & FDMA_DCB_STATUS_DONE; } /* Get the length of a DB. */ static inline int fdma_db_len_get(struct fdma_db *db) { - return FDMA_DCB_STATUS_BLOCKL(db->status); + return FDMA_DCB_STATUS_BLOCKL(le64_to_cpu(db->status)); +} + +/* Get the dataptr of a DB. */ +static inline u64 fdma_db_dataptr_get(struct fdma_db *db) +{ + return le64_to_cpu(db->dataptr); } /* Set the length of a DB. */ static inline void fdma_dcb_len_set(struct fdma_dcb *dcb, u32 len) { - dcb->info = FDMA_DCB_INFO_DATAL(len); + dcb->info = cpu_to_le64(FDMA_DCB_INFO_DATAL(len)); } /* Get a DB by index. */ @@ -197,8 +210,8 @@ static inline int fdma_nextptr_cb(struct fdma *fdma, int dcb_idx, u64 *nextptr) * if the dataptr addresses and DCB's are in contiguous memory and the driver * supports XDP. */ -static inline u64 fdma_dataptr_get_contiguous(struct fdma *fdma, int dcb_idx, - int db_idx) +static inline u64 fdma_dataptr_dma_addr_contiguous(struct fdma *fdma, + int dcb_idx, int db_idx) { return fdma->dma + (sizeof(struct fdma_dcb) * fdma->n_dcbs) + (dcb_idx * fdma->n_dbs + db_idx) * fdma->db_size + @@ -209,8 +222,8 @@ static inline u64 fdma_dataptr_get_contiguous(struct fdma *fdma, int dcb_idx, * applicable if the dataptr addresses and DCB's are in contiguous memory and * the driver supports XDP. */ -static inline void *fdma_dataptr_virt_get_contiguous(struct fdma *fdma, - int dcb_idx, int db_idx) +static inline void *fdma_dataptr_virt_addr_contiguous(struct fdma *fdma, + int dcb_idx, int db_idx) { return (u8 *)fdma->dcbs + (sizeof(struct fdma_dcb) * fdma->n_dcbs) + (dcb_idx * fdma->n_dbs + db_idx) * fdma->db_size + @@ -233,9 +246,16 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status, int fdma_alloc_coherent(struct device *dev, struct fdma *fdma); int fdma_alloc_phys(struct fdma *fdma); +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) +int fdma_alloc_coherent_and_map(struct device *dev, struct fdma *fdma, + struct fdma_pci_atu *atu); +#endif void fdma_free_coherent(struct device *dev, struct fdma *fdma); void fdma_free_phys(struct fdma *fdma); +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) +void fdma_free_coherent_and_unmap(struct device *dev, struct fdma *fdma); +#endif u32 fdma_get_size(struct fdma *fdma); u32 fdma_get_size_contiguous(struct fdma *fdma); diff --git a/drivers/net/ethernet/microchip/fdma/fdma_pci.c b/drivers/net/ethernet/microchip/fdma/fdma_pci.c new file mode 100644 index 000000000000..dd1dc46cbc9d --- /dev/null +++ b/drivers/net/ethernet/microchip/fdma/fdma_pci.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <linux/align.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/types.h> + +#include "fdma_pci.h" + +/* When the switch operates as a PCIe endpoint, the FDMA engine needs to + * DMA to/from host memory. The FDMA writes to addresses within the endpoint's + * internal Outbound (OB) address space, and the PCIe ATU translates these to + * DMA addresses on the PCIe bus, targeting host memory. + * + * The ATU supports up to six outbound regions. This implementation divides + * the OB address space into six equally sized chunks. + * + * +-------------+------------+------------+-----+------------+ + * | Index | Region 0 | Region 1 | ... | Region 5 | + * +-------------+------------+------------+-----+------------+ + * | Base addr | 0x10000000 | 0x12aa0000 | ... | 0x1d520000 | + * | Limit addr | 0x12a9ffff | 0x1553ffff | ... | 0x1ffbffff | + * | Target addr | host dma | host dma | ... | host dma | + * +-------------+------------+------------+-----+------------+ + * + * Base addr is the start address of the region within the OB address space. + * Limit addr is each region's own upper bound. The value actually + * programmed is set per-mapping as base_addr + mapped size - 1, and is + * usually smaller than this. + * Target addr is the host DMA address that the base addr translates to. + */ + +#define FDMA_PCI_ATU_OB_START 0x10000000 +#define FDMA_PCI_ATU_OB_END 0x1fffffff + +#define FDMA_PCI_ATU_ADDR 0x300000 +#define FDMA_PCI_ATU_IDX_SIZE 0x200 +#define FDMA_PCI_ATU_ENA_REG 0x4 +#define FDMA_PCI_ATU_ENA_BIT BIT(31) +#define FDMA_PCI_ATU_LWR_BASE_ADDR 0x8 +#define FDMA_PCI_ATU_UPP_BASE_ADDR 0xc +#define FDMA_PCI_ATU_LIMIT_ADDR 0x10 +#define FDMA_PCI_ATU_LWR_TARGET_ADDR 0x14 +#define FDMA_PCI_ATU_UPP_TARGET_ADDR 0x18 + +static u32 fdma_pci_atu_region_size(void) +{ + return round_down((FDMA_PCI_ATU_OB_END - FDMA_PCI_ATU_OB_START) / + FDMA_PCI_ATU_REGION_MAX, FDMA_PCI_ATU_REGION_ALIGN); +} + +static void __iomem *fdma_pci_atu_addr_get(void __iomem *addr, int offset, + int idx) +{ + return addr + FDMA_PCI_ATU_ADDR + FDMA_PCI_ATU_IDX_SIZE * idx + offset; +} + +static void fdma_pci_atu_region_enable(struct fdma_pci_atu_region *region) +{ + writel(FDMA_PCI_ATU_ENA_BIT, + fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG, + region->idx)); +} + +static void fdma_pci_atu_region_disable(struct fdma_pci_atu_region *region) +{ + writel(0, fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG, + region->idx)); +} + +/* Configure the address translation in the ATU. */ +static void +fdma_pci_atu_configure_translation(struct fdma_pci_atu_region *region) +{ + struct fdma_pci_atu *atu = region->atu; + int idx = region->idx; + + writel(lower_32_bits(region->base_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_LWR_BASE_ADDR, idx)); + + writel(upper_32_bits(region->base_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_UPP_BASE_ADDR, idx)); + + /* The OB address space lies entirely below 4GB, so the limit always + * fits the lower limit register and the upper one is left alone. + */ + writel(region->limit_addr, + fdma_pci_atu_addr_get(atu->addr, FDMA_PCI_ATU_LIMIT_ADDR, idx)); + + writel(lower_32_bits(region->target_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_LWR_TARGET_ADDR, idx)); + + writel(upper_32_bits(region->target_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_UPP_TARGET_ADDR, idx)); +} + +/* Find an unused ATU region. */ +static struct fdma_pci_atu_region * +fdma_pci_atu_region_get_free(struct fdma_pci_atu *atu) +{ + struct fdma_pci_atu_region *regions = atu->regions; + + for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) { + if (regions[i].in_use) + continue; + + return ®ions[i]; + } + + return ERR_PTR(-ENOSPC); +} + +/* Unmap an ATU region, clearing its translation and disabling it. */ +void fdma_pci_atu_region_unmap(struct fdma_pci_atu_region *region) +{ + if (IS_ERR_OR_NULL(region)) + return; + + mutex_lock(®ion->atu->lock); + + region->target_addr = 0; + region->in_use = false; + + fdma_pci_atu_region_disable(region); + fdma_pci_atu_configure_translation(region); + + mutex_unlock(®ion->atu->lock); +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_region_unmap); + +/* Map a host DMA address into a free outbound region. */ +struct fdma_pci_atu_region * +fdma_pci_atu_region_map(struct fdma_pci_atu *atu, u64 target_addr, int size) +{ + struct fdma_pci_atu_region *region; + + if (!atu) + return ERR_PTR(-EINVAL); + + if (size <= 0) + return ERR_PTR(-EINVAL); + + if (size > fdma_pci_atu_region_size()) + return ERR_PTR(-ERANGE); + + /* The ATU region base is only ever aligned to FDMA_PCI_ATU_REGION_ALIGN; + * require the same alignment of the host target address, since the ATU + * translates addr - target_addr + base_addr and any misalignment here + * would shift every translated address by the same amount. + */ + if (!IS_ALIGNED(target_addr, FDMA_PCI_ATU_REGION_ALIGN)) + return ERR_PTR(-EINVAL); + + mutex_lock(&atu->lock); + + region = fdma_pci_atu_region_get_free(atu); + if (IS_ERR(region)) { + mutex_unlock(&atu->lock); + return region; + } + + region->target_addr = target_addr; + region->limit_addr = region->base_addr + size - 1; + region->in_use = true; + + fdma_pci_atu_configure_translation(region); + fdma_pci_atu_region_enable(region); + + mutex_unlock(&atu->lock); + + return region; +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_region_map); + +/* Translate a host DMA address to the corresponding OB address. */ +u64 fdma_pci_atu_translate_addr(struct fdma_pci_atu_region *region, u64 addr) +{ + return region->base_addr + (addr - region->target_addr); +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_translate_addr); + +/* Initialize ATU, dividing the OB space into equally sized regions. */ +void fdma_pci_atu_init(struct fdma_pci_atu *atu, void __iomem *addr) +{ + struct fdma_pci_atu_region *regions = atu->regions; + u32 region_size = fdma_pci_atu_region_size(); + + atu->addr = addr; + mutex_init(&atu->lock); + + for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) { + regions[i].base_addr = + FDMA_PCI_ATU_OB_START + (i * region_size); + regions[i].limit_addr = 0; + regions[i].target_addr = 0; + regions[i].idx = i; + regions[i].atu = atu; + regions[i].in_use = false; + + fdma_pci_atu_region_disable(®ions[i]); + } +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_init); diff --git a/drivers/net/ethernet/microchip/fdma/fdma_pci.h b/drivers/net/ethernet/microchip/fdma/fdma_pci.h new file mode 100644 index 000000000000..010bdb4d50b0 --- /dev/null +++ b/drivers/net/ethernet/microchip/fdma/fdma_pci.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _FDMA_PCI_H_ +#define _FDMA_PCI_H_ + +#include <linux/align.h> +#include <linux/bits.h> +#include <linux/mutex.h> +#include <linux/types.h> + +#define FDMA_PCI_ATU_REGION_MAX 6 + +/* Outbound regions are 64KB granular (datasheet section 3.24.7.4.1), so both + * the region base and the mapped size must be aligned to this. + */ +#define FDMA_PCI_ATU_REGION_ALIGN BIT(16) + +#define FDMA_PCI_DB_ALIGN 128 +#define FDMA_PCI_DB_SIZE(mtu) ALIGN(mtu, FDMA_PCI_DB_ALIGN) + +struct fdma_pci_atu; + +struct fdma_pci_atu_region { + struct fdma_pci_atu *atu; + u64 base_addr; /* Base addr of the OB window */ + u64 limit_addr; /* End addr of the active mapping (base_addr + size - 1) */ + u64 target_addr; /* Host DMA address this region maps to */ + int idx; + bool in_use; +}; + +struct fdma_pci_atu { + void __iomem *addr; + struct mutex lock; /* Protects region alloc/free and ATU register access */ + struct fdma_pci_atu_region regions[FDMA_PCI_ATU_REGION_MAX]; +}; + +/* Initialize ATU, dividing OB space into regions. */ +void fdma_pci_atu_init(struct fdma_pci_atu *atu, void __iomem *addr); + +/* Unmap an ATU region, clearing its translation and disabling it. */ +void fdma_pci_atu_region_unmap(struct fdma_pci_atu_region *region); + +/* Map a host DMA address into a free ATU region. target_addr and size must be + * FDMA_PCI_ATU_REGION_ALIGN aligned; a misaligned target_addr returns -EINVAL. + */ +struct fdma_pci_atu_region *fdma_pci_atu_region_map(struct fdma_pci_atu *atu, + u64 target_addr, + int size); + +/* Translate a host DMA address to the OB address space. Reads the region + * unlocked, so the caller must quiesce DMA and the descriptor paths before + * unmapping the region. + */ +u64 fdma_pci_atu_translate_addr(struct fdma_pci_atu_region *region, u64 addr); + +#endif diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c index 633d00857774..d7e5dfc22396 100644 --- a/drivers/net/ethernet/microchip/lan865x/lan865x.c +++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c @@ -349,8 +349,8 @@ static int lan865x_probe(struct spi_device *spi) netdev->netdev_ops = &lan865x_netdev_ops; priv->tc6 = oa_tc6_init(spi, netdev, NULL); - if (!priv->tc6) { - ret = -ENODEV; + if (IS_ERR(priv->tc6)) { + ret = PTR_ERR(priv->tc6); goto free_netdev; } diff --git a/drivers/net/ethernet/microchip/lan966x/Makefile b/drivers/net/ethernet/microchip/lan966x/Makefile index 4cdbe263502c..02db4d4d9826 100644 --- a/drivers/net/ethernet/microchip/lan966x/Makefile +++ b/drivers/net/ethernet/microchip/lan966x/Makefile @@ -18,6 +18,10 @@ lan966x-switch-objs := lan966x_main.o lan966x_phylink.o lan966x_port.o \ lan966x-switch-$(CONFIG_LAN966X_DCB) += lan966x_dcb.o lan966x-switch-$(CONFIG_DEBUG_FS) += lan966x_vcap_debugfs.o +ifneq ($(CONFIG_MCHP_LAN966X_PCI),) +lan966x-switch-y += lan966x_fdma_pci.o +endif + # Provide include files ccflags-y += -I$(srctree)/drivers/net/ethernet/microchip/vcap ccflags-y += -I$(srctree)/drivers/net/ethernet/microchip/fdma diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c index 41d4ec7f2f57..a7940eca5df3 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c @@ -80,7 +80,7 @@ static int lan966x_fdma_rx_alloc_page_pool(struct lan966x_rx *rx) .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV, .pool_size = rx->fdma.n_dcbs, .nid = NUMA_NO_NODE, - .dev = lan966x->dev, + .dev = lan966x->dma_dev, .dma_dir = DMA_FROM_DEVICE, .offset = XDP_PACKET_HEADROOM, .max_len = rx->max_mtu - @@ -109,6 +109,13 @@ static int lan966x_fdma_rx_alloc_page_pool(struct lan966x_rx *rx) return 0; } +void lan966x_fdma_llp_configure(struct lan966x *lan966x, u64 addr, + u8 channel_id) +{ + lan_wr(lower_32_bits(addr), lan966x, FDMA_DCB_LLP(channel_id)); + lan_wr(upper_32_bits(addr), lan966x, FDMA_DCB_LLP1(channel_id)); +} + static int lan966x_fdma_rx_alloc(struct lan966x_rx *rx) { struct lan966x *lan966x = rx->lan966x; @@ -119,7 +126,7 @@ static int lan966x_fdma_rx_alloc(struct lan966x_rx *rx) if (err) return err; - err = fdma_alloc_coherent(lan966x->dev, fdma); + err = fdma_alloc_coherent(lan966x->dma_dev, fdma); if (err) { page_pool_destroy(rx->page_pool); return err; @@ -128,22 +135,20 @@ static int lan966x_fdma_rx_alloc(struct lan966x_rx *rx) fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size), FDMA_DCB_STATUS_INTR); + lan966x_fdma_llp_configure(lan966x, fdma->dma, fdma->channel_id); + return 0; } -static void lan966x_fdma_rx_start(struct lan966x_rx *rx) +void lan966x_fdma_rx_start(struct lan966x_rx *rx) { struct lan966x *lan966x = rx->lan966x; struct fdma *fdma = &rx->fdma; u32 mask; - /* When activating a channel, first is required to write the first DCB - * address and then to activate it - */ - lan_wr(lower_32_bits((u64)fdma->dma), lan966x, - FDMA_DCB_LLP(fdma->channel_id)); - lan_wr(upper_32_bits((u64)fdma->dma), lan966x, - FDMA_DCB_LLP1(fdma->channel_id)); + lan_wr(FDMA_INTR_ENA_INTR_PORT_ENA_SET(GENMASK(1, 0)) | + FDMA_INTR_ENA_INTR_CH_ENA_SET(GENMASK(7, 0)), + lan966x, FDMA_INTR_ENA); lan_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) | FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) | @@ -170,7 +175,7 @@ static void lan966x_fdma_rx_start(struct lan966x_rx *rx) lan966x, FDMA_CH_ACTIVATE); } -static void lan966x_fdma_rx_disable(struct lan966x_rx *rx) +void lan966x_fdma_rx_disable(struct lan966x_rx *rx) { struct lan966x *lan966x = rx->lan966x; struct fdma *fdma = &rx->fdma; @@ -190,7 +195,7 @@ static void lan966x_fdma_rx_disable(struct lan966x_rx *rx) lan966x, FDMA_CH_DB_DISCARD); } -static void lan966x_fdma_rx_reload(struct lan966x_rx *rx) +void lan966x_fdma_rx_reload(struct lan966x_rx *rx) { struct lan966x *lan966x = rx->lan966x; @@ -209,12 +214,14 @@ static int lan966x_fdma_tx_alloc(struct lan966x_tx *tx) if (!tx->dcbs_buf) return -ENOMEM; - err = fdma_alloc_coherent(lan966x->dev, fdma); + err = fdma_alloc_coherent(lan966x->dma_dev, fdma); if (err) goto out; fdma_dcbs_init(fdma, 0, 0); + lan966x_fdma_llp_configure(lan966x, fdma->dma, fdma->channel_id); + return 0; out: @@ -227,7 +234,7 @@ static void lan966x_fdma_tx_free(struct lan966x_tx *tx) struct lan966x *lan966x = tx->lan966x; kfree(tx->dcbs_buf); - fdma_free_coherent(lan966x->dev, &tx->fdma); + fdma_free_coherent(lan966x->dma_dev, &tx->fdma); } static void lan966x_fdma_tx_activate(struct lan966x_tx *tx) @@ -236,14 +243,6 @@ static void lan966x_fdma_tx_activate(struct lan966x_tx *tx) struct fdma *fdma = &tx->fdma; u32 mask; - /* When activating a channel, first is required to write the first DCB - * address and then to activate it - */ - lan_wr(lower_32_bits((u64)fdma->dma), lan966x, - FDMA_DCB_LLP(fdma->channel_id)); - lan_wr(upper_32_bits((u64)fdma->dma), lan966x, - FDMA_DCB_LLP1(fdma->channel_id)); - lan_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) | FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) | FDMA_CH_CFG_CH_INJ_PORT_SET(0) | @@ -269,7 +268,7 @@ static void lan966x_fdma_tx_activate(struct lan966x_tx *tx) lan966x, FDMA_CH_ACTIVATE); } -static void lan966x_fdma_tx_disable(struct lan966x_tx *tx) +void lan966x_fdma_tx_disable(struct lan966x_tx *tx) { struct lan966x *lan966x = tx->lan966x; struct fdma *fdma = &tx->fdma; @@ -301,7 +300,7 @@ static void lan966x_fdma_tx_reload(struct lan966x_tx *tx) lan966x, FDMA_CH_RELOAD); } -static void lan966x_fdma_wakeup_netdev(struct lan966x *lan966x) +void lan966x_fdma_wakeup_netdev(struct lan966x *lan966x) { struct lan966x_port *port; int i; @@ -360,7 +359,7 @@ static void lan966x_fdma_tx_clear_buf(struct lan966x *lan966x, int weight) dcb_buf->used = false; if (dcb_buf->use_skb) { - dma_unmap_single(lan966x->dev, + dma_unmap_single(lan966x->dma_dev, dcb_buf->dma_addr, dcb_buf->len, DMA_TO_DEVICE); @@ -369,7 +368,7 @@ static void lan966x_fdma_tx_clear_buf(struct lan966x *lan966x, int weight) napi_consume_skb(dcb_buf->data.skb, weight); } else { if (dcb_buf->xdp_ndo) - dma_unmap_single(lan966x->dev, + dma_unmap_single(lan966x->dma_dev, dcb_buf->dma_addr, dcb_buf->len, DMA_TO_DEVICE); @@ -405,9 +404,10 @@ static int lan966x_fdma_rx_check_frame(struct lan966x_rx *rx, u64 *src_port) if (unlikely(!page)) return FDMA_ERROR; - dma_sync_single_for_cpu(lan966x->dev, - (dma_addr_t)db->dataptr + XDP_PACKET_HEADROOM, - FDMA_DCB_STATUS_BLOCKL(db->status), + dma_sync_single_for_cpu(lan966x->dma_dev, + (dma_addr_t)fdma_db_dataptr_get(db) + + XDP_PACKET_HEADROOM, + fdma_db_len_get(db), DMA_FROM_DEVICE); lan966x_ifh_get_src_port(page_address(page) + XDP_PACKET_HEADROOM, @@ -419,7 +419,7 @@ static int lan966x_fdma_rx_check_frame(struct lan966x_rx *rx, u64 *src_port) if (!lan966x_xdp_port_present(port)) return FDMA_PASS; - return lan966x_xdp_run(port, page, FDMA_DCB_STATUS_BLOCKL(db->status)); + return lan966x_xdp_run(port, page, fdma_db_len_get(db)); } static struct sk_buff *lan966x_fdma_rx_get_frame(struct lan966x_rx *rx, @@ -443,7 +443,7 @@ static struct sk_buff *lan966x_fdma_rx_get_frame(struct lan966x_rx *rx, skb_mark_for_recycle(skb); skb_reserve(skb, XDP_PACKET_HEADROOM); - skb_put(skb, FDMA_DCB_STATUS_BLOCKL(db->status)); + skb_put(skb, fdma_db_len_get(db)); lan966x_ifh_get_timestamp(skb->data, ×tamp); @@ -475,7 +475,7 @@ free_page: return NULL; } -static int lan966x_fdma_napi_poll(struct napi_struct *napi, int weight) +int lan966x_fdma_napi_poll(struct napi_struct *napi, int weight) { struct lan966x *lan966x = container_of(napi, struct lan966x, napi); struct lan966x_rx *rx = &lan966x->rx; @@ -588,7 +588,7 @@ static int lan966x_fdma_get_next_dcb(struct lan966x_tx *tx) return -1; } -static void lan966x_fdma_tx_start(struct lan966x_tx *tx) +void lan966x_fdma_tx_start(struct lan966x_tx *tx) { struct lan966x *lan966x = tx->lan966x; @@ -640,11 +640,11 @@ int lan966x_fdma_xmit_xdpf(struct lan966x_port *port, void *ptr, u32 len) lan966x_ifh_set_bypass(ifh, 1); lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port)); - dma_addr = dma_map_single(lan966x->dev, + dma_addr = dma_map_single(lan966x->dma_dev, xdpf->data - IFH_LEN_BYTES, xdpf->len + IFH_LEN_BYTES, DMA_TO_DEVICE); - if (dma_mapping_error(lan966x->dev, dma_addr)) { + if (dma_mapping_error(lan966x->dma_dev, dma_addr)) { ret = NETDEV_TX_OK; goto out; } @@ -660,7 +660,7 @@ int lan966x_fdma_xmit_xdpf(struct lan966x_port *port, void *ptr, u32 len) lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port)); dma_addr = page_pool_get_dma_addr(page); - dma_sync_single_for_device(lan966x->dev, + dma_sync_single_for_device(lan966x->dma_dev, dma_addr + XDP_PACKET_HEADROOM, len + IFH_LEN_BYTES, DMA_TO_DEVICE); @@ -739,9 +739,9 @@ int lan966x_fdma_xmit(struct sk_buff *skb, __be32 *ifh, struct net_device *dev) memcpy(skb->data, ifh, IFH_LEN_BYTES); skb_put(skb, 4); - dma_addr = dma_map_single(lan966x->dev, skb->data, skb->len, + dma_addr = dma_map_single(lan966x->dma_dev, skb->data, skb->len, DMA_TO_DEVICE); - if (dma_mapping_error(lan966x->dev, dma_addr)) { + if (dma_mapping_error(lan966x->dma_dev, dma_addr)) { dev->stats.tx_dropped++; err = NETDEV_TX_OK; goto release; @@ -806,7 +806,7 @@ static int lan966x_fdma_get_max_mtu(struct lan966x *lan966x) return max_mtu; } -static int lan966x_qsys_sw_status(struct lan966x *lan966x) +int lan966x_qsys_sw_status(struct lan966x *lan966x) { return lan_rd(lan966x, QSYS_SW_STATUS(CPU_PORT)); } @@ -847,7 +847,7 @@ static int lan966x_fdma_reload(struct lan966x *lan966x, int new_mtu) page_pool_put_full_page(page_pool, old_pages[i][j], false); - fdma_free_coherent(lan966x->dev, &fdma_rx_old); + fdma_free_coherent(lan966x->dma_dev, &fdma_rx_old); page_pool_destroy(page_pool); @@ -876,6 +876,9 @@ restore: MEM_TYPE_PAGE_POOL, page_pool); } + lan966x_fdma_llp_configure(lan966x, lan966x->rx.fdma.dma, + lan966x->rx.fdma.channel_id); + lan966x_fdma_rx_start(&lan966x->rx); lan966x_fdma_wakeup_netdev(lan966x); @@ -885,13 +888,9 @@ restore: return err; } -static int lan966x_fdma_get_max_frame(struct lan966x *lan966x) +int lan966x_fdma_get_max_frame(struct lan966x *lan966x) { - return lan966x_fdma_get_max_mtu(lan966x) + - IFH_LEN_BYTES + - SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + - VLAN_HLEN * 2 + - XDP_PACKET_HEADROOM; + return lan966x_fdma_get_max_mtu(lan966x) + FDMA_OVERHEAD; } static int __lan966x_fdma_reload(struct lan966x *lan966x, int max_mtu) @@ -949,7 +948,7 @@ void lan966x_fdma_netdev_init(struct lan966x *lan966x, struct net_device *dev) return; lan966x->fdma_ndev = dev; - netif_napi_add(dev, &lan966x->napi, lan966x_fdma_napi_poll); + netif_napi_add(dev, &lan966x->napi, lan966x->ops->fdma_poll); napi_enable(&lan966x->napi); } @@ -994,7 +993,7 @@ int lan966x_fdma_init(struct lan966x *lan966x) err = lan966x_fdma_tx_alloc(&lan966x->tx); if (err) { - fdma_free_coherent(lan966x->dev, &lan966x->rx.fdma); + fdma_free_coherent(lan966x->dma_dev, &lan966x->rx.fdma); page_pool_destroy(lan966x->rx.page_pool); return err; } @@ -1016,7 +1015,7 @@ void lan966x_fdma_deinit(struct lan966x *lan966x) napi_disable(&lan966x->napi); lan966x_fdma_rx_free_pages(&lan966x->rx); - fdma_free_coherent(lan966x->dev, &lan966x->rx.fdma); + fdma_free_coherent(lan966x->dma_dev, &lan966x->rx.fdma); page_pool_destroy(lan966x->rx.page_pool); lan966x_fdma_tx_free(&lan966x->tx); } diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma_pci.c b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma_pci.c new file mode 100644 index 000000000000..949994874ed9 --- /dev/null +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma_pci.c @@ -0,0 +1,731 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <linux/bpf_trace.h> +#include <linux/ip.h> +#include <net/addrconf.h> + +#include "fdma_api.h" +#include "lan966x_main.h" + +static int lan966x_fdma_pci_dataptr_cb(struct fdma *fdma, int dcb, int db, + u64 *dataptr) +{ + u64 addr; + + addr = fdma_dataptr_dma_addr_contiguous(fdma, dcb, db); + + *dataptr = fdma_pci_atu_translate_addr(fdma->atu_region, addr); + + return 0; +} + +static int lan966x_fdma_pci_nextptr_cb(struct fdma *fdma, int dcb, u64 *nextptr) +{ + u64 addr; + + fdma_nextptr_cb(fdma, dcb, &addr); + + *nextptr = fdma_pci_atu_translate_addr(fdma->atu_region, addr); + + return 0; +} + +/* Stop the TX queues on every port, so nothing feeds the injection channel + * while it is torn down or resized. + */ +static void lan966x_fdma_tx_disable_netdev(struct lan966x *lan966x) +{ + struct lan966x_port *port; + int i; + + for (i = 0; i < lan966x->num_phys_ports; ++i) { + port = lan966x->ports[i]; + if (!port) + continue; + + netif_tx_disable(port->dev); + } +} + +static int lan966x_fdma_pci_rx_alloc(struct lan966x_rx *rx) +{ + struct lan966x *lan966x = rx->lan966x; + struct fdma *fdma = &rx->fdma; + int err; + + err = fdma_alloc_coherent_and_map(lan966x->dma_dev, fdma, + &lan966x->atu); + if (err) + return err; + + err = fdma_dcbs_init(fdma, + FDMA_DCB_INFO_DATAL(fdma->db_size - XDP_PACKET_HEADROOM), + FDMA_DCB_STATUS_INTR); + if (err) { + fdma_free_coherent_and_unmap(lan966x->dma_dev, fdma); + return err; + } + + lan966x_fdma_llp_configure(lan966x, + fdma->atu_region->base_addr, + fdma->channel_id); + + return 0; +} + +static int lan966x_fdma_pci_tx_alloc(struct lan966x_tx *tx) +{ + struct lan966x *lan966x = tx->lan966x; + struct fdma *fdma = &tx->fdma; + int err; + + err = fdma_alloc_coherent_and_map(lan966x->dma_dev, fdma, + &lan966x->atu); + if (err) + return err; + + err = fdma_dcbs_init(fdma, + FDMA_DCB_INFO_DATAL(fdma->db_size), + FDMA_DCB_STATUS_DONE); + if (err) { + fdma_free_coherent_and_unmap(lan966x->dma_dev, fdma); + return err; + } + + lan966x_fdma_llp_configure(lan966x, + fdma->atu_region->base_addr, + fdma->channel_id); + + return 0; +} + +static int lan966x_fdma_pci_get_next_dcb(struct fdma *fdma) +{ + struct fdma_db *db; + + for (int i = 0; i < fdma->n_dcbs; i++) { + db = fdma_db_get(fdma, i, 0); + + if (!fdma_db_is_done(db)) + continue; + if (fdma_is_last(fdma, &fdma->dcbs[i])) + continue; + + return i; + } + + return -ENOSPC; +} + +/* TX slot layout (sizes in bytes): + * + * +---------------------+-----+---------+-----+ + * | XDP_PACKET_HEADROOM | IFH | payload | FCS | + * | 256 | 28 | len | 4 | + * +---------------------+-----+---------+-----+ + * |<---------------- db_size ----------------->| + * + * Return true if the frame plus required overhead fits. + */ +static bool lan966x_fdma_pci_tx_size_fits(struct fdma *fdma, u32 len) +{ + return XDP_PACKET_HEADROOM + IFH_LEN_BYTES + len + ETH_FCS_LEN <= + fdma->db_size; +} + +/* Return true if blockl is a valid RX frame size. */ +static bool lan966x_fdma_pci_rx_size_fits(struct fdma *fdma, u32 blockl) +{ + return blockl >= IFH_LEN_BYTES + ETH_HLEN + ETH_FCS_LEN && + blockl <= fdma->db_size - XDP_PACKET_HEADROOM; +} + +static int lan966x_fdma_pci_xmit_xdpf(struct lan966x_port *port, + void *ptr, u32 len) +{ + struct lan966x *lan966x = port->lan966x; + struct lan966x_tx *tx = &lan966x->tx; + struct fdma *fdma = &tx->fdma; + int next_to_use, ret = 0; + void *virt_addr; + + spin_lock(&lan966x->tx_lock); + + next_to_use = lan966x_fdma_pci_get_next_dcb(fdma); + + if (next_to_use < 0) { + netif_stop_queue(port->dev); + port->dev->stats.tx_dropped++; + ret = NETDEV_TX_BUSY; + goto out; + } + + /* Only the upper bound is enforced: XDP owns the frame contents and + * length, so a program that shrinks below ETH_ZLEN gets what it asked + * for. + */ + if (!lan966x_fdma_pci_tx_size_fits(fdma, len)) { + port->dev->stats.tx_dropped++; + ret = -EINVAL; + goto out; + } + + /* virt_addr points to the IFH. */ + virt_addr = fdma_dataptr_virt_addr_contiguous(fdma, next_to_use, 0); + + /* Construct a fresh IFH. */ + memset(virt_addr, 0, IFH_LEN_BYTES); + lan966x_ifh_set_bypass(virt_addr, 1); + lan966x_ifh_set_port(virt_addr, BIT_ULL(port->chip_port)); + + /* Copy the (post-XDP) frame after the IFH. */ + memcpy(virt_addr + IFH_LEN_BYTES, ptr, len); + + /* Order frame write before DCB status write below. */ + dma_wmb(); + + /* Reserve ETH_FCS_LEN for the HW-inserted FCS (len is FCS-stripped). */ + fdma_dcb_add(fdma, + next_to_use, + 0, + FDMA_DCB_STATUS_INTR | + FDMA_DCB_STATUS_SOF | + FDMA_DCB_STATUS_EOF | + FDMA_DCB_STATUS_BLOCKO(0) | + FDMA_DCB_STATUS_BLOCKL(IFH_LEN_BYTES + len + ETH_FCS_LEN)); + + /* Start the transmission. */ + lan966x_fdma_tx_start(tx); + + port->dev->stats.tx_bytes += len; + port->dev->stats.tx_packets++; + +out: + spin_unlock(&lan966x->tx_lock); + + return ret; +} + +static int lan966x_xdp_pci_run(struct lan966x_port *port, void *data, + u32 data_len, void **xdp_data, u32 *xdp_len) +{ + /* Read once so the NULL check and bpf_prog_run_xdp() see the same + * pointer. + */ + struct bpf_prog *xdp_prog = READ_ONCE(port->xdp_prog); + struct lan966x *lan966x = port->lan966x; + struct fdma *fdma = &lan966x->rx.fdma; + struct xdp_buff xdp; + u32 act; + + if (!xdp_prog) + return FDMA_PASS; + + xdp_init_buff(&xdp, fdma->db_size, &port->xdp_rxq); + + /* hard_start is set to slot start (virt_addr is XDP_PACKET_HEADROOM + * into the slot). Headroom includes the IFH; BPF may grow into it + * via adjust_head. IFH is rebuilt on XDP_TX and unread on XDP_PASS. + */ + xdp_prepare_buff(&xdp, + data - XDP_PACKET_HEADROOM, + XDP_PACKET_HEADROOM + IFH_LEN_BYTES, + data_len, + false); + + act = bpf_prog_run_xdp(xdp_prog, &xdp); + + *xdp_data = xdp.data; + *xdp_len = xdp.data_end - xdp.data; + + switch (act) { + case XDP_PASS: + return FDMA_PASS; + case XDP_TX: + return lan966x_fdma_pci_xmit_xdpf(port, *xdp_data, *xdp_len) ? + FDMA_DROP : FDMA_TX; + default: + bpf_warn_invalid_xdp_action(port->dev, xdp_prog, act); + fallthrough; + case XDP_ABORTED: + trace_xdp_exception(port->dev, xdp_prog, act); + fallthrough; + case XDP_DROP: + return FDMA_DROP; + } +} + +static int lan966x_fdma_pci_rx_check_frame(struct lan966x_rx *rx, u64 *src_port, + void **data, u32 *data_len) +{ + struct lan966x *lan966x = rx->lan966x; + struct fdma *fdma = &rx->fdma; + struct lan966x_port *port; + struct fdma_db *db; + void *virt_addr; + u32 blockl; + + /* virt_addr points to the IFH. */ + virt_addr = fdma_dataptr_virt_addr_contiguous(fdma, + fdma->dcb_index, + fdma->db_index); + + lan966x_ifh_get_src_port(virt_addr, src_port); + + if (*src_port >= lan966x->num_phys_ports) + return FDMA_ERROR; + + port = lan966x->ports[*src_port]; + if (!port) + return FDMA_ERROR; + + db = fdma_db_next_get(fdma); + + /* BLOCKL is a 16-bit HW-populated field; reject obviously-bad + * values before they feed memcpy/XDP sizes. + */ + blockl = fdma_db_len_get(db); + if (!lan966x_fdma_pci_rx_size_fits(fdma, blockl)) + return FDMA_ERROR; + + /* Present the Ethernet frame (no IFH, no FCS). HW re-inserts the + * FCS on TX; see lan966x_fdma_pci_xmit_xdpf(). May be overridden + * by XDP. The FCS strip is unconditional because NETIF_F_RXFCS + * is not advertised in hw_features. + */ + *data = virt_addr + IFH_LEN_BYTES; + *data_len = blockl - IFH_LEN_BYTES - ETH_FCS_LEN; + + return lan966x_xdp_pci_run(port, virt_addr, *data_len, data, data_len); +} + +static bool lan966x_fdma_pci_hw_offload(struct lan966x *lan966x, u32 port, + struct sk_buff **pskb) +{ + struct sk_buff *skb = *pskb; + u32 val; + + val = lan_rd(lan966x, ANA_CPU_FWD_CFG(port)); + if (!(val & (ANA_CPU_FWD_CFG_IGMP_REDIR_ENA | + ANA_CPU_FWD_CFG_MLD_REDIR_ENA))) + return true; + + if (eth_type_vlan(skb->protocol)) { + skb = skb_vlan_untag(skb); + *pskb = skb; + if (unlikely(!skb)) + return false; + } + + if (skb->protocol == htons(ETH_P_IP) && + ip_hdr(skb)->protocol == IPPROTO_IGMP) + return false; + + if (IS_ENABLED(CONFIG_IPV6) && + skb->protocol == htons(ETH_P_IPV6) && + ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) && + !ipv6_mc_check_mld(skb)) + return false; + + return true; +} + +static struct sk_buff *lan966x_fdma_pci_rx_get_frame(struct lan966x_rx *rx, + u64 src_port, void *data, + u32 data_len) +{ + struct lan966x *lan966x = rx->lan966x; + struct sk_buff *skb; + + skb = napi_alloc_skb(&lan966x->napi, data_len); + if (unlikely(!skb)) + return NULL; + + memcpy(skb->data, data, data_len); + + skb_put(skb, data_len); + + skb->dev = lan966x->ports[src_port]->dev; + + skb->protocol = eth_type_trans(skb, skb->dev); + + if (lan966x->bridge_mask & BIT(src_port)) { + skb->offload_fwd_mark = 1; + + skb_reset_network_header(skb); + if (!lan966x_fdma_pci_hw_offload(lan966x, src_port, &skb)) { + if (unlikely(!skb)) + return NULL; + skb->offload_fwd_mark = 0; + } + } + + skb->dev->stats.rx_bytes += skb->len; + skb->dev->stats.rx_packets++; + + return skb; +} + +static int lan966x_fdma_pci_xmit(struct sk_buff *skb, __be32 *ifh, + struct net_device *dev) +{ + struct lan966x_port *port = netdev_priv(dev); + struct lan966x *lan966x = port->lan966x; + struct lan966x_tx *tx = &lan966x->tx; + struct fdma *fdma = &tx->fdma; + int next_to_use; + void *virt_addr; + + next_to_use = lan966x_fdma_pci_get_next_dcb(fdma); + + if (next_to_use < 0) { + netif_stop_queue(dev); + return NETDEV_TX_BUSY; + } + + if (skb_put_padto(skb, ETH_ZLEN)) { + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + if (!lan966x_fdma_pci_tx_size_fits(fdma, skb->len)) { + dev_kfree_skb_any(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + skb_tx_timestamp(skb); + + /* virt_addr points to the IFH. */ + virt_addr = fdma_dataptr_virt_addr_contiguous(fdma, next_to_use, 0); + memcpy(virt_addr, ifh, IFH_LEN_BYTES); + memcpy(virt_addr + IFH_LEN_BYTES, skb->data, skb->len); + + /* Order frame write before DCB status write below. */ + dma_wmb(); + + fdma_dcb_add(fdma, + next_to_use, + 0, + FDMA_DCB_STATUS_INTR | + FDMA_DCB_STATUS_SOF | + FDMA_DCB_STATUS_EOF | + FDMA_DCB_STATUS_BLOCKO(0) | + FDMA_DCB_STATUS_BLOCKL(IFH_LEN_BYTES + skb->len + ETH_FCS_LEN)); + + /* Start the transmission. */ + lan966x_fdma_tx_start(tx); + + dev->stats.tx_bytes += skb->len; + dev->stats.tx_packets++; + + /* Safe to free: PTP is not supported on the PCIe path yet, + * so lan966x->ptp is always 0 here. + */ + dev_consume_skb_any(skb); + + return NETDEV_TX_OK; +} + +static int lan966x_fdma_pci_napi_poll(struct napi_struct *napi, int weight) +{ + struct lan966x *lan966x = container_of(napi, struct lan966x, napi); + struct lan966x_rx *rx = &lan966x->rx; + struct fdma *fdma = &rx->fdma; + int dcb_reload, old_dcb; + struct sk_buff *skb; + int counter = 0; + u64 src_port; + u32 data_len; + void *data; + + /* Wake any stopped TX queues if a TX DCB is available. */ + spin_lock(&lan966x->tx_lock); + if (lan966x_fdma_pci_get_next_dcb(&lan966x->tx.fdma) >= 0) + lan966x_fdma_wakeup_netdev(lan966x); + spin_unlock(&lan966x->tx_lock); + + dcb_reload = fdma->dcb_index; + + /* Get all received skbs. */ + while (counter < weight) { + if (!fdma_has_frames(fdma)) + break; + /* Order DONE read before DCB/frame reads below. */ + dma_rmb(); + counter++; + switch (lan966x_fdma_pci_rx_check_frame(rx, + &src_port, + &data, + &data_len)) { + case FDMA_PASS: + break; + case FDMA_ERROR: + /* No rx_dropped increment here because src_port is + * invalid. + */ + fdma_dcb_advance(fdma); + continue; + case FDMA_TX: + fdma_dcb_advance(fdma); + continue; + case FDMA_DROP: + fdma_dcb_advance(fdma); + continue; + } + skb = lan966x_fdma_pci_rx_get_frame(rx, + src_port, + data, + data_len); + fdma_dcb_advance(fdma); + if (!skb) { + lan966x->ports[src_port]->dev->stats.rx_dropped++; + continue; + } + + napi_gro_receive(&lan966x->napi, skb); + } + while (dcb_reload != fdma->dcb_index) { + old_dcb = dcb_reload; + dcb_reload++; + dcb_reload &= fdma->n_dcbs - 1; + + fdma_dcb_add(fdma, + old_dcb, + FDMA_DCB_INFO_DATAL(fdma->db_size - XDP_PACKET_HEADROOM), + FDMA_DCB_STATUS_INTR); + + lan966x_fdma_rx_reload(rx); + } + + if (counter < weight && napi_complete_done(napi, counter)) + lan_wr(0xff, lan966x, FDMA_INTR_DB_ENA); + + return counter; +} + +static int lan966x_fdma_pci_init(struct lan966x *lan966x) +{ + struct fdma *rx_fdma = &lan966x->rx.fdma; + struct fdma *tx_fdma = &lan966x->tx.fdma; + int err; + + if (!lan966x->fdma) + return 0; + + lan_wr(FDMA_CTRL_NRESET_SET(0), lan966x, FDMA_CTRL); + lan_wr(FDMA_CTRL_NRESET_SET(1), lan966x, FDMA_CTRL); + + fdma_pci_atu_init(&lan966x->atu, lan966x->regs[TARGET_PCIE_DBI]); + + lan966x->rx.lan966x = lan966x; + lan966x->rx.max_mtu = lan966x_fdma_get_max_frame(lan966x); + rx_fdma->channel_id = FDMA_XTR_CHANNEL; + rx_fdma->n_dcbs = FDMA_PCI_DCB_MAX; + rx_fdma->n_dbs = FDMA_RX_DCB_MAX_DBS; + rx_fdma->priv = lan966x; + rx_fdma->db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu); + rx_fdma->size = fdma_get_size_contiguous(rx_fdma); + rx_fdma->ops.nextptr_cb = &lan966x_fdma_pci_nextptr_cb; + rx_fdma->ops.dataptr_cb = &lan966x_fdma_pci_dataptr_cb; + + lan966x->tx.lan966x = lan966x; + tx_fdma->channel_id = FDMA_INJ_CHANNEL; + tx_fdma->n_dcbs = FDMA_PCI_DCB_MAX; + tx_fdma->n_dbs = FDMA_TX_DCB_MAX_DBS; + tx_fdma->priv = lan966x; + tx_fdma->db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu); + tx_fdma->size = fdma_get_size_contiguous(tx_fdma); + tx_fdma->ops.nextptr_cb = &lan966x_fdma_pci_nextptr_cb; + tx_fdma->ops.dataptr_cb = &lan966x_fdma_pci_dataptr_cb; + + err = lan966x_fdma_pci_rx_alloc(&lan966x->rx); + if (err) + return err; + + err = lan966x_fdma_pci_tx_alloc(&lan966x->tx); + if (err) { + fdma_free_coherent_and_unmap(lan966x->dma_dev, rx_fdma); + return err; + } + + lan966x_fdma_rx_start(&lan966x->rx); + + return 0; +} + +/* Reset existing rx and tx buffers. */ +static void lan966x_fdma_pci_reset_mem(struct lan966x *lan966x) +{ + struct lan966x_rx *rx = &lan966x->rx; + struct lan966x_tx *tx = &lan966x->tx; + + memset(rx->fdma.dcbs, 0, rx->fdma.size); + memset(tx->fdma.dcbs, 0, tx->fdma.size); + + fdma_dcbs_init(&rx->fdma, + FDMA_DCB_INFO_DATAL(rx->fdma.db_size - XDP_PACKET_HEADROOM), + FDMA_DCB_STATUS_INTR); + + fdma_dcbs_init(&tx->fdma, + FDMA_DCB_INFO_DATAL(tx->fdma.db_size), + FDMA_DCB_STATUS_DONE); + + lan966x_fdma_llp_configure(lan966x, + tx->fdma.atu_region->base_addr, + tx->fdma.channel_id); + lan966x_fdma_llp_configure(lan966x, + rx->fdma.atu_region->base_addr, + rx->fdma.channel_id); +} + +/* Wake all TX queues on every port (undoes lan966x_fdma_tx_disable_netdev). */ +static void lan966x_fdma_pci_wakeup_netdev(struct lan966x *lan966x) +{ + for (int i = 0; i < lan966x->num_phys_ports; ++i) { + struct lan966x_port *port = lan966x->ports[i]; + + if (port) + netif_tx_wake_all_queues(port->dev); + } +} + +static int lan966x_fdma_pci_reload(struct lan966x *lan966x, int new_mtu) +{ + struct fdma tx_fdma_old = lan966x->tx.fdma; + struct fdma rx_fdma_old = lan966x->rx.fdma; + u32 old_mtu = lan966x->rx.max_mtu; + int err; + + napi_disable(&lan966x->napi); + lan966x_fdma_tx_disable_netdev(lan966x); + lan966x_fdma_rx_disable(&lan966x->rx); + lan966x_fdma_tx_disable(&lan966x->tx); + + lan966x->rx.max_mtu = new_mtu; + + /* Must be NULL'ed in order to realloc them. */ + lan966x->rx.fdma.atu_region = NULL; + lan966x->tx.fdma.atu_region = NULL; + + lan966x->tx.fdma.db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu); + lan966x->tx.fdma.size = fdma_get_size_contiguous(&lan966x->tx.fdma); + lan966x->rx.fdma.db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu); + lan966x->rx.fdma.size = fdma_get_size_contiguous(&lan966x->rx.fdma); + + err = lan966x_fdma_pci_rx_alloc(&lan966x->rx); + if (err) + goto restore; + + err = lan966x_fdma_pci_tx_alloc(&lan966x->tx); + if (err) { + fdma_free_coherent_and_unmap(lan966x->dma_dev, + &lan966x->rx.fdma); + goto restore; + } + + /* Free and unmap old memory. */ + fdma_free_coherent_and_unmap(lan966x->dma_dev, &rx_fdma_old); + fdma_free_coherent_and_unmap(lan966x->dma_dev, &tx_fdma_old); + + /* Order matters: napi_enable() must precede the wakes, or a TX that + * completes first clears FDMA_INTR_DB_ENA with nothing scheduled to + * restore it, leaving RX dead until the next reload. + */ + napi_enable(&lan966x->napi); + lan966x_fdma_rx_start(&lan966x->rx); + lan966x_fdma_pci_wakeup_netdev(lan966x); + + return err; +restore: + + /* No new buffers are allocated at this point. Use the old buffers, + * but reset them before starting the FDMA again. + */ + + memcpy(&lan966x->tx.fdma, &tx_fdma_old, sizeof(struct fdma)); + memcpy(&lan966x->rx.fdma, &rx_fdma_old, sizeof(struct fdma)); + + lan966x->rx.max_mtu = old_mtu; + + lan966x_fdma_pci_reset_mem(lan966x); + + napi_enable(&lan966x->napi); + lan966x_fdma_rx_start(&lan966x->rx); + lan966x_fdma_pci_wakeup_netdev(lan966x); + + return err; +} + +static int __lan966x_fdma_pci_reload(struct lan966x *lan966x, int max_mtu) +{ + int err; + u32 val; + + /* Disable the CPU port. */ + lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(0), + QSYS_SW_PORT_MODE_PORT_ENA, + lan966x, QSYS_SW_PORT_MODE(CPU_PORT)); + + /* Flush the CPU queues. */ + readx_poll_timeout(lan966x_qsys_sw_status, + lan966x, + val, + !(QSYS_SW_STATUS_EQ_AVAIL_GET(val)), + READL_SLEEP_US, READL_TIMEOUT_US); + + /* Add a sleep in case there are frames between the queues and the CPU + * port + */ + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + + err = lan966x_fdma_pci_reload(lan966x, max_mtu); + + /* Enable back the CPU port. */ + lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(1), + QSYS_SW_PORT_MODE_PORT_ENA, + lan966x, QSYS_SW_PORT_MODE(CPU_PORT)); + + return err; +} + +static int lan966x_fdma_pci_resize(struct lan966x *lan966x) +{ + int max_mtu; + + /* Nothing to resize until fdma_pci_init() has built the rings; it + * sizes them from DEV_MAC_MAXLEN_CFG, which the caller already set. + */ + if (!lan966x->rx.lan966x) + return 0; + + max_mtu = lan966x_fdma_get_max_frame(lan966x); + if (max_mtu == lan966x->rx.max_mtu) + return 0; + + return __lan966x_fdma_pci_reload(lan966x, max_mtu); +} + +static void lan966x_fdma_pci_deinit(struct lan966x *lan966x) +{ + if (!lan966x->fdma) + return; + + if (lan966x->fdma_ndev) + napi_disable(&lan966x->napi); + + lan966x_fdma_tx_disable_netdev(lan966x); + lan966x_fdma_rx_disable(&lan966x->rx); + lan966x_fdma_tx_disable(&lan966x->tx); + + fdma_free_coherent_and_unmap(lan966x->dma_dev, &lan966x->rx.fdma); + fdma_free_coherent_and_unmap(lan966x->dma_dev, &lan966x->tx.fdma); +} + +const struct lan966x_fdma_ops lan966x_fdma_pci_ops = { + .fdma_init = &lan966x_fdma_pci_init, + .fdma_deinit = &lan966x_fdma_pci_deinit, + .fdma_xmit = &lan966x_fdma_pci_xmit, + .fdma_poll = &lan966x_fdma_pci_napi_poll, + .fdma_resize = &lan966x_fdma_pci_resize, +}; diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c index 1179a6e127c5..e4c9c5812cca 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c @@ -7,6 +7,7 @@ #include <linux/ip.h> #include <linux/of.h> #include <linux/of_net.h> +#include <linux/pci.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/reset.h> @@ -26,6 +27,14 @@ #define IO_RANGES 2 +static const struct lan966x_fdma_ops lan966x_fdma_ops = { + .fdma_init = &lan966x_fdma_init, + .fdma_deinit = &lan966x_fdma_deinit, + .fdma_xmit = &lan966x_fdma_xmit, + .fdma_poll = &lan966x_fdma_napi_poll, + .fdma_resize = &lan966x_fdma_change_mtu, +}; + static const struct of_device_id lan966x_match[] = { { .compatible = "microchip,lan966x-switch" }, { } @@ -41,6 +50,7 @@ struct lan966x_main_io_resource { static const struct lan966x_main_io_resource lan966x_main_iomap[] = { { TARGET_CPU, 0xc0000, 0 }, /* 0xe00c0000 */ { TARGET_FDMA, 0xc0400, 0 }, /* 0xe00c0400 */ + { TARGET_PCIE_DBI, 0x400000, 0 }, /* 0xe0400000 */ { TARGET_ORG, 0, 1 }, /* 0xe2000000 */ { TARGET_GCB, 0x4000, 1 }, /* 0xe2004000 */ { TARGET_QS, 0x8000, 1 }, /* 0xe2008000 */ @@ -391,7 +401,7 @@ static netdev_tx_t lan966x_port_xmit(struct sk_buff *skb, spin_lock(&lan966x->tx_lock); if (port->lan966x->fdma) - err = lan966x_fdma_xmit(skb, ifh, dev); + err = lan966x->ops->fdma_xmit(skb, ifh, dev); else err = lan966x_port_ifh_xmit(skb, ifh, dev); spin_unlock(&lan966x->tx_lock); @@ -413,7 +423,7 @@ static int lan966x_port_change_mtu(struct net_device *dev, int new_mtu) if (!lan966x->fdma) return 0; - err = lan966x_fdma_change_mtu(lan966x); + err = lan966x->ops->fdma_resize(lan966x); if (err) { lan_wr(DEV_MAC_MAXLEN_CFG_MAX_LEN_SET(LAN966X_HW_MTU(old_mtu)), lan966x, DEV_MAC_MAXLEN_CFG(port->chip_port)); @@ -813,7 +823,8 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p, port->chip_port = p; lan966x->ports[p] = port; - dev->max_mtu = ETH_MAX_MTU; + dev->max_mtu = lan966x_is_pci(lan966x) && lan966x->fdma ? + FDMA_PCI_MAX_MTU : ETH_MAX_MTU; dev->netdev_ops = &lan966x_port_netdev_ops; dev->ethtool_ops = &lan966x_ethtool_ops; @@ -864,10 +875,13 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p, port->phylink = phylink; - if (lan966x->fdma) - dev->xdp_features = NETDEV_XDP_ACT_BASIC | - NETDEV_XDP_ACT_REDIRECT | - NETDEV_XDP_ACT_NDO_XMIT; + if (lan966x->fdma) { + dev->xdp_features = NETDEV_XDP_ACT_BASIC; + + if (!lan966x_is_pci(lan966x)) + dev->xdp_features |= NETDEV_XDP_ACT_REDIRECT | + NETDEV_XDP_ACT_NDO_XMIT; + } err = register_netdev(dev); if (err) { @@ -1058,6 +1072,15 @@ static int lan966x_reset_switch(struct lan966x *lan966x) reset_control_reset(switch_reset); + /* When in PCI mode, the GCB soft reset issued by the reset + * controller can latch spurious bits in the FDMA error and + * data-block stickies. Clear them before request_irq hooks up the + * FDMA IRQ line, otherwise the handler fires immediately on probe. + */ + lan_wr(lan_rd(lan966x, FDMA_ERRORS), lan966x, FDMA_ERRORS); + lan_wr(lan_rd(lan966x, FDMA_INTR_ERR), lan966x, FDMA_INTR_ERR); + lan_wr(lan_rd(lan966x, FDMA_INTR_DB), lan966x, FDMA_INTR_DB); + /* Don't reinitialize the switch core, if it is already initialized. In * case it is initialized twice, some pointers inside the queue system * in HW will get corrupted and then after a while the queue system gets @@ -1081,6 +1104,20 @@ static int lan966x_reset_switch(struct lan966x *lan966x) return 0; } +/* When enumerated over PCIe, dev is a platform device with no + * iommus/dma-ranges of its own, so DMA must target the PCIe endpoint instead. + * The result differs from dev only in that case; lan966x_is_pci() relies on it. + */ +static struct device *lan966x_get_dma_dev(struct device *dev) +{ + for (struct device *p = dev->parent; p; p = p->parent) { + if (dev_is_pci(p)) + return p; + } + + return dev; +} + static int lan966x_probe(struct platform_device *pdev) { struct fwnode_handle *ports, *portnp; @@ -1094,6 +1131,10 @@ static int lan966x_probe(struct platform_device *pdev) platform_set_drvdata(pdev, lan966x); lan966x->dev = &pdev->dev; + lan966x->dma_dev = lan966x_get_dma_dev(lan966x->dev); + + lan966x->ops = lan966x_is_pci(lan966x) ? &lan966x_fdma_pci_ops : + &lan966x_fdma_ops; if (!device_get_mac_address(&pdev->dev, mac_addr)) { ether_addr_copy(lan966x->base_mac, mac_addr); @@ -1152,7 +1193,9 @@ static int lan966x_probe(struct platform_device *pdev) if (err) return dev_err_probe(&pdev->dev, err, "Unable to use ptp irq"); - lan966x->ptp = 1; + /* PTP is not supported on the PCIe path yet. */ + if (!lan966x_is_pci(lan966x)) + lan966x->ptp = 1; } lan966x->fdma_irq = platform_get_irq_byname(pdev, "fdma"); @@ -1234,7 +1277,7 @@ static int lan966x_probe(struct platform_device *pdev) if (err) goto cleanup_fdb; - err = lan966x_fdma_init(lan966x); + err = lan966x->ops->fdma_init(lan966x); if (err) goto cleanup_ptp; @@ -1247,7 +1290,7 @@ static int lan966x_probe(struct platform_device *pdev) return 0; cleanup_fdma: - lan966x_fdma_deinit(lan966x); + lan966x->ops->fdma_deinit(lan966x); cleanup_ptp: lan966x_ptp_deinit(lan966x); @@ -1275,7 +1318,7 @@ static void lan966x_remove(struct platform_device *pdev) lan966x_taprio_deinit(lan966x); lan966x_vcap_deinit(lan966x); - lan966x_fdma_deinit(lan966x); + lan966x->ops->fdma_deinit(lan966x); lan966x_cleanup_ports(lan966x); cancel_delayed_work_sync(&lan966x->stats_work); @@ -1289,9 +1332,65 @@ static void lan966x_remove(struct platform_device *pdev) debugfs_remove_recursive(lan966x->debugfs_root); } +static void lan966x_shutdown(struct platform_device *pdev) +{ + struct lan966x *lan966x = platform_get_drvdata(pdev); + + /* As a PCIe endpoint the switch is not reset by the host reboot, so it + * has to be quiesced here: + * + * Free the irqs and mask the sources: no source can assert INTx. + * Disable NAPI: the teardown must not race a poll. + * Stop and detach the netdevs: drains xmit, closes ndo_open and MTU. + * Stop the FDMA channels: waits for the engine to go idle. + * Unmap the ATU windows: revokes the engine's access to host memory. + */ + if (!lan966x_is_pci(lan966x)) + return; + + if (lan966x->xtr_irq > 0) + devm_free_irq(lan966x->dev, lan966x->xtr_irq, lan966x); + if (lan966x->ana_irq > 0) + devm_free_irq(lan966x->dev, lan966x->ana_irq, lan966x); + if (lan966x->fdma_irq > 0) + devm_free_irq(lan966x->dev, lan966x->fdma_irq, lan966x); + + lan_wr(0, lan966x, ANA_ANAINTR); + + if (!lan966x->fdma) + return; + + rtnl_lock(); + + if (lan966x->fdma_ndev) + napi_disable(&lan966x->napi); + + for (int p = 0; p < lan966x->num_phys_ports; p++) { + if (!lan966x->ports[p] || !lan966x->ports[p]->dev) + continue; + + netif_tx_disable(lan966x->ports[p]->dev); + netif_device_detach(lan966x->ports[p]->dev); + } + + lan966x_fdma_rx_disable(&lan966x->rx); + lan966x_fdma_tx_disable(&lan966x->tx); + + lan_wr(0, lan966x, FDMA_INTR_ENA); + lan_wr(0, lan966x, FDMA_INTR_DB_ENA); + +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) + fdma_pci_atu_region_unmap(lan966x->rx.fdma.atu_region); + fdma_pci_atu_region_unmap(lan966x->tx.fdma.atu_region); +#endif + + rtnl_unlock(); +} + static struct platform_driver lan966x_driver = { .probe = lan966x_probe, .remove = lan966x_remove, + .shutdown = lan966x_shutdown, .driver = { .name = "lan966x-switch", .of_match_table = lan966x_match, diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h index eea286c29474..1877f1916d71 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h @@ -7,6 +7,7 @@ #include <linux/etherdevice.h> #include <linux/if_vlan.h> #include <linux/jiffies.h> +#include <linux/mmzone.h> #include <linux/phy.h> #include <linux/phylink.h> #include <linux/ptp_clock_kernel.h> @@ -17,6 +18,7 @@ #include <net/xdp.h> #include <fdma_api.h> +#include <fdma_pci.h> #include <vcap_api.h> #include <vcap_api_client.h> @@ -86,6 +88,33 @@ #define FDMA_INJ_CHANNEL 0 #define FDMA_DCB_MAX 512 +/* Ring must fit in one MAX_PAGE_ORDER DMA block; 512 DCBs overflows + * at jumbo MTU. + */ +#define FDMA_PCI_DCB_MAX 256 + +#define FDMA_OVERHEAD \ + (IFH_LEN_BYTES + \ + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + \ + VLAN_HLEN * 2 + \ + XDP_PACKET_HEADROOM) + +/* Largest db_size keeping the ATU-padded ring inside one MAX_PAGE_ORDER + * block and within the 16-bit DCB DATAL field. Inverts ALIGN(x, R) <= L + * into x <= ALIGN_DOWN(L, R) to bound x directly. + */ +#define FDMA_PCI_DB_SIZE_MAX \ + MIN_T(u32, \ + (ALIGN_DOWN(PAGE_SIZE << MAX_PAGE_ORDER, \ + FDMA_PCI_ATU_REGION_ALIGN) - \ + FDMA_PCI_DCB_MAX * sizeof(struct fdma_dcb)) / \ + (FDMA_PCI_DCB_MAX * FDMA_RX_DCB_MAX_DBS), \ + ALIGN_DOWN(GENMASK(15, 0), FDMA_PCI_DB_ALIGN)) + +#define FDMA_PCI_MAX_MTU \ + (FDMA_PCI_DB_SIZE_MAX - FDMA_OVERHEAD - \ + (ETH_HLEN + ETH_FCS_LEN)) + #define SE_IDX_QUEUE 0 /* 0-79 : Queue scheduler elements */ #define SE_IDX_PORT 80 /* 80-89 : Port schedular elements */ @@ -193,6 +222,17 @@ enum vcap_is1_port_sel_rt { VCAP_IS1_PS_RT_FOLLOW_OTHER = 7, }; +struct lan966x; + +struct lan966x_fdma_ops { + int (*fdma_init)(struct lan966x *lan966x); + void (*fdma_deinit)(struct lan966x *lan966x); + int (*fdma_xmit)(struct sk_buff *skb, __be32 *ifh, + struct net_device *dev); + int (*fdma_poll)(struct napi_struct *napi, int weight); + int (*fdma_resize)(struct lan966x *lan966x); +}; + struct lan966x_port; struct lan966x_rx { @@ -270,11 +310,20 @@ struct lan966x_skb_cb { struct lan966x { struct device *dev; + /* Device used for DMA; the PCIe endpoint when enumerated over PCIe. */ + struct device *dma_dev; + + const struct lan966x_fdma_ops *ops; + u8 num_phys_ports; struct lan966x_port **ports; void __iomem *regs[NUM_TARGETS]; +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) + struct fdma_pci_atu atu; +#endif + int shared_queue_sz; u8 base_mac[ETH_ALEN]; @@ -561,6 +610,26 @@ int lan966x_fdma_init(struct lan966x *lan966x); void lan966x_fdma_deinit(struct lan966x *lan966x); irqreturn_t lan966x_fdma_irq_handler(int irq, void *args); int lan966x_fdma_reload_page_pool(struct lan966x *lan966x); +int lan966x_fdma_napi_poll(struct napi_struct *napi, int weight); +void lan966x_fdma_llp_configure(struct lan966x *lan966x, u64 addr, + u8 channel_id); +void lan966x_fdma_rx_start(struct lan966x_rx *rx); +void lan966x_fdma_rx_disable(struct lan966x_rx *rx); +void lan966x_fdma_rx_reload(struct lan966x_rx *rx); +void lan966x_fdma_tx_start(struct lan966x_tx *tx); +void lan966x_fdma_tx_disable(struct lan966x_tx *tx); +void lan966x_fdma_wakeup_netdev(struct lan966x *lan966x); +int lan966x_fdma_get_max_frame(struct lan966x *lan966x); +int lan966x_qsys_sw_status(struct lan966x *lan966x); + +extern const struct lan966x_fdma_ops lan966x_fdma_pci_ops; + +/* dma_dev differs from dev only on the PCIe path. */ +static inline bool lan966x_is_pci(struct lan966x *lan966x) +{ + return IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) && + lan966x->dma_dev != lan966x->dev; +} int lan966x_lag_port_join(struct lan966x_port *port, struct net_device *brport_dev, diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h b/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h index 4b553927d2e0..4778ea217673 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h @@ -20,6 +20,7 @@ enum lan966x_target { TARGET_FDMA = 21, TARGET_GCB = 27, TARGET_ORG = 36, + TARGET_PCIE_DBI = 40, TARGET_PTP = 41, TARGET_QS = 42, TARGET_QSYS = 46, @@ -1009,6 +1010,15 @@ enum lan966x_target { #define FDMA_CH_CFG_CH_MEM_GET(x)\ FIELD_GET(FDMA_CH_CFG_CH_MEM, x) +/* FDMA:FDMA:FDMA_CTRL */ +#define FDMA_CTRL __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 424, 0, 1, 4) + +#define FDMA_CTRL_NRESET BIT(0) +#define FDMA_CTRL_NRESET_SET(x)\ + FIELD_PREP(FDMA_CTRL_NRESET, x) +#define FDMA_CTRL_NRESET_GET(x)\ + FIELD_GET(FDMA_CTRL_NRESET, x) + /* FDMA:FDMA:FDMA_PORT_CTRL */ #define FDMA_PORT_CTRL(r) __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 376, r, 2, 4) @@ -1039,6 +1049,21 @@ enum lan966x_target { /* FDMA:FDMA:FDMA_INTR_ERR */ #define FDMA_INTR_ERR __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 400, 0, 1, 4) +/* FDMA:FDMA:FDMA_INTR_ENA */ +#define FDMA_INTR_ENA __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 404, 0, 1, 4) + +#define FDMA_INTR_ENA_INTR_PORT_ENA GENMASK(9, 8) +#define FDMA_INTR_ENA_INTR_PORT_ENA_SET(x)\ + FIELD_PREP(FDMA_INTR_ENA_INTR_PORT_ENA, x) +#define FDMA_INTR_ENA_INTR_PORT_ENA_GET(x)\ + FIELD_GET(FDMA_INTR_ENA_INTR_PORT_ENA, x) + +#define FDMA_INTR_ENA_INTR_CH_ENA GENMASK(7, 0) +#define FDMA_INTR_ENA_INTR_CH_ENA_SET(x)\ + FIELD_PREP(FDMA_INTR_ENA_INTR_CH_ENA, x) +#define FDMA_INTR_ENA_INTR_CH_ENA_GET(x)\ + FIELD_GET(FDMA_INTR_ENA_INTR_CH_ENA, x) + /* FDMA:FDMA:FDMA_ERRORS */ #define FDMA_ERRORS __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 412, 0, 1, 4) diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c b/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c index 9ee61db8690b..b98426afc785 100644 --- a/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c @@ -24,6 +24,12 @@ static int lan966x_xdp_setup(struct net_device *dev, struct netdev_bpf *xdp) old_prog = xchg(&port->xdp_prog, xdp->prog); new_xdp = lan966x_xdp_present(lan966x); + /* PCIe FDMA uses contiguous buffers, so no page_pool reload + * is needed. + */ + if (lan966x_is_pci(lan966x)) + goto out; + if (old_xdp == new_xdp) goto out; diff --git a/drivers/net/ethernet/microchip/sparx5/Makefile b/drivers/net/ethernet/microchip/sparx5/Makefile index d447f9e84d92..06cf583effdb 100644 --- a/drivers/net/ethernet/microchip/sparx5/Makefile +++ b/drivers/net/ethernet/microchip/sparx5/Makefile @@ -11,7 +11,7 @@ sparx5-switch-y := sparx5_main.o sparx5_packet.o \ sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \ sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \ sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \ - sparx5_psfp.o sparx5_mirror.o sparx5_regs.o + sparx5_psfp.o sparx5_mirror.o sparx5_regs.o sparx5_tas.o sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c index f3a9c71bea36..c02499d1b297 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c @@ -318,6 +318,8 @@ static const struct sparx5_consts lan969x_consts = { .n_gates = 256, .n_sdlbs = 496, .n_dsm_cal_taxis = 5, + .n_tas_gcl = 3000, + .n_tas_lists = 60, .buf_size = 1572864, .qres_max_prio_idx = 315, .qres_max_colour_idx = 323, diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c index e857640df185..4442c0c1a957 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c @@ -82,6 +82,9 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi, struct lan969x_dsm_cal_dev_speed *speed; int err; + for (u32 i = 0; i < SPX5_DSM_CAL_LEN; i++) + data->schedule[i] = SPX5_DSM_CAL_EMPTY; + /* Maximum bandwidth for this taxi */ taxi_bw = (128 * 1000000) / sparx5_clk_period(sparx5->coreclock); @@ -125,7 +128,7 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi, } if (n_devs == 0) { - data->schedule[0] = SPX5_DSM_CAL_EMPTY; + data->schedule[0] = SPX5_DSM_CAL_MAX_DEVS_PER_TAXI; return 0; } @@ -167,9 +170,6 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi, return -EINVAL; } - for (u32 i = 0; i < SPX5_DSM_CAL_LEN; i++) - data->schedule[i] = SPX5_DSM_CAL_EMPTY; - /* Place the remaining devices */ for (u32 i = 0; i < DSM_CAL_DEV_MAX; i++) { speed = &dev_speeds[i]; diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c index c7e0e58ff0f7..053d125eda65 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c @@ -54,10 +54,9 @@ static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight) { struct fdma *fdma = &sparx5->tx.fdma; struct sparx5_tx_buf *db; - unsigned long flags; int i; - spin_lock_irqsave(&sparx5->tx_lock, flags); + spin_lock(&sparx5->tx_lock); for (i = 0; i < fdma->n_dcbs; ++i) { db = &sparx5->tx.dbs[i]; @@ -83,7 +82,7 @@ static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight) db->used = false; } - spin_unlock_irqrestore(&sparx5->tx_lock, flags); + spin_unlock(&sparx5->tx_lock); } static void lan969x_fdma_free_pages(struct sparx5_rx *rx) diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c index 3fc2c006ba12..175fcf8bdd61 100644 --- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c +++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c @@ -96,6 +96,9 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = { [GA_HSCH_MMGT] = 36260, [GA_HSCH_TAS_CONFIG] = 37696, [GA_HSCH_TAS_PROFILE_CFG] = 37712, + [GA_HSCH_TAS_LIST_CFG] = 36288, + [GA_HSCH_TAS_GCL_CFG] = 36240, + [GA_HSCH_HSCH_TAS_STATE] = 36256, [GA_PTP_PTP_CFG] = 512, [GA_PTP_PTP_TOD_DOMAINS] = 528, [GA_PTP_PHASE_DETECTOR_CTRL] = 628, @@ -147,6 +150,7 @@ const unsigned int lan969x_gsize[GSIZE_LAST] = { [GW_GCB_CHIP_REGS] = 180, [GW_HSCH_TAS_CONFIG] = 16, [GW_HSCH_TAS_PROFILE_CFG] = 68, + [GW_HSCH_TAS_GCL_CFG] = 16, [GW_PTP_PHASE_DETECTOR_CTRL] = 12, [GW_QSYS_PAUSE_CFG] = 988, }; @@ -201,6 +205,12 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = { [FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST] = 14, [FW_HSCH_FLUSH_CTRL_FLUSH_PORT] = 6, [FW_HSCH_FLUSH_CTRL_FLUSH_HIER] = 14, + [FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX] = 6, + [FW_HSCH_TAS_CFG_CTRL_LIST_NUM] = 6, + [FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM] = 12, + [FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS] = 11, + [FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX] = 6, + [FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR] = 12, [FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW] = 13, [FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX] = 8, [FW_LRN_AUTOAGE_CFG_2_NEXT_ROW] = 13, diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c index b530fb6d3d0c..e039f5369b95 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c @@ -1064,6 +1064,8 @@ static const struct sparx5_consts sparx5_consts = { .n_gates = 1024, .n_sdlbs = 4096, .n_dsm_cal_taxis = 8, + .n_tas_gcl = 10000, + .n_tas_lists = 128, .buf_size = 4194280, .qres_max_prio_idx = 630, .qres_max_colour_idx = 638, diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h index 0f7d8440f614..8dc3a500c1c9 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h @@ -108,6 +108,7 @@ enum sparx5_feature { #define SPX5_PRIOS 8 /* Number of priority queues */ #define SPX5_BUFFER_CELL_SZ 184 /* Cell size */ #define SPX5_BUFFER_MEMORY 4194280 /* 22795 words * 184 bytes */ +#define SPX5_TAS_ENTRIES_PER_PORT 2 /* TAS lists per port */ #define XTR_QUEUE 0 #define INJ_QUEUE 0 @@ -236,6 +237,11 @@ struct sparx5_port { struct sk_buff_head tx_skbs; bool is_mrouter; struct list_head tc_templates; /* list of TC templates on this port */ + /* TAS GCL entries owned by each of the port's two TAS lists. */ + struct { + int gcl_base; + int gcl_count; + } tas_lists[SPX5_TAS_ENTRIES_PER_PORT]; }; enum sparx5_core_clockfreq { @@ -316,6 +322,8 @@ struct sparx5_consts { u32 n_gates; /* Number of PSFP gates */ u32 n_sdlbs; /* Number of service dual leaky buckets */ u32 n_dsm_cal_taxis; /* Number of DSM calendar taxis */ + u32 n_tas_gcl; /* Number of TAS gate control list entries */ + u32 n_tas_lists; /* Number of TAS lists */ u32 buf_size; /* Amount of QLIM watermark memory */ u32 qres_max_prio_idx; /* Maximum QRES prio index */ u32 qres_max_colour_idx; /* Maximum QRES colour index */ @@ -374,6 +382,8 @@ struct sparx5 { void __iomem *regs[NUM_TARGETS]; int port_count; struct mutex lock; /* MAC reg lock */ + struct mutex tas_lock; /* Time Aware Shaper lock */ + unsigned long *tas_gcl_bitmap; /* Allocated TAS GCL entries */ /* port structures are in net device */ struct sparx5_port *ports[SPX5_PORTS]; enum sparx5_core_clockfreq coreclock; @@ -707,6 +717,24 @@ void sparx5_psfp_init(struct sparx5 *sparx5); void sparx5_new_base_time(struct sparx5 *sparx5, const u32 cycle_time, const ktime_t org_base_time, ktime_t *new_base_time); +/* sparx5_tas.c */ +/* TAS list states, mirrors the HSCH_TAS_LIST_STATE_LIST_STATE HW field */ +enum sparx5_tas_state { + SPX5_TAS_STATE_ADMIN, + SPX5_TAS_STATE_ADVANCING, + SPX5_TAS_STATE_PENDING, + SPX5_TAS_STATE_OPERATING, + SPX5_TAS_STATE_TERMINATING, + SPX5_NUM_TAS_STATE, +}; + +int sparx5_tas_init(struct sparx5 *sparx5); +struct tc_taprio_qopt_offload; +int sparx5_tas_enable(struct sparx5_port *port, + struct tc_taprio_qopt_offload *qopt); +int sparx5_tas_disable(struct sparx5_port *port); +void sparx5_tas_speed(struct sparx5_port *port, int speed); + /* sparx5_mirror.c */ int sparx5_mirror_add(struct sparx5_mall_entry *entry); void sparx5_mirror_del(struct sparx5_mall_entry *entry); diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h index d34467513648..1464fd1a1d44 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h @@ -5358,6 +5358,50 @@ extern const struct sparx5_regs *regs; #define HSCH_RESET_CFG_CORE_ENA_GET(x)\ FIELD_GET(HSCH_RESET_CFG_CORE_ENA, x) +/* HSCH:TAS_CONFIG:TAS_CFG_CTRL */ +#define HSCH_TAS_CFG_CTRL \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_CONFIG], 0, 1, \ + regs->gsize[GW_HSCH_TAS_CONFIG], 0, 0, 1, 4) + +#define HSCH_TAS_CFG_CTRL_LIST_NUM_MAX\ + GENMASK(regs->fsize[FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX] + 22 - 1, 22) +#define HSCH_TAS_CFG_CTRL_LIST_NUM_MAX_SET(x)\ + spx5_field_prep(HSCH_TAS_CFG_CTRL_LIST_NUM_MAX, x) +#define HSCH_TAS_CFG_CTRL_LIST_NUM_MAX_GET(x)\ + spx5_field_get(HSCH_TAS_CFG_CTRL_LIST_NUM_MAX, x) + +#define HSCH_TAS_CFG_CTRL_LIST_NUM\ + GENMASK(regs->fsize[FW_HSCH_TAS_CFG_CTRL_LIST_NUM] + 15 - 1, 15) +#define HSCH_TAS_CFG_CTRL_LIST_NUM_SET(x)\ + spx5_field_prep(HSCH_TAS_CFG_CTRL_LIST_NUM, x) +#define HSCH_TAS_CFG_CTRL_LIST_NUM_GET(x)\ + spx5_field_get(HSCH_TAS_CFG_CTRL_LIST_NUM, x) + +#define HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q BIT(14) +#define HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q_SET(x)\ + FIELD_PREP(HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q, x) +#define HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q_GET(x)\ + FIELD_GET(HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q, x) + +#define HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM\ + GENMASK(regs->fsize[FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM] + 0 - 1, 0) +#define HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM_SET(x)\ + spx5_field_prep(HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM, x) +#define HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM_GET(x)\ + spx5_field_get(HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM, x) + +/* HSCH:TAS_CONFIG:TAS_GATE_STATE_CTRL */ +#define HSCH_TAS_GATE_STATE_CTRL \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_CONFIG], 0, 1, \ + regs->gsize[GW_HSCH_TAS_CONFIG], 4, 0, 1, 4) + +#define HSCH_TAS_GATE_STATE_CTRL_HSCH_POS\ + GENMASK(regs->fsize[FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS] + 0 - 1, 0) +#define HSCH_TAS_GATE_STATE_CTRL_HSCH_POS_SET(x)\ + spx5_field_prep(HSCH_TAS_GATE_STATE_CTRL_HSCH_POS, x) +#define HSCH_TAS_GATE_STATE_CTRL_HSCH_POS_GET(x)\ + spx5_field_get(HSCH_TAS_GATE_STATE_CTRL_HSCH_POS, x) + /* HSCH:TAS_CONFIG:TAS_STATEMACHINE_CFG */ #define HSCH_TAS_STATEMACHINE_CFG \ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_CONFIG], 0, 1, \ @@ -5375,12 +5419,172 @@ extern const struct sparx5_regs *regs; regs->gcnt[GC_HSCH_TAS_PROFILE_CFG], \ regs->gsize[GW_HSCH_TAS_PROFILE_CFG], 32, 0, 1, 4) +/* SPARX5 ONLY */ +#define HSCH_TAS_PROFILE_CONFIG_PORT_NUM GENMASK(17, 11) +#define HSCH_TAS_PROFILE_CONFIG_PORT_NUM_SET(x)\ + FIELD_PREP(HSCH_TAS_PROFILE_CONFIG_PORT_NUM, x) +#define HSCH_TAS_PROFILE_CONFIG_PORT_NUM_GET(x)\ + FIELD_GET(HSCH_TAS_PROFILE_CONFIG_PORT_NUM, x) + #define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED GENMASK(10, 8) #define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(x)\ FIELD_PREP(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x) #define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_GET(x)\ FIELD_GET(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x) +/* HSCH:TAS_LIST_CFG:TAS_BASE_TIME_NSEC */ +#define HSCH_TAS_BASE_TIME_NSEC \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \ + 0, 0, 1, 4) + +#define HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC GENMASK(29, 0) +#define HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC_SET(x)\ + FIELD_PREP(HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC, x) +#define HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC_GET(x)\ + FIELD_GET(HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC, x) + +/* HSCH:TAS_LIST_CFG:TAS_BASE_TIME_SEC_LSB */ +#define HSCH_TAS_BASE_TIME_SEC_LSB \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \ + 4, 0, 1, 4) + +/* HSCH:TAS_LIST_CFG:TAS_BASE_TIME_SEC_MSB */ +#define HSCH_TAS_BASE_TIME_SEC_MSB \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \ + 8, 0, 1, 4) + +#define HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB GENMASK(15, 0) +#define HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB_SET(x)\ + FIELD_PREP(HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB, x) +#define HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB_GET(x)\ + FIELD_GET(HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB, x) + +/* HSCH:TAS_LIST_CFG:TAS_CYCLE_TIME_CFG */ +#define HSCH_TAS_CYCLE_TIME_CFG \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \ + 24, 0, 1, 4) + +/* HSCH:TAS_LIST_CFG:TAS_STARTUP_CFG */ +#define HSCH_TAS_STARTUP_CFG \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \ + 28, 0, 1, 4) + +#define HSCH_TAS_STARTUP_CFG_STARTUP_TIME GENMASK(21, 0) +#define HSCH_TAS_STARTUP_CFG_STARTUP_TIME_SET(x)\ + FIELD_PREP(HSCH_TAS_STARTUP_CFG_STARTUP_TIME, x) +#define HSCH_TAS_STARTUP_CFG_STARTUP_TIME_GET(x)\ + FIELD_GET(HSCH_TAS_STARTUP_CFG_STARTUP_TIME, x) + +#define HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX\ + GENMASK(regs->fsize[FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX] + 23 - 1, 23) +#define HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX_SET(x)\ + spx5_field_prep(HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX, x) +#define HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX_GET(x)\ + spx5_field_get(HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX, x) + +#define HSCH_TAS_STARTUP_CFG_STARTUP_ERROR BIT(22) +#define HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_SET(x)\ + FIELD_PREP(HSCH_TAS_STARTUP_CFG_STARTUP_ERROR, x) +#define HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(x)\ + FIELD_GET(HSCH_TAS_STARTUP_CFG_STARTUP_ERROR, x) + +/* HSCH:TAS_LIST_CFG:TAS_LIST_CFG */ +#define HSCH_TAS_LIST_CFG \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \ + 32, 0, 1, 4) + +#define HSCH_TAS_LIST_CFG_LIST_BASE_ADDR\ + GENMASK(regs->fsize[FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR] + 0 - 1, 0) +#define HSCH_TAS_LIST_CFG_LIST_BASE_ADDR_SET(x)\ + spx5_field_prep(HSCH_TAS_LIST_CFG_LIST_BASE_ADDR, x) +#define HSCH_TAS_LIST_CFG_LIST_BASE_ADDR_GET(x)\ + spx5_field_get(HSCH_TAS_LIST_CFG_LIST_BASE_ADDR, x) + +/* SPARX5 ONLY */ +#define HSCH_TAS_LIST_CFG_LIST_LENGTH GENMASK(29, 16) +#define HSCH_TAS_LIST_CFG_LIST_LENGTH_SET(x)\ + FIELD_PREP(HSCH_TAS_LIST_CFG_LIST_LENGTH, x) +#define HSCH_TAS_LIST_CFG_LIST_LENGTH_GET(x)\ + FIELD_GET(HSCH_TAS_LIST_CFG_LIST_LENGTH, x) + +/* LAN969X ONLY */ +#define HSCH_TAS_LIST_CFG_LIST_HSCH_POS GENMASK(31, 21) +#define HSCH_TAS_LIST_CFG_LIST_HSCH_POS_SET(x)\ + FIELD_PREP(HSCH_TAS_LIST_CFG_LIST_HSCH_POS, x) +#define HSCH_TAS_LIST_CFG_LIST_HSCH_POS_GET(x)\ + FIELD_GET(HSCH_TAS_LIST_CFG_LIST_HSCH_POS, x) + +/* LAN969X ONLY */ +#define HSCH_TAS_LIST_CFG_LIST_PORT_NUM GENMASK(20, 16) +#define HSCH_TAS_LIST_CFG_LIST_PORT_NUM_SET(x)\ + FIELD_PREP(HSCH_TAS_LIST_CFG_LIST_PORT_NUM, x) +#define HSCH_TAS_LIST_CFG_LIST_PORT_NUM_GET(x)\ + FIELD_GET(HSCH_TAS_LIST_CFG_LIST_PORT_NUM, x) + +/* HSCH:TAS_LIST_CFG:TAS_LIST_STATE */ +#define HSCH_TAS_LIST_STATE \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \ + 36, 0, 1, 4) + +#define HSCH_TAS_LIST_STATE_LIST_STATE GENMASK(2, 0) +#define HSCH_TAS_LIST_STATE_LIST_STATE_SET(x)\ + FIELD_PREP(HSCH_TAS_LIST_STATE_LIST_STATE, x) +#define HSCH_TAS_LIST_STATE_LIST_STATE_GET(x)\ + FIELD_GET(HSCH_TAS_LIST_STATE_LIST_STATE, x) + +/* HSCH:TAS_GCL_CFG:TAS_GCL_CTRL_CFG */ +#define HSCH_TAS_GCL_CTRL_CFG \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_GCL_CFG], 0, 1, \ + regs->gsize[GW_HSCH_TAS_GCL_CFG], 0, 0, 1, 4) + +#define HSCH_TAS_GCL_CTRL_CFG_GATE_STATE GENMASK(27, 20) +#define HSCH_TAS_GCL_CTRL_CFG_GATE_STATE_SET(x)\ + FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG_GATE_STATE, x) +#define HSCH_TAS_GCL_CTRL_CFG_GATE_STATE_GET(x)\ + FIELD_GET(HSCH_TAS_GCL_CTRL_CFG_GATE_STATE, x) + +/* SPARX5 ONLY */ +#define HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE GENMASK(19, 13) +#define HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE_SET(x)\ + FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE, x) +#define HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE_GET(x)\ + FIELD_GET(HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE, x) + +/* SPARX5 ONLY */ +#define HSCH_TAS_GCL_CTRL_CFG_HSCH_POS GENMASK(12, 0) +#define HSCH_TAS_GCL_CTRL_CFG_HSCH_POS_SET(x)\ + FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG_HSCH_POS, x) +#define HSCH_TAS_GCL_CTRL_CFG_HSCH_POS_GET(x)\ + FIELD_GET(HSCH_TAS_GCL_CTRL_CFG_HSCH_POS, x) + +/* HSCH:TAS_GCL_CFG:TAS_GCL_TIME_CFG */ +#define HSCH_TAS_GCL_TIME_CFG \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_GCL_CFG], 0, 1, \ + regs->gsize[GW_HSCH_TAS_GCL_CFG], 4, 0, 1, 4) + +/* LAN969X ONLY */ +/* HSCH:TAS_GCL_CFG:TAS_GCL_CTRL_CFG2 */ +#define HSCH_TAS_GCL_CTRL_CFG2 \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_GCL_CFG], 0, 1, \ + regs->gsize[GW_HSCH_TAS_GCL_CFG], 8, 0, 1, 4) + +#define HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL GENMASK(11, 0) +#define HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL_SET(x)\ + FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL, x) +#define HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL_GET(x)\ + FIELD_GET(HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL, x) + +/* HSCH:HSCH_TAS_STATE:TAS_GATE_STATE */ +#define HSCH_TAS_GATE_STATE \ + __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_HSCH_TAS_STATE], 0, 1, 4, \ + 0, 0, 1, 4) + +#define HSCH_TAS_GATE_STATE_TAS_GATE_STATE GENMASK(7, 0) +#define HSCH_TAS_GATE_STATE_TAS_GATE_STATE_SET(x)\ + FIELD_PREP(HSCH_TAS_GATE_STATE_TAS_GATE_STATE, x) +#define HSCH_TAS_GATE_STATE_TAS_GATE_STATE_GET(x)\ + FIELD_GET(HSCH_TAS_GATE_STATE_TAS_GATE_STATE, x) + /* LAN969X ONLY */ /* HSIOWRAP:XMII_CFG:XMII_CFG */ #define HSIO_WRAP_XMII_CFG(g) \ diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c index 83b37f95ee46..fa19a48f941d 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c @@ -322,6 +322,7 @@ void sparx5_psfp_init(struct sparx5 *sparx5) { const struct sparx5_ops *ops = sparx5->data->ops; const struct sparx5_sdlb_group *group; + u32 sg_ct_clks; int i; for (i = 0; i < sparx5->data->consts->n_lb_groups; i++) { @@ -330,7 +331,14 @@ void sparx5_psfp_init(struct sparx5 *sparx5) group->min_burst, group->frame_size, i); } - spx5_wr(ANA_AC_SG_CYCLETIME_UPDATE_PERIOD_SG_CT_UPDATE_ENA_SET(1), + /* Poll the stream gate cycle start times every 10 us. The core clock + * period is in picoseconds. + */ + sg_ct_clks = 10 * NSEC_PER_USEC * PSEC_PER_NSEC / + sparx5_clk_period(sparx5->coreclock); + + spx5_wr(ANA_AC_SG_CYCLETIME_UPDATE_PERIOD_SG_CT_CLKS_SET(sg_ct_clks) | + ANA_AC_SG_CYCLETIME_UPDATE_PERIOD_SG_CT_UPDATE_ENA_SET(1), sparx5, ANA_AC_SG_CYCLETIME_UPDATE_PERIOD); spx5_rmw(ANA_L2_FWD_CFG_ISDX_LOOKUP_ENA_SET(1), diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c index 972da8a71f5a..7370533243b6 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c @@ -9,17 +9,6 @@ #include "sparx5_main.h" #include "sparx5_qos.h" -enum sparx5_tas_link_speed { - TAS_SPEED_NO_GB, - TAS_SPEED_10, - TAS_SPEED_100, - TAS_SPEED_1000, - TAS_SPEED_2500, - TAS_SPEED_5000, - TAS_SPEED_10000, - TAS_SPEED_25000, -}; - /* Calculate new base_time based on cycle_time. * * The hardware requires a base_time that is always in the future. @@ -468,6 +457,10 @@ int sparx5_qos_init(struct sparx5 *sparx5) if (ret < 0) return ret; + ret = sparx5_tas_init(sparx5); + if (ret) + return ret; + sparx5_psfp_init(sparx5); return 0; @@ -592,41 +585,3 @@ int sparx5_tc_ets_del(struct sparx5_port *port) return sparx5_dwrr_conf_set(port, &dwrr); } - -void sparx5_tas_speed(struct sparx5_port *port, int speed) -{ - struct sparx5 *sparx5 = port->sparx5; - u8 spd; - - switch (speed) { - case SPEED_10: - spd = TAS_SPEED_10; - break; - case SPEED_100: - spd = TAS_SPEED_100; - break; - case SPEED_1000: - spd = TAS_SPEED_1000; - break; - case SPEED_2500: - spd = TAS_SPEED_2500; - break; - case SPEED_5000: - spd = TAS_SPEED_5000; - break; - case SPEED_10000: - spd = TAS_SPEED_10000; - break; - case SPEED_25000: - spd = TAS_SPEED_25000; - break; - default: - netdev_err(port->ndev, "TAS: Unsupported speed: %d\n", speed); - return; - } - - spx5_rmw(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(spd), - HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, - sparx5, - HSCH_TAS_PROFILE_CONFIG(port->portno)); -} diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h index a92a699c551f..04f76f1e23f6 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h @@ -60,7 +60,6 @@ struct sparx5_dwrr { }; int sparx5_qos_init(struct sparx5 *sparx5); -void sparx5_tas_speed(struct sparx5_port *port, int speed); /* Multi-Queue Priority */ int sparx5_tc_mqprio_add(struct net_device *ndev, u8 num_tc); diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c index 3863f954bd83..75c1bd4f0053 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c @@ -96,6 +96,9 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = { [GA_HSCH_MMGT] = 162368, [GA_HSCH_TAS_CONFIG] = 162384, [GA_HSCH_TAS_PROFILE_CFG] = 188416, + [GA_HSCH_TAS_LIST_CFG] = 161600, + [GA_HSCH_TAS_GCL_CFG] = 161584, + [GA_HSCH_HSCH_TAS_STATE] = 161580, [GA_PTP_PTP_CFG] = 320, [GA_PTP_PTP_TOD_DOMAINS] = 336, [GA_PTP_PHASE_DETECTOR_CTRL] = 420, @@ -147,6 +150,7 @@ const unsigned int sparx5_gsize[GSIZE_LAST] = { [GW_GCB_CHIP_REGS] = 424, [GW_HSCH_TAS_CONFIG] = 12, [GW_HSCH_TAS_PROFILE_CFG] = 64, + [GW_HSCH_TAS_GCL_CFG] = 8, [GW_PTP_PHASE_DETECTOR_CTRL] = 8, [GW_QSYS_PAUSE_CFG] = 1128, }; @@ -201,6 +205,12 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = { [FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST] = 16, [FW_HSCH_FLUSH_CTRL_FLUSH_PORT] = 7, [FW_HSCH_FLUSH_CTRL_FLUSH_HIER] = 16, + [FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX] = 7, + [FW_HSCH_TAS_CFG_CTRL_LIST_NUM] = 7, + [FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM] = 14, + [FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS] = 13, + [FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX] = 7, + [FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR] = 14, [FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW] = 14, [FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX] = 11, [FW_LRN_AUTOAGE_CFG_2_NEXT_ROW] = 14, diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h index 585589a31e90..97d496ebb750 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h @@ -105,6 +105,9 @@ enum sparx5_gaddr_enum { GA_HSCH_MMGT, GA_HSCH_TAS_CONFIG, GA_HSCH_TAS_PROFILE_CFG, + GA_HSCH_TAS_LIST_CFG, + GA_HSCH_TAS_GCL_CFG, + GA_HSCH_HSCH_TAS_STATE, GA_PTP_PTP_CFG, GA_PTP_PTP_TOD_DOMAINS, GA_PTP_PHASE_DETECTOR_CTRL, @@ -158,6 +161,7 @@ enum sparx5_gsize_enum { GW_GCB_CHIP_REGS, GW_HSCH_TAS_CONFIG, GW_HSCH_TAS_PROFILE_CFG, + GW_HSCH_TAS_GCL_CFG, GW_PTP_PHASE_DETECTOR_CTRL, GW_QSYS_PAUSE_CFG, GSIZE_LAST, @@ -214,6 +218,12 @@ enum sparx5_fsize_enum { FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST, FW_HSCH_FLUSH_CTRL_FLUSH_PORT, FW_HSCH_FLUSH_CTRL_FLUSH_HIER, + FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX, + FW_HSCH_TAS_CFG_CTRL_LIST_NUM, + FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM, + FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS, + FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX, + FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR, FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW, FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX, FW_LRN_AUTOAGE_CFG_2_NEXT_ROW, diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c new file mode 100644 index 000000000000..ec80bc69371a --- /dev/null +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Microchip Sparx5 Switch driver + * + * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries. + */ + +#include <net/pkt_sched.h> + +#include "sparx5_main.h" +#include "sparx5_qos.h" + +enum sparx5_tas_link_speed { + TAS_SPEED_NO_GB, + TAS_SPEED_10, + TAS_SPEED_100, + TAS_SPEED_1000, + TAS_SPEED_2500, + TAS_SPEED_5000, + TAS_SPEED_10000, + TAS_SPEED_25000, +}; + +/* Maximum time in milliseconds to wait for TAS state transitions. */ +#define TAS_TIMEOUT_MS 2000 + +/* Minimum supported cycle time in nanoseconds */ +#define TAS_MIN_CYCLE_TIME_NS (1 * NSEC_PER_USEC) /* 1 usec */ + +/* Maximum supported cycle time in nanoseconds */ +#define TAS_MAX_CYCLE_TIME_NS ((1 * NSEC_PER_SEC) - 1) /* 999.999.999 nsec */ + +/* STARTUP_TIME in units of 256 ns, must be non-zero for a list to start */ +#define TAS_STARTUP_TIME_DEFAULT 8 + +/* We use 2 TAS lists per present port, as far as the chip has lists: + * num_tas_lists = min(port_count * SPX5_TAS_ENTRIES_PER_PORT, n_tas_lists); + * + * The index for the 2 entries of the n'th present port is calculated as: + * index_1 = n * SPX5_TAS_ENTRIES_PER_PORT; + * index_2 = index_1 + 1; + * + * GCL entries are allocated from a bitmap shared by all ports and sized to the + * chip's GCL pool. Each list records the range it owns. The range is freed + * once hardware reports the list back in ADMIN, which for a list obsoleted by + * a replace happens some time after the replace itself. Entry 0 is reserved: + * unwritten NEXT_GCL fields read as 0, so a list must never own it. + * + * We use 1 TAS profile per port, shared by both of its lists: + * index = portno; + * + * We do not yet support frame preemption and the default guard band + * of 1536 bytes is always used on all queues. + * + * The gate mask of each entry is written as is to the port's 8 queues, + * where bit n gates queue (QoS class) n. The mqprio parameters of the + * taprio qdisc (prio_tc_map, queue ranges) are not used, so a schedule + * must use 8 traffic classes mapped 1:1 to the queues, as with the + * mqprio offload. Other values of num_tc are rejected. + * + * These are the possible combinations of states for the two TAS lists in a + * running system. + * + * ADMIN/ADMIN: No schedules are currently operating or pending. + * + * ADMIN/PENDING: No schedule is currently operating but one is pending and + * when current time exceeds the configured base time it will automatically + * enter the OPERATING state. The state is now ADMIN/OPERATING. + * + * ADMIN/OPERATING: A schedule is currently operating and will run until it is + * terminated manually. + * + * OPERATING/PENDING: A schedule is currently operating and another is + * pending. When current time exceeds the configured base time for the + * pending schedule it will automatically stop the operating schedule and + * enter the OPERATING state. The state is now ADMIN/OPERATING. + * + * When we want to disable TAS on a port, we must stop schedules that are in + * state PENDING or OPERATING. + * Pending schedules are stopped first and then operating schedules. + * The gate state for the port must be restored to "all-queues-open" manually + * in case the schedule was stopped with one or more of the queues closed. + * + * When we add a schedule we always use a base time in the future, where base + * time is at least current time + (2 * cycle time). This is a requirement + * from the hardware. This means that a schedule will always start in state + * PENDING. It also means that we always use the new schedule to stop an + * eventually operating schedule. + * + * When we want to add a new schedule we must consider the current state of + * the two TAS list entries: + * + * ADMIN/ADMIN: Just add the new schedule in one of the TAS lists. + * + * ADMIN/PENDING: Stop the current pending schedule and add the new one. + * + * ADMIN/OPERATING: Add the new schedule in the TAS list that is currently in + * admin state and configure it to stop the currently operating schedule when + * current time exceeds the configured base time in the new schedule. + * + * OPERATING/PENDING: Stop the current pending schedule, add the new one and + * configure it to stop the currently operating schedule when + * current time exceeds the configured base time in the new schedule. + */ + +/* Scheduler element index (HSCH_POS) for this port's per-port TAS gate, + * i.e. its position at the layer-2 boundary of the scheduling hierarchy. + */ +static int sparx5_tas_scheduler_get(struct sparx5 *sparx5, int portno) +{ + if (is_sparx5(sparx5)) + return SPX5_HSCH_L0_SE_CNT + SPX5_HSCH_L1_SE_CNT + portno; + else + return 1120 + portno; +} + +static int sparx5_tas_list_index(struct sparx5_port *port, u8 tas_entry) +{ + const struct sparx5_consts *consts = port->sparx5->data->consts; + int portno, pidx = 0; + + /* Limit the index to available ports: the n'th present port uses + * lists 2n and 2n + 1, so the lists of all ports fit in + * port_count * SPX5_TAS_ENTRIES_PER_PORT. + */ + for (portno = 0; portno < consts->n_ports; ++portno) { + if (portno == port->portno) + return (pidx * SPX5_TAS_ENTRIES_PER_PORT) + tas_entry; + if (port->sparx5->ports[portno]) + pidx++; + } + + return 0; +} + +/* Number of TAS lists in use: two per present port, as far as the chip has + * lists for. A port whose lists fall outside this range has no TAS. + */ +static int sparx5_tas_num_lists(struct sparx5 *sparx5) +{ + return min_t(int, sparx5->port_count * SPX5_TAS_ENTRIES_PER_PORT, + sparx5->data->consts->n_tas_lists); +} + +static bool sparx5_tas_port_has_lists(struct sparx5_port *port) +{ + return sparx5_tas_list_index(port, SPX5_TAS_ENTRIES_PER_PORT - 1) < + sparx5_tas_num_lists(port->sparx5); +} + +static const char *sparx5_tas_state_to_str(int state) +{ + switch (state) { + case SPX5_TAS_STATE_ADMIN: + return "ADMIN"; + case SPX5_TAS_STATE_ADVANCING: + return "ADVANCING"; + case SPX5_TAS_STATE_PENDING: + return "PENDING"; + case SPX5_TAS_STATE_OPERATING: + return "OPERATING"; + case SPX5_TAS_STATE_TERMINATING: + return "TERMINATING"; + default: + return "??"; + } +} + +static void sparx5_tas_select_list(struct sparx5 *sparx5, int list) +{ + spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list), + HSCH_TAS_CFG_CTRL_LIST_NUM, + sparx5, + HSCH_TAS_CFG_CTRL); +} + +static int sparx5_tas_list_state_get(struct sparx5 *sparx5) +{ + return HSCH_TAS_LIST_STATE_LIST_STATE_GET(spx5_rd(sparx5, + HSCH_TAS_LIST_STATE)); +} + +static int sparx5_tas_gcl_alloc(struct sparx5 *sparx5, int count) +{ + u32 n_gcl = sparx5->data->consts->n_tas_gcl; + unsigned long base; + + base = bitmap_find_next_zero_area(sparx5->tas_gcl_bitmap, + n_gcl, + 0, + count, + 0); + if (base >= n_gcl) + return -ENOSPC; + + bitmap_set(sparx5->tas_gcl_bitmap, base, count); + + return base; +} + +static void sparx5_tas_gcl_free(struct sparx5_port *port, int slot) +{ + if (port->tas_lists[slot].gcl_count) + bitmap_clear(port->sparx5->tas_gcl_bitmap, + port->tas_lists[slot].gcl_base, + port->tas_lists[slot].gcl_count); + + port->tas_lists[slot].gcl_base = 0; + port->tas_lists[slot].gcl_count = 0; +} + +/* Free the GCL entries of every list that hardware has returned to ADMIN */ +static void sparx5_tas_gcl_flush(struct sparx5 *sparx5) +{ + struct sparx5_port *port; + int i, j; + + lockdep_assert_held(&sparx5->tas_lock); + + for (i = 0; i < sparx5->data->consts->n_ports; i++) { + port = sparx5->ports[i]; + if (!port) + continue; + + for (j = 0; j < SPX5_TAS_ENTRIES_PER_PORT; j++) { + if (!port->tas_lists[j].gcl_count) + continue; + + sparx5_tas_select_list(sparx5, + sparx5_tas_list_index(port, j)); + if (sparx5_tas_list_state_get(sparx5) == + SPX5_TAS_STATE_ADMIN) + sparx5_tas_gcl_free(port, j); + } + } +} + +/* Request state 'req' (or only wait if 'req' < 0) on the selected list until + * it reaches a state in the 'done' mask. + */ +static int sparx5_tas_list_state_wait(struct sparx5_port *port, int list, + int req, u32 done) +{ + struct sparx5 *sparx5 = port->sparx5; + unsigned long end; + int state; + + /* Do not wait forever for the state change */ + end = jiffies + msecs_to_jiffies(TAS_TIMEOUT_MS); + do { + if (req >= 0) + spx5_rmw(HSCH_TAS_LIST_STATE_LIST_STATE_SET(req), + HSCH_TAS_LIST_STATE_LIST_STATE, + sparx5, + HSCH_TAS_LIST_STATE); + + state = sparx5_tas_list_state_get(sparx5); + if (BIT(state) & done) + return 0; + + cond_resched(); + } while (!time_after(jiffies, end)); + + netdev_err(port->ndev, "Timeout switching TAS state %s in list %d\n", + sparx5_tas_state_to_str(state), list); + + return -ETIME; +} + +/* Wait for hardware to bring the selected list back to ADMIN */ +static int sparx5_tas_wait_admin(struct sparx5_port *port, int list) +{ + return sparx5_tas_list_state_wait(port, list, -1, + BIT(SPX5_TAS_STATE_ADMIN)); +} + +static int sparx5_tas_shutdown_pending(struct sparx5_port *port) +{ + struct sparx5 *sparx5 = port->sparx5; + int i, list, state, err; + + for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) { + list = sparx5_tas_list_index(port, i); + spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list), + HSCH_TAS_CFG_CTRL_LIST_NUM, + sparx5, + HSCH_TAS_CFG_CTRL); + + state = sparx5_tas_list_state_get(sparx5); + if (state != SPX5_TAS_STATE_ADVANCING && + state != SPX5_TAS_STATE_PENDING) + continue; + + err = sparx5_tas_list_state_wait(port, list, + SPX5_TAS_STATE_ADMIN, + BIT(SPX5_TAS_STATE_ADMIN)); + if (err) + return err; + + sparx5_tas_gcl_free(port, i); + } + + return 0; +} + +static int sparx5_tas_shutdown_operating(struct sparx5_port *port) +{ + struct sparx5 *sparx5 = port->sparx5; + int i, list, state, err = 0; + u32 sched; + + sched = sparx5_tas_scheduler_get(sparx5, port->portno); + + for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) { + list = sparx5_tas_list_index(port, i); + spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list), + HSCH_TAS_CFG_CTRL_LIST_NUM, + sparx5, + HSCH_TAS_CFG_CTRL); + + /* A list being stopped still runs its gate operations until + * the end of its cycle, so wait for it before opening the + * gates. + */ + state = sparx5_tas_list_state_get(sparx5); + if (state != SPX5_TAS_STATE_OPERATING && + state != SPX5_TAS_STATE_TERMINATING) + continue; + + if (state == SPX5_TAS_STATE_OPERATING && + sparx5_tas_list_state_wait(port, list, + SPX5_TAS_STATE_TERMINATING, + BIT(SPX5_TAS_STATE_TERMINATING) | + BIT(SPX5_TAS_STATE_ADMIN))) { + err = -ETIME; + continue; + } + + if (sparx5_tas_wait_admin(port, list)) { + err = -ETIME; + continue; + } + + sparx5_tas_gcl_free(port, i); + } + + /* Restore gate state to "all-queues-open". */ + + /* Select port n on layer 2 of Hierarchical Scheduler */ + spx5_wr(HSCH_TAS_GATE_STATE_CTRL_HSCH_POS_SET(sched), + sparx5, + HSCH_TAS_GATE_STATE_CTRL); + + /* Set gate state to "all-queues-open" */ + spx5_wr(HSCH_TAS_GATE_STATE_TAS_GATE_STATE_SET(0xff), + sparx5, + HSCH_TAS_GATE_STATE); + + return err; +} + +/* Find an ADMIN list for a new schedule, after stopping a pending one. An + * operating list is returned in obsolete, to be stopped by the new list. + */ +static int sparx5_tas_list_find(struct sparx5_port *port, int *new, + int *obsolete) +{ + int i, err, oper = -1, state_cnt[SPX5_NUM_TAS_STATE] = {0}; + struct sparx5 *sparx5 = port->sparx5; + int state[SPX5_TAS_ENTRIES_PER_PORT]; + int list[SPX5_TAS_ENTRIES_PER_PORT]; + bool valid = false; + + err = sparx5_tas_shutdown_pending(port); + if (err) + return err; + + for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) { + list[i] = sparx5_tas_list_index(port, i); + spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list[i]), + HSCH_TAS_CFG_CTRL_LIST_NUM, + sparx5, + HSCH_TAS_CFG_CTRL); + + state[i] = sparx5_tas_list_state_get(sparx5); + + if (state[i] == SPX5_TAS_STATE_TERMINATING) { + err = sparx5_tas_wait_admin(port, list[i]); + if (err) + return err; + state[i] = SPX5_TAS_STATE_ADMIN; + } + + if (state[i] >= SPX5_NUM_TAS_STATE) { + netdev_err(port->ndev, + "Invalid tas list state %u %u %d\n", + state[i], port->portno, i); + return -EINVAL; + } + + if (state[i] == SPX5_TAS_STATE_OPERATING) + oper = list[i]; + + state_cnt[state[i]]++; + } + + if (state_cnt[SPX5_TAS_STATE_ADMIN] == 2) + valid = true; + + if (state_cnt[SPX5_TAS_STATE_ADMIN] == 1 && + state_cnt[SPX5_TAS_STATE_OPERATING] == 1) + valid = true; + + if (!valid) { + netdev_err(port->ndev, + "Invalid tas state combination: %d %d %d %d %d\n", + state_cnt[SPX5_TAS_STATE_ADMIN], + state_cnt[SPX5_TAS_STATE_ADVANCING], + state_cnt[SPX5_TAS_STATE_PENDING], + state_cnt[SPX5_TAS_STATE_OPERATING], + state_cnt[SPX5_TAS_STATE_TERMINATING]); + + return -EINVAL; + } + + for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) { + if (state[i] == SPX5_TAS_STATE_ADMIN) { + *new = list[i]; + *obsolete = (oper == -1) ? *new : oper; + return 0; + } + } + + return -EBUSY; /* No suitable list found */ +} + +/* Program the list and its GCL entries [base, base + num_entries) */ +static void sparx5_tas_gcl_setup(struct sparx5_port *port, int list, int base, + struct tc_taprio_qopt_offload *qopt) +{ + struct sparx5 *sparx5 = port->sparx5; + u32 sched, entry, prof; + int i; + + sched = sparx5_tas_scheduler_get(sparx5, port->portno); + /* Each port uses the TAS profile of its own number */ + prof = port->portno; + + /* Program only the chosen list; the sibling slot stays in ADMIN. */ + sparx5_tas_select_list(sparx5, list); + + spx5_rmw(HSCH_TAS_LIST_CFG_LIST_BASE_ADDR_SET(base), + HSCH_TAS_LIST_CFG_LIST_BASE_ADDR, + sparx5, + HSCH_TAS_LIST_CFG); + + if (is_sparx5(sparx5)) { + spx5_rmw(HSCH_TAS_LIST_CFG_LIST_LENGTH_SET(qopt->num_entries), + HSCH_TAS_LIST_CFG_LIST_LENGTH, + sparx5, + HSCH_TAS_LIST_CFG); + } else { + /* Associate TAS list with physical port number and + * scheduler element. + */ + spx5_rmw(HSCH_TAS_LIST_CFG_LIST_PORT_NUM_SET(port->portno), + HSCH_TAS_LIST_CFG_LIST_PORT_NUM, sparx5, + HSCH_TAS_LIST_CFG); + spx5_rmw(HSCH_TAS_LIST_CFG_LIST_HSCH_POS_SET(sched), + HSCH_TAS_LIST_CFG_LIST_HSCH_POS, sparx5, + HSCH_TAS_LIST_CFG); + } + + for (i = 0; i < qopt->num_entries; i++) { + u32 gcl_next = (i >= qopt->num_entries - 1) ? base : + base + i + 1; + u32 gate_mask = qopt->entries[i].gate_mask; + + /* On sparx5 GCL_ENTRY_NUM is relative to the list's BASE_ADDR. + * On lan969x the GCL is a linked list and GCL_ENTRY_NUM is an + * absolute index into the pool, as is NEXT_GCL. + */ + entry = is_sparx5(sparx5) ? i : base + i; + + spx5_rmw(HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM_SET(entry), + HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM, + sparx5, + HSCH_TAS_CFG_CTRL); + + /* Set gate states for this GCL */ + spx5_rmw(HSCH_TAS_GCL_CTRL_CFG_GATE_STATE_SET(gate_mask), + HSCH_TAS_GCL_CTRL_CFG_GATE_STATE, sparx5, + HSCH_TAS_GCL_CTRL_CFG); + + if (is_sparx5(sparx5)) { + spx5_rmw(HSCH_TAS_GCL_CTRL_CFG_HSCH_POS_SET(sched) | + HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE_SET(prof), + HSCH_TAS_GCL_CTRL_CFG_HSCH_POS | + HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE, sparx5, + HSCH_TAS_GCL_CTRL_CFG); + } else { + /* The GCL list is a linked list on lan969x */ + spx5_wr(HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL_SET(gcl_next), + sparx5, HSCH_TAS_GCL_CTRL_CFG2); + } + + spx5_wr(qopt->entries[i].interval, + sparx5, + HSCH_TAS_GCL_TIME_CFG); + } +} + +int sparx5_tas_enable(struct sparx5_port *port, + struct tc_taprio_qopt_offload *qopt) +{ + u64 cycle_time = qopt->cycle_time, calculated_cycle_time = 0; + int i, err, base, slot, state, new_list = -1, obsolete = -1; + struct sparx5 *sparx5 = port->sparx5; + struct timespec64 ts; + ktime_t base_time; + unsigned long end; + u32 startup, val; + + if (qopt->cycle_time_extension) { + netdev_err(port->ndev, + "cycle_time_extension is not supported\n"); + return -EOPNOTSUPP; + } + + /* The base time is taken from the PTP clock */ + if (!sparx5->ptp) + return -EOPNOTSUPP; + + if (qopt->base_time < 0) { + netdev_err(port->ndev, "Invalid base_time %lld\n", + qopt->base_time); + return -EINVAL; + } + + if (!sparx5_tas_port_has_lists(port)) { + netdev_err(port->ndev, "No TAS lists left for this port\n"); + return -EOPNOTSUPP; + } + + if (qopt->mqprio.qopt.num_tc != SPX5_PRIOS) { + netdev_err(port->ndev, "Only %d traffic classes supported\n", + SPX5_PRIOS); + return -EOPNOTSUPP; + } + + for (i = 0; i < qopt->num_entries; i++) { + if (qopt->entries[i].command != TC_TAPRIO_CMD_SET_GATES) { + netdev_err(port->ndev, "Unsupported GCL command: %d\n", + qopt->entries[i].command); + return -EOPNOTSUPP; + } + } + + mutex_lock(&sparx5->tas_lock); + + sparx5_tas_gcl_flush(sparx5); + + if (cycle_time > TAS_MAX_CYCLE_TIME_NS) { + netdev_err(port->ndev, "Invalid cycle_time %llu\n", cycle_time); + err = -EINVAL; + goto out; + } + + for (i = 0; i < qopt->num_entries; i++) { + u32 interval = qopt->entries[i].interval; + + if (interval < TAS_MIN_CYCLE_TIME_NS) { + netdev_err(port->ndev, + "Invalid minimum cycle time %u\n", interval); + err = -EINVAL; + goto out; + } + + if (interval > TAS_MAX_CYCLE_TIME_NS) { + netdev_err(port->ndev, + "Invalid maximum cycle time %u\n", interval); + err = -EINVAL; + goto out; + } + + calculated_cycle_time += interval; + } + + if (calculated_cycle_time > TAS_MAX_CYCLE_TIME_NS || + cycle_time < calculated_cycle_time) { + netdev_err(port->ndev, "Invalid cycle_time %llu\n", cycle_time); + err = -EINVAL; + goto out; + } + + sparx5_new_base_time(sparx5, cycle_time, qopt->base_time, &base_time); + + /* Allocate before sparx5_tas_list_find() stops a pending schedule, so + * running out of GCL entries fails the replace without touching + * hardware. + */ + base = sparx5_tas_gcl_alloc(sparx5, qopt->num_entries); + if (base < 0) { + netdev_err(port->ndev, "No room for %zu GCL entries\n", + qopt->num_entries); + err = base; + goto out; + } + + /* Select an appropriate entry to use */ + err = sparx5_tas_list_find(port, &new_list, &obsolete); + if (err) { + bitmap_clear(sparx5->tas_gcl_bitmap, base, qopt->num_entries); + goto out; + } + + /* The new list is in ADMIN, so a range it still owns can go */ + slot = new_list - sparx5_tas_list_index(port, 0); + sparx5_tas_gcl_free(port, slot); + + sparx5_tas_gcl_setup(port, new_list, base, qopt); + + /* The obsolete list keeps its entries until it reaches ADMIN */ + port->tas_lists[slot].gcl_base = base; + port->tas_lists[slot].gcl_count = qopt->num_entries; + + /* Setup TAS list */ + ts = ktime_to_timespec64(base_time); + spx5_wr(HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC_SET(ts.tv_nsec), + sparx5, + HSCH_TAS_BASE_TIME_NSEC); + + spx5_wr((ts.tv_sec & GENMASK(31, 0)), + sparx5, + HSCH_TAS_BASE_TIME_SEC_LSB); + + val = ts.tv_sec >> 32; + spx5_wr(HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB_SET(val), + sparx5, + HSCH_TAS_BASE_TIME_SEC_MSB); + + spx5_wr(cycle_time, + sparx5, + HSCH_TAS_CYCLE_TIME_CFG); + + /* Full write, to also clear a sticky STARTUP_ERROR from a previous use + * of this list. + */ + spx5_wr(HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX_SET(obsolete) | + HSCH_TAS_STARTUP_CFG_STARTUP_TIME_SET(TAS_STARTUP_TIME_DEFAULT), + sparx5, + HSCH_TAS_STARTUP_CFG); + + /* Start list processing */ + spx5_rmw(HSCH_TAS_LIST_STATE_LIST_STATE_SET(SPX5_TAS_STATE_ADVANCING), + HSCH_TAS_LIST_STATE_LIST_STATE, + sparx5, + HSCH_TAS_LIST_STATE); + + /* Do not wait forever for the startup error flag to be reported */ + end = jiffies + msecs_to_jiffies(TAS_TIMEOUT_MS); + do { + /* Read the state first, so an error that ended ADVANCING is + * already latched when STARTUP_CFG is read. + */ + val = spx5_rd(sparx5, HSCH_TAS_LIST_STATE); + state = HSCH_TAS_LIST_STATE_LIST_STATE_GET(val); + startup = spx5_rd(sparx5, HSCH_TAS_STARTUP_CFG); + if (HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(startup) || + state != SPX5_TAS_STATE_ADVANCING) + break; + + cond_resched(); + } while (!time_after(jiffies, end)); + + if (HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(startup)) { + netdev_err(port->ndev, "TAS startup error in list %d\n", + new_list); + err = -EIO; + } else if (state == SPX5_TAS_STATE_ADVANCING) { + netdev_err(port->ndev, "Timeout starting TAS list %d\n", + new_list); + err = -ETIME; + } + + /* taprio sends no DESTROY for a failed offload, so do not leave a + * list behind that may still start. + */ + if (err) + sparx5_tas_shutdown_pending(port); + +out: + mutex_unlock(&sparx5->tas_lock); + return err; +} + +int sparx5_tas_disable(struct sparx5_port *port) +{ + int err, ret; + + /* Nothing was offloaded on a port without lists */ + if (!sparx5_tas_port_has_lists(port)) + return 0; + + mutex_lock(&port->sparx5->tas_lock); + + sparx5_tas_gcl_flush(port->sparx5); + + /* Stop the operating list and restore the gates even if a pending list + * could not be stopped, as taprio does not retry a failed destroy. + */ + err = sparx5_tas_shutdown_pending(port); + ret = sparx5_tas_shutdown_operating(port); + if (!err) + err = ret; + + mutex_unlock(&port->sparx5->tas_lock); + + return err; +} + +int sparx5_tas_init(struct sparx5 *sparx5) +{ + const struct sparx5_consts *consts = sparx5->data->consts; + int i, num_ports, list_num_max; + + /* There are only 128 (sparx5) / 60 (lan969x) TAS lists, not enough + * for the whole port range. LIST_NUM_MAX is the highest list index + * processed, and the datasheet gives 24 as its minimum value. + */ + num_ports = consts->n_ports; + list_num_max = max(sparx5_tas_num_lists(sparx5) - 1, 24); + + sparx5->tas_gcl_bitmap = devm_bitmap_zalloc(sparx5->dev, + consts->n_tas_gcl, + GFP_KERNEL); + if (!sparx5->tas_gcl_bitmap) + return -ENOMEM; + + /* Unwritten NEXT_GCL fields read as 0, so no list may own entry 0 */ + bitmap_set(sparx5->tas_gcl_bitmap, 0, 1); + + mutex_init(&sparx5->tas_lock); + + /* Apply the guard band to gate closes of all traffic classes, not only + * the classes not marked as scheduled in SCH_TRAFFIC_QUEUES. + */ + spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_MAX_SET(list_num_max) | + HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q_SET(1), + HSCH_TAS_CFG_CTRL_LIST_NUM_MAX | + HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q, + sparx5, + HSCH_TAS_CFG_CTRL); + + /* Associate profile with port (profile idx = port on lan969x) */ + if (is_sparx5(sparx5)) { + for (i = 0; i < num_ports; i++) { + if (!sparx5->ports[i]) + continue; + + spx5_rmw(HSCH_TAS_PROFILE_CONFIG_PORT_NUM_SET(i), + HSCH_TAS_PROFILE_CONFIG_PORT_NUM, + sparx5, + HSCH_TAS_PROFILE_CONFIG(i)); + } + } + + return 0; +} + +void sparx5_tas_speed(struct sparx5_port *port, int speed) +{ + struct sparx5 *sparx5 = port->sparx5; + u8 spd; + + switch (speed) { + case SPEED_10: + spd = TAS_SPEED_10; + break; + case SPEED_100: + spd = TAS_SPEED_100; + break; + case SPEED_1000: + spd = TAS_SPEED_1000; + break; + case SPEED_2500: + spd = TAS_SPEED_2500; + break; + case SPEED_5000: + spd = TAS_SPEED_5000; + break; + case SPEED_10000: + spd = TAS_SPEED_10000; + break; + case SPEED_25000: + spd = TAS_SPEED_25000; + break; + default: + netdev_err(port->ndev, "TAS: Unsupported speed: %d\n", speed); + return; + } + + spx5_rmw(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(spd), + HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, + sparx5, + HSCH_TAS_PROFILE_CONFIG(port->portno)); +} diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c index 28b2514c8330..30da5db57ef3 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c @@ -154,6 +154,21 @@ static int sparx5_tc_setup_qdisc_ets(struct net_device *ndev, return -EOPNOTSUPP; } +static int sparx5_tc_setup_qdisc_taprio(struct net_device *ndev, + struct tc_taprio_qopt_offload *qopt) +{ + struct sparx5_port *port = netdev_priv(ndev); + + switch (qopt->cmd) { + case TAPRIO_CMD_REPLACE: + return sparx5_tas_enable(port, qopt); + case TAPRIO_CMD_DESTROY: + return sparx5_tas_disable(port); + default: + return -EOPNOTSUPP; + } +} + int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type, void *type_data) { @@ -166,6 +181,8 @@ int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type, return sparx5_tc_setup_qdisc_tbf(ndev, type_data); case TC_SETUP_QDISC_ETS: return sparx5_tc_setup_qdisc_ets(ndev, type_data); + case TC_SETUP_QDISC_TAPRIO: + return sparx5_tc_setup_qdisc_taprio(ndev, type_data); default: return -EOPNOTSUPP; } diff --git a/drivers/net/ethernet/microchip/vcap/Kconfig b/drivers/net/ethernet/microchip/vcap/Kconfig index 97f43fd4473f..1af30a358a15 100644 --- a/drivers/net/ethernet/microchip/vcap/Kconfig +++ b/drivers/net/ethernet/microchip/vcap/Kconfig @@ -40,7 +40,6 @@ config VCAP_KUNIT_TEST bool "KUnit test for VCAP library" if !KUNIT_ALL_TESTS depends on KUNIT depends on KUNIT=y && VCAP=y && y - select DEBUG_FS default KUNIT_ALL_TESTS help This builds unit tests for the VCAP library. |
