diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-09 15:47:47 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-09 15:47:47 +0100 |
| commit | 51b7e08afddaee38468d99dab8ced15c0f3874b1 (patch) | |
| tree | 533987b6a57e7352e645104cfb46a59eb60634de | |
| parent | 31ea129f8f7d75945c87957c0cd901429db07238 (diff) | |
| parent | fe3e786ef4eb6e47d2901f568a27bd920477bbe9 (diff) | |
| download | linux-next-51b7e08afddaee38468d99dab8ced15c0f3874b1.tar.gz linux-next-51b7e08afddaee38468d99dab8ced15c0f3874b1.zip | |
Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git
# Conflicts:
# MAINTAINERS
# tools/testing/selftests/net/lib.sh
249 files changed, 6923 insertions, 9873 deletions
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index bae14766d4f7..f83150917814 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -91,6 +91,13 @@ following pin states: - ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as a valid input for automatic selection algorithm +Pins that have the ``DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE`` +capability can additionally be set to ``DPLL_PIN_STATE_CONNECTED`` in +automatic mode, overriding the active input selection. This is useful +for automatic-only DPLL devices where mode cannot be switched to manual. +When such a pin is disconnected, the device returns to automatic input +selection. + The actual hardware status of a pin is reported via the operational state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent device: diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 52ad1e7805d1..38b1190f3d26 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -895,6 +895,16 @@ attribute-sets: resource-dump response. Bit 0 (dev) selects device-level resources; bit 1 (port) selects port-level resources. When absent all classes are returned. + - + name: parent-dev + type: nest + nested-attributes: dl-parent-dev + doc: | + Identifies the devlink instance which owns the parent rate node. + Used with rate-set and rate-new to parent a rate object to a node on + a different devlink instance, enabling cross-device rate scheduling. + When absent, the parent node is resolved on the same instance. + - name: dl-dev-stats subset-of: devlink @@ -1317,6 +1327,16 @@ attribute-sets: Specifies the bandwidth share assigned to the Traffic Class. The bandwidth for the traffic class is determined in proportion to the sum of the shares of all configured classes. + - + name: dl-parent-dev + subset-of: devlink + attributes: + - + name: bus-name + - + name: dev-name + - + name: index operations: enum-model: directional @@ -2289,8 +2309,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2303,6 +2323,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-new @@ -2311,8 +2332,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2325,6 +2346,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-del diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 2bf83f6732ab..cdc8c7b456df 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -165,6 +165,13 @@ definitions: - name: gnss doc: GNSS recovered clock + - + name: int-nco + doc: | + Device internal numerically controlled oscillator. + When connected as a DPLL input, the DPLL enters NCO mode + where the output frequency is adjusted by the host via + the PTP clock interface. render-max: true - type: enum @@ -252,6 +259,12 @@ definitions: - name: state-can-change doc: pin state can be changed + - + name: state-connected-override + doc: | + pin state can be set to connected regardless of current + DPLL device mode, overriding the active input selection. + Requires state-can-change to be set as well. - type: const name: phase-offset-divider @@ -456,6 +469,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPM (parts per million). This is a lower-precision version of fractional-frequency-offset-ppt. @@ -502,6 +518,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPT (parts per trillion, 10^-12). This is a higher-precision version of fractional-frequency-offset. diff --git a/Documentation/netlink/specs/rt-addr.yaml b/Documentation/netlink/specs/rt-addr.yaml index 163a106c41bb..0ecbd24c890c 100644 --- a/Documentation/netlink/specs/rt-addr.yaml +++ b/Documentation/netlink/specs/rt-addr.yaml @@ -123,6 +123,9 @@ attribute-sets: - name: proto type: u8 + - + name: mc-users + type: u32 operations: @@ -176,6 +179,7 @@ operations: value: 58 attributes: &mcaddr-attrs - multicast + - mc-users - cacheinfo dump: request: diff --git a/Documentation/networking/devlink/devlink-port.rst b/Documentation/networking/devlink/devlink-port.rst index 9374ebe70f48..fe2cfee3e2a6 100644 --- a/Documentation/networking/devlink/devlink-port.rst +++ b/Documentation/networking/devlink/devlink-port.rst @@ -107,6 +107,15 @@ doesn't have the eswitch. Local controller (identified by controller number = 0) has the eswitch. The Devlink instance on the local controller has eswitch devlink ports for both the controllers. +A non-zero controller number may also be used for ports that are not external. +For example, a SmartNIC may have additional local PCI physical functions +that are managed by the eswitch but are not on an external host. These +ports use a non-zero controller number to distinguish them from the eswitch +manager's own functions, while the external flag remains unset. + +The ``phys_port_name`` includes the controller prefix (``c<controller_num>``) +whenever the controller number is non-zero, regardless of the external flag. + Function configuration ====================== @@ -420,6 +429,8 @@ API allows to configure following rate object's parameters: Parent node name. Parent node rate limits are considered as additional limits to all node children limits. ``tx_max`` is an upper limit for children. ``tx_share`` is a total bandwidth distributed among children. + If the device supports cross-function scheduling, the parent can be from a + different function of the same underlying device. ``tc_bw`` Allow users to set the bandwidth allocation per traffic class on rate diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index 32f70879ddd0..4745148fecf4 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -31,10 +31,10 @@ sure to respect following rules: - Lock ordering should be maintained. If driver needs to take instance lock of both nested and parent instances at the same time, devlink - instance lock of the parent instance should be taken first, only then - instance lock of the nested instance could be taken. - - Driver should use object-specific helpers to setup the nested relationship - before registering the nested devlink instance: + instance lock of the nested instance should be taken first, only then + instance lock of the parent instance could be taken. + - Driver should use object-specific helpers to setup the + nested relationship: - ``devl_nested_devlink_set()`` - called to setup devlink -> nested devlink relationship (could be used for multiple nested instances). diff --git a/Documentation/networking/devlink/mlx5.rst b/Documentation/networking/devlink/mlx5.rst index 4bba4d780a4a..cf1dffa67669 100644 --- a/Documentation/networking/devlink/mlx5.rst +++ b/Documentation/networking/devlink/mlx5.rst @@ -419,3 +419,36 @@ User commands examples: .. note:: This command can run over all interfaces such as PF/VF and representor ports. + +Rates +===== + +mlx5 devices can limit transmission of individual VFs or a group of them via +the devlink-rate API in switchdev mode. + +User commands examples: + +- Print the existing rates:: + + $ devlink port function rate show + +- Set a max tx limit on traffic from VF0:: + + $ devlink port function rate set pci/0000:82:00.0/1 tx_max 10Gbit + +- Create a rate group with a max tx limit and add two VFs to it:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Same scenario, with a min guarantee of 20% of the bandwidth for the first VF:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 tx_share 2Gbit + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Cross-device scheduling:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.1/32769 parent pci/0000:82:00.0/group1 diff --git a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst index 6dbd97d435e9..3dc03bff739e 100644 --- a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst +++ b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst @@ -22,6 +22,7 @@ struct_mutex ra_mutex struct_fib_rules_ops* rules_ops struct_fib_table fib_main struct_fib_table fib_default +spinlock_t fib_table_hash_lock unsigned_int fib_rules_require_fldissect bool fib_has_custom_rules bool fib_has_custom_local_routes diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst index d2a238f8cc8b..db71d4283032 100644 --- a/Documentation/networking/netdevices.rst +++ b/Documentation/networking/netdevices.rst @@ -421,10 +421,17 @@ running under the lock: * ``NETDEV_CHANGENAME`` * ``NETDEV_REGISTER`` * ``NETDEV_UP`` +* ``NETDEV_DOWN`` +* ``NETDEV_GOING_DOWN`` The following notifiers are running without the lock: * ``NETDEV_UNREGISTER`` +Many SW devices (uppers) catch their lower's ``NETDEV_UNREGISTER`` +events and may interact with them via ``dev_*()`` handlers, which take +the instance lock. Until we convert these devices to ``netif_*()`` variants, +``NETDEV_UNREGISTER`` stays unlocked. + There are no clear expectations for the remaining notifiers. Notifiers not on the list may run with or without the instance lock, potentially even invoking the same notifier type with and without the lock from different code paths. diff --git a/MAINTAINERS b/MAINTAINERS index 0c7e67220b6e..90bcdb8342ef 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9502,11 +9502,6 @@ L: linux-fbdev@vger.kernel.org S: Maintained F: drivers/video/fbdev/efifb.c -EHEA (IBM pSeries eHEA 10Gb ethernet adapter) DRIVER -L: netdev@vger.kernel.org -S: Orphan -F: drivers/net/ethernet/ibm/ehea/ - ELM327 CAN NETWORK DRIVER M: Max Staudt <max@enpas.org> L: linux-can@vger.kernel.org diff --git a/arch/powerpc/configs/ppc64_defconfig b/arch/powerpc/configs/ppc64_defconfig index f795b74602ec..1eb8e3457e8b 100644 --- a/arch/powerpc/configs/ppc64_defconfig +++ b/arch/powerpc/configs/ppc64_defconfig @@ -210,7 +210,6 @@ CONFIG_BNX2X=m CONFIG_CHELSIO_T1=m CONFIG_BE2NET=m CONFIG_IBMVETH=m -CONFIG_EHEA=m CONFIG_IBMVNIC=m CONFIG_E100=y CONFIG_E1000=y diff --git a/arch/powerpc/mm/mem.c b/arch/powerpc/mm/mem.c index 4c1afab91996..b617b69452cd 100644 --- a/arch/powerpc/mm/mem.c +++ b/arch/powerpc/mm/mem.c @@ -371,12 +371,6 @@ int devmem_is_allowed(unsigned long pfn) } #endif /* CONFIG_STRICT_DEVMEM */ -/* - * This is defined in kernel/resource.c but only powerpc needs to export it, for - * the EHEA driver. Drop this when drivers/net/ethernet/ibm/ehea is removed. - */ -EXPORT_SYMBOL_GPL(walk_system_ram_range); - #ifdef CONFIG_EXECMEM static struct execmem_info execmem_info __ro_after_init; diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index 07555ae63859..f7cebb026552 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -101,6 +101,8 @@ void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr, struct dentry *cxl_debugfs_create_dir(const char *dir); int cxl_dpa_set_part(struct cxl_endpoint_decoder *cxled, enum cxl_partition_mode mode); +struct cxl_memdev_state; +int cxl_mem_get_partition_info(struct cxl_memdev_state *mds); int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size); int cxl_dpa_free(struct cxl_endpoint_decoder *cxled); resource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled); diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index 7c6c5b7450a5..97b1e61ad018 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -1152,7 +1152,7 @@ EXPORT_SYMBOL_NS_GPL(cxl_mem_get_event_records, "CXL"); * * See CXL @8.2.9.5.2.1 Get Partition Info */ -static int cxl_mem_get_partition_info(struct cxl_memdev_state *mds) +int cxl_mem_get_partition_info(struct cxl_memdev_state *mds) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; struct cxl_mbox_get_partition_info pi; @@ -1308,55 +1308,6 @@ int cxl_mem_sanitize(struct cxl_memdev *cxlmd, u16 cmd) return -EBUSY; } -static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_partition_mode mode) -{ - int i = info->nr_partitions; - - if (size == 0) - return; - - info->part[i].range = (struct range) { - .start = start, - .end = start + size - 1, - }; - info->part[i].mode = mode; - info->nr_partitions++; -} - -int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) -{ - struct cxl_dev_state *cxlds = &mds->cxlds; - struct device *dev = cxlds->dev; - int rc; - - if (!cxlds->media_ready) { - info->size = 0; - return 0; - } - - info->size = mds->total_bytes; - - if (mds->partition_align_bytes == 0) { - add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM); - add_part(info, mds->volatile_only_bytes, - mds->persistent_only_bytes, CXL_PARTMODE_PMEM); - return 0; - } - - rc = cxl_mem_get_partition_info(mds); - if (rc) { - dev_err(dev, "Failed to query partition information\n"); - return rc; - } - - add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM); - add_part(info, mds->active_volatile_bytes, mds->active_persistent_bytes, - CXL_PARTMODE_PMEM); - - return 0; -} -EXPORT_SYMBOL_NS_GPL(cxl_mem_dpa_fetch, "CXL"); - int cxl_get_dirty_count(struct cxl_memdev_state *mds, u32 *count) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index 33a3d2e7b13a..2e457b1ebc7d 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -594,6 +594,73 @@ bool is_cxl_memdev(const struct device *dev) } EXPORT_SYMBOL_NS_GPL(is_cxl_memdev, "CXL"); +static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_partition_mode mode) +{ + int i = info->nr_partitions; + + if (size == 0) + return; + + info->part[i].range = (struct range) { + .start = start, + .end = start + size - 1, + }; + info->part[i].mode = mode; + info->nr_partitions++; +} + +int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) +{ + struct cxl_dev_state *cxlds = &mds->cxlds; + struct device *dev = cxlds->dev; + int rc; + + if (!cxlds->media_ready) { + info->size = 0; + return 0; + } + + info->size = mds->total_bytes; + + if (mds->partition_align_bytes == 0) { + add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM); + add_part(info, mds->volatile_only_bytes, + mds->persistent_only_bytes, CXL_PARTMODE_PMEM); + return 0; + } + + rc = cxl_mem_get_partition_info(mds); + if (rc) { + dev_err(dev, "Failed to query partition information\n"); + return rc; + } + + add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM); + add_part(info, mds->active_volatile_bytes, mds->active_persistent_bytes, + CXL_PARTMODE_PMEM); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cxl_mem_dpa_fetch, "CXL"); + + +/** + * cxl_set_capacity: initialize dpa by a driver without a mailbox. + * + * @cxlds: pointer to cxl_dev_state + * @capacity: device volatile memory size + */ +int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity) +{ + struct cxl_dpa_info range_info = { + .size = capacity, + }; + + add_part(&range_info, 0, capacity, CXL_PARTMODE_RAM); + return cxl_dpa_setup(cxlds, &range_info); +} +EXPORT_SYMBOL_NS_GPL(cxl_set_capacity, "CXL"); + /** * set_exclusive_cxl_commands() - atomically disable user cxl commands * @mds: The device state to operate on diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c index e4338fd7e01b..9d807c1a002c 100644 --- a/drivers/cxl/core/pci.c +++ b/drivers/cxl/core/pci.c @@ -6,6 +6,7 @@ #include <linux/delay.h> #include <linux/pci.h> #include <linux/pci-doe.h> +#include <cxl/pci.h> #include <linux/aer.h> #include <cxlpci.h> #include <cxlmem.h> diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index 1215ee4f4035..cb633e19151b 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -11,6 +11,7 @@ #include <linux/idr.h> #include <linux/node.h> #include <cxl/einj.h> +#include <cxl/pci.h> #include <cxlmem.h> #include <cxlpci.h> #include <cxl.h> diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c index 93710cf4f0a6..20c2d9fbcfe7 100644 --- a/drivers/cxl/core/regs.c +++ b/drivers/cxl/core/regs.c @@ -4,6 +4,7 @@ #include <linux/device.h> #include <linux/slab.h> #include <linux/pci.h> +#include <cxl/pci.h> #include <cxlmem.h> #include <cxlpci.h> #include <pmu.h> diff --git a/drivers/cxl/cxlpci.h b/drivers/cxl/cxlpci.h index b826eb53cf7b..110ec9c44f09 100644 --- a/drivers/cxl/cxlpci.h +++ b/drivers/cxl/cxlpci.h @@ -13,16 +13,6 @@ */ #define CXL_PCI_DEFAULT_MAX_VECTORS 16 -/* Register Block Identifier (RBI) */ -enum cxl_regloc_type { - CXL_REGLOC_RBI_EMPTY = 0, - CXL_REGLOC_RBI_COMPONENT, - CXL_REGLOC_RBI_VIRT, - CXL_REGLOC_RBI_MEMDEV, - CXL_REGLOC_RBI_PMU, - CXL_REGLOC_RBI_TYPES -}; - /* * Table Access DOE, CDAT Read Entry Response * @@ -112,6 +102,4 @@ static inline void devm_cxl_port_ras_setup(struct cxl_port *port) } #endif -int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, - struct cxl_register_map *map); #endif /* __CXL_PCI_H__ */ diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index 267c679b0b3c..bb892dbfdd6d 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -11,6 +11,7 @@ #include <linux/pci.h> #include <linux/aer.h> #include <linux/io.h> +#include <cxl/pci.h> #include <cxl/mailbox.h> #include "cxlmem.h" #include "cxlpci.h" diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 2e8690cb3c16..bb1e8650c9d5 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -884,7 +884,11 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, WARN_ON(ops->measured_freq_get && (!dpll_device_ops(dpll)->freq_monitor_get || !dpll_device_ops(dpll)->freq_monitor_set)) || - WARN_ON(ops->supported_ffo && !ops->ffo_get)) + WARN_ON(ops->supported_ffo && !ops->ffo_get) || + WARN_ON((pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) && + !(pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE))) return -EINVAL; mutex_lock(&dpll_lock); diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index ed3bbe9841ea..b1ba490e72b0 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] = [DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, }, - [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5), + [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6), }; /* DPLL_CMD_PIN_GET - do */ diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c index 2fe3c3da84bb..4ec2cf53dad4 100644 --- a/drivers/dpll/zl3073x/chan.c +++ b/drivers/dpll/zl3073x/chan.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include <linux/cleanup.h> +#include <linux/delay.h> #include <linux/dev_printk.h> #include <linux/string.h> #include <linux/types.h> @@ -31,9 +32,18 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; - /* Read df_offset vs tracked reference */ + /* Read df_offset only when locked to a reference. In NCO mode + * df_offset was captured at entry by nco_mode_set() - preserve it. + */ + if (!zl3073x_chan_is_locked(chan)) { + if (!zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; @@ -43,7 +53,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) return rc; rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; @@ -57,6 +68,96 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) } /** + * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index + * + * Switches the channel to NCO mode and reads the df_offset + * auto-captured by nco_auto_read directly from the register. + * No DF_READ handshake is needed as nco_auto_read populates + * the register before the mode switch completes. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_chan *chan = &zldev->chan[index]; + u8 prev_mode, df_read; + u64 val; + int rc; + + prev_mode = zl3073x_chan_mode_get(chan); + + /* nco_auto_read captures the tracking offset at NCO entry only + * from reflock, auto or holdover mode. From freerun the captured + * value is not meaningful. + */ + if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) { + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + + /* Configure df_read for nco_auto_read: + * ref_ofst=0 - reads offset relative to master clock (not input ref) + * cmd=CMD_ACC_I - accumulated I-part covering both locked and + * holdover entry. + * + * No semaphore is set - this only configures what the df_offset + * value represents after the mode switch; nco_auto_read performs + * the actual read automatically. + */ + df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) | + FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read); + if (rc) + return rc; + + /* Wait for df_read configuration to take effect before + * triggering nco_auto_read via mode switch. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + /* Wait for nco_auto_read to populate df_offset. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + /* Read df_offset captured by nco_auto_read during mode switch. + * No DF_READ semaphore handshake needed. Mode switch already + * succeeded, so don't propagate a read failure back to userspace. + */ + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val); + if (rc) { + dev_warn(zldev->dev, + "Failed to read DPLL%u df_offset: %pe\n", + index, ERR_PTR(rc)); + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + } else { + chan->df_offset = sign_extend64(val, 47); + } + + return 0; +} + +/** * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware * @zldev: pointer to zl3073x_dev structure * @index: DPLL channel index to fetch state for @@ -71,6 +172,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) struct zl3073x_chan *chan = &zldev->chan[index]; int rc, i; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl); + if (rc) + return rc; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), &chan->mode_refsel); if (rc) @@ -83,6 +188,13 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; + /* If firmware left the channel in NCO mode, mark df_offset as + * unknown - we cannot know whether the preconditions for a valid + * nco_auto_read capture were met. + */ + if (zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + dev_dbg(zldev->dev, "DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n", index, zl3073x_chan_lock_state_get(chan), @@ -145,7 +257,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg))) return 0; - /* Direct register write for mode_refsel */ + /* Direct register writes for ctrl and mode_refsel */ + if (dchan->ctrl != chan->ctrl) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index), + chan->ctrl); + if (rc) + return rc; + dchan->ctrl = chan->ctrl; + } + if (dchan->mode_refsel != chan->mode_refsel) { rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), chan->mode_refsel); diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h index 4353809c6912..dc9c6d95bdee 100644 --- a/drivers/dpll/zl3073x/chan.h +++ b/drivers/dpll/zl3073x/chan.h @@ -13,6 +13,7 @@ struct zl3073x_dev; /** * struct zl3073x_chan - DPLL channel state + * @ctrl: DPLL control register value * @mode_refsel: mode and reference selection register value * @ref_prio: reference priority registers (4 bits per ref, P/N packed) * @mon_status: monitor status register value @@ -21,6 +22,7 @@ struct zl3073x_dev; */ struct zl3073x_chan { struct_group(cfg, + u8 ctrl; u8 mode_refsel; u8 ref_prio[ZL3073X_NUM_REFS / 2]; ); @@ -38,6 +40,7 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_chan *chan); int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index); +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index); /** * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference @@ -153,6 +156,51 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan) } /** + * zl3073x_chan_is_locked - check if channel is locked to a reference + * @chan: pointer to channel state + * + * Return: true if channel is locked, false otherwise + */ +static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan) +{ + u8 lock_state = zl3073x_chan_lock_state_get(chan); + return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK; +} + +/** + * zl3073x_chan_mode_is_auto - check if channel is in automatic mode + * @chan: pointer to channel state + * + * Return: true if channel is in automatic mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO; +} + +/** + * zl3073x_chan_mode_is_nco - check if channel is in NCO mode + * @chan: pointer to channel state + * + * Return: true if channel is in NCO mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO; +} + +/** + * zl3073x_chan_mode_is_reflock - check if channel is in reflock mode + * @chan: pointer to channel state + * + * Return: true if channel is in reflock mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; +} + +/** * zl3073x_chan_is_ho_ready - check if holdover is ready * @chan: pointer to channel state * diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 8e6416a4741d..7f5afaaae634 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -311,17 +311,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val) * @zldev: zl3073x device pointer * @reg: register to poll (has to be 8bit register) * @mask: bit mask for polling + * @timeout_us: timeout in microseconds * * Waits for bits specified by @mask in register @reg value to be cleared * by the device. * * Returns: 0 on success, <0 on error */ -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us) { - /* Register polling sleep & timeout */ -#define ZL_POLL_SLEEP_US 10 -#define ZL_POLL_TIMEOUT_US 2000000 +#define ZL_POLL_SLEEP_US 10 unsigned int val; /* Check the register is 8bit */ @@ -335,7 +335,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask), - ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US); + ZL_POLL_SLEEP_US, timeout_us); } int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, @@ -354,7 +354,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, return rc; /* Wait for the operation to actually finish */ - return zl3073x_poll_zero_u8(zldev, op_reg, op_val); + return zl3073x_poll_zero_u8(zldev, op_reg, op_val, + ZL_POLL_MB_TIMEOUT_US); } /** @@ -377,8 +378,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op) return rc; /* Poll for completion - pending bit cleared */ - return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, - ZL_HWREG_OP_PENDING); + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING, + ZL_POLL_HWREG_TIMEOUT_US); } /** @@ -566,19 +567,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev) } } -static void -zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev) -{ - int i, rc; - for (i = 0; i < zldev->info->num_channels; i++) { - rc = zl3073x_chan_state_update(zldev, i); - if (rc) - dev_warn(zldev->dev, - "Failed to get DPLL%u state: %pe\n", i, - ERR_PTR(rc)); - } -} /** * zl3073x_ref_phase_offsets_update - update reference phase offsets @@ -609,7 +598,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) * to be zero to ensure that the measured data are coherent. */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); if (rc) return rc; @@ -628,7 +618,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); } /** @@ -648,7 +639,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for previous measurement to finish */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); if (rc) return rc; @@ -669,7 +661,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); } /** @@ -715,9 +708,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update input references' states */ zl3073x_dev_ref_states_update(zldev); - /* Update DPLL channels' states */ - zl3073x_dev_chan_states_update(zldev); - /* Update DPLL-to-connected-ref phase offsets registers */ rc = zl3073x_ref_phase_offsets_update(zldev, -1); if (rc) @@ -727,7 +717,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update measured input reference frequencies if frequency * monitoring is enabled. */ - if (zldev->freq_monitor) { + if (READ_ONCE(zldev->freq_monitor)) { rc = zl3073x_ref_freq_meas_update(zldev); if (rc) dev_warn(zldev->dev, @@ -763,7 +753,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor) return rc; /* Save the new factor */ - zldev->phase_avg_factor = factor; + WRITE_ONCE(zldev->phase_avg_factor, factor); return 0; } diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index addba378b0df..78dc208f3eea 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -7,6 +7,7 @@ #include <linux/kthread.h> #include <linux/list.h> #include <linux/mutex.h> +#include <linux/time64.h> #include <linux/types.h> #include "chan.h" @@ -19,6 +20,12 @@ struct device; struct regmap; struct zl3073x_dpll; +/* Per-operation poll timeouts */ +#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC) +#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC) enum zl3073x_flags { ZL3073X_FLAG_REF_PHASE_COMP_32_BIT, @@ -94,7 +101,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev); static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev) { - return zldev->phase_avg_factor; + return READ_ONCE(zldev->phase_avg_factor); } int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor); @@ -127,7 +134,8 @@ struct zl3073x_hwreg_seq_item { int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, unsigned int mask_reg, u16 mask_val); -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask); +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us); int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val); int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val); int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val); diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 4ab045e85a89..a2a641b8358f 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -#include <linux/atomic.h> #include <linux/bits.h> #include <linux/bitfield.h> #include <linux/bug.h> @@ -57,7 +56,7 @@ struct zl3073x_dpll_pin { s32 phase_gran; enum dpll_pin_operstate operstate; s64 phase_offset; - atomic64_t freq_offset; + s64 freq_offset; u32 measured_freq; }; @@ -81,6 +80,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin) } /** + * zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin + * @pin: pin to check + * + * Return: true if pin is a virtual NCO pin, false otherwise. + */ +static bool +zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->id == ZL3073X_NCO_PIN_ID; +} + +/** * zl3073x_dpll_is_p_pin - check if the pin is P-pin * @pin: pin to check * @@ -119,6 +130,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) return NULL; } +static struct zl3073x_dpll_pin * +zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_nco_pin(pin)) + return pin; + } + + return NULL; +} + static int zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, void *pin_priv, @@ -133,6 +157,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -169,6 +195,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id, sync_mode; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -204,6 +232,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id, mode, pair; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); mode = zl3073x_ref_sync_mode_get(ref); @@ -235,6 +265,8 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; int rc; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -298,12 +330,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, struct dpll_ffo_param *ffo, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return -ENODATA; - ffo->ffo = atomic64_read(&pin->freq_offset); + ffo->ffo = pin->freq_offset; return 0; } @@ -315,8 +350,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin, void *dpll_priv, u64 *measured_freq, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *measured_freq = pin->measured_freq; *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER; @@ -334,6 +372,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll_pin *pin = pin_priv; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); *frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id); @@ -353,6 +393,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Get reference state */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -401,6 +443,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, u8 conn_id, ref_id; s64 ref_phase; + guard(mutex)(&zldpll->lock); + /* Get currently connected reference */ conn_id = zl3073x_dpll_connected_ref_get(zldpll); @@ -458,6 +502,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, s64 phase_comp; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -490,6 +536,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -523,6 +571,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin, const struct zl3073x_ref *ref; u8 ref_id; + lockdep_assert_held(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); /* Check if this pin is the currently locked reference */ @@ -556,6 +606,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, const struct zl3073x_chan *chan; u8 mode, ref; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); mode = zl3073x_chan_mode_get(chan); @@ -589,8 +641,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, enum dpll_pin_operstate *operstate, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + return zl3073x_dpll_ref_operstate_get(pin, operstate); } @@ -604,9 +659,12 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, { struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 mode, ref; - int rc; + int rc = 0; + + mutex_lock(&zldpll->lock); chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); @@ -633,6 +691,10 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, goto invalid_state; } break; + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + if (state == DPLL_PIN_STATE_CONNECTED) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + fallthrough; case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: if (state == DPLL_PIN_STATE_CONNECTED) { @@ -648,13 +710,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, case ZL_DPLL_MODE_REFSEL_MODE_AUTO: if (state == DPLL_PIN_STATE_SELECTABLE) { if (zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already selectable */ + goto unlock; /* Pin is already selectable */ /* Restore pin priority in HW */ zl3073x_chan_ref_prio_set(&chan, ref, pin->prio); } else if (state == DPLL_PIN_STATE_DISCONNECTED) { if (!zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already disconnected */ + goto unlock; /* Pin is already disconnected */ /* Set pin priority to none in HW */ zl3073x_chan_ref_prio_set(&chan, ref, @@ -667,18 +729,27 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, /* In other modes we cannot change input reference */ NL_SET_ERR_MSG(extack, "Pin state cannot be changed in current mode"); - return -EOPNOTSUPP; + rc = -EOPNOTSUPP; + goto unlock; } /* Commit DPLL channel changes */ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); - if (rc) - return rc; + goto unlock; - return 0; invalid_state: NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode"); - return -EINVAL; + rc = -EINVAL; +unlock: + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (!rc && nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + + return rc; } static int @@ -686,8 +757,11 @@ zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u32 *prio, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *prio = pin->prio; return 0; @@ -704,6 +778,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv, u8 ref; int rc; + guard(mutex)(&zldpll->lock); + if (prio > ZL_DPLL_REF_PRIO_MAX) return -EINVAL; @@ -739,6 +815,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, u32 synth_freq, out_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -796,6 +874,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, u32 synth_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -816,7 +896,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, /* If esync is being disabled just write mailbox and finish */ if (!freq) - goto write_mailbox; + return zl3073x_out_state_set(zldev, out_id, &out); /* Get attached synth frequency */ synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); @@ -833,7 +913,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, */ out.esync_n_width = out.div / 2; -write_mailbox: /* Commit output configuration */ return zl3073x_out_state_set(zldev, out_id, &out); } @@ -848,6 +927,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id); return 0; @@ -868,6 +949,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -941,6 +1024,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, const struct zl3073x_out *out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -964,6 +1049,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -989,6 +1076,144 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, } static int +zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *operstate, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *operstate = DPLL_PIN_OPERSTATE_ACTIVE; + else + *operstate = DPLL_PIN_OPERSTATE_STANDBY; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *ref_pin = NULL; + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_chan chan; + u8 ref; + int rc; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + if (zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + if (zl3073x_chan_mode_is_auto(&chan)) { + NL_SET_ERR_MSG(extack, + "NCO pin cannot be connected in automatic mode"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + if (zl3073x_chan_mode_is_reflock(&chan)) { + /* Get currently connected pin */ + ref = zl3073x_chan_ref_get(&chan); + ref_pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); + } + rc = zl3073x_chan_nco_mode_set(zldpll->dev, zldpll->id); + break; + case DPLL_PIN_STATE_DISCONNECTED: + if (!zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + /* Switch to freerun - holdover averaging was not + * updated during NCO mode. + */ + zl3073x_chan_mode_set(&chan, + ZL_DPLL_MODE_REFSEL_MODE_FREERUN); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + break; + default: + NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + + mutex_unlock(&zldpll->lock); + + if (!rc && ref_pin) + __dpll_pin_change_ntf(ref_pin->dpll_pin); + + return rc; +} + +static int +zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + s64 df_offset; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return -ENODATA; + + /* Do not report FFO if a failure occurred during switching to NCO. */ + df_offset = zl3073x_chan_df_offset_get(chan); + if (df_offset == ZL_DPLL_DF_OFFSET_UNKNOWN) + return -ENODATA; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * NCO pin reports DPLL output offset from nominal, so negate. + * Convert to PPT: ppt = -df * 5^12 / 2^36 + */ + ffo->ffo = -mul_s64_u64_shr(df_offset, 244140625, 36); + + return 0; +} + +static int zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, s32 *temp, struct netlink_ext_ack *extack) { @@ -997,6 +1222,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, u16 val; int rc; + guard(mutex)(&zldpll->lock); + rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val); if (rc) return rc; @@ -1008,14 +1235,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, } static int -zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, - enum dpll_lock_status *status, - enum dpll_lock_status_error *status_error, - struct netlink_ext_ack *extack) +__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll, + enum dpll_lock_status *status) { - struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + lockdep_assert_held(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1052,23 +1278,24 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, } static int -zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, - void *dpll_priv, unsigned long *modes, - struct netlink_ext_ack *extack) +zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_lock_status *status, + enum dpll_lock_status_error *status_error, + struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; - const struct zl3073x_chan *chan; - chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + guard(mutex)(&zldpll->lock); - /* We support switching between automatic and manual mode, except in - * a case where the DPLL channel is configured to run in NCO mode. - * In this case, report only the manual mode to which the NCO is mapped - * as the only supported one. - */ - if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO) - __set_bit(DPLL_MODE_AUTOMATIC, modes); + return __zl3073x_dpll_lock_status_get(zldpll, status); +} +static int +zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, + void *dpll_priv, unsigned long *modes, + struct netlink_ext_ack *extack) +{ + __set_bit(DPLL_MODE_AUTOMATIC, modes); __set_bit(DPLL_MODE_MANUAL, modes); return 0; @@ -1081,6 +1308,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1137,8 +1366,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll, return rc; } - /* The averaging factor is common for all DPLL channels so after change - * we have to send a notification for other DPLL devices. + /* The averaging factor is common for all DPLL channels so after + * change we have to send a notification for other DPLL devices. */ list_for_each_entry(item, &zldpll->dev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); @@ -1155,10 +1384,13 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, enum dpll_mode mode, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 hw_mode, ref; int rc; + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_chan_refsel_ref_get(&chan); @@ -1178,6 +1410,9 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, else hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; } else { + if (zl3073x_chan_mode_is_nco(&chan)) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + /* We are switching from manual to automatic mode: * - if there is a valid reference selected then ensure that * it is selectable after switch to automatic mode @@ -1206,9 +1441,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, if (rc) { NL_SET_ERR_MSG_MOD(extack, "failed to set reference selection mode"); + mutex_unlock(&zldpll->lock); return rc; } + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + return 0; } @@ -1220,6 +1464,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + if (zldpll->phase_monitor) *state = DPLL_FEATURE_STATE_ENABLE; else @@ -1236,6 +1482,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); return 0; @@ -1249,7 +1497,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; - if (zldpll->dev->freq_monitor) + if (READ_ONCE(zldpll->dev->freq_monitor)) *state = DPLL_FEATURE_STATE_ENABLE; else *state = DPLL_FEATURE_STATE_DISABLE; @@ -1264,13 +1512,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, struct netlink_ext_ack *extack) { struct zl3073x_dpll *item, *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; - zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE); /* The frequency monitoring is common for all DPLL channels so after * change we have to send a notification for other DPLL devices. */ - list_for_each_entry(item, &zldpll->dev->dplls, list) { + list_for_each_entry(item, &zldev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); if (item != zldpll && dpll_dev) @@ -1312,6 +1561,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = { .state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get, }; +static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE), + .direction_get = zl3073x_dpll_pin_direction_get, + .ffo_get = zl3073x_dpll_nco_pin_ffo_get, + .operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get, + .state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get, + .state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set, +}; + static const struct dpll_device_ops zl3073x_dpll_device_ops = { .lock_status_get = zl3073x_dpll_lock_status_get, .mode_get = zl3073x_dpll_mode_get, @@ -1459,7 +1717,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) WARN(!pin->dpll_pin, "DPLL pin is not registered\n"); - if (zl3073x_dpll_is_input_pin(pin)) + if (zl3073x_dpll_is_nco_pin(pin)) + ops = &zl3073x_dpll_nco_pin_ops; + else if (zl3073x_dpll_is_input_pin(pin)) ops = &zl3073x_dpll_input_pin_ops; else ops = &zl3073x_dpll_output_pin_ops; @@ -1512,20 +1772,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, u8 index) { struct zl3073x_dev *zldev = zldpll->dev; - const struct zl3073x_chan *chan; bool is_diff, is_enabled; const char *name; - chan = zl3073x_chan_state_get(zldev, zldpll->id); - if (dir == DPLL_PIN_DIRECTION_INPUT) { u8 ref_id = zl3073x_input_pin_ref_get(index); const struct zl3073x_ref *ref; - /* Skip the pin if the DPLL is running in NCO mode */ - if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO) - return false; - name = "REF"; ref = zl3073x_ref_state_get(zldev, ref_id); is_diff = zl3073x_ref_is_diff(ref); @@ -1566,6 +1819,66 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, return true; } +static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = { + .type = DPLL_PIN_TYPE_INT_NCO, + .package_label = "NCO", + .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE, +}; + +static int +zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + struct zl3073x_chan chan; + int rc; + + /* Ensure that ctrl bits are configured for NCO operation: + * - nco_auto_read: auto-capture tracking offset on NCO entry + * - tod_step_reset: apply negated ToD step on NCO exit + * - tie_clear: PPS DPLLs re-align outputs on NCO exit + */ + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl, + zldpll->type == DPLL_TYPE_PPS ? 1 : 0); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + mutex_unlock(&zldpll->lock); + if (rc) + return rc; + + pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT, + ZL3073X_NCO_PIN_ID); + if (IS_ERR(pin)) + return PTR_ERR(pin); + + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID, + THIS_MODULE, &zl3073x_dpll_nco_pin_props, + &pin->tracker); + if (IS_ERR(pin->dpll_pin)) { + rc = PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, + &zl3073x_dpll_nco_pin_ops, pin); + if (rc) + goto err_register; + + list_add(&pin->list, &zldpll->pins); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + pin->dpll_pin = NULL; + kfree(pin); + + return rc; +} + /** * zl3073x_dpll_pins_register - register all registerable DPLL pins * @zldpll: pointer to zl3073x_dpll structure @@ -1613,6 +1926,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) list_add(&pin->list, &zldpll->pins); } + /* Register NCO virtual input pin */ + rc = zl3073x_dpll_nco_pin_register(zldpll); + if (rc) + goto error; + return 0; error: @@ -1648,8 +1966,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) return rc; } - rc = dpll_device_register(zldpll->dpll_dev, - zl3073x_prop_dpll_type_get(zldev, zldpll->id), + zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id); + rc = dpll_device_register(zldpll->dpll_dev, zldpll->type, &zldpll->ops, zldpll); if (rc) { dpll_device_put(zldpll->dpll_dev, &zldpll->tracker); @@ -1696,6 +2014,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin) u8 ref_id; int rc; + lockdep_assert_held(&zldpll->lock); + /* No phase offset if the ref monitor reports signal errors */ ref_id = zl3073x_input_pin_ref_get(pin->id); if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id)) @@ -1752,16 +2072,27 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) const struct zl3073x_chan *chan; s64 ffo; + lockdep_assert_held(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return false; chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_df_offset_get(chan) == ZL_DPLL_DF_OFFSET_UNKNOWN) + return false; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * Input pin FFO is pin-vs-DPLL (opposite of DPLL-vs-reference), + * so the two inversions cancel out: ppt = df * 5^12 / 2^36 + */ ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan), 244140625, 36); - if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) { + if (pin->freq_offset != ffo) { dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n", pin->label, ffo); + pin->freq_offset = ffo; return true; } @@ -1786,7 +2117,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) u8 ref_id; u32 freq; - if (!zldpll->dev->freq_monitor) + lockdep_assert_held(&zldpll->lock); + + if (!READ_ONCE(zldpll->dev->freq_monitor)) return false; ref_id = zl3073x_input_pin_ref_get(pin->id); @@ -1816,27 +2149,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) void zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) { + DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS); struct zl3073x_dev *zldev = zldpll->dev; enum dpll_lock_status lock_status; struct device *dev = zldev->dev; struct zl3073x_dpll_pin *pin; + bool dev_changed = false; int rc; + bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS); + + mutex_lock(&zldpll->lock); + zldpll->check_count++; - /* Get current lock status for the DPLL */ - rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll, - &lock_status, NULL, NULL); + rc = zl3073x_chan_state_update(zldev, zldpll->id); + if (rc) { + dev_err(dev, "Failed to get DPLL%u state: %pe\n", + zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status); if (rc) { dev_err(dev, "Failed to get DPLL%u lock status: %pe\n", zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } - /* If lock status was changed then notify DPLL core */ if (zldpll->lock_status != lock_status) { zldpll->lock_status = lock_status; - dpll_device_change_ntf(zldpll->dpll_dev); + dev_changed = true; } /* Update phase offset latch registers for this DPLL if the phase @@ -1848,18 +2191,16 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(zldev->dev, "Failed to update phase offsets: %pe\n", ERR_PTR(rc)); - return; + goto unlock; } } list_for_each_entry(pin, &zldpll->pins, list) { enum dpll_pin_operstate operstate; - bool pin_changed = false; - /* Output pins change checks are not necessary because output - * states are constant. - */ - if (!zl3073x_dpll_is_input_pin(pin)) + /* Only physical input pins need monitoring */ + if (!zl3073x_dpll_is_input_pin(pin) || + zl3073x_dpll_is_nco_pin(pin)) continue; rc = zl3073x_dpll_ref_operstate_get(pin, &operstate); @@ -1867,31 +2208,41 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(dev, "Failed to get %s on DPLL%u oper state: %pe\n", pin->label, zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } if (operstate != pin->operstate) { dev_dbg(dev, "%s oper state changed: %u->%u\n", pin->label, pin->operstate, operstate); pin->operstate = operstate; - pin_changed = true; + set_bit(pin->id, changed_pins); } - /* Check for phase offset, ffo, and measured freq change - * once per second. - */ if (zldpll->check_count % 2 == 0) { if (zl3073x_dpll_pin_phase_offset_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_ffo_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_measured_freq_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); } + } + +unlock: + mutex_unlock(&zldpll->lock); - if (pin_changed) + /* Send notifications outside the lock to avoid ABBA deadlock + * with dpll_lock taken by notification functions. + */ + if (dev_changed) + dpll_device_change_ntf(zldpll->dpll_dev); + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_input_pin(pin) && + !zl3073x_dpll_is_nco_pin(pin) && + test_bit(pin->id, changed_pins)) dpll_pin_change_ntf(pin->dpll_pin); } } @@ -1948,6 +2299,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) zldpll->dev = zldev; zldpll->id = ch; + mutex_init(&zldpll->lock); INIT_LIST_HEAD(&zldpll->pins); return zldpll; @@ -1964,6 +2316,7 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll) { WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + mutex_destroy(&zldpll->lock); kfree(zldpll); } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index 21adcc18e45e..faebc402ba1b 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -18,6 +18,8 @@ * @ops: DPLL device operations for this instance * @dpll_dev: pointer to registered DPLL device * @tracker: tracking object for the acquired reference + * @lock: per-DPLL mutex serializing all operations + * @type: DPLL type (PPS or EEC) * @lock_status: last saved DPLL lock status * @pins: list of pins */ @@ -30,6 +32,8 @@ struct zl3073x_dpll { struct dpll_device_ops ops; struct dpll_device *dpll_dev; dpll_tracker tracker; + struct mutex lock; + enum dpll_type type; enum dpll_lock_status lock_status; struct list_head pins; }; diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h index 9578f0009528..b70ead7d4495 100644 --- a/drivers/dpll/zl3073x/regs.h +++ b/drivers/dpll/zl3073x/regs.h @@ -5,6 +5,7 @@ #include <linux/bitfield.h> #include <linux/bits.h> +#include <linux/limits.h> /* * Hardware limits for ZL3073x chip family @@ -17,6 +18,7 @@ #define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2) #define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ ZL3073X_NUM_OUTPUT_PINS) +#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS /* * Register address structure: @@ -164,10 +166,18 @@ #define ZL_DPLL_MODE_REFSEL_MODE_NCO 4 #define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4) +#define ZL_REG_DPLL_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4) +#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0) +#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2) +#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7) + #define ZL_REG_DPLL_DF_READ(_idx) \ ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1) #define ZL_DPLL_DF_READ_SEM BIT(4) #define ZL_DPLL_DF_READ_REF_OFST BIT(3) +#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0) +#define ZL_DPLL_DF_READ_CMD_ACC_I 4 #define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) #define ZL_DPLL_MEAS_CTRL_EN BIT(0) @@ -190,6 +200,7 @@ #define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6) #define ZL_REG_DPLL_DF_OFFSET(_idx) \ ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4) +#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN /*********************************** * Register Page 9, Synth and Output diff --git a/drivers/net/amt.c b/drivers/net/amt.c index 951dd10e192b..02168862f676 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -708,11 +708,13 @@ static void amt_send_request(struct amt_dev *amt, bool v6) struct iphdr *iph; struct rtable *rt; struct flowi4 fl4; + __be32 remote_ip; struct sock *sk; u32 len; int err; rcu_read_lock(); + remote_ip = READ_ONCE(amt->remote_ip); sk = rcu_dereference(amt->sk); if (!sk) goto out; @@ -721,7 +723,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) goto out; rt = ip_route_output_ports(amt->net, &fl4, sk, - amt->remote_ip, amt->local_ip, + remote_ip, amt->local_ip, amt->gw_port, amt->relay_port, IPPROTO_UDP, 0, amt->stream_dev->ifindex); @@ -762,7 +764,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) udph->check = 0; offset = skb_transport_offset(skb); skb->csum = skb_checksum(skb, offset, skb->len - offset, 0); - udph->check = csum_tcpudp_magic(amt->local_ip, amt->remote_ip, + udph->check = csum_tcpudp_magic(amt->local_ip, remote_ip, sizeof(*udph) + sizeof(*amtrh), IPPROTO_UDP, skb->csum); @@ -773,7 +775,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) iph->tos = AMT_TOS; iph->frag_off = 0; iph->ttl = ip4_dst_hoplimit(&rt->dst); - iph->daddr = amt->remote_ip; + iph->daddr = remote_ip; iph->saddr = amt->local_ip; iph->protocol = IPPROTO_UDP; iph->tot_len = htons(len); @@ -962,7 +964,7 @@ static void amt_event_send_request(struct amt_dev *amt) amt->qi = AMT_INIT_REQ_TIMEOUT; WRITE_ONCE(amt->ready4, false); WRITE_ONCE(amt->ready6, false); - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt_update_gw_status(amt, AMT_STATUS_INIT, false); amt->req_cnt = 0; amt->nonce = 0; @@ -999,6 +1001,7 @@ static bool amt_send_membership_update(struct amt_dev *amt, struct sk_buff *skb, bool v6) { + __be32 remote_ip = READ_ONCE(amt->remote_ip); struct amt_header_membership_update *amtmu; struct iphdr *iph; struct flowi4 fl4; @@ -1018,13 +1021,13 @@ static bool amt_send_membership_update(struct amt_dev *amt, skb_reset_inner_headers(skb); memset(&fl4, 0, sizeof(struct flowi4)); fl4.flowi4_oif = amt->stream_dev->ifindex; - fl4.daddr = amt->remote_ip; + fl4.daddr = remote_ip; fl4.saddr = amt->local_ip; fl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS); fl4.flowi4_proto = IPPROTO_UDP; rt = ip_route_output_key(amt->net, &fl4); if (IS_ERR(rt)) { - netdev_dbg(amt->dev, "no route to %pI4\n", &amt->remote_ip); + netdev_dbg(amt->dev, "no route to %pI4\n", &remote_ip); return true; } @@ -2272,8 +2275,8 @@ static bool amt_advertisement_handler(struct amt_dev *amt, struct sk_buff *skb) amt->nonce != amta->nonce) return true; - amt->remote_ip = amta->ip4; - netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amt->remote_ip); + WRITE_ONCE(amt->remote_ip, amta->ip4); + netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amta->ip4); mod_delayed_work(amt_wq, &amt->req_wq, 0); amt_update_gw_status(amt, AMT_STATUS_RECEIVED_ADVERTISEMENT, true); @@ -2773,6 +2776,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) { struct amt_dev *amt; struct iphdr *iph; + __be32 remote_ip; int type; bool err; @@ -2783,6 +2787,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) kfree_skb(skb); goto out; } + remote_ip = READ_ONCE(amt->remote_ip); skb->dev = amt->dev; iph = ip_hdr(skb); @@ -2807,7 +2812,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) } goto out; case AMT_MSG_MULTICAST_DATA: - if (iph->saddr != amt->remote_ip) { + if (iph->saddr != remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -2818,7 +2823,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb) else goto out; case AMT_MSG_MEMBERSHIP_QUERY: - if (iph->saddr != amt->remote_ip) { + if (iph->saddr != remote_ip) { netdev_dbg(amt->dev, "Invalid Relay IP\n"); err = true; goto drop; @@ -3000,7 +3005,7 @@ static int amt_dev_open(struct net_device *dev) return err; amt->req_cnt = 0; - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt->nonce = 0; get_random_bytes(&amt->key, sizeof(siphash_key_t)); @@ -3045,7 +3050,7 @@ static int amt_dev_stop(struct net_device *dev) amt->ready4 = false; amt->ready6 = false; amt->req_cnt = 0; - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); list_for_each_entry_safe(tunnel, tmp, &amt->tunnel_list, list) { list_del_rcu(&tunnel->list); @@ -3244,7 +3249,7 @@ static int amt_newlink(struct net_device *dev, "gateway port must not be 0"); goto err; } - amt->remote_ip = 0; + WRITE_ONCE(amt->remote_ip, 0); amt->discovery_ip = nla_get_in_addr(data[IFLA_AMT_DISCOVERY_IP]); if (ipv4_is_loopback(amt->discovery_ip) || ipv4_is_zeronet(amt->discovery_ip) || @@ -3308,8 +3313,10 @@ static size_t amt_get_size(const struct net_device *dev) static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct amt_dev *amt = netdev_priv(dev); + const struct amt_dev *amt = netdev_priv(dev); + __be32 remote_ip; + rcu_read_lock(); if (nla_put_u32(skb, IFLA_AMT_MODE, amt->mode)) goto nla_put_failure; if (nla_put_be16(skb, IFLA_AMT_RELAY_PORT, amt->relay_port)) @@ -3322,15 +3329,19 @@ static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev) goto nla_put_failure; if (nla_put_in_addr(skb, IFLA_AMT_DISCOVERY_IP, amt->discovery_ip)) goto nla_put_failure; - if (amt->remote_ip) - if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, amt->remote_ip)) + + remote_ip = READ_ONCE(amt->remote_ip); + if (remote_ip) + if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, remote_ip)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_AMT_MAX_TUNNELS, amt->max_tunnels)) goto nla_put_failure; + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } diff --git a/drivers/net/bonding/bond_3ad.c b/drivers/net/bonding/bond_3ad.c index acbba08dbdfa..b8e4b4d68dd6 100644 --- a/drivers/net/bonding/bond_3ad.c +++ b/drivers/net/bonding/bond_3ad.c @@ -760,14 +760,14 @@ static int __agg_usable_ports(struct aggregator *agg) return valid; } -static int __agg_active_ports(struct aggregator *agg) +static int __agg_active_ports(const struct aggregator *agg) { - struct port *port; + const struct port *port; int active = 0; for (port = agg->lag_ports; port; port = port->next_port_in_aggregator) { - if (port->is_enabled) + if (READ_ONCE(port->is_enabled)) active++; } @@ -2801,11 +2801,11 @@ void bond_3ad_handle_link_change(struct slave *slave, char link) * some of he adaptors(ce1000.lan) report. */ if (link == BOND_LINK_UP) { - port->is_enabled = true; + WRITE_ONCE(port->is_enabled, true); ad_update_actor_keys(port, false); } else { /* link has failed */ - port->is_enabled = false; + WRITE_ONCE(port->is_enabled, false); ad_update_actor_keys(port, true); } agg = __get_first_agg(port); @@ -2878,16 +2878,20 @@ out: * Returns: 0 on success * < 0 on error */ -int __bond_3ad_get_active_agg_info(struct bonding *bond, +int __bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info) { - struct aggregator *aggregator = NULL, *tmp; + const struct aggregator *aggregator = NULL, *tmp; + struct ad_slave_info *ad_slave_info; + const struct port *port; struct list_head *iter; struct slave *slave; - struct port *port; bond_for_each_slave_rcu(bond, slave, iter) { - port = &(SLAVE_AD_INFO(slave)->port); + ad_slave_info = SLAVE_AD_INFO(slave); + if (!ad_slave_info) + continue; + port = &ad_slave_info->port; tmp = rcu_dereference(port->aggregator); if (tmp && tmp->is_active) { aggregator = tmp; @@ -2907,7 +2911,7 @@ int __bond_3ad_get_active_agg_info(struct bonding *bond, return 0; } -int bond_3ad_get_active_agg_info(struct bonding *bond, struct ad_info *ad_info) +int bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info) { int ret; diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index e044fc733b8c..e1bec8164221 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -4857,12 +4857,6 @@ static int bond_set_mac_address(struct net_device *bond_dev, void *addr) __func__, slave); res = dev_set_mac_address(slave->dev, addr, NULL); if (res) { - /* TODO: consider downing the slave - * and retry ? - * User should expect communications - * breakage anyway until ARP finish - * updating, so... - */ slave_dbg(bond_dev, slave->dev, "%s: err %d\n", __func__, res); goto unwind; diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 4a11572f663d..55d2f8a539d4 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -686,53 +686,58 @@ static size_t bond_get_size(const struct net_device *bond_dev) 0; } -static int bond_option_active_slave_get_ifindex(struct bonding *bond) +static int bond_option_active_slave_get_ifindex_rcu(const struct bonding *bond) { - const struct net_device *slave; - int ifindex; + const struct net_device *dev = NULL; + const struct slave *slave; - rcu_read_lock(); - slave = bond_option_active_slave_get_rcu(bond); - ifindex = slave ? slave->ifindex : 0; - rcu_read_unlock(); - return ifindex; + slave = rcu_dereference(bond->curr_active_slave); + if (slave) + dev = slave->dev; + return dev ? dev->ifindex : 0; } static int bond_fill_info(struct sk_buff *skb, const struct net_device *bond_dev) { - struct bonding *bond = netdev_priv(bond_dev); - unsigned int packets_per_slave; - int ifindex, i, targets_added; + const struct bonding *bond = netdev_priv(bond_dev); + int i, targets_added, miimon, mode; + const struct slave *primary; struct nlattr *targets; - struct slave *primary; - if (nla_put_u8(skb, IFLA_BOND_MODE, BOND_MODE(bond))) + rcu_read_lock(); + mode = READ_ONCE(bond->params.mode); + if (nla_put_u8(skb, IFLA_BOND_MODE, mode)) goto nla_put_failure; - ifindex = bond_option_active_slave_get_ifindex(bond); - if (ifindex && nla_put_u32(skb, IFLA_BOND_ACTIVE_SLAVE, ifindex)) - goto nla_put_failure; + if (bond_mode_uses_primary(mode)) { + int ifindex = bond_option_active_slave_get_ifindex_rcu(bond); + + if (ifindex && nla_put_u32(skb, IFLA_BOND_ACTIVE_SLAVE, ifindex)) + goto nla_put_failure; + } - if (nla_put_u32(skb, IFLA_BOND_MIIMON, bond->params.miimon)) + miimon = READ_ONCE(bond->params.miimon); + if (nla_put_u32(skb, IFLA_BOND_MIIMON, miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_UPDELAY, - bond->params.updelay * bond->params.miimon)) + READ_ONCE(bond->params.updelay) * miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_DOWNDELAY, - bond->params.downdelay * bond->params.miimon)) + READ_ONCE(bond->params.downdelay) * miimon)) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_PEER_NOTIF_DELAY, - bond->params.peer_notif_delay * bond->params.miimon)) + READ_ONCE(bond->params.peer_notif_delay) * miimon)) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_USE_CARRIER, 1)) goto nla_put_failure; - if (nla_put_u32(skb, IFLA_BOND_ARP_INTERVAL, bond->params.arp_interval)) + if (nla_put_u32(skb, IFLA_BOND_ARP_INTERVAL, + READ_ONCE(bond->params.arp_interval))) goto nla_put_failure; targets = nla_nest_start_noflag(skb, IFLA_BOND_ARP_IP_TARGET); @@ -741,8 +746,10 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { - if (bond->params.arp_targets[i]) { - if (nla_put_be32(skb, i, bond->params.arp_targets[i])) + __be32 t = READ_ONCE(bond->params.arp_targets[i]); + + if (t) { + if (nla_put_be32(skb, i, t)) goto nla_put_failure; targets_added = 1; } @@ -753,11 +760,12 @@ static int bond_fill_info(struct sk_buff *skb, else nla_nest_cancel(skb, targets); - if (nla_put_u32(skb, IFLA_BOND_ARP_VALIDATE, bond->params.arp_validate)) + if (nla_put_u32(skb, IFLA_BOND_ARP_VALIDATE, + READ_ONCE(bond->params.arp_validate))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_ARP_ALL_TARGETS, - bond->params.arp_all_targets)) + READ_ONCE(bond->params.arp_all_targets))) goto nla_put_failure; #if IS_ENABLED(CONFIG_IPV6) @@ -767,6 +775,9 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_NS_TARGETS; i++) { + /* Note: IPv6 addresses can not be read in an atomic READ_ONCE() yet. + * We accept this minor race for the moment. + */ if (!ipv6_addr_any(&bond->params.ns_targets[i])) { if (nla_put_in6_addr(skb, i, &bond->params.ns_targets[i])) goto nla_put_failure; @@ -780,97 +791,97 @@ static int bond_fill_info(struct sk_buff *skb, nla_nest_cancel(skb, targets); #endif - primary = rtnl_dereference(bond->primary_slave); + primary = rcu_dereference(bond->primary_slave); if (primary && nla_put_u32(skb, IFLA_BOND_PRIMARY, primary->dev->ifindex)) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_PRIMARY_RESELECT, - bond->params.primary_reselect)) + READ_ONCE(bond->params.primary_reselect))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_FAIL_OVER_MAC, - bond->params.fail_over_mac)) + READ_ONCE(bond->params.fail_over_mac))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_XMIT_HASH_POLICY, - bond->params.xmit_policy)) + READ_ONCE(bond->params.xmit_policy))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_RESEND_IGMP, - bond->params.resend_igmp)) + READ_ONCE(bond->params.resend_igmp))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_NUM_PEER_NOTIF, - bond->params.num_peer_notif)) + READ_ONCE(bond->params.num_peer_notif))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_ALL_SLAVES_ACTIVE, - bond->params.all_slaves_active)) + READ_ONCE(bond->params.all_slaves_active))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_MIN_LINKS, - bond->params.min_links)) + READ_ONCE(bond->params.min_links))) goto nla_put_failure; if (nla_put_u32(skb, IFLA_BOND_LP_INTERVAL, - bond->params.lp_interval)) + READ_ONCE(bond->params.lp_interval))) goto nla_put_failure; - packets_per_slave = bond->params.packets_per_slave; if (nla_put_u32(skb, IFLA_BOND_PACKETS_PER_SLAVE, - packets_per_slave)) + READ_ONCE(bond->params.packets_per_slave))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_LACP_ACTIVE, - bond->params.lacp_active)) + READ_ONCE(bond->params.lacp_active))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_LACP_RATE, - bond->params.lacp_fast)) + READ_ONCE(bond->params.lacp_fast))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_AD_SELECT, - bond->params.ad_select)) + READ_ONCE(bond->params.ad_select))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_TLB_DYNAMIC_LB, - bond->params.tlb_dynamic_lb)) + READ_ONCE(bond->params.tlb_dynamic_lb))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_MISSED_MAX, - bond->params.missed_max)) + READ_ONCE(bond->params.missed_max))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_COUPLED_CONTROL, - bond->params.coupled_control)) + READ_ONCE(bond->params.coupled_control))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_BROADCAST_NEIGH, - bond->params.broadcast_neighbor)) + READ_ONCE(bond->params.broadcast_neighbor))) goto nla_put_failure; if (nla_put_u8(skb, IFLA_BOND_LACP_STRICT, - bond->params.lacp_strict)) + READ_ONCE(bond->params.lacp_strict))) goto nla_put_failure; - if (BOND_MODE(bond) == BOND_MODE_8023AD) { + if (mode == BOND_MODE_8023AD) { struct ad_info info; if (capable(CAP_NET_ADMIN)) { if (nla_put_u16(skb, IFLA_BOND_AD_ACTOR_SYS_PRIO, - bond->params.ad_actor_sys_prio)) + READ_ONCE(bond->params.ad_actor_sys_prio))) goto nla_put_failure; if (nla_put_u16(skb, IFLA_BOND_AD_USER_PORT_KEY, - bond->params.ad_user_port_key)) + READ_ONCE(bond->params.ad_user_port_key))) goto nla_put_failure; + /* Small race here, this is a minor trade off. */ if (nla_put(skb, IFLA_BOND_AD_ACTOR_SYSTEM, ETH_ALEN, &bond->params.ad_actor_system)) goto nla_put_failure; } - if (!bond_3ad_get_active_agg_info(bond, &info)) { + if (!__bond_3ad_get_active_agg_info(bond, &info)) { struct nlattr *nest; nest = nla_nest_start_noflag(skb, IFLA_BOND_AD_INFO); @@ -898,9 +909,11 @@ static int bond_fill_info(struct sk_buff *skb, } } + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index e590c8dee86e..36b8d89387ee 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -934,14 +934,14 @@ static int bond_option_mode_set(struct bonding *bond, /* don't cache arp_validate between modes */ WRITE_ONCE(bond->params.arp_validate, BOND_ARP_VALIDATE_NONE); - bond->params.mode = newval->value; + WRITE_ONCE(bond->params.mode, newval->value); /* When changing mode, the bond device is down, we may reduce * the bond_bcast_neigh_enabled in bond_close() if broadcast_neighbor * enabled in 8023ad mode. Therefore, only clear broadcast_neighbor * to 0. */ - bond->params.broadcast_neighbor = 0; + WRITE_ONCE(bond->params.broadcast_neighbor, 0); if (bond->dev->reg_state == NETREG_REGISTERED) { bool update = false; @@ -1706,7 +1706,7 @@ static int bond_option_lacp_strict_set(struct bonding *bond, { netdev_dbg(bond->dev, "Setting LACP fallback to %s (%llu)\n", newval->string, newval->value); - bond->params.lacp_strict = newval->value; + WRITE_ONCE(bond->params.lacp_strict, newval->value); bond_3ad_set_carrier(bond); return 0; @@ -1927,7 +1927,7 @@ static int bond_option_broadcast_neigh_set(struct bonding *bond, if (bond->params.broadcast_neighbor == newval->value) return 0; - bond->params.broadcast_neighbor = newval->value; + WRITE_ONCE(bond->params.broadcast_neighbor, newval->value); if (bond->dev->flags & IFF_UP) { if (bond->params.broadcast_neighbor) static_branch_inc(&bond_bcast_neigh_enabled); diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index cd27a7344e89..29cca7945df7 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -23,6 +23,7 @@ #include <linux/mutex.h> #include <linux/phylink.h> #include <linux/etherdevice.h> +#include <linux/gpio/consumer.h> #include <net/dsa.h> #include "b53_regs.h" @@ -467,7 +468,7 @@ static inline void b53_arl_search_read(struct b53_device *dev, u8 idx, #ifdef CONFIG_BCM47XX #include <linux/bcm47xx_nvram.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <bcm47xx_board.h> static inline struct gpio_desc *b53_switch_get_reset_gpio(struct b53_device *dev) { diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 138f2ab0774e..c4c769028a20 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -18,7 +18,7 @@ #include <linux/delay.h> #include <linux/dsa/ksz_common.h> #include <linux/export.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/if_vlan.h> #include <linux/kernel.h> #include <linux/module.h> @@ -36,6 +36,52 @@ #include "ksz8_reg.h" #include "ksz8.h" +/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. + * variants. + * This values are documented in KSZ8873 and KSZ8863 datasheets. + */ +static const struct ksz_drive_strength ksz88x3_drive_strengths[] = { + { 0, 8000 }, + { KSZ8873_DRIVE_STRENGTH_16MA, 16000 }, +}; + +struct ksz88xx_stats_raw { + u64 rx; + u64 rx_hi; + u64 rx_undersize; + u64 rx_fragments; + u64 rx_oversize; + u64 rx_jabbers; + u64 rx_symbol_err; + u64 rx_crc_err; + u64 rx_align_err; + u64 rx_mac_ctrl; + u64 rx_pause; + u64 rx_bcast; + u64 rx_mcast; + u64 rx_ucast; + u64 rx_64_or_less; + u64 rx_65_127; + u64 rx_128_255; + u64 rx_256_511; + u64 rx_512_1023; + u64 rx_1024_1522; + u64 tx; + u64 tx_hi; + u64 tx_late_col; + u64 tx_pause; + u64 tx_bcast; + u64 tx_mcast; + u64 tx_ucast; + u64 tx_deferred; + u64 tx_total_col; + u64 tx_exc_col; + u64 tx_single_col; + u64 tx_mult_col; + u64 rx_discards; + u64 tx_discards; +}; + static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { ksz_rmw8(dev, addr, bits, set ? bits : 0); @@ -158,10 +204,17 @@ static int ksz8_reset_switch(struct ksz_device *dev) return 0; } -static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size) +static int ksz88xx_change_mtu(struct dsa_switch *ds, int port, int mtu) { + struct ksz_device *dev = ds->priv; + int frame_size; u8 ctrl2 = 0; + if (!dsa_is_cpu_port(dev->ds, port)) + return 0; + + frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + if (frame_size <= KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE; else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) @@ -171,11 +224,18 @@ static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size) KSZ8863_HUGE_PACKET_ENABLE, ctrl2); } -static int ksz8795_change_mtu(struct ksz_device *dev, int frame_size) +static int ksz87xx_change_mtu(struct dsa_switch *ds, int port, int mtu) { + struct ksz_device *dev = ds->priv; u8 ctrl1 = 0, ctrl2 = 0; + u16 frame_size; int ret; + if (!dsa_is_cpu_port(dev->ds, port)) + return 0; + + frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + if (frame_size > KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= SW_LEGAL_PACKET_DISABLE; if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) @@ -188,29 +248,14 @@ static int ksz8795_change_mtu(struct ksz_device *dev, int frame_size) return ksz_rmw8(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, ctrl2); } -static int ksz8_change_mtu(struct dsa_switch *ds, int port, int mtu) +static int ksz87xx_max_mtu(struct dsa_switch *ds, int port) { - struct ksz_device *dev = ds->priv; - u16 frame_size; - - if (!dsa_is_cpu_port(dev->ds, port)) - return 0; - - frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; - - switch (dev->chip_id) { - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - return ksz8795_change_mtu(dev, frame_size); - case KSZ8463_CHIP_ID: - case KSZ88X3_CHIP_ID: - case KSZ8864_CHIP_ID: - case KSZ8895_CHIP_ID: - return ksz8863_change_mtu(dev, frame_size); - } + return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; +} - return -EOPNOTSUPP; +static int ksz88xx_max_mtu(struct dsa_switch *ds, int port) +{ + return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; } static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues) @@ -1746,6 +1791,159 @@ static void ksz8_port_mirror_del(struct dsa_switch *ds, int port, PORT_MIRROR_SNIFFER, false); } +static u8 ksz8463_tc_ctrl(int port, int queue) +{ + u8 reg; + + reg = 0xC8 + port * 4; + reg += ((3 - queue) / 2) * 2; + reg++; + reg -= (queue & 1); + return reg; +} + +/** + * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to configure + * @p: Pointer to offload replace parameters describing ETS bands and mapping + * + * The KSZ88x3 supports two scheduling modes: Strict Priority and + * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: + * - No configurable queue-to-priority mapping + * - No weight adjustment in WFQ mode + * + * This function configures the switch to use strict priority mode by + * clearing the WFQ enable bit for all queues associated with ETS bands. + * If strict priority is not explicitly requested, the switch will default + * to WFQ mode. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p) +{ + int ret, band; + + /* Only strict priority mode is supported for now. + * WFQ is implicitly enabled when strict mode is disabled. + */ + for (band = 0; band < p->bands; band++) { + int queue = ksz_ets_band_to_queue(p, band); + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Clear WFQ enable bit to select strict priority scheduling */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); + if (ret) + return ret; + } + + return 0; +} + +/** + * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to reset + * + * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or + * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or + * queue mapping. This function resets the port’s scheduling mode to + * the default, which is WFQ, by enabling the WFQ bit for all queues. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) +{ + int ret, queue; + + /* Iterate over all transmit queues for this port */ + for (queue = 0; queue < dev->info->num_tx_queues; queue++) { + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Set WFQ enable bit to revert back to default scheduling + * mode + */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, + KSZ8873_TXQ_WFQ_ENABLE); + if (ret) + return ret; + } + + return 0; +} + +static int ksz8_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, + struct tc_ets_qopt_offload *qopt) +{ + struct ksz_device *dev = ds->priv; + int ret; + + if (!(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) + return -EOPNOTSUPP; + + if (qopt->parent != TC_H_ROOT) { + dev_err(dev->dev, "Parent should be \"root\"\n"); + return -EOPNOTSUPP; + } + + switch (qopt->command) { + case TC_ETS_REPLACE: + ret = ksz_tc_ets_validate(dev, port, &qopt->replace_params); + if (ret) + return ret; + + return ksz88x3_tc_ets_add(dev, port, &qopt->replace_params); + case TC_ETS_DESTROY: + return ksz88x3_tc_ets_del(dev, port); + case TC_ETS_STATS: + case TC_ETS_GRAFT: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static int ksz87xx_setup_tc(struct dsa_switch *ds, int port, + enum tc_setup_type type, void *type_data) +{ + switch (type) { + case TC_SETUP_QDISC_CBS: + return ksz_setup_tc_cbs(ds, port, type_data); + default: + return -EOPNOTSUPP; + } +} + +static int ksz8_setup_tc(struct dsa_switch *ds, int port, + enum tc_setup_type type, void *type_data) +{ + switch (type) { + case TC_SETUP_QDISC_CBS: + return ksz_setup_tc_cbs(ds, port, type_data); + case TC_SETUP_QDISC_ETS: + return ksz8_tc_setup_qdisc_ets(ds, port, type_data); + default: + return -EOPNOTSUPP; + } +} + static void ksz8795_cpu_interface_select(struct ksz_device *dev, int port) { struct ksz_port *p = &dev->ports[port]; @@ -2053,6 +2251,171 @@ static int ksz8_enable_stp_addr(struct ksz_device *dev) return ksz8_w_sta_mac_table(dev, 0, &alu); } +static void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) +{ + struct ethtool_pause_stats *pstats; + struct rtnl_link_stats64 *stats; + struct ksz88xx_stats_raw *raw; + struct ksz_port_mib *mib; + + mib = &dev->ports[port].mib; + stats = &mib->stats64; + pstats = &mib->pause_stats; + raw = (struct ksz88xx_stats_raw *)mib->counters; + + spin_lock(&mib->stats64_lock); + + stats->rx_packets = raw->rx_bcast + raw->rx_mcast + raw->rx_ucast + + raw->rx_pause; + stats->tx_packets = raw->tx_bcast + raw->tx_mcast + raw->tx_ucast + + raw->tx_pause; + + /* HW counters are counting bytes + FCS which is not acceptable + * for rtnl_link_stats64 interface + */ + stats->rx_bytes = raw->rx + raw->rx_hi - stats->rx_packets * ETH_FCS_LEN; + stats->tx_bytes = raw->tx + raw->tx_hi - stats->tx_packets * ETH_FCS_LEN; + + stats->rx_length_errors = raw->rx_undersize + raw->rx_fragments + + raw->rx_oversize; + + stats->rx_crc_errors = raw->rx_crc_err; + stats->rx_frame_errors = raw->rx_align_err; + stats->rx_dropped = raw->rx_discards; + stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors + + stats->rx_frame_errors + stats->rx_dropped; + + stats->tx_window_errors = raw->tx_late_col; + stats->tx_fifo_errors = raw->tx_discards; + stats->tx_aborted_errors = raw->tx_exc_col; + stats->tx_errors = stats->tx_window_errors + stats->tx_fifo_errors + + stats->tx_aborted_errors; + + stats->multicast = raw->rx_mcast; + stats->collisions = raw->tx_total_col; + + pstats->tx_pause_frames = raw->tx_pause; + pstats->rx_pause_frames = raw->rx_pause; + + spin_unlock(&mib->stats64_lock); +} + +/** + * ksz88x3_drive_strength_write() - Set the drive strength configuration for + * KSZ8863 compatible chip variants. + * @dev: ksz device + * @props: Array of drive strength properties to be set + * @num_props: Number of properties in the array + * + * This function applies the specified drive strength settings to KSZ88X3 chip + * variants (KSZ8873, KSZ8863). + * It ensures the configurations align with what the chip variant supports and + * warns or errors out on unsupported settings. + * + * Return: 0 on success, error code otherwise + */ +static int ksz88x3_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props) +{ + size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); + int microamp; + int i, ret; + + for (i = 0; i < num_props; i++) { + if (props[i].value == -1 || i == KSZ_DRIVER_STRENGTH_IO) + continue; + + dev_warn(dev->dev, "%s is not supported by this chip variant\n", + props[i].name); + } + + microamp = props[KSZ_DRIVER_STRENGTH_IO].value; + ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size, + microamp); + if (ret < 0) { + ksz_drive_strength_error(dev, ksz88x3_drive_strengths, + array_size, microamp); + return ret; + } + + return ksz_rmw8(dev, KSZ8873_REG_GLOBAL_CTRL_12, + KSZ8873_DRIVE_STRENGTH_16MA, ret); +} + +/** + * ksz8_parse_drive_strength() - Extract and apply drive strength configurations + * from device tree properties. + * @dev: ksz device + * + * This function reads the specified drive strength properties from the + * device tree, validates against the supported chip variants, and sets + * them accordingly. An error should be critical here, as the drive strength + * settings are crucial for EMI compliance. + * + * Return: 0 on success, error code otherwise + */ +static int ksz8_parse_drive_strength(struct ksz_device *dev) +{ + struct ksz_driver_strength_prop of_props[] = { + [KSZ_DRIVER_STRENGTH_HI] = { + .name = "microchip,hi-drive-strength-microamp", + .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_LO] = { + .name = "microchip,lo-drive-strength-microamp", + .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_IO] = { + .name = "microchip,io-drive-strength-microamp", + .offset = 0, /* don't care */ + .value = -1, + }, + }; + struct device_node *np = dev->dev->of_node; + bool have_any_prop = false; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + ret = of_property_read_u32(np, of_props[i].name, + &of_props[i].value); + if (ret && ret != -EINVAL) + dev_warn(dev->dev, "Failed to read %s\n", + of_props[i].name); + if (ret) + continue; + + have_any_prop = true; + } + + if (!have_any_prop) + return 0; + + switch (dev->chip_id) { + case KSZ88X3_CHIP_ID: + return ksz88x3_drive_strength_write(dev, of_props, + ARRAY_SIZE(of_props)); + case KSZ8795_CHIP_ID: + case KSZ8794_CHIP_ID: + case KSZ8765_CHIP_ID: + return ksz_drive_strength_write(dev, of_props, + ARRAY_SIZE(of_props)); + default: + /* KSZ8864, KSZ8895 */ + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + if (of_props[i].value == -1) + continue; + + dev_warn(dev->dev, "%s is not supported by this chip variant\n", + of_props[i].name); + } + } + + return 0; +} + static int ksz8_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -2075,7 +2438,7 @@ static int ksz8_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); + ret = ksz8_parse_drive_strength(dev); if (ret) return ret; @@ -2551,8 +2914,8 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_change_mtu = ksz88xx_change_mtu, + .port_max_mtu = ksz88xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, @@ -2560,7 +2923,7 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz8_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, @@ -2601,8 +2964,8 @@ const struct dsa_switch_ops ksz87xx_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_change_mtu = ksz87xx_change_mtu, + .port_max_mtu = ksz87xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, @@ -2610,7 +2973,7 @@ const struct dsa_switch_ops ksz87xx_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz87xx_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, @@ -2652,8 +3015,8 @@ const struct dsa_switch_ops ksz88xx_switch_ops = { .port_mirror_del = ksz8_port_mirror_del, .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, - .port_change_mtu = ksz8_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_change_mtu = ksz88xx_change_mtu, + .port_max_mtu = ksz88xx_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, .suspend = ksz_suspend, @@ -2663,7 +3026,7 @@ const struct dsa_switch_ops ksz88xx_switch_ops = { .port_hwtstamp_set = ksz_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, - .port_setup_tc = ksz_setup_tc, + .port_setup_tc = ksz8_setup_tc, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, .port_get_dscp_prio = ksz_port_get_dscp_prio, diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index f3f0c98dfb5a..3ee995545c57 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -15,6 +15,7 @@ #include <linux/if_hsr.h> #include <linux/if_vlan.h> #include <net/dsa.h> +#include <net/ieee8021q.h> #include <net/switchdev.h> #include "ksz9477_reg.h" @@ -60,6 +61,11 @@ static int ksz9477_change_mtu(struct dsa_switch *ds, int port, int mtu) REG_SW_MTU_MASK, frame_size); } +int ksz9477_max_mtu(struct dsa_switch *ds, int port) +{ + return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; +} + static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev) { unsigned int val; @@ -478,8 +484,8 @@ static int ksz9477_half_duplex_monitor(struct ksz_device *dev, int port, return ret; } -int ksz9477_errata_monitor(struct ksz_device *dev, int port, - u64 tx_late_col) +static int ksz9477_errata_monitor(struct ksz_device *dev, int port, + u64 tx_late_col) { u8 status; int ret; @@ -497,6 +503,24 @@ int ksz9477_errata_monitor(struct ksz_device *dev, int port, return ret; } +static void ksz9477_r_mib_stats64(struct ksz_device *dev, int port) +{ + struct ksz_stats_raw *raw; + struct ksz_port_mib *mib; + int ret; + + ksz_r_mib_stats64(dev, port); + + if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { + mib = &dev->ports[port].mib; + raw = (struct ksz_stats_raw *)mib->counters; + + ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); + if (ret) + dev_err(dev->dev, "Failed to monitor transmission halt\n"); + } +}; + void ksz9477_port_init_cnt(struct ksz_device *dev, int port) { struct ksz_port_mib *mib = &dev->ports[port].mib; @@ -1176,6 +1200,58 @@ void ksz9477_port_mirror_del(struct dsa_switch *ds, int port, PORT_MIRROR_SNIFFER, false); } +static bool ksz9477_get_gbit(struct ksz_device *dev, int port) +{ + const u8 *bitval = dev->info->xmii_ctrl1; + const u16 *regs = dev->info->regs; + bool gbit = false; + u8 data8; + bool val; + + ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); + + val = FIELD_GET(P_GMII_1GBIT_M, data8); + + if (val == bitval[P_GMII_1GBIT]) + gbit = true; + + return gbit; +} + +static phy_interface_t ksz9477_get_xmii(struct ksz_device *dev, int port, + bool gbit) +{ + const u8 *bitval = dev->info->xmii_ctrl1; + const u16 *regs = dev->info->regs; + phy_interface_t interface; + u8 data8; + u8 val; + + ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); + + val = FIELD_GET(P_MII_SEL_M, data8); + + if (val == bitval[P_MII_SEL]) { + if (gbit) + interface = PHY_INTERFACE_MODE_GMII; + else + interface = PHY_INTERFACE_MODE_MII; + } else if (val == bitval[P_RMII_SEL]) { + interface = PHY_INTERFACE_MODE_RMII; + } else { + interface = PHY_INTERFACE_MODE_RGMII; + if (data8 & P_RGMII_ID_EG_ENABLE) + interface = PHY_INTERFACE_MODE_RGMII_TXID; + if (data8 & P_RGMII_ID_IG_ENABLE) { + interface = PHY_INTERFACE_MODE_RGMII_RXID; + if (data8 & P_RGMII_ID_EG_ENABLE) + interface = PHY_INTERFACE_MODE_RGMII_ID; + } + } + + return interface; +} + static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) { phy_interface_t interface; @@ -1184,9 +1260,9 @@ static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) if (dev->info->internal_phy[port]) return PHY_INTERFACE_MODE_NA; - gbit = ksz_get_gbit(dev, port); + gbit = ksz9477_get_gbit(dev, port); - interface = ksz_get_xmii(dev, port, gbit); + interface = ksz9477_get_xmii(dev, port, gbit); return interface; } @@ -1335,6 +1411,28 @@ static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) ksz_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0); } +int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port) +{ + u32 queue_map = 0; + int ipm; + + for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { + int queue; + + /* Traffic Type (TT) is corresponding to the Internal Priority + * Map (IPM) in the switch. Traffic Class (TC) is + * corresponding to the queue in the switch. + */ + queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); + if (queue < 0) + return queue; + + queue_map |= queue << (ipm * KSZ9477_PORT_TC_MAP_S); + } + + return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); +} + static int ksz9477_dsa_port_setup(struct dsa_switch *ds, int port) { struct ksz_device *dev = ds->priv; @@ -1517,6 +1615,58 @@ int ksz9477_enable_stp_addr(struct ksz_device *dev) return 0; } +/** + * ksz9477_parse_drive_strength() - Extract and apply drive strength + * configurations from device tree properties. + * @dev: ksz device + * + * This function reads the specified drive strength properties from the + * device tree, validates against the supported chip variants, and sets + * them accordingly. An error should be critical here, as the drive strength + * settings are crucial for EMI compliance. + * + * Return: 0 on success, error code otherwise + */ +static int ksz9477_parse_drive_strength(struct ksz_device *dev) +{ + struct ksz_driver_strength_prop of_props[] = { + [KSZ_DRIVER_STRENGTH_HI] = { + .name = "microchip,hi-drive-strength-microamp", + .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_LO] = { + .name = "microchip,lo-drive-strength-microamp", + .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, + .value = -1, + }, + [KSZ_DRIVER_STRENGTH_IO] = { + .name = "microchip,io-drive-strength-microamp", + .offset = 0, /* don't care */ + .value = -1, + }, + }; + struct device_node *np = dev->dev->of_node; + bool have_any_prop = false; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(of_props); i++) { + ret = of_property_read_u32(np, of_props[i].name, + &of_props[i].value); + if (ret && ret != -EINVAL) + dev_warn(dev->dev, "Failed to read %s\n", + of_props[i].name); + if (ret) + continue; + + have_any_prop = true; + } + + if (!have_any_prop) + return 0; + + return ksz_drive_strength_write(dev, of_props, ARRAY_SIZE(of_props)); +} static int ksz9477_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -1539,7 +1689,7 @@ static int ksz9477_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); + ret = ksz9477_parse_drive_strength(dev); if (ret) return ret; @@ -2052,7 +2202,7 @@ const struct ksz_dev_ops ksz9477_dev_ops = { .cfg_port_member = ksz9477_cfg_port_member, .r_mib_cnt = ksz9477_r_mib_cnt, .r_mib_pkt = ksz9477_r_mib_pkt, - .r_mib_stat64 = ksz_r_mib_stats64, + .r_mib_stat64 = ksz9477_r_mib_stats64, .freeze_mib = ksz9477_freeze_mib, .port_init_cnt = ksz9477_port_init_cnt, .pme_write8 = ksz_write8, @@ -2098,7 +2248,7 @@ const struct dsa_switch_ops ksz9477_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = ksz9477_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz9477_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, .suspend = ksz_suspend, diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index 92a1d889224d..a84c000000e6 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -21,6 +21,7 @@ void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze); void ksz9477_port_init_cnt(struct ksz_device *dev, int port); int ksz9477_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag, struct netlink_ext_ack *extack); +int ksz9477_max_mtu(struct dsa_switch *ds, int port); int ksz9477_port_vlan_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack); @@ -31,8 +32,6 @@ int ksz9477_port_mirror_add(struct dsa_switch *ds, int port, bool ingress, struct netlink_ext_ack *extack); void ksz9477_port_mirror_del(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror); -int ksz9477_errata_monitor(struct ksz_device *dev, int port, - u64 tx_late_col); int ksz9477_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb, void *data); int ksz9477_fdb_add(struct dsa_switch *ds, int port, @@ -45,6 +44,7 @@ int ksz9477_mdb_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db); int ksz9477_enable_stp_addr(struct ksz_device *dev); void ksz9477_port_queue_split(struct ksz_device *dev, int port); +int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port); int ksz9477_port_acl_init(struct ksz_device *dev, int port); void ksz9477_port_acl_free(struct ksz_device *dev, int port); diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index d1726778bb48..67ab6ddb9e53 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -37,82 +37,6 @@ #define MIB_COUNTER_NUM 0x20 -struct ksz_stats_raw { - u64 rx_hi; - u64 rx_undersize; - u64 rx_fragments; - u64 rx_oversize; - u64 rx_jabbers; - u64 rx_symbol_err; - u64 rx_crc_err; - u64 rx_align_err; - u64 rx_mac_ctrl; - u64 rx_pause; - u64 rx_bcast; - u64 rx_mcast; - u64 rx_ucast; - u64 rx_64_or_less; - u64 rx_65_127; - u64 rx_128_255; - u64 rx_256_511; - u64 rx_512_1023; - u64 rx_1024_1522; - u64 rx_1523_2000; - u64 rx_2001; - u64 tx_hi; - u64 tx_late_col; - u64 tx_pause; - u64 tx_bcast; - u64 tx_mcast; - u64 tx_ucast; - u64 tx_deferred; - u64 tx_total_col; - u64 tx_exc_col; - u64 tx_single_col; - u64 tx_mult_col; - u64 rx_total; - u64 tx_total; - u64 rx_discards; - u64 tx_discards; -}; - -struct ksz88xx_stats_raw { - u64 rx; - u64 rx_hi; - u64 rx_undersize; - u64 rx_fragments; - u64 rx_oversize; - u64 rx_jabbers; - u64 rx_symbol_err; - u64 rx_crc_err; - u64 rx_align_err; - u64 rx_mac_ctrl; - u64 rx_pause; - u64 rx_bcast; - u64 rx_mcast; - u64 rx_ucast; - u64 rx_64_or_less; - u64 rx_65_127; - u64 rx_128_255; - u64 rx_256_511; - u64 rx_512_1023; - u64 rx_1024_1522; - u64 tx; - u64 tx_hi; - u64 tx_late_col; - u64 tx_pause; - u64 tx_bcast; - u64 tx_mcast; - u64 tx_ucast; - u64 tx_deferred; - u64 tx_total_col; - u64 tx_exc_col; - u64 tx_single_col; - u64 tx_mult_col; - u64 rx_discards; - u64 tx_discards; -}; - static const struct ksz_mib_names ksz88xx_mib_names[] = { { 0x00, "rx" }, { 0x01, "rx_hi" }, @@ -189,28 +113,6 @@ static const struct ksz_mib_names ksz9477_mib_names[] = { { 0x83, "tx_discards" }, }; -struct ksz_driver_strength_prop { - const char *name; - int offset; - int value; -}; - -enum ksz_driver_strength_type { - KSZ_DRIVER_STRENGTH_HI, - KSZ_DRIVER_STRENGTH_LO, - KSZ_DRIVER_STRENGTH_IO, -}; - -/** - * struct ksz_drive_strength - drive strength mapping - * @reg_val: register value - * @microamp: microamp value - */ -struct ksz_drive_strength { - u32 reg_val; - u32 microamp; -}; - /* ksz9477_drive_strengths - Drive strength mapping for KSZ9477 variants * * This values are not documented in KSZ9477 variants but confirmed by @@ -246,15 +148,6 @@ static const struct ksz_drive_strength ksz9477_drive_strengths[] = { { SW_DRIVE_STRENGTH_28MA, 28000 }, }; -/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. - * variants. - * This values are documented in KSZ8873 and KSZ8863 datasheets. - */ -static const struct ksz_drive_strength ksz88x3_drive_strengths[] = { - { 0, 8000 }, - { KSZ8873_DRIVE_STRENGTH_16MA, 16000 }, -}; - /** * ksz_phylink_mac_disable_tx_lpi() - Callback to signal LPI support (Dummy) * @config: phylink config structure @@ -2013,7 +1906,6 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) struct rtnl_link_stats64 *stats; struct ksz_stats_raw *raw; struct ksz_port_mib *mib; - int ret; mib = &dev->ports[port].mib; stats = &mib->stats64; @@ -2055,61 +1947,6 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) pstats->rx_pause_frames = raw->rx_pause; spin_unlock(&mib->stats64_lock); - - if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { - ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); - if (ret) - dev_err(dev->dev, "Failed to monitor transmission halt\n"); - } -} - -void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) -{ - struct ethtool_pause_stats *pstats; - struct rtnl_link_stats64 *stats; - struct ksz88xx_stats_raw *raw; - struct ksz_port_mib *mib; - - mib = &dev->ports[port].mib; - stats = &mib->stats64; - pstats = &mib->pause_stats; - raw = (struct ksz88xx_stats_raw *)mib->counters; - - spin_lock(&mib->stats64_lock); - - stats->rx_packets = raw->rx_bcast + raw->rx_mcast + raw->rx_ucast + - raw->rx_pause; - stats->tx_packets = raw->tx_bcast + raw->tx_mcast + raw->tx_ucast + - raw->tx_pause; - - /* HW counters are counting bytes + FCS which is not acceptable - * for rtnl_link_stats64 interface - */ - stats->rx_bytes = raw->rx + raw->rx_hi - stats->rx_packets * ETH_FCS_LEN; - stats->tx_bytes = raw->tx + raw->tx_hi - stats->tx_packets * ETH_FCS_LEN; - - stats->rx_length_errors = raw->rx_undersize + raw->rx_fragments + - raw->rx_oversize; - - stats->rx_crc_errors = raw->rx_crc_err; - stats->rx_frame_errors = raw->rx_align_err; - stats->rx_dropped = raw->rx_discards; - stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors + - stats->rx_frame_errors + stats->rx_dropped; - - stats->tx_window_errors = raw->tx_late_col; - stats->tx_fifo_errors = raw->tx_discards; - stats->tx_aborted_errors = raw->tx_exc_col; - stats->tx_errors = stats->tx_window_errors + stats->tx_fifo_errors + - stats->tx_aborted_errors; - - stats->multicast = raw->rx_mcast; - stats->collisions = raw->tx_total_col; - - pstats->tx_pause_frames = raw->tx_pause; - pstats->rx_pause_frames = raw->rx_pause; - - spin_unlock(&mib->stats64_lock); } void ksz_get_stats64(struct dsa_switch *ds, int port, @@ -2885,28 +2722,6 @@ void ksz_port_bridge_leave(struct dsa_switch *ds, int port, */ } -int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port) -{ - u32 queue_map = 0; - int ipm; - - for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { - int queue; - - /* Traffic Type (TT) is corresponding to the Internal Priority - * Map (IPM) in the switch. Traffic Class (TC) is - * corresponding to the queue in the switch. - */ - queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); - if (queue < 0) - return queue; - - queue_map |= queue << (ipm * KSZ9477_PORT_TC_MAP_S); - } - - return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); -} - void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) { struct ksz_device *dev = ds->priv; @@ -2984,40 +2799,6 @@ int ksz_port_bridge_flags(struct dsa_switch *ds, int port, return 0; } -int ksz_max_mtu(struct dsa_switch *ds, int port) -{ - struct ksz_device *dev = ds->priv; - - switch (dev->chip_id) { - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - case KSZ8463_CHIP_ID: - case KSZ88X3_CHIP_ID: - case KSZ8864_CHIP_ID: - case KSZ8895_CHIP_ID: - return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - case KSZ8563_CHIP_ID: - case KSZ8567_CHIP_ID: - case KSZ9477_CHIP_ID: - case KSZ9563_CHIP_ID: - case KSZ9567_CHIP_ID: - case KSZ9893_CHIP_ID: - case KSZ9896_CHIP_ID: - case KSZ9897_CHIP_ID: - case LAN9370_CHIP_ID: - case LAN9371_CHIP_ID: - case LAN9372_CHIP_ID: - case LAN9373_CHIP_ID: - case LAN9374_CHIP_ID: - case LAN9646_CHIP_ID: - return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - } - - return -EOPNOTSUPP; -} - int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e) { @@ -3086,39 +2867,6 @@ void ksz_set_xmii(struct ksz_device *dev, int port, phy_interface_t interface) ksz_pwrite8(dev, port, regs[P_XMII_CTRL_1], data8); } -phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit) -{ - const u8 *bitval = dev->info->xmii_ctrl1; - const u16 *regs = dev->info->regs; - phy_interface_t interface; - u8 data8; - u8 val; - - ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); - - val = FIELD_GET(P_MII_SEL_M, data8); - - if (val == bitval[P_MII_SEL]) { - if (gbit) - interface = PHY_INTERFACE_MODE_GMII; - else - interface = PHY_INTERFACE_MODE_MII; - } else if (val == bitval[P_RMII_SEL]) { - interface = PHY_INTERFACE_MODE_RMII; - } else { - interface = PHY_INTERFACE_MODE_RGMII; - if (data8 & P_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_TXID; - if (data8 & P_RGMII_ID_IG_ENABLE) { - interface = PHY_INTERFACE_MODE_RGMII_RXID; - if (data8 & P_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_ID; - } - } - - return interface; -} - bool ksz_phylink_need_config(struct phylink_config *config, unsigned int mode) { @@ -3154,24 +2902,6 @@ void ksz_phylink_mac_config(struct phylink_config *config, ksz_set_xmii(dev, port, state->interface); } -bool ksz_get_gbit(struct ksz_device *dev, int port) -{ - const u8 *bitval = dev->info->xmii_ctrl1; - const u16 *regs = dev->info->regs; - bool gbit = false; - u8 data8; - bool val; - - ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8); - - val = FIELD_GET(P_GMII_1GBIT_M, data8); - - if (val == bitval[P_GMII_1GBIT]) - gbit = true; - - return gbit; -} - static int ksz_switch_detect(struct ksz_device *dev) { u8 id1, id2, id4; @@ -3314,8 +3044,8 @@ static int ksz_setup_tc_mode(struct ksz_device *dev, int port, u8 scheduler, FIELD_PREP(MTI_SHAPING_M, shaper)); } -static int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, - struct tc_cbs_qopt_offload *qopt) +int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, + struct tc_cbs_qopt_offload *qopt) { struct ksz_device *dev = ds->priv; int ret; @@ -3380,8 +3110,8 @@ static int ksz_disable_egress_rate_limit(struct ksz_device *dev, int port) return 0; } -static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, - int band) +int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, + int band) { /* Compared to queues, bands prioritize packets differently. In strict * priority mode, the lowest priority is assigned to Queue 0 while the @@ -3390,104 +3120,6 @@ static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, return p->bands - 1 - band; } -static u8 ksz8463_tc_ctrl(int port, int queue) -{ - u8 reg; - - reg = 0xC8 + port * 4; - reg += ((3 - queue) / 2) * 2; - reg++; - reg -= (queue & 1); - return reg; -} - -/** - * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) - * for a port on KSZ88x3 switch - * @dev: Pointer to the KSZ switch device structure - * @port: Port number to configure - * @p: Pointer to offload replace parameters describing ETS bands and mapping - * - * The KSZ88x3 supports two scheduling modes: Strict Priority and - * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: - * - No configurable queue-to-priority mapping - * - No weight adjustment in WFQ mode - * - * This function configures the switch to use strict priority mode by - * clearing the WFQ enable bit for all queues associated with ETS bands. - * If strict priority is not explicitly requested, the switch will default - * to WFQ mode. - * - * Return: 0 on success, or a negative error code on failure - */ -static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, - struct tc_ets_qopt_offload_replace_params *p) -{ - int ret, band; - - /* Only strict priority mode is supported for now. - * WFQ is implicitly enabled when strict mode is disabled. - */ - for (band = 0; band < p->bands; band++) { - int queue = ksz_ets_band_to_queue(p, band); - u8 reg; - - /* Calculate TXQ Split Control register address for this - * port/queue - */ - reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); - if (ksz_is_ksz8463(dev)) - reg = ksz8463_tc_ctrl(port, queue); - - /* Clear WFQ enable bit to select strict priority scheduling */ - ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); - if (ret) - return ret; - } - - return 0; -} - -/** - * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config - * for a port on KSZ88x3 switch - * @dev: Pointer to the KSZ switch device structure - * @port: Port number to reset - * - * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or - * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or - * queue mapping. This function resets the port’s scheduling mode to - * the default, which is WFQ, by enabling the WFQ bit for all queues. - * - * Return: 0 on success, or a negative error code on failure - */ -static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) -{ - int ret, queue; - - /* Iterate over all transmit queues for this port */ - for (queue = 0; queue < dev->info->num_tx_queues; queue++) { - u8 reg; - - /* Calculate TXQ Split Control register address for this - * port/queue - */ - reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); - if (ksz_is_ksz8463(dev)) - reg = ksz8463_tc_ctrl(port, queue); - - /* Set WFQ enable bit to revert back to default scheduling - * mode - */ - ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, - KSZ8873_TXQ_WFQ_ENABLE); - if (ret) - return ret; - } - - return 0; -} - static int ksz_queue_set_strict(struct ksz_device *dev, int port, int queue) { int ret; @@ -3580,8 +3212,8 @@ static int ksz_tc_ets_del(struct ksz_device *dev, int port) return ksz9477_set_default_prio_queue_mapping(dev, port); } -static int ksz_tc_ets_validate(struct ksz_device *dev, int port, - struct tc_ets_qopt_offload_replace_params *p) +int ksz_tc_ets_validate(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p) { int band; @@ -3622,9 +3254,6 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, struct ksz_device *dev = ds->priv; int ret; - if (is_ksz8(dev) && !(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) - return -EOPNOTSUPP; - if (qopt->parent != TC_H_ROOT) { dev_err(dev->dev, "Parent should be \"root\"\n"); return -EOPNOTSUPP; @@ -3636,16 +3265,9 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, if (ret) return ret; - if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) - return ksz88x3_tc_ets_add(dev, port, - &qopt->replace_params); - else - return ksz_tc_ets_add(dev, port, &qopt->replace_params); + return ksz_tc_ets_add(dev, port, &qopt->replace_params); case TC_ETS_DESTROY: - if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) - return ksz88x3_tc_ets_del(dev, port); - else - return ksz_tc_ets_del(dev, port); + return ksz_tc_ets_del(dev, port); case TC_ETS_STATS: case TC_ETS_GRAFT: return -EOPNOTSUPP; @@ -3705,6 +3327,34 @@ int ksz_handle_wake_reason(struct ksz_device *dev, int port) } /** + * ksz_is_port_mac_global_usable - Check if the MAC address on a given port + * can be used as a global address. + * @ds: Pointer to the DSA switch structure. + * @port: The port number on which the MAC address is to be checked. + * + * This function examines the MAC address set on the specified port and + * determines if it can be used as a global address for the switch. + * + * Return: true if the port's MAC address can be used as a global address, false + * otherwise. + */ +static bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port) +{ + struct net_device *user = dsa_to_port(ds, port)->user; + const unsigned char *addr = user->dev_addr; + struct ksz_switch_macaddr *switch_macaddr; + struct ksz_device *dev = ds->priv; + + ASSERT_RTNL(); + + switch_macaddr = dev->switch_macaddr; + if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) + return false; + + return true; +} + +/** * ksz_get_wol - Get Wake-on-LAN settings for a specified port. * @ds: The dsa_switch structure. * @port: The port number. @@ -3898,34 +3548,6 @@ int ksz_port_set_mac_address(struct dsa_switch *ds, int port, } /** - * ksz_is_port_mac_global_usable - Check if the MAC address on a given port - * can be used as a global address. - * @ds: Pointer to the DSA switch structure. - * @port: The port number on which the MAC address is to be checked. - * - * This function examines the MAC address set on the specified port and - * determines if it can be used as a global address for the switch. - * - * Return: true if the port's MAC address can be used as a global address, false - * otherwise. - */ -bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port) -{ - struct net_device *user = dsa_to_port(ds, port)->user; - const unsigned char *addr = user->dev_addr; - struct ksz_switch_macaddr *switch_macaddr; - struct ksz_device *dev = ds->priv; - - ASSERT_RTNL(); - - switch_macaddr = dev->switch_macaddr; - if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) - return false; - - return true; -} - -/** * ksz_switch_macaddr_get - Program the switch's MAC address register. * @ds: DSA switch instance. * @port: Port number. @@ -4132,8 +3754,8 @@ static void ksz_parse_rgmii_delay(struct ksz_device *dev, int port_num, * Returns: If found, the corresponding register value for that drive strength * is returned. Otherwise, -EINVAL is returned indicating an invalid value. */ -static int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, - size_t array_size, int microamp) +int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, + size_t array_size, int microamp) { int i; @@ -4156,9 +3778,9 @@ static int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, * is detected. It lists out all the supported drive strength values for * reference in the error message. */ -static void ksz_drive_strength_error(struct ksz_device *dev, - const struct ksz_drive_strength *array, - size_t array_size, int microamp) +void ksz_drive_strength_error(struct ksz_device *dev, + const struct ksz_drive_strength *array, + size_t array_size, int microamp) { char supported_values[100]; size_t remaining_size; @@ -4185,8 +3807,8 @@ static void ksz_drive_strength_error(struct ksz_device *dev, } /** - * ksz9477_drive_strength_write() - Set the drive strength for specific KSZ9477 - * chip variants. + * ksz_drive_strength_write() - Set the drive strength for specific KSZ9477 + * and the KSZ87xx chip variants. * @dev: ksz device * @props: Array of drive strength properties to be applied * @num_props: Number of properties in the array @@ -4197,9 +3819,9 @@ static void ksz_drive_strength_error(struct ksz_device *dev, * * Return: 0 on successful configuration, a negative error code on failure. */ -static int ksz9477_drive_strength_write(struct ksz_device *dev, - struct ksz_driver_strength_prop *props, - int num_props) +int ksz_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props) { size_t array_size = ARRAY_SIZE(ksz9477_drive_strengths); int i, ret, reg; @@ -4236,130 +3858,6 @@ static int ksz9477_drive_strength_write(struct ksz_device *dev, return ksz_rmw8(dev, reg, mask, val); } -/** - * ksz88x3_drive_strength_write() - Set the drive strength configuration for - * KSZ8863 compatible chip variants. - * @dev: ksz device - * @props: Array of drive strength properties to be set - * @num_props: Number of properties in the array - * - * This function applies the specified drive strength settings to KSZ88X3 chip - * variants (KSZ8873, KSZ8863). - * It ensures the configurations align with what the chip variant supports and - * warns or errors out on unsupported settings. - * - * Return: 0 on success, error code otherwise - */ -static int ksz88x3_drive_strength_write(struct ksz_device *dev, - struct ksz_driver_strength_prop *props, - int num_props) -{ - size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); - int microamp; - int i, ret; - - for (i = 0; i < num_props; i++) { - if (props[i].value == -1 || i == KSZ_DRIVER_STRENGTH_IO) - continue; - - dev_warn(dev->dev, "%s is not supported by this chip variant\n", - props[i].name); - } - - microamp = props[KSZ_DRIVER_STRENGTH_IO].value; - ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size, - microamp); - if (ret < 0) { - ksz_drive_strength_error(dev, ksz88x3_drive_strengths, - array_size, microamp); - return ret; - } - - return ksz_rmw8(dev, KSZ8873_REG_GLOBAL_CTRL_12, - KSZ8873_DRIVE_STRENGTH_16MA, ret); -} - -/** - * ksz_parse_drive_strength() - Extract and apply drive strength configurations - * from device tree properties. - * @dev: ksz device - * - * This function reads the specified drive strength properties from the - * device tree, validates against the supported chip variants, and sets - * them accordingly. An error should be critical here, as the drive strength - * settings are crucial for EMI compliance. - * - * Return: 0 on success, error code otherwise - */ -int ksz_parse_drive_strength(struct ksz_device *dev) -{ - struct ksz_driver_strength_prop of_props[] = { - [KSZ_DRIVER_STRENGTH_HI] = { - .name = "microchip,hi-drive-strength-microamp", - .offset = SW_HI_SPEED_DRIVE_STRENGTH_S, - .value = -1, - }, - [KSZ_DRIVER_STRENGTH_LO] = { - .name = "microchip,lo-drive-strength-microamp", - .offset = SW_LO_SPEED_DRIVE_STRENGTH_S, - .value = -1, - }, - [KSZ_DRIVER_STRENGTH_IO] = { - .name = "microchip,io-drive-strength-microamp", - .offset = 0, /* don't care */ - .value = -1, - }, - }; - struct device_node *np = dev->dev->of_node; - bool have_any_prop = false; - int i, ret; - - for (i = 0; i < ARRAY_SIZE(of_props); i++) { - ret = of_property_read_u32(np, of_props[i].name, - &of_props[i].value); - if (ret && ret != -EINVAL) - dev_warn(dev->dev, "Failed to read %s\n", - of_props[i].name); - if (ret) - continue; - - have_any_prop = true; - } - - if (!have_any_prop) - return 0; - - switch (dev->chip_id) { - case KSZ88X3_CHIP_ID: - return ksz88x3_drive_strength_write(dev, of_props, - ARRAY_SIZE(of_props)); - case KSZ8795_CHIP_ID: - case KSZ8794_CHIP_ID: - case KSZ8765_CHIP_ID: - case KSZ8563_CHIP_ID: - case KSZ8567_CHIP_ID: - case KSZ9477_CHIP_ID: - case KSZ9563_CHIP_ID: - case KSZ9567_CHIP_ID: - case KSZ9893_CHIP_ID: - case KSZ9896_CHIP_ID: - case KSZ9897_CHIP_ID: - case LAN9646_CHIP_ID: - return ksz9477_drive_strength_write(dev, of_props, - ARRAY_SIZE(of_props)); - default: - for (i = 0; i < ARRAY_SIZE(of_props); i++) { - if (of_props[i].value == -1) - continue; - - dev_warn(dev->dev, "%s is not supported by this chip variant\n", - of_props[i].name); - } - } - - return 0; -} - static int ksz8463_configure_straps_spi(struct ksz_device *dev) { struct pinctrl *pinctrl; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index b4a5673ba365..acaf70e6f393 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -14,6 +14,7 @@ #include <linux/phy.h> #include <linux/regmap.h> #include <net/dsa.h> +#include <net/pkt_cls.h> #include <linux/irq.h> #include <linux/platform_data/microchip-ksz.h> @@ -40,6 +41,45 @@ struct vlan_table { u32 table[3]; }; +struct ksz_stats_raw { + u64 rx_hi; + u64 rx_undersize; + u64 rx_fragments; + u64 rx_oversize; + u64 rx_jabbers; + u64 rx_symbol_err; + u64 rx_crc_err; + u64 rx_align_err; + u64 rx_mac_ctrl; + u64 rx_pause; + u64 rx_bcast; + u64 rx_mcast; + u64 rx_ucast; + u64 rx_64_or_less; + u64 rx_65_127; + u64 rx_128_255; + u64 rx_256_511; + u64 rx_512_1023; + u64 rx_1024_1522; + u64 rx_1523_2000; + u64 rx_2001; + u64 tx_hi; + u64 tx_late_col; + u64 tx_pause; + u64 tx_bcast; + u64 tx_mcast; + u64 tx_ucast; + u64 tx_deferred; + u64 tx_total_col; + u64 tx_exc_col; + u64 tx_single_col; + u64 tx_mult_col; + u64 rx_total; + u64 tx_total; + u64 rx_discards; + u64 tx_discards; +}; + struct ksz_port_mib { struct mutex cnt_mutex; /* structure access */ u8 cnt_ptr; @@ -393,12 +433,8 @@ int ksz_switch_resume(struct device *dev); void ksz_teardown(struct dsa_switch *ds); void ksz_init_mib_timer(struct ksz_device *dev); -bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port); void ksz_r_mib_stats64(struct ksz_device *dev, int port); -void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port); void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state); -bool ksz_get_gbit(struct ksz_device *dev, int port); -phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit); extern const struct ksz_chip_data ksz_switch_chips[]; int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, struct netlink_ext_ack *extack); @@ -443,11 +479,15 @@ void ksz_phylink_mac_link_down(struct phylink_config *config, unsigned int mode, phy_interface_t interface); -int ksz_max_mtu(struct dsa_switch *ds, int port); - int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e); +int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, + int band); +int ksz_setup_tc_cbs(struct dsa_switch *ds, int port, + struct tc_cbs_qopt_offload *qopt); +int ksz_tc_ets_validate(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p); int ksz_setup_tc(struct dsa_switch *ds, int port, enum tc_setup_type type, void *type_data); @@ -471,8 +511,37 @@ int ksz_mdio_register(struct ksz_device *dev); int ksz_pirq_setup(struct ksz_device *dev, u8 p); int ksz_girq_setup(struct ksz_device *dev); void ksz_irq_free(struct ksz_irq *kirq); -int ksz_parse_drive_strength(struct ksz_device *dev); -int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port); + +struct ksz_driver_strength_prop { + const char *name; + int offset; + int value; +}; + +enum ksz_driver_strength_type { + KSZ_DRIVER_STRENGTH_HI, + KSZ_DRIVER_STRENGTH_LO, + KSZ_DRIVER_STRENGTH_IO, +}; + +/** + * struct ksz_drive_strength - drive strength mapping + * @reg_val: register value + * @microamp: microamp value + */ +struct ksz_drive_strength { + u32 reg_val; + u32 microamp; +}; + +void ksz_drive_strength_error(struct ksz_device *dev, + const struct ksz_drive_strength *array, + size_t array_size, int microamp); +int ksz_drive_strength_to_reg(const struct ksz_drive_strength *array, + size_t array_size, int microamp); +int ksz_drive_strength_write(struct ksz_device *dev, + struct ksz_driver_strength_prop *props, + int num_props); /* Common register access functions */ static inline struct regmap *ksz_regmap_8(struct ksz_device *dev) diff --git a/drivers/net/dsa/microchip/lan937x_main.c b/drivers/net/dsa/microchip/lan937x_main.c index 8eb5337b0c10..86ce3a86705f 100644 --- a/drivers/net/dsa/microchip/lan937x_main.c +++ b/drivers/net/dsa/microchip/lan937x_main.c @@ -787,10 +787,6 @@ static int lan937x_setup(struct dsa_switch *ds) return ret; } - ret = ksz_parse_drive_strength(dev); - if (ret) - return ret; - /* set broadcast storm protection 10% rate */ storm_mask = BROADCAST_STORM_RATE; storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; @@ -983,7 +979,7 @@ const struct dsa_switch_ops lan937x_switch_ops = { .get_stats64 = ksz_get_stats64, .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = lan937x_change_mtu, - .port_max_mtu = ksz_max_mtu, + .port_max_mtu = ksz9477_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, diff --git a/drivers/net/dsa/qca/qca8k-leds.c b/drivers/net/dsa/qca/qca8k-leds.c index ef496e345a4e..0ada28377f46 100644 --- a/drivers/net/dsa/qca/qca8k-leds.c +++ b/drivers/net/dsa/qca/qca8k-leds.c @@ -457,6 +457,9 @@ qca8k_setup_led_ctrl(struct qca8k_priv *priv) int ret; ports = device_get_named_child_node(priv->dev, "ports"); + if (!ports) + ports = device_get_named_child_node(priv->dev, "ethernet-ports"); + if (!ports) { dev_info(priv->dev, "No ports node specified in device tree!"); return 0; diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c index 103039fe3086..d2fa8ff6a5d0 100644 --- a/drivers/net/dsa/realtek/rtl8366rb.c +++ b/drivers/net/dsa/realtek/rtl8366rb.c @@ -791,11 +791,87 @@ static int rtl8366rb_setup_all_leds_off(struct realtek_priv *priv) return ret; } +static int rtl8366rb_port_set_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + /* Bit 0 enables isolation so set this if we enable isolation + * any of the ports an clear it if we disable on all of them. + */ + if (mask) + mask = RTL8366RB_PORT_ISO_PORTS(mask) | RTL8366RB_PORT_ISO_EN; + + return regmap_write(priv->map, RTL8366RB_PORT_ISO(port), + mask); +} + +static int rtl8366rb_port_add_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + /* We assume isolation bit is on */ + return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), + RTL8366RB_PORT_ISO_PORTS(mask), + RTL8366RB_PORT_ISO_PORTS(mask)); +} + +static int rtl8366rb_port_remove_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), + RTL8366RB_PORT_ISO_PORTS(mask), 0); +} + +static void +rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) +{ + struct realtek_priv *priv = ds->priv; + u32 val; + int i; + + switch (state) { + case BR_STATE_DISABLED: + val = RTL8366RB_STP_STATE_DISABLED; + break; + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + val = RTL8366RB_STP_STATE_BLOCKING; + break; + case BR_STATE_LEARNING: + val = RTL8366RB_STP_STATE_LEARNING; + break; + case BR_STATE_FORWARDING: + val = RTL8366RB_STP_STATE_FORWARDING; + break; + default: + dev_err(priv->dev, "unknown bridge state requested\n"); + return; + } + + /* Set the same status for the port on all the FIDs */ + for (i = 0; i < RTL8366RB_NUM_FIDS; i++) { + regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i, + RTL8366RB_STP_STATE_MASK(port), + RTL8366RB_STP_STATE(port, val)); + } +} + +static int rtl8366rb_port_set_learning(struct realtek_priv *priv, int port, + bool enable) +{ + /* Notice inverted semantics in this register: setting a bit disables + * learning instead of enabling it. + */ + return regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, + BIT(port), enable ? 0 : BIT(port)); +} + static int rtl8366rb_setup(struct dsa_switch *ds) { struct realtek_priv *priv = ds->priv; const struct rtl8366rb_jam_tbl_entry *jam_table; + u32 downports_mask = 0; struct rtl8366rb *rb; + u32 upports_mask = 0; + struct dsa_port *dp; u32 chip_ver = 0; u32 chip_id = 0; int jam_size; @@ -866,20 +942,58 @@ static int rtl8366rb_setup(struct dsa_switch *ds) if (ret) return ret; - /* Isolate all user ports so they can only send packets to itself and the CPU port */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(RTL8366RB_PORT_NUM_CPU)) | - RTL8366RB_PORT_ISO_EN); + /* Start with all ports blocked, including unused ports */ + dsa_switch_for_each_port(dp, ds) { + /* Set the initial STP state of all ports to DISABLED, otherwise + * ports will still forward frames to the CPU despite being + * administratively down by default. + */ + rtl8366rb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); + + /* Start with all ports completely isolated */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, 0); + if (ret) + return ret; + + /* Disable learning */ + ret = rtl8366rb_port_set_learning(priv, dp->index, false); + if (ret) + return ret; + + /* Collect CPU ports. If we support cascade switches, it should + * also include the upstream DSA ports. + */ + if (!dsa_port_is_cpu(dp)) + continue; + + upports_mask |= BIT(dp->index); + } + + /* Configure user ports */ + dsa_switch_for_each_port(dp, ds) { + if (!dsa_port_is_user(dp)) + continue; + + /* Forward only to the CPU */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, upports_mask); + if (ret) + return ret; + + /* If we support cascade switches, it should also include the + * downstream DSA ports. + */ + downports_mask |= BIT(dp->index); + } + + /* Configure CPU ports. If we support cascade switches, this will also + * include DSA ports. + */ + dsa_switch_for_each_cpu_port(dp, ds) { + /* Forward to all user ports */ + ret = rtl8366rb_port_set_isolation(priv, dp->index, downports_mask); if (ret) return ret; } - /* CPU port can send packets to all ports */ - ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(RTL8366RB_PORT_NUM_CPU), - RTL8366RB_PORT_ISO_PORTS(dsa_user_ports(ds)) | - RTL8366RB_PORT_ISO_EN); - if (ret) - return ret; /* Set up the "green ethernet" feature */ ret = rtl8366rb_jam_table(rtl8366rb_green_jam, @@ -938,12 +1052,6 @@ static int rtl8366rb_setup(struct dsa_switch *ds) rb->max_mtu[i] = ETH_DATA_LEN; } - /* Disable learning for all ports */ - ret = regmap_write(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, - RTL8366RB_PORT_ALL); - if (ret) - return ret; - /* Enable auto ageing for all ports */ ret = regmap_write(priv->map, RTL8366RB_SECURITY_CTRL, 0); if (ret) @@ -1184,70 +1292,6 @@ rtl8366rb_port_disable(struct dsa_switch *ds, int port) return; } -static int -rtl8366rb_port_bridge_join(struct dsa_switch *ds, int port, - struct dsa_bridge bridge, - bool *tx_fwd_offload, - struct netlink_ext_ack *extack) -{ - struct realtek_priv *priv = ds->priv; - unsigned int port_bitmap = 0; - int ret, i; - - /* Loop over all other ports than the current one */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - /* Current port handled last */ - if (i == port) - continue; - /* Not on this bridge */ - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Join this port to each other port on the bridge */ - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(port)), - RTL8366RB_PORT_ISO_PORTS(BIT(port))); - if (ret) - dev_err(priv->dev, "failed to join port %d\n", port); - - port_bitmap |= BIT(i); - } - - /* Set the bits for the ports we can access */ - return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), - RTL8366RB_PORT_ISO_PORTS(port_bitmap), - RTL8366RB_PORT_ISO_PORTS(port_bitmap)); -} - -static void -rtl8366rb_port_bridge_leave(struct dsa_switch *ds, int port, - struct dsa_bridge bridge) -{ - struct realtek_priv *priv = ds->priv; - unsigned int port_bitmap = 0; - int ret, i; - - /* Loop over all other ports than this one */ - for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) { - /* Current port handled last */ - if (i == port) - continue; - /* Not on this bridge */ - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Remove this port from any other port on the bridge */ - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i), - RTL8366RB_PORT_ISO_PORTS(BIT(port)), 0); - if (ret) - dev_err(priv->dev, "failed to leave port %d\n", port); - - port_bitmap |= BIT(i); - } - - /* Clear the bits for the ports we can not access, leave ourselves */ - regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port), - RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0); -} - /** * rtl8366rb_drop_untagged() - make the switch drop untagged and C-tagged frames * @priv: SMI state container @@ -1306,59 +1350,6 @@ rtl8366rb_port_pre_bridge_flags(struct dsa_switch *ds, int port, return 0; } -static int -rtl8366rb_port_bridge_flags(struct dsa_switch *ds, int port, - struct switchdev_brport_flags flags, - struct netlink_ext_ack *extack) -{ - struct realtek_priv *priv = ds->priv; - int ret; - - if (flags.mask & BR_LEARNING) { - ret = regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL, - BIT(port), - (flags.val & BR_LEARNING) ? 0 : BIT(port)); - if (ret) - return ret; - } - - return 0; -} - -static void -rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) -{ - struct realtek_priv *priv = ds->priv; - u32 val; - int i; - - switch (state) { - case BR_STATE_DISABLED: - val = RTL8366RB_STP_STATE_DISABLED; - break; - case BR_STATE_BLOCKING: - case BR_STATE_LISTENING: - val = RTL8366RB_STP_STATE_BLOCKING; - break; - case BR_STATE_LEARNING: - val = RTL8366RB_STP_STATE_LEARNING; - break; - case BR_STATE_FORWARDING: - val = RTL8366RB_STP_STATE_FORWARDING; - break; - default: - dev_err(priv->dev, "unknown bridge state requested\n"); - return; - } - - /* Set the same status for the port on all the FIDs */ - for (i = 0; i < RTL8366RB_NUM_FIDS; i++) { - regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i, - RTL8366RB_STP_STATE_MASK(port), - RTL8366RB_STP_STATE(port, val)); - } -} - static void rtl8366rb_port_fast_age(struct dsa_switch *ds, int port) { @@ -1801,15 +1792,15 @@ static const struct dsa_switch_ops rtl8366rb_switch_ops = { .get_strings = rtl8366_get_strings, .get_ethtool_stats = rtl8366_get_ethtool_stats, .get_sset_count = rtl8366_get_sset_count, - .port_bridge_join = rtl8366rb_port_bridge_join, - .port_bridge_leave = rtl8366rb_port_bridge_leave, + .port_bridge_join = rtl83xx_port_bridge_join, + .port_bridge_leave = rtl83xx_port_bridge_leave, .port_vlan_filtering = rtl8366rb_vlan_filtering, .port_vlan_add = rtl8366_vlan_add, .port_vlan_del = rtl8366_vlan_del, .port_enable = rtl8366rb_port_enable, .port_disable = rtl8366rb_port_disable, .port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags, - .port_bridge_flags = rtl8366rb_port_bridge_flags, + .port_bridge_flags = rtl83xx_port_bridge_flags, .port_stp_state_set = rtl8366rb_port_stp_state_set, .port_fast_age = rtl8366rb_port_fast_age, .port_change_mtu = rtl8366rb_change_mtu, @@ -1830,6 +1821,9 @@ static const struct realtek_ops rtl8366rb_ops = { .is_vlan_valid = rtl8366rb_is_vlan_valid, .enable_vlan = rtl8366rb_enable_vlan, .enable_vlan4k = rtl8366rb_enable_vlan4k, + .port_add_isolation = rtl8366rb_port_add_isolation, + .port_remove_isolation = rtl8366rb_port_remove_isolation, + .port_set_learning = rtl8366rb_port_set_learning, .phy_read = rtl8366rb_phy_read, .phy_write = rtl8366rb_phy_write, }; diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 71124ecca92f..90843d52c5a8 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -356,9 +356,6 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, if (!priv->ops->port_add_isolation) return -EOPNOTSUPP; - if (!priv->ops->port_set_learning) - return -EOPNOTSUPP; - dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port); /* Add this port to the isolation group of every other port @@ -396,9 +393,11 @@ int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, goto undo_self_isolation; } - ret = priv->ops->port_set_learning(priv, port, true); - if (ret) - goto undo_efid; + if (priv->ops->port_set_learning) { + ret = priv->ops->port_set_learning(priv, port, true); + if (ret) + goto undo_efid; + } return 0; @@ -443,9 +442,6 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, if (!priv->ops->port_remove_isolation) return; - if (!priv->ops->port_set_learning) - return; - dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port); /* Remove this port from the isolation group of every other @@ -474,11 +470,13 @@ void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, * downstream DSA ports from the isolation group. */ - ret = priv->ops->port_set_learning(priv, port, false); - if (ret) - dev_err(priv->dev, - "failed to disable learning on port %d: %pe\n", - port, ERR_PTR(ret)); + if (priv->ops->port_set_learning) { + ret = priv->ops->port_set_learning(priv, port, false); + if (ret) + dev_err(priv->dev, + "failed to disable learning on port %d: %pe\n", + port, ERR_PTR(ret)); + } /* Remove those ports from the isolation group of this port */ ret = priv->ops->port_remove_isolation(priv, port, mask); diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index fc7341a5cbb7..42174249ef61 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -15,7 +15,7 @@ #include <linux/clk.h> #include <linux/etherdevice.h> #include <linux/ethtool.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/mii.h> diff --git a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c index 3b2951030a38..507db46daf2b 100644 --- a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c +++ b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c @@ -7,7 +7,7 @@ * Keyur Chudgar <kchudgar@apm.com> */ -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include "xgene_enet_main.h" #include "xgene_enet_hw.h" #include "xgene_enet_sgmac.h" diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c index 07a908a2c72f..d560524d317d 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_sp.c @@ -26,6 +26,7 @@ #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/crc32c.h> +#include <linux/slab.h> #include "bnx2x.h" #include "bnx2x_cmn.h" #include "bnx2x_sp.h" @@ -2664,7 +2665,7 @@ static void bnx2x_free_groups(struct list_head *mcast_group_list) struct bnx2x_mcast_elem_group, mcast_group_link); list_del(¤t_mcast_group->mcast_group_link); - free_page((unsigned long)current_mcast_group); + kfree(current_mcast_group); } } @@ -2713,8 +2714,7 @@ static int bnx2x_mcast_enqueue_cmd(struct bnx2x *bp, total_elems = BNX2X_MCAST_BINS_NUM; } while (total_elems > 0) { - elem_group = (struct bnx2x_mcast_elem_group *) - __get_free_page(GFP_ATOMIC | __GFP_ZERO); + elem_group = kzalloc(PAGE_SIZE, GFP_ATOMIC); if (!elem_group) { bnx2x_free_groups(&new_cmd->group_head); kfree(new_cmd); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 858ba844ac51..8320b26c3f72 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -25,6 +25,9 @@ #define DEFAULT_VLAN_ID 1 +static struct notifier_block dpaa2_switch_port_switchdev_nb; +static struct notifier_block dpaa2_switch_port_switchdev_blocking_nb; + static u16 dpaa2_switch_port_get_fdb_id(struct ethsw_port_priv *port_priv) { return port_priv->fdb->fdb_id; @@ -51,6 +54,17 @@ dpaa2_switch_filter_block_get_unused(struct ethsw_core *ethsw) return NULL; } +static struct dpaa2_switch_lag * +dpaa2_switch_lag_get_unused(struct ethsw_core *ethsw) +{ + int i; + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + if (!ethsw->lags[i].in_use) + return ðsw->lags[i]; + return NULL; +} + static bool dpaa2_switch_fdb_in_use_by_others(struct ethsw_core *ethsw, struct dpaa2_switch_fdb *fdb, struct ethsw_port_priv *except) @@ -71,9 +85,9 @@ static struct dpaa2_switch_fdb * dpaa2_switch_fdb_for_join(struct ethsw_port_priv *port_priv, struct net_device *upper_dev) { - struct ethsw_port_priv *other_port_priv; - struct net_device *other_dev; - struct list_head *iter; + struct ethsw_port_priv *other_port_priv = NULL; + struct net_device *other_dev, *other_dev2; + struct list_head *iter, *iter2; /* The below call to netdev_for_each_lower_dev() demands the RTNL lock * being held. Assert on it so that it's easier to catch new code @@ -82,17 +96,32 @@ dpaa2_switch_fdb_for_join(struct ethsw_port_priv *port_priv, ASSERT_RTNL(); /* If part of a bridge, use the FDB of the first dpaa2 switch interface - * to be present in that bridge + * to be present in that bridge. The search descends one level through + * a bridged bond's lowers as well. */ netdev_for_each_lower_dev(upper_dev, other_dev, iter) { - if (!dpaa2_switch_port_dev_check(other_dev)) - continue; + if (netif_is_lag_master(other_dev)) { + netdev_for_each_lower_dev(other_dev, other_dev2, iter2) { + if (!dpaa2_switch_port_dev_check(other_dev2)) + continue; - if (other_dev == port_priv->netdev) - continue; + if (other_dev2 == port_priv->netdev) + continue; - other_port_priv = netdev_priv(other_dev); - return other_port_priv->fdb; + other_port_priv = netdev_priv(other_dev2); + break; + } + } else { + if (!dpaa2_switch_port_dev_check(other_dev)) + continue; + + if (other_dev == port_priv->netdev) + continue; + + other_port_priv = netdev_priv(other_dev); + } + if (other_port_priv) + return other_port_priv->fdb; } return port_priv->fdb; @@ -537,6 +566,103 @@ static int dpaa2_switch_port_fdb_del_mc(struct ethsw_port_priv *port_priv, return err; } +static int dpaa2_switch_port_fdb_add(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + int err; + + if (is_unicast_ether_addr(addr)) + err = dpaa2_switch_port_fdb_add_uc(port_priv, addr); + else + err = dpaa2_switch_port_fdb_add_mc(port_priv, addr); + + return err; +} + +static int dpaa2_switch_port_fdb_del(struct ethsw_port_priv *port_priv, + const unsigned char *addr) +{ + if (is_unicast_ether_addr(addr)) + return dpaa2_switch_port_fdb_del_uc(port_priv, addr); + else + return dpaa2_switch_port_fdb_del_mc(port_priv, addr); +} + +static struct dpaa2_mac_addr * +dpaa2_switch_mac_addr_find(struct list_head *addr_list, + const unsigned char *addr, u16 vid) +{ + struct dpaa2_mac_addr *a; + + list_for_each_entry(a, addr_list, list) + if (ether_addr_equal(a->addr, addr) && a->vid == vid) + return a; + + return NULL; +} + +static int dpaa2_switch_lag_fdb_add(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv = lag->primary; + struct dpaa2_mac_addr *a; + int err = 0; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (a) { + refcount_inc(&a->refcount); + goto out; + } + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) { + err = -ENOMEM; + goto out; + } + + err = dpaa2_switch_port_fdb_add(port_priv, addr); + if (err) { + kfree(a); + goto out; + } + + ether_addr_copy(a->addr, addr); + a->vid = vid; + refcount_set(&a->refcount, 1); + list_add_tail(&a->list, &lag->fdbs); + +out: + mutex_unlock(&lag->fdb_lock); + + return err; +} + +static void dpaa2_switch_lag_fdb_del(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv = lag->primary; + struct dpaa2_mac_addr *a; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (!a) + goto out; + + if (!refcount_dec_and_test(&a->refcount)) + goto out; + + list_del(&a->list); + kfree(a); + + dpaa2_switch_port_fdb_del(port_priv, addr); + +out: + mutex_unlock(&lag->fdb_lock); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1485,6 +1611,33 @@ bool dpaa2_switch_port_dev_check(const struct net_device *netdev) return netdev->netdev_ops == &dpaa2_switch_port_ops; } +static bool dpaa2_switch_foreign_dev_check(const struct net_device *dev, + const struct net_device *foreign_dev) +{ + struct ethsw_port_priv *port_priv = netdev_priv(dev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port; + int i; + + if (netif_is_bridge_master(foreign_dev)) + if (port_priv->fdb->bridge_dev == foreign_dev) + return false; + + if (netif_is_bridge_port(foreign_dev)) { + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + other_port = ethsw->ports[i]; + + if (!other_port) + continue; + if (dpaa2_switch_port_offloads_bridge_port(other_port, + foreign_dev)) + return false; + } + } + + return true; +} + static int dpaa2_switch_port_connect_mac(struct ethsw_port_priv *port_priv) { struct fsl_mc_device *dpsw_port_dev, *dpmac_dev; @@ -1860,51 +2013,32 @@ int dpaa2_switch_port_vlans_add(struct net_device *netdev, vlan->changed); } -static int dpaa2_switch_port_lookup_address(struct net_device *netdev, int is_uc, - const unsigned char *addr) -{ - struct netdev_hw_addr_list *list = (is_uc) ? &netdev->uc : &netdev->mc; - struct netdev_hw_addr *ha; - - netif_addr_lock_bh(netdev); - list_for_each_entry(ha, &list->list, list) { - if (ether_addr_equal(ha->addr, addr)) { - netif_addr_unlock_bh(netdev); - return 1; - } - } - netif_addr_unlock_bh(netdev); - return 0; -} - static int dpaa2_switch_port_mdb_add(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - int err; + struct dpaa2_switch_lag *lag; - /* Check if address is already set on this port */ - if (dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) - return -EEXIST; - - err = dpaa2_switch_port_fdb_add_mc(port_priv, mdb->addr); - if (err) - return err; - - err = dev_mc_add(netdev, mdb->addr); - if (err) { - netdev_err(netdev, "dev_mc_add err %d\n", err); - dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); - } - - return err; + lag = rtnl_dereference(port_priv->lag); + if (lag) + return dpaa2_switch_lag_fdb_add(lag, mdb->addr, mdb->vid); + else + return dpaa2_switch_port_fdb_add(port_priv, mdb->addr); } -static int dpaa2_switch_port_obj_add(struct net_device *netdev, - const struct switchdev_obj *obj) +static int dpaa2_switch_port_obj_add(struct net_device *netdev, const void *ctx, + const struct switchdev_obj *obj, + struct netlink_ext_ack *extack) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_add(netdev, @@ -2000,29 +2134,29 @@ static int dpaa2_switch_port_mdb_del(struct net_device *netdev, const struct switchdev_obj_port_mdb *mdb) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - int err; - - if (!dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr)) - return -ENOENT; - - err = dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr); - if (err) - return err; + struct dpaa2_switch_lag *lag; - err = dev_mc_del(netdev, mdb->addr); - if (err) { - netdev_err(netdev, "dev_mc_del err %d\n", err); - return err; - } + lag = rtnl_dereference(port_priv->lag); + if (lag) + dpaa2_switch_lag_fdb_del(lag, mdb->addr, mdb->vid); + else + dpaa2_switch_port_fdb_del(port_priv, mdb->addr); - return err; + return 0; } -static int dpaa2_switch_port_obj_del(struct net_device *netdev, +static int dpaa2_switch_port_obj_del(struct net_device *netdev, const void *ctx, const struct switchdev_obj *obj) { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); int err; + if (ctx && ctx != port_priv) + return 0; + + if (!dpaa2_switch_port_offloads_bridge_port(port_priv, obj->orig_dev)) + return -EOPNOTSUPP; + switch (obj->id) { case SWITCHDEV_OBJ_ID_PORT_VLAN: err = dpaa2_switch_port_vlans_del(netdev, SWITCHDEV_OBJ_PORT_VLAN(obj)); @@ -2048,6 +2182,21 @@ static int dpaa2_switch_port_attr_set_event(struct net_device *netdev, return notifier_from_errno(err); } +static struct net_device * +dpaa2_switch_port_to_bridge_port(struct ethsw_port_priv *port_priv) +{ + struct dpaa2_switch_lag *lag; + + if (!port_priv->fdb->bridge_dev) + return NULL; + + lag = rtnl_dereference(port_priv->lag); + if (lag) + return lag->bond_dev; + + return port_priv->netdev; +} + static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) @@ -2055,6 +2204,7 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, struct ethsw_port_priv *port_priv = netdev_priv(netdev); struct dpaa2_switch_fdb *old_fdb = port_priv->fdb; struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brport_dev; bool learn_ena; int err; @@ -2066,7 +2216,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, dpaa2_switch_port_set_fdb(port_priv, upper_dev, true); /* Inherit the initial bridge port learning state */ - learn_ena = br_port_flag_is_set(netdev, BR_LEARNING); + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + learn_ena = br_port_flag_is_set(brport_dev, BR_LEARNING); err = dpaa2_switch_port_set_learning(port_priv, learn_ena); port_priv->learn_ena = learn_ena; @@ -2080,8 +2231,11 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, if (err) goto err_egress_flood; - err = switchdev_bridge_port_offload(netdev, netdev, NULL, - NULL, NULL, false, extack); + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + err = switchdev_bridge_port_offload(brport_dev, netdev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb, + false, extack); if (err) goto err_switchdev_offload; @@ -2115,7 +2269,22 @@ static int dpaa2_switch_port_restore_rxvlan(struct net_device *vdev, int vid, vo static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) { - switchdev_bridge_port_unoffload(netdev, NULL, NULL, NULL); + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brport_dev; + + brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); + if (!brport_dev) + return; + + switchdev_bridge_port_unoffload(brport_dev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb); + + /* Make sure that any FDB add/del operations are completed before the + * bridge layout changes + */ + flush_workqueue(ethsw->workqueue); } static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) @@ -2177,30 +2346,53 @@ static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) false); } +static int +dpaa2_switch_have_vlan_upper(struct net_device *upper_dev, + __always_unused struct netdev_nested_priv *priv) +{ + return is_vlan_dev(upper_dev); +} + static int dpaa2_switch_prevent_bridging_with_8021q_upper(struct net_device *netdev) { - struct net_device *upper_dev; - struct list_head *iter; + struct netdev_nested_priv priv = {}; /* RCU read lock not necessary because we have write-side protection - * (rtnl_mutex), however a non-rcu iterator does not exist. + * (rtnl_mutex), however a non-rcu iterator does not exist. Walk the + * entire upper chain so that a VLAN device stacked on a intermediate + * bond is caught too. */ - netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) - if (is_vlan_dev(upper_dev)) - return -EOPNOTSUPP; + if (netdev_walk_all_upper_dev_rcu(netdev, dpaa2_switch_have_vlan_upper, + &priv)) + return -EOPNOTSUPP; return 0; } +static int dpaa2_switch_check_dpsw_instance(struct net_device *dev, + struct netdev_nested_priv *priv) +{ + struct ethsw_port_priv *port_priv = (struct ethsw_port_priv *)priv->data; + struct ethsw_port_priv *other_priv = netdev_priv(dev); + + if (!dpaa2_switch_port_dev_check(dev)) + return 0; + + if (other_priv->ethsw_data == port_priv->ethsw_data) + return 0; + + return 1; +} + static int dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, struct net_device *upper_dev, struct netlink_ext_ack *extack) { struct ethsw_port_priv *port_priv = netdev_priv(netdev); - struct ethsw_port_priv *other_port_priv; - struct net_device *other_dev; - struct list_head *iter; + struct netdev_nested_priv data = { + .data = (void *)port_priv, + }; int err; if (!br_vlan_enabled(upper_dev)) { @@ -2215,6 +2407,70 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, return err; } + err = netdev_walk_all_lower_dev(upper_dev, + dpaa2_switch_check_dpsw_instance, + &data); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Interface from a different DPSW is in the bridge already"); + return -EINVAL; + } + + return 0; +} + +static int dpaa2_switch_pre_lag_join(struct net_device *netdev, + struct net_device *upper_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv; + struct dpaa2_switch_lag *lag = NULL; + struct dpsw_lag_cfg cfg = {0}; + struct net_device *other_dev; + int i, num_ifs = 0, err; + struct list_head *iter; + + if (!(ethsw->features & ETHSW_FEATURE_LAG_OFFLOAD)) { + NL_SET_ERR_MSG_MOD(extack, + "LAG offload is supported only for DPSW >= v8.13"); + return -EOPNOTSUPP; + } + + if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { + NL_SET_ERR_MSG_MOD(extack, + "Can only offload LAG using hash TX type"); + return -EOPNOTSUPP; + } + + if (info->hash_type != NETDEV_LAG_HASH_L23) { + NL_SET_ERR_MSG_MOD(extack, "Can only offload L2+L3 Tx hash"); + return -EOPNOTSUPP; + } + + if (!dpaa2_switch_port_has_mac(port_priv)) { + NL_SET_ERR_MSG_MOD(extack, + "Only switch interfaces connected to MACs can be under a LAG"); + return -EINVAL; + } + + if (vlan_uses_dev(upper_dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot join a LAG upper that has a VLAN"); + return -EOPNOTSUPP; + } + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (!ethsw->lags[i].in_use) + continue; + if (ethsw->lags[i].bond_dev != upper_dev) + continue; + lag = ðsw->lags[i]; + break; + } + netdev_for_each_lower_dev(upper_dev, other_dev, iter) { if (!dpaa2_switch_port_dev_check(other_dev)) continue; @@ -2222,9 +2478,248 @@ dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev, other_port_priv = netdev_priv(other_dev); if (other_port_priv->ethsw_data != port_priv->ethsw_data) { NL_SET_ERR_MSG_MOD(extack, - "Interface from a different DPSW is in the bridge already"); + "Interface from a different DPSW is in the bond already"); return -EINVAL; } + + cfg.if_id[num_ifs++] = other_port_priv->idx; + + if (num_ifs >= DPSW_MAX_LAG_IFS) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add more than 8 DPAA2 switch ports under the same bond"); + return -EINVAL; + } + } + + if (lag) { + cfg.group_id = lag->id; + cfg.if_id[num_ifs++] = port_priv->idx; + cfg.num_ifs = num_ifs; + cfg.phase = DPSW_LAG_SET_PHASE_CHECK; + + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); + if (err) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot offload LAG configuration"); + return -EOPNOTSUPP; + } + } + + return 0; +} + +static void dpaa2_switch_port_set_lag_group(struct ethsw_port_priv *port_priv, + struct net_device *bond_dev) +{ + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port_priv = NULL; + struct dpaa2_switch_lag *lag = NULL; + struct dpaa2_switch_lag *other_lag; + struct net_device *other_dev; + struct list_head *iter; + + netdev_for_each_lower_dev(bond_dev, other_dev, iter) { + if (!dpaa2_switch_port_dev_check(other_dev)) + continue; + + other_port_priv = netdev_priv(other_dev); + other_lag = rtnl_dereference(other_port_priv->lag); + if (!other_lag) + continue; + + if (other_lag->bond_dev == bond_dev) { + rcu_assign_pointer(port_priv->lag, other_lag); + return; + } + } + + /* This is the first interface to be added under a bond device. Find an + * unused LAG group. No need to check for NULL since there are the same + * amount of DPSW ports as LAG groups, meaning that each port can have + * its own LAG group. + */ + lag = dpaa2_switch_lag_get_unused(ethsw); + lag->in_use = true; + lag->bond_dev = bond_dev; + lag->primary = port_priv; + rcu_assign_pointer(port_priv->lag, lag); +} + +static bool dpaa2_switch_port_in_lag(struct ethsw_port_priv *port_priv, + struct net_device *bond_dev) +{ + struct dpaa2_switch_lag *lag; + + if (!port_priv) + return false; + + lag = rtnl_dereference(port_priv->lag); + return lag && lag->bond_dev == bond_dev; +} + +static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, + struct ethsw_core *ethsw) +{ + struct dpaa2_switch_lag *lag = ðsw->lags[lag_id - 1]; + struct ethsw_port_priv *primary, *port_priv; + struct ethsw_port_priv *new_primary = NULL; + struct dpsw_lag_cfg cfg = {0}; + struct dpaa2_mac_addr *a; + u8 num_ifs = 0; + int err, i; + + cfg.group_id = lag_id; + + /* Determine the primary port. The caller clears ->lag on the port that + * is leaving, so a NULL ->lag on the current primary means it is the + * one leaving: elect the first remaining member as the new primary. + * Otherwise keep the current primary. + */ + if (rtnl_dereference(lag->primary->lag)) { + primary = lag->primary; + } else { + primary = NULL; + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + if (dpaa2_switch_port_in_lag(ethsw->ports[i], bond_dev)) { + new_primary = ethsw->ports[i]; + primary = new_primary; + break; + } + } + } + + /* Build the interface list, always placing the primary first */ + if (primary) + cfg.if_id[num_ifs++] = primary->idx; + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + port_priv = ethsw->ports[i]; + if (port_priv == primary) + continue; + if (!dpaa2_switch_port_in_lag(port_priv, bond_dev)) + continue; + + cfg.if_id[num_ifs++] = port_priv->idx; + } + cfg.num_ifs = num_ifs; + + /* No more interfaces under this LAG group, mark it as not in use. Wait + * for a grace period so that any readers of the lag structure finished. + */ + if (!num_ifs) { + synchronize_net(); + + lag->bond_dev = NULL; + lag->primary = NULL; + lag->in_use = false; + } + + /* When the primary changes, migrate the FDB entries from the old + * primary to the new one: remove them before reconfiguring the LAG in + * hardware and re-add them on the new primary afterwards. We do not + * touch any refcounting since the intention is to change the HW entry, + * not the parallel software tracking. + */ + if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) + dpaa2_switch_port_fdb_del(lag->primary, a->addr); + mutex_unlock(&lag->fdb_lock); + } + + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); + if (err) + return err; + + if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) { + err = dpaa2_switch_port_fdb_add(new_primary, a->addr); + if (err) + netdev_err(new_primary->netdev, "Unable to migrate FDB\n"); + } + mutex_unlock(&lag->fdb_lock); + + synchronize_net(); + lag->primary = new_primary; + } + + return 0; +} + +static int dpaa2_switch_port_bond_join(struct net_device *netdev, + struct net_device *bond_dev, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *bridge_dev; + struct dpaa2_switch_lag *lag; + int err = 0; + u8 lag_id; + + /* Setup the port_priv->lag pointer for this switch port */ + dpaa2_switch_port_set_lag_group(port_priv, bond_dev); + + /* Create the LAG configuration and apply it in MC */ + lag = rtnl_dereference(port_priv->lag); + lag_id = lag->id; + err = dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); + if (err) + goto err_lag_cfg; + + /* If the bond device is a switch port, join the bridge as well */ + bridge_dev = netdev_master_upper_dev_get(bond_dev); + if (!bridge_dev || !netif_is_bridge_master(bridge_dev)) + return 0; + + err = dpaa2_switch_port_bridge_join(netdev, bridge_dev, extack); + if (err) + goto err_lag_cfg; + + return err; + +err_lag_cfg: + rcu_assign_pointer(port_priv->lag, NULL); + dpaa2_switch_set_lag_cfg(bond_dev, lag_id, ethsw); + + return err; +} + +static int dpaa2_switch_port_bond_leave(struct net_device *netdev, + struct net_device *bond_dev) +{ + struct net_device *bridge_dev = netdev_master_upper_dev_get(bond_dev); + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct dpaa2_switch_lag *lag = rtnl_dereference(port_priv->lag); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct net_device *brpdev; + bool learn_ena; + int err; + + if (!lag) + return 0; + + /* Recreate the LAG configuration for the LAG group that we left. */ + rcu_assign_pointer(port_priv->lag, NULL); + dpaa2_switch_set_lag_cfg(bond_dev, lag->id, ethsw); + + if (bridge_dev && netif_is_bridge_master(bridge_dev)) { + /* Make sure that the new primary inherits the learning state */ + if (lag->primary) { + brpdev = dpaa2_switch_port_to_bridge_port(lag->primary); + learn_ena = br_port_flag_is_set(brpdev, BR_LEARNING); + err = dpaa2_switch_port_set_learning(lag->primary, + learn_ena); + if (err) + return err; + lag->primary->learn_ena = learn_ena; + } + + /* In case the bond is a bridge port, leave the upper bridge as + * well. + */ + return dpaa2_switch_port_bridge_leave(netdev); } return 0; @@ -2234,8 +2729,8 @@ static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, struct netdev_notifier_changeupper_info *info) { struct ethsw_port_priv *port_priv; + struct net_device *upper_dev, *br; struct netlink_ext_ack *extack; - struct net_device *upper_dev; int err; if (!dpaa2_switch_port_dev_check(netdev)) @@ -2252,6 +2747,24 @@ static int dpaa2_switch_port_prechangeupper(struct net_device *netdev, if (!info->linking) dpaa2_switch_port_pre_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (!info->linking) { + if (netif_is_bridge_port(upper_dev)) + dpaa2_switch_port_pre_bridge_leave(netdev); + return 0; + } + + if (netif_is_bridge_port(upper_dev)) { + br = netdev_master_upper_dev_get(upper_dev); + err = dpaa2_switch_prechangeupper_sanity_checks(netdev, + br, + extack); + if (err) + return err; + } + + return dpaa2_switch_pre_lag_join(netdev, upper_dev, + info->upper_info, extack); } else if (is_vlan_dev(upper_dev)) { port_priv = netdev_priv(netdev); if (port_priv->fdb->bridge_dev) { @@ -2283,6 +2796,80 @@ static int dpaa2_switch_port_changeupper(struct net_device *netdev, extack); else return dpaa2_switch_port_bridge_leave(netdev); + } else if (netif_is_lag_master(upper_dev)) { + if (info->linking) + return dpaa2_switch_port_bond_join(netdev, upper_dev, + info->upper_info, + extack); + else + return dpaa2_switch_port_bond_leave(netdev, upper_dev); + } + + return 0; +} + +static int +dpaa2_switch_lag_prechangeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_prechangeupper(lower, info); + if (err) + return err; + } + + return err; +} + +static int +dpaa2_switch_lag_changeupper(struct net_device *netdev, + struct netdev_notifier_changeupper_info *info) +{ + struct net_device *lower; + struct list_head *iter; + int err = 0; + + if (!netif_is_lag_master(netdev)) + return 0; + + netdev_for_each_lower_dev(netdev, lower, iter) { + if (!dpaa2_switch_port_dev_check(lower)) + continue; + + err = dpaa2_switch_port_changeupper(lower, info); + if (err) + return err; + } + + return 0; +} + +static int +dpaa2_switch_port_changelowerstate(struct net_device *netdev, + struct netdev_lag_lower_state_info *linfo) +{ + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + int err; + + if (!rtnl_dereference(port_priv->lag)) + return 0; + + err = dpsw_if_set_lag_state(ethsw->mc_io, 0, ethsw->dpsw_handle, + port_priv->idx, linfo->tx_enabled ? 1 : 0); + if (err) { + netdev_err(netdev, "dpsw_if_set_lag_state() = %d\n", err); + return err; } return 0; @@ -2292,6 +2879,7 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + struct netdev_notifier_changelowerstate_info *info; int err = 0; switch (event) { @@ -2300,13 +2888,29 @@ static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb, if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_prechangeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; case NETDEV_CHANGEUPPER: err = dpaa2_switch_port_changeupper(netdev, ptr); if (err) return notifier_from_errno(err); + err = dpaa2_switch_lag_changeupper(netdev, ptr); + if (err) + return notifier_from_errno(err); + break; + case NETDEV_CHANGELOWERSTATE: + info = ptr; + if (!dpaa2_switch_port_dev_check(netdev)) + break; + + err = dpaa2_switch_port_changelowerstate(netdev, + info->lower_state_info); + return notifier_from_errno(err); } return NOTIFY_DONE; @@ -2316,80 +2920,97 @@ struct ethsw_switchdev_event_work { struct work_struct work; struct switchdev_notifier_fdb_info fdb_info; struct net_device *dev; + struct net_device *orig_dev; unsigned long event; + u16 vid; }; static void dpaa2_switch_event_work(struct work_struct *work) { struct ethsw_switchdev_event_work *switchdev_work = container_of(work, struct ethsw_switchdev_event_work, work); + struct net_device *orig_dev = switchdev_work->orig_dev; struct net_device *dev = switchdev_work->dev; + struct ethsw_port_priv *port_priv = netdev_priv(dev); struct switchdev_notifier_fdb_info *fdb_info; + struct dpaa2_switch_lag *lag; int err; - rtnl_lock(); fdb_info = &switchdev_work->fdb_info; + /* The lag structures are freed only from dpaa2_switch_remove(), which + * first flushes this workqueue, so the pointer stays valid for the + * lifetime of the work item. Only the dereference needs the RCU + * read-side lock; the FDB helpers below can sleep and must run outside + * of it. + */ + rcu_read_lock(); + lag = rcu_dereference(port_priv->lag); + rcu_read_unlock(); + switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; - if (is_unicast_ether_addr(fdb_info->addr)) - err = dpaa2_switch_port_fdb_add_uc(netdev_priv(dev), - fdb_info->addr); + if (lag) + err = dpaa2_switch_lag_fdb_add(lag, fdb_info->addr, + switchdev_work->vid); else - err = dpaa2_switch_port_fdb_add_mc(netdev_priv(dev), - fdb_info->addr); + err = dpaa2_switch_port_fdb_add(port_priv, + fdb_info->addr); if (err) break; fdb_info->offloaded = true; - call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev, + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, orig_dev, &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - if (!fdb_info->added_by_user || fdb_info->is_local) - break; - if (is_unicast_ether_addr(fdb_info->addr)) - dpaa2_switch_port_fdb_del_uc(netdev_priv(dev), fdb_info->addr); + if (lag) + dpaa2_switch_lag_fdb_del(lag, fdb_info->addr, + switchdev_work->vid); else - dpaa2_switch_port_fdb_del_mc(netdev_priv(dev), fdb_info->addr); + dpaa2_switch_port_fdb_del(port_priv, fdb_info->addr); break; } - rtnl_unlock(); kfree(switchdev_work->fdb_info.addr); kfree(switchdev_work); dev_put(dev); + dev_put(orig_dev); } -/* Called under rcu_read_lock() */ -static int dpaa2_switch_port_event(struct notifier_block *nb, - unsigned long event, void *ptr) +static int +dpaa2_switch_port_fdb_event(struct net_device *dev, + struct net_device *orig_dev, + unsigned long event, const void *ctx, + const struct switchdev_notifier_fdb_info *fdb_info) { - struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct ethsw_port_priv *port_priv = netdev_priv(dev); struct ethsw_switchdev_event_work *switchdev_work; - struct switchdev_notifier_fdb_info *fdb_info = ptr; struct ethsw_core *ethsw = port_priv->ethsw_data; - if (event == SWITCHDEV_PORT_ATTR_SET) - return dpaa2_switch_port_attr_set_event(dev, ptr); + if (ctx && ctx != port_priv) + return 0; - if (!dpaa2_switch_port_dev_check(dev)) - return NOTIFY_DONE; + /* For the moment, do nothing with entries towards foreign devices */ + if (dpaa2_switch_foreign_dev_check(dev, orig_dev)) + return 0; + + if (!fdb_info->added_by_user || fdb_info->is_local) + return 0; switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) - return NOTIFY_BAD; + return -ENOMEM; INIT_WORK(&switchdev_work->work, dpaa2_switch_event_work); switchdev_work->dev = dev; switchdev_work->event = event; + switchdev_work->orig_dev = orig_dev; + switchdev_work->vid = fdb_info->vid; switch (event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - memcpy(&switchdev_work->fdb_info, ptr, + memcpy(&switchdev_work->fdb_info, fdb_info, sizeof(switchdev_work->fdb_info)); switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); if (!switchdev_work->fdb_info.addr) @@ -2400,52 +3021,61 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, /* Take a reference on the device to avoid being freed. */ dev_hold(dev); + dev_hold(orig_dev); break; default: kfree(switchdev_work); - return NOTIFY_DONE; + return 0; } queue_work(ethsw->workqueue, &switchdev_work->work); - return NOTIFY_DONE; + return 0; err_addr_alloc: kfree(switchdev_work); - return NOTIFY_BAD; + return -ENOMEM; } -static int dpaa2_switch_port_obj_event(unsigned long event, - struct net_device *netdev, - struct switchdev_notifier_port_obj_info *port_obj_info) +/* Called under rcu_read_lock() */ +static int dpaa2_switch_port_event(struct notifier_block *nb, + unsigned long event, void *ptr) { - int err = -EOPNOTSUPP; - - if (!dpaa2_switch_port_dev_check(netdev)) - return NOTIFY_DONE; + struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { - case SWITCHDEV_PORT_OBJ_ADD: - err = dpaa2_switch_port_obj_add(netdev, port_obj_info->obj); - break; - case SWITCHDEV_PORT_OBJ_DEL: - err = dpaa2_switch_port_obj_del(netdev, port_obj_info->obj); - break; + case SWITCHDEV_PORT_ATTR_SET: + return dpaa2_switch_port_attr_set_event(dev, ptr); + case SWITCHDEV_FDB_ADD_TO_DEVICE: + case SWITCHDEV_FDB_DEL_TO_DEVICE: + err = switchdev_handle_fdb_event_to_device(dev, event, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_foreign_dev_check, + dpaa2_switch_port_fdb_event); + return notifier_from_errno(err); + default: + return NOTIFY_DONE; } - - port_obj_info->handled = true; - return notifier_from_errno(err); } static int dpaa2_switch_port_blocking_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { case SWITCHDEV_PORT_OBJ_ADD: + err = switchdev_handle_port_obj_add(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_add); + return notifier_from_errno(err); case SWITCHDEV_PORT_OBJ_DEL: - return dpaa2_switch_port_obj_event(event, dev, ptr); + err = switchdev_handle_port_obj_del(dev, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_port_obj_del); + return notifier_from_errno(err); case SWITCHDEV_PORT_ATTR_SET: return dpaa2_switch_port_attr_set_event(dev, ptr); } @@ -2490,19 +3120,22 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, dma_addr_t addr = dpaa2_fd_get_addr(fd); struct ethsw_core *ethsw = fq->ethsw; struct ethsw_port_priv *port_priv; + struct dpaa2_switch_lag *lag; struct net_device *netdev; struct vlan_ethhdr *hdr; struct sk_buff *skb; u16 vlan_tci, vid; int if_id, err; void *vaddr; + u64 flc; vaddr = dpaa2_iova_to_virt(ethsw->iommu_domain, addr); dma_unmap_page(ethsw->dev, addr, DPAA2_SWITCH_RX_BUF_SIZE, DMA_FROM_DEVICE); /* get switch ingress interface ID */ - if_id = upper_32_bits(dpaa2_fd_get_flc(fd)) & 0x0000FFFF; + flc = dpaa2_fd_get_flc(fd); + if_id = DPAA2_ETHSW_FLC_IF_ID(flc); if (if_id >= ethsw->sw_attr.num_ifs) { dev_err(ethsw->dev, "Frame received from unknown interface!\n"); goto err_free_fd; @@ -2541,12 +3174,20 @@ static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq, } } - skb->dev = netdev; + rcu_read_lock(); + + lag = rcu_dereference(port_priv->lag); + if (DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) && lag) + skb->dev = lag->bond_dev; + else + skb->dev = netdev; skb->protocol = eth_type_trans(skb, skb->dev); /* Setup the offload_fwd_mark only if the port is under a bridge */ skb->offload_fwd_mark = !!(port_priv->fdb->bridge_dev); + rcu_read_unlock(); + netif_receive_skb(skb); return; @@ -2561,6 +3202,9 @@ static void dpaa2_switch_detect_features(struct ethsw_core *ethsw) if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 6)) ethsw->features |= ETHSW_FEATURE_MAC_ADDR; + + if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 13)) + ethsw->features |= ETHSW_FEATURE_LAG_OFFLOAD; } static int dpaa2_switch_setup_fqs(struct ethsw_core *ethsw) @@ -3195,17 +3839,15 @@ err_close: return err; } -/* Add an ACL to redirect frames with specific destination MAC address to - * control interface - */ +/* Add an ACL to redirect frames to control interface based on the dst MAC */ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, - const char *mac) + const u8 *mac, const u8 *mask) { struct dpaa2_switch_acl_entry acl_entry = {0}; /* Match on the destination MAC address */ ether_addr_copy(acl_entry.key.match.l2_dest_mac, mac); - eth_broadcast_addr(acl_entry.key.mask.l2_dest_mac); + ether_addr_copy(acl_entry.key.mask.l2_dest_mac, mask); /* Trap to CPU */ acl_entry.cfg.precedence = 0; @@ -3216,7 +3858,8 @@ static int dpaa2_switch_port_trap_mac_addr(struct ethsw_port_priv *port_priv, static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) { - const char stpa[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mac[ETH_ALEN] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}; + const u8 ll_mask[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xf0}; struct switchdev_obj_port_vlan vlan = { .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN, .vid = DEFAULT_VLAN_ID, @@ -3291,7 +3934,7 @@ static int dpaa2_switch_port_init(struct ethsw_port_priv *port_priv, u16 port) if (err) return err; - err = dpaa2_switch_port_trap_mac_addr(port_priv, stpa); + err = dpaa2_switch_port_trap_mac_addr(port_priv, ll_mac, ll_mask); if (err) return err; @@ -3331,6 +3974,9 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) dev = &sw_dev->dev; ethsw = dev_get_drvdata(dev); + /* Make sure that all events were handled before we kfree anything */ + flush_workqueue(ethsw->workqueue); + dpaa2_switch_teardown_irqs(sw_dev); dpsw_disable(ethsw->mc_io, 0, ethsw->dpsw_handle); @@ -3344,12 +3990,15 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) for (i = 0; i < DPAA2_SWITCH_RX_NUM_FQS; i++) netif_napi_del(ðsw->fq[i].napi); - for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { dpaa2_switch_remove_port(ethsw, i); + mutex_destroy(ðsw->lags[i].fdb_lock); + } kfree(ethsw->fdbs); kfree(ethsw->filter_blocks); kfree(ethsw->ports); + kfree(ethsw->lags); dpaa2_switch_teardown(sw_dev); @@ -3377,6 +4026,7 @@ static int dpaa2_switch_probe_port(struct ethsw_core *ethsw, port_priv = netdev_priv(port_netdev); port_priv->netdev = port_netdev; port_priv->ethsw_data = ethsw; + rcu_assign_pointer(port_priv->lag, NULL); mutex_init(&port_priv->mac_lock); @@ -3484,6 +4134,21 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) goto err_free_fdbs; } + ethsw->lags = kcalloc(ethsw->sw_attr.num_ifs, sizeof(*ethsw->lags), + GFP_KERNEL); + if (!ethsw->lags) { + err = -ENOMEM; + goto err_free_filter; + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + ethsw->lags[i].bond_dev = NULL; + ethsw->lags[i].ethsw = ethsw; + ethsw->lags[i].id = i + 1; + ethsw->lags[i].in_use = 0; + mutex_init(ðsw->lags[i].fdb_lock); + INIT_LIST_HEAD(ðsw->lags[i].fdbs); + } + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { err = dpaa2_switch_probe_port(ethsw, i); if (err) @@ -3530,6 +4195,10 @@ err_stop: err_free_netdev: for (i--; i >= 0; i--) dpaa2_switch_remove_port(ethsw, i); + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + mutex_destroy(ðsw->lags[i].fdb_lock); + kfree(ethsw->lags); +err_free_filter: kfree(ethsw->filter_blocks); err_free_fdbs: kfree(ethsw->fdbs); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index 42b3ca73f55d..63b702b0000c 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -41,7 +41,8 @@ #define ETHSW_MAX_FRAME_LENGTH (DPAA2_MFL - VLAN_ETH_HLEN - ETH_FCS_LEN) #define ETHSW_L2_MAX_FRM(mtu) ((mtu) + VLAN_ETH_HLEN + ETH_FCS_LEN) -#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_MAC_ADDR BIT(0) +#define ETHSW_FEATURE_LAG_OFFLOAD BIT(1) /* Number of receive queues (one RX and one TX_CONF) */ #define DPAA2_SWITCH_RX_NUM_FQS 2 @@ -86,6 +87,9 @@ #define DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE 256 +#define DPAA2_ETHSW_FLC_IF_ID(flc) (((flc) >> 32) & GENMASK(15, 0)) +#define DPAA2_ETHSW_FLC_IMPRECISE_IF_ID(flc) ((flc) & BIT_ULL(63)) + extern const struct ethtool_ops dpaa2_switch_port_ethtool_ops; struct ethsw_core; @@ -99,12 +103,30 @@ struct dpaa2_switch_fq { u32 fqid; }; +struct dpaa2_mac_addr { + unsigned char addr[ETH_ALEN]; + u16 vid; + refcount_t refcount; + struct list_head list; +}; + struct dpaa2_switch_fdb { struct net_device *bridge_dev; u16 fdb_id; bool in_use; }; +struct dpaa2_switch_lag { + struct ethsw_core *ethsw; + struct net_device *bond_dev; + bool in_use; + u8 id; + struct ethsw_port_priv *primary; + /* Protects the list of fdbs installed on this LAG */ + struct mutex fdb_lock; + struct list_head fdbs; +}; + struct dpaa2_switch_acl_entry { struct list_head list; u16 prio; @@ -163,6 +185,8 @@ struct ethsw_port_priv { struct dpaa2_mac *mac; /* Protects against changes to port_priv->mac */ struct mutex mac_lock; + + struct dpaa2_switch_lag __rcu *lag; }; /* Switch data */ @@ -190,6 +214,8 @@ struct ethsw_core { struct dpaa2_switch_fdb *fdbs; struct dpaa2_switch_filter_block *filter_blocks; u16 mirror_port; + + struct dpaa2_switch_lag *lags; }; static inline int dpaa2_switch_get_index(struct ethsw_core *ethsw, @@ -274,4 +300,18 @@ int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block, int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block, struct ethsw_port_priv *port_priv); + +static inline bool +dpaa2_switch_port_offloads_bridge_port(struct ethsw_port_priv *port_priv, + const struct net_device *dev) +{ + struct dpaa2_switch_lag *lag = rcu_dereference_rtnl(port_priv->lag); + + if (lag && lag->bond_dev == dev) + return true; + if (port_priv->netdev == dev) + return true; + return false; +} + #endif /* __ETHSW_H */ diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h index 397d55f2bd99..9a2055c64983 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h @@ -12,7 +12,7 @@ /* DPSW Version */ #define DPSW_VER_MAJOR 8 -#define DPSW_VER_MINOR 9 +#define DPSW_VER_MINOR 13 #define DPSW_CMD_BASE_VERSION 1 #define DPSW_CMD_VERSION_2 2 @@ -92,11 +92,14 @@ #define DPSW_CMDID_CTRL_IF_SET_POOLS DPSW_CMD_ID(0x0A1) #define DPSW_CMDID_CTRL_IF_ENABLE DPSW_CMD_ID(0x0A2) #define DPSW_CMDID_CTRL_IF_DISABLE DPSW_CMD_ID(0x0A3) +#define DPSW_CMDID_SET_LAG DPSW_CMD_V2(0x0A4) #define DPSW_CMDID_CTRL_IF_SET_QUEUE DPSW_CMD_ID(0x0A6) #define DPSW_CMDID_SET_EGRESS_FLOOD DPSW_CMD_ID(0x0AC) #define DPSW_CMDID_IF_SET_LEARNING_MODE DPSW_CMD_ID(0x0AD) +#define DPSW_CMDID_IF_SET_LAG_STATE DPSW_CMD_ID(0x0B0) + /* Macros for accessing command fields smaller than 1byte */ #define DPSW_MASK(field) \ GENMASK(DPSW_##field##_SHIFT + DPSW_##field##_SIZE - 1, \ @@ -552,5 +555,18 @@ struct dpsw_cmd_if_reflection { /* only 2 bits from the LSB */ u8 filter; }; + +struct dpsw_cmd_lag { + u8 group_id; + u8 num_ifs; + u8 pad[6]; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +struct dpsw_cmd_if_set_lag_state { + __le16 if_id; + u8 tx_enabled; +}; #pragma pack(pop) #endif /* __FSL_DPSW_CMD_H */ diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.c b/drivers/net/ethernet/freescale/dpaa2/dpsw.c index ab921d75deb2..f75cbdce42ba 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.c @@ -1659,3 +1659,63 @@ int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, return mc_send_command(mc_io, &cmd); } + +/** + * dpsw_lag_set() - Set LAG configuration + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @cfg: pointer to LAG configuration + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg) +{ + struct fsl_mc_command cmd = { 0 }; + struct dpsw_cmd_lag *cmd_params; + int i = 0; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_SET_LAG, cmd_flags, token); + + if (cfg->num_ifs > DPSW_MAX_LAG_IFS) + return -EOPNOTSUPP; + + cmd_params = (struct dpsw_cmd_lag *)cmd.params; + cmd_params->group_id = cfg->group_id; + cmd_params->num_ifs = cfg->num_ifs; + cmd_params->phase = cfg->phase; + + for (i = 0; i < cfg->num_ifs; i++) + cmd_params->if_id[i] = cfg->if_id[i]; + + return mc_send_command(mc_io, &cmd); +} + +/** + * dpsw_if_set_lag_state() - Change per port LAG state + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPSW object + * @if_id: ID of the switch interface + * @tx_enabled: Value of the per port LAG state + * - 0 if the interface will not be active as part of the LAG group + * - 1 if the interface will be active in the LAG group + * + * Return: '0' on Success; Error code otherwise. + */ +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled) +{ + struct dpsw_cmd_if_set_lag_state *cmd_params; + struct fsl_mc_command cmd = { 0 }; + + cmd.header = mc_encode_cmd_header(DPSW_CMDID_IF_SET_LAG_STATE, + cmd_flags, token); + + cmd_params = (struct dpsw_cmd_if_set_lag_state *)cmd.params; + cmd_params->if_id = cpu_to_le16(if_id); + cmd_params->tx_enabled = tx_enabled; + + return mc_send_command(mc_io, &cmd); +} diff --git a/drivers/net/ethernet/freescale/dpaa2/dpsw.h b/drivers/net/ethernet/freescale/dpaa2/dpsw.h index b90bd363f47a..89f0267de8e9 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpsw.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpsw.h @@ -20,6 +20,8 @@ struct fsl_mc_io; #define DPSW_MAX_IF 64 +#define DPSW_MAX_LAG_IFS 8 + int dpsw_open(struct fsl_mc_io *mc_io, u32 cmd_flags, int dpsw_id, u16 *token); int dpsw_close(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token); @@ -788,4 +790,32 @@ int dpsw_if_add_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, int dpsw_if_remove_reflection(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, u16 if_id, const struct dpsw_reflection_cfg *cfg); + +/* Link Aggregation Group configuration */ + +#define DPSW_LAG_SET_PHASE_APPLY 0 +#define DPSW_LAG_SET_PHASE_CHECK 1 + +/** + * struct dpsw_lag_cfg - Configuration structure for a LAG group + * @group_id: Link aggregation group ID. Valid values are in the + * [1, DPSW_MAX_LAG_IFS] range. + * @num_ifs: Number of interfaces in this LAG group, valid range is + * [0, DPSW_MAX_LAG_IFS]. + * @if_id: Array containing the interface IDs of the ports part of a LAG group + * @phase: Use DPSW_LAG_SET_PHASE_APPLY for LAG configuration processing or + * DPSW_LAG_SET_PHASE_CHECK for LAG configuration validation. + */ +struct dpsw_lag_cfg { + u8 group_id; + u8 num_ifs; + u8 if_id[DPSW_MAX_LAG_IFS]; + u8 phase; +}; + +int dpsw_lag_set(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + const struct dpsw_lag_cfg *cfg); + +int dpsw_if_set_lag_state(struct fsl_mc_io *mc_io, u32 cmd_flags, u16 token, + u16 if_id, u8 tx_enabled); #endif /* __FSL_DPSW_H */ diff --git a/drivers/net/ethernet/hisilicon/hns3/hnae3.h b/drivers/net/ethernet/hisilicon/hns3/hnae3.h index a8798eecd9fb..4286af9239b0 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hnae3.h +++ b/drivers/net/ethernet/hisilicon/hns3/hnae3.h @@ -602,6 +602,10 @@ typedef int (*read_func)(struct seq_file *s, void *data); * Config wake on lan * dbg_get_read_func * Return the read func for debugfs seq file + * set_pfc_prevention_tout + * Set PFC storm prevention timeout + * get_pfc_prevention_tout + * Get PFC storm prevention timeout */ struct hnae3_ae_ops { int (*init_ae_dev)(struct hnae3_ae_dev *ae_dev); @@ -810,6 +814,8 @@ struct hnae3_ae_ops { int (*hwtstamp_set)(struct hnae3_handle *handle, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack); + int (*set_pfc_prevention_tout)(struct hnae3_handle *handle, u16 times); + int (*get_pfc_prevention_tout)(struct hnae3_handle *handle, u16 *times); }; struct hnae3_dcb_ops { @@ -891,6 +897,14 @@ struct hnae3_roce_private_info { unsigned long state; }; +struct hnae3_pfc_storm_para { + u32 dir; + u32 enable; + u32 period_ms; + u32 times; + u32 recovery_period_ms; +}; + #define HNAE3_SUPPORT_APP_LOOPBACK BIT(0) #define HNAE3_SUPPORT_PHY_LOOPBACK BIT(1) #define HNAE3_SUPPORT_SERDES_SERIAL_LOOPBACK BIT(2) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h b/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h index 2c2a2f1e0d7a..6dde07dde1e8 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_common/hclge_comm_cmd.h @@ -314,6 +314,9 @@ enum hclge_opcode_type { /* Query link diagnosis info command */ HCLGE_OPC_QUERY_LINK_DIAGNOSIS = 0x702A, + + /* Config pause storm param command */ + HCLGE_OPC_CFG_PAUSE_STORM_PARA = 0x7019, }; enum hclge_comm_cmd_return_status { diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c index 442f15476af3..e7318f236315 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c @@ -1895,6 +1895,12 @@ static int hns3_get_tunable(struct net_device *netdev, case ETHTOOL_TX_COPYBREAK_BUF_SIZE: *(u32 *)data = h->kinfo.tx_spare_buf_size; break; + case ETHTOOL_PFC_PREVENTION_TOUT: + if (!h->ae_algo->ops->get_pfc_prevention_tout) + return -EOPNOTSUPP; + + ret = h->ae_algo->ops->get_pfc_prevention_tout(h, (u16 *)data); + break; default: ret = -EOPNOTSUPP; break; @@ -2021,6 +2027,12 @@ static int hns3_set_tunable(struct net_device *netdev, priv->ring->tx_spare->len); break; + case ETHTOOL_PFC_PREVENTION_TOUT: + if (!h->ae_algo->ops->set_pfc_prevention_tout) + return -EOPNOTSUPP; + + ret = h->ae_algo->ops->set_pfc_prevention_tout(h, *(u16 *)data); + break; default: ret = -EOPNOTSUPP; break; diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h index 4ce92ddefcde..1c029dc32ab8 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h @@ -890,6 +890,15 @@ struct hclge_query_wol_supported_cmd { u8 rsv[20]; }; +struct hclge_pfc_storm_para_cmd { + __le32 dir; + __le32 enable; + __le32 period_ms; + __le32 times; + __le32 recovery_period_ms; + __le32 rsv; +}; + struct hclge_hw; int hclge_cmd_send(struct hclge_hw *hw, struct hclge_desc *desc, int num); #endif diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index fc8587c80813..a08d8a35aef9 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -3524,6 +3524,122 @@ static int hclge_set_vf_link_state(struct hnae3_handle *handle, int vf, return ret; } +static int hclge_set_pfc_storm_para(struct hclge_dev *hdev, + struct hnae3_pfc_storm_para *para) +{ + struct hclge_pfc_storm_para_cmd *para_cmd; + struct hclge_desc desc; + int ret; + + if (hdev->ae_dev->dev_version < HNAE3_DEVICE_VERSION_V3) + return -EOPNOTSUPP; + + hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_CFG_PAUSE_STORM_PARA, + false); + para_cmd = (struct hclge_pfc_storm_para_cmd *)desc.data; + para_cmd->dir = cpu_to_le32(para->dir); + para_cmd->enable = cpu_to_le32(para->enable); + para_cmd->period_ms = cpu_to_le32(para->period_ms); + para_cmd->times = cpu_to_le32(para->times); + para_cmd->recovery_period_ms = cpu_to_le32(para->recovery_period_ms); + + ret = hclge_cmd_send(&hdev->hw, &desc, 1); + if (ret) + dev_err(&hdev->pdev->dev, + "failed to set pfc storm para, ret = %d\n", ret); + return ret; +} + +static int hclge_get_pfc_storm_para(struct hclge_dev *hdev, + struct hnae3_pfc_storm_para *para) +{ + struct hclge_pfc_storm_para_cmd *para_cmd; + struct hclge_desc desc; + int ret; + + if (hdev->ae_dev->dev_version < HNAE3_DEVICE_VERSION_V3) + return -EOPNOTSUPP; + + hclge_cmd_setup_basic_desc(&desc, HCLGE_OPC_CFG_PAUSE_STORM_PARA, true); + para_cmd = (struct hclge_pfc_storm_para_cmd *)desc.data; + para_cmd->dir = cpu_to_le32(para->dir); + ret = hclge_cmd_send(&hdev->hw, &desc, 1); + if (ret) { + dev_err(&hdev->pdev->dev, + "failed to get pfc storm para, ret = %d\n", ret); + return ret; + } + + para->enable = le32_to_cpu(para_cmd->enable); + para->period_ms = le32_to_cpu(para_cmd->period_ms); + para->times = le32_to_cpu(para_cmd->times); + para->recovery_period_ms = le32_to_cpu(para_cmd->recovery_period_ms); + + return 0; +} + +static int hclge_enable_pfc_storm_prevent(struct hclge_dev *hdev, + int dir, bool enable) +{ + struct hnae3_pfc_storm_para para = {0}; + int ret; + + para.dir = dir; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + para.enable = enable; + return hclge_set_pfc_storm_para(hdev, ¶); +} + +static int hclge_set_pfc_prevention_tout(struct hnae3_handle *h, u16 times) +{ + struct hclge_vport *vport = hclge_get_vport(h); + struct hclge_dev *hdev = vport->back; + struct hnae3_pfc_storm_para para; + int ret; + + if (times > HCLGE_MAX_PFC_PREVENTION_TOUT) { + dev_err(&hdev->pdev->dev, + "times %u should be no more than %u!\n", + times, HCLGE_MAX_PFC_PREVENTION_TOUT); + return -EINVAL; + } + + para.dir = HCLGE_DIR_TX; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + para.enable = times ? 1 : 0; + para.times = (u32)times; + ret = hclge_set_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + hdev->pfc_prevention_tout = times; + + return 0; +} + +static int hclge_get_pfc_prevention_tout(struct hnae3_handle *h, u16 *times) +{ + struct hclge_vport *vport = hclge_get_vport(h); + struct hclge_dev *hdev = vport->back; + struct hnae3_pfc_storm_para para; + int ret; + + para.dir = HCLGE_DIR_TX; + ret = hclge_get_pfc_storm_para(hdev, ¶); + if (ret) + return ret; + + *times = para.enable ? (u16)para.times : 0; + + return 0; +} + static void hclge_set_reset_pending(struct hclge_dev *hdev, enum hnae3_reset_type reset_type) { @@ -4317,6 +4433,26 @@ static int hclge_reset_prepare(struct hclge_dev *hdev) return hclge_reset_prepare_wait(hdev); } +static void hclge_restore_pfc_storm_prevention_tout(struct hclge_dev *hdev) +{ + struct hnae3_handle *handle = &hdev->vport[0].nic; + int ret; + + ret = hclge_enable_pfc_storm_prevent(hdev, HCLGE_DIR_RX, false); + if (ret == -EOPNOTSUPP) + return; + else if (ret) + dev_warn(&hdev->pdev->dev, + "failed to disable rx pfc storm prevent, ret = %d\n", + ret); + + ret = hclge_set_pfc_prevention_tout(handle, hdev->pfc_prevention_tout); + if (ret) + dev_warn(&hdev->pdev->dev, + "failed to set tx pfc storm prevent, ret = %d\n", + ret); +} + static int hclge_reset_rebuild(struct hclge_dev *hdev) { int ret; @@ -9278,6 +9414,32 @@ static int hclge_init_wol(struct hclge_dev *hdev) return hclge_update_wol(hdev); } +static void hclge_init_pfc_prevention_tout(struct hclge_dev *hdev) +{ + struct hnae3_handle *handle = &hdev->vport[0].nic; + u16 times; + int ret; + + ret = hclge_enable_pfc_storm_prevent(hdev, HCLGE_DIR_RX, false); + if (ret == -EOPNOTSUPP) + return; + else if (ret) + dev_warn(&hdev->pdev->dev, + "failed to disable rx pfc storm prevent, ret = %d\n", + ret); + + ret = hclge_get_pfc_prevention_tout(handle, ×); + if (ret) { + dev_warn(&hdev->pdev->dev, + "failed to get tx pfc prevention timeout, ret = %d\n", + ret); + times = HCLGE_DEFAULT_PFC_PREVENTION_TOUT; + } + + hdev->pfc_prevention_tout = times; + hdev->pfc_prevention_tout_default = times; +} + static void hclge_get_wol(struct hnae3_handle *handle, struct ethtool_wolinfo *wol) { @@ -9547,6 +9709,8 @@ static int hclge_init_ae_dev(struct hnae3_ae_dev *ae_dev) dev_warn(&pdev->dev, "failed to wake on lan init, ret = %d\n", ret); + hclge_init_pfc_prevention_tout(hdev); + ret = hclge_devlink_init(hdev); if (ret) goto err_ptp_uninit; @@ -9946,6 +10110,8 @@ static int hclge_reset_ae_dev(struct hnae3_ae_dev *ae_dev) dev_warn(&pdev->dev, "failed to update wol config, ret = %d\n", ret); + hclge_restore_pfc_storm_prevention_tout(hdev); + dev_info(&pdev->dev, "Reset done, %s driver initialization finished.\n", HCLGE_DRIVER_NAME); @@ -9977,6 +10143,10 @@ static void hclge_uninit_ae_dev(struct hnae3_ae_dev *ae_dev) hclge_config_nic_hw_error(hdev, false); hclge_config_rocee_ras_interrupt(hdev, false); + /* Restore hw default values for the next initialization */ + hclge_set_pfc_prevention_tout(&hdev->vport->nic, + hdev->pfc_prevention_tout_default); + hclge_comm_cmd_uninit(hdev->ae_dev, &hdev->hw.hw); hclge_misc_irq_uninit(hdev); hclge_devlink_uninit(hdev); @@ -10539,6 +10709,8 @@ static const struct hnae3_ae_ops hclge_ops = { .set_wol = hclge_set_wol, .hwtstamp_get = hclge_ptp_get_cfg, .hwtstamp_set = hclge_ptp_set_cfg, + .set_pfc_prevention_tout = hclge_set_pfc_prevention_tout, + .get_pfc_prevention_tout = hclge_get_pfc_prevention_tout, }; static struct hnae3_ae_algo ae_algo = { diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h index 7419481422c3..0cee8947f6b4 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.h @@ -346,6 +346,11 @@ enum hclge_link_fail_code { #define HCLGE_LINK_STATUS_DOWN 0 #define HCLGE_LINK_STATUS_UP 1 +#define HCLGE_DIR_RX 0 +#define HCLGE_DIR_TX 1 +#define HCLGE_MAX_PFC_PREVENTION_TOUT 2000 +#define HCLGE_DEFAULT_PFC_PREVENTION_TOUT 1000 + #define HCLGE_PG_NUM 4 #define HCLGE_SCH_MODE_SP 0 #define HCLGE_SCH_MODE_DWRR 1 @@ -898,6 +903,8 @@ struct hclge_dev { u16 vf_rss_size_max; /* HW defined VF max RSS task queue */ u16 pf_rss_size_max; /* HW defined PF max RSS task queue */ u32 tx_spare_buf_size; /* HW defined TX spare buffer size */ + u16 pfc_prevention_tout; /* User config, restored after reset */ + u16 pfc_prevention_tout_default; /* HW default, to avoid stale state */ u16 fdir_pf_filter_count; /* Num of guaranteed filters for this PF */ u16 num_alloc_vport; /* Num vports this driver supports */ diff --git a/drivers/net/ethernet/ibm/Kconfig b/drivers/net/ethernet/ibm/Kconfig index 4f4b23465c47..8e55faac2035 100644 --- a/drivers/net/ethernet/ibm/Kconfig +++ b/drivers/net/ethernet/ibm/Kconfig @@ -42,15 +42,6 @@ config IBMVETH_KUNIT_TEST source "drivers/net/ethernet/ibm/emac/Kconfig" -config EHEA - tristate "eHEA Ethernet support" - depends on IBMEBUS && SPARSEMEM - help - This driver supports the IBM pSeries eHEA ethernet adapter. - - To compile the driver as a module, choose M here. The module - will be called ehea. - config IBMVNIC tristate "IBM Virtual NIC support" depends on PPC_PSERIES diff --git a/drivers/net/ethernet/ibm/Makefile b/drivers/net/ethernet/ibm/Makefile index 1d17d0c33d4d..c7e5d891c946 100644 --- a/drivers/net/ethernet/ibm/Makefile +++ b/drivers/net/ethernet/ibm/Makefile @@ -6,4 +6,3 @@ obj-$(CONFIG_IBMVETH) += ibmveth.o obj-$(CONFIG_IBMVNIC) += ibmvnic.o obj-$(CONFIG_IBM_EMAC) += emac/ -obj-$(CONFIG_EHEA) += ehea/ diff --git a/drivers/net/ethernet/ibm/ehea/Makefile b/drivers/net/ethernet/ibm/ehea/Makefile deleted file mode 100644 index 9e1e5c7aafe2..000000000000 --- a/drivers/net/ethernet/ibm/ehea/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for the eHEA ethernet device driver for IBM eServer System p -# -ehea-y = ehea_main.o ehea_phyp.o ehea_qmr.o ehea_ethtool.o -obj-$(CONFIG_EHEA) += ehea.o - diff --git a/drivers/net/ethernet/ibm/ehea/ehea.h b/drivers/net/ethernet/ibm/ehea/ehea.h deleted file mode 100644 index 208c440a602b..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea.h +++ /dev/null @@ -1,477 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#ifndef __EHEA_H__ -#define __EHEA_H__ - -#include <linux/module.h> -#include <linux/ethtool.h> -#include <linux/vmalloc.h> -#include <linux/if_vlan.h> -#include <linux/platform_device.h> - -#include <asm/ibmebus.h> -#include <asm/io.h> - -#define DRV_NAME "ehea" -#define DRV_VERSION "EHEA_0107" - -/* eHEA capability flags */ -#define DLPAR_PORT_ADD_REM 1 -#define DLPAR_MEM_ADD 2 -#define DLPAR_MEM_REM 4 -#define EHEA_CAPABILITIES (DLPAR_PORT_ADD_REM | DLPAR_MEM_ADD | DLPAR_MEM_REM) - -#define EHEA_MSG_DEFAULT (NETIF_MSG_LINK | NETIF_MSG_TIMER \ - | NETIF_MSG_RX_ERR | NETIF_MSG_TX_ERR) - -#define EHEA_MAX_ENTRIES_RQ1 32767 -#define EHEA_MAX_ENTRIES_RQ2 16383 -#define EHEA_MAX_ENTRIES_RQ3 16383 -#define EHEA_MAX_ENTRIES_SQ 32767 -#define EHEA_MIN_ENTRIES_QP 127 - -#define EHEA_SMALL_QUEUES - -#ifdef EHEA_SMALL_QUEUES -#define EHEA_MAX_CQE_COUNT 1023 -#define EHEA_DEF_ENTRIES_SQ 1023 -#define EHEA_DEF_ENTRIES_RQ1 1023 -#define EHEA_DEF_ENTRIES_RQ2 1023 -#define EHEA_DEF_ENTRIES_RQ3 511 -#else -#define EHEA_MAX_CQE_COUNT 4080 -#define EHEA_DEF_ENTRIES_SQ 4080 -#define EHEA_DEF_ENTRIES_RQ1 8160 -#define EHEA_DEF_ENTRIES_RQ2 2040 -#define EHEA_DEF_ENTRIES_RQ3 2040 -#endif - -#define EHEA_MAX_ENTRIES_EQ 20 - -#define EHEA_SG_SQ 2 -#define EHEA_SG_RQ1 1 -#define EHEA_SG_RQ2 0 -#define EHEA_SG_RQ3 0 - -#define EHEA_MAX_PACKET_SIZE 9022 /* for jumbo frames */ -#define EHEA_RQ2_PKT_SIZE 2048 -#define EHEA_L_PKT_SIZE 256 /* low latency */ - -/* Send completion signaling */ - -/* Protection Domain Identifier */ -#define EHEA_PD_ID 0xaabcdeff - -#define EHEA_RQ2_THRESHOLD 1 -#define EHEA_RQ3_THRESHOLD 4 /* use RQ3 threshold of 2048 bytes */ - -#define EHEA_SPEED_10G 10000 -#define EHEA_SPEED_1G 1000 -#define EHEA_SPEED_100M 100 -#define EHEA_SPEED_10M 10 -#define EHEA_SPEED_AUTONEG 0 - -/* Broadcast/Multicast registration types */ -#define EHEA_BCMC_SCOPE_ALL 0x08 -#define EHEA_BCMC_SCOPE_SINGLE 0x00 -#define EHEA_BCMC_MULTICAST 0x04 -#define EHEA_BCMC_BROADCAST 0x00 -#define EHEA_BCMC_UNTAGGED 0x02 -#define EHEA_BCMC_TAGGED 0x00 -#define EHEA_BCMC_VLANID_ALL 0x01 -#define EHEA_BCMC_VLANID_SINGLE 0x00 - -#define EHEA_CACHE_LINE 128 - -/* Memory Regions */ -#define EHEA_MR_ACC_CTRL 0x00800000 - -#define EHEA_BUSMAP_START 0x8000000000000000ULL -#define EHEA_INVAL_ADDR 0xFFFFFFFFFFFFFFFFULL -#define EHEA_DIR_INDEX_SHIFT 13 /* 8k Entries in 64k block */ -#define EHEA_TOP_INDEX_SHIFT (EHEA_DIR_INDEX_SHIFT * 2) -#define EHEA_MAP_ENTRIES (1 << EHEA_DIR_INDEX_SHIFT) -#define EHEA_MAP_SIZE (0x10000) /* currently fixed map size */ -#define EHEA_INDEX_MASK (EHEA_MAP_ENTRIES - 1) - - -#define EHEA_WATCH_DOG_TIMEOUT 10*HZ - -/* utility functions */ - -void ehea_dump(void *adr, int len, char *msg); - -#define EHEA_BMASK(pos, length) (((pos) << 16) + (length)) - -#define EHEA_BMASK_IBM(from, to) (((63 - to) << 16) + ((to) - (from) + 1)) - -#define EHEA_BMASK_SHIFTPOS(mask) (((mask) >> 16) & 0xffff) - -#define EHEA_BMASK_MASK(mask) \ - (0xffffffffffffffffULL >> ((64 - (mask)) & 0xffff)) - -#define EHEA_BMASK_SET(mask, value) \ - ((EHEA_BMASK_MASK(mask) & ((u64)(value))) << EHEA_BMASK_SHIFTPOS(mask)) - -#define EHEA_BMASK_GET(mask, value) \ - (EHEA_BMASK_MASK(mask) & (((u64)(value)) >> EHEA_BMASK_SHIFTPOS(mask))) - -/* - * Generic ehea page - */ -struct ehea_page { - u8 entries[PAGE_SIZE]; -}; - -/* - * Generic queue in linux kernel virtual memory - */ -struct hw_queue { - u64 current_q_offset; /* current queue entry */ - struct ehea_page **queue_pages; /* array of pages belonging to queue */ - u32 qe_size; /* queue entry size */ - u32 queue_length; /* queue length allocated in bytes */ - u32 pagesize; - u32 toggle_state; /* toggle flag - per page */ - u32 reserved; /* 64 bit alignment */ -}; - -/* - * For pSeries this is a 64bit memory address where - * I/O memory is mapped into CPU address space - */ -struct h_epa { - void __iomem *addr; -}; - -struct h_epa_user { - u64 addr; -}; - -struct h_epas { - struct h_epa kernel; /* kernel space accessible resource, - set to 0 if unused */ - struct h_epa_user user; /* user space accessible resource - set to 0 if unused */ -}; - -/* - * Memory map data structures - */ -struct ehea_dir_bmap -{ - u64 ent[EHEA_MAP_ENTRIES]; -}; -struct ehea_top_bmap -{ - struct ehea_dir_bmap *dir[EHEA_MAP_ENTRIES]; -}; -struct ehea_bmap -{ - struct ehea_top_bmap *top[EHEA_MAP_ENTRIES]; -}; - -struct ehea_qp; -struct ehea_cq; -struct ehea_eq; -struct ehea_port; -struct ehea_av; - -/* - * Queue attributes passed to ehea_create_qp() - */ -struct ehea_qp_init_attr { - /* input parameter */ - u32 qp_token; /* queue token */ - u8 low_lat_rq1; - u8 signalingtype; /* cqe generation flag */ - u8 rq_count; /* num of receive queues */ - u8 eqe_gen; /* eqe generation flag */ - u16 max_nr_send_wqes; /* max number of send wqes */ - u16 max_nr_rwqes_rq1; /* max number of receive wqes */ - u16 max_nr_rwqes_rq2; - u16 max_nr_rwqes_rq3; - u8 wqe_size_enc_sq; - u8 wqe_size_enc_rq1; - u8 wqe_size_enc_rq2; - u8 wqe_size_enc_rq3; - u8 swqe_imm_data_len; /* immediate data length for swqes */ - u16 port_nr; - u16 rq2_threshold; - u16 rq3_threshold; - u64 send_cq_handle; - u64 recv_cq_handle; - u64 aff_eq_handle; - - /* output parameter */ - u32 qp_nr; - u16 act_nr_send_wqes; - u16 act_nr_rwqes_rq1; - u16 act_nr_rwqes_rq2; - u16 act_nr_rwqes_rq3; - u8 act_wqe_size_enc_sq; - u8 act_wqe_size_enc_rq1; - u8 act_wqe_size_enc_rq2; - u8 act_wqe_size_enc_rq3; - u32 nr_sq_pages; - u32 nr_rq1_pages; - u32 nr_rq2_pages; - u32 nr_rq3_pages; - u32 liobn_sq; - u32 liobn_rq1; - u32 liobn_rq2; - u32 liobn_rq3; -}; - -/* - * Event Queue attributes, passed as parameter - */ -struct ehea_eq_attr { - u32 type; - u32 max_nr_of_eqes; - u8 eqe_gen; /* generate eqe flag */ - u64 eq_handle; - u32 act_nr_of_eqes; - u32 nr_pages; - u32 ist1; /* Interrupt service token */ - u32 ist2; - u32 ist3; - u32 ist4; -}; - - -/* - * Event Queue - */ -struct ehea_eq { - struct ehea_adapter *adapter; - struct hw_queue hw_queue; - u64 fw_handle; - struct h_epas epas; - spinlock_t spinlock; - struct ehea_eq_attr attr; -}; - -/* - * HEA Queues - */ -struct ehea_qp { - struct ehea_adapter *adapter; - u64 fw_handle; /* QP handle for firmware calls */ - struct hw_queue hw_squeue; - struct hw_queue hw_rqueue1; - struct hw_queue hw_rqueue2; - struct hw_queue hw_rqueue3; - struct h_epas epas; - struct ehea_qp_init_attr init_attr; -}; - -/* - * Completion Queue attributes - */ -struct ehea_cq_attr { - /* input parameter */ - u32 max_nr_of_cqes; - u32 cq_token; - u64 eq_handle; - - /* output parameter */ - u32 act_nr_of_cqes; - u32 nr_pages; -}; - -/* - * Completion Queue - */ -struct ehea_cq { - struct ehea_adapter *adapter; - u64 fw_handle; - struct hw_queue hw_queue; - struct h_epas epas; - struct ehea_cq_attr attr; -}; - -/* - * Memory Region - */ -struct ehea_mr { - struct ehea_adapter *adapter; - u64 handle; - u64 vaddr; - u32 lkey; -}; - -/* - * Port state information - */ -struct port_stats { - int poll_receive_errors; - int queue_stopped; - int err_tcp_cksum; - int err_ip_cksum; - int err_frame_crc; -}; - -#define EHEA_IRQ_NAME_SIZE 20 - -/* - * Queue SKB Array - */ -struct ehea_q_skb_arr { - struct sk_buff **arr; /* skb array for queue */ - int len; /* array length */ - int index; /* array index */ - int os_skbs; /* rq2/rq3 only: outstanding skbs */ -}; - -/* - * Port resources - */ -struct ehea_port_res { - struct napi_struct napi; - struct port_stats p_stats; - struct ehea_mr send_mr; /* send memory region */ - struct ehea_mr recv_mr; /* receive memory region */ - struct ehea_port *port; - char int_recv_name[EHEA_IRQ_NAME_SIZE]; - char int_send_name[EHEA_IRQ_NAME_SIZE]; - struct ehea_qp *qp; - struct ehea_cq *send_cq; - struct ehea_cq *recv_cq; - struct ehea_eq *eq; - struct ehea_q_skb_arr rq1_skba; - struct ehea_q_skb_arr rq2_skba; - struct ehea_q_skb_arr rq3_skba; - struct ehea_q_skb_arr sq_skba; - int sq_skba_size; - int swqe_refill_th; - atomic_t swqe_avail; - int swqe_ll_count; - u32 swqe_id_counter; - u64 tx_packets; - u64 tx_bytes; - u64 rx_packets; - u64 rx_bytes; - int sq_restart_flag; -}; - - -#define EHEA_MAX_PORTS 16 - -#define EHEA_NUM_PORTRES_FW_HANDLES 6 /* QP handle, SendCQ handle, - RecvCQ handle, EQ handle, - SendMR handle, RecvMR handle */ -#define EHEA_NUM_PORT_FW_HANDLES 1 /* EQ handle */ -#define EHEA_NUM_ADAPTER_FW_HANDLES 2 /* MR handle, NEQ handle */ - -struct ehea_adapter { - u64 handle; - struct platform_device *ofdev; - struct ehea_port *port[EHEA_MAX_PORTS]; - struct ehea_eq *neq; /* notification event queue */ - struct tasklet_struct neq_tasklet; - struct ehea_mr mr; - u32 pd; /* protection domain */ - u64 max_mc_mac; /* max number of multicast mac addresses */ - int active_ports; - struct list_head list; -}; - - -struct ehea_mc_list { - struct list_head list; - u64 macaddr; -}; - -/* kdump support */ -struct ehea_fw_handle_entry { - u64 adh; /* Adapter Handle */ - u64 fwh; /* Firmware Handle */ -}; - -struct ehea_fw_handle_array { - struct ehea_fw_handle_entry *arr; - int num_entries; - struct mutex lock; -}; - -struct ehea_bcmc_reg_entry { - u64 adh; /* Adapter Handle */ - u32 port_id; /* Logical Port Id */ - u8 reg_type; /* Registration Type */ - u64 macaddr; -}; - -struct ehea_bcmc_reg_array { - struct ehea_bcmc_reg_entry *arr; - int num_entries; - spinlock_t lock; -}; - -#define EHEA_PORT_UP 1 -#define EHEA_PORT_DOWN 0 -#define EHEA_PHY_LINK_UP 1 -#define EHEA_PHY_LINK_DOWN 0 -#define EHEA_MAX_PORT_RES 16 -struct ehea_port { - struct ehea_adapter *adapter; /* adapter that owns this port */ - struct net_device *netdev; - struct rtnl_link_stats64 stats; - struct ehea_port_res port_res[EHEA_MAX_PORT_RES]; - struct platform_device ofdev; /* Open Firmware Device */ - struct ehea_mc_list *mc_list; /* Multicast MAC addresses */ - struct ehea_eq *qp_eq; - struct work_struct reset_task; - struct delayed_work stats_work; - struct mutex port_lock; - char int_aff_name[EHEA_IRQ_NAME_SIZE]; - int allmulti; /* Indicates IFF_ALLMULTI state */ - int promisc; /* Indicates IFF_PROMISC state */ - int num_mcs; - int resets; - unsigned long flags; - u64 mac_addr; - u32 logical_port_id; - u32 port_speed; - u32 msg_enable; - u32 sig_comp_iv; - u32 state; - u8 phy_link; - u8 full_duplex; - u8 autoneg; - u8 num_def_qps; - wait_queue_head_t swqe_avail_wq; - wait_queue_head_t restart_wq; -}; - -struct port_res_cfg { - int max_entries_rcq; - int max_entries_scq; - int max_entries_sq; - int max_entries_rq1; - int max_entries_rq2; - int max_entries_rq3; -}; - -enum ehea_flag_bits { - __EHEA_STOP_XFER, - __EHEA_DISABLE_PORT_RESET -}; - -void ehea_set_ethtool_ops(struct net_device *netdev); -int ehea_sense_port_attr(struct ehea_port *port); -int ehea_set_portspeed(struct ehea_port *port, u32 port_speed); - -#endif /* __EHEA_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c b/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c deleted file mode 100644 index 1db5b6790a41..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_ethtool.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "ehea.h" -#include "ehea_phyp.h" - -static int ehea_get_link_ksettings(struct net_device *dev, - struct ethtool_link_ksettings *cmd) -{ - struct ehea_port *port = netdev_priv(dev); - u32 supported, advertising; - u32 speed; - int ret; - - ret = ehea_sense_port_attr(port); - - if (ret) - return ret; - - if (netif_carrier_ok(dev)) { - switch (port->port_speed) { - case EHEA_SPEED_10M: - speed = SPEED_10; - break; - case EHEA_SPEED_100M: - speed = SPEED_100; - break; - case EHEA_SPEED_1G: - speed = SPEED_1000; - break; - case EHEA_SPEED_10G: - speed = SPEED_10000; - break; - default: - speed = -1; - break; /* BUG */ - } - cmd->base.duplex = port->full_duplex == 1 ? - DUPLEX_FULL : DUPLEX_HALF; - } else { - speed = SPEED_UNKNOWN; - cmd->base.duplex = DUPLEX_UNKNOWN; - } - cmd->base.speed = speed; - - if (cmd->base.speed == SPEED_10000) { - supported = (SUPPORTED_10000baseT_Full | SUPPORTED_FIBRE); - advertising = (ADVERTISED_10000baseT_Full | ADVERTISED_FIBRE); - cmd->base.port = PORT_FIBRE; - } else { - supported = (SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full - | SUPPORTED_100baseT_Half | SUPPORTED_10baseT_Full - | SUPPORTED_10baseT_Half | SUPPORTED_Autoneg - | SUPPORTED_TP); - advertising = (ADVERTISED_1000baseT_Full | ADVERTISED_Autoneg - | ADVERTISED_TP); - cmd->base.port = PORT_TP; - } - - cmd->base.autoneg = port->autoneg == 1 ? - AUTONEG_ENABLE : AUTONEG_DISABLE; - - ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported, - supported); - ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising, - advertising); - - return 0; -} - -static int ehea_set_link_ksettings(struct net_device *dev, - const struct ethtool_link_ksettings *cmd) -{ - struct ehea_port *port = netdev_priv(dev); - int ret = 0; - u32 sp; - - if (cmd->base.autoneg == AUTONEG_ENABLE) { - sp = EHEA_SPEED_AUTONEG; - goto doit; - } - - switch (cmd->base.speed) { - case SPEED_10: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_10M_F; - else - sp = H_SPEED_10M_H; - break; - - case SPEED_100: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_100M_F; - else - sp = H_SPEED_100M_H; - break; - - case SPEED_1000: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_1G_F; - else - ret = -EINVAL; - break; - - case SPEED_10000: - if (cmd->base.duplex == DUPLEX_FULL) - sp = H_SPEED_10G_F; - else - ret = -EINVAL; - break; - - default: - ret = -EINVAL; - break; - } - - if (ret) - goto out; -doit: - ret = ehea_set_portspeed(port, sp); - - if (!ret) - netdev_info(dev, - "Port speed successfully set: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? "Full" : "Half"); -out: - return ret; -} - -static int ehea_nway_reset(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - int ret; - - ret = ehea_set_portspeed(port, EHEA_SPEED_AUTONEG); - - if (!ret) - netdev_info(port->netdev, - "Port speed successfully set: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? "Full" : "Half"); - return ret; -} - -static void ehea_get_drvinfo(struct net_device *dev, - struct ethtool_drvinfo *info) -{ - strscpy(info->driver, DRV_NAME, sizeof(info->driver)); - strscpy(info->version, DRV_VERSION, sizeof(info->version)); -} - -static u32 ehea_get_msglevel(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - return port->msg_enable; -} - -static void ehea_set_msglevel(struct net_device *dev, u32 value) -{ - struct ehea_port *port = netdev_priv(dev); - port->msg_enable = value; -} - -static const char ehea_ethtool_stats_keys[][ETH_GSTRING_LEN] = { - {"sig_comp_iv"}, - {"swqe_refill_th"}, - {"port resets"}, - {"Receive errors"}, - {"TCP cksum errors"}, - {"IP cksum errors"}, - {"Frame cksum errors"}, - {"num SQ stopped"}, - {"PR0 free_swqes"}, - {"PR1 free_swqes"}, - {"PR2 free_swqes"}, - {"PR3 free_swqes"}, - {"PR4 free_swqes"}, - {"PR5 free_swqes"}, - {"PR6 free_swqes"}, - {"PR7 free_swqes"}, - {"PR8 free_swqes"}, - {"PR9 free_swqes"}, - {"PR10 free_swqes"}, - {"PR11 free_swqes"}, - {"PR12 free_swqes"}, - {"PR13 free_swqes"}, - {"PR14 free_swqes"}, - {"PR15 free_swqes"}, -}; - -static void ehea_get_strings(struct net_device *dev, u32 stringset, u8 *data) -{ - if (stringset == ETH_SS_STATS) { - memcpy(data, &ehea_ethtool_stats_keys, - sizeof(ehea_ethtool_stats_keys)); - } -} - -static int ehea_get_sset_count(struct net_device *dev, int sset) -{ - switch (sset) { - case ETH_SS_STATS: - return ARRAY_SIZE(ehea_ethtool_stats_keys); - default: - return -EOPNOTSUPP; - } -} - -static void ehea_get_ethtool_stats(struct net_device *dev, - struct ethtool_stats *stats, u64 *data) -{ - int i, k, tmp; - struct ehea_port *port = netdev_priv(dev); - - for (i = 0; i < ehea_get_sset_count(dev, ETH_SS_STATS); i++) - data[i] = 0; - i = 0; - - data[i++] = port->sig_comp_iv; - data[i++] = port->port_res[0].swqe_refill_th; - data[i++] = port->resets; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.poll_receive_errors; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_tcp_cksum; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_ip_cksum; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.err_frame_crc; - data[i++] = tmp; - - for (k = 0, tmp = 0; k < EHEA_MAX_PORT_RES; k++) - tmp += port->port_res[k].p_stats.queue_stopped; - data[i++] = tmp; - - for (k = 0; k < 16; k++) - data[i++] = atomic_read(&port->port_res[k].swqe_avail); -} - -static const struct ethtool_ops ehea_ethtool_ops = { - .get_drvinfo = ehea_get_drvinfo, - .get_msglevel = ehea_get_msglevel, - .set_msglevel = ehea_set_msglevel, - .get_link = ethtool_op_get_link, - .get_strings = ehea_get_strings, - .get_sset_count = ehea_get_sset_count, - .get_ethtool_stats = ehea_get_ethtool_stats, - .nway_reset = ehea_nway_reset, /* Restart autonegotiation */ - .get_link_ksettings = ehea_get_link_ksettings, - .set_link_ksettings = ehea_set_link_ksettings, -}; - -void ehea_set_ethtool_ops(struct net_device *netdev) -{ - netdev->ethtool_ops = &ehea_ethtool_ops; -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_hw.h b/drivers/net/ethernet/ibm/ehea/ehea_hw.h deleted file mode 100644 index 590933a45d65..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_hw.h +++ /dev/null @@ -1,253 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_hw.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#ifndef __EHEA_HW_H__ -#define __EHEA_HW_H__ - -#define QPX_SQA_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ1A_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ2A_VALUE EHEA_BMASK_IBM(48, 63) -#define QPX_RQ3A_VALUE EHEA_BMASK_IBM(48, 63) - -#define QPTEMM_OFFSET(x) offsetof(struct ehea_qptemm, x) - -struct ehea_qptemm { - u64 qpx_hcr; - u64 qpx_c; - u64 qpx_herr; - u64 qpx_aer; - u64 qpx_sqa; - u64 qpx_sqc; - u64 qpx_rq1a; - u64 qpx_rq1c; - u64 qpx_st; - u64 qpx_aerr; - u64 qpx_tenure; - u64 qpx_reserved1[(0x098 - 0x058) / 8]; - u64 qpx_portp; - u64 qpx_reserved2[(0x100 - 0x0A0) / 8]; - u64 qpx_t; - u64 qpx_sqhp; - u64 qpx_sqptp; - u64 qpx_reserved3[(0x140 - 0x118) / 8]; - u64 qpx_sqwsize; - u64 qpx_reserved4[(0x170 - 0x148) / 8]; - u64 qpx_sqsize; - u64 qpx_reserved5[(0x1B0 - 0x178) / 8]; - u64 qpx_sigt; - u64 qpx_wqecnt; - u64 qpx_rq1hp; - u64 qpx_rq1ptp; - u64 qpx_rq1size; - u64 qpx_reserved6[(0x220 - 0x1D8) / 8]; - u64 qpx_rq1wsize; - u64 qpx_reserved7[(0x240 - 0x228) / 8]; - u64 qpx_pd; - u64 qpx_scqn; - u64 qpx_rcqn; - u64 qpx_aeqn; - u64 reserved49; - u64 qpx_ram; - u64 qpx_reserved8[(0x300 - 0x270) / 8]; - u64 qpx_rq2a; - u64 qpx_rq2c; - u64 qpx_rq2hp; - u64 qpx_rq2ptp; - u64 qpx_rq2size; - u64 qpx_rq2wsize; - u64 qpx_rq2th; - u64 qpx_rq3a; - u64 qpx_rq3c; - u64 qpx_rq3hp; - u64 qpx_rq3ptp; - u64 qpx_rq3size; - u64 qpx_rq3wsize; - u64 qpx_rq3th; - u64 qpx_lpn; - u64 qpx_reserved9[(0x400 - 0x378) / 8]; - u64 reserved_ext[(0x500 - 0x400) / 8]; - u64 reserved2[(0x1000 - 0x500) / 8]; -}; - -#define MRx_HCR_LPARID_VALID EHEA_BMASK_IBM(0, 0) - -#define MRMWMM_OFFSET(x) offsetof(struct ehea_mrmwmm, x) - -struct ehea_mrmwmm { - u64 mrx_hcr; - u64 mrx_c; - u64 mrx_herr; - u64 mrx_aer; - u64 mrx_pp; - u64 reserved1; - u64 reserved2; - u64 reserved3; - u64 reserved4[(0x200 - 0x40) / 8]; - u64 mrx_ctl[64]; -}; - -#define QPEDMM_OFFSET(x) offsetof(struct ehea_qpedmm, x) - -struct ehea_qpedmm { - - u64 reserved0[(0x400) / 8]; - u64 qpedx_phh; - u64 qpedx_ppsgp; - u64 qpedx_ppsgu; - u64 qpedx_ppdgp; - u64 qpedx_ppdgu; - u64 qpedx_aph; - u64 qpedx_apsgp; - u64 qpedx_apsgu; - u64 qpedx_apdgp; - u64 qpedx_apdgu; - u64 qpedx_apav; - u64 qpedx_apsav; - u64 qpedx_hcr; - u64 reserved1[4]; - u64 qpedx_rrl0; - u64 qpedx_rrrkey0; - u64 qpedx_rrva0; - u64 reserved2; - u64 qpedx_rrl1; - u64 qpedx_rrrkey1; - u64 qpedx_rrva1; - u64 reserved3; - u64 qpedx_rrl2; - u64 qpedx_rrrkey2; - u64 qpedx_rrva2; - u64 reserved4; - u64 qpedx_rrl3; - u64 qpedx_rrrkey3; - u64 qpedx_rrva3; -}; - -#define CQX_FECADDER EHEA_BMASK_IBM(32, 63) -#define CQX_FEC_CQE_CNT EHEA_BMASK_IBM(32, 63) -#define CQX_N1_GENERATE_COMP_EVENT EHEA_BMASK_IBM(0, 0) -#define CQX_EP_EVENT_PENDING EHEA_BMASK_IBM(0, 0) - -#define CQTEMM_OFFSET(x) offsetof(struct ehea_cqtemm, x) - -struct ehea_cqtemm { - u64 cqx_hcr; - u64 cqx_c; - u64 cqx_herr; - u64 cqx_aer; - u64 cqx_ptp; - u64 cqx_tp; - u64 cqx_fec; - u64 cqx_feca; - u64 cqx_ep; - u64 cqx_eq; - u64 reserved1; - u64 cqx_n0; - u64 cqx_n1; - u64 reserved2[(0x1000 - 0x60) / 8]; -}; - -#define EQTEMM_OFFSET(x) offsetof(struct ehea_eqtemm, x) - -struct ehea_eqtemm { - u64 eqx_hcr; - u64 eqx_c; - u64 eqx_herr; - u64 eqx_aer; - u64 eqx_ptp; - u64 eqx_tp; - u64 eqx_ssba; - u64 eqx_psba; - u64 eqx_cec; - u64 eqx_meql; - u64 eqx_xisbi; - u64 eqx_xisc; - u64 eqx_it; -}; - -/* - * These access functions will be changed when the dissuccsion about - * the new access methods for POWER has settled. - */ - -static inline u64 epa_load(struct h_epa epa, u32 offset) -{ - return __raw_readq((void __iomem *)(epa.addr + offset)); -} - -static inline void epa_store(struct h_epa epa, u32 offset, u64 value) -{ - __raw_writeq(value, (void __iomem *)(epa.addr + offset)); - epa_load(epa, offset); /* synchronize explicitly to eHEA */ -} - -static inline void epa_store_acc(struct h_epa epa, u32 offset, u64 value) -{ - __raw_writeq(value, (void __iomem *)(epa.addr + offset)); -} - -#define epa_store_cq(epa, offset, value)\ - epa_store(epa, CQTEMM_OFFSET(offset), value) -#define epa_load_cq(epa, offset)\ - epa_load(epa, CQTEMM_OFFSET(offset)) - -static inline void ehea_update_sqa(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_sqa), - EHEA_BMASK_SET(QPX_SQA_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq3a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq3a), - EHEA_BMASK_SET(QPX_RQ1A_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq2a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq2a), - EHEA_BMASK_SET(QPX_RQ2A_VALUE, nr_wqes)); -} - -static inline void ehea_update_rq1a(struct ehea_qp *qp, u16 nr_wqes) -{ - struct h_epa epa = qp->epas.kernel; - epa_store_acc(epa, QPTEMM_OFFSET(qpx_rq1a), - EHEA_BMASK_SET(QPX_RQ3A_VALUE, nr_wqes)); -} - -static inline void ehea_update_feca(struct ehea_cq *cq, u32 nr_cqes) -{ - struct h_epa epa = cq->epas.kernel; - epa_store_acc(epa, CQTEMM_OFFSET(cqx_feca), - EHEA_BMASK_SET(CQX_FECADDER, nr_cqes)); -} - -static inline void ehea_reset_cq_n1(struct ehea_cq *cq) -{ - struct h_epa epa = cq->epas.kernel; - epa_store_cq(epa, cqx_n1, - EHEA_BMASK_SET(CQX_N1_GENERATE_COMP_EVENT, 1)); -} - -static inline void ehea_reset_cq_ep(struct ehea_cq *my_cq) -{ - struct h_epa epa = my_cq->epas.kernel; - epa_store_acc(epa, CQTEMM_OFFSET(cqx_ep), - EHEA_BMASK_SET(CQX_EP_EVENT_PENDING, 0)); -} - -#endif /* __EHEA_HW_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_main.c b/drivers/net/ethernet/ibm/ehea/ehea_main.c deleted file mode 100644 index bfc8699a05b9..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_main.c +++ /dev/null @@ -1,3581 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_main.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/device.h> -#include <linux/in.h> -#include <linux/ip.h> -#include <linux/tcp.h> -#include <linux/udp.h> -#include <linux/if.h> -#include <linux/list.h> -#include <linux/slab.h> -#include <linux/if_ether.h> -#include <linux/notifier.h> -#include <linux/reboot.h> -#include <linux/memory.h> -#include <asm/kexec.h> -#include <linux/mutex.h> -#include <linux/prefetch.h> -#include <linux/of.h> -#include <linux/of_device.h> -#include <linux/platform_device.h> - -#include <net/ip.h> - -#include "ehea.h" -#include "ehea_qmr.h" -#include "ehea_phyp.h" - - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Christoph Raisch <raisch@de.ibm.com>"); -MODULE_DESCRIPTION("IBM eServer HEA Driver"); -MODULE_VERSION(DRV_VERSION); - - -static int msg_level = -1; -static int rq1_entries = EHEA_DEF_ENTRIES_RQ1; -static int rq2_entries = EHEA_DEF_ENTRIES_RQ2; -static int rq3_entries = EHEA_DEF_ENTRIES_RQ3; -static int sq_entries = EHEA_DEF_ENTRIES_SQ; -static int use_mcs = 1; -static int prop_carrier_state; - -module_param(msg_level, int, 0); -module_param(rq1_entries, int, 0); -module_param(rq2_entries, int, 0); -module_param(rq3_entries, int, 0); -module_param(sq_entries, int, 0); -module_param(prop_carrier_state, int, 0); -module_param(use_mcs, int, 0); - -MODULE_PARM_DESC(msg_level, "msg_level"); -MODULE_PARM_DESC(prop_carrier_state, "Propagate carrier state of physical " - "port to stack. 1:yes, 0:no. Default = 0 "); -MODULE_PARM_DESC(rq3_entries, "Number of entries for Receive Queue 3 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ3) ")"); -MODULE_PARM_DESC(rq2_entries, "Number of entries for Receive Queue 2 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ2) ")"); -MODULE_PARM_DESC(rq1_entries, "Number of entries for Receive Queue 1 " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_RQ1) ")"); -MODULE_PARM_DESC(sq_entries, " Number of entries for the Send Queue " - "[2^x - 1], x = [7..14]. Default = " - __MODULE_STRING(EHEA_DEF_ENTRIES_SQ) ")"); -MODULE_PARM_DESC(use_mcs, " Multiple receive queues, 1: enable, 0: disable, " - "Default = 1"); - -static int port_name_cnt; -static LIST_HEAD(adapter_list); -static unsigned long ehea_driver_flags; -static DEFINE_MUTEX(dlpar_mem_lock); -static struct ehea_fw_handle_array ehea_fw_handles; -static struct ehea_bcmc_reg_array ehea_bcmc_regs; - - -static int ehea_probe_adapter(struct platform_device *dev); - -static void ehea_remove(struct platform_device *dev); - -static const struct of_device_id ehea_module_device_table[] = { - { - .name = "lhea", - .compatible = "IBM,lhea", - }, - { - .type = "network", - .compatible = "IBM,lhea-ethernet", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, ehea_module_device_table); - -static const struct of_device_id ehea_device_table[] = { - { - .name = "lhea", - .compatible = "IBM,lhea", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, ehea_device_table); - -static struct platform_driver ehea_driver = { - .driver = { - .name = "ehea", - .owner = THIS_MODULE, - .of_match_table = ehea_device_table, - }, - .probe = ehea_probe_adapter, - .remove = ehea_remove, -}; - -void ehea_dump(void *adr, int len, char *msg) -{ - int x; - unsigned char *deb = adr; - for (x = 0; x < len; x += 16) { - pr_info("%s adr=%p ofs=%04x %016llx %016llx\n", - msg, deb, x, *((u64 *)&deb[0]), *((u64 *)&deb[8])); - deb += 16; - } -} - -static void ehea_schedule_port_reset(struct ehea_port *port) -{ - if (!test_bit(__EHEA_DISABLE_PORT_RESET, &port->flags)) - schedule_work(&port->reset_task); -} - -static void ehea_update_firmware_handles(void) -{ - struct ehea_fw_handle_entry *arr = NULL; - struct ehea_adapter *adapter; - int num_adapters = 0; - int num_ports = 0; - int num_portres = 0; - int i = 0; - int num_fw_handles, k, l; - - /* Determine number of handles */ - mutex_lock(&ehea_fw_handles.lock); - - list_for_each_entry(adapter, &adapter_list, list) { - num_adapters++; - - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - num_ports++; - num_portres += port->num_def_qps; - } - } - - num_fw_handles = num_adapters * EHEA_NUM_ADAPTER_FW_HANDLES + - num_ports * EHEA_NUM_PORT_FW_HANDLES + - num_portres * EHEA_NUM_PORTRES_FW_HANDLES; - - if (num_fw_handles) { - arr = kzalloc_objs(*arr, num_fw_handles); - if (!arr) - goto out; /* Keep the existing array */ - } else - goto out_update; - - list_for_each_entry(adapter, &adapter_list, list) { - if (num_adapters == 0) - break; - - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP) || - (num_ports == 0)) - continue; - - for (l = 0; l < port->num_def_qps; l++) { - struct ehea_port_res *pr = &port->port_res[l]; - - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->qp->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->send_cq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->recv_cq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->eq->fw_handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->send_mr.handle; - arr[i].adh = adapter->handle; - arr[i++].fwh = pr->recv_mr.handle; - } - arr[i].adh = adapter->handle; - arr[i++].fwh = port->qp_eq->fw_handle; - num_ports--; - } - - arr[i].adh = adapter->handle; - arr[i++].fwh = adapter->neq->fw_handle; - - if (adapter->mr.handle) { - arr[i].adh = adapter->handle; - arr[i++].fwh = adapter->mr.handle; - } - num_adapters--; - } - -out_update: - kfree(ehea_fw_handles.arr); - ehea_fw_handles.arr = arr; - ehea_fw_handles.num_entries = i; -out: - mutex_unlock(&ehea_fw_handles.lock); -} - -static void ehea_update_bcmc_registrations(void) -{ - unsigned long flags; - struct ehea_bcmc_reg_entry *arr = NULL; - struct ehea_adapter *adapter; - struct ehea_mc_list *mc_entry; - int num_registrations = 0; - int i = 0; - int k; - - spin_lock_irqsave(&ehea_bcmc_regs.lock, flags); - - /* Determine number of registrations */ - list_for_each_entry(adapter, &adapter_list, list) - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - num_registrations += 2; /* Broadcast registrations */ - - list_for_each_entry(mc_entry, &port->mc_list->list,list) - num_registrations += 2; - } - - if (num_registrations) { - arr = kzalloc_objs(*arr, num_registrations, GFP_ATOMIC); - if (!arr) - goto out; /* Keep the existing array */ - } else - goto out_update; - - list_for_each_entry(adapter, &adapter_list, list) { - for (k = 0; k < EHEA_MAX_PORTS; k++) { - struct ehea_port *port = adapter->port[k]; - - if (!port || (port->state != EHEA_PORT_UP)) - continue; - - if (num_registrations == 0) - goto out_update; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_BROADCAST | - EHEA_BCMC_UNTAGGED; - arr[i++].macaddr = port->mac_addr; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_BROADCAST | - EHEA_BCMC_VLANID_ALL; - arr[i++].macaddr = port->mac_addr; - num_registrations -= 2; - - list_for_each_entry(mc_entry, - &port->mc_list->list, list) { - if (num_registrations == 0) - goto out_update; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_MULTICAST | - EHEA_BCMC_UNTAGGED; - if (mc_entry->macaddr == 0) - arr[i].reg_type |= EHEA_BCMC_SCOPE_ALL; - arr[i++].macaddr = mc_entry->macaddr; - - arr[i].adh = adapter->handle; - arr[i].port_id = port->logical_port_id; - arr[i].reg_type = EHEA_BCMC_MULTICAST | - EHEA_BCMC_VLANID_ALL; - if (mc_entry->macaddr == 0) - arr[i].reg_type |= EHEA_BCMC_SCOPE_ALL; - arr[i++].macaddr = mc_entry->macaddr; - num_registrations -= 2; - } - } - } - -out_update: - kfree(ehea_bcmc_regs.arr); - ehea_bcmc_regs.arr = arr; - ehea_bcmc_regs.num_entries = i; -out: - spin_unlock_irqrestore(&ehea_bcmc_regs.lock, flags); -} - -static void ehea_get_stats64(struct net_device *dev, - struct rtnl_link_stats64 *stats) -{ - struct ehea_port *port = netdev_priv(dev); - u64 rx_packets = 0, tx_packets = 0, rx_bytes = 0, tx_bytes = 0; - int i; - - for (i = 0; i < port->num_def_qps; i++) { - rx_packets += port->port_res[i].rx_packets; - rx_bytes += port->port_res[i].rx_bytes; - } - - for (i = 0; i < port->num_def_qps; i++) { - tx_packets += port->port_res[i].tx_packets; - tx_bytes += port->port_res[i].tx_bytes; - } - - stats->tx_packets = tx_packets; - stats->rx_bytes = rx_bytes; - stats->tx_bytes = tx_bytes; - stats->rx_packets = rx_packets; - - stats->multicast = port->stats.multicast; - stats->rx_errors = port->stats.rx_errors; -} - -static void ehea_update_stats(struct work_struct *work) -{ - struct ehea_port *port = - container_of(work, struct ehea_port, stats_work.work); - struct net_device *dev = port->netdev; - struct rtnl_link_stats64 *stats = &port->stats; - struct hcp_ehea_port_cb2 *cb2; - u64 hret; - - cb2 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb2) { - netdev_err(dev, "No mem for cb2. Some interface statistics were not updated\n"); - goto resched; - } - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB2, H_PORT_CB2_ALL, cb2); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_port failed\n"); - goto out_herr; - } - - if (netif_msg_hw(port)) - ehea_dump(cb2, sizeof(*cb2), "net_device_stats"); - - stats->multicast = cb2->rxmcp; - stats->rx_errors = cb2->rxuerr; - -out_herr: - free_page((unsigned long)cb2); -resched: - schedule_delayed_work(&port->stats_work, - round_jiffies_relative(msecs_to_jiffies(1000))); -} - -static void ehea_refill_rq1(struct ehea_port_res *pr, int index, int nr_of_wqes) -{ - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct net_device *dev = pr->port->netdev; - int max_index_mask = pr->rq1_skba.len - 1; - int fill_wqes = pr->rq1_skba.os_skbs + nr_of_wqes; - int adder = 0; - int i; - - pr->rq1_skba.os_skbs = 0; - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - if (nr_of_wqes > 0) - pr->rq1_skba.index = index; - pr->rq1_skba.os_skbs = fill_wqes; - return; - } - - for (i = 0; i < fill_wqes; i++) { - if (!skb_arr_rq1[index]) { - skb_arr_rq1[index] = netdev_alloc_skb(dev, - EHEA_L_PKT_SIZE); - if (!skb_arr_rq1[index]) { - pr->rq1_skba.os_skbs = fill_wqes - i; - break; - } - } - index--; - index &= max_index_mask; - adder++; - } - - if (adder == 0) - return; - - /* Ring doorbell */ - ehea_update_rq1a(pr->qp, adder); -} - -static void ehea_init_fill_rq1(struct ehea_port_res *pr, int nr_rq1a) -{ - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct net_device *dev = pr->port->netdev; - int i; - - if (nr_rq1a > pr->rq1_skba.len) { - netdev_err(dev, "NR_RQ1A bigger than skb array len\n"); - return; - } - - for (i = 0; i < nr_rq1a; i++) { - skb_arr_rq1[i] = netdev_alloc_skb(dev, EHEA_L_PKT_SIZE); - if (!skb_arr_rq1[i]) - break; - } - /* Ring doorbell */ - ehea_update_rq1a(pr->qp, i - 1); -} - -static int ehea_refill_rq_def(struct ehea_port_res *pr, - struct ehea_q_skb_arr *q_skba, int rq_nr, - int num_wqes, int wqe_type, int packet_size) -{ - struct net_device *dev = pr->port->netdev; - struct ehea_qp *qp = pr->qp; - struct sk_buff **skb_arr = q_skba->arr; - struct ehea_rwqe *rwqe; - int i, index, max_index_mask, fill_wqes; - int adder = 0; - int ret = 0; - - fill_wqes = q_skba->os_skbs + num_wqes; - q_skba->os_skbs = 0; - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - q_skba->os_skbs = fill_wqes; - return ret; - } - - index = q_skba->index; - max_index_mask = q_skba->len - 1; - for (i = 0; i < fill_wqes; i++) { - u64 tmp_addr; - struct sk_buff *skb; - - skb = netdev_alloc_skb_ip_align(dev, packet_size); - if (!skb) { - q_skba->os_skbs = fill_wqes - i; - if (q_skba->os_skbs == q_skba->len - 2) { - netdev_info(pr->port->netdev, - "rq%i ran dry - no mem for skb\n", - rq_nr); - ret = -ENOMEM; - } - break; - } - - skb_arr[index] = skb; - tmp_addr = ehea_map_vaddr(skb->data); - if (tmp_addr == -1) { - dev_consume_skb_any(skb); - q_skba->os_skbs = fill_wqes - i; - ret = 0; - break; - } - - rwqe = ehea_get_next_rwqe(qp, rq_nr); - rwqe->wr_id = EHEA_BMASK_SET(EHEA_WR_ID_TYPE, wqe_type) - | EHEA_BMASK_SET(EHEA_WR_ID_INDEX, index); - rwqe->sg_list[0].l_key = pr->recv_mr.lkey; - rwqe->sg_list[0].vaddr = tmp_addr; - rwqe->sg_list[0].len = packet_size; - rwqe->data_segments = 1; - - index++; - index &= max_index_mask; - adder++; - } - - q_skba->index = index; - if (adder == 0) - goto out; - - /* Ring doorbell */ - iosync(); - if (rq_nr == 2) - ehea_update_rq2a(pr->qp, adder); - else - ehea_update_rq3a(pr->qp, adder); -out: - return ret; -} - - -static int ehea_refill_rq2(struct ehea_port_res *pr, int nr_of_wqes) -{ - return ehea_refill_rq_def(pr, &pr->rq2_skba, 2, - nr_of_wqes, EHEA_RWQE2_TYPE, - EHEA_RQ2_PKT_SIZE); -} - - -static int ehea_refill_rq3(struct ehea_port_res *pr, int nr_of_wqes) -{ - return ehea_refill_rq_def(pr, &pr->rq3_skba, 3, - nr_of_wqes, EHEA_RWQE3_TYPE, - EHEA_MAX_PACKET_SIZE); -} - -static inline int ehea_check_cqe(struct ehea_cqe *cqe, int *rq_num) -{ - *rq_num = (cqe->type & EHEA_CQE_TYPE_RQ) >> 5; - if ((cqe->status & EHEA_CQE_STAT_ERR_MASK) == 0) - return 0; - if (((cqe->status & EHEA_CQE_STAT_ERR_TCP) != 0) && - (cqe->header_length == 0)) - return 0; - return -EINVAL; -} - -static inline void ehea_fill_skb(struct net_device *dev, - struct sk_buff *skb, struct ehea_cqe *cqe, - struct ehea_port_res *pr) -{ - int length = cqe->num_bytes_transfered - 4; /*remove CRC */ - - skb_put(skb, length); - skb->protocol = eth_type_trans(skb, dev); - - /* The packet was not an IPV4 packet so a complemented checksum was - calculated. The value is found in the Internet Checksum field. */ - if (cqe->status & EHEA_CQE_BLIND_CKSUM) { - skb->ip_summed = CHECKSUM_COMPLETE; - skb->csum = csum_unfold(~cqe->inet_checksum_value); - } else - skb->ip_summed = CHECKSUM_UNNECESSARY; - - skb_record_rx_queue(skb, pr - &pr->port->port_res[0]); -} - -static inline struct sk_buff *get_skb_by_index(struct sk_buff **skb_array, - int arr_len, - struct ehea_cqe *cqe) -{ - int skb_index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, cqe->wr_id); - struct sk_buff *skb; - void *pref; - int x; - - x = skb_index + 1; - x &= (arr_len - 1); - - pref = skb_array[x]; - if (pref) { - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - - pref = (skb_array[x]->data); - prefetch(pref); - prefetch(pref + EHEA_CACHE_LINE); - prefetch(pref + EHEA_CACHE_LINE * 2); - prefetch(pref + EHEA_CACHE_LINE * 3); - } - - skb = skb_array[skb_index]; - skb_array[skb_index] = NULL; - return skb; -} - -static inline struct sk_buff *get_skb_by_index_ll(struct sk_buff **skb_array, - int arr_len, int wqe_index) -{ - struct sk_buff *skb; - void *pref; - int x; - - x = wqe_index + 1; - x &= (arr_len - 1); - - pref = skb_array[x]; - if (pref) { - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - - pref = (skb_array[x]->data); - prefetchw(pref); - prefetchw(pref + EHEA_CACHE_LINE); - } - - skb = skb_array[wqe_index]; - skb_array[wqe_index] = NULL; - return skb; -} - -static int ehea_treat_poll_error(struct ehea_port_res *pr, int rq, - struct ehea_cqe *cqe, int *processed_rq2, - int *processed_rq3) -{ - struct sk_buff *skb; - - if (cqe->status & EHEA_CQE_STAT_ERR_TCP) - pr->p_stats.err_tcp_cksum++; - if (cqe->status & EHEA_CQE_STAT_ERR_IP) - pr->p_stats.err_ip_cksum++; - if (cqe->status & EHEA_CQE_STAT_ERR_CRC) - pr->p_stats.err_frame_crc++; - - if (rq == 2) { - *processed_rq2 += 1; - skb = get_skb_by_index(pr->rq2_skba.arr, pr->rq2_skba.len, cqe); - dev_kfree_skb(skb); - } else if (rq == 3) { - *processed_rq3 += 1; - skb = get_skb_by_index(pr->rq3_skba.arr, pr->rq3_skba.len, cqe); - dev_kfree_skb(skb); - } - - if (cqe->status & EHEA_CQE_STAT_FAT_ERR_MASK) { - if (netif_msg_rx_err(pr->port)) { - pr_err("Critical receive error for QP %d. Resetting port.\n", - pr->qp->init_attr.qp_nr); - ehea_dump(cqe, sizeof(*cqe), "CQE"); - } - ehea_schedule_port_reset(pr->port); - return 1; - } - - return 0; -} - -static int ehea_proc_rwqes(struct net_device *dev, - struct ehea_port_res *pr, - int budget) -{ - struct ehea_port *port = pr->port; - struct ehea_qp *qp = pr->qp; - struct ehea_cqe *cqe; - struct sk_buff *skb; - struct sk_buff **skb_arr_rq1 = pr->rq1_skba.arr; - struct sk_buff **skb_arr_rq2 = pr->rq2_skba.arr; - struct sk_buff **skb_arr_rq3 = pr->rq3_skba.arr; - int skb_arr_rq1_len = pr->rq1_skba.len; - int skb_arr_rq2_len = pr->rq2_skba.len; - int skb_arr_rq3_len = pr->rq3_skba.len; - int processed, processed_rq1, processed_rq2, processed_rq3; - u64 processed_bytes = 0; - int wqe_index, last_wqe_index, rq, port_reset; - - processed = processed_rq1 = processed_rq2 = processed_rq3 = 0; - last_wqe_index = 0; - - cqe = ehea_poll_rq1(qp, &wqe_index); - while ((processed < budget) && cqe) { - ehea_inc_rq1(qp); - processed_rq1++; - processed++; - if (netif_msg_rx_status(port)) - ehea_dump(cqe, sizeof(*cqe), "CQE"); - - last_wqe_index = wqe_index; - rmb(); - if (!ehea_check_cqe(cqe, &rq)) { - if (rq == 1) { - /* LL RQ1 */ - skb = get_skb_by_index_ll(skb_arr_rq1, - skb_arr_rq1_len, - wqe_index); - if (unlikely(!skb)) { - netif_info(port, rx_err, dev, - "LL rq1: skb=NULL\n"); - - skb = netdev_alloc_skb(dev, - EHEA_L_PKT_SIZE); - if (!skb) - break; - } - skb_copy_to_linear_data(skb, ((char *)cqe) + 64, - cqe->num_bytes_transfered - 4); - ehea_fill_skb(dev, skb, cqe, pr); - } else if (rq == 2) { - /* RQ2 */ - skb = get_skb_by_index(skb_arr_rq2, - skb_arr_rq2_len, cqe); - if (unlikely(!skb)) { - netif_err(port, rx_err, dev, - "rq2: skb=NULL\n"); - break; - } - ehea_fill_skb(dev, skb, cqe, pr); - processed_rq2++; - } else { - /* RQ3 */ - skb = get_skb_by_index(skb_arr_rq3, - skb_arr_rq3_len, cqe); - if (unlikely(!skb)) { - netif_err(port, rx_err, dev, - "rq3: skb=NULL\n"); - break; - } - ehea_fill_skb(dev, skb, cqe, pr); - processed_rq3++; - } - - processed_bytes += skb->len; - - if (cqe->status & EHEA_CQE_VLAN_TAG_XTRACT) - __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), - cqe->vlan_tag); - - napi_gro_receive(&pr->napi, skb); - } else { - pr->p_stats.poll_receive_errors++; - port_reset = ehea_treat_poll_error(pr, rq, cqe, - &processed_rq2, - &processed_rq3); - if (port_reset) - break; - } - cqe = ehea_poll_rq1(qp, &wqe_index); - } - - pr->rx_packets += processed; - pr->rx_bytes += processed_bytes; - - ehea_refill_rq1(pr, last_wqe_index, processed_rq1); - ehea_refill_rq2(pr, processed_rq2); - ehea_refill_rq3(pr, processed_rq3); - - return processed; -} - -#define SWQE_RESTART_CHECK 0xdeadbeaff00d0000ull - -static void reset_sq_restart_flag(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - pr->sq_restart_flag = 0; - } - wake_up(&port->restart_wq); -} - -static void check_sqs(struct ehea_port *port) -{ - struct ehea_swqe *swqe; - int swqe_index; - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - int ret; - swqe = ehea_get_swqe(pr->qp, &swqe_index); - memset(swqe, 0, SWQE_HEADER_SIZE); - atomic_dec(&pr->swqe_avail); - - swqe->tx_control |= EHEA_SWQE_PURGE; - swqe->wr_id = SWQE_RESTART_CHECK; - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - swqe->tx_control |= EHEA_SWQE_IMM_DATA_PRESENT; - swqe->immediate_data_length = 80; - - ehea_post_swqe(pr->qp, swqe); - - ret = wait_event_timeout(port->restart_wq, - pr->sq_restart_flag == 0, - msecs_to_jiffies(100)); - - if (!ret) { - pr_err("HW/SW queues out of sync\n"); - ehea_schedule_port_reset(pr->port); - return; - } - } -} - - -static struct ehea_cqe *ehea_proc_cqes(struct ehea_port_res *pr, int my_quota) -{ - struct sk_buff *skb; - struct ehea_cq *send_cq = pr->send_cq; - struct ehea_cqe *cqe; - int quota = my_quota; - int cqe_counter = 0; - int swqe_av = 0; - int index; - struct netdev_queue *txq = netdev_get_tx_queue(pr->port->netdev, - pr - &pr->port->port_res[0]); - - cqe = ehea_poll_cq(send_cq); - while (cqe && (quota > 0)) { - ehea_inc_cq(send_cq); - - cqe_counter++; - rmb(); - - if (cqe->wr_id == SWQE_RESTART_CHECK) { - pr->sq_restart_flag = 1; - swqe_av++; - break; - } - - if (cqe->status & EHEA_CQE_STAT_ERR_MASK) { - pr_err("Bad send completion status=0x%04X\n", - cqe->status); - - if (netif_msg_tx_err(pr->port)) - ehea_dump(cqe, sizeof(*cqe), "Send CQE"); - - if (cqe->status & EHEA_CQE_STAT_RESET_MASK) { - pr_err("Resetting port\n"); - ehea_schedule_port_reset(pr->port); - break; - } - } - - if (netif_msg_tx_done(pr->port)) - ehea_dump(cqe, sizeof(*cqe), "CQE"); - - if (likely(EHEA_BMASK_GET(EHEA_WR_ID_TYPE, cqe->wr_id) - == EHEA_SWQE2_TYPE)) { - - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, cqe->wr_id); - skb = pr->sq_skba.arr[index]; - dev_consume_skb_any(skb); - pr->sq_skba.arr[index] = NULL; - } - - swqe_av += EHEA_BMASK_GET(EHEA_WR_ID_REFILL, cqe->wr_id); - quota--; - - cqe = ehea_poll_cq(send_cq); - } - - ehea_update_feca(send_cq, cqe_counter); - atomic_add(swqe_av, &pr->swqe_avail); - - if (unlikely(netif_tx_queue_stopped(txq) && - (atomic_read(&pr->swqe_avail) >= pr->swqe_refill_th))) { - __netif_tx_lock(txq, smp_processor_id()); - if (netif_tx_queue_stopped(txq) && - (atomic_read(&pr->swqe_avail) >= pr->swqe_refill_th)) - netif_tx_wake_queue(txq); - __netif_tx_unlock(txq); - } - - wake_up(&pr->port->swqe_avail_wq); - - return cqe; -} - -#define EHEA_POLL_MAX_CQES 65535 - -static int ehea_poll(struct napi_struct *napi, int budget) -{ - struct ehea_port_res *pr = container_of(napi, struct ehea_port_res, - napi); - struct net_device *dev = pr->port->netdev; - struct ehea_cqe *cqe; - struct ehea_cqe *cqe_skb = NULL; - int wqe_index; - int rx = 0; - - cqe_skb = ehea_proc_cqes(pr, EHEA_POLL_MAX_CQES); - rx += ehea_proc_rwqes(dev, pr, budget - rx); - - while (rx != budget) { - napi_complete(napi); - ehea_reset_cq_ep(pr->recv_cq); - ehea_reset_cq_ep(pr->send_cq); - ehea_reset_cq_n1(pr->recv_cq); - ehea_reset_cq_n1(pr->send_cq); - rmb(); - cqe = ehea_poll_rq1(pr->qp, &wqe_index); - cqe_skb = ehea_poll_cq(pr->send_cq); - - if (!cqe && !cqe_skb) - return rx; - - if (!napi_schedule(napi)) - return rx; - - cqe_skb = ehea_proc_cqes(pr, EHEA_POLL_MAX_CQES); - rx += ehea_proc_rwqes(dev, pr, budget - rx); - } - - return rx; -} - -static irqreturn_t ehea_recv_irq_handler(int irq, void *param) -{ - struct ehea_port_res *pr = param; - - napi_schedule(&pr->napi); - - return IRQ_HANDLED; -} - -static irqreturn_t ehea_qp_aff_irq_handler(int irq, void *param) -{ - struct ehea_port *port = param; - struct ehea_eqe *eqe; - struct ehea_qp *qp; - u32 qp_token; - u64 resource_type, aer, aerr; - int reset_port = 0; - - eqe = ehea_poll_eq(port->qp_eq); - - while (eqe) { - qp_token = EHEA_BMASK_GET(EHEA_EQE_QP_TOKEN, eqe->entry); - pr_err("QP aff_err: entry=0x%llx, token=0x%x\n", - eqe->entry, qp_token); - - qp = port->port_res[qp_token].qp; - - resource_type = ehea_error_data(port->adapter, qp->fw_handle, - &aer, &aerr); - - if (resource_type == EHEA_AER_RESTYPE_QP) { - if ((aer & EHEA_AER_RESET_MASK) || - (aerr & EHEA_AERR_RESET_MASK)) - reset_port = 1; - } else - reset_port = 1; /* Reset in case of CQ or EQ error */ - - eqe = ehea_poll_eq(port->qp_eq); - } - - if (reset_port) { - pr_err("Resetting port\n"); - ehea_schedule_port_reset(port); - } - - return IRQ_HANDLED; -} - -static struct ehea_port *ehea_get_port(struct ehea_adapter *adapter, - int logical_port) -{ - int i; - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) - if (adapter->port[i]->logical_port_id == logical_port) - return adapter->port[i]; - return NULL; -} - -int ehea_sense_port_attr(struct ehea_port *port) -{ - int ret; - u64 hret; - struct hcp_ehea_port_cb0 *cb0; - - /* may be called via ehea_neq_tasklet() */ - cb0 = (void *)get_zeroed_page(GFP_ATOMIC); - if (!cb0) { - pr_err("no mem for cb0\n"); - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, H_PORT_CB0, - EHEA_BMASK_SET(H_PORT_CB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_free; - } - - /* MAC address */ - port->mac_addr = cb0->port_mac_addr << 16; - - if (!is_valid_ether_addr((u8 *)&port->mac_addr)) { - ret = -EADDRNOTAVAIL; - goto out_free; - } - - /* Port speed */ - switch (cb0->port_speed) { - case H_SPEED_10M_H: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 0; - break; - case H_SPEED_10M_F: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 1; - break; - case H_SPEED_100M_H: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 0; - break; - case H_SPEED_100M_F: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 1; - break; - case H_SPEED_1G_F: - port->port_speed = EHEA_SPEED_1G; - port->full_duplex = 1; - break; - case H_SPEED_10G_F: - port->port_speed = EHEA_SPEED_10G; - port->full_duplex = 1; - break; - default: - port->port_speed = 0; - port->full_duplex = 0; - break; - } - - port->autoneg = 1; - port->num_mcs = cb0->num_default_qps; - - /* Number of default QPs */ - if (use_mcs) - port->num_def_qps = cb0->num_default_qps; - else - port->num_def_qps = 1; - - if (!port->num_def_qps) { - ret = -EINVAL; - goto out_free; - } - - ret = 0; -out_free: - if (ret || netif_msg_probe(port)) - ehea_dump(cb0, sizeof(*cb0), "ehea_sense_port_attr"); - free_page((unsigned long)cb0); -out: - return ret; -} - -int ehea_set_portspeed(struct ehea_port *port, u32 port_speed) -{ - struct hcp_ehea_port_cb4 *cb4; - u64 hret; - int ret = 0; - - cb4 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb4) { - pr_err("no mem for cb4\n"); - ret = -ENOMEM; - goto out; - } - - cb4->port_speed = port_speed; - - netif_carrier_off(port->netdev); - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, H_PORT_CB4_SPEED, cb4); - if (hret == H_SUCCESS) { - port->autoneg = port_speed == EHEA_SPEED_AUTONEG ? 1 : 0; - - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, H_PORT_CB4_SPEED, - cb4); - if (hret == H_SUCCESS) { - switch (cb4->port_speed) { - case H_SPEED_10M_H: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 0; - break; - case H_SPEED_10M_F: - port->port_speed = EHEA_SPEED_10M; - port->full_duplex = 1; - break; - case H_SPEED_100M_H: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 0; - break; - case H_SPEED_100M_F: - port->port_speed = EHEA_SPEED_100M; - port->full_duplex = 1; - break; - case H_SPEED_1G_F: - port->port_speed = EHEA_SPEED_1G; - port->full_duplex = 1; - break; - case H_SPEED_10G_F: - port->port_speed = EHEA_SPEED_10G; - port->full_duplex = 1; - break; - default: - port->port_speed = 0; - port->full_duplex = 0; - break; - } - } else { - pr_err("Failed sensing port speed\n"); - ret = -EIO; - } - } else { - if (hret == H_AUTHORITY) { - pr_info("Hypervisor denied setting port speed\n"); - ret = -EPERM; - } else { - ret = -EIO; - pr_err("Failed setting port speed\n"); - } - } - if (!prop_carrier_state || (port->phy_link == EHEA_PHY_LINK_UP)) - netif_carrier_on(port->netdev); - - free_page((unsigned long)cb4); -out: - return ret; -} - -static void ehea_parse_eqe(struct ehea_adapter *adapter, u64 eqe) -{ - int ret; - u8 ec; - u8 portnum; - struct ehea_port *port; - struct net_device *dev; - - ec = EHEA_BMASK_GET(NEQE_EVENT_CODE, eqe); - portnum = EHEA_BMASK_GET(NEQE_PORTNUM, eqe); - port = ehea_get_port(adapter, portnum); - if (!port) { - netdev_err(NULL, "unknown portnum %x\n", portnum); - return; - } - dev = port->netdev; - - switch (ec) { - case EHEA_EC_PORTSTATE_CHG: /* port state change */ - - if (EHEA_BMASK_GET(NEQE_PORT_UP, eqe)) { - if (!netif_carrier_ok(dev)) { - ret = ehea_sense_port_attr(port); - if (ret) { - netdev_err(dev, "failed resensing port attributes\n"); - break; - } - - netif_info(port, link, dev, - "Logical port up: %dMbps %s Duplex\n", - port->port_speed, - port->full_duplex == 1 ? - "Full" : "Half"); - - netif_carrier_on(dev); - netif_wake_queue(dev); - } - } else - if (netif_carrier_ok(dev)) { - netif_info(port, link, dev, - "Logical port down\n"); - netif_carrier_off(dev); - netif_tx_disable(dev); - } - - if (EHEA_BMASK_GET(NEQE_EXTSWITCH_PORT_UP, eqe)) { - port->phy_link = EHEA_PHY_LINK_UP; - netif_info(port, link, dev, - "Physical port up\n"); - if (prop_carrier_state) - netif_carrier_on(dev); - } else { - port->phy_link = EHEA_PHY_LINK_DOWN; - netif_info(port, link, dev, - "Physical port down\n"); - if (prop_carrier_state) - netif_carrier_off(dev); - } - - if (EHEA_BMASK_GET(NEQE_EXTSWITCH_PRIMARY, eqe)) - netdev_info(dev, - "External switch port is primary port\n"); - else - netdev_info(dev, - "External switch port is backup port\n"); - - break; - case EHEA_EC_ADAPTER_MALFUNC: - netdev_err(dev, "Adapter malfunction\n"); - break; - case EHEA_EC_PORT_MALFUNC: - netdev_info(dev, "Port malfunction\n"); - netif_carrier_off(dev); - netif_tx_disable(dev); - break; - default: - netdev_err(dev, "unknown event code %x, eqe=0x%llX\n", ec, eqe); - break; - } -} - -static void ehea_neq_tasklet(struct tasklet_struct *t) -{ - struct ehea_adapter *adapter = from_tasklet(adapter, t, neq_tasklet); - struct ehea_eqe *eqe; - u64 event_mask; - - eqe = ehea_poll_eq(adapter->neq); - pr_debug("eqe=%p\n", eqe); - - while (eqe) { - pr_debug("*eqe=%lx\n", (unsigned long) eqe->entry); - ehea_parse_eqe(adapter, eqe->entry); - eqe = ehea_poll_eq(adapter->neq); - pr_debug("next eqe=%p\n", eqe); - } - - event_mask = EHEA_BMASK_SET(NELR_PORTSTATE_CHG, 1) - | EHEA_BMASK_SET(NELR_ADAPTER_MALFUNC, 1) - | EHEA_BMASK_SET(NELR_PORT_MALFUNC, 1); - - ehea_h_reset_events(adapter->handle, - adapter->neq->fw_handle, event_mask); -} - -static irqreturn_t ehea_interrupt_neq(int irq, void *param) -{ - struct ehea_adapter *adapter = param; - tasklet_hi_schedule(&adapter->neq_tasklet); - return IRQ_HANDLED; -} - - -static int ehea_fill_port_res(struct ehea_port_res *pr) -{ - int ret; - struct ehea_qp_init_attr *init_attr = &pr->qp->init_attr; - - ehea_init_fill_rq1(pr, pr->rq1_skba.len); - - ret = ehea_refill_rq2(pr, init_attr->act_nr_rwqes_rq2 - 1); - - ret |= ehea_refill_rq3(pr, init_attr->act_nr_rwqes_rq3 - 1); - - return ret; -} - -static int ehea_reg_interrupts(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_port_res *pr; - int i, ret; - - - snprintf(port->int_aff_name, EHEA_IRQ_NAME_SIZE - 1, "%s-aff", - dev->name); - - ret = ibmebus_request_irq(port->qp_eq->attr.ist1, - ehea_qp_aff_irq_handler, - 0, port->int_aff_name, port); - if (ret) { - netdev_err(dev, "failed registering irq for qp_aff_irq_handler:ist=%X\n", - port->qp_eq->attr.ist1); - goto out_free_qpeq; - } - - netif_info(port, ifup, dev, - "irq_handle 0x%X for function qp_aff_irq_handler registered\n", - port->qp_eq->attr.ist1); - - - for (i = 0; i < port->num_def_qps; i++) { - pr = &port->port_res[i]; - snprintf(pr->int_send_name, EHEA_IRQ_NAME_SIZE - 1, - "%s-queue%d", dev->name, i); - ret = ibmebus_request_irq(pr->eq->attr.ist1, - ehea_recv_irq_handler, - 0, pr->int_send_name, pr); - if (ret) { - netdev_err(dev, "failed registering irq for ehea_queue port_res_nr:%d, ist=%X\n", - i, pr->eq->attr.ist1); - goto out_free_req; - } - netif_info(port, ifup, dev, - "irq_handle 0x%X for function ehea_queue_int %d registered\n", - pr->eq->attr.ist1, i); - } -out: - return ret; - - -out_free_req: - while (--i >= 0) { - u32 ist = port->port_res[i].eq->attr.ist1; - ibmebus_free_irq(ist, &port->port_res[i]); - } - -out_free_qpeq: - ibmebus_free_irq(port->qp_eq->attr.ist1, port); - i = port->num_def_qps; - - goto out; - -} - -static void ehea_free_interrupts(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_port_res *pr; - int i; - - /* send */ - - for (i = 0; i < port->num_def_qps; i++) { - pr = &port->port_res[i]; - ibmebus_free_irq(pr->eq->attr.ist1, pr); - netif_info(port, intr, dev, - "free send irq for res %d with handle 0x%X\n", - i, pr->eq->attr.ist1); - } - - /* associated events */ - ibmebus_free_irq(port->qp_eq->attr.ist1, port); - netif_info(port, intr, dev, - "associated event interrupt for handle 0x%X freed\n", - port->qp_eq->attr.ist1); -} - -static int ehea_configure_port(struct ehea_port *port) -{ - int ret, i; - u64 hret, mask; - struct hcp_ehea_port_cb0 *cb0; - - ret = -ENOMEM; - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) - goto out; - - cb0->port_rc = EHEA_BMASK_SET(PXLY_RC_VALID, 1) - | EHEA_BMASK_SET(PXLY_RC_IP_CHKSUM, 1) - | EHEA_BMASK_SET(PXLY_RC_TCP_UDP_CHKSUM, 1) - | EHEA_BMASK_SET(PXLY_RC_VLAN_XTRACT, 1) - | EHEA_BMASK_SET(PXLY_RC_VLAN_TAG_FILTER, - PXLY_RC_VLAN_FILTER) - | EHEA_BMASK_SET(PXLY_RC_JUMBO_FRAME, 1); - - for (i = 0; i < port->num_mcs; i++) - if (use_mcs) - cb0->default_qpn_arr[i] = - port->port_res[i].qp->init_attr.qp_nr; - else - cb0->default_qpn_arr[i] = - port->port_res[0].qp->init_attr.qp_nr; - - if (netif_msg_ifup(port)) - ehea_dump(cb0, sizeof(*cb0), "ehea_configure_port"); - - mask = EHEA_BMASK_SET(H_PORT_CB0_PRC, 1) - | EHEA_BMASK_SET(H_PORT_CB0_DEFQPNARRAY, 1); - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB0, mask, cb0); - ret = -EIO; - if (hret != H_SUCCESS) - goto out_free; - - ret = 0; - -out_free: - free_page((unsigned long)cb0); -out: - return ret; -} - -static int ehea_gen_smrs(struct ehea_port_res *pr) -{ - int ret; - struct ehea_adapter *adapter = pr->port->adapter; - - ret = ehea_gen_smr(adapter, &adapter->mr, &pr->send_mr); - if (ret) - goto out; - - ret = ehea_gen_smr(adapter, &adapter->mr, &pr->recv_mr); - if (ret) - goto out_free; - - return 0; - -out_free: - ehea_rem_mr(&pr->send_mr); -out: - pr_err("Generating SMRS failed\n"); - return -EIO; -} - -static int ehea_rem_smrs(struct ehea_port_res *pr) -{ - if ((ehea_rem_mr(&pr->send_mr)) || - (ehea_rem_mr(&pr->recv_mr))) - return -EIO; - else - return 0; -} - -static int ehea_init_q_skba(struct ehea_q_skb_arr *q_skba, int max_q_entries) -{ - int arr_size = sizeof(void *) * max_q_entries; - - q_skba->arr = vzalloc(arr_size); - if (!q_skba->arr) - return -ENOMEM; - - q_skba->len = max_q_entries; - q_skba->index = 0; - q_skba->os_skbs = 0; - - return 0; -} - -static int ehea_init_port_res(struct ehea_port *port, struct ehea_port_res *pr, - struct port_res_cfg *pr_cfg, int queue_token) -{ - struct ehea_adapter *adapter = port->adapter; - enum ehea_eq_type eq_type = EHEA_EQ; - struct ehea_qp_init_attr *init_attr = NULL; - int ret = -EIO; - u64 tx_bytes, rx_bytes, tx_packets, rx_packets; - - tx_bytes = pr->tx_bytes; - tx_packets = pr->tx_packets; - rx_bytes = pr->rx_bytes; - rx_packets = pr->rx_packets; - - memset(pr, 0, sizeof(struct ehea_port_res)); - - pr->tx_bytes = tx_bytes; - pr->tx_packets = tx_packets; - pr->rx_bytes = rx_bytes; - pr->rx_packets = rx_packets; - - pr->port = port; - - pr->eq = ehea_create_eq(adapter, eq_type, EHEA_MAX_ENTRIES_EQ, 0); - if (!pr->eq) { - pr_err("create_eq failed (eq)\n"); - goto out_free; - } - - pr->recv_cq = ehea_create_cq(adapter, pr_cfg->max_entries_rcq, - pr->eq->fw_handle, - port->logical_port_id); - if (!pr->recv_cq) { - pr_err("create_cq failed (cq_recv)\n"); - goto out_free; - } - - pr->send_cq = ehea_create_cq(adapter, pr_cfg->max_entries_scq, - pr->eq->fw_handle, - port->logical_port_id); - if (!pr->send_cq) { - pr_err("create_cq failed (cq_send)\n"); - goto out_free; - } - - if (netif_msg_ifup(port)) - pr_info("Send CQ: act_nr_cqes=%d, Recv CQ: act_nr_cqes=%d\n", - pr->send_cq->attr.act_nr_of_cqes, - pr->recv_cq->attr.act_nr_of_cqes); - - init_attr = kzalloc_obj(*init_attr); - if (!init_attr) { - ret = -ENOMEM; - pr_err("no mem for ehea_qp_init_attr\n"); - goto out_free; - } - - init_attr->low_lat_rq1 = 1; - init_attr->signalingtype = 1; /* generate CQE if specified in WQE */ - init_attr->rq_count = 3; - init_attr->qp_token = queue_token; - init_attr->max_nr_send_wqes = pr_cfg->max_entries_sq; - init_attr->max_nr_rwqes_rq1 = pr_cfg->max_entries_rq1; - init_attr->max_nr_rwqes_rq2 = pr_cfg->max_entries_rq2; - init_attr->max_nr_rwqes_rq3 = pr_cfg->max_entries_rq3; - init_attr->wqe_size_enc_sq = EHEA_SG_SQ; - init_attr->wqe_size_enc_rq1 = EHEA_SG_RQ1; - init_attr->wqe_size_enc_rq2 = EHEA_SG_RQ2; - init_attr->wqe_size_enc_rq3 = EHEA_SG_RQ3; - init_attr->rq2_threshold = EHEA_RQ2_THRESHOLD; - init_attr->rq3_threshold = EHEA_RQ3_THRESHOLD; - init_attr->port_nr = port->logical_port_id; - init_attr->send_cq_handle = pr->send_cq->fw_handle; - init_attr->recv_cq_handle = pr->recv_cq->fw_handle; - init_attr->aff_eq_handle = port->qp_eq->fw_handle; - - pr->qp = ehea_create_qp(adapter, adapter->pd, init_attr); - if (!pr->qp) { - pr_err("create_qp failed\n"); - ret = -EIO; - goto out_free; - } - - if (netif_msg_ifup(port)) - pr_info("QP: qp_nr=%d\n act_nr_snd_wqe=%d\n nr_rwqe_rq1=%d\n nr_rwqe_rq2=%d\n nr_rwqe_rq3=%d\n", - init_attr->qp_nr, - init_attr->act_nr_send_wqes, - init_attr->act_nr_rwqes_rq1, - init_attr->act_nr_rwqes_rq2, - init_attr->act_nr_rwqes_rq3); - - pr->sq_skba_size = init_attr->act_nr_send_wqes + 1; - - ret = ehea_init_q_skba(&pr->sq_skba, pr->sq_skba_size); - ret |= ehea_init_q_skba(&pr->rq1_skba, init_attr->act_nr_rwqes_rq1 + 1); - ret |= ehea_init_q_skba(&pr->rq2_skba, init_attr->act_nr_rwqes_rq2 + 1); - ret |= ehea_init_q_skba(&pr->rq3_skba, init_attr->act_nr_rwqes_rq3 + 1); - if (ret) - goto out_free; - - pr->swqe_refill_th = init_attr->act_nr_send_wqes / 10; - if (ehea_gen_smrs(pr) != 0) { - ret = -EIO; - goto out_free; - } - - atomic_set(&pr->swqe_avail, init_attr->act_nr_send_wqes - 1); - - kfree(init_attr); - - netif_napi_add(pr->port->netdev, &pr->napi, ehea_poll); - - ret = 0; - goto out; - -out_free: - kfree(init_attr); - vfree(pr->sq_skba.arr); - vfree(pr->rq1_skba.arr); - vfree(pr->rq2_skba.arr); - vfree(pr->rq3_skba.arr); - ehea_destroy_qp(pr->qp); - ehea_destroy_cq(pr->send_cq); - ehea_destroy_cq(pr->recv_cq); - ehea_destroy_eq(pr->eq); -out: - return ret; -} - -static int ehea_clean_portres(struct ehea_port *port, struct ehea_port_res *pr) -{ - int ret, i; - - if (pr->qp) - netif_napi_del(&pr->napi); - - ret = ehea_destroy_qp(pr->qp); - - if (!ret) { - ehea_destroy_cq(pr->send_cq); - ehea_destroy_cq(pr->recv_cq); - ehea_destroy_eq(pr->eq); - - for (i = 0; i < pr->rq1_skba.len; i++) - dev_kfree_skb(pr->rq1_skba.arr[i]); - - for (i = 0; i < pr->rq2_skba.len; i++) - dev_kfree_skb(pr->rq2_skba.arr[i]); - - for (i = 0; i < pr->rq3_skba.len; i++) - dev_kfree_skb(pr->rq3_skba.arr[i]); - - for (i = 0; i < pr->sq_skba.len; i++) - dev_kfree_skb(pr->sq_skba.arr[i]); - - vfree(pr->rq1_skba.arr); - vfree(pr->rq2_skba.arr); - vfree(pr->rq3_skba.arr); - vfree(pr->sq_skba.arr); - ret = ehea_rem_smrs(pr); - } - return ret; -} - -static void write_swqe2_immediate(struct sk_buff *skb, struct ehea_swqe *swqe, - u32 lkey) -{ - int skb_data_size = skb_headlen(skb); - u8 *imm_data = &swqe->u.immdata_desc.immediate_data[0]; - struct ehea_vsgentry *sg1entry = &swqe->u.immdata_desc.sg_entry; - unsigned int immediate_len = SWQE2_MAX_IMM; - - swqe->descriptors = 0; - - if (skb_is_gso(skb)) { - swqe->tx_control |= EHEA_SWQE_TSO; - swqe->mss = skb_shinfo(skb)->gso_size; - /* - * For TSO packets we only copy the headers into the - * immediate area. - */ - immediate_len = skb_tcp_all_headers(skb); - } - - if (skb_is_gso(skb) || skb_data_size >= SWQE2_MAX_IMM) { - skb_copy_from_linear_data(skb, imm_data, immediate_len); - swqe->immediate_data_length = immediate_len; - - if (skb_data_size > immediate_len) { - sg1entry->l_key = lkey; - sg1entry->len = skb_data_size - immediate_len; - sg1entry->vaddr = - ehea_map_vaddr(skb->data + immediate_len); - swqe->descriptors++; - } - } else { - skb_copy_from_linear_data(skb, imm_data, skb_data_size); - swqe->immediate_data_length = skb_data_size; - } -} - -static inline void write_swqe2_data(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe, u32 lkey) -{ - struct ehea_vsgentry *sg_list, *sg1entry, *sgentry; - skb_frag_t *frag; - int nfrags, sg1entry_contains_frag_data, i; - - nfrags = skb_shinfo(skb)->nr_frags; - sg1entry = &swqe->u.immdata_desc.sg_entry; - sg_list = (struct ehea_vsgentry *)&swqe->u.immdata_desc.sg_list; - sg1entry_contains_frag_data = 0; - - write_swqe2_immediate(skb, swqe, lkey); - - /* write descriptors */ - if (nfrags > 0) { - if (swqe->descriptors == 0) { - /* sg1entry not yet used */ - frag = &skb_shinfo(skb)->frags[0]; - - /* copy sg1entry data */ - sg1entry->l_key = lkey; - sg1entry->len = skb_frag_size(frag); - sg1entry->vaddr = - ehea_map_vaddr(skb_frag_address(frag)); - swqe->descriptors++; - sg1entry_contains_frag_data = 1; - } - - for (i = sg1entry_contains_frag_data; i < nfrags; i++) { - - frag = &skb_shinfo(skb)->frags[i]; - sgentry = &sg_list[i - sg1entry_contains_frag_data]; - - sgentry->l_key = lkey; - sgentry->len = skb_frag_size(frag); - sgentry->vaddr = ehea_map_vaddr(skb_frag_address(frag)); - swqe->descriptors++; - } - } -} - -static int ehea_broadcast_reg_helper(struct ehea_port *port, u32 hcallid) -{ - int ret = 0; - u64 hret; - u8 reg_type; - - /* De/Register untagged packets */ - reg_type = EHEA_BCMC_BROADCAST | EHEA_BCMC_UNTAGGED; - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, port->mac_addr, 0, hcallid); - if (hret != H_SUCCESS) { - pr_err("%sregistering bc address failed (tagged)\n", - hcallid == H_REG_BCMC ? "" : "de"); - ret = -EIO; - goto out_herr; - } - - /* De/Register VLAN packets */ - reg_type = EHEA_BCMC_BROADCAST | EHEA_BCMC_VLANID_ALL; - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, port->mac_addr, 0, hcallid); - if (hret != H_SUCCESS) { - pr_err("%sregistering bc address failed (vlan)\n", - hcallid == H_REG_BCMC ? "" : "de"); - ret = -EIO; - } -out_herr: - return ret; -} - -static int ehea_set_mac_addr(struct net_device *dev, void *sa) -{ - struct ehea_port *port = netdev_priv(dev); - struct sockaddr *mac_addr = sa; - struct hcp_ehea_port_cb0 *cb0; - int ret; - u64 hret; - - if (!is_valid_ether_addr(mac_addr->sa_data)) { - ret = -EADDRNOTAVAIL; - goto out; - } - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - pr_err("no mem for cb0\n"); - ret = -ENOMEM; - goto out; - } - - memcpy(&(cb0->port_mac_addr), &(mac_addr->sa_data[0]), ETH_ALEN); - - cb0->port_mac_addr = cb0->port_mac_addr >> 16; - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, H_PORT_CB0, - EHEA_BMASK_SET(H_PORT_CB0_MAC, 1), cb0); - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_free; - } - - eth_hw_addr_set(dev, mac_addr->sa_data); - - /* Deregister old MAC in pHYP */ - if (port->state == EHEA_PORT_UP) { - ret = ehea_broadcast_reg_helper(port, H_DEREG_BCMC); - if (ret) - goto out_upregs; - } - - port->mac_addr = cb0->port_mac_addr << 16; - - /* Register new MAC in pHYP */ - if (port->state == EHEA_PORT_UP) { - ret = ehea_broadcast_reg_helper(port, H_REG_BCMC); - if (ret) - goto out_upregs; - } - - ret = 0; - -out_upregs: - ehea_update_bcmc_registrations(); -out_free: - free_page((unsigned long)cb0); -out: - return ret; -} - -static void ehea_promiscuous_error(u64 hret, int enable) -{ - if (hret == H_AUTHORITY) - pr_info("Hypervisor denied %sabling promiscuous mode\n", - enable == 1 ? "en" : "dis"); - else - pr_err("failed %sabling promiscuous mode\n", - enable == 1 ? "en" : "dis"); -} - -static void ehea_promiscuous(struct net_device *dev, int enable) -{ - struct ehea_port *port = netdev_priv(dev); - struct hcp_ehea_port_cb7 *cb7; - u64 hret; - - if (enable == port->promisc) - return; - - cb7 = (void *)get_zeroed_page(GFP_ATOMIC); - if (!cb7) { - pr_err("no mem for cb7\n"); - goto out; - } - - /* Modify Pxs_DUCQPN in CB7 */ - cb7->def_uc_qpn = enable == 1 ? port->port_res[0].qp->fw_handle : 0; - - hret = ehea_h_modify_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB7, H_PORT_CB7_DUCQPN, cb7); - if (hret) { - ehea_promiscuous_error(hret, enable); - goto out; - } - - port->promisc = enable; -out: - free_page((unsigned long)cb7); -} - -static u64 ehea_multicast_reg_helper(struct ehea_port *port, u64 mc_mac_addr, - u32 hcallid) -{ - u64 hret; - u8 reg_type; - - reg_type = EHEA_BCMC_MULTICAST | EHEA_BCMC_UNTAGGED; - if (mc_mac_addr == 0) - reg_type |= EHEA_BCMC_SCOPE_ALL; - - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, mc_mac_addr, 0, hcallid); - if (hret) - goto out; - - reg_type = EHEA_BCMC_MULTICAST | EHEA_BCMC_VLANID_ALL; - if (mc_mac_addr == 0) - reg_type |= EHEA_BCMC_SCOPE_ALL; - - hret = ehea_h_reg_dereg_bcmc(port->adapter->handle, - port->logical_port_id, - reg_type, mc_mac_addr, 0, hcallid); -out: - return hret; -} - -static int ehea_drop_multicast_list(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_mc_list *mc_entry = port->mc_list; - struct list_head *pos; - struct list_head *temp; - int ret = 0; - u64 hret; - - list_for_each_safe(pos, temp, &(port->mc_list->list)) { - mc_entry = list_entry(pos, struct ehea_mc_list, list); - - hret = ehea_multicast_reg_helper(port, mc_entry->macaddr, - H_DEREG_BCMC); - if (hret) { - pr_err("failed deregistering mcast MAC\n"); - ret = -EIO; - } - - list_del(pos); - kfree(mc_entry); - } - return ret; -} - -static void ehea_allmulti(struct net_device *dev, int enable) -{ - struct ehea_port *port = netdev_priv(dev); - u64 hret; - - if (!port->allmulti) { - if (enable) { - /* Enable ALLMULTI */ - ehea_drop_multicast_list(dev); - hret = ehea_multicast_reg_helper(port, 0, H_REG_BCMC); - if (!hret) - port->allmulti = 1; - else - netdev_err(dev, - "failed enabling IFF_ALLMULTI\n"); - } - } else { - if (!enable) { - /* Disable ALLMULTI */ - hret = ehea_multicast_reg_helper(port, 0, H_DEREG_BCMC); - if (!hret) - port->allmulti = 0; - else - netdev_err(dev, - "failed disabling IFF_ALLMULTI\n"); - } - } -} - -static void ehea_add_multicast_entry(struct ehea_port *port, u8 *mc_mac_addr) -{ - struct ehea_mc_list *ehea_mcl_entry; - u64 hret; - - ehea_mcl_entry = kzalloc_obj(*ehea_mcl_entry, GFP_ATOMIC); - if (!ehea_mcl_entry) - return; - - INIT_LIST_HEAD(&ehea_mcl_entry->list); - - memcpy(&ehea_mcl_entry->macaddr, mc_mac_addr, ETH_ALEN); - - hret = ehea_multicast_reg_helper(port, ehea_mcl_entry->macaddr, - H_REG_BCMC); - if (!hret) - list_add(&ehea_mcl_entry->list, &port->mc_list->list); - else { - pr_err("failed registering mcast MAC\n"); - kfree(ehea_mcl_entry); - } -} - -static void ehea_set_multicast_list(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct netdev_hw_addr *ha; - int ret; - - ehea_promiscuous(dev, !!(dev->flags & IFF_PROMISC)); - - if (dev->flags & IFF_ALLMULTI) { - ehea_allmulti(dev, 1); - goto out; - } - ehea_allmulti(dev, 0); - - if (!netdev_mc_empty(dev)) { - ret = ehea_drop_multicast_list(dev); - if (ret) { - /* Dropping the current multicast list failed. - * Enabling ALL_MULTI is the best we can do. - */ - ehea_allmulti(dev, 1); - } - - if (netdev_mc_count(dev) > port->adapter->max_mc_mac) { - pr_info("Mcast registration limit reached (0x%llx). Use ALLMULTI!\n", - port->adapter->max_mc_mac); - goto out; - } - - netdev_for_each_mc_addr(ha, dev) - ehea_add_multicast_entry(port, ha->addr); - - } -out: - ehea_update_bcmc_registrations(); -} - -static void xmit_common(struct sk_buff *skb, struct ehea_swqe *swqe) -{ - swqe->tx_control |= EHEA_SWQE_IMM_DATA_PRESENT | EHEA_SWQE_CRC; - - if (vlan_get_protocol(skb) != htons(ETH_P_IP)) - return; - - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_IP_CHECKSUM; - - swqe->ip_start = skb_network_offset(skb); - swqe->ip_end = swqe->ip_start + ip_hdrlen(skb) - 1; - - switch (ip_hdr(skb)->protocol) { - case IPPROTO_UDP: - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_TCP_CHECKSUM; - - swqe->tcp_offset = swqe->ip_end + 1 + - offsetof(struct udphdr, check); - break; - - case IPPROTO_TCP: - if (skb->ip_summed == CHECKSUM_PARTIAL) - swqe->tx_control |= EHEA_SWQE_TCP_CHECKSUM; - - swqe->tcp_offset = swqe->ip_end + 1 + - offsetof(struct tcphdr, check); - break; - } -} - -static void ehea_xmit2(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe, u32 lkey) -{ - swqe->tx_control |= EHEA_SWQE_DESCRIPTORS_PRESENT; - - xmit_common(skb, swqe); - - write_swqe2_data(skb, dev, swqe, lkey); -} - -static void ehea_xmit3(struct sk_buff *skb, struct net_device *dev, - struct ehea_swqe *swqe) -{ - u8 *imm_data = &swqe->u.immdata_nodesc.immediate_data[0]; - - xmit_common(skb, swqe); - - if (!skb->data_len) - skb_copy_from_linear_data(skb, imm_data, skb->len); - else - skb_copy_bits(skb, 0, imm_data, skb->len); - - swqe->immediate_data_length = skb->len; - dev_consume_skb_any(skb); -} - -static netdev_tx_t ehea_start_xmit(struct sk_buff *skb, struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_swqe *swqe; - u32 lkey; - int swqe_index; - struct ehea_port_res *pr; - struct netdev_queue *txq; - - pr = &port->port_res[skb_get_queue_mapping(skb)]; - txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb)); - - swqe = ehea_get_swqe(pr->qp, &swqe_index); - memset(swqe, 0, SWQE_HEADER_SIZE); - atomic_dec(&pr->swqe_avail); - - if (skb_vlan_tag_present(skb)) { - swqe->tx_control |= EHEA_SWQE_VLAN_INSERT; - swqe->vlan_tag = skb_vlan_tag_get(skb); - } - - pr->tx_packets++; - pr->tx_bytes += skb->len; - - if (skb->len <= SWQE3_MAX_IMM) { - u32 sig_iv = port->sig_comp_iv; - u32 swqe_num = pr->swqe_id_counter; - ehea_xmit3(skb, dev, swqe); - swqe->wr_id = EHEA_BMASK_SET(EHEA_WR_ID_TYPE, EHEA_SWQE3_TYPE) - | EHEA_BMASK_SET(EHEA_WR_ID_COUNT, swqe_num); - if (pr->swqe_ll_count >= (sig_iv - 1)) { - swqe->wr_id |= EHEA_BMASK_SET(EHEA_WR_ID_REFILL, - sig_iv); - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - pr->swqe_ll_count = 0; - } else - pr->swqe_ll_count += 1; - } else { - swqe->wr_id = - EHEA_BMASK_SET(EHEA_WR_ID_TYPE, EHEA_SWQE2_TYPE) - | EHEA_BMASK_SET(EHEA_WR_ID_COUNT, pr->swqe_id_counter) - | EHEA_BMASK_SET(EHEA_WR_ID_REFILL, 1) - | EHEA_BMASK_SET(EHEA_WR_ID_INDEX, pr->sq_skba.index); - pr->sq_skba.arr[pr->sq_skba.index] = skb; - - pr->sq_skba.index++; - pr->sq_skba.index &= (pr->sq_skba.len - 1); - - lkey = pr->send_mr.lkey; - ehea_xmit2(skb, dev, swqe, lkey); - swqe->tx_control |= EHEA_SWQE_SIGNALLED_COMPLETION; - } - pr->swqe_id_counter += 1; - - netif_info(port, tx_queued, dev, - "post swqe on QP %d\n", pr->qp->init_attr.qp_nr); - if (netif_msg_tx_queued(port)) - ehea_dump(swqe, 512, "swqe"); - - if (unlikely(test_bit(__EHEA_STOP_XFER, &ehea_driver_flags))) { - netif_tx_stop_queue(txq); - swqe->tx_control |= EHEA_SWQE_PURGE; - } - - ehea_post_swqe(pr->qp, swqe); - - if (unlikely(atomic_read(&pr->swqe_avail) <= 1)) { - pr->p_stats.queue_stopped++; - netif_tx_stop_queue(txq); - } - - return NETDEV_TX_OK; -} - -static int ehea_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_ehea_port_cb1 *cb1; - int index; - u64 hret; - int err = 0; - - cb1 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb1) { - pr_err("no mem for cb1\n"); - err = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("query_ehea_port failed\n"); - err = -EINVAL; - goto out; - } - - index = (vid / 64); - cb1->vlan_filter[index] |= ((u64)(0x8000000000000000 >> (vid & 0x3F))); - - hret = ehea_h_modify_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_port failed\n"); - err = -EINVAL; - } -out: - free_page((unsigned long)cb1); - return err; -} - -static int ehea_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_ehea_port_cb1 *cb1; - int index; - u64 hret; - int err = 0; - - cb1 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb1) { - pr_err("no mem for cb1\n"); - err = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("query_ehea_port failed\n"); - err = -EINVAL; - goto out; - } - - index = (vid / 64); - cb1->vlan_filter[index] &= ~((u64)(0x8000000000000000 >> (vid & 0x3F))); - - hret = ehea_h_modify_ehea_port(adapter->handle, port->logical_port_id, - H_PORT_CB1, H_PORT_CB1_ALL, cb1); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_port failed\n"); - err = -EINVAL; - } -out: - free_page((unsigned long)cb1); - return err; -} - -static int ehea_activate_qp(struct ehea_adapter *adapter, struct ehea_qp *qp) -{ - int ret = -EIO; - u64 hret; - u16 dummy16 = 0; - u64 dummy64 = 0; - struct hcp_modify_qp_cb0 *cb0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (1)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_STATE_INITIALIZED; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (1)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (2)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_ENABLED | H_QP_CR_STATE_INITIALIZED; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (2)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (3)\n"); - goto out; - } - - cb0->qp_ctl_reg = H_QP_CR_ENABLED | H_QP_CR_STATE_RDY2SND; - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, 1), cb0, - &dummy64, &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (3)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (4)\n"); - goto out; - } - - ret = 0; -out: - free_page((unsigned long)cb0); - return ret; -} - -static int ehea_port_res_setup(struct ehea_port *port, int def_qps) -{ - int ret, i; - struct port_res_cfg pr_cfg, pr_cfg_small_rx; - enum ehea_eq_type eq_type = EHEA_EQ; - - port->qp_eq = ehea_create_eq(port->adapter, eq_type, - EHEA_MAX_ENTRIES_EQ, 1); - if (!port->qp_eq) { - ret = -EINVAL; - pr_err("ehea_create_eq failed (qp_eq)\n"); - goto out_kill_eq; - } - - pr_cfg.max_entries_rcq = rq1_entries + rq2_entries + rq3_entries; - pr_cfg.max_entries_scq = sq_entries * 2; - pr_cfg.max_entries_sq = sq_entries; - pr_cfg.max_entries_rq1 = rq1_entries; - pr_cfg.max_entries_rq2 = rq2_entries; - pr_cfg.max_entries_rq3 = rq3_entries; - - pr_cfg_small_rx.max_entries_rcq = 1; - pr_cfg_small_rx.max_entries_scq = sq_entries; - pr_cfg_small_rx.max_entries_sq = sq_entries; - pr_cfg_small_rx.max_entries_rq1 = 1; - pr_cfg_small_rx.max_entries_rq2 = 1; - pr_cfg_small_rx.max_entries_rq3 = 1; - - for (i = 0; i < def_qps; i++) { - ret = ehea_init_port_res(port, &port->port_res[i], &pr_cfg, i); - if (ret) - goto out_clean_pr; - } - for (i = def_qps; i < def_qps; i++) { - ret = ehea_init_port_res(port, &port->port_res[i], - &pr_cfg_small_rx, i); - if (ret) - goto out_clean_pr; - } - - return 0; - -out_clean_pr: - while (--i >= 0) - ehea_clean_portres(port, &port->port_res[i]); - -out_kill_eq: - ehea_destroy_eq(port->qp_eq); - return ret; -} - -static int ehea_clean_all_portres(struct ehea_port *port) -{ - int ret = 0; - int i; - - for (i = 0; i < port->num_def_qps; i++) - ret |= ehea_clean_portres(port, &port->port_res[i]); - - ret |= ehea_destroy_eq(port->qp_eq); - - return ret; -} - -static void ehea_remove_adapter_mr(struct ehea_adapter *adapter) -{ - if (adapter->active_ports) - return; - - ehea_rem_mr(&adapter->mr); -} - -static int ehea_add_adapter_mr(struct ehea_adapter *adapter) -{ - if (adapter->active_ports) - return 0; - - return ehea_reg_kernel_mr(adapter, &adapter->mr); -} - -static int ehea_up(struct net_device *dev) -{ - int ret, i; - struct ehea_port *port = netdev_priv(dev); - - if (port->state == EHEA_PORT_UP) - return 0; - - ret = ehea_port_res_setup(port, port->num_def_qps); - if (ret) { - netdev_err(dev, "port_res_failed\n"); - goto out; - } - - /* Set default QP for this port */ - ret = ehea_configure_port(port); - if (ret) { - netdev_err(dev, "ehea_configure_port failed. ret:%d\n", ret); - goto out_clean_pr; - } - - ret = ehea_reg_interrupts(dev); - if (ret) { - netdev_err(dev, "reg_interrupts failed. ret:%d\n", ret); - goto out_clean_pr; - } - - for (i = 0; i < port->num_def_qps; i++) { - ret = ehea_activate_qp(port->adapter, port->port_res[i].qp); - if (ret) { - netdev_err(dev, "activate_qp failed\n"); - goto out_free_irqs; - } - } - - for (i = 0; i < port->num_def_qps; i++) { - ret = ehea_fill_port_res(&port->port_res[i]); - if (ret) { - netdev_err(dev, "out_free_irqs\n"); - goto out_free_irqs; - } - } - - ret = ehea_broadcast_reg_helper(port, H_REG_BCMC); - if (ret) { - ret = -EIO; - goto out_free_irqs; - } - - port->state = EHEA_PORT_UP; - - ret = 0; - goto out; - -out_free_irqs: - ehea_free_interrupts(dev); - -out_clean_pr: - ehea_clean_all_portres(port); -out: - if (ret) - netdev_info(dev, "Failed starting. ret=%i\n", ret); - - ehea_update_bcmc_registrations(); - ehea_update_firmware_handles(); - - return ret; -} - -static void port_napi_disable(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) - napi_disable(&port->port_res[i].napi); -} - -static void port_napi_enable(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) - napi_enable(&port->port_res[i].napi); -} - -static int ehea_open(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - mutex_lock(&port->port_lock); - - netif_info(port, ifup, dev, "enabling port\n"); - - netif_carrier_off(dev); - - ret = ehea_up(dev); - if (!ret) { - port_napi_enable(port); - netif_tx_start_all_queues(dev); - } - - mutex_unlock(&port->port_lock); - schedule_delayed_work(&port->stats_work, - round_jiffies_relative(msecs_to_jiffies(1000))); - - return ret; -} - -static int ehea_down(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - if (port->state == EHEA_PORT_DOWN) - return 0; - - ehea_drop_multicast_list(dev); - ehea_allmulti(dev, 0); - ehea_broadcast_reg_helper(port, H_DEREG_BCMC); - - ehea_free_interrupts(dev); - - port->state = EHEA_PORT_DOWN; - - ehea_update_bcmc_registrations(); - - ret = ehea_clean_all_portres(port); - if (ret) - netdev_info(dev, "Failed freeing resources. ret=%i\n", ret); - - ehea_update_firmware_handles(); - - return ret; -} - -static int ehea_stop(struct net_device *dev) -{ - int ret; - struct ehea_port *port = netdev_priv(dev); - - netif_info(port, ifdown, dev, "disabling port\n"); - - set_bit(__EHEA_DISABLE_PORT_RESET, &port->flags); - cancel_work_sync(&port->reset_task); - cancel_delayed_work_sync(&port->stats_work); - mutex_lock(&port->port_lock); - netif_tx_stop_all_queues(dev); - port_napi_disable(port); - ret = ehea_down(dev); - mutex_unlock(&port->port_lock); - clear_bit(__EHEA_DISABLE_PORT_RESET, &port->flags); - return ret; -} - -static void ehea_purge_sq(struct ehea_qp *orig_qp) -{ - struct ehea_qp qp = *orig_qp; - struct ehea_qp_init_attr *init_attr = &qp.init_attr; - struct ehea_swqe *swqe; - int wqe_index; - int i; - - for (i = 0; i < init_attr->act_nr_send_wqes; i++) { - swqe = ehea_get_swqe(&qp, &wqe_index); - swqe->tx_control |= EHEA_SWQE_PURGE; - } -} - -static void ehea_flush_sq(struct ehea_port *port) -{ - int i; - - for (i = 0; i < port->num_def_qps; i++) { - struct ehea_port_res *pr = &port->port_res[i]; - int swqe_max = pr->sq_skba_size - 2 - pr->swqe_ll_count; - int ret; - - ret = wait_event_timeout(port->swqe_avail_wq, - atomic_read(&pr->swqe_avail) >= swqe_max, - msecs_to_jiffies(100)); - - if (!ret) { - pr_err("WARNING: sq not flushed completely\n"); - break; - } - } -} - -static int ehea_stop_qps(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - struct hcp_modify_qp_cb0 *cb0; - int ret = -EIO; - int dret; - int i; - u64 hret; - u64 dummy64 = 0; - u16 dummy16 = 0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) { - ret = -ENOMEM; - goto out; - } - - for (i = 0; i < (port->num_def_qps); i++) { - struct ehea_port_res *pr = &port->port_res[i]; - struct ehea_qp *qp = pr->qp; - - /* Purge send queue */ - ehea_purge_sq(qp); - - /* Disable queue pair */ - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (1)\n"); - goto out; - } - - cb0->qp_ctl_reg = (cb0->qp_ctl_reg & H_QP_CR_RES_STATE) << 8; - cb0->qp_ctl_reg &= ~H_QP_CR_ENABLED; - - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, - 1), cb0, &dummy64, - &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - pr_err("modify_ehea_qp failed (1)\n"); - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - pr_err("query_ehea_qp failed (2)\n"); - goto out; - } - - /* deregister shared memory regions */ - dret = ehea_rem_smrs(pr); - if (dret) { - pr_err("unreg shared memory region failed\n"); - goto out; - } - } - - ret = 0; -out: - free_page((unsigned long)cb0); - - return ret; -} - -static void ehea_update_rqs(struct ehea_qp *orig_qp, struct ehea_port_res *pr) -{ - struct ehea_qp qp = *orig_qp; - struct ehea_qp_init_attr *init_attr = &qp.init_attr; - struct ehea_rwqe *rwqe; - struct sk_buff **skba_rq2 = pr->rq2_skba.arr; - struct sk_buff **skba_rq3 = pr->rq3_skba.arr; - struct sk_buff *skb; - u32 lkey = pr->recv_mr.lkey; - - - int i; - int index; - - for (i = 0; i < init_attr->act_nr_rwqes_rq2 + 1; i++) { - rwqe = ehea_get_next_rwqe(&qp, 2); - rwqe->sg_list[0].l_key = lkey; - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, rwqe->wr_id); - skb = skba_rq2[index]; - if (skb) - rwqe->sg_list[0].vaddr = ehea_map_vaddr(skb->data); - } - - for (i = 0; i < init_attr->act_nr_rwqes_rq3 + 1; i++) { - rwqe = ehea_get_next_rwqe(&qp, 3); - rwqe->sg_list[0].l_key = lkey; - index = EHEA_BMASK_GET(EHEA_WR_ID_INDEX, rwqe->wr_id); - skb = skba_rq3[index]; - if (skb) - rwqe->sg_list[0].vaddr = ehea_map_vaddr(skb->data); - } -} - -static int ehea_restart_qps(struct net_device *dev) -{ - struct ehea_port *port = netdev_priv(dev); - struct ehea_adapter *adapter = port->adapter; - int ret = 0; - int i; - - struct hcp_modify_qp_cb0 *cb0; - u64 hret; - u64 dummy64 = 0; - u16 dummy16 = 0; - - cb0 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb0) - return -ENOMEM; - - for (i = 0; i < (port->num_def_qps); i++) { - struct ehea_port_res *pr = &port->port_res[i]; - struct ehea_qp *qp = pr->qp; - - ret = ehea_gen_smrs(pr); - if (ret) { - netdev_err(dev, "creation of shared memory regions failed\n"); - goto out; - } - - ehea_update_rqs(qp, pr); - - /* Enable queue pair */ - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_qp failed (1)\n"); - ret = -EFAULT; - goto out; - } - - cb0->qp_ctl_reg = (cb0->qp_ctl_reg & H_QP_CR_RES_STATE) << 8; - cb0->qp_ctl_reg |= H_QP_CR_ENABLED; - - hret = ehea_h_modify_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_QP_CTL_REG, - 1), cb0, &dummy64, - &dummy64, &dummy16, &dummy16); - if (hret != H_SUCCESS) { - netdev_err(dev, "modify_ehea_qp failed (1)\n"); - ret = -EFAULT; - goto out; - } - - hret = ehea_h_query_ehea_qp(adapter->handle, 0, qp->fw_handle, - EHEA_BMASK_SET(H_QPCB0_ALL, 0xFFFF), - cb0); - if (hret != H_SUCCESS) { - netdev_err(dev, "query_ehea_qp failed (2)\n"); - ret = -EFAULT; - goto out; - } - - /* refill entire queue */ - ehea_refill_rq1(pr, pr->rq1_skba.index, 0); - ehea_refill_rq2(pr, 0); - ehea_refill_rq3(pr, 0); - } -out: - free_page((unsigned long)cb0); - - return ret; -} - -static void ehea_reset_port(struct work_struct *work) -{ - int ret; - struct ehea_port *port = - container_of(work, struct ehea_port, reset_task); - struct net_device *dev = port->netdev; - - mutex_lock(&dlpar_mem_lock); - port->resets++; - mutex_lock(&port->port_lock); - netif_tx_disable(dev); - - port_napi_disable(port); - - ehea_down(dev); - - ret = ehea_up(dev); - if (ret) - goto out; - - ehea_set_multicast_list(dev); - - netif_info(port, timer, dev, "reset successful\n"); - - port_napi_enable(port); - - netif_tx_wake_all_queues(dev); -out: - mutex_unlock(&port->port_lock); - mutex_unlock(&dlpar_mem_lock); -} - -static void ehea_rereg_mrs(void) -{ - int ret, i; - struct ehea_adapter *adapter; - - pr_info("LPAR memory changed - re-initializing driver\n"); - - list_for_each_entry(adapter, &adapter_list, list) - if (adapter->active_ports) { - /* Shutdown all ports */ - for (i = 0; i < EHEA_MAX_PORTS; i++) { - struct ehea_port *port = adapter->port[i]; - struct net_device *dev; - - if (!port) - continue; - - dev = port->netdev; - - if (dev->flags & IFF_UP) { - mutex_lock(&port->port_lock); - netif_tx_disable(dev); - ehea_flush_sq(port); - ret = ehea_stop_qps(dev); - if (ret) { - mutex_unlock(&port->port_lock); - goto out; - } - port_napi_disable(port); - mutex_unlock(&port->port_lock); - } - reset_sq_restart_flag(port); - } - - /* Unregister old memory region */ - ret = ehea_rem_mr(&adapter->mr); - if (ret) { - pr_err("unregister MR failed - driver inoperable!\n"); - goto out; - } - } - - clear_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - - list_for_each_entry(adapter, &adapter_list, list) - if (adapter->active_ports) { - /* Register new memory region */ - ret = ehea_reg_kernel_mr(adapter, &adapter->mr); - if (ret) { - pr_err("register MR failed - driver inoperable!\n"); - goto out; - } - - /* Restart all ports */ - for (i = 0; i < EHEA_MAX_PORTS; i++) { - struct ehea_port *port = adapter->port[i]; - - if (port) { - struct net_device *dev = port->netdev; - - if (dev->flags & IFF_UP) { - mutex_lock(&port->port_lock); - ret = ehea_restart_qps(dev); - if (!ret) { - check_sqs(port); - port_napi_enable(port); - netif_tx_wake_all_queues(dev); - } else { - netdev_err(dev, "Unable to restart QPS\n"); - } - mutex_unlock(&port->port_lock); - } - } - } - } - pr_info("re-initializing driver complete\n"); -out: - return; -} - -static void ehea_tx_watchdog(struct net_device *dev, unsigned int txqueue) -{ - struct ehea_port *port = netdev_priv(dev); - - if (netif_carrier_ok(dev) && - !test_bit(__EHEA_STOP_XFER, &ehea_driver_flags)) - ehea_schedule_port_reset(port); -} - -static int ehea_sense_adapter_attr(struct ehea_adapter *adapter) -{ - struct hcp_query_ehea *cb; - u64 hret; - int ret; - - cb = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb) { - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_query_ehea(adapter->handle, cb); - - if (hret != H_SUCCESS) { - ret = -EIO; - goto out_herr; - } - - adapter->max_mc_mac = cb->max_mc_mac - 1; - ret = 0; - -out_herr: - free_page((unsigned long)cb); -out: - return ret; -} - -static int ehea_get_jumboframe_status(struct ehea_port *port, int *jumbo) -{ - struct hcp_ehea_port_cb4 *cb4; - u64 hret; - int ret = 0; - - *jumbo = 0; - - /* (Try to) enable *jumbo frames */ - cb4 = (void *)get_zeroed_page(GFP_KERNEL); - if (!cb4) { - pr_err("no mem for cb4\n"); - ret = -ENOMEM; - goto out; - } else { - hret = ehea_h_query_ehea_port(port->adapter->handle, - port->logical_port_id, - H_PORT_CB4, - H_PORT_CB4_JUMBO, cb4); - if (hret == H_SUCCESS) { - if (cb4->jumbo_frame) - *jumbo = 1; - else { - cb4->jumbo_frame = 1; - hret = ehea_h_modify_ehea_port(port->adapter-> - handle, - port-> - logical_port_id, - H_PORT_CB4, - H_PORT_CB4_JUMBO, - cb4); - if (hret == H_SUCCESS) - *jumbo = 1; - } - } else - ret = -EINVAL; - - free_page((unsigned long)cb4); - } -out: - return ret; -} - -static ssize_t log_port_id_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct ehea_port *port = container_of(dev, struct ehea_port, ofdev.dev); - return sprintf(buf, "%d", port->logical_port_id); -} - -static DEVICE_ATTR_RO(log_port_id); - -static void logical_port_release(struct device *dev) -{ - struct ehea_port *port = container_of(dev, struct ehea_port, ofdev.dev); - of_node_put(port->ofdev.dev.of_node); -} - -static struct device *ehea_register_port(struct ehea_port *port, - struct device_node *dn) -{ - int ret; - - port->ofdev.dev.of_node = of_node_get(dn); - port->ofdev.dev.parent = &port->adapter->ofdev->dev; - port->ofdev.dev.bus = &ibmebus_bus_type; - - dev_set_name(&port->ofdev.dev, "port%d", port_name_cnt++); - port->ofdev.dev.release = logical_port_release; - - ret = of_device_register(&port->ofdev); - if (ret) { - pr_err("failed to register device. ret=%d\n", ret); - put_device(&port->ofdev.dev); - goto out; - } - - ret = device_create_file(&port->ofdev.dev, &dev_attr_log_port_id); - if (ret) { - pr_err("failed to register attributes, ret=%d\n", ret); - goto out_unreg_of_dev; - } - - return &port->ofdev.dev; - -out_unreg_of_dev: - of_device_unregister(&port->ofdev); -out: - return NULL; -} - -static void ehea_unregister_port(struct ehea_port *port) -{ - device_remove_file(&port->ofdev.dev, &dev_attr_log_port_id); - of_device_unregister(&port->ofdev); -} - -static const struct net_device_ops ehea_netdev_ops = { - .ndo_open = ehea_open, - .ndo_stop = ehea_stop, - .ndo_start_xmit = ehea_start_xmit, - .ndo_get_stats64 = ehea_get_stats64, - .ndo_set_mac_address = ehea_set_mac_addr, - .ndo_validate_addr = eth_validate_addr, - .ndo_set_rx_mode = ehea_set_multicast_list, - .ndo_vlan_rx_add_vid = ehea_vlan_rx_add_vid, - .ndo_vlan_rx_kill_vid = ehea_vlan_rx_kill_vid, - .ndo_tx_timeout = ehea_tx_watchdog, -}; - -static struct ehea_port *ehea_setup_single_port(struct ehea_adapter *adapter, - u32 logical_port_id, - struct device_node *dn) -{ - int ret; - struct net_device *dev; - struct ehea_port *port; - struct device *port_dev; - int jumbo; - - /* allocate memory for the port structures */ - dev = alloc_etherdev_mq(sizeof(struct ehea_port), EHEA_MAX_PORT_RES); - - if (!dev) { - ret = -ENOMEM; - goto out_err; - } - - port = netdev_priv(dev); - - mutex_init(&port->port_lock); - port->state = EHEA_PORT_DOWN; - port->sig_comp_iv = sq_entries / 10; - - port->adapter = adapter; - port->netdev = dev; - port->logical_port_id = logical_port_id; - - port->msg_enable = netif_msg_init(msg_level, EHEA_MSG_DEFAULT); - - port->mc_list = kzalloc_obj(struct ehea_mc_list); - if (!port->mc_list) { - ret = -ENOMEM; - goto out_free_ethdev; - } - - INIT_LIST_HEAD(&port->mc_list->list); - - ret = ehea_sense_port_attr(port); - if (ret) - goto out_free_mc_list; - - netif_set_real_num_rx_queues(dev, port->num_def_qps); - netif_set_real_num_tx_queues(dev, port->num_def_qps); - - port_dev = ehea_register_port(port, dn); - if (!port_dev) - goto out_free_mc_list; - - SET_NETDEV_DEV(dev, port_dev); - - /* initialize net_device structure */ - eth_hw_addr_set(dev, (u8 *)&port->mac_addr); - - dev->netdev_ops = &ehea_netdev_ops; - ehea_set_ethtool_ops(dev); - - dev->hw_features = NETIF_F_SG | NETIF_F_TSO | - NETIF_F_IP_CSUM | NETIF_F_HW_VLAN_CTAG_TX; - dev->features = NETIF_F_SG | NETIF_F_TSO | - NETIF_F_HIGHDMA | NETIF_F_IP_CSUM | - NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX | - NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_RXCSUM; - dev->vlan_features = NETIF_F_SG | NETIF_F_TSO | NETIF_F_HIGHDMA | - NETIF_F_IP_CSUM; - dev->watchdog_timeo = EHEA_WATCH_DOG_TIMEOUT; - - /* MTU range: 68 - 9022 */ - dev->min_mtu = ETH_MIN_MTU; - dev->max_mtu = EHEA_MAX_PACKET_SIZE; - - INIT_WORK(&port->reset_task, ehea_reset_port); - INIT_DELAYED_WORK(&port->stats_work, ehea_update_stats); - - init_waitqueue_head(&port->swqe_avail_wq); - init_waitqueue_head(&port->restart_wq); - - ret = register_netdev(dev); - if (ret) { - pr_err("register_netdev failed. ret=%d\n", ret); - goto out_unreg_port; - } - - ret = ehea_get_jumboframe_status(port, &jumbo); - if (ret) - netdev_err(dev, "failed determining jumbo frame status\n"); - - netdev_info(dev, "Jumbo frames are %sabled\n", - jumbo == 1 ? "en" : "dis"); - - adapter->active_ports++; - - return port; - -out_unreg_port: - ehea_unregister_port(port); - -out_free_mc_list: - kfree(port->mc_list); - -out_free_ethdev: - free_netdev(dev); - -out_err: - pr_err("setting up logical port with id=%d failed, ret=%d\n", - logical_port_id, ret); - return NULL; -} - -static void ehea_shutdown_single_port(struct ehea_port *port) -{ - struct ehea_adapter *adapter = port->adapter; - - cancel_work_sync(&port->reset_task); - cancel_delayed_work_sync(&port->stats_work); - unregister_netdev(port->netdev); - ehea_unregister_port(port); - kfree(port->mc_list); - free_netdev(port->netdev); - adapter->active_ports--; -} - -static int ehea_setup_ports(struct ehea_adapter *adapter) -{ - struct device_node *lhea_dn; - struct device_node *eth_dn; - - const u32 *dn_log_port_id; - int i = 0; - - lhea_dn = adapter->ofdev->dev.of_node; - for_each_child_of_node(lhea_dn, eth_dn) { - dn_log_port_id = of_get_property(eth_dn, "ibm,hea-port-no", - NULL); - if (!dn_log_port_id) { - pr_err("bad device node: eth_dn name=%pOF\n", eth_dn); - continue; - } - - if (ehea_add_adapter_mr(adapter)) { - pr_err("creating MR failed\n"); - of_node_put(eth_dn); - return -EIO; - } - - adapter->port[i] = ehea_setup_single_port(adapter, - *dn_log_port_id, - eth_dn); - if (adapter->port[i]) - netdev_info(adapter->port[i]->netdev, - "logical port id #%d\n", *dn_log_port_id); - else - ehea_remove_adapter_mr(adapter); - - i++; - } - return 0; -} - -static struct device_node *ehea_get_eth_dn(struct ehea_adapter *adapter, - u32 logical_port_id) -{ - struct device_node *lhea_dn; - struct device_node *eth_dn; - const u32 *dn_log_port_id; - - lhea_dn = adapter->ofdev->dev.of_node; - for_each_child_of_node(lhea_dn, eth_dn) { - dn_log_port_id = of_get_property(eth_dn, "ibm,hea-port-no", - NULL); - if (dn_log_port_id) - if (*dn_log_port_id == logical_port_id) - return eth_dn; - } - - return NULL; -} - -static ssize_t probe_port_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ehea_adapter *adapter = dev_get_drvdata(dev); - struct ehea_port *port; - struct device_node *eth_dn = NULL; - int i; - - u32 logical_port_id; - - sscanf(buf, "%d", &logical_port_id); - - port = ehea_get_port(adapter, logical_port_id); - - if (port) { - netdev_info(port->netdev, "adding port with logical port id=%d failed: port already configured\n", - logical_port_id); - return -EINVAL; - } - - eth_dn = ehea_get_eth_dn(adapter, logical_port_id); - - if (!eth_dn) { - pr_info("no logical port with id %d found\n", logical_port_id); - return -EINVAL; - } - - if (ehea_add_adapter_mr(adapter)) { - pr_err("creating MR failed\n"); - of_node_put(eth_dn); - return -EIO; - } - - port = ehea_setup_single_port(adapter, logical_port_id, eth_dn); - - of_node_put(eth_dn); - - if (port) { - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (!adapter->port[i]) { - adapter->port[i] = port; - break; - } - - netdev_info(port->netdev, "added: (logical port id=%d)\n", - logical_port_id); - } else { - ehea_remove_adapter_mr(adapter); - return -EIO; - } - - return (ssize_t) count; -} - -static ssize_t remove_port_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ehea_adapter *adapter = dev_get_drvdata(dev); - struct ehea_port *port; - int i; - u32 logical_port_id; - - sscanf(buf, "%d", &logical_port_id); - - port = ehea_get_port(adapter, logical_port_id); - - if (port) { - netdev_info(port->netdev, "removed: (logical port id=%d)\n", - logical_port_id); - - ehea_shutdown_single_port(port); - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i] == port) { - adapter->port[i] = NULL; - break; - } - } else { - pr_err("removing port with logical port id=%d failed. port not configured.\n", - logical_port_id); - return -EINVAL; - } - - ehea_remove_adapter_mr(adapter); - - return (ssize_t) count; -} - -static DEVICE_ATTR_WO(probe_port); -static DEVICE_ATTR_WO(remove_port); - -static int ehea_create_device_sysfs(struct platform_device *dev) -{ - int ret = device_create_file(&dev->dev, &dev_attr_probe_port); - if (ret) - goto out; - - ret = device_create_file(&dev->dev, &dev_attr_remove_port); - if (ret) - device_remove_file(&dev->dev, &dev_attr_probe_port); -out: - return ret; -} - -static void ehea_remove_device_sysfs(struct platform_device *dev) -{ - device_remove_file(&dev->dev, &dev_attr_probe_port); - device_remove_file(&dev->dev, &dev_attr_remove_port); -} - -static int ehea_reboot_notifier(struct notifier_block *nb, - unsigned long action, void *unused) -{ - if (action == SYS_RESTART) { - pr_info("Reboot: freeing all eHEA resources\n"); - ibmebus_unregister_driver(&ehea_driver); - } - return NOTIFY_DONE; -} - -static struct notifier_block ehea_reboot_nb = { - .notifier_call = ehea_reboot_notifier, -}; - -static int ehea_mem_notifier(struct notifier_block *nb, - unsigned long action, void *data) -{ - int ret = NOTIFY_BAD; - struct memory_notify *arg = data; - - mutex_lock(&dlpar_mem_lock); - - switch (action) { - case MEM_CANCEL_OFFLINE: - pr_info("memory offlining canceled"); - fallthrough; /* re-add canceled memory block */ - - case MEM_ONLINE: - pr_info("memory is going online"); - set_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - if (ehea_add_sect_bmap(arg->start_pfn, arg->nr_pages)) - goto out_unlock; - ehea_rereg_mrs(); - break; - - case MEM_GOING_OFFLINE: - pr_info("memory is going offline"); - set_bit(__EHEA_STOP_XFER, &ehea_driver_flags); - if (ehea_rem_sect_bmap(arg->start_pfn, arg->nr_pages)) - goto out_unlock; - ehea_rereg_mrs(); - break; - - default: - break; - } - - ehea_update_firmware_handles(); - ret = NOTIFY_OK; - -out_unlock: - mutex_unlock(&dlpar_mem_lock); - return ret; -} - -static struct notifier_block ehea_mem_nb = { - .notifier_call = ehea_mem_notifier, -}; - -static void ehea_crash_handler(void) -{ - int i; - - if (ehea_fw_handles.arr) - for (i = 0; i < ehea_fw_handles.num_entries; i++) - ehea_h_free_resource(ehea_fw_handles.arr[i].adh, - ehea_fw_handles.arr[i].fwh, - FORCE_FREE); - - if (ehea_bcmc_regs.arr) - for (i = 0; i < ehea_bcmc_regs.num_entries; i++) - ehea_h_reg_dereg_bcmc(ehea_bcmc_regs.arr[i].adh, - ehea_bcmc_regs.arr[i].port_id, - ehea_bcmc_regs.arr[i].reg_type, - ehea_bcmc_regs.arr[i].macaddr, - 0, H_DEREG_BCMC); -} - -static atomic_t ehea_memory_hooks_registered; - -/* Register memory hooks on probe of first adapter */ -static int ehea_register_memory_hooks(void) -{ - int ret = 0; - - if (atomic_inc_return(&ehea_memory_hooks_registered) > 1) - return 0; - - ret = ehea_create_busmap(); - if (ret) { - pr_info("ehea_create_busmap failed\n"); - goto out; - } - - ret = register_reboot_notifier(&ehea_reboot_nb); - if (ret) { - pr_info("register_reboot_notifier failed\n"); - goto out; - } - - ret = register_memory_notifier(&ehea_mem_nb); - if (ret) { - pr_info("register_memory_notifier failed\n"); - goto out2; - } - - ret = crash_shutdown_register(ehea_crash_handler); - if (ret) { - pr_info("crash_shutdown_register failed\n"); - goto out3; - } - - return 0; - -out3: - unregister_memory_notifier(&ehea_mem_nb); -out2: - unregister_reboot_notifier(&ehea_reboot_nb); -out: - atomic_dec(&ehea_memory_hooks_registered); - return ret; -} - -static void ehea_unregister_memory_hooks(void) -{ - /* Only remove the hooks if we've registered them */ - if (atomic_read(&ehea_memory_hooks_registered) == 0) - return; - - unregister_reboot_notifier(&ehea_reboot_nb); - if (crash_shutdown_unregister(ehea_crash_handler)) - pr_info("failed unregistering crash handler\n"); - unregister_memory_notifier(&ehea_mem_nb); -} - -static int ehea_probe_adapter(struct platform_device *dev) -{ - struct ehea_adapter *adapter; - const u64 *adapter_handle; - int ret; - int i; - - ret = ehea_register_memory_hooks(); - if (ret) - return ret; - - if (!dev || !dev->dev.of_node) { - pr_err("Invalid ibmebus device probed\n"); - return -EINVAL; - } - - adapter = devm_kzalloc(&dev->dev, sizeof(*adapter), GFP_KERNEL); - if (!adapter) { - ret = -ENOMEM; - dev_err(&dev->dev, "no mem for ehea_adapter\n"); - goto out; - } - - list_add(&adapter->list, &adapter_list); - - adapter->ofdev = dev; - - adapter_handle = of_get_property(dev->dev.of_node, "ibm,hea-handle", - NULL); - if (adapter_handle) - adapter->handle = *adapter_handle; - - if (!adapter->handle) { - dev_err(&dev->dev, "failed getting handle for adapter" - " '%pOF'\n", dev->dev.of_node); - ret = -ENODEV; - goto out_free_ad; - } - - adapter->pd = EHEA_PD_ID; - - platform_set_drvdata(dev, adapter); - - - /* initialize adapter and ports */ - /* get adapter properties */ - ret = ehea_sense_adapter_attr(adapter); - if (ret) { - dev_err(&dev->dev, "sense_adapter_attr failed: %d\n", ret); - goto out_free_ad; - } - - adapter->neq = ehea_create_eq(adapter, - EHEA_NEQ, EHEA_MAX_ENTRIES_EQ, 1); - if (!adapter->neq) { - ret = -EIO; - dev_err(&dev->dev, "NEQ creation failed\n"); - goto out_free_ad; - } - - tasklet_setup(&adapter->neq_tasklet, ehea_neq_tasklet); - - ret = ehea_create_device_sysfs(dev); - if (ret) - goto out_kill_eq; - - ret = ehea_setup_ports(adapter); - if (ret) { - dev_err(&dev->dev, "setup_ports failed\n"); - goto out_rem_dev_sysfs; - } - - ret = ibmebus_request_irq(adapter->neq->attr.ist1, - ehea_interrupt_neq, 0, - "ehea_neq", adapter); - if (ret) { - dev_err(&dev->dev, "requesting NEQ IRQ failed\n"); - goto out_shutdown_ports; - } - - /* Handle any events that might be pending. */ - tasklet_hi_schedule(&adapter->neq_tasklet); - - ret = 0; - goto out; - -out_shutdown_ports: - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) { - ehea_shutdown_single_port(adapter->port[i]); - adapter->port[i] = NULL; - } - -out_rem_dev_sysfs: - ehea_remove_device_sysfs(dev); - -out_kill_eq: - ehea_destroy_eq(adapter->neq); - -out_free_ad: - list_del(&adapter->list); - -out: - ehea_update_firmware_handles(); - - return ret; -} - -static void ehea_remove(struct platform_device *dev) -{ - struct ehea_adapter *adapter = platform_get_drvdata(dev); - int i; - - for (i = 0; i < EHEA_MAX_PORTS; i++) - if (adapter->port[i]) { - ehea_shutdown_single_port(adapter->port[i]); - adapter->port[i] = NULL; - } - - ehea_remove_device_sysfs(dev); - - ibmebus_free_irq(adapter->neq->attr.ist1, adapter); - tasklet_kill(&adapter->neq_tasklet); - - ehea_destroy_eq(adapter->neq); - ehea_remove_adapter_mr(adapter); - list_del(&adapter->list); - - ehea_update_firmware_handles(); -} - -static int check_module_parm(void) -{ - int ret = 0; - - if ((rq1_entries < EHEA_MIN_ENTRIES_QP) || - (rq1_entries > EHEA_MAX_ENTRIES_RQ1)) { - pr_info("Bad parameter: rq1_entries\n"); - ret = -EINVAL; - } - if ((rq2_entries < EHEA_MIN_ENTRIES_QP) || - (rq2_entries > EHEA_MAX_ENTRIES_RQ2)) { - pr_info("Bad parameter: rq2_entries\n"); - ret = -EINVAL; - } - if ((rq3_entries < EHEA_MIN_ENTRIES_QP) || - (rq3_entries > EHEA_MAX_ENTRIES_RQ3)) { - pr_info("Bad parameter: rq3_entries\n"); - ret = -EINVAL; - } - if ((sq_entries < EHEA_MIN_ENTRIES_QP) || - (sq_entries > EHEA_MAX_ENTRIES_SQ)) { - pr_info("Bad parameter: sq_entries\n"); - ret = -EINVAL; - } - - return ret; -} - -static ssize_t capabilities_show(struct device_driver *drv, char *buf) -{ - return sprintf(buf, "%d", EHEA_CAPABILITIES); -} - -static DRIVER_ATTR_RO(capabilities); - -static int __init ehea_module_init(void) -{ - int ret; - - pr_info("IBM eHEA ethernet device driver (Release %s)\n", DRV_VERSION); - - memset(&ehea_fw_handles, 0, sizeof(ehea_fw_handles)); - memset(&ehea_bcmc_regs, 0, sizeof(ehea_bcmc_regs)); - - mutex_init(&ehea_fw_handles.lock); - spin_lock_init(&ehea_bcmc_regs.lock); - - ret = check_module_parm(); - if (ret) - goto out; - - ret = ibmebus_register_driver(&ehea_driver); - if (ret) { - pr_err("failed registering eHEA device driver on ebus\n"); - goto out; - } - - ret = driver_create_file(&ehea_driver.driver, - &driver_attr_capabilities); - if (ret) { - pr_err("failed to register capabilities attribute, ret=%d\n", - ret); - goto out2; - } - - return ret; - -out2: - ibmebus_unregister_driver(&ehea_driver); -out: - return ret; -} - -static void __exit ehea_module_exit(void) -{ - driver_remove_file(&ehea_driver.driver, &driver_attr_capabilities); - ibmebus_unregister_driver(&ehea_driver); - ehea_unregister_memory_hooks(); - kfree(ehea_fw_handles.arr); - kfree(ehea_bcmc_regs.arr); - ehea_destroy_busmap(); -} - -module_init(ehea_module_init); -module_exit(ehea_module_exit); diff --git a/drivers/net/ethernet/ibm/ehea/ehea_phyp.c b/drivers/net/ethernet/ibm/ehea/ehea_phyp.c deleted file mode 100644 index e63716e139f5..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_phyp.c +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_phyp.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "ehea_phyp.h" - - -static inline u16 get_order_of_qentries(u16 queue_entries) -{ - u8 ld = 1; /* logarithmus dualis */ - while (((1U << ld) - 1) < queue_entries) - ld++; - return ld - 1; -} - -/* Defines for H_CALL H_ALLOC_RESOURCE */ -#define H_ALL_RES_TYPE_QP 1 -#define H_ALL_RES_TYPE_CQ 2 -#define H_ALL_RES_TYPE_EQ 3 -#define H_ALL_RES_TYPE_MR 5 -#define H_ALL_RES_TYPE_MW 6 - -static long ehea_plpar_hcall_norets(unsigned long opcode, - unsigned long arg1, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5, - unsigned long arg6, - unsigned long arg7) -{ - long ret; - int i, sleep_msecs; - - for (i = 0; i < 5; i++) { - ret = plpar_hcall_norets(opcode, arg1, arg2, arg3, arg4, - arg5, arg6, arg7); - - if (H_IS_LONG_BUSY(ret)) { - sleep_msecs = get_longbusy_msecs(ret); - msleep_interruptible(sleep_msecs); - continue; - } - - if (ret < H_SUCCESS) - pr_err("opcode=%lx ret=%lx" - " arg1=%lx arg2=%lx arg3=%lx arg4=%lx" - " arg5=%lx arg6=%lx arg7=%lx\n", - opcode, ret, - arg1, arg2, arg3, arg4, arg5, arg6, arg7); - - return ret; - } - - return H_BUSY; -} - -static long ehea_plpar_hcall9(unsigned long opcode, - unsigned long *outs, /* array of 9 outputs */ - unsigned long arg1, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5, - unsigned long arg6, - unsigned long arg7, - unsigned long arg8, - unsigned long arg9) -{ - long ret; - int i, sleep_msecs; - u8 cb_cat; - - for (i = 0; i < 5; i++) { - ret = plpar_hcall9(opcode, outs, - arg1, arg2, arg3, arg4, arg5, - arg6, arg7, arg8, arg9); - - if (H_IS_LONG_BUSY(ret)) { - sleep_msecs = get_longbusy_msecs(ret); - msleep_interruptible(sleep_msecs); - continue; - } - - cb_cat = EHEA_BMASK_GET(H_MEHEAPORT_CAT, arg2); - - if ((ret < H_SUCCESS) && !(((ret == H_AUTHORITY) - && (opcode == H_MODIFY_HEA_PORT)) - && (((cb_cat == H_PORT_CB4) && ((arg3 == H_PORT_CB4_JUMBO) - || (arg3 == H_PORT_CB4_SPEED))) || ((cb_cat == H_PORT_CB7) - && (arg3 == H_PORT_CB7_DUCQPN))))) - pr_err("opcode=%lx ret=%lx" - " arg1=%lx arg2=%lx arg3=%lx arg4=%lx" - " arg5=%lx arg6=%lx arg7=%lx arg8=%lx" - " arg9=%lx" - " out1=%lx out2=%lx out3=%lx out4=%lx" - " out5=%lx out6=%lx out7=%lx out8=%lx" - " out9=%lx\n", - opcode, ret, - arg1, arg2, arg3, arg4, arg5, - arg6, arg7, arg8, arg9, - outs[0], outs[1], outs[2], outs[3], outs[4], - outs[5], outs[6], outs[7], outs[8]); - return ret; - } - - return H_BUSY; -} - -u64 ehea_h_query_ehea_qp(const u64 adapter_handle, const u8 qp_category, - const u64 qp_handle, const u64 sel_mask, void *cb_addr) -{ - return ehea_plpar_hcall_norets(H_QUERY_HEA_QP, - adapter_handle, /* R4 */ - qp_category, /* R5 */ - qp_handle, /* R6 */ - sel_mask, /* R7 */ - __pa(cb_addr), /* R8 */ - 0, 0); -} - -/* input param R5 */ -#define H_ALL_RES_QP_EQPO EHEA_BMASK_IBM(9, 11) -#define H_ALL_RES_QP_QPP EHEA_BMASK_IBM(12, 12) -#define H_ALL_RES_QP_RQR EHEA_BMASK_IBM(13, 15) -#define H_ALL_RES_QP_EQEG EHEA_BMASK_IBM(16, 16) -#define H_ALL_RES_QP_LL_QP EHEA_BMASK_IBM(17, 17) -#define H_ALL_RES_QP_DMA128 EHEA_BMASK_IBM(19, 19) -#define H_ALL_RES_QP_HSM EHEA_BMASK_IBM(20, 21) -#define H_ALL_RES_QP_SIGT EHEA_BMASK_IBM(22, 23) -#define H_ALL_RES_QP_TENURE EHEA_BMASK_IBM(48, 55) -#define H_ALL_RES_QP_RES_TYP EHEA_BMASK_IBM(56, 63) - -/* input param R9 */ -#define H_ALL_RES_QP_TOKEN EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_PD EHEA_BMASK_IBM(32, 63) - -/* input param R10 */ -#define H_ALL_RES_QP_MAX_SWQE EHEA_BMASK_IBM(4, 7) -#define H_ALL_RES_QP_MAX_R1WQE EHEA_BMASK_IBM(12, 15) -#define H_ALL_RES_QP_MAX_R2WQE EHEA_BMASK_IBM(20, 23) -#define H_ALL_RES_QP_MAX_R3WQE EHEA_BMASK_IBM(28, 31) -/* Max Send Scatter Gather Elements */ -#define H_ALL_RES_QP_MAX_SSGE EHEA_BMASK_IBM(37, 39) -#define H_ALL_RES_QP_MAX_R1SGE EHEA_BMASK_IBM(45, 47) -/* Max Receive SG Elements RQ1 */ -#define H_ALL_RES_QP_MAX_R2SGE EHEA_BMASK_IBM(53, 55) -#define H_ALL_RES_QP_MAX_R3SGE EHEA_BMASK_IBM(61, 63) - -/* input param R11 */ -#define H_ALL_RES_QP_SWQE_IDL EHEA_BMASK_IBM(0, 7) -/* max swqe immediate data length */ -#define H_ALL_RES_QP_PORT_NUM EHEA_BMASK_IBM(48, 63) - -/* input param R12 */ -#define H_ALL_RES_QP_TH_RQ2 EHEA_BMASK_IBM(0, 15) -/* Threshold RQ2 */ -#define H_ALL_RES_QP_TH_RQ3 EHEA_BMASK_IBM(16, 31) -/* Threshold RQ3 */ - -/* output param R6 */ -#define H_ALL_RES_QP_ACT_SWQE EHEA_BMASK_IBM(0, 15) -#define H_ALL_RES_QP_ACT_R1WQE EHEA_BMASK_IBM(16, 31) -#define H_ALL_RES_QP_ACT_R2WQE EHEA_BMASK_IBM(32, 47) -#define H_ALL_RES_QP_ACT_R3WQE EHEA_BMASK_IBM(48, 63) - -/* output param, R7 */ -#define H_ALL_RES_QP_ACT_SSGE EHEA_BMASK_IBM(0, 7) -#define H_ALL_RES_QP_ACT_R1SGE EHEA_BMASK_IBM(8, 15) -#define H_ALL_RES_QP_ACT_R2SGE EHEA_BMASK_IBM(16, 23) -#define H_ALL_RES_QP_ACT_R3SGE EHEA_BMASK_IBM(24, 31) -#define H_ALL_RES_QP_ACT_SWQE_IDL EHEA_BMASK_IBM(32, 39) - -/* output param R8,R9 */ -#define H_ALL_RES_QP_SIZE_SQ EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_SIZE_RQ1 EHEA_BMASK_IBM(32, 63) -#define H_ALL_RES_QP_SIZE_RQ2 EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_SIZE_RQ3 EHEA_BMASK_IBM(32, 63) - -/* output param R11,R12 */ -#define H_ALL_RES_QP_LIOBN_SQ EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_LIOBN_RQ1 EHEA_BMASK_IBM(32, 63) -#define H_ALL_RES_QP_LIOBN_RQ2 EHEA_BMASK_IBM(0, 31) -#define H_ALL_RES_QP_LIOBN_RQ3 EHEA_BMASK_IBM(32, 63) - -u64 ehea_h_alloc_resource_qp(const u64 adapter_handle, - struct ehea_qp_init_attr *init_attr, const u32 pd, - u64 *qp_handle, struct h_epas *h_epas) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - u64 allocate_controls = - EHEA_BMASK_SET(H_ALL_RES_QP_EQPO, init_attr->low_lat_rq1 ? 1 : 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_QPP, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_RQR, 6) /* rq1 & rq2 & rq3 */ - | EHEA_BMASK_SET(H_ALL_RES_QP_EQEG, 0) /* EQE gen. disabled */ - | EHEA_BMASK_SET(H_ALL_RES_QP_LL_QP, init_attr->low_lat_rq1) - | EHEA_BMASK_SET(H_ALL_RES_QP_DMA128, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_HSM, 0) - | EHEA_BMASK_SET(H_ALL_RES_QP_SIGT, init_attr->signalingtype) - | EHEA_BMASK_SET(H_ALL_RES_QP_RES_TYP, H_ALL_RES_TYPE_QP); - - u64 r9_reg = EHEA_BMASK_SET(H_ALL_RES_QP_PD, pd) - | EHEA_BMASK_SET(H_ALL_RES_QP_TOKEN, init_attr->qp_token); - - u64 max_r10_reg = - EHEA_BMASK_SET(H_ALL_RES_QP_MAX_SWQE, - get_order_of_qentries(init_attr->max_nr_send_wqes)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R1WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq1)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R2WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq2)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R3WQE, - get_order_of_qentries(init_attr->max_nr_rwqes_rq3)) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_SSGE, init_attr->wqe_size_enc_sq) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R1SGE, - init_attr->wqe_size_enc_rq1) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R2SGE, - init_attr->wqe_size_enc_rq2) - | EHEA_BMASK_SET(H_ALL_RES_QP_MAX_R3SGE, - init_attr->wqe_size_enc_rq3); - - u64 r11_in = - EHEA_BMASK_SET(H_ALL_RES_QP_SWQE_IDL, init_attr->swqe_imm_data_len) - | EHEA_BMASK_SET(H_ALL_RES_QP_PORT_NUM, init_attr->port_nr); - u64 threshold = - EHEA_BMASK_SET(H_ALL_RES_QP_TH_RQ2, init_attr->rq2_threshold) - | EHEA_BMASK_SET(H_ALL_RES_QP_TH_RQ3, init_attr->rq3_threshold); - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - allocate_controls, /* R5 */ - init_attr->send_cq_handle, /* R6 */ - init_attr->recv_cq_handle, /* R7 */ - init_attr->aff_eq_handle, /* R8 */ - r9_reg, /* R9 */ - max_r10_reg, /* R10 */ - r11_in, /* R11 */ - threshold); /* R12 */ - - *qp_handle = outs[0]; - init_attr->qp_nr = (u32)outs[1]; - - init_attr->act_nr_send_wqes = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_SWQE, outs[2]); - init_attr->act_nr_rwqes_rq1 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R1WQE, outs[2]); - init_attr->act_nr_rwqes_rq2 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R2WQE, outs[2]); - init_attr->act_nr_rwqes_rq3 = - (u16)EHEA_BMASK_GET(H_ALL_RES_QP_ACT_R3WQE, outs[2]); - - init_attr->act_wqe_size_enc_sq = init_attr->wqe_size_enc_sq; - init_attr->act_wqe_size_enc_rq1 = init_attr->wqe_size_enc_rq1; - init_attr->act_wqe_size_enc_rq2 = init_attr->wqe_size_enc_rq2; - init_attr->act_wqe_size_enc_rq3 = init_attr->wqe_size_enc_rq3; - - init_attr->nr_sq_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_SQ, outs[4]); - init_attr->nr_rq1_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ1, outs[4]); - init_attr->nr_rq2_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ2, outs[5]); - init_attr->nr_rq3_pages = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_SIZE_RQ3, outs[5]); - - init_attr->liobn_sq = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_SQ, outs[7]); - init_attr->liobn_rq1 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ1, outs[7]); - init_attr->liobn_rq2 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ2, outs[8]); - init_attr->liobn_rq3 = - (u32)EHEA_BMASK_GET(H_ALL_RES_QP_LIOBN_RQ3, outs[8]); - - if (!hret) - hcp_epas_ctor(h_epas, outs[6], outs[6]); - - return hret; -} - -u64 ehea_h_alloc_resource_cq(const u64 adapter_handle, - struct ehea_cq_attr *cq_attr, - u64 *cq_handle, struct h_epas *epas) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - H_ALL_RES_TYPE_CQ, /* R5 */ - cq_attr->eq_handle, /* R6 */ - cq_attr->cq_token, /* R7 */ - cq_attr->max_nr_of_cqes, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - *cq_handle = outs[0]; - cq_attr->act_nr_of_cqes = outs[3]; - cq_attr->nr_pages = outs[4]; - - if (!hret) - hcp_epas_ctor(epas, outs[5], outs[6]); - - return hret; -} - -/* Defines for H_CALL H_ALLOC_RESOURCE */ -#define H_ALL_RES_TYPE_QP 1 -#define H_ALL_RES_TYPE_CQ 2 -#define H_ALL_RES_TYPE_EQ 3 -#define H_ALL_RES_TYPE_MR 5 -#define H_ALL_RES_TYPE_MW 6 - -/* input param R5 */ -#define H_ALL_RES_EQ_NEQ EHEA_BMASK_IBM(0, 0) -#define H_ALL_RES_EQ_NON_NEQ_ISN EHEA_BMASK_IBM(6, 7) -#define H_ALL_RES_EQ_INH_EQE_GEN EHEA_BMASK_IBM(16, 16) -#define H_ALL_RES_EQ_RES_TYPE EHEA_BMASK_IBM(56, 63) -/* input param R6 */ -#define H_ALL_RES_EQ_MAX_EQE EHEA_BMASK_IBM(32, 63) - -/* output param R6 */ -#define H_ALL_RES_EQ_LIOBN EHEA_BMASK_IBM(32, 63) - -/* output param R7 */ -#define H_ALL_RES_EQ_ACT_EQE EHEA_BMASK_IBM(32, 63) - -/* output param R8 */ -#define H_ALL_RES_EQ_ACT_PS EHEA_BMASK_IBM(32, 63) - -/* output param R9 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_C EHEA_BMASK_IBM(30, 31) -#define H_ALL_RES_EQ_ACT_EQ_IST_1 EHEA_BMASK_IBM(40, 63) - -/* output param R10 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_2 EHEA_BMASK_IBM(40, 63) - -/* output param R11 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_3 EHEA_BMASK_IBM(40, 63) - -/* output param R12 */ -#define H_ALL_RES_EQ_ACT_EQ_IST_4 EHEA_BMASK_IBM(40, 63) - -u64 ehea_h_alloc_resource_eq(const u64 adapter_handle, - struct ehea_eq_attr *eq_attr, u64 *eq_handle) -{ - u64 hret, allocate_controls; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - /* resource type */ - allocate_controls = - EHEA_BMASK_SET(H_ALL_RES_EQ_RES_TYPE, H_ALL_RES_TYPE_EQ) - | EHEA_BMASK_SET(H_ALL_RES_EQ_NEQ, eq_attr->type ? 1 : 0) - | EHEA_BMASK_SET(H_ALL_RES_EQ_INH_EQE_GEN, !eq_attr->eqe_gen) - | EHEA_BMASK_SET(H_ALL_RES_EQ_NON_NEQ_ISN, 1); - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - allocate_controls, /* R5 */ - eq_attr->max_nr_of_eqes, /* R6 */ - 0, 0, 0, 0, 0, 0); /* R7-R10 */ - - *eq_handle = outs[0]; - eq_attr->act_nr_of_eqes = outs[3]; - eq_attr->nr_pages = outs[4]; - eq_attr->ist1 = outs[5]; - eq_attr->ist2 = outs[6]; - eq_attr->ist3 = outs[7]; - eq_attr->ist4 = outs[8]; - - return hret; -} - -u64 ehea_h_modify_ehea_qp(const u64 adapter_handle, const u8 cat, - const u64 qp_handle, const u64 sel_mask, - void *cb_addr, u64 *inv_attr_id, u64 *proc_mask, - u16 *out_swr, u16 *out_rwr) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_MODIFY_HEA_QP, - outs, - adapter_handle, /* R4 */ - (u64) cat, /* R5 */ - qp_handle, /* R6 */ - sel_mask, /* R7 */ - __pa(cb_addr), /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - *inv_attr_id = outs[0]; - *out_swr = outs[3]; - *out_rwr = outs[4]; - *proc_mask = outs[5]; - - return hret; -} - -u64 ehea_h_register_rpage(const u64 adapter_handle, const u8 pagesize, - const u8 queue_type, const u64 resource_handle, - const u64 log_pageaddr, u64 count) -{ - u64 reg_control; - - reg_control = EHEA_BMASK_SET(H_REG_RPAGE_PAGE_SIZE, pagesize) - | EHEA_BMASK_SET(H_REG_RPAGE_QT, queue_type); - - return ehea_plpar_hcall_norets(H_REGISTER_HEA_RPAGES, - adapter_handle, /* R4 */ - reg_control, /* R5 */ - resource_handle, /* R6 */ - log_pageaddr, /* R7 */ - count, /* R8 */ - 0, 0); /* R9-R10 */ -} - -u64 ehea_h_register_smr(const u64 adapter_handle, const u64 orig_mr_handle, - const u64 vaddr_in, const u32 access_ctrl, const u32 pd, - struct ehea_mr *mr) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_REGISTER_SMR, - outs, - adapter_handle , /* R4 */ - orig_mr_handle, /* R5 */ - vaddr_in, /* R6 */ - (((u64)access_ctrl) << 32ULL), /* R7 */ - pd, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ - - mr->handle = outs[0]; - mr->lkey = (u32)outs[2]; - - return hret; -} - -u64 ehea_h_disable_and_get_hea(const u64 adapter_handle, const u64 qp_handle) -{ - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - return ehea_plpar_hcall9(H_DISABLE_AND_GET_HEA, - outs, - adapter_handle, /* R4 */ - H_DISABLE_GET_EHEA_WQE_P, /* R5 */ - qp_handle, /* R6 */ - 0, 0, 0, 0, 0, 0); /* R7-R12 */ -} - -u64 ehea_h_free_resource(const u64 adapter_handle, const u64 res_handle, - u64 force_bit) -{ - return ehea_plpar_hcall_norets(H_FREE_RESOURCE, - adapter_handle, /* R4 */ - res_handle, /* R5 */ - force_bit, - 0, 0, 0, 0); /* R7-R10 */ -} - -u64 ehea_h_alloc_resource_mr(const u64 adapter_handle, const u64 vaddr, - const u64 length, const u32 access_ctrl, - const u32 pd, u64 *mr_handle, u32 *lkey) -{ - u64 hret; - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - - hret = ehea_plpar_hcall9(H_ALLOC_HEA_RESOURCE, - outs, - adapter_handle, /* R4 */ - 5, /* R5 */ - vaddr, /* R6 */ - length, /* R7 */ - (((u64) access_ctrl) << 32ULL), /* R8 */ - pd, /* R9 */ - 0, 0, 0); /* R10-R12 */ - - *mr_handle = outs[0]; - *lkey = (u32)outs[2]; - return hret; -} - -u64 ehea_h_register_rpage_mr(const u64 adapter_handle, const u64 mr_handle, - const u8 pagesize, const u8 queue_type, - const u64 log_pageaddr, const u64 count) -{ - if ((count > 1) && (log_pageaddr & ~PAGE_MASK)) { - pr_err("not on pageboundary\n"); - return H_PARAMETER; - } - - return ehea_h_register_rpage(adapter_handle, pagesize, - queue_type, mr_handle, - log_pageaddr, count); -} - -u64 ehea_h_query_ehea(const u64 adapter_handle, void *cb_addr) -{ - u64 hret, cb_logaddr; - - cb_logaddr = __pa(cb_addr); - - hret = ehea_plpar_hcall_norets(H_QUERY_HEA, - adapter_handle, /* R4 */ - cb_logaddr, /* R5 */ - 0, 0, 0, 0, 0); /* R6-R10 */ -#ifdef DEBUG - ehea_dump(cb_addr, sizeof(struct hcp_query_ehea), "hcp_query_ehea"); -#endif - return hret; -} - -u64 ehea_h_query_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr) -{ - u64 port_info; - u64 cb_logaddr = __pa(cb_addr); - u64 arr_index = 0; - - port_info = EHEA_BMASK_SET(H_MEHEAPORT_CAT, cb_cat) - | EHEA_BMASK_SET(H_MEHEAPORT_PN, port_num); - - return ehea_plpar_hcall_norets(H_QUERY_HEA_PORT, - adapter_handle, /* R4 */ - port_info, /* R5 */ - select_mask, /* R6 */ - arr_index, /* R7 */ - cb_logaddr, /* R8 */ - 0, 0); /* R9-R10 */ -} - -u64 ehea_h_modify_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr) -{ - unsigned long outs[PLPAR_HCALL9_BUFSIZE]; - u64 port_info; - u64 arr_index = 0; - u64 cb_logaddr = __pa(cb_addr); - - port_info = EHEA_BMASK_SET(H_MEHEAPORT_CAT, cb_cat) - | EHEA_BMASK_SET(H_MEHEAPORT_PN, port_num); -#ifdef DEBUG - ehea_dump(cb_addr, sizeof(struct hcp_ehea_port_cb0), "Before HCALL"); -#endif - return ehea_plpar_hcall9(H_MODIFY_HEA_PORT, - outs, - adapter_handle, /* R4 */ - port_info, /* R5 */ - select_mask, /* R6 */ - arr_index, /* R7 */ - cb_logaddr, /* R8 */ - 0, 0, 0, 0); /* R9-R12 */ -} - -u64 ehea_h_reg_dereg_bcmc(const u64 adapter_handle, const u16 port_num, - const u8 reg_type, const u64 mc_mac_addr, - const u16 vlan_id, const u32 hcall_id) -{ - u64 r5_port_num, r6_reg_type, r7_mc_mac_addr, r8_vlan_id; - u64 mac_addr = mc_mac_addr >> 16; - - r5_port_num = EHEA_BMASK_SET(H_REGBCMC_PN, port_num); - r6_reg_type = EHEA_BMASK_SET(H_REGBCMC_REGTYPE, reg_type); - r7_mc_mac_addr = EHEA_BMASK_SET(H_REGBCMC_MACADDR, mac_addr); - r8_vlan_id = EHEA_BMASK_SET(H_REGBCMC_VLANID, vlan_id); - - return ehea_plpar_hcall_norets(hcall_id, - adapter_handle, /* R4 */ - r5_port_num, /* R5 */ - r6_reg_type, /* R6 */ - r7_mc_mac_addr, /* R7 */ - r8_vlan_id, /* R8 */ - 0, 0); /* R9-R12 */ -} - -u64 ehea_h_reset_events(const u64 adapter_handle, const u64 neq_handle, - const u64 event_mask) -{ - return ehea_plpar_hcall_norets(H_RESET_EVENTS, - adapter_handle, /* R4 */ - neq_handle, /* R5 */ - event_mask, /* R6 */ - 0, 0, 0, 0); /* R7-R12 */ -} - -u64 ehea_h_error_data(const u64 adapter_handle, const u64 ressource_handle, - void *rblock) -{ - return ehea_plpar_hcall_norets(H_ERROR_DATA, - adapter_handle, /* R4 */ - ressource_handle, /* R5 */ - __pa(rblock), /* R6 */ - 0, 0, 0, 0); /* R7-R12 */ -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_phyp.h b/drivers/net/ethernet/ibm/ehea/ehea_phyp.h deleted file mode 100644 index e8b56c103410..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_phyp.h +++ /dev/null @@ -1,433 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_phyp.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#ifndef __EHEA_PHYP_H__ -#define __EHEA_PHYP_H__ - -#include <linux/delay.h> -#include <asm/hvcall.h> -#include "ehea.h" -#include "ehea_hw.h" - -/* Some abbreviations used here: - * - * hcp_* - structures, variables and functions releated to Hypervisor Calls - */ - -/* Number of pages which can be registered at once by H_REGISTER_HEA_RPAGES */ -#define EHEA_MAX_RPAGE 512 - -/* Notification Event Queue (NEQ) Entry bit masks */ -#define NEQE_EVENT_CODE EHEA_BMASK_IBM(2, 7) -#define NEQE_PORTNUM EHEA_BMASK_IBM(32, 47) -#define NEQE_PORT_UP EHEA_BMASK_IBM(16, 16) -#define NEQE_EXTSWITCH_PORT_UP EHEA_BMASK_IBM(17, 17) -#define NEQE_EXTSWITCH_PRIMARY EHEA_BMASK_IBM(18, 18) -#define NEQE_PLID EHEA_BMASK_IBM(16, 47) - -/* Notification Event Codes */ -#define EHEA_EC_PORTSTATE_CHG 0x30 -#define EHEA_EC_ADAPTER_MALFUNC 0x32 -#define EHEA_EC_PORT_MALFUNC 0x33 - -/* Notification Event Log Register (NELR) bit masks */ -#define NELR_PORT_MALFUNC EHEA_BMASK_IBM(61, 61) -#define NELR_ADAPTER_MALFUNC EHEA_BMASK_IBM(62, 62) -#define NELR_PORTSTATE_CHG EHEA_BMASK_IBM(63, 63) - -static inline void hcp_epas_ctor(struct h_epas *epas, u64 paddr_kernel, - u64 paddr_user) -{ - /* To support 64k pages we must round to 64k page boundary */ - epas->kernel.addr = ioremap((paddr_kernel & PAGE_MASK), PAGE_SIZE) + - (paddr_kernel & ~PAGE_MASK); - epas->user.addr = paddr_user; -} - -static inline void hcp_epas_dtor(struct h_epas *epas) -{ - if (epas->kernel.addr) - iounmap((void __iomem *)((u64)epas->kernel.addr & PAGE_MASK)); - - epas->user.addr = 0; - epas->kernel.addr = 0; -} - -struct hcp_modify_qp_cb0 { - u64 qp_ctl_reg; /* 00 */ - u32 max_swqe; /* 02 */ - u32 max_rwqe; /* 03 */ - u32 port_nb; /* 04 */ - u32 reserved0; /* 05 */ - u64 qp_aer; /* 06 */ - u64 qp_tenure; /* 08 */ -}; - -/* Hcall Query/Modify Queue Pair Control Block 0 Selection Mask Bits */ -#define H_QPCB0_ALL EHEA_BMASK_IBM(0, 5) -#define H_QPCB0_QP_CTL_REG EHEA_BMASK_IBM(0, 0) -#define H_QPCB0_MAX_SWQE EHEA_BMASK_IBM(1, 1) -#define H_QPCB0_MAX_RWQE EHEA_BMASK_IBM(2, 2) -#define H_QPCB0_PORT_NB EHEA_BMASK_IBM(3, 3) -#define H_QPCB0_QP_AER EHEA_BMASK_IBM(4, 4) -#define H_QPCB0_QP_TENURE EHEA_BMASK_IBM(5, 5) - -/* Queue Pair Control Register Status Bits */ -#define H_QP_CR_ENABLED 0x8000000000000000ULL /* QP enabled */ - /* QP States: */ -#define H_QP_CR_STATE_RESET 0x0000010000000000ULL /* Reset */ -#define H_QP_CR_STATE_INITIALIZED 0x0000020000000000ULL /* Initialized */ -#define H_QP_CR_STATE_RDY2RCV 0x0000030000000000ULL /* Ready to recv */ -#define H_QP_CR_STATE_RDY2SND 0x0000050000000000ULL /* Ready to send */ -#define H_QP_CR_STATE_ERROR 0x0000800000000000ULL /* Error */ -#define H_QP_CR_RES_STATE 0x0000007F00000000ULL /* Resultant state */ - -struct hcp_modify_qp_cb1 { - u32 qpn; /* 00 */ - u32 qp_asyn_ev_eq_nb; /* 01 */ - u64 sq_cq_handle; /* 02 */ - u64 rq_cq_handle; /* 04 */ - /* sgel = scatter gather element */ - u32 sgel_nb_sq; /* 06 */ - u32 sgel_nb_rq1; /* 07 */ - u32 sgel_nb_rq2; /* 08 */ - u32 sgel_nb_rq3; /* 09 */ -}; - -/* Hcall Query/Modify Queue Pair Control Block 1 Selection Mask Bits */ -#define H_QPCB1_ALL EHEA_BMASK_IBM(0, 7) -#define H_QPCB1_QPN EHEA_BMASK_IBM(0, 0) -#define H_QPCB1_ASYN_EV_EQ_NB EHEA_BMASK_IBM(1, 1) -#define H_QPCB1_SQ_CQ_HANDLE EHEA_BMASK_IBM(2, 2) -#define H_QPCB1_RQ_CQ_HANDLE EHEA_BMASK_IBM(3, 3) -#define H_QPCB1_SGEL_NB_SQ EHEA_BMASK_IBM(4, 4) -#define H_QPCB1_SGEL_NB_RQ1 EHEA_BMASK_IBM(5, 5) -#define H_QPCB1_SGEL_NB_RQ2 EHEA_BMASK_IBM(6, 6) -#define H_QPCB1_SGEL_NB_RQ3 EHEA_BMASK_IBM(7, 7) - -struct hcp_query_ehea { - u32 cur_num_qps; /* 00 */ - u32 cur_num_cqs; /* 01 */ - u32 cur_num_eqs; /* 02 */ - u32 cur_num_mrs; /* 03 */ - u32 auth_level; /* 04 */ - u32 max_num_qps; /* 05 */ - u32 max_num_cqs; /* 06 */ - u32 max_num_eqs; /* 07 */ - u32 max_num_mrs; /* 08 */ - u32 reserved0; /* 09 */ - u32 int_clock_freq; /* 10 */ - u32 max_num_pds; /* 11 */ - u32 max_num_addr_handles; /* 12 */ - u32 max_num_cqes; /* 13 */ - u32 max_num_wqes; /* 14 */ - u32 max_num_sgel_rq1wqe; /* 15 */ - u32 max_num_sgel_rq2wqe; /* 16 */ - u32 max_num_sgel_rq3wqe; /* 17 */ - u32 mr_page_size; /* 18 */ - u32 reserved1; /* 19 */ - u64 max_mr_size; /* 20 */ - u64 reserved2; /* 22 */ - u32 num_ports; /* 24 */ - u32 reserved3; /* 25 */ - u32 reserved4; /* 26 */ - u32 reserved5; /* 27 */ - u64 max_mc_mac; /* 28 */ - u64 ehea_cap; /* 30 */ - u32 max_isn_per_eq; /* 32 */ - u32 max_num_neq; /* 33 */ - u64 max_num_vlan_ids; /* 34 */ - u32 max_num_port_group; /* 36 */ - u32 max_num_phys_port; /* 37 */ - -}; - -/* Hcall Query/Modify Port Control Block defines */ -#define H_PORT_CB0 0 -#define H_PORT_CB1 1 -#define H_PORT_CB2 2 -#define H_PORT_CB3 3 -#define H_PORT_CB4 4 -#define H_PORT_CB5 5 -#define H_PORT_CB6 6 -#define H_PORT_CB7 7 - -struct hcp_ehea_port_cb0 { - u64 port_mac_addr; - u64 port_rc; - u64 reserved0; - u32 port_op_state; - u32 port_speed; - u32 ext_swport_op_state; - u32 neg_tpf_prpf; - u32 num_default_qps; - u32 reserved1; - u64 default_qpn_arr[16]; -}; - -/* Hcall Query/Modify Port Control Block 0 Selection Mask Bits */ -#define H_PORT_CB0_ALL EHEA_BMASK_IBM(0, 7) /* Set all bits */ -#define H_PORT_CB0_MAC EHEA_BMASK_IBM(0, 0) /* MAC address */ -#define H_PORT_CB0_PRC EHEA_BMASK_IBM(1, 1) /* Port Recv Control */ -#define H_PORT_CB0_DEFQPNARRAY EHEA_BMASK_IBM(7, 7) /* Default QPN Array */ - -/* Hcall Query Port: Returned port speed values */ -#define H_SPEED_10M_H 1 /* 10 Mbps, Half Duplex */ -#define H_SPEED_10M_F 2 /* 10 Mbps, Full Duplex */ -#define H_SPEED_100M_H 3 /* 100 Mbps, Half Duplex */ -#define H_SPEED_100M_F 4 /* 100 Mbps, Full Duplex */ -#define H_SPEED_1G_F 6 /* 1 Gbps, Full Duplex */ -#define H_SPEED_10G_F 8 /* 10 Gbps, Full Duplex */ - -/* Port Receive Control Status Bits */ -#define PXLY_RC_VALID EHEA_BMASK_IBM(49, 49) -#define PXLY_RC_VLAN_XTRACT EHEA_BMASK_IBM(50, 50) -#define PXLY_RC_TCP_6_TUPLE EHEA_BMASK_IBM(51, 51) -#define PXLY_RC_UDP_6_TUPLE EHEA_BMASK_IBM(52, 52) -#define PXLY_RC_TCP_3_TUPLE EHEA_BMASK_IBM(53, 53) -#define PXLY_RC_TCP_2_TUPLE EHEA_BMASK_IBM(54, 54) -#define PXLY_RC_LLC_SNAP EHEA_BMASK_IBM(55, 55) -#define PXLY_RC_JUMBO_FRAME EHEA_BMASK_IBM(56, 56) -#define PXLY_RC_FRAG_IP_PKT EHEA_BMASK_IBM(57, 57) -#define PXLY_RC_TCP_UDP_CHKSUM EHEA_BMASK_IBM(58, 58) -#define PXLY_RC_IP_CHKSUM EHEA_BMASK_IBM(59, 59) -#define PXLY_RC_MAC_FILTER EHEA_BMASK_IBM(60, 60) -#define PXLY_RC_UNTAG_FILTER EHEA_BMASK_IBM(61, 61) -#define PXLY_RC_VLAN_TAG_FILTER EHEA_BMASK_IBM(62, 63) - -#define PXLY_RC_VLAN_FILTER 2 -#define PXLY_RC_VLAN_PERM 0 - - -#define H_PORT_CB1_ALL 0x8000000000000000ULL - -struct hcp_ehea_port_cb1 { - u64 vlan_filter[64]; -}; - -#define H_PORT_CB2_ALL 0xFFE0000000000000ULL - -struct hcp_ehea_port_cb2 { - u64 rxo; - u64 rxucp; - u64 rxufd; - u64 rxuerr; - u64 rxftl; - u64 rxmcp; - u64 rxbcp; - u64 txo; - u64 txucp; - u64 txmcp; - u64 txbcp; -}; - -struct hcp_ehea_port_cb3 { - u64 vlan_bc_filter[64]; - u64 vlan_mc_filter[64]; - u64 vlan_un_filter[64]; - u64 port_mac_hash_array[64]; -}; - -#define H_PORT_CB4_ALL 0xF000000000000000ULL -#define H_PORT_CB4_JUMBO 0x1000000000000000ULL -#define H_PORT_CB4_SPEED 0x8000000000000000ULL - -struct hcp_ehea_port_cb4 { - u32 port_speed; - u32 pause_frame; - u32 ens_port_op_state; - u32 jumbo_frame; - u32 ens_port_wrap; -}; - -/* Hcall Query/Modify Port Control Block 5 Selection Mask Bits */ -#define H_PORT_CB5_RCU 0x0001000000000000ULL -#define PXS_RCU EHEA_BMASK_IBM(61, 63) - -struct hcp_ehea_port_cb5 { - u64 prc; /* 00 */ - u64 uaa; /* 01 */ - u64 macvc; /* 02 */ - u64 xpcsc; /* 03 */ - u64 xpcsp; /* 04 */ - u64 pcsid; /* 05 */ - u64 xpcsst; /* 06 */ - u64 pthlb; /* 07 */ - u64 pthrb; /* 08 */ - u64 pqu; /* 09 */ - u64 pqd; /* 10 */ - u64 prt; /* 11 */ - u64 wsth; /* 12 */ - u64 rcb; /* 13 */ - u64 rcm; /* 14 */ - u64 rcu; /* 15 */ - u64 macc; /* 16 */ - u64 pc; /* 17 */ - u64 pst; /* 18 */ - u64 ducqpn; /* 19 */ - u64 mcqpn; /* 20 */ - u64 mma; /* 21 */ - u64 pmc0h; /* 22 */ - u64 pmc0l; /* 23 */ - u64 lbc; /* 24 */ -}; - -#define H_PORT_CB6_ALL 0xFFFFFE7FFFFF8000ULL - -struct hcp_ehea_port_cb6 { - u64 rxo; /* 00 */ - u64 rx64; /* 01 */ - u64 rx65; /* 02 */ - u64 rx128; /* 03 */ - u64 rx256; /* 04 */ - u64 rx512; /* 05 */ - u64 rx1024; /* 06 */ - u64 rxbfcs; /* 07 */ - u64 rxime; /* 08 */ - u64 rxrle; /* 09 */ - u64 rxorle; /* 10 */ - u64 rxftl; /* 11 */ - u64 rxjab; /* 12 */ - u64 rxse; /* 13 */ - u64 rxce; /* 14 */ - u64 rxrf; /* 15 */ - u64 rxfrag; /* 16 */ - u64 rxuoc; /* 17 */ - u64 rxcpf; /* 18 */ - u64 rxsb; /* 19 */ - u64 rxfd; /* 20 */ - u64 rxoerr; /* 21 */ - u64 rxaln; /* 22 */ - u64 ducqpn; /* 23 */ - u64 reserved0; /* 24 */ - u64 rxmcp; /* 25 */ - u64 rxbcp; /* 26 */ - u64 txmcp; /* 27 */ - u64 txbcp; /* 28 */ - u64 txo; /* 29 */ - u64 tx64; /* 30 */ - u64 tx65; /* 31 */ - u64 tx128; /* 32 */ - u64 tx256; /* 33 */ - u64 tx512; /* 34 */ - u64 tx1024; /* 35 */ - u64 txbfcs; /* 36 */ - u64 txcpf; /* 37 */ - u64 txlf; /* 38 */ - u64 txrf; /* 39 */ - u64 txime; /* 40 */ - u64 txsc; /* 41 */ - u64 txmc; /* 42 */ - u64 txsqe; /* 43 */ - u64 txdef; /* 44 */ - u64 txlcol; /* 45 */ - u64 txexcol; /* 46 */ - u64 txcse; /* 47 */ - u64 txbor; /* 48 */ -}; - -#define H_PORT_CB7_DUCQPN 0x8000000000000000ULL - -struct hcp_ehea_port_cb7 { - u64 def_uc_qpn; -}; - -u64 ehea_h_query_ehea_qp(const u64 adapter_handle, - const u8 qp_category, - const u64 qp_handle, const u64 sel_mask, - void *cb_addr); - -u64 ehea_h_modify_ehea_qp(const u64 adapter_handle, - const u8 cat, - const u64 qp_handle, - const u64 sel_mask, - void *cb_addr, - u64 *inv_attr_id, - u64 *proc_mask, u16 *out_swr, u16 *out_rwr); - -u64 ehea_h_alloc_resource_eq(const u64 adapter_handle, - struct ehea_eq_attr *eq_attr, u64 *eq_handle); - -u64 ehea_h_alloc_resource_cq(const u64 adapter_handle, - struct ehea_cq_attr *cq_attr, - u64 *cq_handle, struct h_epas *epas); - -u64 ehea_h_alloc_resource_qp(const u64 adapter_handle, - struct ehea_qp_init_attr *init_attr, - const u32 pd, - u64 *qp_handle, struct h_epas *h_epas); - -#define H_REG_RPAGE_PAGE_SIZE EHEA_BMASK_IBM(48, 55) -#define H_REG_RPAGE_QT EHEA_BMASK_IBM(62, 63) - -u64 ehea_h_register_rpage(const u64 adapter_handle, - const u8 pagesize, - const u8 queue_type, - const u64 resource_handle, - const u64 log_pageaddr, u64 count); - -#define H_DISABLE_GET_EHEA_WQE_P 1 -#define H_DISABLE_GET_SQ_WQE_P 2 -#define H_DISABLE_GET_RQC 3 - -u64 ehea_h_disable_and_get_hea(const u64 adapter_handle, const u64 qp_handle); - -#define FORCE_FREE 1 -#define NORMAL_FREE 0 - -u64 ehea_h_free_resource(const u64 adapter_handle, const u64 res_handle, - u64 force_bit); - -u64 ehea_h_alloc_resource_mr(const u64 adapter_handle, const u64 vaddr, - const u64 length, const u32 access_ctrl, - const u32 pd, u64 *mr_handle, u32 *lkey); - -u64 ehea_h_register_rpage_mr(const u64 adapter_handle, const u64 mr_handle, - const u8 pagesize, const u8 queue_type, - const u64 log_pageaddr, const u64 count); - -u64 ehea_h_register_smr(const u64 adapter_handle, const u64 orig_mr_handle, - const u64 vaddr_in, const u32 access_ctrl, const u32 pd, - struct ehea_mr *mr); - -u64 ehea_h_query_ehea(const u64 adapter_handle, void *cb_addr); - -/* output param R5 */ -#define H_MEHEAPORT_CAT EHEA_BMASK_IBM(40, 47) -#define H_MEHEAPORT_PN EHEA_BMASK_IBM(48, 63) - -u64 ehea_h_query_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr); - -u64 ehea_h_modify_ehea_port(const u64 adapter_handle, const u16 port_num, - const u8 cb_cat, const u64 select_mask, - void *cb_addr); - -#define H_REGBCMC_PN EHEA_BMASK_IBM(48, 63) -#define H_REGBCMC_REGTYPE EHEA_BMASK_IBM(60, 63) -#define H_REGBCMC_MACADDR EHEA_BMASK_IBM(16, 63) -#define H_REGBCMC_VLANID EHEA_BMASK_IBM(52, 63) - -u64 ehea_h_reg_dereg_bcmc(const u64 adapter_handle, const u16 port_num, - const u8 reg_type, const u64 mc_mac_addr, - const u16 vlan_id, const u32 hcall_id); - -u64 ehea_h_reset_events(const u64 adapter_handle, const u64 neq_handle, - const u64 event_mask); - -u64 ehea_h_error_data(const u64 adapter_handle, const u64 ressource_handle, - void *rblock); - -#endif /* __EHEA_PHYP_H__ */ diff --git a/drivers/net/ethernet/ibm/ehea/ehea_qmr.c b/drivers/net/ethernet/ibm/ehea/ehea_qmr.c deleted file mode 100644 index 60629a0032b2..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_qmr.c +++ /dev/null @@ -1,999 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_qmr.c - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/mm.h> -#include <linux/slab.h> -#include "ehea.h" -#include "ehea_phyp.h" -#include "ehea_qmr.h" - -static struct ehea_bmap *ehea_bmap; - -static void *hw_qpageit_get_inc(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - - queue->current_q_offset += queue->pagesize; - if (queue->current_q_offset > queue->queue_length) { - queue->current_q_offset -= queue->pagesize; - retvalue = NULL; - } else if (((u64) retvalue) & (EHEA_PAGESIZE-1)) { - pr_err("not on pageboundary\n"); - retvalue = NULL; - } - return retvalue; -} - -static int hw_queue_ctor(struct hw_queue *queue, const u32 nr_of_pages, - const u32 pagesize, const u32 qe_size) -{ - int pages_per_kpage = PAGE_SIZE / pagesize; - int i, k; - - if ((pagesize > PAGE_SIZE) || (!pages_per_kpage)) { - pr_err("pagesize conflict! kernel pagesize=%d, ehea pagesize=%d\n", - (int)PAGE_SIZE, (int)pagesize); - return -EINVAL; - } - - queue->queue_length = nr_of_pages * pagesize; - queue->queue_pages = kmalloc_array(nr_of_pages, sizeof(void *), - GFP_KERNEL); - if (!queue->queue_pages) - return -ENOMEM; - - /* - * allocate pages for queue: - * outer loop allocates whole kernel pages (page aligned) and - * inner loop divides a kernel page into smaller hea queue pages - */ - i = 0; - while (i < nr_of_pages) { - u8 *kpage = (u8 *)get_zeroed_page(GFP_KERNEL); - if (!kpage) - goto out_nomem; - for (k = 0; k < pages_per_kpage && i < nr_of_pages; k++) { - (queue->queue_pages)[i] = (struct ehea_page *)kpage; - kpage += pagesize; - i++; - } - } - - queue->current_q_offset = 0; - queue->qe_size = qe_size; - queue->pagesize = pagesize; - queue->toggle_state = 1; - - return 0; -out_nomem: - for (i = 0; i < nr_of_pages; i += pages_per_kpage) { - if (!(queue->queue_pages)[i]) - break; - free_page((unsigned long)(queue->queue_pages)[i]); - } - return -ENOMEM; -} - -static void hw_queue_dtor(struct hw_queue *queue) -{ - int pages_per_kpage; - int i, nr_pages; - - if (!queue || !queue->queue_pages) - return; - - pages_per_kpage = PAGE_SIZE / queue->pagesize; - - nr_pages = queue->queue_length / queue->pagesize; - - for (i = 0; i < nr_pages; i += pages_per_kpage) - free_page((unsigned long)(queue->queue_pages)[i]); - - kfree(queue->queue_pages); -} - -struct ehea_cq *ehea_create_cq(struct ehea_adapter *adapter, - int nr_of_cqe, u64 eq_handle, u32 cq_token) -{ - struct ehea_cq *cq; - u64 hret, rpage; - u32 counter; - int ret; - void *vpage; - - cq = kzalloc_obj(*cq); - if (!cq) - goto out_nomem; - - cq->attr.max_nr_of_cqes = nr_of_cqe; - cq->attr.cq_token = cq_token; - cq->attr.eq_handle = eq_handle; - - cq->adapter = adapter; - - hret = ehea_h_alloc_resource_cq(adapter->handle, &cq->attr, - &cq->fw_handle, &cq->epas); - if (hret != H_SUCCESS) { - pr_err("alloc_resource_cq failed\n"); - goto out_freemem; - } - - ret = hw_queue_ctor(&cq->hw_queue, cq->attr.nr_pages, - EHEA_PAGESIZE, sizeof(struct ehea_cqe)); - if (ret) - goto out_freeres; - - for (counter = 0; counter < cq->attr.nr_pages; counter++) { - vpage = hw_qpageit_get_inc(&cq->hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - goto out_kill_hwq; - } - - rpage = __pa(vpage); - hret = ehea_h_register_rpage(adapter->handle, - 0, EHEA_CQ_REGISTER_ORIG, - cq->fw_handle, rpage, 1); - if (hret < H_SUCCESS) { - pr_err("register_rpage_cq failed ehea_cq=%p hret=%llx counter=%i act_pages=%i\n", - cq, hret, counter, cq->attr.nr_pages); - goto out_kill_hwq; - } - - if (counter == (cq->attr.nr_pages - 1)) { - vpage = hw_qpageit_get_inc(&cq->hw_queue); - - if ((hret != H_SUCCESS) || (vpage)) { - pr_err("registration of pages not complete hret=%llx\n", - hret); - goto out_kill_hwq; - } - } else { - if (hret != H_PAGE_REGISTERED) { - pr_err("CQ: registration of page failed hret=%llx\n", - hret); - goto out_kill_hwq; - } - } - } - - hw_qeit_reset(&cq->hw_queue); - ehea_reset_cq_ep(cq); - ehea_reset_cq_n1(cq); - - return cq; - -out_kill_hwq: - hw_queue_dtor(&cq->hw_queue); - -out_freeres: - ehea_h_free_resource(adapter->handle, cq->fw_handle, FORCE_FREE); - -out_freemem: - kfree(cq); - -out_nomem: - return NULL; -} - -static u64 ehea_destroy_cq_res(struct ehea_cq *cq, u64 force) -{ - u64 hret; - u64 adapter_handle = cq->adapter->handle; - - /* deregister all previous registered pages */ - hret = ehea_h_free_resource(adapter_handle, cq->fw_handle, force); - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&cq->hw_queue); - kfree(cq); - - return hret; -} - -int ehea_destroy_cq(struct ehea_cq *cq) -{ - u64 hret, aer, aerr; - if (!cq) - return 0; - - hcp_epas_dtor(&cq->epas); - hret = ehea_destroy_cq_res(cq, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(cq->adapter, cq->fw_handle, &aer, &aerr); - hret = ehea_destroy_cq_res(cq, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy CQ failed\n"); - return -EIO; - } - - return 0; -} - -struct ehea_eq *ehea_create_eq(struct ehea_adapter *adapter, - const enum ehea_eq_type type, - const u32 max_nr_of_eqes, const u8 eqe_gen) -{ - int ret, i; - u64 hret, rpage; - void *vpage; - struct ehea_eq *eq; - - eq = kzalloc_obj(*eq); - if (!eq) - return NULL; - - eq->adapter = adapter; - eq->attr.type = type; - eq->attr.max_nr_of_eqes = max_nr_of_eqes; - eq->attr.eqe_gen = eqe_gen; - spin_lock_init(&eq->spinlock); - - hret = ehea_h_alloc_resource_eq(adapter->handle, - &eq->attr, &eq->fw_handle); - if (hret != H_SUCCESS) { - pr_err("alloc_resource_eq failed\n"); - goto out_freemem; - } - - ret = hw_queue_ctor(&eq->hw_queue, eq->attr.nr_pages, - EHEA_PAGESIZE, sizeof(struct ehea_eqe)); - if (ret) { - pr_err("can't allocate eq pages\n"); - goto out_freeres; - } - - for (i = 0; i < eq->attr.nr_pages; i++) { - vpage = hw_qpageit_get_inc(&eq->hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - hret = H_RESOURCE; - goto out_kill_hwq; - } - - rpage = __pa(vpage); - - hret = ehea_h_register_rpage(adapter->handle, 0, - EHEA_EQ_REGISTER_ORIG, - eq->fw_handle, rpage, 1); - - if (i == (eq->attr.nr_pages - 1)) { - /* last page */ - vpage = hw_qpageit_get_inc(&eq->hw_queue); - if ((hret != H_SUCCESS) || (vpage)) - goto out_kill_hwq; - - } else { - if (hret != H_PAGE_REGISTERED) - goto out_kill_hwq; - - } - } - - hw_qeit_reset(&eq->hw_queue); - return eq; - -out_kill_hwq: - hw_queue_dtor(&eq->hw_queue); - -out_freeres: - ehea_h_free_resource(adapter->handle, eq->fw_handle, FORCE_FREE); - -out_freemem: - kfree(eq); - return NULL; -} - -struct ehea_eqe *ehea_poll_eq(struct ehea_eq *eq) -{ - struct ehea_eqe *eqe; - unsigned long flags; - - spin_lock_irqsave(&eq->spinlock, flags); - eqe = hw_eqit_eq_get_inc_valid(&eq->hw_queue); - spin_unlock_irqrestore(&eq->spinlock, flags); - - return eqe; -} - -static u64 ehea_destroy_eq_res(struct ehea_eq *eq, u64 force) -{ - u64 hret; - unsigned long flags; - - spin_lock_irqsave(&eq->spinlock, flags); - - hret = ehea_h_free_resource(eq->adapter->handle, eq->fw_handle, force); - spin_unlock_irqrestore(&eq->spinlock, flags); - - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&eq->hw_queue); - kfree(eq); - - return hret; -} - -int ehea_destroy_eq(struct ehea_eq *eq) -{ - u64 hret, aer, aerr; - if (!eq) - return 0; - - hcp_epas_dtor(&eq->epas); - - hret = ehea_destroy_eq_res(eq, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(eq->adapter, eq->fw_handle, &aer, &aerr); - hret = ehea_destroy_eq_res(eq, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy EQ failed\n"); - return -EIO; - } - - return 0; -} - -/* allocates memory for a queue and registers pages in phyp */ -static int ehea_qp_alloc_register(struct ehea_qp *qp, struct hw_queue *hw_queue, - int nr_pages, int wqe_size, int act_nr_sges, - struct ehea_adapter *adapter, int h_call_q_selector) -{ - u64 hret, rpage; - int ret, cnt; - void *vpage; - - ret = hw_queue_ctor(hw_queue, nr_pages, EHEA_PAGESIZE, wqe_size); - if (ret) - return ret; - - for (cnt = 0; cnt < nr_pages; cnt++) { - vpage = hw_qpageit_get_inc(hw_queue); - if (!vpage) { - pr_err("hw_qpageit_get_inc failed\n"); - goto out_kill_hwq; - } - rpage = __pa(vpage); - hret = ehea_h_register_rpage(adapter->handle, - 0, h_call_q_selector, - qp->fw_handle, rpage, 1); - if (hret < H_SUCCESS) { - pr_err("register_rpage_qp failed\n"); - goto out_kill_hwq; - } - } - hw_qeit_reset(hw_queue); - return 0; - -out_kill_hwq: - hw_queue_dtor(hw_queue); - return -EIO; -} - -static inline u32 map_wqe_size(u8 wqe_enc_size) -{ - return 128 << wqe_enc_size; -} - -struct ehea_qp *ehea_create_qp(struct ehea_adapter *adapter, - u32 pd, struct ehea_qp_init_attr *init_attr) -{ - int ret; - u64 hret; - struct ehea_qp *qp; - u32 wqe_size_in_bytes_sq, wqe_size_in_bytes_rq1; - u32 wqe_size_in_bytes_rq2, wqe_size_in_bytes_rq3; - - - qp = kzalloc_obj(*qp); - if (!qp) - return NULL; - - qp->adapter = adapter; - - hret = ehea_h_alloc_resource_qp(adapter->handle, init_attr, pd, - &qp->fw_handle, &qp->epas); - if (hret != H_SUCCESS) { - pr_err("ehea_h_alloc_resource_qp failed\n"); - goto out_freemem; - } - - wqe_size_in_bytes_sq = map_wqe_size(init_attr->act_wqe_size_enc_sq); - wqe_size_in_bytes_rq1 = map_wqe_size(init_attr->act_wqe_size_enc_rq1); - wqe_size_in_bytes_rq2 = map_wqe_size(init_attr->act_wqe_size_enc_rq2); - wqe_size_in_bytes_rq3 = map_wqe_size(init_attr->act_wqe_size_enc_rq3); - - ret = ehea_qp_alloc_register(qp, &qp->hw_squeue, init_attr->nr_sq_pages, - wqe_size_in_bytes_sq, - init_attr->act_wqe_size_enc_sq, adapter, - 0); - if (ret) { - pr_err("can't register for sq ret=%x\n", ret); - goto out_freeres; - } - - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue1, - init_attr->nr_rq1_pages, - wqe_size_in_bytes_rq1, - init_attr->act_wqe_size_enc_rq1, - adapter, 1); - if (ret) { - pr_err("can't register for rq1 ret=%x\n", ret); - goto out_kill_hwsq; - } - - if (init_attr->rq_count > 1) { - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue2, - init_attr->nr_rq2_pages, - wqe_size_in_bytes_rq2, - init_attr->act_wqe_size_enc_rq2, - adapter, 2); - if (ret) { - pr_err("can't register for rq2 ret=%x\n", ret); - goto out_kill_hwr1q; - } - } - - if (init_attr->rq_count > 2) { - ret = ehea_qp_alloc_register(qp, &qp->hw_rqueue3, - init_attr->nr_rq3_pages, - wqe_size_in_bytes_rq3, - init_attr->act_wqe_size_enc_rq3, - adapter, 3); - if (ret) { - pr_err("can't register for rq3 ret=%x\n", ret); - goto out_kill_hwr2q; - } - } - - qp->init_attr = *init_attr; - - return qp; - -out_kill_hwr2q: - hw_queue_dtor(&qp->hw_rqueue2); - -out_kill_hwr1q: - hw_queue_dtor(&qp->hw_rqueue1); - -out_kill_hwsq: - hw_queue_dtor(&qp->hw_squeue); - -out_freeres: - ehea_h_disable_and_get_hea(adapter->handle, qp->fw_handle); - ehea_h_free_resource(adapter->handle, qp->fw_handle, FORCE_FREE); - -out_freemem: - kfree(qp); - return NULL; -} - -static u64 ehea_destroy_qp_res(struct ehea_qp *qp, u64 force) -{ - u64 hret; - struct ehea_qp_init_attr *qp_attr = &qp->init_attr; - - - ehea_h_disable_and_get_hea(qp->adapter->handle, qp->fw_handle); - hret = ehea_h_free_resource(qp->adapter->handle, qp->fw_handle, force); - if (hret != H_SUCCESS) - return hret; - - hw_queue_dtor(&qp->hw_squeue); - hw_queue_dtor(&qp->hw_rqueue1); - - if (qp_attr->rq_count > 1) - hw_queue_dtor(&qp->hw_rqueue2); - if (qp_attr->rq_count > 2) - hw_queue_dtor(&qp->hw_rqueue3); - kfree(qp); - - return hret; -} - -int ehea_destroy_qp(struct ehea_qp *qp) -{ - u64 hret, aer, aerr; - if (!qp) - return 0; - - hcp_epas_dtor(&qp->epas); - - hret = ehea_destroy_qp_res(qp, NORMAL_FREE); - if (hret == H_R_STATE) { - ehea_error_data(qp->adapter, qp->fw_handle, &aer, &aerr); - hret = ehea_destroy_qp_res(qp, FORCE_FREE); - } - - if (hret != H_SUCCESS) { - pr_err("destroy QP failed\n"); - return -EIO; - } - - return 0; -} - -static inline int ehea_calc_index(unsigned long i, unsigned long s) -{ - return (i >> s) & EHEA_INDEX_MASK; -} - -static inline int ehea_init_top_bmap(struct ehea_top_bmap *ehea_top_bmap, - int dir) -{ - if (!ehea_top_bmap->dir[dir]) { - ehea_top_bmap->dir[dir] = - kzalloc_obj(struct ehea_dir_bmap); - if (!ehea_top_bmap->dir[dir]) - return -ENOMEM; - } - return 0; -} - -static inline int ehea_init_bmap(struct ehea_bmap *ehea_bmap, int top, int dir) -{ - if (!ehea_bmap->top[top]) { - ehea_bmap->top[top] = - kzalloc_obj(struct ehea_top_bmap); - if (!ehea_bmap->top[top]) - return -ENOMEM; - } - return ehea_init_top_bmap(ehea_bmap->top[top], dir); -} - -static DEFINE_MUTEX(ehea_busmap_mutex); -static unsigned long ehea_mr_len; - -#define EHEA_BUSMAP_ADD_SECT 1 -#define EHEA_BUSMAP_REM_SECT 0 - -static void ehea_rebuild_busmap(void) -{ - u64 vaddr = EHEA_BUSMAP_START; - int top, dir, idx; - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - struct ehea_top_bmap *ehea_top; - int valid_dir_entries = 0; - - if (!ehea_bmap->top[top]) - continue; - ehea_top = ehea_bmap->top[top]; - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - struct ehea_dir_bmap *ehea_dir; - int valid_entries = 0; - - if (!ehea_top->dir[dir]) - continue; - valid_dir_entries++; - ehea_dir = ehea_top->dir[dir]; - for (idx = 0; idx < EHEA_MAP_ENTRIES; idx++) { - if (!ehea_dir->ent[idx]) - continue; - valid_entries++; - ehea_dir->ent[idx] = vaddr; - vaddr += EHEA_SECTSIZE; - } - if (!valid_entries) { - ehea_top->dir[dir] = NULL; - kfree(ehea_dir); - } - } - if (!valid_dir_entries) { - ehea_bmap->top[top] = NULL; - kfree(ehea_top); - } - } -} - -static int ehea_update_busmap(unsigned long pfn, unsigned long nr_pages, int add) -{ - unsigned long i, start_section, end_section; - - if (!nr_pages) - return 0; - - if (!ehea_bmap) { - ehea_bmap = kzalloc_obj(struct ehea_bmap); - if (!ehea_bmap) - return -ENOMEM; - } - - start_section = (pfn * PAGE_SIZE) / EHEA_SECTSIZE; - end_section = start_section + ((nr_pages * PAGE_SIZE) / EHEA_SECTSIZE); - /* Mark entries as valid or invalid only; address is assigned later */ - for (i = start_section; i < end_section; i++) { - u64 flag; - int top = ehea_calc_index(i, EHEA_TOP_INDEX_SHIFT); - int dir = ehea_calc_index(i, EHEA_DIR_INDEX_SHIFT); - int idx = i & EHEA_INDEX_MASK; - - if (add) { - int ret = ehea_init_bmap(ehea_bmap, top, dir); - if (ret) - return ret; - flag = 1; /* valid */ - ehea_mr_len += EHEA_SECTSIZE; - } else { - if (!ehea_bmap->top[top]) - continue; - if (!ehea_bmap->top[top]->dir[dir]) - continue; - flag = 0; /* invalid */ - ehea_mr_len -= EHEA_SECTSIZE; - } - - ehea_bmap->top[top]->dir[dir]->ent[idx] = flag; - } - ehea_rebuild_busmap(); /* Assign contiguous addresses for mr */ - return 0; -} - -int ehea_add_sect_bmap(unsigned long pfn, unsigned long nr_pages) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ret = ehea_update_busmap(pfn, nr_pages, EHEA_BUSMAP_ADD_SECT); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -int ehea_rem_sect_bmap(unsigned long pfn, unsigned long nr_pages) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ret = ehea_update_busmap(pfn, nr_pages, EHEA_BUSMAP_REM_SECT); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -static int ehea_is_hugepage(unsigned long pfn) -{ - if (pfn & EHEA_HUGEPAGE_PFN_MASK) - return 0; - - if (page_shift(pfn_to_page(pfn)) != EHEA_HUGEPAGESHIFT) - return 0; - - return 1; -} - -static int ehea_create_busmap_callback(unsigned long initial_pfn, - unsigned long total_nr_pages, void *arg) -{ - int ret; - unsigned long pfn, start_pfn, end_pfn, nr_pages; - - if ((total_nr_pages * PAGE_SIZE) < EHEA_HUGEPAGE_SIZE) - return ehea_update_busmap(initial_pfn, total_nr_pages, - EHEA_BUSMAP_ADD_SECT); - - /* Given chunk is >= 16GB -> check for hugepages */ - start_pfn = initial_pfn; - end_pfn = initial_pfn + total_nr_pages; - pfn = start_pfn; - - while (pfn < end_pfn) { - if (ehea_is_hugepage(pfn)) { - /* Add mem found in front of the hugepage */ - nr_pages = pfn - start_pfn; - ret = ehea_update_busmap(start_pfn, nr_pages, - EHEA_BUSMAP_ADD_SECT); - if (ret) - return ret; - - /* Skip the hugepage */ - pfn += (EHEA_HUGEPAGE_SIZE / PAGE_SIZE); - start_pfn = pfn; - } else - pfn += (EHEA_SECTSIZE / PAGE_SIZE); - } - - /* Add mem found behind the hugepage(s) */ - nr_pages = pfn - start_pfn; - return ehea_update_busmap(start_pfn, nr_pages, EHEA_BUSMAP_ADD_SECT); -} - -int ehea_create_busmap(void) -{ - int ret; - - mutex_lock(&ehea_busmap_mutex); - ehea_mr_len = 0; - ret = walk_system_ram_range(0, 1ULL << MAX_PHYSMEM_BITS, NULL, - ehea_create_busmap_callback); - mutex_unlock(&ehea_busmap_mutex); - return ret; -} - -void ehea_destroy_busmap(void) -{ - int top, dir; - mutex_lock(&ehea_busmap_mutex); - if (!ehea_bmap) - goto out_destroy; - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - if (!ehea_bmap->top[top]) - continue; - - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - if (!ehea_bmap->top[top]->dir[dir]) - continue; - - kfree(ehea_bmap->top[top]->dir[dir]); - } - - kfree(ehea_bmap->top[top]); - } - - kfree(ehea_bmap); - ehea_bmap = NULL; -out_destroy: - mutex_unlock(&ehea_busmap_mutex); -} - -u64 ehea_map_vaddr(void *caddr) -{ - int top, dir, idx; - unsigned long index, offset; - - if (!ehea_bmap) - return EHEA_INVAL_ADDR; - - index = __pa(caddr) >> SECTION_SIZE_BITS; - top = (index >> EHEA_TOP_INDEX_SHIFT) & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]) - return EHEA_INVAL_ADDR; - - dir = (index >> EHEA_DIR_INDEX_SHIFT) & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]->dir[dir]) - return EHEA_INVAL_ADDR; - - idx = index & EHEA_INDEX_MASK; - if (!ehea_bmap->top[top]->dir[dir]->ent[idx]) - return EHEA_INVAL_ADDR; - - offset = (unsigned long)caddr & (EHEA_SECTSIZE - 1); - return ehea_bmap->top[top]->dir[dir]->ent[idx] | offset; -} - -static inline void *ehea_calc_sectbase(int top, int dir, int idx) -{ - unsigned long ret = idx; - ret |= dir << EHEA_DIR_INDEX_SHIFT; - ret |= top << EHEA_TOP_INDEX_SHIFT; - return __va(ret << SECTION_SIZE_BITS); -} - -static u64 ehea_reg_mr_section(int top, int dir, int idx, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - void *pg; - u64 j, m, hret; - unsigned long k = 0; - u64 pt_abs = __pa(pt); - - void *sectbase = ehea_calc_sectbase(top, dir, idx); - - for (j = 0; j < (EHEA_PAGES_PER_SECTION / EHEA_MAX_RPAGE); j++) { - - for (m = 0; m < EHEA_MAX_RPAGE; m++) { - pg = sectbase + ((k++) * EHEA_PAGESIZE); - pt[m] = __pa(pg); - } - hret = ehea_h_register_rpage_mr(adapter->handle, mr->handle, 0, - 0, pt_abs, EHEA_MAX_RPAGE); - - if ((hret != H_SUCCESS) && - (hret != H_PAGE_REGISTERED)) { - ehea_h_free_resource(adapter->handle, mr->handle, - FORCE_FREE); - pr_err("register_rpage_mr failed\n"); - return hret; - } - } - return hret; -} - -static u64 ehea_reg_mr_sections(int top, int dir, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - u64 hret = H_SUCCESS; - int idx; - - for (idx = 0; idx < EHEA_MAP_ENTRIES; idx++) { - if (!ehea_bmap->top[top]->dir[dir]->ent[idx]) - continue; - - hret = ehea_reg_mr_section(top, dir, idx, pt, adapter, mr); - if ((hret != H_SUCCESS) && (hret != H_PAGE_REGISTERED)) - return hret; - } - return hret; -} - -static u64 ehea_reg_mr_dir_sections(int top, u64 *pt, - struct ehea_adapter *adapter, - struct ehea_mr *mr) -{ - u64 hret = H_SUCCESS; - int dir; - - for (dir = 0; dir < EHEA_MAP_ENTRIES; dir++) { - if (!ehea_bmap->top[top]->dir[dir]) - continue; - - hret = ehea_reg_mr_sections(top, dir, pt, adapter, mr); - if ((hret != H_SUCCESS) && (hret != H_PAGE_REGISTERED)) - return hret; - } - return hret; -} - -int ehea_reg_kernel_mr(struct ehea_adapter *adapter, struct ehea_mr *mr) -{ - int ret; - u64 *pt; - u64 hret; - u32 acc_ctrl = EHEA_MR_ACC_CTRL; - - unsigned long top; - - pt = (void *)get_zeroed_page(GFP_KERNEL); - if (!pt) { - pr_err("no mem\n"); - ret = -ENOMEM; - goto out; - } - - hret = ehea_h_alloc_resource_mr(adapter->handle, EHEA_BUSMAP_START, - ehea_mr_len, acc_ctrl, adapter->pd, - &mr->handle, &mr->lkey); - - if (hret != H_SUCCESS) { - pr_err("alloc_resource_mr failed\n"); - ret = -EIO; - goto out; - } - - if (!ehea_bmap) { - ehea_h_free_resource(adapter->handle, mr->handle, FORCE_FREE); - pr_err("no busmap available\n"); - ret = -EIO; - goto out; - } - - for (top = 0; top < EHEA_MAP_ENTRIES; top++) { - if (!ehea_bmap->top[top]) - continue; - - hret = ehea_reg_mr_dir_sections(top, pt, adapter, mr); - if((hret != H_PAGE_REGISTERED) && (hret != H_SUCCESS)) - break; - } - - if (hret != H_SUCCESS) { - ehea_h_free_resource(adapter->handle, mr->handle, FORCE_FREE); - pr_err("registering mr failed\n"); - ret = -EIO; - goto out; - } - - mr->vaddr = EHEA_BUSMAP_START; - mr->adapter = adapter; - ret = 0; -out: - free_page((unsigned long)pt); - return ret; -} - -int ehea_rem_mr(struct ehea_mr *mr) -{ - u64 hret; - - if (!mr || !mr->adapter) - return -EINVAL; - - hret = ehea_h_free_resource(mr->adapter->handle, mr->handle, - FORCE_FREE); - if (hret != H_SUCCESS) { - pr_err("destroy MR failed\n"); - return -EIO; - } - - return 0; -} - -int ehea_gen_smr(struct ehea_adapter *adapter, struct ehea_mr *old_mr, - struct ehea_mr *shared_mr) -{ - u64 hret; - - hret = ehea_h_register_smr(adapter->handle, old_mr->handle, - old_mr->vaddr, EHEA_MR_ACC_CTRL, - adapter->pd, shared_mr); - if (hret != H_SUCCESS) - return -EIO; - - shared_mr->adapter = adapter; - - return 0; -} - -static void print_error_data(u64 *data) -{ - int length; - u64 type = EHEA_BMASK_GET(ERROR_DATA_TYPE, data[2]); - u64 resource = data[1]; - - length = EHEA_BMASK_GET(ERROR_DATA_LENGTH, data[0]); - - if (length > EHEA_PAGESIZE) - length = EHEA_PAGESIZE; - - if (type == EHEA_AER_RESTYPE_QP) - pr_err("QP (resource=%llX) state: AER=0x%llX, AERR=0x%llX, port=%llX\n", - resource, data[6], data[12], data[22]); - else if (type == EHEA_AER_RESTYPE_CQ) - pr_err("CQ (resource=%llX) state: AER=0x%llX\n", - resource, data[6]); - else if (type == EHEA_AER_RESTYPE_EQ) - pr_err("EQ (resource=%llX) state: AER=0x%llX\n", - resource, data[6]); - - ehea_dump(data, length, "error data"); -} - -u64 ehea_error_data(struct ehea_adapter *adapter, u64 res_handle, - u64 *aer, u64 *aerr) -{ - unsigned long ret; - u64 *rblock; - u64 type = 0; - - rblock = (void *)get_zeroed_page(GFP_KERNEL); - if (!rblock) { - pr_err("Cannot allocate rblock memory\n"); - goto out; - } - - ret = ehea_h_error_data(adapter->handle, res_handle, rblock); - - if (ret == H_SUCCESS) { - type = EHEA_BMASK_GET(ERROR_DATA_TYPE, rblock[2]); - *aer = rblock[6]; - *aerr = rblock[12]; - print_error_data(rblock); - } else if (ret == H_R_STATE) { - pr_err("No error data available: %llX\n", res_handle); - } else - pr_err("Error data could not be fetched: %llX\n", res_handle); - - free_page((unsigned long)rblock); -out: - return type; -} diff --git a/drivers/net/ethernet/ibm/ehea/ehea_qmr.h b/drivers/net/ethernet/ibm/ehea/ehea_qmr.h deleted file mode 100644 index 7c7cccd820f7..000000000000 --- a/drivers/net/ethernet/ibm/ehea/ehea_qmr.h +++ /dev/null @@ -1,390 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * linux/drivers/net/ethernet/ibm/ehea/ehea_qmr.h - * - * eHEA ethernet device driver for IBM eServer System p - * - * (C) Copyright IBM Corp. 2006 - * - * Authors: - * Christoph Raisch <raisch@de.ibm.com> - * Jan-Bernd Themann <themann@de.ibm.com> - * Thomas Klein <tklein@de.ibm.com> - */ - -#ifndef __EHEA_QMR_H__ -#define __EHEA_QMR_H__ - -#include <linux/prefetch.h> -#include "ehea.h" -#include "ehea_hw.h" - -/* - * page size of ehea hardware queues - */ - -#define EHEA_PAGESHIFT 12 -#define EHEA_PAGESIZE (1UL << EHEA_PAGESHIFT) -#define EHEA_SECTSIZE (1UL << 24) -#define EHEA_PAGES_PER_SECTION (EHEA_SECTSIZE >> EHEA_PAGESHIFT) -#define EHEA_HUGEPAGESHIFT 34 -#define EHEA_HUGEPAGE_SIZE (1UL << EHEA_HUGEPAGESHIFT) -#define EHEA_HUGEPAGE_PFN_MASK ((EHEA_HUGEPAGE_SIZE - 1) >> PAGE_SHIFT) - -#if ((1UL << SECTION_SIZE_BITS) < EHEA_SECTSIZE) -#error eHEA module cannot work if kernel sectionsize < ehea sectionsize -#endif - -/* Some abbreviations used here: - * - * WQE - Work Queue Entry - * SWQE - Send Work Queue Entry - * RWQE - Receive Work Queue Entry - * CQE - Completion Queue Entry - * EQE - Event Queue Entry - * MR - Memory Region - */ - -/* Use of WR_ID field for EHEA */ -#define EHEA_WR_ID_COUNT EHEA_BMASK_IBM(0, 19) -#define EHEA_WR_ID_TYPE EHEA_BMASK_IBM(20, 23) -#define EHEA_SWQE2_TYPE 0x1 -#define EHEA_SWQE3_TYPE 0x2 -#define EHEA_RWQE2_TYPE 0x3 -#define EHEA_RWQE3_TYPE 0x4 -#define EHEA_WR_ID_INDEX EHEA_BMASK_IBM(24, 47) -#define EHEA_WR_ID_REFILL EHEA_BMASK_IBM(48, 63) - -struct ehea_vsgentry { - u64 vaddr; - u32 l_key; - u32 len; -}; - -/* maximum number of sg entries allowed in a WQE */ -#define EHEA_MAX_WQE_SG_ENTRIES 252 -#define SWQE2_MAX_IMM (0xD0 - 0x30) -#define SWQE3_MAX_IMM 224 - -/* tx control flags for swqe */ -#define EHEA_SWQE_CRC 0x8000 -#define EHEA_SWQE_IP_CHECKSUM 0x4000 -#define EHEA_SWQE_TCP_CHECKSUM 0x2000 -#define EHEA_SWQE_TSO 0x1000 -#define EHEA_SWQE_SIGNALLED_COMPLETION 0x0800 -#define EHEA_SWQE_VLAN_INSERT 0x0400 -#define EHEA_SWQE_IMM_DATA_PRESENT 0x0200 -#define EHEA_SWQE_DESCRIPTORS_PRESENT 0x0100 -#define EHEA_SWQE_WRAP_CTL_REC 0x0080 -#define EHEA_SWQE_WRAP_CTL_FORCE 0x0040 -#define EHEA_SWQE_BIND 0x0020 -#define EHEA_SWQE_PURGE 0x0010 - -/* sizeof(struct ehea_swqe) less the union */ -#define SWQE_HEADER_SIZE 32 - -struct ehea_swqe { - u64 wr_id; - u16 tx_control; - u16 vlan_tag; - u8 reserved1; - u8 ip_start; - u8 ip_end; - u8 immediate_data_length; - u8 tcp_offset; - u8 reserved2; - u16 reserved2b; - u8 wrap_tag; - u8 descriptors; /* number of valid descriptors in WQE */ - u16 reserved3; - u16 reserved4; - u16 mss; - u32 reserved5; - union { - /* Send WQE Format 1 */ - struct { - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES]; - } no_immediate_data; - - /* Send WQE Format 2 */ - struct { - struct ehea_vsgentry sg_entry; - /* 0x30 */ - u8 immediate_data[SWQE2_MAX_IMM]; - /* 0xd0 */ - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES-1]; - } immdata_desc __packed; - - /* Send WQE Format 3 */ - struct { - u8 immediate_data[SWQE3_MAX_IMM]; - } immdata_nodesc; - } u; -}; - -struct ehea_rwqe { - u64 wr_id; /* work request ID */ - u8 reserved1[5]; - u8 data_segments; - u16 reserved2; - u64 reserved3; - u64 reserved4; - struct ehea_vsgentry sg_list[EHEA_MAX_WQE_SG_ENTRIES]; -}; - -#define EHEA_CQE_VLAN_TAG_XTRACT 0x0400 - -#define EHEA_CQE_TYPE_RQ 0x60 -#define EHEA_CQE_STAT_ERR_MASK 0x700F -#define EHEA_CQE_STAT_FAT_ERR_MASK 0xF -#define EHEA_CQE_BLIND_CKSUM 0x8000 -#define EHEA_CQE_STAT_ERR_TCP 0x4000 -#define EHEA_CQE_STAT_ERR_IP 0x2000 -#define EHEA_CQE_STAT_ERR_CRC 0x1000 - -/* Defines which bad send cqe stati lead to a port reset */ -#define EHEA_CQE_STAT_RESET_MASK 0x0002 - -struct ehea_cqe { - u64 wr_id; /* work request ID from WQE */ - u8 type; - u8 valid; - u16 status; - u16 reserved1; - u16 num_bytes_transfered; - u16 vlan_tag; - u16 inet_checksum_value; - u8 reserved2; - u8 header_length; - u16 reserved3; - u16 page_offset; - u16 wqe_count; - u32 qp_token; - u32 timestamp; - u32 reserved4; - u64 reserved5[3]; -}; - -#define EHEA_EQE_VALID EHEA_BMASK_IBM(0, 0) -#define EHEA_EQE_IS_CQE EHEA_BMASK_IBM(1, 1) -#define EHEA_EQE_IDENTIFIER EHEA_BMASK_IBM(2, 7) -#define EHEA_EQE_QP_CQ_NUMBER EHEA_BMASK_IBM(8, 31) -#define EHEA_EQE_QP_TOKEN EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_CQ_TOKEN EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_KEY EHEA_BMASK_IBM(32, 63) -#define EHEA_EQE_PORT_NUMBER EHEA_BMASK_IBM(56, 63) -#define EHEA_EQE_EQ_NUMBER EHEA_BMASK_IBM(48, 63) -#define EHEA_EQE_SM_ID EHEA_BMASK_IBM(48, 63) -#define EHEA_EQE_SM_MECH_NUMBER EHEA_BMASK_IBM(48, 55) -#define EHEA_EQE_SM_PORT_NUMBER EHEA_BMASK_IBM(56, 63) - -#define EHEA_AER_RESTYPE_QP 0x8 -#define EHEA_AER_RESTYPE_CQ 0x4 -#define EHEA_AER_RESTYPE_EQ 0x3 - -/* Defines which affiliated errors lead to a port reset */ -#define EHEA_AER_RESET_MASK 0xFFFFFFFFFEFFFFFFULL -#define EHEA_AERR_RESET_MASK 0xFFFFFFFFFFFFFFFFULL - -struct ehea_eqe { - u64 entry; -}; - -#define ERROR_DATA_LENGTH EHEA_BMASK_IBM(52, 63) -#define ERROR_DATA_TYPE EHEA_BMASK_IBM(0, 7) - -static inline void *hw_qeit_calc(struct hw_queue *queue, u64 q_offset) -{ - struct ehea_page *current_page; - - if (q_offset >= queue->queue_length) - q_offset -= queue->queue_length; - current_page = (queue->queue_pages)[q_offset >> EHEA_PAGESHIFT]; - return ¤t_page->entries[q_offset & (EHEA_PAGESIZE - 1)]; -} - -static inline void *hw_qeit_get(struct hw_queue *queue) -{ - return hw_qeit_calc(queue, queue->current_q_offset); -} - -static inline void hw_qeit_inc(struct hw_queue *queue) -{ - queue->current_q_offset += queue->qe_size; - if (queue->current_q_offset >= queue->queue_length) { - queue->current_q_offset = 0; - /* toggle the valid flag */ - queue->toggle_state = (~queue->toggle_state) & 1; - } -} - -static inline void *hw_qeit_get_inc(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - hw_qeit_inc(queue); - return retvalue; -} - -static inline void *hw_qeit_get_inc_valid(struct hw_queue *queue) -{ - struct ehea_cqe *retvalue = hw_qeit_get(queue); - u8 valid = retvalue->valid; - void *pref; - - if ((valid >> 7) == (queue->toggle_state & 1)) { - /* this is a good one */ - hw_qeit_inc(queue); - pref = hw_qeit_calc(queue, queue->current_q_offset); - prefetch(pref); - prefetch(pref + 128); - } else - retvalue = NULL; - return retvalue; -} - -static inline void *hw_qeit_get_valid(struct hw_queue *queue) -{ - struct ehea_cqe *retvalue = hw_qeit_get(queue); - void *pref; - u8 valid; - - pref = hw_qeit_calc(queue, queue->current_q_offset); - prefetch(pref); - prefetch(pref + 128); - prefetch(pref + 256); - valid = retvalue->valid; - if (!((valid >> 7) == (queue->toggle_state & 1))) - retvalue = NULL; - return retvalue; -} - -static inline void *hw_qeit_reset(struct hw_queue *queue) -{ - queue->current_q_offset = 0; - return hw_qeit_get(queue); -} - -static inline void *hw_qeit_eq_get_inc(struct hw_queue *queue) -{ - u64 last_entry_in_q = queue->queue_length - queue->qe_size; - void *retvalue; - - retvalue = hw_qeit_get(queue); - queue->current_q_offset += queue->qe_size; - if (queue->current_q_offset > last_entry_in_q) { - queue->current_q_offset = 0; - queue->toggle_state = (~queue->toggle_state) & 1; - } - return retvalue; -} - -static inline void *hw_eqit_eq_get_inc_valid(struct hw_queue *queue) -{ - void *retvalue = hw_qeit_get(queue); - u32 qe = *(u8 *)retvalue; - if ((qe >> 7) == (queue->toggle_state & 1)) - hw_qeit_eq_get_inc(queue); - else - retvalue = NULL; - return retvalue; -} - -static inline struct ehea_rwqe *ehea_get_next_rwqe(struct ehea_qp *qp, - int rq_nr) -{ - struct hw_queue *queue; - - if (rq_nr == 1) - queue = &qp->hw_rqueue1; - else if (rq_nr == 2) - queue = &qp->hw_rqueue2; - else - queue = &qp->hw_rqueue3; - - return hw_qeit_get_inc(queue); -} - -static inline struct ehea_swqe *ehea_get_swqe(struct ehea_qp *my_qp, - int *wqe_index) -{ - struct hw_queue *queue = &my_qp->hw_squeue; - struct ehea_swqe *wqe_p; - - *wqe_index = (queue->current_q_offset) >> (7 + EHEA_SG_SQ); - wqe_p = hw_qeit_get_inc(&my_qp->hw_squeue); - - return wqe_p; -} - -static inline void ehea_post_swqe(struct ehea_qp *my_qp, struct ehea_swqe *swqe) -{ - iosync(); - ehea_update_sqa(my_qp, 1); -} - -static inline struct ehea_cqe *ehea_poll_rq1(struct ehea_qp *qp, int *wqe_index) -{ - struct hw_queue *queue = &qp->hw_rqueue1; - - *wqe_index = (queue->current_q_offset) >> (7 + EHEA_SG_RQ1); - return hw_qeit_get_valid(queue); -} - -static inline void ehea_inc_cq(struct ehea_cq *cq) -{ - hw_qeit_inc(&cq->hw_queue); -} - -static inline void ehea_inc_rq1(struct ehea_qp *qp) -{ - hw_qeit_inc(&qp->hw_rqueue1); -} - -static inline struct ehea_cqe *ehea_poll_cq(struct ehea_cq *my_cq) -{ - return hw_qeit_get_valid(&my_cq->hw_queue); -} - -#define EHEA_CQ_REGISTER_ORIG 0 -#define EHEA_EQ_REGISTER_ORIG 0 - -enum ehea_eq_type { - EHEA_EQ = 0, /* event queue */ - EHEA_NEQ /* notification event queue */ -}; - -struct ehea_eq *ehea_create_eq(struct ehea_adapter *adapter, - enum ehea_eq_type type, - const u32 length, const u8 eqe_gen); - -int ehea_destroy_eq(struct ehea_eq *eq); - -struct ehea_eqe *ehea_poll_eq(struct ehea_eq *eq); - -struct ehea_cq *ehea_create_cq(struct ehea_adapter *adapter, int cqe, - u64 eq_handle, u32 cq_token); - -int ehea_destroy_cq(struct ehea_cq *cq); - -struct ehea_qp *ehea_create_qp(struct ehea_adapter *adapter, u32 pd, - struct ehea_qp_init_attr *init_attr); - -int ehea_destroy_qp(struct ehea_qp *qp); - -int ehea_reg_kernel_mr(struct ehea_adapter *adapter, struct ehea_mr *mr); - -int ehea_gen_smr(struct ehea_adapter *adapter, struct ehea_mr *old_mr, - struct ehea_mr *shared_mr); - -int ehea_rem_mr(struct ehea_mr *mr); - -u64 ehea_error_data(struct ehea_adapter *adapter, u64 res_handle, - u64 *aer, u64 *aerr); - -int ehea_add_sect_bmap(unsigned long pfn, unsigned long nr_pages); -int ehea_rem_sect_bmap(unsigned long pfn, unsigned long nr_pages); -int ehea_create_busmap(void); -void ehea_destroy_busmap(void); -u64 ehea_map_vaddr(void *caddr); - -#endif /* __EHEA_QMR_H__ */ diff --git a/drivers/net/ethernet/intel/ice/ice_gnss.c b/drivers/net/ethernet/intel/ice/ice_gnss.c index 8fd954f1ebd6..7d21c3417b0b 100644 --- a/drivers/net/ethernet/intel/ice/ice_gnss.c +++ b/drivers/net/ethernet/intel/ice/ice_gnss.c @@ -2,6 +2,7 @@ /* Copyright (C) 2021-2022, Intel Corporation. */ #include "ice.h" +#include <linux/slab.h> #include "ice_lib.h" /** @@ -124,7 +125,7 @@ static void ice_gnss_read(struct kthread_work *work) data_len = min_t(typeof(data_len), data_len, PAGE_SIZE); - buf = (char *)get_zeroed_page(GFP_KERNEL); + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto requeue; @@ -151,7 +152,7 @@ static void ice_gnss_read(struct kthread_work *work) count, i); delay = ICE_GNSS_TIMER_DELAY_TIME; free_buf: - free_page((unsigned long)buf); + kfree(buf); requeue: kthread_queue_delayed_work(gnss->kworker, &gnss->read_work, delay); if (err) diff --git a/drivers/net/ethernet/intel/igb/e1000_82575.h b/drivers/net/ethernet/intel/igb/e1000_82575.h index 63ec253ac788..9e696d55e512 100644 --- a/drivers/net/ethernet/intel/igb/e1000_82575.h +++ b/drivers/net/ethernet/intel/igb/e1000_82575.h @@ -87,6 +87,27 @@ union e1000_adv_rx_desc { } wb; /* writeback */ }; +#define E1000_RSS_TYPE_NO_HASH 0 +#define E1000_RSS_TYPE_HASH_TCP_IPV4 1 +#define E1000_RSS_TYPE_HASH_IPV4 2 +#define E1000_RSS_TYPE_HASH_TCP_IPV6 3 +#define E1000_RSS_TYPE_HASH_IPV6_EX 4 +#define E1000_RSS_TYPE_HASH_IPV6 5 +#define E1000_RSS_TYPE_HASH_TCP_IPV6_EX 6 +#define E1000_RSS_TYPE_HASH_UDP_IPV4 7 +#define E1000_RSS_TYPE_HASH_UDP_IPV6 8 +#define E1000_RSS_TYPE_HASH_UDP_IPV6_EX 9 + +#define E1000_RSS_TYPE_MASK GENMASK(3, 0) + +#define E1000_RSS_L4_TYPES_MASK \ + (BIT(E1000_RSS_TYPE_HASH_TCP_IPV4) | \ + BIT(E1000_RSS_TYPE_HASH_TCP_IPV6) | \ + BIT(E1000_RSS_TYPE_HASH_TCP_IPV6_EX) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV4) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV6) | \ + BIT(E1000_RSS_TYPE_HASH_UDP_IPV6_EX)) + #define E1000_RXDADV_HDRBUFLEN_MASK 0x7FE0 #define E1000_RXDADV_HDRBUFLEN_SHIFT 5 #define E1000_RXDADV_STAT_TS 0x10000 /* Pkt was time stamped */ diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h index 0fff1df81b7b..8c9b02058cec 100644 --- a/drivers/net/ethernet/intel/igb/igb.h +++ b/drivers/net/ethernet/intel/igb/igb.h @@ -495,6 +495,7 @@ struct hwmon_buff { #define IGB_N_PEROUT 2 #define IGB_N_SDP 4 #define IGB_RETA_SIZE 128 +#define IGB_RSS_KEY_SIZE 40 enum igb_filter_match_flags { IGB_FILTER_FLAG_ETHER_TYPE = 0x1, @@ -655,6 +656,7 @@ struct igb_adapter { struct i2c_client *i2c_client; u32 rss_indir_tbl_init; u8 rss_indir_tbl[IGB_RETA_SIZE]; + u8 rss_key[IGB_RSS_KEY_SIZE]; unsigned long link_check_timeout; int copper_tries; @@ -735,6 +737,7 @@ void igb_down(struct igb_adapter *); void igb_reinit_locked(struct igb_adapter *); void igb_reset(struct igb_adapter *); int igb_reinit_queues(struct igb_adapter *); +void igb_write_rss_key(struct igb_adapter *adapter); void igb_write_rss_indir_tbl(struct igb_adapter *); int igb_set_spd_dplx(struct igb_adapter *, u32, u8); int igb_setup_tx_resources(struct igb_ring *); diff --git a/drivers/net/ethernet/intel/igb/igb_ethtool.c b/drivers/net/ethernet/intel/igb/igb_ethtool.c index f7938c1da835..65014a54a6d1 100644 --- a/drivers/net/ethernet/intel/igb/igb_ethtool.c +++ b/drivers/net/ethernet/intel/igb/igb_ethtool.c @@ -3019,6 +3019,27 @@ static int igb_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd) return ret; } +/** + * igb_write_rss_key - Program the RSS key into device registers + * @adapter: board private structure + * + * Write the RSS key stored in adapter->rss_key to the E1000 hardware registers. + * Each 32-bit chunk of the key is read using get_unaligned_le32() and written + * to the appropriate register. + */ +void igb_write_rss_key(struct igb_adapter *adapter) +{ + struct e1000_hw *hw = &adapter->hw; + + ASSERT_RTNL(); + + for (int i = 0; i < IGB_RSS_KEY_SIZE / 4; i++) { + u32 val = get_unaligned_le32(&adapter->rss_key[i * 4]); + + wr32(E1000_RSSRK(i), val); + } +} + static int igb_get_eee(struct net_device *netdev, struct ethtool_keee *edata) { struct igb_adapter *adapter = netdev_priv(netdev); @@ -3276,10 +3297,12 @@ static int igb_get_rxfh(struct net_device *netdev, int i; rxfh->hfunc = ETH_RSS_HASH_TOP; - if (!rxfh->indir) - return 0; - for (i = 0; i < IGB_RETA_SIZE; i++) - rxfh->indir[i] = adapter->rss_indir_tbl[i]; + if (rxfh->indir) + for (i = 0; i < IGB_RETA_SIZE; i++) + rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->key) + memcpy(rxfh->key, adapter->rss_key, sizeof(adapter->rss_key)); return 0; } @@ -3319,6 +3342,11 @@ void igb_write_rss_indir_tbl(struct igb_adapter *adapter) } } +static u32 igb_get_rxfh_key_size(struct net_device *netdev) +{ + return IGB_RSS_KEY_SIZE; +} + static int igb_set_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh, struct netlink_ext_ack *extack) @@ -3329,35 +3357,39 @@ static int igb_set_rxfh(struct net_device *netdev, u32 num_queues; /* We do not allow change in unsupported parameters */ - if (rxfh->key || - (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && - rxfh->hfunc != ETH_RSS_HASH_TOP)) + if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && + rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; - if (!rxfh->indir) - return 0; - num_queues = adapter->rss_queues; + if (rxfh->indir) { + num_queues = adapter->rss_queues; - switch (hw->mac.type) { - case e1000_82576: - /* 82576 supports 2 RSS queues for SR-IOV */ - if (adapter->vfs_allocated_count) - num_queues = 2; - break; - default: - break; - } + switch (hw->mac.type) { + case e1000_82576: + /* 82576 supports 2 RSS queues for SR-IOV */ + if (adapter->vfs_allocated_count) + num_queues = 2; + break; + default: + break; + } + + /* Verify user input. */ + for (i = 0; i < IGB_RETA_SIZE; i++) + if (rxfh->indir[i] >= num_queues) + return -EINVAL; - /* Verify user input. */ - for (i = 0; i < IGB_RETA_SIZE; i++) - if (rxfh->indir[i] >= num_queues) - return -EINVAL; + for (i = 0; i < IGB_RETA_SIZE; i++) + adapter->rss_indir_tbl[i] = rxfh->indir[i]; - for (i = 0; i < IGB_RETA_SIZE; i++) - adapter->rss_indir_tbl[i] = rxfh->indir[i]; + igb_write_rss_indir_tbl(adapter); + } - igb_write_rss_indir_tbl(adapter); + if (rxfh->key) { + memcpy(adapter->rss_key, rxfh->key, sizeof(adapter->rss_key)); + igb_write_rss_key(adapter); + } return 0; } @@ -3483,6 +3515,7 @@ static const struct ethtool_ops igb_ethtool_ops = { .get_module_eeprom = igb_get_module_eeprom, .get_rxfh_indir_size = igb_get_rxfh_indir_size, .get_rxfh = igb_get_rxfh, + .get_rxfh_key_size = igb_get_rxfh_key_size, .set_rxfh = igb_set_rxfh, .get_rxfh_fields = igb_get_rxfh_fields, .set_rxfh_fields = igb_set_rxfh_fields, diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c index a1e89a375744..d4a897a8c82c 100644 --- a/drivers/net/ethernet/intel/igb/igb_main.c +++ b/drivers/net/ethernet/intel/igb/igb_main.c @@ -4048,6 +4048,9 @@ static int igb_sw_init(struct igb_adapter *adapter) pci_read_config_word(pdev, PCI_COMMAND, &hw->bus.pci_cmd_word); + /* init RSS key */ + netdev_rss_key_fill(adapter->rss_key, sizeof(adapter->rss_key)); + /* set default ring sizes */ adapter->tx_ring_count = IGB_DEFAULT_TXD; adapter->rx_ring_count = IGB_DEFAULT_RXD; @@ -4522,11 +4525,8 @@ static void igb_setup_mrqc(struct igb_adapter *adapter) struct e1000_hw *hw = &adapter->hw; u32 mrqc, rxcsum; u32 j, num_rx_queues; - u32 rss_key[10]; - netdev_rss_key_fill(rss_key, sizeof(rss_key)); - for (j = 0; j < 10; j++) - wr32(E1000_RSSRK(j), rss_key[j]); + igb_write_rss_key(adapter); num_rx_queues = adapter->rss_queues; @@ -8820,10 +8820,19 @@ static inline void igb_rx_hash(struct igb_ring *ring, union e1000_adv_rx_desc *rx_desc, struct sk_buff *skb) { - if (ring->netdev->features & NETIF_F_RXHASH) - skb_set_hash(skb, - le32_to_cpu(rx_desc->wb.lower.hi_dword.rss), - PKT_HASH_TYPE_L3); + u16 rss_type; + + if (!(ring->netdev->features & NETIF_F_RXHASH)) + return; + + rss_type = le16_to_cpu(rx_desc->wb.lower.lo_dword.pkt_info) & + E1000_RSS_TYPE_MASK; + if (!rss_type) + return; + + skb_set_hash(skb, le32_to_cpu(rx_desc->wb.lower.hi_dword.rss), + (E1000_RSS_L4_TYPES_MASK & BIT(rss_type)) ? + PKT_HASH_TYPE_L4 : PKT_HASH_TYPE_L3); } /** diff --git a/drivers/net/ethernet/intel/igc/igc.h b/drivers/net/ethernet/intel/igc/igc.h index 46d625b15f44..17f213cc93e4 100644 --- a/drivers/net/ethernet/intel/igc/igc.h +++ b/drivers/net/ethernet/intel/igc/igc.h @@ -30,6 +30,7 @@ void igc_ethtool_set_ops(struct net_device *); #define MAX_ETYPE_FILTER 8 #define IGC_RETA_SIZE 128 +#define IGC_RSS_KEY_SIZE 40 /* SDP support */ #define IGC_N_EXTTS 2 @@ -302,6 +303,7 @@ struct igc_adapter { unsigned int nfc_rule_count; u8 rss_indir_tbl[IGC_RETA_SIZE]; + u8 rss_key[IGC_RSS_KEY_SIZE]; unsigned long link_check_timeout; struct igc_info ei; @@ -361,6 +363,7 @@ unsigned int igc_get_max_rss_queues(struct igc_adapter *adapter); void igc_set_flag_queue_pairs(struct igc_adapter *adapter, const u32 max_rss_queues); int igc_reinit_queues(struct igc_adapter *adapter); +void igc_write_rss_key(struct igc_adapter *adapter); void igc_write_rss_indir_tbl(struct igc_adapter *adapter); bool igc_has_link(struct igc_adapter *adapter); void igc_reset(struct igc_adapter *adapter); diff --git a/drivers/net/ethernet/intel/igc/igc_base.c b/drivers/net/ethernet/intel/igc/igc_base.c index 1613b562d17c..ab9120a3127f 100644 --- a/drivers/net/ethernet/intel/igc/igc_base.c +++ b/drivers/net/ethernet/intel/igc/igc_base.c @@ -114,11 +114,35 @@ static s32 igc_setup_copper_link_base(struct igc_hw *hw) u32 ctrl; ctrl = rd32(IGC_CTRL); - ctrl |= IGC_CTRL_SLU; - ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX); - wr32(IGC_CTRL, ctrl); - - ret_val = igc_setup_copper_link(hw); + ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX | + IGC_CTRL_SPEED_MASK | IGC_CTRL_FD); + + if (hw->mac.autoneg_enabled) { + ctrl |= IGC_CTRL_SLU; + wr32(IGC_CTRL, ctrl); + ret_val = igc_setup_copper_link(hw); + } else { + ctrl |= IGC_CTRL_SLU | IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX; + + switch (hw->mac.forced_speed_duplex) { + case IGC_FORCED_10H: + ctrl |= IGC_CTRL_SPEED_10; + break; + case IGC_FORCED_10F: + ctrl |= IGC_CTRL_SPEED_10 | IGC_CTRL_FD; + break; + case IGC_FORCED_100H: + ctrl |= IGC_CTRL_SPEED_100; + break; + case IGC_FORCED_100F: + ctrl |= IGC_CTRL_SPEED_100 | IGC_CTRL_FD; + break; + default: + return -IGC_ERR_CONFIG; + } + wr32(IGC_CTRL, ctrl); + ret_val = igc_setup_copper_link(hw); + } return ret_val; } @@ -443,6 +467,7 @@ static const struct igc_phy_operations igc_phy_ops_base = { .reset = igc_phy_hw_reset, .read_reg = igc_read_phy_reg_gpy, .write_reg = igc_write_phy_reg_gpy, + .force_speed_duplex = igc_force_speed_duplex, }; const struct igc_info igc_base_info = { diff --git a/drivers/net/ethernet/intel/igc/igc_defines.h b/drivers/net/ethernet/intel/igc/igc_defines.h index 9482ab11f050..3f504751c2d9 100644 --- a/drivers/net/ethernet/intel/igc/igc_defines.h +++ b/drivers/net/ethernet/intel/igc/igc_defines.h @@ -129,10 +129,13 @@ #define IGC_ERR_SWFW_SYNC 13 /* Device Control */ +#define IGC_CTRL_FD BIT(0) /* Full Duplex */ #define IGC_CTRL_RST 0x04000000 /* Global reset */ - #define IGC_CTRL_PHY_RST 0x80000000 /* PHY Reset */ #define IGC_CTRL_SLU 0x00000040 /* Set link up (Force Link) */ +#define IGC_CTRL_SPEED_MASK GENMASK(10, 8) +#define IGC_CTRL_SPEED_10 FIELD_PREP(IGC_CTRL_SPEED_MASK, 0) +#define IGC_CTRL_SPEED_100 FIELD_PREP(IGC_CTRL_SPEED_MASK, 1) #define IGC_CTRL_FRCSPD 0x00000800 /* Force Speed */ #define IGC_CTRL_FRCDPX 0x00001000 /* Force Duplex */ #define IGC_CTRL_VME 0x40000000 /* IEEE VLAN mode enable */ @@ -673,6 +676,10 @@ #define IGC_GEN_POLL_TIMEOUT 1920 /* PHY Control Register */ +#define MII_CR_SPEED_MASK (BIT(6) | BIT(13)) +#define MII_CR_SPEED_10 0x0000 /* SSM=0, SSL=0: 10 Mb/s */ +#define MII_CR_SPEED_100 BIT(13) /* SSM=0, SSL=1: 100 Mb/s */ +#define MII_CR_DUPLEX_EN BIT(8) /* 0 = Half Duplex, 1 = Full Duplex */ #define MII_CR_RESTART_AUTO_NEG 0x0200 /* Restart auto negotiation */ #define MII_CR_POWER_DOWN 0x0800 /* Power down */ #define MII_CR_AUTO_NEG_EN 0x1000 /* Auto Neg Enable */ diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index 0122009bedd0..89fe2788a565 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -1460,6 +1460,26 @@ static int igc_ethtool_set_rxnfc(struct net_device *dev, } } +/** + * igc_write_rss_key - Program the RSS key into device registers + * @adapter: board private structure + * + * Write the RSS key stored in adapter->rss_key to the IGC_RSSRK registers. + * Each 32-bit chunk of the key is read using get_unaligned_le32() and written + * to the appropriate register. + */ +void igc_write_rss_key(struct igc_adapter *adapter) +{ + struct igc_hw *hw = &adapter->hw; + u32 val; + int i; + + for (i = 0; i < IGC_RSS_KEY_SIZE / 4; i++) { + val = get_unaligned_le32(&adapter->rss_key[i * 4]); + wr32(IGC_RSSRK(i), val); + } +} + void igc_write_rss_indir_tbl(struct igc_adapter *adapter) { struct igc_hw *hw = &adapter->hw; @@ -1482,6 +1502,11 @@ void igc_write_rss_indir_tbl(struct igc_adapter *adapter) } } +static u32 igc_ethtool_get_rxfh_key_size(struct net_device *netdev) +{ + return IGC_RSS_KEY_SIZE; +} + static u32 igc_ethtool_get_rxfh_indir_size(struct net_device *netdev) { return IGC_RETA_SIZE; @@ -1494,10 +1519,13 @@ static int igc_ethtool_get_rxfh(struct net_device *netdev, int i; rxfh->hfunc = ETH_RSS_HASH_TOP; - if (!rxfh->indir) - return 0; - for (i = 0; i < IGC_RETA_SIZE; i++) - rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->indir) + for (i = 0; i < IGC_RETA_SIZE; i++) + rxfh->indir[i] = adapter->rss_indir_tbl[i]; + + if (rxfh->key) + memcpy(rxfh->key, adapter->rss_key, sizeof(adapter->rss_key)); return 0; } @@ -1511,24 +1539,28 @@ static int igc_ethtool_set_rxfh(struct net_device *netdev, int i; /* We do not allow change in unsupported parameters */ - if (rxfh->key || - (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && - rxfh->hfunc != ETH_RSS_HASH_TOP)) + if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && + rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; - if (!rxfh->indir) - return 0; - num_queues = adapter->rss_queues; + if (rxfh->indir) { + num_queues = adapter->rss_queues; - /* Verify user input. */ - for (i = 0; i < IGC_RETA_SIZE; i++) - if (rxfh->indir[i] >= num_queues) - return -EINVAL; + /* Verify user input. */ + for (i = 0; i < IGC_RETA_SIZE; i++) + if (rxfh->indir[i] >= num_queues) + return -EINVAL; - for (i = 0; i < IGC_RETA_SIZE; i++) - adapter->rss_indir_tbl[i] = rxfh->indir[i]; + for (i = 0; i < IGC_RETA_SIZE; i++) + adapter->rss_indir_tbl[i] = rxfh->indir[i]; + + igc_write_rss_indir_tbl(adapter); + } - igc_write_rss_indir_tbl(adapter); + if (rxfh->key) { + memcpy(adapter->rss_key, rxfh->key, sizeof(adapter->rss_key)); + igc_write_rss_key(adapter); + } return 0; } @@ -1914,44 +1946,58 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, ethtool_link_ksettings_add_link_mode(cmd, supported, TP); ethtool_link_ksettings_add_link_mode(cmd, advertising, TP); - /* advertising link modes */ - if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Half); - if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Half); - if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 1000baseT_Full); - if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL) - ethtool_link_ksettings_add_link_mode(cmd, advertising, 2500baseT_Full); - /* set autoneg settings */ ethtool_link_ksettings_add_link_mode(cmd, supported, Autoneg); - ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg); + if (hw->mac.autoneg_enabled) { + ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg); + cmd->base.autoneg = AUTONEG_ENABLE; + + /* advertising link modes only apply when autoneg is on */ + if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 10baseT_Half); + if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 10baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 100baseT_Half); + if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 100baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 1000baseT_Full); + if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 2500baseT_Full); + + /* Set pause flow control advertising */ + switch (hw->fc.requested_mode) { + case igc_fc_full: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Pause); + break; + case igc_fc_rx_pause: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Pause); + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Asym_Pause); + break; + case igc_fc_tx_pause: + ethtool_link_ksettings_add_link_mode(cmd, advertising, + Asym_Pause); + break; + default: + break; + } + } else { + cmd->base.autoneg = AUTONEG_DISABLE; + } - /* Set pause flow control settings */ + /* Pause is always supported */ ethtool_link_ksettings_add_link_mode(cmd, supported, Pause); - switch (hw->fc.requested_mode) { - case igc_fc_full: - ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause); - break; - case igc_fc_rx_pause: - ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause); - ethtool_link_ksettings_add_link_mode(cmd, advertising, - Asym_Pause); - break; - case igc_fc_tx_pause: - ethtool_link_ksettings_add_link_mode(cmd, advertising, - Asym_Pause); - break; - default: - break; - } - status = pm_runtime_suspended(&adapter->pdev->dev) ? 0 : rd32(IGC_STATUS); @@ -1983,7 +2029,6 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, cmd->base.duplex = DUPLEX_UNKNOWN; } cmd->base.speed = speed; - cmd->base.autoneg = AUTONEG_ENABLE; /* MDI-X => 2; MDI =>1; Invalid =>0 */ if (hw->phy.media_type == igc_media_type_copper) @@ -2000,37 +2045,50 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev, return 0; } -static int -igc_ethtool_set_link_ksettings(struct net_device *netdev, - const struct ethtool_link_ksettings *cmd) +/** + * igc_handle_autoneg_disabled - Configure forced speed/duplex settings + * @adapter: private driver structure + * @speed: requested speed (must be SPEED_10 or SPEED_100) + * @duplex: requested duplex + * + * Records forced speed/duplex when autoneg is disabled. + * Caller must validate speed before calling this function. + */ +static void igc_handle_autoneg_disabled(struct igc_adapter *adapter, u32 speed, + u8 duplex) { - struct igc_adapter *adapter = netdev_priv(netdev); - struct net_device *dev = adapter->netdev; - struct igc_hw *hw = &adapter->hw; - u16 advertised = 0; + struct igc_mac_info *mac = &adapter->hw.mac; - /* When adapter in resetting mode, autoneg/speed/duplex - * cannot be changed - */ - if (igc_check_reset_block(hw)) { - netdev_err(dev, "Cannot change link characteristics when reset is active\n"); - return -EINVAL; + switch (speed) { + case SPEED_10: + mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ? + IGC_FORCED_10F : IGC_FORCED_10H; + break; + case SPEED_100: + mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ? + IGC_FORCED_100F : IGC_FORCED_100H; + break; + default: + WARN_ONCE(1, "Unsupported speed %u\n", speed); + return; } - /* MDI setting is only allowed when autoneg enabled because - * some hardware doesn't allow MDI setting when speed or - * duplex is forced. - */ - if (cmd->base.eth_tp_mdix_ctrl) { - if (cmd->base.eth_tp_mdix_ctrl != ETH_TP_MDI_AUTO && - cmd->base.autoneg != AUTONEG_ENABLE) { - netdev_err(dev, "Forcing MDI/MDI-X state is not supported when link speed and/or duplex are forced\n"); - return -EINVAL; - } - } + mac->autoneg_enabled = false; +} - while (test_and_set_bit(__IGC_RESETTING, &adapter->state)) - usleep_range(1000, 2000); +/** + * igc_handle_autoneg_enabled - Configure autonegotiation advertisement + * @adapter: private driver structure + * @cmd: ethtool link ksettings from user + * + * Records advertised speeds and flow control settings when autoneg + * is enabled. + */ +static void igc_handle_autoneg_enabled(struct igc_adapter *adapter, + const struct ethtool_link_ksettings *cmd) +{ + struct igc_hw *hw = &adapter->hw; + u16 advertised = 0; if (ethtool_link_ksettings_test_link_mode(cmd, advertising, 2500baseT_Full)) @@ -2056,14 +2114,66 @@ igc_ethtool_set_link_ksettings(struct net_device *netdev, 10baseT_Half)) advertised |= ADVERTISE_10_HALF; - if (cmd->base.autoneg == AUTONEG_ENABLE) { - hw->phy.autoneg_advertised = advertised; - if (adapter->fc_autoneg) - hw->fc.requested_mode = igc_fc_default; - } else { - netdev_info(dev, "Force mode currently not supported\n"); + hw->mac.autoneg_enabled = true; + hw->phy.autoneg_advertised = advertised; + if (adapter->fc_autoneg) + hw->fc.requested_mode = igc_fc_default; +} + +static int +igc_ethtool_set_link_ksettings(struct net_device *netdev, + const struct ethtool_link_ksettings *cmd) +{ + struct igc_adapter *adapter = netdev_priv(netdev); + struct net_device *dev = adapter->netdev; + struct igc_hw *hw = &adapter->hw; + + /* When adapter in resetting mode, autoneg/speed/duplex + * cannot be changed + */ + if (igc_check_reset_block(hw)) { + netdev_err(dev, "Cannot change link characteristics when reset is active\n"); + return -EINVAL; + } + + if (cmd->base.autoneg != AUTONEG_ENABLE && + cmd->base.autoneg != AUTONEG_DISABLE) { + netdev_info(dev, "Unsupported autoneg setting\n"); + return -EINVAL; + } + + /* MDI setting is only allowed when autoneg enabled because + * some hardware doesn't allow MDI setting when speed or + * duplex is forced. + */ + if (cmd->base.eth_tp_mdix_ctrl) { + if (cmd->base.eth_tp_mdix_ctrl != ETH_TP_MDI_AUTO && + cmd->base.autoneg != AUTONEG_ENABLE) { + netdev_err(dev, "Forcing MDI/MDI-X state is not supported when link speed and/or duplex are forced\n"); + return -EINVAL; + } } + if (cmd->base.autoneg == AUTONEG_DISABLE) { + if (cmd->base.speed != SPEED_10 && cmd->base.speed != SPEED_100) { + netdev_info(dev, "Unsupported speed for forced link\n"); + return -EINVAL; + } + if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL) { + netdev_info(dev, "Duplex must be half or full for forced link\n"); + return -EINVAL; + } + } + + while (test_and_set_bit(__IGC_RESETTING, &adapter->state)) + usleep_range(1000, 2000); + + if (cmd->base.autoneg == AUTONEG_ENABLE) + igc_handle_autoneg_enabled(adapter, cmd); + else + igc_handle_autoneg_disabled(adapter, cmd->base.speed, + cmd->base.duplex); + /* MDI-X => 2; MDI => 1; Auto => 3 */ if (cmd->base.eth_tp_mdix_ctrl) { /* fix up the value for auto (3 => 0) as zero is mapped @@ -2175,6 +2285,7 @@ static const struct ethtool_ops igc_ethtool_ops = { .get_rxnfc = igc_ethtool_get_rxnfc, .set_rxnfc = igc_ethtool_set_rxnfc, .get_rx_ring_count = igc_ethtool_get_rx_ring_count, + .get_rxfh_key_size = igc_ethtool_get_rxfh_key_size, .get_rxfh_indir_size = igc_ethtool_get_rxfh_indir_size, .get_rxfh = igc_ethtool_get_rxfh, .set_rxfh = igc_ethtool_set_rxfh, diff --git a/drivers/net/ethernet/intel/igc/igc_hw.h b/drivers/net/ethernet/intel/igc/igc_hw.h index be8a49a86d09..62aaee55668a 100644 --- a/drivers/net/ethernet/intel/igc/igc_hw.h +++ b/drivers/net/ethernet/intel/igc/igc_hw.h @@ -73,6 +73,13 @@ struct igc_info { extern const struct igc_info igc_base_info; +enum igc_forced_speed_duplex { + IGC_FORCED_10H, + IGC_FORCED_10F, + IGC_FORCED_100H, + IGC_FORCED_100F, +}; + struct igc_mac_info { struct igc_mac_operations ops; @@ -92,8 +99,9 @@ struct igc_mac_info { bool asf_firmware_present; bool arc_subsystem_valid; - bool autoneg_failed; bool get_link_status; + bool autoneg_enabled; + enum igc_forced_speed_duplex forced_speed_duplex; }; struct igc_nvm_operations { diff --git a/drivers/net/ethernet/intel/igc/igc_mac.c b/drivers/net/ethernet/intel/igc/igc_mac.c index 7ac6637f8db7..d6f3f6618469 100644 --- a/drivers/net/ethernet/intel/igc/igc_mac.c +++ b/drivers/net/ethernet/intel/igc/igc_mac.c @@ -438,26 +438,23 @@ void igc_config_collision_dist(struct igc_hw *hw) * Checks the status of auto-negotiation after link up to ensure that the * speed and duplex were not forced. If the link needed to be forced, then * flow control needs to be forced also. If auto-negotiation is enabled - * and did not fail, then we configure flow control based on our link - * partner. + * then we configure flow control based on our link partner. */ s32 igc_config_fc_after_link_up(struct igc_hw *hw) { u16 mii_status_reg, mii_nway_adv_reg, mii_nway_lp_ability_reg; - struct igc_mac_info *mac = &hw->mac; u16 speed, duplex; s32 ret_val = 0; - /* Check for the case where we have fiber media and auto-neg failed - * so we had to force link. In this case, we need to force the - * configuration of the MAC to match the "fc" parameter. + /* Without autoneg, flow control capability is not exchanged with the + * link partner. IEEE 802.3 prohibits flow control in half-duplex mode. */ - if (mac->autoneg_failed) - ret_val = igc_force_mac_fc(hw); + if (!hw->mac.autoneg_enabled) { + if (hw->mac.forced_speed_duplex == IGC_FORCED_10H || + hw->mac.forced_speed_duplex == IGC_FORCED_100H) + hw->fc.current_mode = igc_fc_none; - if (ret_val) { - hw_dbg("Error forcing flow control settings\n"); - goto out; + goto force_fc; } /* In auto-neg, we need to check and see if Auto-Neg has completed, @@ -472,15 +469,15 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &mii_status_reg); if (ret_val) - goto out; + return ret_val; ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &mii_status_reg); if (ret_val) - goto out; + return ret_val; if (!(mii_status_reg & MII_SR_AUTONEG_COMPLETE)) { hw_dbg("Copper PHY and Auto Neg has not completed.\n"); - goto out; + return ret_val; } /* The AutoNeg process has completed, so we now need to @@ -492,11 +489,11 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->phy.ops.read_reg(hw, PHY_AUTONEG_ADV, &mii_nway_adv_reg); if (ret_val) - goto out; + return ret_val; ret_val = hw->phy.ops.read_reg(hw, PHY_LP_ABILITY, &mii_nway_lp_ability_reg); if (ret_val) - goto out; + return ret_val; /* Two bits in the Auto Negotiation Advertisement Register * (Address 4) and two bits in the Auto Negotiation Base * Page Ability Register (Address 5) determine flow control @@ -612,7 +609,7 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) ret_val = hw->mac.ops.get_speed_and_duplex(hw, &speed, &duplex); if (ret_val) { hw_dbg("Error getting link speed and duplex\n"); - goto out; + return ret_val; } if (duplex == HALF_DUPLEX) @@ -621,13 +618,13 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw) /* Now we call a subroutine to actually force the MAC * controller to use the correct flow control settings. */ +force_fc: ret_val = igc_force_mac_fc(hw); if (ret_val) { hw_dbg("Error forcing flow control settings\n"); - goto out; + return ret_val; } -out: return ret_val; } diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 2c9e2dfd8499..e6e9441fc3d4 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -785,11 +785,8 @@ static void igc_setup_mrqc(struct igc_adapter *adapter) struct igc_hw *hw = &adapter->hw; u32 j, num_rx_queues; u32 mrqc, rxcsum; - u32 rss_key[10]; - netdev_rss_key_fill(rss_key, sizeof(rss_key)); - for (j = 0; j < 10; j++) - wr32(IGC_RSSRK(j), rss_key[j]); + igc_write_rss_key(adapter); num_rx_queues = adapter->rss_queues; @@ -5048,6 +5045,9 @@ static int igc_sw_init(struct igc_adapter *adapter) pci_read_config_word(pdev, PCI_COMMAND, &hw->bus.pci_cmd_word); + /* init RSS key */ + netdev_rss_key_fill(adapter->rss_key, sizeof(adapter->rss_key)); + /* set default ring sizes */ adapter->tx_ring_count = IGC_DEFAULT_TXD; adapter->rx_ring_count = IGC_DEFAULT_RXD; @@ -7298,7 +7298,7 @@ static int igc_probe(struct pci_dev *pdev, /* Initialize link properties that are user-changeable */ adapter->fc_autoneg = true; hw->phy.autoneg_advertised = 0xaf; - + hw->mac.autoneg_enabled = true; hw->fc.requested_mode = igc_fc_default; hw->fc.current_mode = igc_fc_default; diff --git a/drivers/net/ethernet/intel/igc/igc_phy.c b/drivers/net/ethernet/intel/igc/igc_phy.c index 6c4d204aecfa..4cf737fb3b21 100644 --- a/drivers/net/ethernet/intel/igc/igc_phy.c +++ b/drivers/net/ethernet/intel/igc/igc_phy.c @@ -494,12 +494,20 @@ s32 igc_setup_copper_link(struct igc_hw *hw) s32 ret_val = 0; bool link; - /* Setup autoneg and flow control advertisement and perform - * autonegotiation. - */ - ret_val = igc_copper_link_autoneg(hw); - if (ret_val) - goto out; + if (hw->mac.autoneg_enabled) { + /* Setup autoneg and flow control advertisement and perform + * autonegotiation. + */ + ret_val = igc_copper_link_autoneg(hw); + if (ret_val) + goto out; + } else { + ret_val = hw->phy.ops.force_speed_duplex(hw); + if (ret_val) { + hw_dbg("Error Forcing Speed/Duplex\n"); + goto out; + } + } /* Check link status. Wait up to 100 microseconds for link to become * valid. @@ -778,3 +786,48 @@ u16 igc_read_phy_fw_version(struct igc_hw *hw) return gphy_version; } + +/** + * igc_force_speed_duplex - Force PHY speed and duplex settings + * @hw: pointer to the HW structure + * + * Programs the GPY PHY control register to disable autonegotiation + * and force the speed/duplex indicated by hw->mac.forced_speed_duplex. + */ +s32 igc_force_speed_duplex(struct igc_hw *hw) +{ + struct igc_phy_info *phy = &hw->phy; + u16 phy_ctrl; + s32 ret_val; + + ret_val = phy->ops.read_reg(hw, PHY_CONTROL, &phy_ctrl); + if (ret_val) + return ret_val; + + phy_ctrl &= ~(MII_CR_SPEED_MASK | MII_CR_DUPLEX_EN | + MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG); + + switch (hw->mac.forced_speed_duplex) { + case IGC_FORCED_10H: + phy_ctrl |= MII_CR_SPEED_10; + break; + case IGC_FORCED_10F: + phy_ctrl |= MII_CR_SPEED_10 | MII_CR_DUPLEX_EN; + break; + case IGC_FORCED_100H: + phy_ctrl |= MII_CR_SPEED_100; + break; + case IGC_FORCED_100F: + phy_ctrl |= MII_CR_SPEED_100 | MII_CR_DUPLEX_EN; + break; + default: + return -IGC_ERR_CONFIG; + } + + ret_val = phy->ops.write_reg(hw, PHY_CONTROL, phy_ctrl); + if (ret_val) + return ret_val; + + hw->mac.get_link_status = true; + return 0; +} diff --git a/drivers/net/ethernet/intel/igc/igc_phy.h b/drivers/net/ethernet/intel/igc/igc_phy.h index 832a7e359f18..d37a89174826 100644 --- a/drivers/net/ethernet/intel/igc/igc_phy.h +++ b/drivers/net/ethernet/intel/igc/igc_phy.h @@ -18,5 +18,6 @@ void igc_power_down_phy_copper(struct igc_hw *hw); s32 igc_write_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 data); s32 igc_read_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 *data); u16 igc_read_phy_fw_version(struct igc_hw *hw); +s32 igc_force_speed_duplex(struct igc_hw *hw); #endif diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 744d6585a949..543e566425c1 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -5678,6 +5678,24 @@ static int mvneta_probe(struct platform_device *pdev) "use SW buffer management\n"); mvneta_bm_put(pp->bm_priv); pp->bm_priv = NULL; + } else if (!device_link_add(&pdev->dev, + &pp->bm_priv->pdev->dev, + DL_FLAG_AUTOREMOVE_CONSUMER)) { + /* + * Link guarantees BM resumes before mvneta. + * Without it, BM may not be ready when + * mvneta_bm_port_init() runs on resume, + * causing stale buffer addresses and a crash. + * Fall back to SW management to be safe. + */ + dev_warn(&pdev->dev, + "failed to link to BM, use SW buffer management\n"); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_long, 1 << pp->id); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_short, 1 << pp->id); + mvneta_bm_put(pp->bm_priv); + pp->bm_priv = NULL; } } /* Set RX packet offset correction for platforms, whose diff --git a/drivers/net/ethernet/marvell/mvneta_bm.c b/drivers/net/ethernet/marvell/mvneta_bm.c index 6bb380494919..e0c693c0a910 100644 --- a/drivers/net/ethernet/marvell/mvneta_bm.c +++ b/drivers/net/ethernet/marvell/mvneta_bm.c @@ -129,6 +129,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (!IS_ALIGNED((u32)bm_pool->virt_addr, MVNETA_BM_POOL_PTR_ALIGN)) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; dev_err(&pdev->dev, "BM pool %d is not %d bytes aligned\n", bm_pool->id, MVNETA_BM_POOL_PTR_ALIGN); return -ENOMEM; @@ -139,6 +140,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (err < 0) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; return err; } @@ -477,6 +479,75 @@ static void mvneta_bm_remove(struct platform_device *pdev) clk_disable_unprepare(priv->clk); } +static int mvneta_bm_suspend(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i; + + /* Drain buffers and free pool resources while BM is still clocked */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + struct mvneta_bm_pool *bm_pool = &priv->bm_pools[i]; + int size_bytes; + + if (bm_pool->type == MVNETA_BM_FREE) + continue; + + mvneta_bm_bufs_free(priv, bm_pool, bm_pool->port_map); + if (bm_pool->hwbm_pool.buf_num) + dev_warn(&priv->pdev->dev, + "pool %d: %d buffers not freed\n", + bm_pool->id, bm_pool->hwbm_pool.buf_num); + + mvneta_bm_pool_disable(priv, bm_pool->id); + + if (bm_pool->virt_addr) { + size_bytes = sizeof(u32) * bm_pool->hwbm_pool.size; + dma_free_coherent(&priv->pdev->dev, size_bytes, + bm_pool->virt_addr, + bm_pool->phys_addr); + bm_pool->virt_addr = NULL; + } + /* + * Safe to destroy: device_link guarantees all mvneta ports + * have already suspended, so no hwbm_pool_add() can be in + * progress holding buf_lock. Pairs with mutex_init() in + * mvneta_bm_pool_use() on resume. + */ + mutex_destroy(&bm_pool->hwbm_pool.buf_lock); + bm_pool->type = MVNETA_BM_FREE; + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_STOP_MASK); + clk_disable_unprepare(priv->clk); + return 0; +} + +static int mvneta_bm_resume(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i, err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + /* Reinitialize BM hardware; pools are refilled by mvneta_resume() */ + mvneta_bm_default_set(priv); + + /* Restore pool registers lost during clock gating */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + mvneta_bm_write(priv, MVNETA_BM_POOL_READ_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_WRITE_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i), + priv->bm_pools[i].hwbm_pool.size); + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_START_MASK); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mvneta_bm_pm_ops, mvneta_bm_suspend, mvneta_bm_resume); + static const struct of_device_id mvneta_bm_match[] = { { .compatible = "marvell,armada-380-neta-bm" }, { } @@ -489,6 +560,7 @@ static struct platform_driver mvneta_bm_driver = { .driver = { .name = MVNETA_BM_DRIVER_NAME, .of_match_table = mvneta_bm_match, + .pm = pm_sleep_ptr(&mvneta_bm_pm_ops), }, }; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h index 714e47f68d93..f87cdf1b971d 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h @@ -803,6 +803,7 @@ struct npc_set_pkind { */ u8 var_len_off_mask; /* Mask for length with in offset */ u8 shift_dir; /* shift direction to get length of the header at var_len_off */ + u8 skip_size; /* l2 size to skip */ }; /* NPA mbox message formats */ @@ -1267,6 +1268,7 @@ struct nix_rss_flowkey_cfg { #define NIX_FLOW_KEY_TYPE_IPV4_PROTO BIT(21) #define NIX_FLOW_KEY_TYPE_AH BIT(22) #define NIX_FLOW_KEY_TYPE_ESP BIT(23) +#define NIX_FLOW_KEY_TYPE_ROCEV2 BIT(24) #define NIX_FLOW_KEY_TYPE_L4_DST_ONLY BIT(28) #define NIX_FLOW_KEY_TYPE_L4_SRC_ONLY BIT(29) #define NIX_FLOW_KEY_TYPE_L3_DST_ONLY BIT(30) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/npc.h b/drivers/net/ethernet/marvell/octeontx2/af/npc.h index eaed172f1606..719b3618eeb5 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/npc.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/npc.h @@ -161,10 +161,12 @@ enum npc_kpu_lh_ltype { * Software assigns pkind for each incoming port such as CGX * Ethernet interfaces, LBK interfaces, etc. */ -#define NPC_UNRESERVED_PKIND_COUNT NPC_RX_CPT_HDR_PTP_PKIND +#define NPC_UNRESERVED_PKIND_COUNT NPC_RX_SKIP_SIZE_PKIND enum npc_pkind_type { NPC_RX_LBK_PKIND = 0ULL, + NPC_RX_SKIP_SIZE_PKIND = 46ULL, + NPC_RX_CPT_SKIP_SIZE_PKIND = 50ULL, NPC_RX_CPT_HDR_PTP_PKIND = 54ULL, NPC_RX_CUSTOM_PRE_L2_PKIND = 55ULL, NPC_RX_VLAN_EXDSA_PKIND = 56ULL, diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h index 7f3505ae6860..c5610f242687 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h @@ -1181,7 +1181,7 @@ void rvu_switch_enable_lbk_link(struct rvu *rvu, u16 pcifunc, bool ena); int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, u64 pkind, u8 var_len_off, u8 var_len_off_mask, - u8 shift_dir); + u8 shift_dir, u8 skip_size); int rvu_get_hwvf(struct rvu *rvu, int pcifunc); /* CN10K MCS */ diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 6a0ce2665031..78667a0977c0 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -4305,6 +4305,13 @@ static int set_flowkey_fields(struct nix_rx_flowkey_alg *alg, u32 flow_cfg) keyoff_marker = false; } break; + case NIX_FLOW_KEY_TYPE_ROCEV2: + field->hdr_offset = 5; + field->bytesm1 = 2; /* Destination QP */ + field->ltype_mask = 0xF; + field->lid = NPC_LID_LE; + field->ltype_match = NPC_LT_LE_ROCEV2; + break; } field->ena = 1; @@ -5392,7 +5399,7 @@ void rvu_nix_lf_teardown(struct rvu *rvu, u16 pcifunc, int blkaddr, int nixlf) /* reset HW config done for Switch headers */ rvu_npc_set_parse_mode(rvu, pcifunc, OTX2_PRIV_FLAGS_DEFAULT, - (PKIND_TX | PKIND_RX), 0, 0, 0, 0); + (PKIND_TX | PKIND_RX), 0, 0, 0, 0, 0); /* Disabling CGX and NPC config done for PTP */ if (pfvf->hw_rx_tstamp_en) { diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index c7bc0b3a29b9..08b83de9beb4 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -4194,10 +4194,40 @@ npc_set_var_len_offset_pkind(struct rvu *rvu, u16 pcifunc, u64 pkind, return 0; } +static int npc_set_skip_size_pkind(struct rvu *rvu, u16 pcifunc, u64 pkind, + u8 skip_size) +{ + struct npc_kpu_action0 *act0; + int blkaddr; + u64 val; + + blkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, pcifunc); + if (blkaddr < 0) { + dev_err(rvu->dev, "%s: NPC block not implemented\n", __func__); + return -EINVAL; + } + + val = rvu_read64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind)); + act0 = (struct npc_kpu_action0 *)&val; + act0->ptr_advance = skip_size; + rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind), val); + + /* Update CPT_HR new PKIND */ + val = rvu_read64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind + 4)); + act0 = (struct npc_kpu_action0 *)&val; + act0->ptr_advance = (skip_size + 40); + act0->next_state = NPC_S_KPU1_CPT_HDR; + act0->var_len_offset = (skip_size + 6); + act0->var_len_mask = 0xe0; + act0->var_len_shift = 0x5; + act0->var_len_right = 0x1; + rvu_write64(rvu, blkaddr, NPC_AF_PKINDX_ACTION0(pkind + 4), val); + return 0; +} + int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, u64 pkind, u8 var_len_off, u8 var_len_off_mask, - u8 shift_dir) - + u8 shift_dir, u8 skip_size) { struct rvu_pfvf *pfvf = rvu_get_pfvf(rvu, pcifunc); int blkaddr, nixlf, rc, intf_mode; @@ -4218,6 +4248,12 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, shift_dir); if (rc) return rc; + } else if (pkind >= NPC_RX_SKIP_SIZE_PKIND && + pkind <= NPC_RX_SKIP_SIZE_PKIND + 3) { + rc = npc_set_skip_size_pkind(rvu, pcifunc, pkind, + skip_size); + if (rc) + return rc; } rxpkind = pkind; txpkind = pkind; @@ -4254,7 +4290,8 @@ int rvu_mbox_handler_npc_set_pkind(struct rvu *rvu, struct npc_set_pkind *req, { return rvu_npc_set_parse_mode(rvu, req->hdr.pcifunc, req->mode, req->dir, req->pkind, req->var_len_off, - req->var_len_off_mask, req->shift_dir); + req->var_len_off_mask, req->shift_dir, + req->skip_size); } int rvu_mbox_handler_npc_read_base_steer_rule(struct rvu *rvu, diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c index dbf173196608..8e41431c7f9c 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c @@ -656,6 +656,7 @@ static int cn20k_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, pp_params.nid = NUMA_NO_NODE; pp_params.dev = pfvf->dev; pp_params.dma_dir = DMA_FROM_DEVICE; + pp_params.netdev = pfvf->netdev; pool->page_pool = page_pool_create(&pp_params); if (IS_ERR(pool->page_pool)) { netdev_err(pfvf->netdev, "Creation of page pool failed\n"); diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 3d253132a17f..ca73a94db794 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -1035,7 +1035,6 @@ int otx2_sq_init(struct otx2_nic *pfvf, u16 qidx, u16 sqb_aura) if (qidx > pfvf->hw.xdp_queues) otx2_attach_xsk_buff(pfvf, sq, (qidx - pfvf->hw.xdp_queues)); - chan_offset = qidx % pfvf->hw.tx_chan_cnt; err = pfvf->hw_ops->sq_aq_init(pfvf, qidx, chan_offset, sqb_aura); if (err) { @@ -1517,6 +1516,7 @@ int otx2_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, pp_params.nid = NUMA_NO_NODE; pp_params.dev = pfvf->dev; pp_params.dma_dir = DMA_FROM_DEVICE; + pp_params.netdev = pfvf->netdev; pool->page_pool = page_pool_create(&pp_params); if (IS_ERR(pool->page_pool)) { netdev_err(pfvf->netdev, "Creation of page pool failed\n"); diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index 5d291e50a47b..fe7610c42e5d 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -26,6 +26,7 @@ #include <linux/bitfield.h> #include <net/dsa.h> #include <net/dst_metadata.h> +#include <net/netdev_lock.h> #include <net/page_pool/helpers.h> #include <linux/genalloc.h> @@ -5030,10 +5031,14 @@ void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev) continue; list_add_tail(&dev->close_list, &dev_list); + netdev_lock_ops(dev); } netif_close_many(&dev_list, false); + list_for_each_entry(dev, &dev_list, close_list) + netdev_unlock_ops(dev); + eth->dma_dev = dma_dev; list_for_each_entry_safe(dev, tmp, &dev_list, close_list) { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c index c31e05529fc4..b9026cc64383 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c @@ -383,6 +383,7 @@ static const struct devlink_ops mlx5_devlink_ops = { .rate_node_del = mlx5_esw_devlink_rate_node_del, .rate_leaf_parent_set = mlx5_esw_devlink_rate_leaf_parent_set, .rate_node_parent_set = mlx5_esw_devlink_rate_node_parent_set, + .supported_cross_device_rate_nodes = true, #endif #ifdef CONFIG_MLX5_SF_MANAGER .port_new = mlx5_devlink_sf_port_new, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index 6e50311faa27..36b00a856bc2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -74,7 +74,7 @@ static void mlx5_esw_offloads_pf_vf_devlink_port_attrs_set(struct mlx5_eswitch * memcpy(dl_port->attrs.switch_id.id, ppid.id, ppid.id_len); dl_port->attrs.switch_id.id_len = ppid.id_len; devlink_port_attrs_pci_pf_set(dl_port, controller_num, pfnum, - true); + false); } } @@ -134,13 +134,16 @@ static void mlx5_esw_offloads_sf_devlink_port_attrs_set(struct mlx5_eswitch *esw { struct mlx5_core_dev *dev = esw->dev; struct netdev_phys_item_id ppid = {}; + u32 hpf_ctrl; u16 pfnum; pfnum = mlx5_esw_sf_controller_to_pfnum(dev, controller); + hpf_ctrl = mlx5_esw_get_hpf_host_number(dev) + 1; mlx5_esw_get_port_parent_id(dev, &ppid); memcpy(dl_port->attrs.switch_id.id, &ppid.id[0], ppid.id_len); dl_port->attrs.switch_id.id_len = ppid.id_len; - devlink_port_attrs_pci_sf_set(dl_port, controller, pfnum, sfnum, !!controller); + devlink_port_attrs_pci_sf_set(dl_port, controller, pfnum, sfnum, + controller == hpf_ctrl); } int mlx5_esw_offloads_sf_devlink_port_init(struct mlx5_eswitch *esw, struct mlx5_vport *vport, @@ -268,7 +271,6 @@ void mlx5_esw_offloads_devlink_port_unregister(struct mlx5_vport *vport) dl_port = vport->dl_port; mlx5_esw_devlink_port_res_unregister(&dl_port->dl_port); - mlx5_esw_qos_vport_update_parent(vport, NULL, NULL); devl_rate_leaf_destroy(&dl_port->dl_port); devl_port_unregister(&dl_port->dl_port); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index faccc60fc93a..0d20f51b9702 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -11,57 +11,8 @@ /* Minimum supported BW share value by the HW is 1 Mbit/sec */ #define MLX5_MIN_BW_SHARE 1 -/* Holds rate nodes associated with an E-Switch. */ -struct mlx5_qos_domain { - /* Serializes access to all qos changes in the qos domain. */ - struct mutex lock; - /* List of all mlx5_esw_sched_nodes. */ - struct list_head nodes; -}; - -static void esw_qos_lock(struct mlx5_eswitch *esw) -{ - mutex_lock(&esw->qos.domain->lock); -} - -static void esw_qos_unlock(struct mlx5_eswitch *esw) -{ - mutex_unlock(&esw->qos.domain->lock); -} - -static void esw_assert_qos_lock_held(struct mlx5_eswitch *esw) -{ - lockdep_assert_held(&esw->qos.domain->lock); -} - -static struct mlx5_qos_domain *esw_qos_domain_alloc(void) -{ - struct mlx5_qos_domain *qos_domain; - - qos_domain = kzalloc_obj(*qos_domain); - if (!qos_domain) - return NULL; - - mutex_init(&qos_domain->lock); - INIT_LIST_HEAD(&qos_domain->nodes); - - return qos_domain; -} - -static int esw_qos_domain_init(struct mlx5_eswitch *esw) -{ - esw->qos.domain = esw_qos_domain_alloc(); - - return esw->qos.domain ? 0 : -ENOMEM; -} - -static void esw_qos_domain_release(struct mlx5_eswitch *esw) -{ - kfree(esw->qos.domain); - esw->qos.domain = NULL; -} - enum sched_node_type { + SCHED_NODE_TYPE_ROOT, SCHED_NODE_TYPE_VPORTS_TSAR, SCHED_NODE_TYPE_VPORT, SCHED_NODE_TYPE_TC_ARBITER_TSAR, @@ -94,7 +45,9 @@ struct mlx5_esw_sched_node { enum sched_node_type type; /* The eswitch this node belongs to. */ struct mlx5_eswitch *esw; - /* The children nodes of this node, empty list for leaf nodes. */ + /* The children nodes of this node, empty list for leaf nodes. + * Can be from multiple E-Switches. + */ struct list_head children; /* Valid only if this node is associated with a vport. */ struct mlx5_vport *vport; @@ -106,16 +59,63 @@ struct mlx5_esw_sched_node { u32 tc_bw[DEVLINK_RATE_TCS_MAX]; }; -static void esw_qos_node_attach_to_parent(struct mlx5_esw_sched_node *node) +/* Locking notes: + * QoS changes are normally protected by the shd lock. But on older HW shd + * might not be created at all, so there needs to be a fallback serialization + * mechanism. This is esw->state_lock. + * Callers into QoS hold a combination of RTNL, devlink instance lock and + * esw->state_lock. Devlink rate ops additionally hold the shd lock if it + * exists. + * - VF rate ops use esw_qos_lock/esw_qos_unlock. + * - callers with esw->state_lock held use esw_qos_shd_lock/esw_qos_shd_unlock. + * - devlink callers use esw_qos_devlink_lock/esw_qos_devlink_unlock. + */ +static void esw_assert_qos_lock_held(struct mlx5_core_dev *dev) { - if (!node->parent) { - /* Root children are assigned a depth level of 2. */ - node->level = 2; - list_add_tail(&node->entry, &node->esw->qos.domain->nodes); - } else { - node->level = node->parent->level + 1; - list_add_tail(&node->entry, &node->parent->children); - } + if (dev->shd) + devl_assert_locked(dev->shd); + else + lockdep_assert_held(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); + else + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); + else + mutex_unlock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_shd_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); +} + +static void esw_qos_shd_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); +} + +static void esw_qos_devlink_lock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_devlink_unlock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_unlock(&dev->priv.eswitch->state_lock); } static int esw_qos_num_tcs(struct mlx5_core_dev *dev) @@ -125,14 +125,14 @@ static int esw_qos_num_tcs(struct mlx5_core_dev *dev) return num_tcs < DEVLINK_RATE_TCS_MAX ? num_tcs : DEVLINK_RATE_TCS_MAX; } -static void -esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, struct mlx5_esw_sched_node *parent) +static void esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, + struct mlx5_esw_sched_node *parent) { - list_del_init(&node->entry); node->parent = parent; - if (parent) - node->esw = parent->esw; - esw_qos_node_attach_to_parent(node); + node->esw = parent->esw; + node->level = parent->level + 1; + list_del(&node->entry); + list_add_tail(&node->entry, &parent->children); } static void esw_qos_nodes_set_parent(struct list_head *nodes, @@ -321,22 +321,19 @@ static int esw_qos_create_rate_limit_element(struct mlx5_esw_sched_node *node, return esw_qos_node_create_sched_element(node, sched_ctx, extack); } -static u32 esw_qos_calculate_min_rate_divider(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent) +static u32 +esw_qos_calculate_min_rate_divider(struct mlx5_esw_sched_node *parent) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 fw_max_bw_share = MLX5_CAP_QOS(esw->dev, max_tsar_bw_share); + u32 fw_max_bw_share = MLX5_CAP_QOS(parent->esw->dev, max_tsar_bw_share); struct mlx5_esw_sched_node *node; u32 max_guarantee = 0; /* Find max min_rate across all nodes. * This will correspond to fw_max_bw_share in the final bw_share calculation. */ - list_for_each_entry(node, nodes, entry) { - if (node->esw == esw && node->ix != esw->qos.root_tsar_ix && - node->min_rate > max_guarantee) + list_for_each_entry(node, &parent->children, entry) + if (node->min_rate > max_guarantee) max_guarantee = node->min_rate; - } if (max_guarantee) return max_t(u32, max_guarantee / fw_max_bw_share, 1); @@ -368,18 +365,13 @@ static void esw_qos_update_sched_node_bw_share(struct mlx5_esw_sched_node *node, esw_qos_sched_elem_config(node, node->max_rate, bw_share, extack); } -static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent, +static void esw_qos_normalize_min_rate(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 divider = esw_qos_calculate_min_rate_divider(esw, parent); + u32 divider = esw_qos_calculate_min_rate_divider(parent); struct mlx5_esw_sched_node *node; - list_for_each_entry(node, nodes, entry) { - if (node->esw != esw || node->ix == esw->qos.root_tsar_ix) - continue; - + list_for_each_entry(node, &parent->children, entry) { /* Vports TC TSARs don't have a minimum rate configured, * so there's no need to update the bw_share on them. */ @@ -391,7 +383,7 @@ static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, if (list_empty(&node->children)) continue; - esw_qos_normalize_min_rate(node->esw, node, extack); + esw_qos_normalize_min_rate(node, extack); } } @@ -412,14 +404,11 @@ static u32 esw_qos_calculate_tc_bw_divider(u32 *tc_bw) static int esw_qos_set_node_min_rate(struct mlx5_esw_sched_node *node, u32 min_rate, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = node->esw; - if (min_rate == node->min_rate) return 0; node->min_rate = min_rate; - esw_qos_normalize_min_rate(esw, node->parent, extack); - + esw_qos_normalize_min_rate(node->parent, extack); return 0; } @@ -460,6 +449,7 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, struct mlx5_esw_sched_node *parent = vport_node->parent; u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_node->esw->dev; + struct mlx5_vport *vport = vport_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -471,11 +461,17 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_element, attr, vport_number, vport_node->vport->vport); - MLX5_SET(scheduling_context, sched_ctx, parent_element_id, - parent ? parent->ix : vport_node->esw->qos.root_tsar_ix); + MLX5_SET(vport_element, attr, vport_number, vport->vport); + MLX5_SET(scheduling_context, sched_ctx, parent_element_id, parent->ix); MLX5_SET(scheduling_context, sched_ctx, max_average_bw, vport_node->max_rate); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_node, sched_ctx, extack); } @@ -487,6 +483,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, { u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_tc_node->esw->dev; + struct mlx5_vport *vport = vport_tc_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -498,8 +495,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT_TC); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_tc_element, attr, vport_number, - vport_tc_node->vport->vport); + MLX5_SET(vport_tc_element, attr, vport_number, vport->vport); MLX5_SET(vport_tc_element, attr, traffic_class, vport_tc_node->tc); MLX5_SET(scheduling_context, sched_ctx, max_bw_obj_id, rate_limit_elem_ix); @@ -507,13 +503,20 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, vport_tc_node->parent->ix); MLX5_SET(scheduling_context, sched_ctx, bw_share, vport_tc_node->bw_share); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_tc_node, sched_ctx, extack); } static struct mlx5_esw_sched_node * -__esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type type, +__esw_qos_alloc_node(u32 tsar_ix, enum sched_node_type type, struct mlx5_esw_sched_node *parent) { struct mlx5_esw_sched_node *node; @@ -522,20 +525,12 @@ __esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type if (!node) return NULL; - node->esw = esw; node->ix = tsar_ix; node->type = type; - node->parent = parent; INIT_LIST_HEAD(&node->children); - esw_qos_node_attach_to_parent(node); - if (!parent) { - /* The caller is responsible for inserting the node into the - * parent list if necessary. This function can also be used with - * a NULL parent, which doesn't necessarily indicate that it - * refers to the root scheduling element. - */ - list_del_init(&node->entry); - } + INIT_LIST_HEAD(&node->entry); + if (parent) + esw_qos_node_set_parent(node, parent); return node; } @@ -570,7 +565,7 @@ static int esw_qos_create_vports_tc_node(struct mlx5_esw_sched_node *parent, SCHEDULING_HIERARCHY_E_SWITCH)) return -EOPNOTSUPP; - vports_tc_node = __esw_qos_alloc_node(parent->esw, 0, + vports_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORTS_TC_TSAR, parent); if (!vports_tc_node) { @@ -665,7 +660,6 @@ static int esw_qos_create_tc_arbiter_sched_elem( struct netlink_ext_ack *extack) { u32 tsar_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; - u32 tsar_parent_ix; void *attr; if (!mlx5_qos_tsar_type_supported(tc_arbiter_node->esw->dev, @@ -678,10 +672,8 @@ static int esw_qos_create_tc_arbiter_sched_elem( attr = MLX5_ADDR_OF(scheduling_context, tsar_ctx, element_attributes); MLX5_SET(tsar_element, attr, tsar_type, TSAR_ELEMENT_TSAR_TYPE_TC_ARB); - tsar_parent_ix = tc_arbiter_node->parent ? tc_arbiter_node->parent->ix : - tc_arbiter_node->esw->qos.root_tsar_ix; MLX5_SET(scheduling_context, tsar_ctx, parent_element_id, - tsar_parent_ix); + tc_arbiter_node->parent->ix); MLX5_SET(scheduling_context, tsar_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_TSAR); MLX5_SET(scheduling_context, tsar_ctx, max_average_bw, @@ -694,37 +686,36 @@ static int esw_qos_create_tc_arbiter_sched_elem( } static struct mlx5_esw_sched_node * -__esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct mlx5_esw_sched_node *parent, +__esw_qos_create_vports_sched_node(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node; - u32 tsar_ix; int err; + u32 ix; - err = esw_qos_create_node_sched_elem(esw->dev, esw->qos.root_tsar_ix, 0, - 0, &tsar_ix); + err = esw_qos_create_node_sched_elem(parent->esw->dev, parent->ix, 0, 0, + &ix); if (err) { NL_SET_ERR_MSG_MOD(extack, "E-Switch create TSAR for node failed"); return ERR_PTR(err); } - node = __esw_qos_alloc_node(esw, tsar_ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); + node = __esw_qos_alloc_node(ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); if (!node) { NL_SET_ERR_MSG_MOD(extack, "E-Switch alloc node failed"); err = -ENOMEM; goto err_alloc_node; } - list_add_tail(&node->entry, &esw->qos.domain->nodes); - esw_qos_normalize_min_rate(esw, NULL, extack); - trace_mlx5_esw_node_qos_create(esw->dev, node, node->ix); + esw_qos_normalize_min_rate(parent, extack); + trace_mlx5_esw_node_qos_create(parent->esw->dev, node, node->ix); return node; err_alloc_node: - if (mlx5_destroy_scheduling_element_cmd(esw->dev, + if (mlx5_destroy_scheduling_element_cmd(parent->esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, - tsar_ix)) + ix)) NL_SET_ERR_MSG_MOD(extack, "E-Switch destroy TSAR for node failed"); return ERR_PTR(err); } @@ -738,7 +729,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac struct mlx5_esw_sched_node *node; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!MLX5_CAP_QOS(esw->dev, log_esw_max_sched_depth)) return ERR_PTR(-EOPNOTSUPP); @@ -746,7 +737,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac if (err) return ERR_PTR(err); - node = __esw_qos_create_vports_sched_node(esw, NULL, extack); + node = __esw_qos_create_vports_sched_node(esw->qos.root, extack); if (IS_ERR(node)) esw_qos_put(esw); @@ -762,45 +753,54 @@ static void __esw_qos_destroy_node(struct mlx5_esw_sched_node *node, struct netl trace_mlx5_esw_node_qos_destroy(esw->dev, node, node->ix); esw_qos_destroy_node(node, extack); - esw_qos_normalize_min_rate(esw, NULL, extack); + esw_qos_normalize_min_rate(esw->qos.root, extack); } static int esw_qos_create(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = esw->dev; + struct mlx5_esw_sched_node *root; + u32 root_ix; int err; if (!MLX5_CAP_GEN(dev, qos) || !MLX5_CAP_QOS(dev, esw_scheduling)) return -EOPNOTSUPP; - err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, - &esw->qos.root_tsar_ix); + err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, &root_ix); if (err) { esw_warn(dev, "E-Switch create root TSAR failed (%d)\n", err); return err; } + root = __esw_qos_alloc_node(root_ix, SCHED_NODE_TYPE_ROOT, NULL); + if (!root) { + esw_warn(dev, "E-Switch create root node failed\n"); + err = -ENOMEM; + goto out_err; + } + root->esw = esw; + root->level = 1; + esw->qos.root = root; refcount_set(&esw->qos.refcnt, 1); return 0; +out_err: + mlx5_destroy_scheduling_element_cmd(dev, SCHEDULING_HIERARCHY_E_SWITCH, + root_ix); + return err; } static void esw_qos_destroy(struct mlx5_eswitch *esw) { - int err; - - err = mlx5_destroy_scheduling_element_cmd(esw->dev, - SCHEDULING_HIERARCHY_E_SWITCH, - esw->qos.root_tsar_ix); - if (err) - esw_warn(esw->dev, "E-Switch destroy root TSAR failed (%d)\n", err); + esw_qos_destroy_node(esw->qos.root, NULL); + esw->qos.root = NULL; } static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { int err = 0; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!refcount_inc_not_zero(&esw->qos.refcnt)) { /* esw_qos_create() set refcount to 1 only on success. * No need to decrement on failure. @@ -813,7 +813,7 @@ static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) static void esw_qos_put(struct mlx5_eswitch *esw) { - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (refcount_dec_and_test(&esw->qos.refcnt)) esw_qos_destroy(esw); } @@ -866,8 +866,7 @@ esw_qos_create_vport_tc_sched_node(struct mlx5_vport *vport, u8 tc = vports_tc_node->tc; int err; - vport_tc_node = __esw_qos_alloc_node(vport_node->esw, 0, - SCHED_NODE_TYPE_VPORT_TC, + vport_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORT_TC, vports_tc_node); if (!vport_tc_node) return -ENOMEM; @@ -959,7 +958,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, /* Increase the parent's level by 2 to account for both the * TC arbiter and the vports TC scheduling element. */ - new_level = (parent ? parent->level : 2) + 2; + new_level = parent->level + 2; max_level = 1 << MLX5_CAP_QOS(vport_node->esw->dev, log_esw_max_sched_depth); if (new_level > max_level) { @@ -970,7 +969,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, } } - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (type == SCHED_NODE_TYPE_RATE_LIMITER) err = esw_qos_create_rate_limit_element(vport_node, extack); @@ -997,7 +996,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, err_sched_nodes: if (type == SCHED_NODE_TYPE_RATE_LIMITER) { esw_qos_node_destroy_sched_element(vport_node, NULL); - esw_qos_node_attach_to_parent(vport_node); + esw_qos_node_set_parent(vport_node, vport_node->parent); } else { esw_qos_tc_arbiter_scheduling_teardown(vport_node, NULL); } @@ -1055,7 +1054,7 @@ static void esw_qos_vport_disable(struct mlx5_vport *vport, struct netlink_ext_a vport_node->bw_share = 0; memset(vport_node->tc_bw, 0, sizeof(vport_node->tc_bw)); list_del_init(&vport_node->entry); - esw_qos_normalize_min_rate(vport_node->esw, vport_node->parent, extack); + esw_qos_normalize_min_rate(vport_node->parent, extack); trace_mlx5_esw_vport_qos_destroy(vport_node->esw->dev, vport); } @@ -1068,7 +1067,7 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); esw_qos_node_set_parent(vport_node, parent); if (type == SCHED_NODE_TYPE_VPORT) @@ -1079,9 +1078,10 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, return err; vport_node->type = type; - esw_qos_normalize_min_rate(vport_node->esw, parent, extack); - trace_mlx5_esw_vport_qos_create(vport->dev, vport, vport_node->max_rate, - vport_node->bw_share); + esw_qos_normalize_min_rate(parent, extack); + trace_mlx5_esw_vport_qos_create(vport_node->esw->dev, vport, + vport_node->bw_share, + vport_node->max_rate); return 0; } @@ -1092,22 +1092,20 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *sched_node; - struct mlx5_eswitch *parent_esw; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); err = esw_qos_get(esw, extack); if (err) return err; - parent_esw = parent ? parent->esw : esw; - sched_node = __esw_qos_alloc_node(parent_esw, 0, type, parent); + if (!parent) + parent = esw->qos.root; + sched_node = __esw_qos_alloc_node(0, type, parent); if (!sched_node) { esw_qos_put(esw); return -ENOMEM; } - if (!parent) - list_add_tail(&sched_node->entry, &esw->qos.domain->nodes); sched_node->max_rate = max_rate; sched_node->min_rate = min_rate; @@ -1125,33 +1123,23 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t static void mlx5_esw_qos_vport_disable_locked(struct mlx5_vport *vport) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); if (!vport->qos.sched_node) return; esw_qos_vport_disable(vport, NULL); mlx5_esw_qos_vport_qos_free(vport); - esw_qos_put(esw); + esw_qos_put(vport->dev->priv.eswitch); } void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport) { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - struct mlx5_esw_sched_node *parent; lockdep_assert_held(&esw->state_lock); - esw_qos_lock(esw); - if (!vport->qos.sched_node) - goto unlock; - - parent = vport->qos.sched_node->parent; - WARN(parent, "Disabling QoS on port before detaching it from node"); - + esw_qos_shd_lock(vport->dev); mlx5_esw_qos_vport_disable_locked(vport); -unlock: - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); } static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rate, @@ -1159,7 +1147,7 @@ static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, max_rate, 0, @@ -1174,7 +1162,7 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, 0, min_rate, @@ -1187,29 +1175,27 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *vport, u32 max_rate, u32 min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; int err; - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, min_rate, NULL); if (!err) err = mlx5_esw_qos_set_vport_max_rate(vport, max_rate, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; bool enabled; - esw_qos_lock(esw); + esw_qos_shd_lock(vport->dev); enabled = !!vport->qos.sched_node; if (enabled) { *max_rate = vport->qos.sched_node->max_rate; *min_rate = vport->qos.sched_node->min_rate; } - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); return enabled; } @@ -1234,6 +1220,28 @@ static int esw_qos_vport_tc_check_type(enum sched_node_type curr_type, return 0; } +static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, + u32 *tc_bw) +{ + int i, num_tcs = esw_qos_num_tcs(esw->dev); + + for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) + if (tc_bw[i]) + return false; + + return true; +} + +static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, + u32 *tc_bw) +{ + struct mlx5_esw_sched_node *node = vport->qos.sched_node; + struct mlx5_eswitch *esw = node ? + node->parent->esw : vport->dev->priv.eswitch; + + return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); +} + static int esw_qos_vport_update(struct mlx5_vport *vport, enum sched_node_type type, struct mlx5_esw_sched_node *parent, @@ -1245,7 +1253,7 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, u32 curr_tc_bw[DEVLINK_RATE_TCS_MAX] = {0}; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (curr_type == type && curr_parent == parent) return 0; @@ -1253,8 +1261,15 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, if (err) return err; - if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) + if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) { esw_qos_tc_arbiter_get_bw_shares(vport_node, curr_tc_bw); + if (!esw_qos_validate_unsupported_tc_bw(parent->esw, + curr_tc_bw)) { + NL_SET_ERR_MSG_MOD(extack, + "Unsupported traffic classes on the new device"); + return -EOPNOTSUPP; + } + } esw_qos_vport_disable(vport, extack); @@ -1275,11 +1290,10 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *curr_parent; enum sched_node_type type; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); curr_parent = vport->qos.sched_node->parent; if (curr_parent == parent) return 0; @@ -1287,10 +1301,9 @@ static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw /* Set vport QoS type based on parent node type if different from * default QoS; otherwise, use the vport's current QoS type. */ - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_RATE_LIMITER; - else if (curr_parent && - curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + else if (curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_VPORT; else type = vport->qos.sched_node->type; @@ -1319,11 +1332,9 @@ static int esw_qos_switch_tc_arbiter_node_to_vports( struct mlx5_esw_sched_node *node, struct netlink_ext_ack *extack) { - u32 parent_tsar_ix = node->parent ? - node->parent->ix : node->esw->qos.root_tsar_ix; int err; - err = esw_qos_create_node_sched_elem(node->esw->dev, parent_tsar_ix, + err = esw_qos_create_node_sched_elem(node->esw->dev, node->parent->ix, node->max_rate, node->bw_share, &node->ix); if (err) { @@ -1378,8 +1389,8 @@ esw_qos_move_node(struct mlx5_esw_sched_node *curr_node) { struct mlx5_esw_sched_node *new_node; - new_node = __esw_qos_alloc_node(curr_node->esw, curr_node->ix, - curr_node->type, NULL); + new_node = __esw_qos_alloc_node(curr_node->ix, curr_node->type, + curr_node->parent); if (!new_node) return ERR_PTR(-ENOMEM); @@ -1489,41 +1500,16 @@ out: return err; } -static u32 mlx5_esw_qos_lag_link_speed_get(struct mlx5_core_dev *mdev, - bool take_rtnl) -{ - struct ethtool_link_ksettings lksettings; - struct net_device *slave, *master; - u32 speed = SPEED_UNKNOWN; - - slave = mlx5_uplink_netdev_get(mdev); - if (!slave) - goto out; - - if (take_rtnl) - rtnl_lock(); - master = netdev_master_upper_dev_get(slave); - if (master && !__ethtool_get_link_ksettings(master, &lksettings)) - speed = lksettings.base.speed; - if (take_rtnl) - rtnl_unlock(); - -out: - mlx5_uplink_netdev_put(mdev, slave); - return speed; -} - static int mlx5_esw_qos_max_link_speed_get(struct mlx5_core_dev *mdev, u32 *link_speed_max, - bool take_rtnl, struct netlink_ext_ack *extack) { int err; - if (!mlx5_lag_is_active(mdev)) + if (!mlx5_lag_is_active(mdev) || + mlx5_lag_query_bond_speed(mdev, link_speed_max) < 0 || + *link_speed_max == 0) goto skip_lag; - *link_speed_max = mlx5_esw_qos_lag_link_speed_get(mdev, take_rtnl); - if (*link_speed_max != (u32)SPEED_UNKNOWN) return 0; @@ -1560,7 +1546,8 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return PTR_ERR(vport); if (rate_mbps) { - err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, false, NULL); + err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, + NULL); if (err) return err; @@ -1570,9 +1557,9 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return err; } - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, rate_mbps, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } @@ -1598,7 +1585,7 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return -EINVAL; } - err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, true, extack); + err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, extack); if (err) return err; @@ -1610,30 +1597,6 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return 0; } -static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, - u32 *tc_bw) -{ - int i, num_tcs = esw_qos_num_tcs(esw->dev); - - for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) { - if (tc_bw[i]) - return false; - } - - return true; -} - -static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, - u32 *tc_bw) -{ - struct mlx5_esw_sched_node *node = vport->qos.sched_node; - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw = (node && node->parent) ? node->parent->esw : esw; - - return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); -} - static bool esw_qos_tc_bw_disabled(u32 *tc_bw) { int i; @@ -1650,55 +1613,38 @@ static void esw_vport_qos_prune_empty(struct mlx5_vport *vport) { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return; - if (vport_node->parent || vport_node->max_rate || - vport_node->min_rate || !esw_qos_tc_bw_disabled(vport_node->tc_bw)) + if (vport_node->parent != vport_node->esw->qos.root || + vport_node->max_rate || vport_node->min_rate || + !esw_qos_tc_bw_disabled(vport_node->tc_bw)) return; mlx5_esw_qos_vport_disable_locked(vport); } -int mlx5_esw_qos_init(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - return 0; /* Nothing to change. */ - - return esw_qos_domain_init(esw); -} - -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - esw_qos_domain_release(esw); -} - /* Eswitch devlink rate API */ int mlx5_esw_devlink_rate_leaf_tx_share_set(struct devlink_rate *rate_leaf, void *priv, u64 tx_share, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_share", &tx_share, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, tx_share, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1706,24 +1652,20 @@ int mlx5_esw_devlink_rate_leaf_tx_max_set(struct devlink_rate *rate_leaf, void * u64 tx_max, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_max", &tx_max, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, tx_max, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1734,16 +1676,14 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, { struct mlx5_esw_sched_node *vport_node; struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; bool disable; int err = 0; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); if (!esw_qos_vport_validate_unsupported_tc_bw(vport, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, @@ -1777,7 +1717,7 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, if (!err) esw_qos_set_tc_arbiter_bw_shares(vport_node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1787,18 +1727,17 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node = priv; - struct mlx5_eswitch *esw = node->esw; bool disable; int err; - if (!esw_qos_validate_unsupported_tc_bw(esw, tc_bw)) { + if (!esw_qos_validate_unsupported_tc_bw(node->esw, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, "E-Switch traffic classes number is not supported"); return -EOPNOTSUPP; } disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(node->esw->dev); if (disable) { err = esw_qos_node_disable_tc_arbitration(node, extack); goto unlock; @@ -1808,7 +1747,7 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, if (!err) esw_qos_set_tc_arbiter_bw_shares(node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(node->esw->dev); return err; } @@ -1823,9 +1762,9 @@ int mlx5_esw_devlink_rate_node_tx_share_set(struct devlink_rate *rate_node, void if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_set_node_min_rate(node, tx_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1840,9 +1779,9 @@ int mlx5_esw_devlink_rate_node_tx_max_set(struct devlink_rate *rate_node, void * if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_sched_elem_config(node, tx_max, node->bw_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1857,7 +1796,7 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, if (IS_ERR(esw)) return PTR_ERR(esw); - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); if (esw->mode != MLX5_ESWITCH_OFFLOADS) { NL_SET_ERR_MSG_MOD(extack, "Rate node creation supported only in switchdev mode"); @@ -1870,10 +1809,9 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, err = PTR_ERR(node); goto unlock; } - *priv = node; unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1883,25 +1821,53 @@ int mlx5_esw_devlink_rate_node_del(struct devlink_rate *rate_node, void *priv, struct mlx5_esw_sched_node *node = priv; struct mlx5_eswitch *esw = node->esw; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); __esw_qos_destroy_node(node, extack); esw_qos_put(esw); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); + return 0; } -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, - struct netlink_ext_ack *extack) +static int +mlx5_esw_validate_cross_esw_scheduling(struct mlx5_eswitch *esw, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - int err = 0; + if (!parent || esw == parent->esw) + return 0; - if (parent && parent->esw != esw) { - NL_SET_ERR_MSG_MOD(extack, "Cross E-Switch scheduling is not supported"); + if (!MLX5_CAP_QOS(esw->dev, esw_cross_esw_sched)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling is not supported"); + return -EOPNOTSUPP; + } + if (!esw->dev->shd || esw->dev->shd != parent->esw->dev->shd) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add vport to a parent belonging to a different device"); return -EOPNOTSUPP; } + if (!mlx5_lag_is_active(esw->dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling requires LAG to be activated"); + return -EOPNOTSUPP; + } + + return 0; +} + +static int +mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) +{ + struct mlx5_eswitch *esw = vport->dev->priv.eswitch; + int err; + + err = mlx5_esw_validate_cross_esw_scheduling(esw, parent, extack); + if (err) + return err; - esw_qos_lock(esw); if (!vport->qos.sched_node && parent) { enum sched_node_type type; @@ -1910,9 +1876,11 @@ int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_s err = mlx5_esw_qos_vport_enable(vport, type, parent, 0, 0, extack); } else if (vport->qos.sched_node) { - err = esw_qos_vport_update_parent(vport, parent, extack); + err = esw_qos_vport_update_parent(vport, + parent ? : esw->qos.root, + extack); } - esw_qos_unlock(esw); + return err; } @@ -1925,14 +1893,11 @@ int mlx5_esw_devlink_rate_leaf_parent_set(struct devlink_rate *devlink_rate, struct mlx5_vport *vport = priv; int err; + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_vport_update_parent(vport, node, extack); - if (!err) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_qos_lock(esw); + if (!err) esw_vport_qos_prune_empty(vport); - esw_qos_unlock(esw); - } + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1958,7 +1923,7 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, { u8 new_level, max_level; - if (parent && parent->esw != node->esw) { + if (parent->esw != node->esw) { NL_SET_ERR_MSG_MOD(extack, "Cannot assign node to another E-Switch"); return -EOPNOTSUPP; @@ -1970,13 +1935,13 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, return -EOPNOTSUPP; } - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { NL_SET_ERR_MSG_MOD(extack, "Cannot attach a node to a parent with TC bandwidth configured"); return -EOPNOTSUPP; } - new_level = parent ? parent->level + 1 : 2; + new_level = parent->level + 1; if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { /* Increase by one to account for the vports TC scheduling * element. @@ -2027,14 +1992,12 @@ static int esw_qos_vports_node_update_parent(struct mlx5_esw_sched_node *node, { struct mlx5_esw_sched_node *curr_parent = node->parent; struct mlx5_eswitch *esw = node->esw; - u32 parent_ix; int err; - parent_ix = parent ? parent->ix : node->esw->qos.root_tsar_ix; mlx5_destroy_scheduling_element_cmd(esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, node->ix); - err = esw_qos_create_node_sched_elem(esw->dev, parent_ix, + err = esw_qos_create_node_sched_elem(esw->dev, parent->ix, node->max_rate, 0, &node->ix); if (err) { NL_SET_ERR_MSG_MOD(extack, @@ -2061,12 +2024,15 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, struct mlx5_eswitch *esw = node->esw; int err; + esw_qos_devlink_lock(esw->dev); + curr_parent = node->parent; + if (!parent) + parent = esw->qos.root; + err = mlx5_esw_qos_node_validate_set_parent(node, parent, extack); if (err) - return err; + goto out; - esw_qos_lock(esw); - curr_parent = node->parent; if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { err = esw_qos_tc_arbiter_node_update_parent(node, parent, extack); @@ -2077,12 +2043,11 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, if (err) goto out; - esw_qos_normalize_min_rate(esw, curr_parent, extack); - esw_qos_normalize_min_rate(esw, parent, extack); + esw_qos_normalize_min_rate(curr_parent, extack); + esw_qos_normalize_min_rate(parent, extack); out: - esw_qos_unlock(esw); - + esw_qos_devlink_unlock(esw->dev); return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h index 0a50982b0e27..f275e850d2c9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h @@ -6,9 +6,6 @@ #ifdef CONFIG_MLX5_ESWITCH -int mlx5_esw_qos_init(struct mlx5_eswitch *esw); -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw); - int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *evport, u32 max_rate, u32 min_rate); bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate); void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index a0e2ca87b8d8..b6e2c153b4f7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1885,10 +1885,6 @@ int mlx5_eswitch_enable_locked(struct mlx5_eswitch *esw, int num_vfs) MLX5_NB_INIT(&esw->nb, eswitch_vport_event, NIC_VPORT_CHANGE); mlx5_eq_notifier_register(esw->dev, &esw->nb); - err = mlx5_esw_qos_init(esw); - if (err) - goto err_esw_init; - if (esw->mode == MLX5_ESWITCH_LEGACY) { err = esw_legacy_enable(esw); } else { @@ -1990,6 +1986,13 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); mlx5_eswitch_invalidate_wq(esw); + + if (esw->mode == MLX5_ESWITCH_OFFLOADS) { + struct devlink *devlink = priv_to_devlink(esw->dev); + + devl_rate_nodes_destroy(devlink); + } + mlx5_esw_reps_block(esw); if (!mlx5_core_is_ecpf(esw->dev)) { @@ -2003,12 +2006,6 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) } mlx5_esw_reps_unblock(esw); - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) { - struct devlink *devlink = priv_to_devlink(esw->dev); - - devl_rate_nodes_destroy(devlink); - } /* Destroy legacy fdb when disabling sriov in legacy mode. */ if (esw->mode == MLX5_ESWITCH_LEGACY) mlx5_eswitch_disable_locked(esw); @@ -2039,6 +2036,9 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw->mode == MLX5_ESWITCH_LEGACY ? "LEGACY" : "OFFLOADS", esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); + if (esw->mode == MLX5_ESWITCH_OFFLOADS) + devl_rate_nodes_destroy(devlink); + if (esw->fdb_table.flags & MLX5_ESW_FDB_CREATED) { esw->fdb_table.flags &= ~MLX5_ESW_FDB_CREATED; if (esw->mode == MLX5_ESWITCH_OFFLOADS) @@ -2047,9 +2047,6 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw_legacy_disable(esw); mlx5_esw_acls_ns_cleanup(esw); } - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) - devl_rate_nodes_destroy(devlink); } void mlx5_eswitch_disable(struct mlx5_eswitch *esw) @@ -2554,9 +2551,6 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev) goto reps_err; esw->mode = MLX5_ESWITCH_LEGACY; - err = mlx5_esw_qos_init(esw); - if (err) - goto reps_err; mutex_init(&esw->offloads.encap_tbl_lock); hash_init(esw->offloads.encap_tbl); @@ -2611,7 +2605,6 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw) mlx5_eswitch_invalidate_wq(esw); destroy_workqueue(esw->work_queue); - mlx5_esw_qos_cleanup(esw); WARN_ON(refcount_read(&esw->qos.refcnt)); mutex_destroy(&esw->state_lock); WARN_ON(!xa_empty(&esw->offloads.vhca_map)); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index fea72b1dedab..c655f6e8da1c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -234,8 +234,10 @@ struct mlx5_vport { struct mlx5_vport_info info; - /* Protected with the E-Switch qos domain lock. The Vport QoS can - * either be disabled (sched_node is NULL) or in one of three states: + /* Protected by either the shared devlink (dev->shd) lock or by + * esw->state_lock. See esw_assert_qos_lock_held() for more details. + * The Vport QoS can either be disabled (sched_node is NULL) or in one + * of three states: * 1. Regular QoS (sched_node is a vport node). * 2. TC QoS enabled on the vport (sched_node is a TC arbiter). * 3. TC QoS enabled on the vport's parent node @@ -382,7 +384,6 @@ enum { }; struct dentry; -struct mlx5_qos_domain; struct mlx5_eswitch { struct mlx5_core_dev *dev; @@ -411,12 +412,15 @@ struct mlx5_eswitch { atomic64_t user_count; wait_queue_head_t work_queue_wait; - /* Protected with the E-Switch qos domain lock. */ + /* QoS changes are serialized by either the shared devlink (dev->shd) + * lock or by esw->state_lock. See esw_assert_qos_lock_held() for more + * details. + */ struct { /* Initially 0, meaning no QoS users and QoS is disabled. */ refcount_t refcnt; - u32 root_tsar_ix; - struct mlx5_qos_domain *domain; + /* The root node of the hierarchy. */ + struct mlx5_esw_sched_node *root; } qos; struct mlx5_esw_bridge_offloads *br_offloads; @@ -482,8 +486,6 @@ int mlx5_eswitch_set_vport_trust(struct mlx5_eswitch *esw, u16 vport_num, bool setting); int mlx5_eswitch_set_vport_rate(struct mlx5_eswitch *esw, u16 vport, u32 max_rate, u32 min_rate); -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *node, - struct netlink_ext_ack *extack); int mlx5_eswitch_set_vepa(struct mlx5_eswitch *esw, u8 setting); int mlx5_eswitch_get_vepa(struct mlx5_eswitch *esw, u8 *setting); int mlx5_eswitch_get_vport_config(struct mlx5_eswitch *esw, diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c index cbb272a96359..0d0cd093b3c6 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c @@ -1118,7 +1118,7 @@ u8 mlxsw_sp_acl_erp_delta_value(const struct mlxsw_sp_acl_erp_delta *delta, } void mlxsw_sp_acl_erp_delta_clear(const struct mlxsw_sp_acl_erp_delta *delta, - const char *enc_key) + char *enc_key) { u16 start = delta->start; u8 mask = delta->mask; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h index 010204f73ea4..67cc7a5737dd 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h @@ -245,7 +245,7 @@ u8 mlxsw_sp_acl_erp_delta_mask(const struct mlxsw_sp_acl_erp_delta *delta); u8 mlxsw_sp_acl_erp_delta_value(const struct mlxsw_sp_acl_erp_delta *delta, const char *enc_key); void mlxsw_sp_acl_erp_delta_clear(const struct mlxsw_sp_acl_erp_delta *delta, - const char *enc_key); + char *enc_key); struct mlxsw_sp_acl_erp_mask; diff --git a/drivers/net/ethernet/microsoft/Kconfig b/drivers/net/ethernet/microsoft/Kconfig index 3f36ee6a8ece..e9be18c92ca5 100644 --- a/drivers/net/ethernet/microsoft/Kconfig +++ b/drivers/net/ethernet/microsoft/Kconfig @@ -21,6 +21,7 @@ config MICROSOFT_MANA depends on X86_64 || (ARM64 && !CPU_BIG_ENDIAN) depends on PCI_HYPERV select AUXILIARY_BUS + select DIMLIB select PAGE_POOL select NET_SHAPER help diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index e8b7ffb47eb9..aef3b77229c1 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* Copyright (c) 2021, Microsoft Corporation. */ +#include <linux/bitfield.h> #include <linux/debugfs.h> #include <linux/module.h> #include <linux/pci.h> @@ -466,6 +467,7 @@ static int mana_gd_disable_queue(struct gdma_queue *queue) #define DOORBELL_OFFSET_RQ 0x400 #define DOORBELL_OFFSET_CQ 0x800 #define DOORBELL_OFFSET_EQ 0xFF8 +#define DOORBELL_OFFSET_DIM 0x820 static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index, enum gdma_queue_type q_type, u32 qid, @@ -506,6 +508,16 @@ static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index, addr += DOORBELL_OFFSET_SQ; break; + case GDMA_DIM: + e.dim.id = qid; + e.dim.mod_usec = FIELD_GET(MANA_INTR_MODR_USEC_MAX, tail_ptr); + e.dim.mod_usec_vld = !!(tail_ptr & MANA_INTR_MODR_USEC_VLD); + e.dim.mod_comps = FIELD_GET(MANA_INTR_MODR_COMP_MASK, tail_ptr); + e.dim.mod_comps_vld = num_req; + + addr += DOORBELL_OFFSET_DIM; + break; + default: WARN_ON(1); return; @@ -540,6 +552,23 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit) } EXPORT_SYMBOL_NS(mana_gd_ring_cq, "NET_MANA"); +void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld, + u32 mod_comps, bool mod_comps_vld) +{ + struct gdma_context *gc = cq->gdma_dev->gdma_context; + u32 dim_val; + + /* Convert the DIM values to doorbell parameters */ + dim_val = FIELD_PREP(MANA_INTR_MODR_USEC_MAX, mod_usec) | + FIELD_PREP(MANA_INTR_MODR_COMP_MASK, mod_comps); + if (mod_usec_vld) + dim_val |= MANA_INTR_MODR_USEC_VLD; + + mana_gd_ring_doorbell(gc, cq->gdma_dev->doorbell, GDMA_DIM, cq->id, + dim_val, mod_comps_vld); +} +EXPORT_SYMBOL_NS(mana_gd_ring_dim, "NET_MANA"); + #define MANA_SERVICE_PERIOD 10 static void mana_serv_rescan(struct pci_dev *pdev) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 9d9bfd116dab..89e7f59f635d 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -1591,6 +1591,15 @@ int mana_create_wq_obj(struct mana_port_context *apc, mana_gd_init_req_hdr(&req.hdr, MANA_CREATE_WQ_OBJ, sizeof(req), sizeof(resp)); + + /* Our driver uses different message versions for request and + * response in this case. + * Our firmware is forward compatible with newer message versions, so + * the old firmware still properly handles this message, just the new + * feature fields are ignored, and queue creation will be successful. + */ + req.hdr.req.msg_version = GDMA_MESSAGE_V3; + req.hdr.resp.msg_version = GDMA_MESSAGE_V2; req.vport = vport; req.wq_type = wq_type; req.wq_gdma_region = wq_spec->gdma_region; @@ -1599,6 +1608,9 @@ int mana_create_wq_obj(struct mana_port_context *apc, req.cq_size = cq_spec->queue_size; req.cq_moderation_ctx_id = cq_spec->modr_ctx_id; req.cq_parent_qid = cq_spec->attached_eq; + req.req_cq_moderation = cq_spec->req_cq_moderation; + req.cq_moderation_comp = cq_spec->cq_moderation_comp; + req.cq_moderation_usec = cq_spec->cq_moderation_usec; err = mana_send_request(apc->ac, &req, sizeof(req), &resp, sizeof(resp)); @@ -1856,6 +1868,7 @@ static void mana_poll_tx_cq(struct mana_cq *cq) struct gdma_posted_wqe_info *wqe_info; unsigned int pkt_transmitted = 0; unsigned int wqe_unit_cnt = 0; + unsigned int tx_bytes = 0; struct mana_txq *txq = cq->txq; struct mana_port_context *apc; struct netdev_queue *net_txq; @@ -1937,6 +1950,8 @@ static void mana_poll_tx_cq(struct mana_cq *cq) mana_unmap_skb(skb, apc); + tx_bytes += skb->len; + napi_consume_skb(skb, cq->budget); pkt_transmitted++; @@ -1967,6 +1982,10 @@ static void mana_poll_tx_cq(struct mana_cq *cq) if (atomic_sub_return(pkt_transmitted, &txq->pending_sends) < 0) WARN_ON_ONCE(1); + /* Feed DIM with the completion rate observed here, in NAPI context. */ + cq->tx_dim_pkts += pkt_transmitted; + cq->tx_dim_bytes += tx_bytes; + cq->work_done = pkt_transmitted; } @@ -2352,6 +2371,117 @@ static void mana_poll_rx_cq(struct mana_cq *cq) xdp_do_flush(); } +static void mana_rx_dim_work(struct work_struct *work) +{ + struct dim *dim = container_of(work, struct dim, work); + struct dim_cq_moder cur_moder; + struct mana_cq *cq; + + cur_moder = net_dim_get_rx_moderation(dim->mode, dim->profile_ix); + cq = container_of(dim, struct mana_cq, dim); + + cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX); + cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX); + + mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true, + cur_moder.pkts, true); + + dim->state = DIM_START_MEASURE; +} + +static void mana_tx_dim_work(struct work_struct *work) +{ + struct dim *dim = container_of(work, struct dim, work); + struct dim_cq_moder cur_moder; + struct mana_cq *cq; + + cur_moder = net_dim_get_tx_moderation(dim->mode, dim->profile_ix); + cq = container_of(dim, struct mana_cq, dim); + + cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX); + cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX); + + mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true, + cur_moder.pkts, true); + + dim->state = DIM_START_MEASURE; +} + +/* The caller must update apc->rx/tx_dim_enabled before disabling and + * after enabling. And synchronize_net() before draining the DIM work, + * so that NAPI cannot observe a stale flag. + */ +void mana_dim_change(struct mana_cq *cq, bool enable) +{ + bool is_rx = cq->type == MANA_CQ_TYPE_RX; + struct mana_port_context *apc; + work_func_t work_func; + u32 usec, comp; + + if (is_rx) { + apc = netdev_priv(cq->rxq->ndev); + usec = apc->intr_modr_rx_usec; + comp = apc->intr_modr_rx_comp; + work_func = mana_rx_dim_work; + } else { + apc = netdev_priv(cq->txq->ndev); + usec = apc->intr_modr_tx_usec; + comp = apc->intr_modr_tx_comp; + work_func = mana_tx_dim_work; + } + + /* On enable, zero the DIM state so net_dim() starts measuring from + * scratch. + * On disable, drain any pending DIM work and restore the static + * moderation values. + */ + if (enable) { + memset(&cq->dim, 0, sizeof(cq->dim)); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + INIT_WORK(&cq->dim.work, work_func); + } else { + cancel_work_sync(&cq->dim.work); + mana_gd_ring_dim(cq->gdma_cq, usec, true, comp, true); + } +} + +static void mana_update_rx_dim(struct mana_cq *cq) +{ + struct mana_port_context *apc = netdev_priv(cq->rxq->ndev); + struct dim_sample dim_sample = {}; + struct mana_rxq *rxq = cq->rxq; + + /* Pairs with smp_store_release() in mana_set_coalesce(): observing the + * enable flag set guarantees the DIM (re)initialization is visible. + */ + if (!smp_load_acquire(&apc->rx_dim_enabled)) + return; + + dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats.packets, + rxq->stats.bytes, &dim_sample); + net_dim(&cq->dim, &dim_sample); +} + +static void mana_update_tx_dim(struct mana_cq *cq) +{ + struct mana_port_context *apc = netdev_priv(cq->txq->ndev); + struct dim_sample dim_sample = {}; + + /* Pairs with smp_store_release() in mana_set_coalesce(): observing the + * enable flag set guarantees the DIM (re)initialization is visible. + */ + if (!smp_load_acquire(&apc->tx_dim_enabled)) + return; + + /* cq->tx_dim_pkts/bytes are accumulated in mana_poll_tx_cq(), in the + * same NAPI context as this read, so they track the hardware + * completion rate and need no u64_stats_sync protection. + */ + dim_update_sample(READ_ONCE(cq->dim_event_ctr), cq->tx_dim_pkts, + cq->tx_dim_bytes, &dim_sample); + net_dim(&cq->dim, &dim_sample); +} + static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue) { struct mana_cq *cq = context; @@ -2370,6 +2500,15 @@ static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue) if (w < cq->budget) { mana_gd_ring_cq(gdma_queue, SET_ARM_BIT); cq->work_done_since_doorbell = 0; + + /* Update DIM before napi_complete_done() to prevent running + * net_dim() concurrently. + */ + if (cq->type == MANA_CQ_TYPE_RX) + mana_update_rx_dim(cq); + else + mana_update_tx_dim(cq); + napi_complete_done(&cq->napi, w); } else if (cq->work_done_since_doorbell >= (cq->gdma_cq->queue_size / COMP_ENTRY_SIZE) * 4) { @@ -2402,6 +2541,7 @@ static void mana_schedule_napi(void *context, struct gdma_queue *gdma_queue) { struct mana_cq *cq = context; + WRITE_ONCE(cq->dim_event_ctr, cq->dim_event_ctr + 1); napi_schedule_irqoff(&cq->napi); } @@ -2444,6 +2584,7 @@ static void mana_destroy_txq(struct mana_port_context *apc) if (apc->tx_qp[i]->txq.napi_initialized) { napi_synchronize(napi); napi_disable_locked(napi); + cancel_work_sync(&apc->tx_qp[i]->tx_cq.dim.work); netif_napi_del_locked(napi); apc->tx_qp[i]->txq.napi_initialized = false; } @@ -2577,6 +2718,11 @@ static int mana_create_txq(struct mana_port_context *apc, cq_spec.modr_ctx_id = 0; cq_spec.attached_eq = cq->gdma_cq->cq.parent->id; + /* DIM setting can be changed at runtime */ + cq_spec.req_cq_moderation = true; + cq_spec.cq_moderation_usec = apc->intr_modr_tx_usec; + cq_spec.cq_moderation_comp = apc->intr_modr_tx_comp; + err = mana_create_wq_obj(apc, apc->port_handle, GDMA_SQ, &wq_spec, &cq_spec, &apc->tx_qp[i]->tx_object); @@ -2607,6 +2753,13 @@ static int mana_create_txq(struct mana_port_context *apc, set_bit(NAPI_STATE_NO_BUSY_POLL, &cq->napi.state); netif_napi_add_locked(net, &cq->napi, mana_poll); + + /* Initialize the DIM work before enabling NAPI, so that a poll + * cannot reach net_dim() with an uninitialized cq->dim.work. + */ + INIT_WORK(&cq->dim.work, mana_tx_dim_work); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + napi_enable_locked(&cq->napi); txq->napi_initialized = true; @@ -2644,6 +2797,7 @@ static void mana_destroy_rxq(struct mana_port_context *apc, napi_synchronize(napi); napi_disable_locked(napi); + cancel_work_sync(&rxq->rx_cq.dim.work); netif_napi_del_locked(napi); } @@ -2887,6 +3041,11 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, cq_spec.modr_ctx_id = 0; cq_spec.attached_eq = cq->gdma_cq->cq.parent->id; + /* DIM setting can be changed at runtime */ + cq_spec.req_cq_moderation = true; + cq_spec.cq_moderation_usec = apc->intr_modr_rx_usec; + cq_spec.cq_moderation_comp = apc->intr_modr_rx_comp; + err = mana_create_wq_obj(apc, apc->port_handle, GDMA_RQ, &wq_spec, &cq_spec, &rxq->rxobj); if (err) @@ -2919,6 +3078,12 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, WARN_ON(xdp_rxq_info_reg_mem_model(&rxq->xdp_rxq, MEM_TYPE_PAGE_POOL, rxq->page_pool)); + /* Initialize the DIM work before enabling NAPI, so that a poll + * cannot reach net_dim() with an uninitialized cq->dim.work. + */ + INIT_WORK(&cq->dim.work, mana_rx_dim_work); + cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE; + napi_enable_locked(&cq->napi); mana_gd_ring_cq(cq->gdma_cq, SET_ARM_BIT); @@ -3585,6 +3750,16 @@ static int mana_probe_port(struct mana_context *ac, int port_idx, apc->link_cfg_error = 1; apc->cqe_coalescing_enable = 0; + /* Initialize interrupt moderation settings if supported by HW */ + if (gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) { + apc->intr_modr_rx_usec = MANA_INTR_MODR_USEC_DEF; + apc->intr_modr_rx_comp = MANA_INTR_MODR_COMP_DEF; + apc->intr_modr_tx_usec = MANA_INTR_MODR_USEC_DEF; + apc->intr_modr_tx_comp = MANA_INTR_MODR_COMP_DEF; + apc->rx_dim_enabled = MANA_ADAPTIVE_RX_DEF; + apc->tx_dim_enabled = MANA_ADAPTIVE_TX_DEF; + } + mutex_init(&apc->vport_mutex); apc->vport_use_count = 0; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 881df597d7f9..9e31e2595ae3 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -419,6 +419,15 @@ static int mana_get_coalesce(struct net_device *ndev, !kernel_coal->rx_cqe_nsecs) kernel_coal->rx_cqe_nsecs = MANA_RX_CQE_NSEC_DEF; + ec->rx_coalesce_usecs = apc->intr_modr_rx_usec; + ec->rx_max_coalesced_frames = apc->intr_modr_rx_comp; + + ec->tx_coalesce_usecs = apc->intr_modr_tx_usec; + ec->tx_max_coalesced_frames = apc->intr_modr_tx_comp; + + ec->use_adaptive_rx_coalesce = apc->rx_dim_enabled; + ec->use_adaptive_tx_coalesce = apc->tx_dim_enabled; + return 0; } @@ -428,9 +437,34 @@ static int mana_set_coalesce(struct net_device *ndev, struct netlink_ext_ack *extack) { struct mana_port_context *apc = netdev_priv(ndev); - u8 saved_cqe_coalescing_enable; + struct { + u16 intr_modr_rx_usec; + u16 intr_modr_rx_comp; + u16 intr_modr_tx_usec; + u16 intr_modr_tx_comp; + u8 cqe_coalescing_enable; + bool rx_dim_enabled; + bool tx_dim_enabled; + } saved; + bool modr_changed = false; + bool dim_changed = false; + struct gdma_context *gc; int err; + gc = apc->ac->gdma_dev->gdma_context; + + /* Both static and dynamic interrupt moderation (DIM) rely on the + * same HW capability advertised by the PF. + */ + if ((ec->use_adaptive_rx_coalesce || ec->use_adaptive_tx_coalesce || + ec->rx_coalesce_usecs || ec->tx_coalesce_usecs || + ec->rx_max_coalesced_frames || ec->tx_max_coalesced_frames) && + !(gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION)) { + NL_SET_ERR_MSG(extack, + "Interrupt Moderation is not supported by HW"); + return -EOPNOTSUPP; + } + if (kernel_coal->rx_cqe_frames != 1 && kernel_coal->rx_cqe_frames != MANA_RXCOMP_OOB_NUM_PPI) { NL_SET_ERR_MSG_FMT(extack, @@ -440,18 +474,129 @@ static int mana_set_coalesce(struct net_device *ndev, return -EINVAL; } - saved_cqe_coalescing_enable = apc->cqe_coalescing_enable; + if (ec->rx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX || + ec->tx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX) { + NL_SET_ERR_MSG_FMT(extack, + "coalesce usecs must be <= %lu", + MANA_INTR_MODR_USEC_MAX); + return -EINVAL; + } + + if (ec->rx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX || + ec->tx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX) { + NL_SET_ERR_MSG_FMT(extack, + "coalesce frames must be <= %lu", + MANA_INTR_MODR_COMP_MAX); + return -EINVAL; + } + + if (ec->rx_coalesce_usecs != apc->intr_modr_rx_usec || + ec->rx_max_coalesced_frames != apc->intr_modr_rx_comp || + ec->tx_coalesce_usecs != apc->intr_modr_tx_usec || + ec->tx_max_coalesced_frames != apc->intr_modr_tx_comp) + modr_changed = true; + + saved.intr_modr_rx_usec = apc->intr_modr_rx_usec; + saved.intr_modr_rx_comp = apc->intr_modr_rx_comp; + saved.intr_modr_tx_usec = apc->intr_modr_tx_usec; + saved.intr_modr_tx_comp = apc->intr_modr_tx_comp; + + apc->intr_modr_rx_usec = ec->rx_coalesce_usecs; + apc->intr_modr_rx_comp = ec->rx_max_coalesced_frames; + apc->intr_modr_tx_usec = ec->tx_coalesce_usecs; + apc->intr_modr_tx_comp = ec->tx_max_coalesced_frames; + + if (!!ec->use_adaptive_rx_coalesce != apc->rx_dim_enabled || + !!ec->use_adaptive_tx_coalesce != apc->tx_dim_enabled) + dim_changed = true; + + saved.rx_dim_enabled = apc->rx_dim_enabled; + saved.tx_dim_enabled = apc->tx_dim_enabled; + + saved.cqe_coalescing_enable = apc->cqe_coalescing_enable; apc->cqe_coalescing_enable = kernel_coal->rx_cqe_frames == MANA_RXCOMP_OOB_NUM_PPI; - if (!apc->port_is_up) + if (!apc->port_is_up) { + WRITE_ONCE(apc->rx_dim_enabled, !!ec->use_adaptive_rx_coalesce); + WRITE_ONCE(apc->tx_dim_enabled, !!ec->use_adaptive_tx_coalesce); return 0; + } - err = mana_config_rss(apc, TRI_STATE_TRUE, false, false); - if (err) - apc->cqe_coalescing_enable = saved_cqe_coalescing_enable; + if (apc->cqe_coalescing_enable != saved.cqe_coalescing_enable) { + /* CQE coalescing setting is applied via RSS configuration. */ + err = mana_config_rss(apc, TRI_STATE_TRUE, false, false); + if (err) { + netdev_err(ndev, "Change CQE coalescing failed: %d\n", + err); + apc->cqe_coalescing_enable = + saved.cqe_coalescing_enable; + apc->intr_modr_rx_usec = saved.intr_modr_rx_usec; + apc->intr_modr_rx_comp = saved.intr_modr_rx_comp; + apc->intr_modr_tx_usec = saved.intr_modr_tx_usec; + apc->intr_modr_tx_comp = saved.intr_modr_tx_comp; + return err; + } + } - return err; + if (modr_changed || dim_changed) { + bool new_rx_dim = !!ec->use_adaptive_rx_coalesce; + bool new_tx_dim = !!ec->use_adaptive_tx_coalesce; + bool disable_rx_dim = saved.rx_dim_enabled && !new_rx_dim; + bool disable_tx_dim = saved.tx_dim_enabled && !new_tx_dim; + bool enable_rx_dim = !saved.rx_dim_enabled && new_rx_dim; + bool enable_tx_dim = !saved.tx_dim_enabled && new_tx_dim; + int q; + + /* On disable: clear the per-port flag first and + * synchronize_net() so any in-flight NAPI poll observes + * the new value and will not schedule further DIM work; + * then drain pending work and restore the static + * moderation values. + */ + if (disable_rx_dim) + WRITE_ONCE(apc->rx_dim_enabled, false); + if (disable_tx_dim) + WRITE_ONCE(apc->tx_dim_enabled, false); + if (disable_rx_dim || disable_tx_dim) + synchronize_net(); + + for (q = 0; q < apc->num_queues; q++) { + struct mana_cq *rx_cq = &apc->rxqs[q]->rx_cq; + struct mana_cq *tx_cq = &apc->tx_qp[q]->tx_cq; + + if (disable_rx_dim) + mana_dim_change(rx_cq, false); + else if (enable_rx_dim) + mana_dim_change(rx_cq, true); + else if (!new_rx_dim && modr_changed) + mana_gd_ring_dim(rx_cq->gdma_cq, + apc->intr_modr_rx_usec, true, + apc->intr_modr_rx_comp, true); + + if (disable_tx_dim) + mana_dim_change(tx_cq, false); + else if (enable_tx_dim) + mana_dim_change(tx_cq, true); + else if (!new_tx_dim && modr_changed) + mana_gd_ring_dim(tx_cq->gdma_cq, + apc->intr_modr_tx_usec, true, + apc->intr_modr_tx_comp, true); + } + + /* Publish the enable flag with release semantics so a + * concurrent NAPI poll that observes it set also sees the DIM + * (re)init done by mana_dim_change() above. + */ + if (enable_rx_dim) + /* pairs with smp_load_acquire() in mana_update_rx_dim() */ + smp_store_release(&apc->rx_dim_enabled, true); + if (enable_tx_dim) + /* pairs with smp_load_acquire() in mana_update_tx_dim() */ + smp_store_release(&apc->tx_dim_enabled, true); + } + + return 0; } /* mana_set_channels - change the number of queues on a port @@ -595,7 +740,13 @@ static int mana_get_link_ksettings(struct net_device *ndev, } const struct ethtool_ops mana_ethtool_ops = { - .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES, + .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES | + ETHTOOL_COALESCE_RX_USECS | + ETHTOOL_COALESCE_RX_MAX_FRAMES | + ETHTOOL_COALESCE_TX_USECS | + ETHTOOL_COALESCE_TX_MAX_FRAMES | + ETHTOOL_COALESCE_USE_ADAPTIVE_RX | + ETHTOOL_COALESCE_USE_ADAPTIVE_TX, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | ETHTOOL_OP_NEEDS_RTNL_GLINK, diff --git a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c index 48b94ce77490..88c5c52e0e38 100644 --- a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c +++ b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c @@ -16,7 +16,7 @@ #include <linux/net_tstamp.h> #include <linux/ptp_classify.h> #include <linux/ptp_pch.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #define PCH_GBE_MAR_ENTRIES 16 #define PCH_GBE_SHORT_PKT 64 diff --git a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c index 528114877677..c999754afb5f 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c @@ -558,18 +558,15 @@ struct sync_item { void ionic_rx_filter_sync(struct ionic_lif *lif) { struct device *dev = lif->ionic->dev; - struct list_head sync_add_list; - struct list_head sync_del_list; struct sync_item *sync_item; struct ionic_rx_filter *f; + LIST_HEAD(sync_add_list); + LIST_HEAD(sync_del_list); struct hlist_head *head; struct hlist_node *tmp; struct sync_item *spos; unsigned int i; - INIT_LIST_HEAD(&sync_add_list); - INIT_LIST_HEAD(&sync_del_list); - clear_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state); /* Copy the filters to be added and deleted diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c index e9a0e22907a6..125b73be92b1 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c +++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c @@ -1367,7 +1367,8 @@ int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode, * * Return: 0 on success, negative error code on failure. */ -int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, u32 *queue_map) +int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, + const u32 *queue_map) { u32 reg, queue_bitmap_val[PPE_RING_TO_QUEUE_BITMAP_WORD_CNT]; @@ -1380,7 +1381,7 @@ int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, u32 *queue_m } static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id, - const struct ppe_bm_port_config port_cfg) + struct ppe_bm_port_config port_cfg) { u32 reg, val, bm_fc_val[2]; int ret; @@ -1586,7 +1587,7 @@ qm_config_fail: } static int ppe_node_scheduler_config(struct ppe_device *ppe_dev, - const struct ppe_scheduler_port_config config) + struct ppe_scheduler_port_config config) { struct ppe_scheduler_cfg sch_cfg; int ret, i; diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h index 4bb45ca40144..60493e51e0a4 100644 --- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h +++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h @@ -313,5 +313,5 @@ int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode, struct ppe_rss_hash_cfg hash_cfg); int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, - u32 *queue_map); + const u32 *queue_map); #endif diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c index 78d4df55740a..bed6f63facf2 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c @@ -423,7 +423,8 @@ struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id) { struct rmnet_endpoint *ep; - hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode) { + hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode, + lockdep_rtnl_is_held()) { if (ep->mux_id == mux_id) return ep; } diff --git a/drivers/net/ethernet/sfc/Kconfig b/drivers/net/ethernet/sfc/Kconfig index c4c43434f314..979f2801e2a8 100644 --- a/drivers/net/ethernet/sfc/Kconfig +++ b/drivers/net/ethernet/sfc/Kconfig @@ -66,6 +66,15 @@ config SFC_MCDI_LOGGING Driver-Interface) commands and responses, allowing debugging of driver/firmware interaction. The tracing is actually enabled by a sysfs file 'mcdi_logging' under the PCI device. +config SFC_CXL + bool "Solarflare SFC9100-family CXL support" + depends on SFC && CXL_BUS >= SFC + default SFC + help + This enables SFC CXL support if the kernel is configuring CXL for + using CTPIO with CXL.mem. The SFC device with CXL support and + with a CXL-aware firmware can be used for minimizing latencies + when sending through CTPIO. source "drivers/net/ethernet/sfc/falcon/Kconfig" source "drivers/net/ethernet/sfc/siena/Kconfig" diff --git a/drivers/net/ethernet/sfc/Makefile b/drivers/net/ethernet/sfc/Makefile index d99039ec468d..bb0f1891cde6 100644 --- a/drivers/net/ethernet/sfc/Makefile +++ b/drivers/net/ethernet/sfc/Makefile @@ -13,6 +13,7 @@ sfc-$(CONFIG_SFC_SRIOV) += sriov.o ef10_sriov.o ef100_sriov.o ef100_rep.o \ mae.o tc.o tc_bindings.o tc_counters.o \ tc_encap_actions.o tc_conntrack.o +sfc-$(CONFIG_SFC_CXL) += efx_cxl.o obj-$(CONFIG_SFC) += sfc.o obj-$(CONFIG_SFC_FALCON) += falcon/ diff --git a/drivers/net/ethernet/sfc/ef10.c b/drivers/net/ethernet/sfc/ef10.c index 7e04f115bbaa..73bc064929f6 100644 --- a/drivers/net/ethernet/sfc/ef10.c +++ b/drivers/net/ethernet/sfc/ef10.c @@ -24,6 +24,7 @@ #include <linux/wait.h> #include <linux/workqueue.h> #include <net/udp_tunnel.h> +#include "efx_cxl.h" /* Hardware control for EF10 architecture including 'Huntington'. */ @@ -106,7 +107,7 @@ static int efx_ef10_get_vf_index(struct efx_nic *efx) static int efx_ef10_init_datapath_caps(struct efx_nic *efx) { - MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_CAPABILITIES_V4_OUT_LEN); + MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_CAPABILITIES_V7_OUT_LEN); struct efx_ef10_nic_data *nic_data = efx->nic_data; size_t outlen; int rc; @@ -177,6 +178,12 @@ static int efx_ef10_init_datapath_caps(struct efx_nic *efx) efx->num_mac_stats); } + if (outlen < MC_CMD_GET_CAPABILITIES_V7_OUT_LEN) + nic_data->datapath_caps3 = 0; + else + nic_data->datapath_caps3 = MCDI_DWORD(outbuf, + GET_CAPABILITIES_V7_OUT_FLAGS3); + return 0; } @@ -1140,6 +1147,9 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) unsigned int channel_vis, pio_write_vi_base, max_vis; struct efx_ef10_nic_data *nic_data = efx->nic_data; unsigned int uc_mem_map_size, wc_mem_map_size; +#ifdef CONFIG_SFC_CXL + struct efx_probe_data *probe_data; +#endif void __iomem *membase; int rc; @@ -1263,8 +1273,23 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) iounmap(efx->membase); efx->membase = membase; - /* Set up the WC mapping if needed */ - if (wc_mem_map_size) { + if (!wc_mem_map_size) + goto skip_pio; + + /* Set up the WC mapping */ + +#ifdef CONFIG_SFC_CXL + probe_data = container_of(efx, struct efx_probe_data, efx); + if ((nic_data->datapath_caps3 & + (1 << MC_CMD_GET_CAPABILITIES_V7_OUT_CXL_CONFIG_ENABLE_LBN)) && + probe_data->cxl_pio_initialised) { + /* Using PIO through CXL mapping */ + nic_data->pio_write_base = probe_data->cxl->ctpio_cxl; + nic_data->pio_write_vi_base = pio_write_vi_base; + } else +#endif + { + /* Using legacy PIO BAR mapping */ nic_data->wc_membase = ioremap_wc(efx->membase_phys + uc_mem_map_size, wc_mem_map_size); @@ -1279,12 +1304,14 @@ static int efx_ef10_dimension_resources(struct efx_nic *efx) nic_data->wc_membase + (pio_write_vi_base * efx->vi_stride + ER_DZ_TX_PIOBUF - uc_mem_map_size); - - rc = efx_ef10_link_piobufs(efx); - if (rc) - efx_ef10_free_piobufs(efx); } + rc = efx_ef10_link_piobufs(efx); + if (rc) + efx_ef10_free_piobufs(efx); + +skip_pio: + netif_dbg(efx, probe, efx->net_dev, "memory BAR at %pa (virtual %p+%x UC, %p+%x WC)\n", &efx->membase_phys, efx->membase, uc_mem_map_size, diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c index 8f136a11d396..3806cd3dd7f4 100644 --- a/drivers/net/ethernet/sfc/efx.c +++ b/drivers/net/ethernet/sfc/efx.c @@ -34,6 +34,7 @@ #include "selftest.h" #include "sriov.h" #include "efx_devlink.h" +#include "efx_cxl.h" #include "mcdi_port_common.h" #include "mcdi_pcol.h" @@ -981,12 +982,15 @@ static void efx_pci_remove(struct pci_dev *pci_dev) efx_pci_remove_main(efx); efx_fini_io(efx); + + probe_data = container_of(efx, struct efx_probe_data, efx); + efx_cxl_exit(probe_data); + pci_dbg(efx->pci_dev, "shutdown successful\n"); efx_fini_devlink_and_unlock(efx); efx_fini_struct(efx); free_netdev(efx->net_dev); - probe_data = container_of(efx, struct efx_probe_data, efx); kfree(probe_data); }; @@ -1190,6 +1194,17 @@ static int efx_pci_probe(struct pci_dev *pci_dev, if (rc) goto fail2; + /* A successful cxl initialization implies a CXL region created to be + * used for PIO buffers. If there is no CXL support legacy PIO buffers + * defined at specific PCI BAR regions will be used. If there is CXL + * support and the cxl initialization fails, the driver probe fails. + */ + rc = efx_cxl_init(probe_data); + if (rc) { + pci_err(pci_dev, "CXL initialization failed with error %d\n", rc); + goto fail3; + } + rc = efx_pci_probe_post_io(efx); if (rc) { /* On failure, retry once immediately. @@ -1228,6 +1243,7 @@ static int efx_pci_probe(struct pci_dev *pci_dev, return 0; fail3: + efx_cxl_exit(probe_data); efx_fini_io(efx); fail2: efx_fini_struct(efx); diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c new file mode 100644 index 000000000000..348d7404cd7a --- /dev/null +++ b/drivers/net/ethernet/sfc/efx_cxl.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/**************************************************************************** + * + * Driver for AMD network controllers and boards + * Copyright (C) 2025, Advanced Micro Devices, Inc. + */ + +#include <linux/pci.h> + +#include <cxl/cxl.h> +#include <cxl/pci.h> +#include "net_driver.h" +#include "efx_cxl.h" + +#define EFX_CTPIO_BUFFER_SIZE SZ_256M + +int efx_cxl_init(struct efx_probe_data *probe_data) +{ + struct efx_nic *efx = &probe_data->efx; + struct pci_dev *pci_dev = efx->pci_dev; + struct range cxl_pio_range; + struct efx_cxl *cxl; + u16 dvsec; + int rc; + + /* Is the device configured with and using CXL? */ + if (!pcie_is_cxl(pci_dev)) + return 0; + + dvsec = pci_find_dvsec_capability(pci_dev, PCI_VENDOR_ID_CXL, + PCI_DVSEC_CXL_DEVICE); + if (!dvsec) { + pci_info(pci_dev, "CXL_DVSEC_PCIE_DEVICE capability not found\n"); + return 0; + } + + pci_dbg(pci_dev, "CXL_DVSEC_PCIE_DEVICE capability found\n"); + + /* Create a cxl_dev_state embedded in the cxl struct using cxl core api + * specifying no mbox available. + */ + cxl = devm_cxl_dev_state_create(&pci_dev->dev, CXL_DEVTYPE_DEVMEM, + pci_get_dsn(pci_dev), dvsec, + struct efx_cxl, cxlds, false); + + if (!cxl) + return -ENOMEM; + + rc = cxl_pci_setup_regs(pci_dev, CXL_REGLOC_RBI_COMPONENT, + &cxl->cxlds.reg_map); + if (rc) { + pci_err(pci_dev, "No component registers\n"); + return rc; + } + + if (!cxl->cxlds.reg_map.component_map.hdm_decoder.valid) { + pci_err(pci_dev, "Expected HDM component register not found\n"); + return -ENODEV; + } + + if (!cxl->cxlds.reg_map.component_map.ras.valid) { + pci_err(pci_dev, "Expected RAS component register not found\n"); + return -ENODEV; + } + + /* Set media ready explicitly as there are neither mailbox for checking + * this state nor the CXL register involved, both not mandatory for + * type2. + */ + cxl->cxlds.media_ready = true; + + if (cxl_set_capacity(&cxl->cxlds, EFX_CTPIO_BUFFER_SIZE)) { + pci_err(pci_dev, "dpa capacity setup failed\n"); + return -ENODEV; + } + + cxl->cxlmd = devm_cxl_probe_mem(&cxl->cxlds, &cxl_pio_range); + if (IS_ERR(cxl->cxlmd)) { + pci_err(pci_dev, "CXL accel memdev creation failed\n"); + return PTR_ERR(cxl->cxlmd); + } + + cxl->ctpio_cxl = ioremap_wc(cxl_pio_range.start, + range_len(&cxl_pio_range)); + if (!cxl->ctpio_cxl) { + pci_err(pci_dev, "CXL ioremap region (%pra) failed\n", + &cxl_pio_range); + return -ENOMEM; + } + + probe_data->cxl_pio_initialised = true; + probe_data->cxl = cxl; + + return 0; +} + +void efx_cxl_exit(struct efx_probe_data *probe_data) +{ + if (!probe_data->cxl) + return; + + iounmap(probe_data->cxl->ctpio_cxl); +} + +MODULE_IMPORT_NS("CXL"); diff --git a/drivers/net/ethernet/sfc/efx_cxl.h b/drivers/net/ethernet/sfc/efx_cxl.h new file mode 100644 index 000000000000..3e2705cb063f --- /dev/null +++ b/drivers/net/ethernet/sfc/efx_cxl.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/**************************************************************************** + * Driver for AMD network controllers and boards + * Copyright (C) 2025, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, incorporated herein by reference. + */ + +#ifndef EFX_CXL_H +#define EFX_CXL_H + +#ifdef CONFIG_SFC_CXL + +#include <cxl/cxl.h> + +struct efx_probe_data; + +struct efx_cxl { + struct cxl_dev_state cxlds; + struct cxl_memdev *cxlmd; + void __iomem *ctpio_cxl; +}; + +int efx_cxl_init(struct efx_probe_data *probe_data); +void efx_cxl_exit(struct efx_probe_data *probe_data); +#else +static inline int efx_cxl_init(struct efx_probe_data *probe_data) { return 0; } +static inline void efx_cxl_exit(struct efx_probe_data *probe_data) {} +#endif +#endif diff --git a/drivers/net/ethernet/sfc/mcdi.c b/drivers/net/ethernet/sfc/mcdi.c index e65db9b70724..b806d3d90c42 100644 --- a/drivers/net/ethernet/sfc/mcdi.c +++ b/drivers/net/ethernet/sfc/mcdi.c @@ -7,6 +7,7 @@ #include <linux/delay.h> #include <linux/moduleparam.h> #include <linux/atomic.h> +#include <linux/slab.h> #include "net_driver.h" #include "nic.h" #include "io.h" @@ -71,7 +72,7 @@ int efx_mcdi_init(struct efx_nic *efx) mcdi->efx = efx; #ifdef CONFIG_SFC_MCDI_LOGGING /* consuming code assumes buffer is page-sized */ - mcdi->logging_buffer = (char *)__get_free_page(GFP_KERNEL); + mcdi->logging_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!mcdi->logging_buffer) goto fail1; mcdi->logging_enabled = mcdi_logging_default; @@ -112,7 +113,7 @@ int efx_mcdi_init(struct efx_nic *efx) return 0; fail2: #ifdef CONFIG_SFC_MCDI_LOGGING - free_page((unsigned long)mcdi->logging_buffer); + kfree(mcdi->logging_buffer); fail1: #endif kfree(efx->mcdi); @@ -138,7 +139,7 @@ void efx_mcdi_fini(struct efx_nic *efx) return; #ifdef CONFIG_SFC_MCDI_LOGGING - free_page((unsigned long)efx->mcdi->iface.logging_buffer); + kfree(efx->mcdi->iface.logging_buffer); #endif kfree(efx->mcdi); diff --git a/drivers/net/ethernet/sfc/net_driver.h b/drivers/net/ethernet/sfc/net_driver.h index b98c259f672d..3964b2c56609 100644 --- a/drivers/net/ethernet/sfc/net_driver.h +++ b/drivers/net/ethernet/sfc/net_driver.h @@ -1197,14 +1197,24 @@ struct efx_nic { atomic_t n_rx_noskb_drops; }; +#ifdef CONFIG_SFC_CXL +struct efx_cxl; +#endif + /** * struct efx_probe_data - State after hardware probe * @pci_dev: The PCI device * @efx: Efx NIC details + * @cxl: details of related cxl objects + * @cxl_pio_initialised: cxl initialization outcome. */ struct efx_probe_data { struct pci_dev *pci_dev; struct efx_nic efx; +#ifdef CONFIG_SFC_CXL + struct efx_cxl *cxl; + bool cxl_pio_initialised; +#endif }; static inline struct efx_nic *efx_netdev_priv(struct net_device *dev) diff --git a/drivers/net/ethernet/sfc/nic.h b/drivers/net/ethernet/sfc/nic.h index ec3b2df43b68..7480f9995dfb 100644 --- a/drivers/net/ethernet/sfc/nic.h +++ b/drivers/net/ethernet/sfc/nic.h @@ -152,6 +152,8 @@ enum { * %MC_CMD_GET_CAPABILITIES response) * @datapath_caps2: Further Capabilities of datapath firmware (FLAGS2 field of * %MC_CMD_GET_CAPABILITIES response) + * @datapath_caps3: Further Capabilities of datapath firmware (FLAGS3 field of + * %MC_CMD_GET_CAPABILITIES response) * @rx_dpcpu_fw_id: Firmware ID of the RxDPCPU * @tx_dpcpu_fw_id: Firmware ID of the TxDPCPU * @must_probe_vswitching: Flag: vswitching has yet to be setup after MC reboot @@ -187,6 +189,7 @@ struct efx_ef10_nic_data { bool must_check_datapath_caps; u32 datapath_caps; u32 datapath_caps2; + u32 datapath_caps3; unsigned int rx_dpcpu_fw_id; unsigned int tx_dpcpu_fw_id; bool must_probe_vswitching; diff --git a/drivers/net/ethernet/sfc/siena/mcdi.c b/drivers/net/ethernet/sfc/siena/mcdi.c index 4d0d6bd5d3d1..048c1e6017c0 100644 --- a/drivers/net/ethernet/sfc/siena/mcdi.c +++ b/drivers/net/ethernet/sfc/siena/mcdi.c @@ -7,6 +7,7 @@ #include <linux/delay.h> #include <linux/moduleparam.h> #include <linux/atomic.h> +#include <linux/slab.h> #include "net_driver.h" #include "nic.h" #include "io.h" @@ -73,7 +74,7 @@ int efx_siena_mcdi_init(struct efx_nic *efx) mcdi->efx = efx; #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING /* consuming code assumes buffer is page-sized */ - mcdi->logging_buffer = (char *)__get_free_page(GFP_KERNEL); + mcdi->logging_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!mcdi->logging_buffer) goto fail1; mcdi->logging_enabled = efx_siena_mcdi_logging_default; @@ -116,7 +117,7 @@ int efx_siena_mcdi_init(struct efx_nic *efx) return 0; fail2: #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING - free_page((unsigned long)mcdi->logging_buffer); + kfree(mcdi->logging_buffer); fail1: #endif kfree(efx->mcdi); @@ -142,7 +143,7 @@ void efx_siena_mcdi_fini(struct efx_nic *efx) return; #ifdef CONFIG_SFC_SIENA_MCDI_LOGGING - free_page((unsigned long)efx->mcdi->iface.logging_buffer); + kfree(efx->mcdi->iface.logging_buffer); #endif kfree(efx->mcdi); diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c index 39731069d99e..009f37105eaf 100644 --- a/drivers/net/ethernet/sgi/ioc3-eth.c +++ b/drivers/net/ethernet/sgi/ioc3-eth.c @@ -967,11 +967,12 @@ static void ioc3eth_remove(struct platform_device *pdev) struct net_device *dev = platform_get_drvdata(pdev); struct ioc3_private *ip = netdev_priv(dev); + unregister_netdev(dev); + timer_delete_sync(&ip->ioc3_timer); + dma_free_coherent(ip->dma_dev, RX_RING_SIZE, ip->rxr, ip->rxr_dma); dma_free_coherent(ip->dma_dev, TX_RING_SIZE + SZ_16K - 1, ip->tx_ring, ip->txr_dma); - unregister_netdev(dev); - timer_delete_sync(&ip->ioc3_timer); free_netdev(dev); } @@ -1061,9 +1062,9 @@ static netdev_tx_t ioc3_start_xmit(struct sk_buff *skb, struct net_device *dev) d1 = dma_map_single(ip->dma_dev, skb->data, s1, DMA_TO_DEVICE); if (dma_mapping_error(ip->dma_dev, d1)) goto drop_packet; - d2 = dma_map_single(ip->dma_dev, (void *)b2, s1, DMA_TO_DEVICE); + d2 = dma_map_single(ip->dma_dev, (void *)b2, s2, DMA_TO_DEVICE); if (dma_mapping_error(ip->dma_dev, d2)) { - dma_unmap_single(ip->dma_dev, d1, len, DMA_TO_DEVICE); + dma_unmap_single(ip->dma_dev, d1, s1, DMA_TO_DEVICE); goto drop_packet; } desc->p1 = cpu_to_be64(ioc3_map(d1, PCI64_ATTR_PREF)); diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index a60ef32b35b8..4ad9528322c4 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -151,7 +151,8 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_dev *gtp, u64 tid, u16 family) head = >p->tid_hash[gtp0_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid, + lockdep_rtnl_is_held()) { if (pdp->af == family && pdp->gtp_version == GTP_V0 && pdp->u.v0.tid == tid) @@ -168,7 +169,8 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_dev *gtp, u32 tid, u16 family) head = >p->tid_hash[gtp1u_hashfn(tid) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_tid) { + hlist_for_each_entry_rcu(pdp, head, hlist_tid, + lockdep_rtnl_is_held()) { if (pdp->af == family && pdp->gtp_version == GTP_V1 && pdp->u.v1.i_tei == tid) @@ -185,7 +187,8 @@ static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr) head = >p->addr_hash[ipv4_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr, + lockdep_rtnl_is_held()) { if (pdp->af == AF_INET && pdp->ms.addr.s_addr == ms_addr) return pdp; @@ -220,7 +223,8 @@ static struct pdp_ctx *ipv6_pdp_find(struct gtp_dev *gtp, head = >p->addr_hash[ipv6_hashfn(ms_addr) % gtp->hash_size]; - hlist_for_each_entry_rcu(pdp, head, hlist_addr) { + hlist_for_each_entry_rcu(pdp, head, hlist_addr, + lockdep_rtnl_is_held()) { if (pdp->af == AF_INET6 && ipv6_pdp_addr_equal(&pdp->ms.addr6, ms_addr)) return pdp; diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c index dd89282f0179..aa28025a1da1 100644 --- a/drivers/net/macsec.c +++ b/drivers/net/macsec.c @@ -2636,7 +2636,7 @@ static int macsec_update_offload(struct net_device *dev, vlan_drop_rx_ctag_filter_info(dev); vlan_drop_rx_stag_filter_info(dev); } - macsec->offload = offload; + WRITE_ONCE(macsec->offload, offload); /* Add VLAN filters when enabling offload. */ if (prev_offload == MACSEC_OFFLOAD_OFF) { ret = vlan_get_rx_ctag_filter_info(dev); @@ -2666,7 +2666,7 @@ static int macsec_update_offload(struct net_device *dev, return 0; rollback_offload: - macsec->offload = prev_offload; + WRITE_ONCE(macsec->offload, prev_offload); macsec_offload(ops->mdo_del_secy, &ctx); return ret; @@ -3875,52 +3875,53 @@ static int macsec_changelink_common(struct net_device *dev, if (data[IFLA_MACSEC_ENCODING_SA]) { struct macsec_tx_sa *tx_sa; + u8 encoding_sa = nla_get_u8(data[IFLA_MACSEC_ENCODING_SA]); - tx_sc->encoding_sa = nla_get_u8(data[IFLA_MACSEC_ENCODING_SA]); - tx_sa = rtnl_dereference(tx_sc->sa[tx_sc->encoding_sa]); + WRITE_ONCE(tx_sc->encoding_sa, encoding_sa); + tx_sa = rtnl_dereference(tx_sc->sa[encoding_sa]); secy->operational = tx_sa && tx_sa->active; } if (data[IFLA_MACSEC_ENCRYPT]) - tx_sc->encrypt = !!nla_get_u8(data[IFLA_MACSEC_ENCRYPT]); + WRITE_ONCE(tx_sc->encrypt, !!nla_get_u8(data[IFLA_MACSEC_ENCRYPT])); if (data[IFLA_MACSEC_PROTECT]) - secy->protect_frames = !!nla_get_u8(data[IFLA_MACSEC_PROTECT]); + WRITE_ONCE(secy->protect_frames, !!nla_get_u8(data[IFLA_MACSEC_PROTECT])); if (data[IFLA_MACSEC_INC_SCI]) - tx_sc->send_sci = !!nla_get_u8(data[IFLA_MACSEC_INC_SCI]); + WRITE_ONCE(tx_sc->send_sci, !!nla_get_u8(data[IFLA_MACSEC_INC_SCI])); if (data[IFLA_MACSEC_ES]) - tx_sc->end_station = !!nla_get_u8(data[IFLA_MACSEC_ES]); + WRITE_ONCE(tx_sc->end_station, !!nla_get_u8(data[IFLA_MACSEC_ES])); if (data[IFLA_MACSEC_SCB]) - tx_sc->scb = !!nla_get_u8(data[IFLA_MACSEC_SCB]); + WRITE_ONCE(tx_sc->scb, !!nla_get_u8(data[IFLA_MACSEC_SCB])); if (data[IFLA_MACSEC_REPLAY_PROTECT]) - secy->replay_protect = !!nla_get_u8(data[IFLA_MACSEC_REPLAY_PROTECT]); + WRITE_ONCE(secy->replay_protect, !!nla_get_u8(data[IFLA_MACSEC_REPLAY_PROTECT])); if (data[IFLA_MACSEC_VALIDATION]) - secy->validate_frames = nla_get_u8(data[IFLA_MACSEC_VALIDATION]); + WRITE_ONCE(secy->validate_frames, nla_get_u8(data[IFLA_MACSEC_VALIDATION])); if (data[IFLA_MACSEC_CIPHER_SUITE]) { switch (nla_get_u64(data[IFLA_MACSEC_CIPHER_SUITE])) { case MACSEC_CIPHER_ID_GCM_AES_128: case MACSEC_DEFAULT_CIPHER_ID: - secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; - secy->xpn = false; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_128_SAK_LEN); + WRITE_ONCE(secy->xpn, false); break; case MACSEC_CIPHER_ID_GCM_AES_256: - secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; - secy->xpn = false; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_256_SAK_LEN); + WRITE_ONCE(secy->xpn, false); break; case MACSEC_CIPHER_ID_GCM_AES_XPN_128: - secy->key_len = MACSEC_GCM_AES_128_SAK_LEN; - secy->xpn = true; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_128_SAK_LEN); + WRITE_ONCE(secy->xpn, true); break; case MACSEC_CIPHER_ID_GCM_AES_XPN_256: - secy->key_len = MACSEC_GCM_AES_256_SAK_LEN; - secy->xpn = true; + WRITE_ONCE(secy->key_len, MACSEC_GCM_AES_256_SAK_LEN); + WRITE_ONCE(secy->xpn, true); break; default: return -EINVAL; @@ -3928,13 +3929,14 @@ static int macsec_changelink_common(struct net_device *dev, } if (data[IFLA_MACSEC_WINDOW]) { - secy->replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]); + u32 replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]); /* IEEE 802.1AEbw-2013 10.7.8 - maximum replay window * for XPN cipher suites */ if (secy->xpn && - secy->replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW) + replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW) return -EINVAL; + WRITE_ONCE(secy->replay_window, replay_window); } return 0; @@ -4382,21 +4384,21 @@ static size_t macsec_get_size(const struct net_device *dev) static int macsec_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct macsec_tx_sc *tx_sc; - struct macsec_dev *macsec; - struct macsec_secy *secy; + const struct macsec_tx_sc *tx_sc; + const struct macsec_dev *macsec; + const struct macsec_secy *secy; u64 csid; macsec = macsec_priv(dev); secy = &macsec->secy; tx_sc = &secy->tx_sc; - switch (secy->key_len) { + switch (READ_ONCE(secy->key_len)) { case MACSEC_GCM_AES_128_SAK_LEN: - csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; + csid = READ_ONCE(secy->xpn) ? MACSEC_CIPHER_ID_GCM_AES_XPN_128 : MACSEC_DEFAULT_CIPHER_ID; break; case MACSEC_GCM_AES_256_SAK_LEN: - csid = secy->xpn ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; + csid = READ_ONCE(secy->xpn) ? MACSEC_CIPHER_ID_GCM_AES_XPN_256 : MACSEC_CIPHER_ID_GCM_AES_256; break; default: goto nla_put_failure; @@ -4407,20 +4409,20 @@ static int macsec_fill_info(struct sk_buff *skb, nla_put_u8(skb, IFLA_MACSEC_ICV_LEN, secy->icv_len) || nla_put_u64_64bit(skb, IFLA_MACSEC_CIPHER_SUITE, csid, IFLA_MACSEC_PAD) || - nla_put_u8(skb, IFLA_MACSEC_ENCODING_SA, tx_sc->encoding_sa) || - nla_put_u8(skb, IFLA_MACSEC_ENCRYPT, tx_sc->encrypt) || - nla_put_u8(skb, IFLA_MACSEC_PROTECT, secy->protect_frames) || - nla_put_u8(skb, IFLA_MACSEC_INC_SCI, tx_sc->send_sci) || - nla_put_u8(skb, IFLA_MACSEC_ES, tx_sc->end_station) || - nla_put_u8(skb, IFLA_MACSEC_SCB, tx_sc->scb) || - nla_put_u8(skb, IFLA_MACSEC_REPLAY_PROTECT, secy->replay_protect) || - nla_put_u8(skb, IFLA_MACSEC_VALIDATION, secy->validate_frames) || - nla_put_u8(skb, IFLA_MACSEC_OFFLOAD, macsec->offload) || + nla_put_u8(skb, IFLA_MACSEC_ENCODING_SA, READ_ONCE(tx_sc->encoding_sa)) || + nla_put_u8(skb, IFLA_MACSEC_ENCRYPT, READ_ONCE(tx_sc->encrypt)) || + nla_put_u8(skb, IFLA_MACSEC_PROTECT, READ_ONCE(secy->protect_frames)) || + nla_put_u8(skb, IFLA_MACSEC_INC_SCI, READ_ONCE(tx_sc->send_sci)) || + nla_put_u8(skb, IFLA_MACSEC_ES, READ_ONCE(tx_sc->end_station)) || + nla_put_u8(skb, IFLA_MACSEC_SCB, READ_ONCE(tx_sc->scb)) || + nla_put_u8(skb, IFLA_MACSEC_REPLAY_PROTECT, READ_ONCE(secy->replay_protect)) || + nla_put_u8(skb, IFLA_MACSEC_VALIDATION, READ_ONCE(secy->validate_frames)) || + nla_put_u8(skb, IFLA_MACSEC_OFFLOAD, READ_ONCE(macsec->offload)) || 0) goto nla_put_failure; - if (secy->replay_protect) { - if (nla_put_u32(skb, IFLA_MACSEC_WINDOW, secy->replay_window)) + if (READ_ONCE(secy->replay_protect)) { + if (nla_put_u32(skb, IFLA_MACSEC_WINDOW, READ_ONCE(secy->replay_window))) goto nla_put_failure; } diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index c40fa331836b..9a4bc99dbf53 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -171,7 +171,7 @@ static int macvlan_hash_add_source(struct macvlan_dev *vlan, RCU_INIT_POINTER(entry->vlan, vlan); h = &port->vlan_source_hash[macvlan_eth_hash(addr)]; hlist_add_head_rcu(&entry->hlist, h); - vlan->macaddr_count++; + WRITE_ONCE(vlan->macaddr_count, vlan->macaddr_count + 1); return 0; } @@ -277,7 +277,7 @@ static void macvlan_broadcast(struct sk_buff *skb, return; hash_for_each_rcu(port->vlan_hash, i, vlan, hlist) { - if (vlan->dev == src || !(vlan->mode & mode)) + if (vlan->dev == src || !(READ_ONCE(vlan->mode) & mode)) continue; hash = mc_hash(vlan, eth->h_dest); @@ -306,7 +306,7 @@ static void macvlan_multicast_rx(const struct macvlan_port *port, MACVLAN_MODE_VEPA | MACVLAN_MODE_PASSTHRU| MACVLAN_MODE_BRIDGE); - else if (src->mode == MACVLAN_MODE_VEPA) + else if (READ_ONCE(src->mode) == MACVLAN_MODE_VEPA) /* flood to everyone except source */ macvlan_broadcast(skb, port, src->dev, MACVLAN_MODE_VEPA | @@ -402,7 +402,7 @@ static void macvlan_flush_sources(struct macvlan_port *port, if (rcu_access_pointer(entry->vlan) == vlan) macvlan_hash_del_source(entry); - vlan->macaddr_count = 0; + WRITE_ONCE(vlan->macaddr_count, 0); } static void macvlan_forward_source_one(struct sk_buff *skb, @@ -447,7 +447,7 @@ static bool macvlan_forward_source(struct sk_buff *skb, if (!vlan) continue; - if (vlan->flags & MACVLAN_FLAG_NODST) + if (READ_ONCE(vlan->flags) & MACVLAN_FLAG_NODST) consume = true; macvlan_forward_source_one(skb, vlan); } @@ -487,14 +487,18 @@ static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb) return RX_HANDLER_CONSUMED; } src = macvlan_hash_lookup(port, eth->h_source); - if (src && src->mode != MACVLAN_MODE_VEPA && - src->mode != MACVLAN_MODE_BRIDGE) { - /* forward to original port. */ - vlan = src; - ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: - __netif_rx(skb); - handle_res = RX_HANDLER_CONSUMED; - goto out; + if (src) { + enum macvlan_mode mode = READ_ONCE(src->mode); + + if (mode != MACVLAN_MODE_VEPA && + mode != MACVLAN_MODE_BRIDGE) { + /* forward to original port. */ + vlan = src; + ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: + __netif_rx(skb); + handle_res = RX_HANDLER_CONSUMED; + goto out; + } } hash = mc_hash(NULL, eth->h_dest); @@ -515,7 +519,7 @@ static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb) struct macvlan_dev, list); else vlan = macvlan_hash_lookup(port, eth->h_dest); - if (!vlan || vlan->mode == MACVLAN_MODE_SOURCE) + if (!vlan || READ_ONCE(vlan->mode) == MACVLAN_MODE_SOURCE) return RX_HANDLER_PASS; dev = vlan->dev; @@ -548,7 +552,7 @@ static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev) const struct macvlan_port *port = vlan->port; const struct macvlan_dev *dest; - if (vlan->mode == MACVLAN_MODE_BRIDGE) { + if (READ_ONCE(vlan->mode) == MACVLAN_MODE_BRIDGE) { const struct ethhdr *eth = skb_eth_hdr(skb); /* send to other bridge ports directly */ @@ -559,7 +563,7 @@ static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev) } dest = macvlan_hash_lookup(port, eth->h_dest); - if (dest && dest->mode == MACVLAN_MODE_BRIDGE) { + if (dest && READ_ONCE(dest->mode) == MACVLAN_MODE_BRIDGE) { /* send to lowerdev first for its network taps */ dev_forward_skb(vlan->lowerdev, skb); @@ -777,7 +781,7 @@ static int macvlan_set_mac_address(struct net_device *dev, void *p) if (ether_addr_equal(dev->dev_addr, addr->__data)) return 0; - if (vlan->mode == MACVLAN_MODE_PASSTHRU) { + if (READ_ONCE(vlan->mode) == MACVLAN_MODE_PASSTHRU) { macvlan_set_addr_change(vlan->port); return dev_set_mac_address(vlan->lowerdev, addr, NULL); } @@ -870,7 +874,7 @@ static void update_port_bc_cutoff(struct macvlan_dev *vlan, int cutoff) if (vlan->port->bc_cutoff == cutoff) return; - vlan->port->bc_cutoff = cutoff; + WRITE_ONCE(vlan->port->bc_cutoff, cutoff); macvlan_recompute_bc_filter(vlan); } @@ -1423,7 +1427,7 @@ static int macvlan_changelink_sources(struct macvlan_dev *vlan, u32 mode, entry = macvlan_hash_lookup_source(vlan, addr); if (entry) { macvlan_hash_del_source(entry); - vlan->macaddr_count--; + WRITE_ONCE(vlan->macaddr_count, vlan->macaddr_count - 1); } } else if (mode == MACVLAN_MACADDR_FLUSH) { macvlan_flush_sources(vlan->port, vlan); @@ -1645,11 +1649,12 @@ static int macvlan_changelink(struct net_device *dev, if (err < 0) return err; } - vlan->flags = flags; + WRITE_ONCE(vlan->flags, flags); } if (data && data[IFLA_MACVLAN_BC_QUEUE_LEN]) { - vlan->bc_queue_len_req = nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN]); + WRITE_ONCE(vlan->bc_queue_len_req, + nla_get_u32(data[IFLA_MACVLAN_BC_QUEUE_LEN])); update_port_bc_queue_len(vlan->port); } @@ -1658,7 +1663,7 @@ static int macvlan_changelink(struct net_device *dev, vlan, nla_get_s32(data[IFLA_MACVLAN_BC_CUTOFF])); if (set_mode) - vlan->mode = mode; + WRITE_ONCE(vlan->mode, mode); if (data && data[IFLA_MACVLAN_MACADDR_MODE]) { if (vlan->mode != MACVLAN_MODE_SOURCE) return -EINVAL; @@ -1672,10 +1677,12 @@ static int macvlan_changelink(struct net_device *dev, static size_t macvlan_get_size_mac(const struct macvlan_dev *vlan) { - if (vlan->macaddr_count == 0) + unsigned int macaddr_count = READ_ONCE(vlan->macaddr_count); + + if (!macaddr_count) return 0; return nla_total_size(0) /* IFLA_MACVLAN_MACADDR_DATA */ - + vlan->macaddr_count * nla_total_size(sizeof(u8) * ETH_ALEN); + + macaddr_count * nla_total_size(sizeof(u8) * ETH_ALEN); } static size_t macvlan_get_size(const struct net_device *dev) @@ -1698,53 +1705,75 @@ static int macvlan_fill_info_macaddr(struct sk_buff *skb, const int i) { struct hlist_head *h = &vlan->port->vlan_source_hash[i]; - struct macvlan_source_entry *entry; + const struct macvlan_source_entry *entry; + int cnt = 0; - hlist_for_each_entry_rcu(entry, h, hlist, lockdep_rtnl_is_held()) { + hlist_for_each_entry_rcu(entry, h, hlist) { if (rcu_access_pointer(entry->vlan) != vlan) continue; if (nla_put(skb, IFLA_MACVLAN_MACADDR, ETH_ALEN, entry->addr)) - return 1; + return -EMSGSIZE; + cnt++; } - return 0; + return cnt; } static int macvlan_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct macvlan_dev *vlan = netdev_priv(dev); + const struct macvlan_dev *vlan = netdev_priv(dev); struct macvlan_port *port = vlan->port; - int i; - struct nlattr *nest; + unsigned int macaddr_count = 0; + struct nlattr *nest, *attr; + int bc_cutoff, cnt, i; - if (nla_put_u32(skb, IFLA_MACVLAN_MODE, vlan->mode)) + rcu_read_lock(); + if (nla_put_u32(skb, IFLA_MACVLAN_MODE, READ_ONCE(vlan->mode))) goto nla_put_failure; - if (nla_put_u16(skb, IFLA_MACVLAN_FLAGS, vlan->flags)) + + if (nla_put_u16(skb, IFLA_MACVLAN_FLAGS, READ_ONCE(vlan->flags))) goto nla_put_failure; - if (nla_put_u32(skb, IFLA_MACVLAN_MACADDR_COUNT, vlan->macaddr_count)) + + attr = nla_reserve(skb, IFLA_MACVLAN_MACADDR_COUNT, sizeof(u32)); + if (!attr) goto nla_put_failure; - if (vlan->macaddr_count > 0) { + + if (READ_ONCE(vlan->macaddr_count) > 0) { nest = nla_nest_start_noflag(skb, IFLA_MACVLAN_MACADDR_DATA); if (nest == NULL) goto nla_put_failure; for (i = 0; i < MACVLAN_HASH_SIZE; i++) { - if (macvlan_fill_info_macaddr(skb, vlan, i)) + cnt = macvlan_fill_info_macaddr(skb, vlan, i); + if (cnt < 0) goto nla_put_failure; + macaddr_count += cnt; } - nla_nest_end(skb, nest); + if (!macaddr_count) + nla_nest_cancel(skb, nest); + else if (nla_nest_end_safe(skb, nest) < 0) + goto nla_put_failure; } - if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, vlan->bc_queue_len_req)) + *(u32 *)nla_data(attr) = macaddr_count; + + if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, + READ_ONCE(vlan->bc_queue_len_req))) goto nla_put_failure; + if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN_USED, READ_ONCE(port->bc_queue_len_used))) goto nla_put_failure; - if (port->bc_cutoff != 1 && - nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, port->bc_cutoff)) + + bc_cutoff = READ_ONCE(port->bc_cutoff); + if (bc_cutoff != 1 && + nla_put_s32(skb, IFLA_MACVLAN_BC_CUTOFF, bc_cutoff)) goto nla_put_failure; + + rcu_read_unlock(); return 0; nla_put_failure: + rcu_read_unlock(); return -EMSGSIZE; } diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 862001d09aa8..c1812a98365b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1737,7 +1737,7 @@ static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) netpoll_zap_completion_queue(); repeat: - skb = alloc_skb(len, GFP_ATOMIC); + skb = alloc_skb(len, GFP_ATOMIC | __GFP_NOWARN); if (!skb) skb = netcons_skb_pop(np, len); diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index 56080d3d2d25..a18263d5bb02 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -8,7 +8,7 @@ #include <linux/delay.h> #include <linux/errno.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/gpio/consumer.h> #include <linux/init.h> #include <linux/interrupt.h> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 087ac63f9193..59dfe35afa54 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -153,8 +153,7 @@ static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces); * phylink_set_port_modes() - set the port type modes in the ethtool mask * @mask: ethtool link mode mask * - * Sets all the port type modes in the ethtool mask. MAC drivers should - * use this in their 'validate' callback. + * Sets all the port type modes in the ethtool mask. */ void phylink_set_port_modes(unsigned long *mask) { @@ -2095,9 +2094,9 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, /* * This is the new way of dealing with flow control for PHYs, * as described by Timur Tabi in commit 529ed1275263 ("net: phy: - * phy drivers should not set SUPPORTED_[Asym_]Pause") except - * using our validate call to the MAC, we rely upon the MAC - * clearing the bits from both supported and advertising fields. + * phy drivers should not set SUPPORTED_[Asym_]Pause"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. */ phy_support_asym_pause(phy); @@ -3931,9 +3930,9 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) /* * This is the new way of dealing with flow control for PHYs, * as described by Timur Tabi in commit 529ed1275263 ("net: phy: - * phy drivers should not set SUPPORTED_[Asym_]Pause") except - * using our validate call to the MAC, we rely upon the MAC - * clearing the bits from both supported and advertising fields. + * phy drivers should not set SUPPORTED_[Asym_]Pause"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. */ phy_support_asym_pause(phy); diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c index 93a7b0f6c4e7..583426d06381 100644 --- a/drivers/net/ppp/ppp_async.c +++ b/drivers/net/ppp/ppp_async.c @@ -49,8 +49,6 @@ struct asyncppp { unsigned long xmit_flags; u32 xaccm[8]; u32 raccm; - unsigned int bytes_sent; - unsigned int bytes_rcvd; struct sk_buff *tpkt; int tpkt_pos; diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c index b7f243b416f8..0b1bd1635c39 100644 --- a/drivers/net/ppp/ppp_synctty.c +++ b/drivers/net/ppp/ppp_synctty.c @@ -59,8 +59,6 @@ struct syncppp { unsigned long xmit_flags; u32 xaccm[8]; u32 raccm; - unsigned int bytes_sent; - unsigned int bytes_rcvd; struct sk_buff *tpkt; unsigned long last_xmit; diff --git a/drivers/net/usb/rtl8150.c b/drivers/net/usb/rtl8150.c index c880c95c41a5..d51e43170e03 100644 --- a/drivers/net/usb/rtl8150.c +++ b/drivers/net/usb/rtl8150.c @@ -732,7 +732,9 @@ static void set_carrier(struct net_device *netdev) rtl8150_t *dev = netdev_priv(netdev); short tmp; - get_registers(dev, CSCR, 2, &tmp); + if (get_registers(dev, CSCR, 2, &tmp)) + return; + if (tmp & CSCR_LINK_STATUS) netif_carrier_on(netdev); else diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h index 016c74fb747c..802b143de83d 100644 --- a/include/cxl/cxl.h +++ b/include/cxl/cxl.h @@ -226,4 +226,6 @@ struct cxl_dev_state *_devm_cxl_dev_state_create(struct device *dev, struct cxl_memdev *devm_cxl_probe_mem(struct cxl_dev_state *cxlds, struct range *range); + +int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity); #endif /* __CXL_CXL_H__ */ diff --git a/include/cxl/pci.h b/include/cxl/pci.h new file mode 100644 index 000000000000..3e0000015871 --- /dev/null +++ b/include/cxl/pci.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright(c) 2020 Intel Corporation. All rights reserved. */ + +#ifndef __CXL_CXL_PCI_H__ +#define __CXL_CXL_PCI_H__ + +/* Register Block Identifier (RBI) */ +enum cxl_regloc_type { + CXL_REGLOC_RBI_EMPTY = 0, + CXL_REGLOC_RBI_COMPONENT, + CXL_REGLOC_RBI_VIRT, + CXL_REGLOC_RBI_MEMDEV, + CXL_REGLOC_RBI_PMU, + CXL_REGLOC_RBI_TYPES +}; + +struct cxl_register_map; +struct pci_dev; + +int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type, + struct cxl_register_map *map); +#endif diff --git a/include/linux/mdio.h b/include/linux/mdio.h index f4f9d9609448..300805e66592 100644 --- a/include/linux/mdio.h +++ b/include/linux/mdio.h @@ -8,7 +8,6 @@ #include <uapi/linux/mdio.h> #include <linux/bitfield.h> -#include <linux/mod_devicetable.h> struct gpio_desc; struct mii_bus; diff --git a/include/linux/net.h b/include/linux/net.h index fdcf9956805c..3d82966e2243 100644 --- a/include/linux/net.h +++ b/include/linux/net.h @@ -47,6 +47,29 @@ typedef struct sockopt { int optlen; } sockopt_t; +/* + * Initialize a user-backed sockopt_t from the (optval, optlen) __user pair of + * a getsockopt() callback. Used by transitional __user getsockopt wrappers + * while the proto-layer callbacks are converted to take a sockopt_t; the + * caller writes opt->optlen back to the user optlen after the callback. + */ +static inline int sockopt_init_user(sockopt_t *opt, char __user *optval, + int __user *optlen) +{ + int len; + + if (get_user(len, optlen)) + return -EFAULT; + if (len < 0) + return -EINVAL; + + iov_iter_ubuf(&opt->iter_out, ITER_DEST, optval, len); + iov_iter_ubuf(&opt->iter_in, ITER_SOURCE, optval, len); + opt->optlen = len; + + return 0; +} + struct poll_table_struct; struct pipe_inode_info; struct inode; diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h index efbbfa770d66..e99afc1414cd 100644 --- a/include/linux/netfilter.h +++ b/include/linux/netfilter.h @@ -93,6 +93,7 @@ enum nf_hook_ops_type { NF_HOOK_OP_NF_TABLES, NF_HOOK_OP_BPF, NF_HOOK_OP_NFT_FT, + NF_HOOK_OP_NAT, }; struct nf_hook_ops { @@ -140,6 +141,12 @@ struct nf_hook_entries { */ }; +struct nf_nat_lookup_hook_priv { + struct nf_hook_entries __rcu *entries; + + struct rcu_head rcu_head; +}; + #ifdef CONFIG_NETFILTER static inline struct nf_hook_ops **nf_hook_entries_get_hook_ops(const struct nf_hook_entries *e) { diff --git a/include/linux/netfilter/nf_conntrack_h323.h b/include/linux/netfilter/nf_conntrack_h323.h index 81286c499325..b15f37604cde 100644 --- a/include/linux/netfilter/nf_conntrack_h323.h +++ b/include/linux/netfilter/nf_conntrack_h323.h @@ -9,8 +9,6 @@ #include <net/netfilter/nf_conntrack_expect.h> #include <uapi/linux/netfilter/nf_conntrack_tuple_common.h> -#define RAS_PORT 1719 -#define Q931_PORT 1720 #define H323_RTP_CHANNEL_MAX 4 /* Audio, video, FAX and other */ /* This structure exists only once per master */ diff --git a/include/linux/netfilter/nf_conntrack_pptp.h b/include/linux/netfilter/nf_conntrack_pptp.h index c3bdb4370938..c0b305ce7c3c 100644 --- a/include/linux/netfilter/nf_conntrack_pptp.h +++ b/include/linux/netfilter/nf_conntrack_pptp.h @@ -50,8 +50,6 @@ struct nf_nat_pptp { __be16 pac_call_id; /* NAT'ed PAC call id */ }; -#define PPTP_CONTROL_PORT 1723 - #define PPTP_PACKET_CONTROL 1 #define PPTP_PACKET_MGMT 2 diff --git a/include/linux/netfilter/nf_conntrack_sane.h b/include/linux/netfilter/nf_conntrack_sane.h index 46c7acd1b4a7..8501035d7335 100644 --- a/include/linux/netfilter/nf_conntrack_sane.h +++ b/include/linux/netfilter/nf_conntrack_sane.h @@ -3,8 +3,6 @@ #define _NF_CONNTRACK_SANE_H /* SANE tracking. */ -#define SANE_PORT 6566 - enum sane_state { SANE_STATE_NORMAL, SANE_STATE_START_REQUESTED, diff --git a/include/linux/netfilter/nf_conntrack_tftp.h b/include/linux/netfilter/nf_conntrack_tftp.h index 90b334bbce3c..e3d1739c557d 100644 --- a/include/linux/netfilter/nf_conntrack_tftp.h +++ b/include/linux/netfilter/nf_conntrack_tftp.h @@ -2,8 +2,6 @@ #ifndef _NF_CONNTRACK_TFTP_H #define _NF_CONNTRACK_TFTP_H -#define TFTP_PORT 69 - #include <linux/netfilter.h> #include <linux/skbuff.h> #include <linux/types.h> diff --git a/include/net/bond_3ad.h b/include/net/bond_3ad.h index 05572c19e14b..ef667dff2972 100644 --- a/include/net/bond_3ad.h +++ b/include/net/bond_3ad.h @@ -302,8 +302,8 @@ void bond_3ad_state_machine_handler(struct work_struct *); void bond_3ad_initiate_agg_selection(struct bonding *bond, int timeout); void bond_3ad_adapter_speed_duplex_changed(struct slave *slave); void bond_3ad_handle_link_change(struct slave *slave, char link); -int bond_3ad_get_active_agg_info(struct bonding *bond, struct ad_info *ad_info); -int __bond_3ad_get_active_agg_info(struct bonding *bond, +int bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info); +int __bond_3ad_get_active_agg_info(const struct bonding *bond, struct ad_info *ad_info); int bond_3ad_lacpdu_recv(const struct sk_buff *skb, struct bonding *bond, struct slave *slave); diff --git a/include/net/bonding.h b/include/net/bonding.h index 2c54a36a8477..598d56b1bc97 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -345,14 +345,14 @@ static inline bool bond_mode_uses_primary(int mode) mode == BOND_MODE_ALB; } -static inline bool bond_uses_primary(struct bonding *bond) +static inline bool bond_uses_primary(const struct bonding *bond) { return bond_mode_uses_primary(BOND_MODE(bond)); } -static inline struct net_device *bond_option_active_slave_get_rcu(struct bonding *bond) +static inline struct net_device *bond_option_active_slave_get_rcu(const struct bonding *bond) { - struct slave *slave = rcu_dereference_rtnl(bond->curr_active_slave); + const struct slave *slave = rcu_dereference_rtnl(bond->curr_active_slave); return bond_uses_primary(bond) && slave ? slave->dev : NULL; } @@ -703,7 +703,7 @@ void bond_setup(struct net_device *bond_dev); unsigned int bond_get_num_tx_queues(void); int bond_netlink_init(void); void bond_netlink_fini(void); -struct net_device *bond_option_active_slave_get_rcu(struct bonding *bond); +struct net_device *bond_option_active_slave_get_rcu(const struct bonding *bond); const char *bond_slave_link_status(s8 link); struct bond_vlan_tag *bond_verify_device_path(struct net_device *start_dev, struct net_device *end_dev, diff --git a/include/net/devlink.h b/include/net/devlink.h index dd546dbd57cf..4830aba4087a 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -36,7 +36,7 @@ struct devlink_port_phys_attrs { * struct devlink_port_pci_pf_attrs - devlink port's PCI PF attributes * @controller: Associated controller number * @pf: associated PCI function number for the devlink port instance - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_pf_attrs { u32 controller; @@ -50,7 +50,7 @@ struct devlink_port_pci_pf_attrs { * @pf: associated PCI function number for the devlink port instance * @vf: associated PCI VF number of a PF for the devlink port instance; * VF number starts from 0 for the first PCI virtual function - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_vf_attrs { u32 controller; @@ -64,7 +64,7 @@ struct devlink_port_pci_vf_attrs { * @controller: Associated controller number * @sf: associated SF number of a PF for the devlink port instance * @pf: associated PCI function number for the devlink port instance - * @external: when set, indicates if a port is for an external controller + * @external: when set, indicates if a port is for an external host controller. */ struct devlink_port_pci_sf_attrs { u32 controller; @@ -1594,6 +1594,15 @@ struct devlink_ops { struct devlink_rate *parent, void *priv_child, void *priv_parent, struct netlink_ext_ack *extack); + /* Indicates if cross-device rate nodes are supported. + * This also requires a shared common ancestor object all devices that + * could share rate nodes are nested in. + * If enabled, rate operations may be called on an instance with only + * the common ancestor lock held and *without that instance lock held*. + * It is the driver's responsibility to ensure proper serialization + * with other operations. + */ + bool supported_cross_device_rate_nodes; /** * selftests_check() - queries if selftest is supported * @devlink: devlink instance diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index 7dee0ae616e3..c6b94790fa81 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -82,7 +82,7 @@ struct fib_rules_ops { struct fib_rule_hdr *, struct nlattr **, struct netlink_ext_ack *); - int (*delete)(struct fib_rule *); + void (*delete)(struct fib_rule *); int (*compare)(struct fib_rule *, struct fib_rule_hdr *, struct nlattr **); @@ -93,11 +93,13 @@ struct fib_rules_ops { /* Called after modifications to the rules set, must flush * the route cache if one exists. */ void (*flush_cache)(struct fib_rules_ops *ops); + bool (*need_rtnl)(struct net *net); int nlgroup; struct list_head rules_list; struct module *owner; struct net *fro_net; + struct mutex lock; struct rcu_head rcu; }; diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h index c63a3c4967ae..0a35355fb0f3 100644 --- a/include/net/ip_fib.h +++ b/include/net/ip_fib.h @@ -302,7 +302,8 @@ static inline struct fib_table *fib_get_table(struct net *net, u32 id) &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] : &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]; - tb_hlist = rcu_dereference_rtnl(hlist_first_rcu(ptr)); + /* Only fib4_rules_init() adds fib_table. */ + tb_hlist = rcu_dereference_protected(hlist_first_rcu(ptr), true); return hlist_entry(tb_hlist, struct fib_table, tb_hlist); } diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 417ff51f62fc..1737fbfce2d0 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -2124,7 +2124,7 @@ void ip_vs_update_conntrack(struct sk_buff *skb, struct ip_vs_conn *cp, int outin); int ip_vs_confirm_conntrack(struct sk_buff *skb); void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct, - struct ip_vs_conn *cp, u_int8_t proto, + struct ip_vs_conn *cp, u8 proto, const __be16 port, int from_rs); void ip_vs_conn_drop_conntrack(struct ip_vs_conn *cp); diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 0c395917b214..8529cef0d7c4 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -47,6 +47,7 @@ enum gdma_queue_type { GDMA_RQ, GDMA_CQ, GDMA_EQ, + GDMA_DIM, }; enum gdma_work_request_flags { @@ -126,6 +127,17 @@ union gdma_doorbell_entry { u64 tail_ptr : 31; u64 arm : 1; } eq; + + struct { + u64 id : 24; + u64 reserved : 8; + u64 mod_usec : 10; + u64 reserve1 : 5; + u64 mod_usec_vld : 1; + u64 mod_comps : 8; + u64 reserve2 : 7; + u64 mod_comps_vld: 1; + } dim; }; /* HW DATA */ struct gdma_msg_hdr { @@ -502,6 +514,9 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit); int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type); +void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld, + u32 mod_comps, bool mod_comps_vld); + struct gdma_wqe { u32 reserved :24; u32 last_vbytes :8; @@ -650,6 +665,9 @@ enum { /* Driver supports self recovery on Hardware Channel timeouts */ #define GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY BIT(25) +/* Driver supports dynamic interrupt moderation - DIM */ +#define GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(28) + #define GDMA_DRV_CAP_FLAGS1 \ (GDMA_DRV_CAP_FLAG_1_EQ_SHARING_MULTI_VPORT | \ GDMA_DRV_CAP_FLAG_1_NAPI_WKDONE_FIX | \ @@ -665,7 +683,8 @@ enum { GDMA_DRV_CAP_FLAG_1_PROBE_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \ - GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT) + GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT | \ + GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) #define GDMA_DRV_CAP_FLAGS2 0 @@ -701,6 +720,9 @@ struct gdma_verify_ver_req { u8 os_ver_str4[128]; }; /* HW DATA */ +/* HW supports dynamic interrupt moderation - DIM */ +#define GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(15) + struct gdma_verify_ver_resp { struct gdma_resp_hdr hdr; u64 gdma_protocol_ver; diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 04acb6791dbd..226b61504596 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -4,6 +4,7 @@ #ifndef _MANA_H #define _MANA_H +#include <linux/dim.h> #include <net/xdp.h> #include <net/net_shaper.h> @@ -64,6 +65,19 @@ enum TRI_STATE { /* Maximum number of packets per coalesced CQE */ #define MANA_RXCOMP_OOB_NUM_PPI 4 +/* Default/max interrupt moderation settings */ +#define MANA_INTR_MODR_USEC_DEF 0 +#define MANA_INTR_MODR_COMP_DEF 0 + +#define MANA_ADAPTIVE_RX_DEF true +#define MANA_ADAPTIVE_TX_DEF true + +/* DIM doorbell value field layout */ +#define MANA_INTR_MODR_USEC_MAX GENMASK(9, 0) +#define MANA_INTR_MODR_USEC_VLD BIT(15) +#define MANA_INTR_MODR_COMP_MAX GENMASK(7, 0) +#define MANA_INTR_MODR_COMP_MASK GENMASK(23, 16) + /* Update this count whenever the respective structures are changed */ #define MANA_STATS_RX_COUNT (6 + MANA_RXCOMP_OOB_NUM_PPI - 1) #define MANA_STATS_TX_COUNT 11 @@ -297,6 +311,17 @@ struct mana_cq { int work_done; int work_done_since_doorbell; int budget; + + /* DIM - Dynamic Interrupt Moderation */ + struct dim dim; + u16 dim_event_ctr; + + /* Cumulative TX completions fed to DIM. Updated and read only in + * NAPI context (mana_poll_tx_cq() / mana_update_tx_dim()), so they + * measure the hardware completion rate and need no u64_stats_sync. + */ + u64 tx_dim_pkts; + u64 tx_dim_bytes; }; struct mana_recv_buf_oob { @@ -581,6 +606,15 @@ struct mana_port_context { u8 cqe_coalescing_enable; u32 cqe_coalescing_timeout_ns; + /* Interrupt moderation settings */ + u16 intr_modr_rx_usec; + u16 intr_modr_rx_comp; + u16 intr_modr_tx_usec; + u16 intr_modr_tx_comp; + + bool rx_dim_enabled; + bool tx_dim_enabled; + struct mana_ethtool_stats eth_stats; struct mana_ethtool_phy_stats phy_stats; @@ -606,6 +640,8 @@ int mana_alloc_queues(struct net_device *ndev); int mana_attach(struct net_device *ndev); int mana_detach(struct net_device *ndev, bool from_close); +void mana_dim_change(struct mana_cq *cq, bool enable); + int mana_probe(struct gdma_dev *gd, bool resuming); void mana_remove(struct gdma_dev *gd, bool suspending); @@ -641,6 +677,9 @@ struct mana_obj_spec { u32 queue_size; u32 attached_eq; u32 modr_ctx_id; + u8 req_cq_moderation; + u16 cq_moderation_comp; + u16 cq_moderation_usec; }; enum mana_command_code { @@ -772,6 +811,15 @@ struct mana_create_wqobj_req { u32 cq_size; u32 cq_moderation_ctx_id; u32 cq_parent_qid; + + /* V2 */ + u8 allow_rqwqe_chain; + + /* V3 */ + u8 req_cq_moderation; + u16 cq_moderation_comp; + u16 cq_moderation_usec; + u8 reserved2[2]; }; /* HW DATA */ struct mana_create_wqobj_resp { @@ -779,6 +827,12 @@ struct mana_create_wqobj_resp { u32 wq_id; u32 cq_id; mana_handle_t wq_obj; + + /* V2 */ + u16 cq_moderation_comp; + u16 cq_moderation_usec; + u8 cq_moderation_enabled; + u8 reserved1[3]; }; /* HW DATA */ /* Destroy WQ Object */ diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index c761cd8158b2..bc5427d239f4 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -43,11 +43,10 @@ struct nf_conntrack_helper { refcount_t ct_refcnt; - /* Tuple of things we will help (compared against server response) */ - struct nf_conntrack_tuple tuple; + u8 nfproto; /* NFPROTO_*, can be NFPROTO_UNSPEC */ + u8 l4proto; /* IPPROTO_UDP/TCP */ - /* Function to call when data passes; return verdict, or -1 to - invalidate. */ + /* Function to call when data passes; return verdict */ int __rcu (*help)(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct, enum ip_conntrack_info conntrackinfo); @@ -94,8 +93,7 @@ struct nf_conntrack_helper *nf_conntrack_helper_try_module_get(const char *name, void nf_conntrack_helper_put(struct nf_conntrack_helper *helper); void nf_ct_helper_init(struct nf_conntrack_helper *helper, - u16 l3num, u16 protonum, const char *name, - u16 default_port, u16 spec_port, u32 id, + u8 l3num, u16 protonum, const char *name, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, int (*help)(struct sk_buff *skb, unsigned int protoff, diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index 6e27c56514df..cb7f8bf15671 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -118,6 +118,7 @@ struct netns_ipv4 { struct fib_rules_ops *rules_ops; struct fib_table __rcu *fib_main; struct fib_table __rcu *fib_default; + spinlock_t fib_table_hash_lock; unsigned int fib_rules_require_fldissect; bool fib_has_custom_rules; #endif diff --git a/include/net/udp.h b/include/net/udp.h index 8262e2b215b4..1fee17274745 100644 --- a/include/net/udp.h +++ b/include/net/udp.h @@ -430,7 +430,7 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb, netdev_features_t features, bool is_ipv6); int udp_lib_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen); + sockopt_t *opt); int udp_lib_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval, unsigned int optlen, int (*push_pending_frames)(struct sock *)); diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index ca713bcc47b9..a6801feb7744 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -648,6 +648,8 @@ enum devlink_attr { DEVLINK_ATTR_INDEX, /* uint */ DEVLINK_ATTR_RESOURCE_SCOPE_MASK, /* u32 */ + DEVLINK_ATTR_PARENT_DEV, /* nested */ + /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate * net/devlink/netlink_gen.c. diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 55eaa82f5f98..85b898b1db5e 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -129,6 +129,9 @@ enum dpll_type { * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock + * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator. + * When connected as a DPLL input, the DPLL enters NCO mode where the output + * frequency is adjusted by the host via the PTP clock interface. */ enum dpll_pin_type { DPLL_PIN_TYPE_MUX = 1, @@ -136,6 +139,7 @@ enum dpll_pin_type { DPLL_PIN_TYPE_SYNCE_ETH_PORT, DPLL_PIN_TYPE_INT_OSCILLATOR, DPLL_PIN_TYPE_GNSS, + DPLL_PIN_TYPE_INT_NCO, /* private: */ __DPLL_PIN_TYPE_MAX, @@ -208,11 +212,15 @@ enum dpll_pin_operstate { * @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed * @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed * @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed + * @DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE: pin state can be set to + * connected regardless of current DPLL device mode, overriding the active + * input selection. Requires state-can-change to be set as well. */ enum dpll_pin_capabilities { DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1, DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2, DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4, + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE = 8, }; #define DPLL_PHASE_OFFSET_DIVIDER 1000 diff --git a/include/uapi/linux/if_addr.h b/include/uapi/linux/if_addr.h index aa7958b4e41d..7fb630b7fe31 100644 --- a/include/uapi/linux/if_addr.h +++ b/include/uapi/linux/if_addr.h @@ -36,6 +36,7 @@ enum { IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ IFA_TARGET_NETNSID, IFA_PROTO, /* u8, address protocol */ + IFA_MC_USERS, /* u32, multicast group users */ __IFA_MAX, }; diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index bb2f012b454e..22622283f59b 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -404,23 +404,14 @@ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, /* send a batman ogm packet */ static void batadv_iv_ogm_emit(struct batadv_forw_packet *forw_packet) { - struct net_device *mesh_iface; - if (!forw_packet->if_incoming) { pr_err("Error - can't forward packet: incoming iface not specified\n"); return; } - mesh_iface = forw_packet->if_incoming->mesh_iface; - if (WARN_ON(!forw_packet->if_outgoing)) return; - if (forw_packet->if_outgoing->mesh_iface != mesh_iface) { - pr_warn("%s: mesh interface switch for queued OGM\n", __func__); - return; - } - if (forw_packet->if_incoming->if_status != BATADV_IF_ACTIVE) return; @@ -910,8 +901,7 @@ out: static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) return; mutex_lock(&hard_iface->bat_iv.ogm_buff_mutex); diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index 4841f0f1a9b1..262e40040007 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -90,12 +90,6 @@ static bool batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh, u32 throughput; int ret; - /* don't query throughput when no longer associated with any - * batman-adv interface - */ - if (!mesh_iface) - return false; - /* if the user specified a customised value for this interface, then * return it directly */ @@ -311,8 +305,7 @@ static void batadv_v_elp_periodic_work(struct work_struct *work) goto out; /* we are in the process of shutting this interface down */ - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) goto out; /* the interface was enabled but may not be ready yet */ diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c index 037921aad35d..e921d49f7ece 100644 --- a/net/batman-adv/bat_v_ogm.c +++ b/net/batman-adv/bat_v_ogm.c @@ -115,14 +115,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv) /** * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to send * @hard_iface: the interface to use to send the OGM */ -static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_send_to_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); + if (hard_iface->if_status != BATADV_IF_ACTIVE) { kfree_skb(skb); return; @@ -189,7 +189,6 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) /** * batadv_v_ogm_aggr_send() - flush & send aggregation queue - * @bat_priv: the bat priv with all the mesh interface information * @hard_iface: the interface with the aggregation queue to flush * * Aggregates all OGMv2 packets currently in the aggregation queue into a @@ -199,8 +198,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) * * Caller needs to hold the hard_iface->bat_v.aggr_list.lock. */ -static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, - struct batadv_hard_iface *hard_iface) +static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface) { unsigned int aggr_len = hard_iface->bat_v.aggr_len; struct sk_buff *skb_aggr; @@ -230,26 +228,21 @@ static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, consume_skb(skb); } - batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface); + batadv_v_ogm_send_to_if(skb_aggr, hard_iface); } /** * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to queue * @hard_iface: the interface to queue the OGM on */ -static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_queue_on_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { - if (hard_iface->mesh_iface != bat_priv->mesh_iface) { - kfree_skb(skb); - return; - } + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); if (!READ_ONCE(bat_priv->aggregated_ogms)) { - batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface); + batadv_v_ogm_send_to_if(skb, hard_iface); return; } @@ -260,7 +253,7 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, } if (!batadv_v_ogm_queue_left(skb, hard_iface)) - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb); __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb); @@ -357,7 +350,7 @@ static void batadv_v_ogm_send_meshif(struct batadv_priv *bat_priv) break; } - batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface); + batadv_v_ogm_queue_on_if(skb_tmp, hard_iface); batadv_hardif_put(hard_iface); } rcu_read_unlock(); @@ -397,14 +390,12 @@ void batadv_v_ogm_aggr_work(struct work_struct *work) { struct batadv_hard_iface_bat_v *batv; struct batadv_hard_iface *hard_iface; - struct batadv_priv *bat_priv; batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work); hard_iface = container_of(batv, struct batadv_hard_iface, bat_v); - bat_priv = netdev_priv(hard_iface->mesh_iface); spin_lock_bh(&hard_iface->bat_v.aggr_list.lock); - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock); batadv_v_ogm_start_queue_timer(hard_iface); @@ -601,7 +592,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv, if_outgoing->net_dev->name, ntohl(ogm_forward->throughput), ogm_forward->ttl, if_incoming->net_dev->name); - batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing); + batadv_v_ogm_queue_on_if(skb, if_outgoing); out: batadv_orig_ifinfo_put(orig_ifinfo); diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index 5c73f6ba16cf..f9a1fadf8de9 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -344,7 +344,6 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, struct sk_buff *skb; struct ethhdr *ethhdr; struct batadv_hard_iface *primary_if; - struct net_device *mesh_iface; u8 *hw_src; struct batadv_bla_claim_dst local_claim_dest; __be32 zeroip = 0; @@ -357,14 +356,10 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, sizeof(local_claim_dest)); local_claim_dest.type = claimtype; - mesh_iface = READ_ONCE(primary_if->mesh_iface); - if (!mesh_iface) - goto out; - skb = arp_create(ARPOP_REPLY, ETH_P_ARP, /* IP DST: 0.0.0.0 */ zeroip, - mesh_iface, + primary_if->mesh_iface, /* IP SRC: 0.0.0.0 */ zeroip, /* Ethernet DST: Broadcast */ @@ -442,7 +437,7 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, } skb_reset_mac_header(skb); - skb->protocol = eth_type_trans(skb, mesh_iface); + skb->protocol = eth_type_trans(skb, primary_if->mesh_iface); batadv_inc_counter(bat_priv, BATADV_CNT_RX); batadv_add_counter(bat_priv, BATADV_CNT_RX_BYTES, skb->len + ETH_HLEN); diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 03d01c20a954..b6867576bbaf 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -63,6 +63,7 @@ void batadv_hardif_release(struct kref *ref) struct batadv_hard_iface *hard_iface; hard_iface = container_of(ref, struct batadv_hard_iface, refcount); + netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); netdev_put(hard_iface->net_dev, &hard_iface->dev_tracker); kfree_rcu(hard_iface, rcu); @@ -75,21 +76,21 @@ void batadv_hardif_release(struct kref *ref) * Return: batadv_hard_iface of net_dev (with increased refcnt), NULL on errors */ struct batadv_hard_iface * -batadv_hardif_get_by_netdev(const struct net_device *net_dev) +batadv_hardif_get_by_netdev(struct net_device *net_dev) { struct batadv_hard_iface *hard_iface; + struct net_device *mesh_iface; - rcu_read_lock(); - list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) { - if (hard_iface->net_dev == net_dev && - kref_get_unless_zero(&hard_iface->refcount)) - goto out; - } + ASSERT_RTNL(); - hard_iface = NULL; + mesh_iface = netdev_master_upper_dev_get(net_dev); + if (!mesh_iface || !batadv_meshif_is_valid(mesh_iface)) + return NULL; + + hard_iface = netdev_lower_dev_get_private(mesh_iface, net_dev); + if (!kref_get_unless_zero(&hard_iface->refcount)) + return NULL; -out: - rcu_read_unlock(); return hard_iface; } @@ -245,7 +246,7 @@ struct net_device *__batadv_get_real_netdev(struct net_device *netdev) } hard_iface = batadv_hardif_get_by_netdev(netdev); - if (!hard_iface || !hard_iface->mesh_iface) + if (!hard_iface) goto out; net = dev_net(hard_iface->mesh_iface); @@ -539,16 +540,10 @@ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_ifa const struct batadv_hard_iface *tmp_hard_iface; struct list_head *iter; - if (!mesh_iface) - return; - netdev_for_each_lower_private(mesh_iface, tmp_hard_iface, iter) { if (tmp_hard_iface == hard_iface) continue; - if (tmp_hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - if (!batadv_compare_eth(tmp_hard_iface->net_dev->dev_addr, hard_iface->net_dev->dev_addr)) continue; @@ -574,9 +569,6 @@ static void batadv_hardif_recalc_extra_skbroom(struct net_device *mesh_iface) rcu_read_lock(); netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - lower_header_len = max_t(unsigned short, lower_header_len, hard_iface->net_dev->hard_header_len); @@ -723,38 +715,63 @@ batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) } /** - * batadv_hardif_enable_interface() - Enslave hard interface to mesh interface - * @hard_iface: hard interface to add to mesh interface + * batadv_hardif_enable_interface() - Enslave interface to mesh interface + * @net_dev: netdev struct of the interface to add to mesh interface * @mesh_iface: netdev struct of the mesh interface * * Return: 0 on success or negative error number in case of failure */ -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface) { struct batadv_priv *bat_priv; __be16 ethertype = htons(ETH_P_BATMAN); int max_header_len = batadv_max_header_len(); + struct batadv_hard_iface *hard_iface; unsigned int required_mtu; unsigned int hardif_mtu; bool fragmentation; int ret; - hardif_mtu = READ_ONCE(hard_iface->net_dev->mtu); + ASSERT_RTNL(); + + if (!batadv_is_valid_iface(net_dev)) + return -EINVAL; + + hardif_mtu = READ_ONCE(net_dev->mtu); required_mtu = READ_ONCE(mesh_iface->mtu) + max_header_len; if (hardif_mtu < ETH_MIN_MTU + max_header_len) return -EINVAL; - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - goto out; + hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); + if (!hard_iface) + return -ENOMEM; - kref_get(&hard_iface->refcount); + netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); + hard_iface->net_dev = net_dev; + + hard_iface->if_status = BATADV_IF_INACTIVE; + + INIT_HLIST_HEAD(&hard_iface->neigh_list); + + mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); + spin_lock_init(&hard_iface->neigh_list_lock); + kref_init(&hard_iface->refcount); + + hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; + if (batadv_is_wifi_hardif(hard_iface)) + hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; + + WRITE_ONCE(hard_iface->hop_penalty, 0); + + batadv_v_hardif_init(hard_iface); netdev_hold(mesh_iface, &hard_iface->meshif_dev_tracker, GFP_ATOMIC); hard_iface->mesh_iface = mesh_iface; bat_priv = netdev_priv(hard_iface->mesh_iface); + bat_priv->hardif_generation++; ret = netdev_master_upper_dev_link(hard_iface->net_dev, mesh_iface, hard_iface, NULL, NULL); if (ret) @@ -764,9 +781,6 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (ret < 0) goto err_upper; - hard_iface->if_status = BATADV_IF_INACTIVE; - - kref_get(&hard_iface->refcount); hard_iface->batman_adv_ptype.type = ethertype; hard_iface->batman_adv_ptype.func = batadv_batman_skb_recv; hard_iface->batman_adv_ptype.dev = hard_iface->net_dev; @@ -802,14 +816,11 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (bat_priv->algo_ops->iface.enabled) bat_priv->algo_ops->iface.enabled(hard_iface); -out: return 0; err_upper: netdev_upper_dev_unlink(hard_iface->net_dev, mesh_iface); err_dev: - hard_iface->mesh_iface = NULL; - netdev_put(mesh_iface, &hard_iface->meshif_dev_tracker); batadv_hardif_put(hard_iface); return ret; } @@ -821,17 +832,18 @@ err_dev: void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); - struct batadv_hard_iface *primary_if = NULL; + struct batadv_hard_iface *primary_if; + + ASSERT_RTNL(); batadv_hardif_deactivate_interface(hard_iface); if (hard_iface->if_status != BATADV_IF_INACTIVE) - goto out; + return; batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); dev_remove_pack(&hard_iface->batman_adv_ptype); - batadv_hardif_put(hard_iface); primary_if = batadv_primary_if_get_selected(bat_priv); if (hard_iface == primary_if) { @@ -842,15 +854,16 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(new_if); } + batadv_hardif_put(primary_if); bat_priv->algo_ops->iface.disable(hard_iface); - hard_iface->if_status = BATADV_IF_NOT_IN_USE; + hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; /* delete all references to this hard_iface */ batadv_purge_orig_ref(bat_priv); batadv_purge_outstanding_packets(bat_priv, hard_iface); - netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); + bat_priv->hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface); @@ -858,67 +871,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) if (list_empty(&hard_iface->mesh_iface->adj_list.lower)) batadv_gw_check_client_stop(bat_priv); - hard_iface->mesh_iface = NULL; - batadv_hardif_put(hard_iface); - -out: - batadv_hardif_put(primary_if); -} - -static struct batadv_hard_iface * -batadv_hardif_add_interface(struct net_device *net_dev) -{ - struct batadv_hard_iface *hard_iface; - - ASSERT_RTNL(); - - if (!batadv_is_valid_iface(net_dev)) - return NULL; - - hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); - if (!hard_iface) - return NULL; - - netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); - hard_iface->net_dev = net_dev; - - hard_iface->mesh_iface = NULL; - hard_iface->if_status = BATADV_IF_NOT_IN_USE; - - INIT_LIST_HEAD(&hard_iface->list); - INIT_HLIST_HEAD(&hard_iface->neigh_list); - - mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); - spin_lock_init(&hard_iface->neigh_list_lock); - kref_init(&hard_iface->refcount); - - hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; - if (batadv_is_wifi_hardif(hard_iface)) - hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; - - WRITE_ONCE(hard_iface->hop_penalty, 0); - - batadv_v_hardif_init(hard_iface); - - kref_get(&hard_iface->refcount); - list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); - batadv_hardif_generation++; - - return hard_iface; -} - -static void batadv_hardif_remove_interface(struct batadv_hard_iface *hard_iface) -{ - ASSERT_RTNL(); - - /* first deactivate interface */ - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - batadv_hardif_disable_interface(hard_iface); - - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - return; - - hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; batadv_hardif_put(hard_iface); } @@ -1082,10 +1034,6 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_wifi_net_device_event(event, net_dev); hard_iface = batadv_hardif_get_by_netdev(net_dev); - if (!hard_iface && (event == NETDEV_REGISTER || - event == NETDEV_POST_TYPE_CHANGE)) - hard_iface = batadv_hardif_add_interface(net_dev); - if (!hard_iface) goto out; @@ -1099,19 +1047,12 @@ static int batadv_hard_if_event(struct notifier_block *this, break; case NETDEV_UNREGISTER: case NETDEV_PRE_TYPE_CHANGE: - list_del_rcu(&hard_iface->list); - batadv_hardif_generation++; - - batadv_hardif_remove_interface(hard_iface); + batadv_hardif_disable_interface(hard_iface); break; case NETDEV_CHANGEMTU: - if (hard_iface->mesh_iface) - batadv_update_min_mtu(hard_iface->mesh_iface); + batadv_update_min_mtu(hard_iface->mesh_iface); break; case NETDEV_CHANGEADDR: - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - goto hardif_put; - batadv_check_known_mac_addr(hard_iface); bat_priv = netdev_priv(hard_iface->mesh_iface); diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index af31696c3978..935f47ca9a48 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -22,12 +22,6 @@ */ enum batadv_hard_if_state { /** - * @BATADV_IF_NOT_IN_USE: interface is not used as slave interface of a - * batman-adv mesh interface - */ - BATADV_IF_NOT_IN_USE, - - /** * @BATADV_IF_TO_BE_REMOVED: interface will be removed from mesh * interface */ @@ -74,8 +68,8 @@ u32 batadv_netdev_get_wifi_flags(struct net_device *net_dev); u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface); bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface); struct batadv_hard_iface* -batadv_hardif_get_by_netdev(const struct net_device *net_dev); -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +batadv_hardif_get_by_netdev(struct net_device *net_dev); +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface); void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface); int batadv_hardif_min_mtu(struct net_device *mesh_iface); diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 67bed3ee77e7..73becb054948 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -59,11 +59,6 @@ #include "tp_meter.h" #include "translation-table.h" -/* List manipulations on hardif_list have to be rtnl_lock()'ed, - * list traversals just rcu-locked - */ -struct list_head batadv_hardif_list; -unsigned int batadv_hardif_generation; static int (*batadv_rx_handler[256])(struct sk_buff *skb, struct batadv_hard_iface *recv_if); @@ -95,7 +90,6 @@ static int __init batadv_init(void) if (ret < 0) return ret; - INIT_LIST_HEAD(&batadv_hardif_list); batadv_algo_init(); batadv_recv_handler_init(); @@ -458,9 +452,6 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, if (unlikely(skb->mac_len != ETH_HLEN || !skb_mac_header(skb))) goto err_free; - if (!hard_iface->mesh_iface) - goto err_free; - bat_priv = netdev_priv(hard_iface->mesh_iface); if (READ_ONCE(bat_priv->mesh_state) != BATADV_MESH_ACTIVE) diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h index f68fc8b7239c..e738758ee4a7 100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@ -226,9 +226,6 @@ static inline int batadv_print_vid(unsigned short vid) return -1; } -extern struct list_head batadv_hardif_list; -extern unsigned int batadv_hardif_generation; - extern struct workqueue_struct *batadv_event_workqueue; int batadv_mesh_init(struct net_device *mesh_iface); diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index fbfd99268de4..1dc6cfae9e9a 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -840,18 +840,7 @@ static int batadv_meshif_slave_add(struct net_device *dev, struct net_device *slave_dev, struct netlink_ext_ack *extack) { - struct batadv_hard_iface *hard_iface; - int ret = -EINVAL; - - hard_iface = batadv_hardif_get_by_netdev(slave_dev); - if (!hard_iface || hard_iface->mesh_iface) - goto out; - - ret = batadv_hardif_enable_interface(hard_iface, dev); - -out: - batadv_hardif_put(hard_iface); - return ret; + return batadv_hardif_enable_interface(slave_dev, dev); } /** diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index 4cf9e3c54ad3..d2bc48c70714 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -968,7 +968,7 @@ batadv_netlink_dump_hardif(struct sk_buff *msg, struct netlink_callback *cb) bat_priv = netdev_priv(mesh_iface); rtnl_lock(); - cb->seq = batadv_hardif_generation << 1 | 1; + cb->seq = bat_priv->hardif_generation << 1 | 1; netdev_for_each_lower_private(mesh_iface, hard_iface, iter) { if (i++ < skip) @@ -1211,7 +1211,9 @@ batadv_netlink_get_hardif_from_ifindex(struct batadv_priv *bat_priv, if (!hard_dev) return ERR_PTR(-ENODEV); + rtnl_lock(); hard_iface = batadv_hardif_get_by_netdev(hard_dev); + rtnl_unlock(); if (!hard_iface) goto err_put_harddev; diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 9b38bd9e8da7..48f837cf665a 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -1033,7 +1033,6 @@ batadv_purge_neigh_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1077,7 +1076,6 @@ batadv_purge_orig_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1127,10 +1125,8 @@ batadv_purge_orig_neighbors(struct batadv_priv *bat_priv, if (batadv_has_timed_out(last_seen, BATADV_PURGE_TIMEOUT) || if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) { if (if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) batadv_dbg(BATADV_DBG_BATMAN, bat_priv, "neighbor purge: originator %pM, neighbor: %pM, iface: %s\n", diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index c2eea7dbc488..00467aa79de9 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -358,28 +358,16 @@ batadv_tp_list_find_sender_session(struct batadv_priv *bat_priv, const u8 *dst, } /** - * batadv_tp_vars_common_release() - release batadv_tp_vars_common from lists + * batadv_tp_sender_release() - release batadv_tp_sender * and queue for free after rcu grace period - * @ref: kref pointer of the batadv_tp_vars_common + * @ref: kref pointer of the batadv_tp_sender */ -static void batadv_tp_vars_common_release(struct kref *ref) +static void batadv_tp_sender_release(struct kref *ref) { - struct batadv_tp_vars_common *tp_vars; - struct batadv_tp_unacked *un, *safe; - - tp_vars = container_of(ref, struct batadv_tp_vars_common, refcount); - - /* lock should not be needed because this object is now out of any - * context! - */ - spin_lock_bh(&tp_vars->unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { - list_del(&un->list); - kfree(un); - } - spin_unlock_bh(&tp_vars->unacked_lock); + struct batadv_tp_sender *tp_vars; - kfree_rcu(tp_vars, rcu); + tp_vars = container_of(ref, struct batadv_tp_sender, common.refcount); + kfree_rcu(tp_vars, common.rcu); } /** @@ -392,7 +380,7 @@ static void batadv_tp_sender_put(struct batadv_tp_sender *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_sender_release); } /** @@ -1145,9 +1133,6 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst, init_waitqueue_head(&tp_vars->more_bytes); init_completion(&tp_vars->finished); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - spin_lock_init(&tp_vars->cc_lock); tp_vars->prerandom_offset = 0; @@ -1252,6 +1237,33 @@ batadv_tp_list_find_receiver_session(struct batadv_priv *bat_priv, const u8 *dst } /** + * batadv_tp_receiver_release() - release batadv_tp_receiver + * and queue for free after rcu grace period + * @ref: kref pointer of the batadv_tp_receiver + */ +static void batadv_tp_receiver_release(struct kref *ref) +{ + struct batadv_tp_receiver *tp_vars; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; + + tp_vars = container_of(ref, struct batadv_tp_receiver, common.refcount); + + /* lock should not be needed because this object is now out of any + * context! + */ + spin_lock_bh(&tp_vars->ack_seqno_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { + list_del(&un->list); + kfree(un); + tp_vars->unacked_count--; + } + spin_unlock_bh(&tp_vars->ack_seqno_lock); + + kfree_rcu(tp_vars, common.rcu); +} + +/** * batadv_tp_receiver_put() - decrement the batadv_tp_receiver * refcounter and possibly release it * @tp_vars: the private data of the current TP meter session to be free'd @@ -1261,7 +1273,7 @@ static void batadv_tp_receiver_put(struct batadv_tp_receiver *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_receiver_release); } /** @@ -1304,13 +1316,13 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t) if (batadv_tp_list_detach(&tp_vars->common)) batadv_tp_receiver_put(tp_vars); - spin_lock_bh(&tp_vars->common.unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + spin_lock_bh(&tp_vars->ack_seqno_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* drop reference of timer */ if (WARN_ON(atomic_xchg(&tp_vars->receiving, 0) != 1)) @@ -1403,69 +1415,106 @@ out: */ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { - struct batadv_tp_unacked *un, *new; - bool added = false; - - new = kmalloc_obj(*new, GFP_ATOMIC); - if (unlikely(!new)) - return false; - - new->seqno = seqno; - new->len = payload_len; - - /* if the list is empty immediately attach this new object */ - if (list_empty(&tp_vars->common.unacked_list)) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; - return true; - } - - /* otherwise loop over the list and either drop the packet because this - * is a duplicate or store it at the right position. + struct list_head *pos = &tp_vars->unacked_list; + struct batadv_tp_unacked *new = NULL; + u32 end_seqno = seqno + payload_len; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; + + /* loop over the list to find either an existing entry which the new + * seqno range can be merged with or the position at which a new entry + * has to be inserted. * * The iteration is done in the reverse way because it is likely that * the last received packet (the one being processed now) has a bigger * seqno than all the others already stored. */ - list_for_each_entry_reverse(un, &tp_vars->common.unacked_list, list) { - /* check for duplicates */ - if (new->seqno == un->seqno) { - if (new->len > un->len) - un->len = new->len; - kfree(new); - added = true; - break; - } - - /* look for the right position */ - if (batadv_seq_before(new->seqno, un->seqno)) + list_for_each_entry_reverse(un, &tp_vars->unacked_list, list) { + /* look for the right position - an un which is smaller */ + if (batadv_seq_before(seqno, un->seqno)) continue; - /* as soon as an entry having a bigger seqno is found, the new - * one is attached _after_ it. In this way the list is kept in - * ascending order + /* smaller/equal seqno was found but they might be directly + * after another or overlapping. keep only a single entry + * + * It is already known that: + * + * un->seqno <= seqno + * + * When establishing that: + * + * seqno <= un->seqno + un->len + * + * Then it is not necessary to add a new entry because the + * smaller/equal seqno of un might already contain the new + * received packet or we only add new data directly after + * the end of un. The latter can be identified using: + * + * un->seqno + un->len <= end_seqno */ - list_add(&new->list, &un->list); - added = true; - tp_vars->common.unacked_count++; + if (!batadv_seq_before(un->seqno + un->len, seqno)) { + /* new data directly after un? */ + if (!batadv_seq_before(end_seqno, un->seqno + un->len)) + un->len = end_seqno - un->seqno; + + /* un now represents both old un + new range and has to + * be used to check if the gap to the next seqno range + * was closed + */ + new = un; + } else { + /* as soon as an entry having a smaller seqno is found, + * the new one is attached _after_ it. In this way the + * list is kept in ascending order + */ + pos = &un->list; + } + break; } - /* received packet with smallest seqno out of order; add it to front */ - if (!added) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; + /* no entry to merge with was found; insert a new one after the entry + * with the next smaller seqno (or at the front of the list when the + * new seqno is the smallest or the list is empty) + */ + if (!new) { + new = kmalloc_obj(*new, GFP_ATOMIC); + if (unlikely(!new)) + return false; + + new->seqno = seqno; + new->len = payload_len; + + list_add(&new->list, pos); + tp_vars->unacked_count++; + } + + /* check if new filled the gap to the next list entries */ + un = new; + list_for_each_entry_safe_continue(un, safe, &tp_vars->unacked_list, list) { + if (batadv_seq_before(end_seqno, un->seqno)) + break; + + /* next entry is overlapping or adjacent - combine both */ + if (batadv_seq_before(end_seqno, un->seqno + un->len)) { + end_seqno = un->seqno + un->len; + new->len = end_seqno - new->seqno; + } + + list_del(&un->list); + kfree(un); + tp_vars->unacked_count--; } /* remove the last (biggest) unacked seqno when list is too large */ - if (tp_vars->common.unacked_count > BATADV_TP_MAX_UNACKED) { - un = list_last_entry(&tp_vars->common.unacked_list, + if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) { + un = list_last_entry(&tp_vars->unacked_list, struct batadv_tp_unacked, list); list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } return true; @@ -1477,7 +1526,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * @tp_vars: the private data of the current TP meter session */ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { struct batadv_tp_unacked *un, *safe; u32 to_ack; @@ -1485,7 +1534,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) /* go through the unacked packet list and possibly ACK them as * well */ - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { /* the list is ordered, therefore it is possible to stop as soon * there is a gap between the last acked seqno and the seqno of * the packet under inspection @@ -1493,14 +1542,14 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) if (batadv_seq_before(tp_vars->last_recv, un->seqno)) break; - to_ack = un->seqno + un->len - tp_vars->last_recv; + to_ack = un->seqno + un->len; - if (batadv_seq_before(tp_vars->last_recv, un->seqno + un->len)) - tp_vars->last_recv += to_ack; + if (batadv_seq_before(tp_vars->last_recv, to_ack)) + tp_vars->last_recv = to_ack; list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } } @@ -1547,9 +1596,9 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv, tp_vars->common.bat_priv = bat_priv; kref_init(&tp_vars->common.refcount); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - tp_vars->common.unacked_count = 0; + spin_lock_init(&tp_vars->ack_seqno_lock); + INIT_LIST_HEAD(&tp_vars->unacked_list); + tp_vars->unacked_count = 0; kref_get(&tp_vars->common.refcount); timer_setup(&tp_vars->common.timer, batadv_tp_receiver_shutdown, 0); @@ -1609,7 +1658,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, WRITE_ONCE(tp_vars->last_recv_time, jiffies); } - spin_lock_bh(&tp_vars->common.unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); /* if the packet is a duplicate, it may be the case that an ACK has been * lost. Resend the ACK @@ -1625,7 +1674,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, * not been enqueued correctly */ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) { - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); goto out; } @@ -1641,7 +1690,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, send_ack: to_ack = tp_vars->last_recv; - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* send the ACK. If the received packet was out of order, the ACK that * is going to be sent is a duplicate (the sender will count them and diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index 1c9fb21985f6..49bf2ed9ecdc 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -443,6 +443,54 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, } /** + * batadv_tvlv_hdr_next() - move a tvlv buffer cursor to the next container + * @tvlv_value: cursor into the tvlv buffer, advanced past the returned + * container's content on success + * @tvlv_value_len: remaining length of the tvlv buffer, reduced by the returned + * container's size on success + * + * Parses a single container header at the current cursor position and, if a + * complete container is available, advances the cursor and remaining length + * past it. The returned header stays valid; its content is located at + * (returned header + 1) and is ntohs(hdr->len) bytes long. + * + * Return: pointer to the next tvlv container header, or NULL if no further + * complete container is present in the buffer. + */ +static struct batadv_tvlv_hdr *batadv_tvlv_hdr_next(void **tvlv_value, u16 *tvlv_value_len) +{ + struct batadv_tvlv_hdr *tvlv_hdr; + u16 tvlv_value_cont_len; + void *tvlv_value_cont; + u16 tvlv_len; + + tvlv_value_cont = *tvlv_value; + tvlv_len = *tvlv_value_len; + + if (tvlv_len < sizeof(*tvlv_hdr)) + return NULL; + + tvlv_hdr = tvlv_value_cont; + tvlv_value_cont_len = ntohs(tvlv_hdr->len); + tvlv_value_cont = tvlv_hdr + 1; + tvlv_len -= sizeof(*tvlv_hdr); + + if (tvlv_value_cont_len > tvlv_len) + return NULL; + + /* the next tvlv header is accessed assuming (at least) 2-byte + * alignment, so it must start at an even offset. + */ + if (tvlv_value_cont_len & 1) + return NULL; + + *tvlv_value = (u8 *)tvlv_value_cont + tvlv_value_cont_len; + *tvlv_value_len = tvlv_len - tvlv_value_cont_len; + + return tvlv_hdr; +} + +/** * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container * @tvlv_value: tvlv content * @tvlv_value_len: tvlv content length @@ -457,28 +505,10 @@ static bool batadv_tvlv_containers_contain(void *tvlv_value, u8 version) { struct batadv_tvlv_hdr *tvlv_hdr; - u16 tvlv_value_cont_len; - - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; - tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { if (tvlv_hdr->type == type && tvlv_hdr->version == version) return true; - - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } return false; @@ -511,20 +541,8 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; int ret = NET_RX_SUCCESS; - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; tvlv_handler = batadv_tvlv_handler_get(bat_priv, tvlv_hdr->type, @@ -532,11 +550,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, ret |= batadv_tvlv_call_handler(bat_priv, tvlv_handler, packet_type, orig_node, skb, - tvlv_value, + tvlv_hdr + 1, tvlv_value_cont_len); batadv_tvlv_handler_put(tvlv_handler); - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } if (packet_type != BATADV_IV_OGM && diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index b1f9f8964c3f..cd12755d21f3 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -214,9 +214,6 @@ struct batadv_wifi_net_device_state { * struct batadv_hard_iface - network device known to batman-adv */ struct batadv_hard_iface { - /** @list: list node for batadv_hardif_list */ - struct list_head list; - /** @if_status: status of the interface for batman-adv */ char if_status; @@ -1335,9 +1332,9 @@ struct batadv_tp_unacked { u32 seqno; /** @len: length of the packet */ - u16 len; + u32 len; - /** @list: list node for &batadv_tp_vars_common.unacked_list */ + /** @list: list node for &batadv_tp_receiver.unacked_list */ struct list_head list; }; @@ -1360,15 +1357,6 @@ struct batadv_tp_vars_common { /** @session: TP session identifier */ u8 session[2]; - /** @unacked_list: list of unacked packets (meta-info only) */ - struct list_head unacked_list; - - /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ - spinlock_t unacked_lock; - - /** @unacked_count: number of unacked entries */ - size_t unacked_count; - /** @refcount: number of context where the object is used */ struct kref refcount; @@ -1482,6 +1470,15 @@ struct batadv_tp_receiver { /** @last_recv_time: time (jiffies) a msg was received */ unsigned long last_recv_time; + + /** @unacked_list: list of unacked packets (meta-info only) */ + struct list_head unacked_list; + + /** @ack_seqno_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ + spinlock_t ack_seqno_lock; + + /** @unacked_count: number of unacked entries */ + size_t unacked_count; }; /** @@ -1679,6 +1676,9 @@ struct batadv_priv { /** @tp_num: number of currently active tp sessions */ atomic_t tp_num; + /** @hardif_generation: generation counter added to netlink hardif dumps */ + unsigned int hardif_generation; + /** @orig_work: work queue callback item for orig node purging */ struct delayed_work orig_work; diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index 96c9a8f57c87..0bf541936f60 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c @@ -39,6 +39,8 @@ #define COUNTER_OFFSET(n) (SMP_ALIGN(n * sizeof(struct ebt_counter))) #define COUNTER_BASE(c, n, cpu) ((struct ebt_counter *)(((char *)c) + \ COUNTER_OFFSET(n) * cpu)) +#define MAX_EBT_ENTRIES (((INT_MAX - sizeof(struct ebt_table_info)) / \ + NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) struct ebt_pernet { struct list_head tables; @@ -1126,10 +1128,9 @@ static int do_replace(struct net *net, sockptr_t arg, unsigned int len) return -EINVAL; /* overflow check */ - if (tmp.nentries >= ((INT_MAX - sizeof(struct ebt_table_info)) / - NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) + if (tmp.nentries >= MAX_EBT_ENTRIES) return -ENOMEM; - if (tmp.num_counters >= INT_MAX / sizeof(struct ebt_counter)) + if (tmp.num_counters >= MAX_EBT_ENTRIES) return -ENOMEM; tmp.name[sizeof(tmp.name) - 1] = 0; @@ -2269,10 +2270,9 @@ static int compat_copy_ebt_replace_from_user(struct ebt_replace *repl, if (tmp.entries_size == 0) return -EINVAL; - if (tmp.nentries >= ((INT_MAX - sizeof(struct ebt_table_info)) / - NR_CPUS - SMP_CACHE_BYTES) / sizeof(struct ebt_counter)) + if (tmp.nentries >= MAX_EBT_ENTRIES) return -ENOMEM; - if (tmp.num_counters >= INT_MAX / sizeof(struct ebt_counter)) + if (tmp.num_counters >= MAX_EBT_ENTRIES) return -ENOMEM; memcpy(repl, &tmp, offsetof(struct ebt_replace, hook_entry)); diff --git a/net/core/dev.c b/net/core/dev.c index 5933c5dab09e..2659fec88545 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1802,6 +1802,7 @@ void netif_close_many(struct list_head *head, bool unlink) __dev_close_many(head); list_for_each_entry_safe(dev, tmp, head, close_list) { + netdev_assert_locked_ops_compat(dev); rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP | IFF_RUNNING, GFP_KERNEL, 0, NULL); call_netdevice_notifiers(NETDEV_DOWN, dev); if (unlink) @@ -1912,9 +1913,11 @@ static void call_netdevice_unregister_notifiers(struct notifier_block *nb, struct net_device *dev) { if (dev->flags & IFF_UP) { + netdev_lock_ops(dev); call_netdevice_notifier(nb, NETDEV_GOING_DOWN, dev); call_netdevice_notifier(nb, NETDEV_DOWN, dev); + netdev_unlock_ops(dev); } call_netdevice_notifier(nb, NETDEV_UNREGISTER, dev); } @@ -9791,6 +9794,8 @@ void __dev_notify_flags(struct net_device *dev, unsigned int old_flags, { unsigned int changes = dev->flags ^ old_flags; + netdev_assert_locked_ops_compat(dev); + if (gchanges) rtmsg_ifinfo(RTM_NEWLINK, dev, gchanges, GFP_ATOMIC, portid, nlh); diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index cf374c208732..7df216c17c67 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -51,7 +51,6 @@ bool fib_rule_matchall(const struct fib_rule *rule) return false; return true; } -EXPORT_SYMBOL_GPL(fib_rule_matchall); int fib_default_rule_add(struct fib_rules_ops *ops, u32 pref, u32 table) @@ -78,7 +77,6 @@ int fib_default_rule_add(struct fib_rules_ops *ops, list_add_tail(&r->list, &ops->rules_list); return 0; } -EXPORT_SYMBOL(fib_default_rule_add); static u32 fib_default_rule_pref(struct fib_rules_ops *ops) { @@ -172,6 +170,7 @@ fib_rules_register(const struct fib_rules_ops *tmpl, struct net *net) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&ops->rules_list); + mutex_init(&ops->lock); ops->fro_net = net; err = __fib_rules_register(ops); @@ -182,7 +181,6 @@ fib_rules_register(const struct fib_rules_ops *tmpl, struct net *net) return ops; } -EXPORT_SYMBOL_GPL(fib_rules_register); static void fib_rules_cleanup_ops(struct fib_rules_ops *ops) { @@ -205,9 +203,9 @@ void fib_rules_unregister(struct fib_rules_ops *ops) spin_unlock(&net->rules_mod_lock); fib_rules_cleanup_ops(ops); + mutex_destroy(&ops->lock); kfree_rcu(ops, rcu); } -EXPORT_SYMBOL_GPL(fib_rules_unregister); static int uid_range_set(struct fib_kuid_range *range) { @@ -363,7 +361,6 @@ out: return err; } -EXPORT_SYMBOL_GPL(fib_rules_lookup); static int call_fib_rule_notifier(struct notifier_block *nb, enum fib_event_type event_type, @@ -391,7 +388,7 @@ static int call_fib_rule_notifiers(struct net *net, .rule = rule, }; - ASSERT_RTNL_NET(net); + lockdep_assert_held(&ops->lock); /* Paired with READ_ONCE() in fib_rules_seq() */ WRITE_ONCE(ops->fib_rules_seq, ops->fib_rules_seq + 1); @@ -423,7 +420,6 @@ int fib_rules_dump(struct net *net, struct notifier_block *nb, int family, return err; } -EXPORT_SYMBOL_GPL(fib_rules_dump); unsigned int fib_rules_seq_read(const struct net *net, int family) { @@ -439,7 +435,6 @@ unsigned int fib_rules_seq_read(const struct net *net, int family) return fib_rules_seq; } -EXPORT_SYMBOL_GPL(fib_rules_seq_read); static struct fib_rule *rule_find(struct fib_rules_ops *ops, struct fib_rule_hdr *frh, @@ -740,10 +735,10 @@ errout: return err; } -static int fib_nl2rule_rtnl(struct fib_rule *nlrule, - struct fib_rules_ops *ops, - struct nlattr *tb[], - struct netlink_ext_ack *extack) +static int fib_nl2rule_locked(struct fib_rule *nlrule, + struct fib_rules_ops *ops, + struct nlattr *tb[], + struct netlink_ext_ack *extack) { if (!tb[FRA_PRIORITY]) nlrule->pref = fib_default_rule_pref(ops); @@ -754,12 +749,14 @@ static int fib_nl2rule_rtnl(struct fib_rule *nlrule, return -EINVAL; } + rcu_read_lock(); + if (tb[FRA_IIFNAME]) { struct net_device *dev; - dev = __dev_get_by_name(nlrule->fr_net, nlrule->iifname); + dev = dev_get_by_name_rcu(nlrule->fr_net, nlrule->iifname); if (dev) { - nlrule->iifindex = dev->ifindex; + nlrule->iifindex = READ_ONCE(dev->ifindex); nlrule->iif_is_l3_master = netif_is_l3_master(dev); } } @@ -767,13 +764,15 @@ static int fib_nl2rule_rtnl(struct fib_rule *nlrule, if (tb[FRA_OIFNAME]) { struct net_device *dev; - dev = __dev_get_by_name(nlrule->fr_net, nlrule->oifname); + dev = dev_get_by_name_rcu(nlrule->fr_net, nlrule->oifname); if (dev) { - nlrule->oifindex = dev->ifindex; + nlrule->oifindex = READ_ONCE(dev->ifindex); nlrule->oif_is_l3_master = netif_is_l3_master(dev); } } + rcu_read_unlock(); + return 0; } @@ -883,6 +882,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr *tb[FRA_MAX + 1]; bool user_priority = false; struct fib_rule_hdr *frh; + bool unlock_rtnl = false; frh = nlmsg_payload(nlh, sizeof(*frh)); if (!frh) { @@ -908,10 +908,13 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (err) goto errout; - if (!rtnl_held) + if (!rtnl_held && ops->need_rtnl && ops->need_rtnl(net)) { + unlock_rtnl = true; rtnl_net_lock(net); + } + mutex_lock(&ops->lock); - err = fib_nl2rule_rtnl(rule, ops, tb, extack); + err = fib_nl2rule_locked(rule, ops, tb, extack); if (err) goto errout_free; @@ -959,7 +962,7 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, list_for_each_entry(r, &ops->rules_list, list) { if (r->action == FR_ACT_GOTO && r->target == rule->pref && - rtnl_dereference(r->ctarget) == NULL) { + !rcu_access_pointer(r->ctarget)) { rcu_assign_pointer(r->ctarget, rule); if (--ops->unresolved_rules == 0) break; @@ -978,7 +981,8 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, fib_rule_get(rule); - if (!rtnl_held) + mutex_unlock(&ops->lock); + if (unlock_rtnl) rtnl_net_unlock(net); notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid); @@ -988,7 +992,8 @@ int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, return 0; errout_free: - if (!rtnl_held) + mutex_unlock(&ops->lock); + if (unlock_rtnl) rtnl_net_unlock(net); kfree(rule); errout: @@ -1037,10 +1042,9 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (err) goto errout; - if (!rtnl_held) - rtnl_net_lock(net); + mutex_lock(&ops->lock); - err = fib_nl2rule_rtnl(nlrule, ops, tb, extack); + err = fib_nl2rule_locked(nlrule, ops, tb, extack); if (err) goto errout_free; @@ -1055,11 +1059,8 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, goto errout_free; } - if (ops->delete) { - err = ops->delete(rule); - if (err) - goto errout_free; - } + if (ops->delete) + ops->delete(rule); if (rule->tun_id) ip_tunnel_unneed_metadata(); @@ -1068,7 +1069,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (rule->action == FR_ACT_GOTO) { ops->nr_goto_rules--; - if (rtnl_dereference(rule->ctarget) == NULL) + if (!rcu_access_pointer(rule->ctarget)) ops->unresolved_rules--; } @@ -1086,7 +1087,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, if (&n->list == &ops->rules_list || n->pref != rule->pref) n = NULL; list_for_each_entry(r, &ops->rules_list, list) { - if (rtnl_dereference(r->ctarget) != rule) + if (rcu_access_pointer(r->ctarget) != rule) continue; rcu_assign_pointer(r->ctarget, n); if (!n) @@ -1096,8 +1097,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, call_fib_rule_notifiers(net, FIB_EVENT_RULE_DEL, rule, ops, NULL); - if (!rtnl_held) - rtnl_net_unlock(net); + mutex_unlock(&ops->lock); notify_rule_change(RTM_DELRULE, rule, ops, nlh, NETLINK_CB(skb).portid); fib_rule_put(rule); @@ -1107,8 +1107,7 @@ int fib_delrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, return 0; errout_free: - if (!rtnl_held) - rtnl_net_unlock(net); + mutex_unlock(&ops->lock); kfree(nlrule); errout: rules_ops_put(ops); @@ -1402,24 +1401,30 @@ static int fib_rules_event(struct notifier_block *this, unsigned long event, struct net *net = dev_net(dev); struct fib_rules_ops *ops; - ASSERT_RTNL(); - switch (event) { case NETDEV_REGISTER: - list_for_each_entry(ops, &net->rules_ops, list) + list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); attach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); + } break; case NETDEV_CHANGENAME: list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); detach_rules(&ops->rules_list, dev); attach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); } break; case NETDEV_UNREGISTER: - list_for_each_entry(ops, &net->rules_ops, list) + list_for_each_entry(ops, &net->rules_ops, list) { + mutex_lock(&ops->lock); detach_rules(&ops->rules_list, dev); + mutex_unlock(&ops->lock); + } break; } diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c index 8a81c5430705..abc4c00728b1 100644 --- a/net/core/lock_debug.c +++ b/net/core/lock_debug.c @@ -24,15 +24,15 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event, case NETDEV_CHANGE: case NETDEV_REGISTER: case NETDEV_UP: + case NETDEV_DOWN: + case NETDEV_GOING_DOWN: netdev_assert_locked_ops_compat(dev); fallthrough; - case NETDEV_DOWN: case NETDEV_REBOOT: case NETDEV_UNREGISTER: case NETDEV_CHANGEMTU: case NETDEV_CHANGEADDR: case NETDEV_PRE_CHANGEADDR: - case NETDEV_GOING_DOWN: case NETDEV_FEAT_CHANGE: case NETDEV_BONDING_FAILOVER: case NETDEV_PRE_UP: diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 96d5945e6a30..aed415d3cd74 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -221,7 +221,7 @@ static void refill_skbs(struct netpoll *np) skb_pool = &np->skb_pool; while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { - skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC); + skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); if (!skb) break; diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 12aa3aa1688b..1b7d6f6b8b68 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -3660,14 +3660,16 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm, u32 portid, const struct nlmsghdr *nlh) { unsigned int old_flags, changed; - int err; + int err = 0; + + netdev_lock_ops(dev); old_flags = dev->flags; if (ifm && (ifm->ifi_flags || ifm->ifi_change)) { err = __dev_change_flags(dev, rtnl_dev_combine_flags(dev, ifm), NULL); if (err < 0) - return err; + goto out; } changed = old_flags ^ dev->flags; @@ -3677,7 +3679,10 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm, } __dev_notify_flags(dev, old_flags, changed, portid, nlh); - return 0; + +out: + netdev_unlock_ops(dev); + return err; } EXPORT_SYMBOL(rtnl_configure_link); @@ -3918,22 +3923,20 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm, goto out; } - netdev_lock_ops(dev); - err = rtnl_configure_link(dev, ifm, portid, nlh); if (err < 0) goto out_unregister; if (tb[IFLA_MASTER]) { + netdev_lock_ops(dev); err = do_set_master(dev, nla_get_u32(tb[IFLA_MASTER]), extack); + netdev_unlock_ops(dev); if (err) goto out_unregister; } - netdev_unlock_ops(dev); out: return err; out_unregister: - netdev_unlock_ops(dev); if (ops->newlink) { LIST_HEAD(list_kill); diff --git a/net/devlink/core.c b/net/devlink/core.c index fe9f6a0a67d5..c53a42e17a58 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -67,6 +67,22 @@ static void __devlink_rel_put(struct devlink_rel *rel) devlink_rel_free(rel); } +struct devlink *__must_check devlink_nested_in_get_lock(struct devlink *devlink) +{ + devl_assert_locked(devlink); + if (!devlink->rel) + return NULL; + devlink = devlinks_xa_get(devlink->rel->nested_in.devlink_index); + if (!devlink) + return NULL; + devl_lock(devlink); + if (devl_is_registered(devlink)) + return devlink; + devl_unlock(devlink); + devlink_put(devlink); + return NULL; +} + static void devlink_rel_nested_in_notify_work(struct work_struct *work) { struct devlink_rel *rel = container_of(work, struct devlink_rel, @@ -518,6 +534,9 @@ void devlink_free(struct devlink *devlink) { ASSERT_DEVLINK_NOT_REGISTERED(devlink); + devl_lock(devlink); + WARN_ON(devlink_rates_check(devlink, NULL, NULL)); + devl_unlock(devlink); devlink_rel_put(devlink); WARN_ON(!list_empty(&devlink->trap_policer_list)); @@ -528,7 +547,6 @@ void devlink_free(struct devlink *devlink) WARN_ON(!list_empty(&devlink->resource_list)); WARN_ON(!list_empty(&devlink->dpipe_table_list)); WARN_ON(!list_empty(&devlink->sb_list)); - WARN_ON(devlink_rates_check(devlink, NULL, NULL)); WARN_ON(!list_empty(&devlink->linecard_list)); WARN_ON(!xa_empty(&devlink->ports)); diff --git a/net/devlink/dev.c b/net/devlink/dev.c index 57b2b8f03543..bcf001554e84 100644 --- a/net/devlink/dev.c +++ b/net/devlink/dev.c @@ -222,7 +222,7 @@ static void devlink_notify(struct devlink *devlink, enum devlink_command cmd) int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -519,7 +519,7 @@ free_msg: int devlink_nl_reload_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_reload_action action; enum devlink_reload_limit limit; struct net *dest_net = NULL; @@ -683,7 +683,7 @@ nla_put_failure: int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -704,7 +704,7 @@ int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; enum devlink_eswitch_encap_mode encap_mode; u8 inline_mode; @@ -906,7 +906,7 @@ err_cancel_msg: int devlink_nl_info_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1134,7 +1134,7 @@ int devlink_nl_flash_update_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *nla_overwrite_mask, *nla_file_name; struct devlink_flash_update_params params = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *file_name; u32 supported_params; int ret; @@ -1302,7 +1302,7 @@ err_cancel_msg: int devlink_nl_selftests_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1372,7 +1372,7 @@ static const struct nla_policy devlink_selftest_nl_policy[DEVLINK_ATTR_SELFTEST_ int devlink_nl_selftests_run_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *tb[DEVLINK_ATTR_SELFTEST_ID_MAX + 1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *attrs, *selftests; struct sk_buff *msg; void *hdr; diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index e4e48ee2da5a..cdf894ba5a9d 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -136,6 +136,10 @@ typedef void devlink_rel_notify_cb_t(struct devlink *devlink, u32 obj_index); typedef void devlink_rel_cleanup_cb_t(struct devlink *devlink, u32 obj_index, u32 rel_index); +/* Returns the locked+referenced nested-in instance or NULL. */ +struct devlink *__must_check +devlink_nested_in_get_lock(struct devlink *devlink); + void devlink_rel_nested_in_clear(u32 rel_index); int devlink_rel_nested_in_add(u32 *rel_index, u32 devlink_index, u32 obj_index, devlink_rel_notify_cb_t *notify_cb, @@ -147,6 +151,20 @@ int devlink_rel_devlink_handle_put(struct sk_buff *msg, struct devlink *devlink, bool *msg_updated); /* Netlink */ +struct devlink_nl_ctx { + struct devlink *devlink; + struct devlink_port *devlink_port; + struct devlink *parent_devlink; +}; + +static inline struct devlink_nl_ctx * +devlink_nl_ctx(struct genl_info *info) +{ + BUILD_BUG_ON(sizeof(struct devlink_nl_ctx) > + sizeof_field(struct genl_info, ctx)); + return (struct devlink_nl_ctx *)info->ctx; +} + enum devlink_multicast_groups { DEVLINK_MCGRP_CONFIG, }; @@ -180,6 +198,8 @@ typedef int devlink_nl_dump_one_func_t(struct sk_buff *msg, struct devlink * devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, bool dev_lock); +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs); int devlink_nl_dumpit(struct sk_buff *msg, struct netlink_callback *cb, devlink_nl_dump_one_func_t *dump_one); diff --git a/net/devlink/dpipe.c b/net/devlink/dpipe.c index c8d4a4374ae1..08c7b66fc3e8 100644 --- a/net/devlink/dpipe.c +++ b/net/devlink/dpipe.c @@ -213,7 +213,7 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, struct list_head *dpipe_tables, const char *table_name) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; struct nlattr *tables_attr; struct sk_buff *skb = NULL; @@ -290,7 +290,7 @@ err_table_put: int devlink_nl_dpipe_table_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name = NULL; if (info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]) @@ -478,7 +478,7 @@ int devlink_dpipe_entry_ctx_prepare(struct devlink_dpipe_dump_ctx *dump_ctx) if (!dump_ctx->hdr) goto nla_put_failure; - devlink = dump_ctx->info->user_ptr[0]; + devlink = devlink_nl_ctx(dump_ctx->info)->devlink; if (devlink_nl_put_handle(dump_ctx->skb, devlink)) goto nla_put_failure; dump_ctx->nest = nla_nest_start_noflag(dump_ctx->skb, @@ -563,7 +563,7 @@ send_done: int devlink_nl_dpipe_entries_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; const char *table_name; @@ -650,7 +650,7 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, struct devlink_dpipe_headers * dpipe_headers) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *headers_attr; struct sk_buff *skb = NULL; struct nlmsghdr *nlh; @@ -713,7 +713,7 @@ err_table_put: int devlink_nl_dpipe_headers_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink->dpipe_headers) return -EOPNOTSUPP; @@ -747,7 +747,7 @@ static int devlink_dpipe_table_counters_set(struct devlink *devlink, int devlink_nl_dpipe_table_counters_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name; bool counters_enable; diff --git a/net/devlink/health.c b/net/devlink/health.c index ea7a334e939b..8ce6cd399cb7 100644 --- a/net/devlink/health.c +++ b/net/devlink/health.c @@ -358,7 +358,7 @@ devlink_health_reporter_get_from_info(struct devlink *devlink, int devlink_nl_health_reporter_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct sk_buff *msg; int err; @@ -456,7 +456,7 @@ int devlink_nl_health_reporter_get_dumpit(struct sk_buff *skb, int devlink_nl_health_reporter_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(devlink_health_reporter_state_update); int devlink_nl_health_reporter_recover_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1157,7 +1157,7 @@ nla_put_failure: int devlink_nl_health_reporter_diagnose_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct devlink_fmsg *fmsg; int err; @@ -1252,7 +1252,7 @@ unlock: int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1269,7 +1269,7 @@ int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, int devlink_nl_health_reporter_test_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); diff --git a/net/devlink/linecard.c b/net/devlink/linecard.c index 8315d35cb91d..fd18f2759770 100644 --- a/net/devlink/linecard.c +++ b/net/devlink/linecard.c @@ -171,7 +171,7 @@ void devlink_linecards_notify_unregister(struct devlink *devlink) int devlink_nl_linecard_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; struct sk_buff *msg; int err; @@ -371,7 +371,7 @@ out: int devlink_nl_linecard_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; int err; diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index ae4afc739678..300580c1a217 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -12,6 +12,7 @@ #define DEVLINK_NL_FLAG_NEED_PORT BIT(0) #define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1) #define DEVLINK_NL_FLAG_NEED_DEV_LOCK BIT(2) +#define DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV BIT(3) static const struct genl_multicast_group devlink_nl_mcgrps[] = { [DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME }, @@ -239,37 +240,82 @@ found: return ERR_PTR(-ENODEV); } +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs) +{ + unsigned int maxtype = ARRAY_SIZE(devlink_dl_parent_dev_nl_policy) - 1; + struct devlink *devlink; + struct nlattr **tb; + int err; + + if (!attrs[DEVLINK_ATTR_PARENT_DEV]) + return ERR_PTR(-EINVAL); + + tb = kcalloc(maxtype + 1, sizeof(*tb), GFP_KERNEL); + if (!tb) + return ERR_PTR(-ENOMEM); + + err = nla_parse_nested(tb, maxtype, attrs[DEVLINK_ATTR_PARENT_DEV], + devlink_dl_parent_dev_nl_policy, NULL); + if (err) + goto out; + + devlink = devlink_get_from_attrs_lock(net, tb, false); + kfree(tb); + return devlink; +out: + kfree(tb); + return ERR_PTR(err); +} + static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { + bool parent_dev = flags & DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV; bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; + struct devlink *devlink, *parent_devlink = NULL; + struct net *net = genl_info_net(info); + struct nlattr **attrs = info->attrs; struct devlink_port *devlink_port; - struct devlink *devlink; int err; - devlink = devlink_get_from_attrs_lock(genl_info_net(info), info->attrs, - dev_lock); - if (IS_ERR(devlink)) - return PTR_ERR(devlink); + if (parent_dev && attrs[DEVLINK_ATTR_PARENT_DEV]) { + parent_devlink = devlink_get_parent_from_attrs_lock(net, attrs); + if (IS_ERR(parent_devlink)) + return PTR_ERR(parent_devlink); + devlink_nl_ctx(info)->parent_devlink = parent_devlink; + /* Drop the parent devlink lock but don't release the reference. + * This will keep it alive until the end of the request. + */ + devl_unlock(parent_devlink); + } - info->user_ptr[0] = devlink; + devlink = devlink_get_from_attrs_lock(net, attrs, dev_lock); + if (IS_ERR(devlink)) { + err = PTR_ERR(devlink); + goto parent_put; + } + devlink_nl_ctx(info)->devlink = devlink; if (flags & DEVLINK_NL_FLAG_NEED_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (IS_ERR(devlink_port)) { err = PTR_ERR(devlink_port); goto unlock; } - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } else if (flags & DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (!IS_ERR(devlink_port)) - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } return 0; unlock: devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); +parent_put: + if (parent_dev && parent_devlink) + devlink_put(parent_devlink); return err; } @@ -298,15 +344,25 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, return __devlink_nl_pre_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT); } +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + return __devlink_nl_pre_doit(skb, info, + DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; struct devlink *devlink; - devlink = info->user_ptr[0]; + devlink = devlink_nl_ctx(info)->devlink; devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); + if (devlink_nl_ctx(info)->parent_devlink) + devlink_put(devlink_nl_ctx(info)->parent_devlink); } void devlink_nl_post_doit(const struct genl_split_ops *ops, @@ -322,6 +378,14 @@ devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEV_LOCK); } +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static int devlink_nl_inst_single_dumpit(struct sk_buff *msg, struct netlink_callback *cb, int flags, devlink_nl_dump_one_func_t *dump_one, diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index f52b0c2b19ed..dec00133178d 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -46,6 +46,12 @@ devlink_attr_param_type_validate(const struct nlattr *attr, } /* Common nested types */ +const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1] = { + [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), +}; + const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1] = { [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR] = { .type = NLA_BINARY, }, [DEVLINK_PORT_FN_ATTR_STATE] = NLA_POLICY_MAX(NLA_U8, 1), @@ -608,7 +614,7 @@ static const struct nla_policy devlink_rate_get_dump_nl_policy[DEVLINK_ATTR_INDE }; /* DEVLINK_CMD_RATE_SET - do */ -static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -619,10 +625,11 @@ static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_NEW - do */ -static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -633,6 +640,7 @@ static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_DEL - do */ @@ -1290,21 +1298,21 @@ const struct genl_split_ops devlink_nl_ops[75] = { { .cmd = DEVLINK_CMD_RATE_SET, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_set_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_set_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { .cmd = DEVLINK_CMD_RATE_NEW, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_new_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_new_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index 20034b0929a8..a70e0e4769aa 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -13,6 +13,7 @@ #include <uapi/linux/devlink.h> /* Common nested types */ +extern const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1]; extern const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1]; extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_RATE_TC_ATTR_BW + 1]; extern const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1]; @@ -29,12 +30,19 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, struct genl_info *info); int devlink_nl_pre_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); void devlink_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); void devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info); int devlink_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); diff --git a/net/devlink/param.c b/net/devlink/param.c index 3e9d2e5750c2..1cc562a6ebfd 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -627,7 +627,7 @@ devlink_param_get_from_info(struct xarray *params, struct genl_info *info) int devlink_nl_param_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_param_item *param_item; struct sk_buff *msg; int err; @@ -728,7 +728,7 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, int devlink_nl_param_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; return __devlink_nl_cmd_param_set_doit(devlink, 0, &devlink->params, info, DEVLINK_CMD_PARAM_NEW); diff --git a/net/devlink/port.c b/net/devlink/port.c index 485029d43428..dc82cac68e7d 100644 --- a/net/devlink/port.c +++ b/net/devlink/port.c @@ -594,7 +594,7 @@ void devlink_ports_notify_unregister(struct devlink *devlink) int devlink_nl_port_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct sk_buff *msg; int err; @@ -830,7 +830,7 @@ static int devlink_port_function_set(struct devlink_port *port, int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; int err; if (info->attrs[DEVLINK_ATTR_PORT_TYPE]) { @@ -856,8 +856,8 @@ int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; u32 count; if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_SPLIT_COUNT)) @@ -887,8 +887,8 @@ int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_unsplit_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_unsplit) return -EOPNOTSUPP; @@ -899,7 +899,7 @@ int devlink_nl_port_new_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; struct devlink_port_new_attrs new_attrs = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *devlink_port; struct sk_buff *msg; int err; @@ -961,9 +961,9 @@ err_out_port_del: int devlink_nl_port_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_del) return -EOPNOTSUPP; @@ -1529,7 +1529,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, WARN_ON(1); return -EINVAL; case DEVLINK_PORT_FLAVOUR_PCI_PF: - if (attrs->pci_pf.external) { + if (attrs->pci_pf.external || attrs->pci_pf.controller) { n = snprintf(name, len, "c%u", attrs->pci_pf.controller); if (n >= len) return -EINVAL; @@ -1539,7 +1539,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, n = snprintf(name, len, "pf%u", attrs->pci_pf.pf); break; case DEVLINK_PORT_FLAVOUR_PCI_VF: - if (attrs->pci_vf.external) { + if (attrs->pci_vf.external || attrs->pci_vf.controller) { n = snprintf(name, len, "c%u", attrs->pci_vf.controller); if (n >= len) return -EINVAL; @@ -1550,7 +1550,7 @@ static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, attrs->pci_vf.pf, attrs->pci_vf.vf); break; case DEVLINK_PORT_FLAVOUR_PCI_SF: - if (attrs->pci_sf.external) { + if (attrs->pci_sf.external || attrs->pci_sf.controller) { n = snprintf(name, len, "c%u", attrs->pci_sf.controller); if (n >= len) return -EINVAL; diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 533d21b028a7..e727c8b8b33e 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -30,13 +30,53 @@ devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info) return devlink_rate ?: ERR_PTR(-ENODEV); } +/* Repeatedly walks the nested devlink chain while cross device rate nodes are + * supported and finds the topmost instance where rates should be stored. + * That instance is locked, referenced and returned. + * When cross device rate nodes aren't supported the original devlink instance + * is returned. + */ +static struct devlink *devl_rate_lock(struct devlink *devlink) +{ + struct devlink *rate_devlink = devlink, *parent; + + devl_assert_locked(devlink); + + while (rate_devlink->ops && + rate_devlink->ops->supported_cross_device_rate_nodes) { + parent = devlink_nested_in_get_lock(rate_devlink); + if (!parent) + break; + if (rate_devlink != devlink) { + /* Unlock intermediate instances. */ + devl_unlock(rate_devlink); + devlink_put(rate_devlink); + } + rate_devlink = parent; + } + return rate_devlink; +} + +/* Unlocks and puts 'rate devlink' if different than 'devlink'. */ +static void devl_rate_unlock(struct devlink *devlink, + struct devlink *rate_devlink) +{ + if (devlink == rate_devlink) + return; + + devl_unlock(rate_devlink); + devlink_put(rate_devlink); +} + static struct devlink_rate * -devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) +devlink_rate_node_get_by_name(struct devlink *rate_devlink, + struct devlink *devlink, const char *node_name) { struct devlink_rate *devlink_rate; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate) && + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate) && !strcmp(node_name, devlink_rate->name)) return devlink_rate; } @@ -44,7 +84,8 @@ devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) } static struct devlink_rate * -devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) +devlink_rate_node_get_from_attrs(struct devlink *rate_devlink, + struct devlink *devlink, struct nlattr **attrs) { const char *rate_node_name; size_t len; @@ -57,24 +98,30 @@ devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) if (!len || strspn(rate_node_name, "0123456789") == len) return ERR_PTR(-EINVAL); - return devlink_rate_node_get_by_name(devlink, rate_node_name); + return devlink_rate_node_get_by_name(rate_devlink, devlink, + rate_node_name); } static struct devlink_rate * -devlink_rate_node_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_node_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, + struct genl_info *info) { - return devlink_rate_node_get_from_attrs(devlink, info->attrs); + return devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); } static struct devlink_rate * -devlink_rate_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, struct genl_info *info) { struct nlattr **attrs = info->attrs; if (attrs[DEVLINK_ATTR_PORT_INDEX]) return devlink_rate_leaf_get_from_info(devlink, info); else if (attrs[DEVLINK_ATTR_RATE_NODE_NAME]) - return devlink_rate_node_get_from_info(devlink, info); + return devlink_rate_node_get_from_info(rate_devlink, devlink, + info); else return ERR_PTR(-EINVAL); } @@ -102,6 +149,25 @@ nla_put_failure: return -EMSGSIZE; } +static int devlink_nl_rate_parent_fill(struct sk_buff *msg, + struct devlink_rate *devlink_rate) +{ + struct devlink_rate *parent = devlink_rate->parent; + struct devlink *devlink = parent->devlink; + + if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, + parent->name)) + return -EMSGSIZE; + + if (devlink != devlink_rate->devlink && + devlink_nl_put_nested_handle(msg, + devlink_net(devlink_rate->devlink), + devlink, DEVLINK_ATTR_PARENT_DEV)) + return -EMSGSIZE; + + return 0; +} + static int devlink_nl_rate_fill(struct sk_buff *msg, struct devlink_rate *devlink_rate, enum devlink_command cmd, u32 portid, u32 seq, @@ -146,10 +212,9 @@ static int devlink_nl_rate_fill(struct sk_buff *msg, devlink_rate->tx_weight)) goto nla_put_failure; - if (devlink_rate->parent) - if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, - devlink_rate->parent->name)) - goto nla_put_failure; + if (devlink_rate->parent && + devlink_nl_rate_parent_fill(msg, devlink_rate)) + goto nla_put_failure; if (devlink_rate_put_tc_bws(msg, devlink_rate->tc_bw)) goto nla_put_failure; @@ -190,17 +255,25 @@ static void devlink_rate_notify(struct devlink_rate *devlink_rate, void devlink_rates_notify_register(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); } void devlink_rates_notify_unregister(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry_reverse(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry_reverse(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + devl_rate_unlock(devlink, rate_devlink); } static int @@ -209,17 +282,20 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, { struct devlink_nl_dump_state *state = devlink_dump_state(cb); struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; int idx = 0; int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { enum devlink_command cmd = DEVLINK_CMD_RATE_NEW; u32 id = NETLINK_CB(cb->skb).portid; - if (idx < state->idx) { + if (idx < state->idx || devlink_rate->devlink != devlink) { idx++; continue; } + err = devlink_nl_rate_fill(msg, devlink_rate, cmd, id, cb->nlh->nlmsg_seq, flags, NULL); if (err) { @@ -228,6 +304,7 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, } idx++; } + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -239,28 +316,38 @@ int devlink_nl_rate_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) int devlink_nl_rate_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *devlink_rate; struct sk_buff *msg; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!msg) - return -ENOMEM; + if (!msg) { + err = -ENOMEM; + goto unlock; + } err = devlink_nl_rate_fill(msg, devlink_rate, DEVLINK_CMD_RATE_NEW, info->snd_portid, info->snd_seq, 0, info->extack); - if (err) { - nlmsg_free(msg); - return err; - } + if (err) + goto err_fill; + devl_rate_unlock(devlink, rate_devlink); return genlmsg_reply(msg, info); + +err_fill: + nlmsg_free(msg); +unlock: + devl_rate_unlock(devlink, rate_devlink); + return err; } static bool @@ -277,16 +364,18 @@ devlink_rate_is_parent_node(struct devlink_rate *devlink_rate, static int devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, struct genl_info *info, struct nlattr *nla_parent) { - struct devlink *devlink = devlink_rate->devlink; + struct devlink *devlink = devlink_rate->devlink, *parent_devlink; const char *parent_name = nla_data(nla_parent); const struct devlink_ops *ops = devlink->ops; size_t len = strlen(parent_name); struct devlink_rate *parent; int err = -EOPNOTSUPP; + parent_devlink = devlink_nl_ctx(info)->parent_devlink ? : devlink; parent = devlink_rate->parent; if (parent && !len) { @@ -304,7 +393,14 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, refcount_dec(&parent->refcnt); devlink_rate->parent = NULL; } else if (len) { - parent = devlink_rate_node_get_by_name(devlink, parent_name); + /* parent_devlink (when different than devlink) isn't locked, + * but the rate node devlink instance is, so nobody from the + * same group of devices sharing rates could change the used + * fields or unregister the parent. + */ + parent = devlink_rate_node_get_by_name(rate_devlink, + parent_devlink, + parent_name); if (IS_ERR(parent)) return -ENODEV; @@ -423,6 +519,7 @@ static int devlink_nl_rate_tc_bw_set(struct devlink_rate *devlink_rate, } static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, const struct devlink_ops *ops, struct genl_info *info) { @@ -497,7 +594,8 @@ static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, */ nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; if (nla_parent) { - err = devlink_nl_rate_parent_node_set(devlink_rate, info, + err = devlink_nl_rate_parent_node_set(devlink_rate, + rate_devlink, info, nla_parent); if (err) return err; @@ -588,31 +686,51 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *devlink_rate; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } ops = devlink->ops; - if (!ops || !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) - return -EOPNOTSUPP; + if (!ops || + !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) { + err = -EOPNOTSUPP; + goto unlock; + } + + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + err = -EOPNOTSUPP; + goto unlock; + } - err = devlink_nl_rate_set(devlink_rate, ops, info); + err = devlink_nl_rate_set(devlink_rate, rate_devlink, ops, info); if (!err) devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *rate_node; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; ops = devlink->ops; @@ -624,15 +742,29 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (!devlink_rate_set_ops_supported(ops, info, DEVLINK_RATE_TYPE_NODE)) return -EOPNOTSUPP; - rate_node = devlink_rate_node_get_from_attrs(devlink, info->attrs); - if (!IS_ERR(rate_node)) - return -EEXIST; - else if (rate_node == ERR_PTR(-EINVAL)) - return -EINVAL; + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + return -EOPNOTSUPP; + } + + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); + if (!IS_ERR(rate_node)) { + err = -EEXIST; + goto unlock; + } else if (rate_node == ERR_PTR(-EINVAL)) { + err = -EINVAL; + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return -ENOMEM; + if (!rate_node) { + err = -ENOMEM; + goto unlock; + } rate_node->devlink = devlink; rate_node->type = DEVLINK_RATE_TYPE_NODE; @@ -646,13 +778,14 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (err) goto err_node_new; - err = devlink_nl_rate_set(rate_node, ops, info); + err = devlink_nl_rate_set(rate_node, rate_devlink, ops, info); if (err) goto err_rate_set; refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; err_rate_set: @@ -661,22 +794,29 @@ err_node_new: kfree(rate_node->name); err_strdup: kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *rate_node; int err; - rate_node = devlink_rate_node_get_from_info(devlink, info); - if (IS_ERR(rate_node)) - return PTR_ERR(rate_node); + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_info(rate_devlink, devlink, + info); + if (IS_ERR(rate_node)) { + err = PTR_ERR(rate_node); + goto unlock; + } if (refcount_read(&rate_node->refcnt) > 1) { NL_SET_ERR_MSG(info->extack, "Node has children. Cannot delete node."); - return -EBUSY; + err = -EBUSY; + goto unlock; } devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); @@ -687,6 +827,8 @@ int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) list_del(&rate_node->list); kfree(rate_node->name); kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -695,14 +837,20 @@ int devlink_rates_check(struct devlink *devlink, struct netlink_ext_ack *extack) { struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; + int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) - if (!rate_filter || rate_filter(devlink_rate)) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) + if (devlink_rate->devlink == devlink && + (!rate_filter || rate_filter(devlink_rate))) { if (extack) NL_SET_ERR_MSG(extack, "Rate node(s) exists."); - return -EBUSY; + err = -EBUSY; + break; } - return 0; + devl_rate_unlock(devlink, rate_devlink); + return err; } /** @@ -719,14 +867,21 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, struct devlink_rate *parent) { struct devlink_rate *rate_node; - - rate_node = devlink_rate_node_get_by_name(devlink, node_name); - if (!IS_ERR(rate_node)) - return ERR_PTR(-EEXIST); + struct devlink *rate_devlink; + + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_by_name(rate_devlink, devlink, + node_name); + if (!IS_ERR(rate_node)) { + rate_node = ERR_PTR(-EEXIST); + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return ERR_PTR(-ENOMEM); + if (!rate_node) { + rate_node = ERR_PTR(-ENOMEM); + goto unlock; + } rate_node->type = DEVLINK_RATE_TYPE_NODE; rate_node->devlink = devlink; @@ -735,7 +890,8 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, rate_node->name = kstrdup(node_name, GFP_KERNEL); if (!rate_node->name) { kfree(rate_node); - return ERR_PTR(-ENOMEM); + rate_node = ERR_PTR(-ENOMEM); + goto unlock; } if (parent) { @@ -744,8 +900,10 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, } refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return rate_node; } EXPORT_SYMBOL_GPL(devl_rate_node_create); @@ -761,10 +919,10 @@ EXPORT_SYMBOL_GPL(devl_rate_node_create); int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, struct devlink_rate *parent) { - struct devlink *devlink = devlink_port->devlink; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; struct devlink_rate *devlink_rate; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (WARN_ON(devlink_port->devlink_rate)) return -EBUSY; @@ -773,6 +931,7 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, if (!devlink_rate) return -ENOMEM; + rate_devlink = devl_rate_lock(devlink); if (parent) { devlink_rate->parent = parent; refcount_inc(&devlink_rate->parent->refcnt); @@ -782,9 +941,10 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, devlink_rate->devlink = devlink; devlink_rate->devlink_port = devlink_port; devlink_rate->priv = priv; - list_add_tail(&devlink_rate->list, &devlink->rate_list); + list_add_tail(&devlink_rate->list, &rate_devlink->rate_list); devlink_port->devlink_rate = devlink_rate; devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; } @@ -800,16 +960,19 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_create); void devl_rate_leaf_destroy(struct devlink_port *devlink_port) { struct devlink_rate *devlink_rate = devlink_port->devlink_rate; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (!devlink_rate) return; + rate_devlink = devl_rate_lock(devlink); devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_DEL); if (devlink_rate->parent) refcount_dec(&devlink_rate->parent->refcnt); list_del(&devlink_rate->list); devlink_port->devlink_rate = NULL; + devl_rate_unlock(devlink, rate_devlink); kfree(devlink_rate); } EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); @@ -818,20 +981,30 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); * devl_rate_nodes_destroy - destroy all devlink rate nodes on device * @devlink: devlink instance * - * Unset parent for all rate objects and destroy all rate nodes - * on specified device. + * Unset parent for all rate objects involving this device and destroy all rate + * nodes on it. */ void devl_rate_nodes_destroy(struct devlink *devlink) { - const struct devlink_ops *ops = devlink->ops; struct devlink_rate *devlink_rate, *tmp; + const struct devlink_ops *ops; + struct devlink *rate_devlink; devl_assert_locked(devlink); + rate_devlink = devl_rate_lock(devlink); - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (!devlink_rate->parent) + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (!devlink_rate->parent || + (devlink_rate->devlink != devlink && + devlink_rate->parent->devlink != devlink)) continue; + /* This could destroy rate objects on other devlinks in the + * same hierarchy under 'rate_devlink'. This is safe because + * the shared common ancestor is locked so there can be no + * other concurrent rate operations on devlink_rate->devlink. + */ + ops = devlink_rate->devlink->ops; if (devlink_rate_is_leaf(devlink_rate)) ops->rate_leaf_parent_set(devlink_rate, NULL, devlink_rate->priv, NULL, NULL); @@ -842,13 +1015,17 @@ void devl_rate_nodes_destroy(struct devlink *devlink) refcount_dec(&devlink_rate->parent->refcnt); devlink_rate->parent = NULL; } - list_for_each_entry_safe(devlink_rate, tmp, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate)) { + ops = devlink->ops; + list_for_each_entry_safe(devlink_rate, tmp, &rate_devlink->rate_list, + list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate)) { ops->rate_node_del(devlink_rate, devlink_rate->priv, NULL); list_del(&devlink_rate->list); kfree(devlink_rate->name); kfree(devlink_rate); } } + devl_rate_unlock(devlink, rate_devlink); } EXPORT_SYMBOL_GPL(devl_rate_nodes_destroy); diff --git a/net/devlink/region.c b/net/devlink/region.c index 5588e3d560b9..537779bbff07 100644 --- a/net/devlink/region.c +++ b/net/devlink/region.c @@ -469,7 +469,7 @@ static void devlink_region_snapshot_del(struct devlink_region *region, int devlink_nl_region_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *port = NULL; struct devlink_region *region; const char *region_name; @@ -588,7 +588,7 @@ int devlink_nl_region_get_dumpit(struct sk_buff *skb, int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct devlink_region *region; @@ -633,7 +633,7 @@ int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_region_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct nlattr *snapshot_id_attr; diff --git a/net/devlink/resource.c b/net/devlink/resource.c index 574108ccfe5d..c3cfda7ea070 100644 --- a/net/devlink/resource.c +++ b/net/devlink/resource.c @@ -117,7 +117,7 @@ devlink_resource_validate_size(struct devlink_resource *resource, u64 size, int devlink_nl_resource_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_resource *resource; u64 resource_id; u64 size; @@ -251,8 +251,9 @@ static int devlink_resource_list_fill(struct sk_buff *skb, static int devlink_resource_fill(struct genl_info *info, enum devlink_command cmd, int flags) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct devlink_resource *resource; struct list_head *resource_list; struct nlattr *resources_attr; @@ -263,6 +264,7 @@ static int devlink_resource_fill(struct genl_info *info, int i; int err; + devlink_port = ctx->devlink_port; resource_list = devlink_port ? &devlink_port->resource_list : &devlink->resource_list; resource = list_first_entry(resource_list, @@ -326,10 +328,12 @@ err_resource_put: int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct list_head *resource_list; + devlink_port = ctx->devlink_port; if (info->attrs[DEVLINK_ATTR_PORT_INDEX] && !devlink_port) return -ENODEV; diff --git a/net/devlink/sb.c b/net/devlink/sb.c index 49fcbfe08f15..129bd016e302 100644 --- a/net/devlink/sb.c +++ b/net/devlink/sb.c @@ -204,7 +204,7 @@ nla_put_failure: int devlink_nl_sb_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; int err; @@ -306,7 +306,7 @@ nla_put_failure: int devlink_nl_sb_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; u16 pool_index; @@ -415,7 +415,7 @@ static int devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index, int devlink_nl_sb_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_threshold_type threshold_type; struct devlink_sb *devlink_sb; u16 pool_index; @@ -506,7 +506,7 @@ sb_occ_get_failure: int devlink_nl_sb_port_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -624,8 +624,8 @@ static int devlink_sb_port_pool_set(struct devlink_port *devlink_port, int devlink_nl_sb_port_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; u16 pool_index; u32 threshold; @@ -716,7 +716,7 @@ nla_put_failure: int devlink_nl_sb_tc_pool_bind_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -864,8 +864,8 @@ static int devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port, int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_pool_type pool_type; struct devlink_sb *devlink_sb; u16 tc_index; @@ -902,7 +902,7 @@ int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; @@ -918,7 +918,7 @@ int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_sb_occ_max_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; diff --git a/net/devlink/trap.c b/net/devlink/trap.c index 8edb31654a68..793ffc66dc11 100644 --- a/net/devlink/trap.c +++ b/net/devlink/trap.c @@ -302,7 +302,7 @@ nla_put_failure: int devlink_nl_trap_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; struct sk_buff *msg; int err; @@ -412,7 +412,7 @@ static int devlink_trap_action_set(struct devlink *devlink, int devlink_nl_trap_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; if (list_empty(&devlink->trap_list)) @@ -511,7 +511,7 @@ nla_put_failure: int devlink_nl_trap_group_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; struct sk_buff *msg; int err; @@ -682,7 +682,7 @@ static int devlink_trap_group_set(struct devlink *devlink, int devlink_nl_trap_group_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; bool modified = false; int err; @@ -804,7 +804,7 @@ int devlink_nl_trap_policer_get_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -924,7 +924,7 @@ int devlink_nl_trap_policer_set_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (list_empty(&devlink->trap_policer_list)) return -EOPNOTSUPP; diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 9cb732f6b1e3..da53a666d4b8 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -18,6 +18,7 @@ #include <linux/of.h> #include <linux/of_net.h> #include <net/dsa_stubs.h> +#include <net/netdev_lock.h> #include <net/sch_generic.h> #include "conduit.h" @@ -1620,10 +1621,23 @@ void dsa_switch_shutdown(struct dsa_switch *ds) rtnl_lock(); - dsa_switch_for_each_cpu_port(dp, ds) - list_add(&dp->conduit->close_list, &close_list); + dsa_switch_for_each_cpu_port(dp, ds) { + if (!(dp->conduit->flags & IFF_UP)) + continue; + list_add_tail(&dp->conduit->close_list, &close_list); + netdev_lock_ops(dp->conduit); + } + + netif_close_many(&close_list, false); - netif_close_many(&close_list, true); + while (!list_empty(&close_list)) { + struct net_device *conduit; + + conduit = list_first_entry(&close_list, struct net_device, + close_list); + netdev_unlock_ops(conduit); + list_del_init(&conduit->close_list); + } dsa_switch_for_each_user_port(dp, ds) { conduit = dsa_port_to_conduit(dp); diff --git a/net/dsa/user.c b/net/dsa/user.c index 072fa76972cc..03c7af6abe18 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -13,6 +13,7 @@ #include <linux/of_net.h> #include <linux/of_mdio.h> #include <linux/mdio.h> +#include <net/netdev_lock.h> #include <net/rtnetlink.h> #include <net/pkt_cls.h> #include <net/selftests.h> @@ -3599,10 +3600,24 @@ static int dsa_user_netdevice_event(struct notifier_block *nb, if (dp->cpu_dp != cpu_dp) continue; - list_add(&dp->user->close_list, &close_list); + if (!(dp->user->flags & IFF_UP)) + continue; + + list_add_tail(&dp->user->close_list, &close_list); + netdev_lock_ops(dp->user); } - netif_close_many(&close_list, true); + netif_close_many(&close_list, false); + + while (!list_empty(&close_list)) { + struct net_device *user_dev; + + user_dev = list_first_entry(&close_list, + struct net_device, + close_list); + netdev_unlock_ops(user_dev); + list_del_init(&user_dev->close_list); + } return NOTIFY_OK; } diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index 42212970d735..8a3dc04e8cac 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -76,7 +76,7 @@ fail: struct fib_table *fib_new_table(struct net *net, u32 id) { - struct fib_table *tb, *alias = NULL; + struct fib_table *tb, *new_tb, *alias = NULL; unsigned int h; if (id == 0) @@ -85,14 +85,27 @@ struct fib_table *fib_new_table(struct net *net, u32 id) if (tb) return tb; + if (!check_net(net)) + return NULL; + if (id == RT_TABLE_LOCAL && !net->ipv4.fib_has_custom_rules) alias = fib_new_table(net, RT_TABLE_MAIN); - if (check_net(net)) - tb = fib_trie_table(id, alias); - if (!tb) + new_tb = fib_trie_table(id, alias); + if (!new_tb) return NULL; + spin_lock(&net->ipv4.fib_table_hash_lock); + + tb = fib_get_table(net, id); + if (tb) { + spin_unlock(&net->ipv4.fib_table_hash_lock); + fib_free_table(new_tb); + return tb; + } + + tb = new_tb; + switch (id) { case RT_TABLE_MAIN: rcu_assign_pointer(net->ipv4.fib_main, tb); @@ -106,28 +119,35 @@ struct fib_table *fib_new_table(struct net *net, u32 id) h = id & (FIB_TABLE_HASHSZ - 1); hlist_add_head_rcu(&tb->tb_hlist, &net->ipv4.fib_table_hash[h]); + + spin_unlock(&net->ipv4.fib_table_hash_lock); + return tb; } EXPORT_SYMBOL_GPL(fib_new_table); -/* caller must hold either rtnl or rcu read lock */ struct fib_table *fib_get_table(struct net *net, u32 id) { - struct fib_table *tb; + struct fib_table *tb = NULL; struct hlist_head *head; unsigned int h; if (id == 0) id = RT_TABLE_MAIN; h = id & (FIB_TABLE_HASHSZ - 1); - head = &net->ipv4.fib_table_hash[h]; - hlist_for_each_entry_rcu(tb, head, tb_hlist, - lockdep_rtnl_is_held()) { + + /* fib_table is not destroyed until ip_fib_net_exit() + * except for the merged main/local table. + * fib_unmerge() is called under RTNL, so other readers + * under RTNL (e.g. fib_flush(), fib_info_notify_update()) + * can safely traverse the list with rcu_dereference_raw(). + */ + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) if (tb->tb_id == id) - return tb; - } - return NULL; + break; + + return tb; } #endif /* CONFIG_IP_MULTIPLE_TABLES */ @@ -190,10 +210,9 @@ void fib_flush(struct net *net) for (h = 0; h < FIB_TABLE_HASHSZ; h++) { struct hlist_head *head = &net->ipv4.fib_table_hash[h]; - struct hlist_node *tmp; struct fib_table *tb; - hlist_for_each_entry_safe(tb, tmp, head, tb_hlist) + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) flushed += fib_table_flush(net, tb, false); } @@ -1019,10 +1038,11 @@ static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) .dump_routes = true, .dump_exceptions = true, }; - unsigned int e = 0, s_e, h, s_h; struct hlist_head *head; int dumped = 0, err = 0; struct fib_table *tb; + unsigned int h, s_h; + u32 s_id; rcu_read_lock(); if (cb->strict_check) { @@ -1054,29 +1074,28 @@ static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) } s_h = cb->args[0]; - s_e = cb->args[1]; + s_id = cb->args[1]; err = 0; - for (h = s_h; h < FIB_TABLE_HASHSZ; h++, s_e = 0) { - e = 0; + for (h = s_h; h < FIB_TABLE_HASHSZ; h++, s_id = 0) { head = &net->ipv4.fib_table_hash[h]; hlist_for_each_entry_rcu(tb, head, tb_hlist) { - if (e < s_e) - goto next; + if (s_id && tb->tb_id != s_id) + continue; + + s_id = 0; if (dumped) memset(&cb->args[2], 0, sizeof(cb->args) - 2 * sizeof(cb->args[0])); + cb->args[1] = tb->tb_id; err = fib_table_dump(tb, skb, cb, &filter); if (err < 0) goto out; dumped = 1; -next: - e++; } } out: - cb->args[1] = e; cb->args[0] = h; unlock: @@ -1566,6 +1585,10 @@ static int __net_init ip_fib_net_init(struct net *net) FIB_MULTIPATH_HASH_FIELD_DEFAULT_MASK; #endif +#ifdef CONFIG_IP_MULTIPLE_TABLES + spin_lock_init(&net->ipv4.fib_table_hash_lock); +#endif + /* Avoid false sharing : Use at least a full cache line */ size = max_t(size_t, size, L1_CACHE_BYTES); diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index e068a5bace73..4edb0dca7be8 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -301,10 +301,12 @@ static int fib4_rule_configure(struct fib_rule *rule, struct sk_buff *skb, fib4_nl2rule_dscp_mask(tb[FRA_DSCP_MASK], rule4, extack) < 0) goto errout; - /* split local/main if they are not already split */ - err = fib_unmerge(net); - if (err) - goto errout; + if (!net->ipv4.fib_has_custom_rules) { + /* split local/main if they are not already split */ + err = fib_unmerge(net); + if (err) + goto errout; + } if (rule->table == RT_TABLE_UNSPEC && !rule->l3mdev) { if (rule->action == FR_ACT_TO_TBL) { @@ -349,7 +351,7 @@ errout: return err; } -static int fib4_rule_delete(struct fib_rule *rule) +static void fib4_rule_delete(struct fib_rule *rule) { struct net *net = rule->fr_net; @@ -361,8 +363,6 @@ static int fib4_rule_delete(struct fib_rule *rule) if (net->ipv4.fib_rules_require_fldissect && fib_rule_requires_fldissect(rule)) net->ipv4.fib_rules_require_fldissect--; - - return 0; } static int fib4_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, @@ -460,6 +460,11 @@ static void fib4_rule_flush_cache(struct fib_rules_ops *ops) rt_cache_flush(ops->fro_net); } +static bool fib4_rule_need_rtnl(struct net *net) +{ + return !net->ipv4.fib_has_custom_rules; +} + static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = { .family = AF_INET, .rule_size = sizeof(struct fib4_rule), @@ -473,6 +478,7 @@ static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = { .fill = fib4_rule_fill, .nlmsg_payload = fib4_rule_nlmsg_payload, .flush_cache = fib4_rule_flush_cache, + .need_rtnl = fib4_rule_need_rtnl, .nlgroup = RTNLGRP_IPV4_RULE, .owner = THIS_MODULE, }; diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index e11dc86ceda0..d1d342d7148e 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -2137,8 +2137,7 @@ void fib_info_notify_update(struct net *net, struct nl_info *info) struct hlist_head *head = &net->ipv4.fib_table_hash[h]; struct fib_table *tb; - hlist_for_each_entry_rcu(tb, head, tb_hlist, - lockdep_rtnl_is_held()) + hlist_for_each_entry_rcu(tb, head, tb_hlist, true) __fib_info_notify_update(net, tb, info); } } diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index bb2d4441a492..b80b8a92f46e 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -1487,6 +1487,7 @@ int inet_fill_ifmcaddr(struct sk_buff *skb, struct net_device *dev, ci.ifa_valid = INFINITY_LIFE_TIME; if (nla_put_in_addr(skb, IFA_MULTICAST, im->multiaddr) < 0 || + nla_put_u32(skb, IFA_MC_USERS, READ_ONCE(im->users)) < 0 || nla_put(skb, IFA_CACHEINFO, sizeof(ci), &ci) < 0) { nlmsg_cancel(skb, nlh); return -EMSGSIZE; @@ -1508,6 +1509,7 @@ static void inet_ifmcaddr_notify(struct net_device *dev, skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + nla_total_size(sizeof(__be32)) + + nla_total_size(sizeof(u32)) + nla_total_size(sizeof(struct ifa_cacheinfo)), GFP_KERNEL); if (!skb) diff --git a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c index 0ede138dfd29..e540b86bd15b 100644 --- a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c +++ b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c @@ -213,7 +213,7 @@ static int __init nf_nat_snmp_basic_init(void) RCU_INIT_POINTER(nf_nat_snmp_hook, help); nf_ct_helper_init(&snmp_trap_helper, AF_INET, IPPROTO_UDP, - "snmp_trap", SNMP_TRAP_PORT, SNMP_TRAP_PORT, SNMP_TRAP_PORT, + "snmp_trap", &snmp_exp_policy, 0, help, NULL, THIS_MODULE); err = nf_conntrack_helper_register(&snmp_trap_helper, &snmp_trap_helper_ptr); diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index e9fbab6ad914..2aebaf8297e0 100644 --- a/net/ipv4/raw.c +++ b/net/ipv4/raw.c @@ -809,23 +809,18 @@ static int raw_seticmpfilter(struct sock *sk, sockptr_t optval, int optlen) return 0; } -static int raw_geticmpfilter(struct sock *sk, char __user *optval, int __user *optlen) +static int raw_geticmpfilter(struct sock *sk, sockopt_t *opt) { - int len, ret = -EFAULT; + int len = opt->optlen; - if (get_user(len, optlen)) - goto out; - ret = -EINVAL; if (len < 0) - goto out; + return -EINVAL; if (len > sizeof(struct icmp_filter)) len = sizeof(struct icmp_filter); - ret = -EFAULT; - if (put_user(len, optlen) || - copy_to_user(optval, &raw_sk(sk)->filter, len)) - goto out; - ret = 0; -out: return ret; + opt->optlen = len; + if (copy_to_iter(&raw_sk(sk)->filter, len, &opt->iter_out) != len) + return -EFAULT; + return 0; } static int do_raw_setsockopt(struct sock *sk, int optname, @@ -848,14 +843,13 @@ static int raw_setsockopt(struct sock *sk, int level, int optname, return do_raw_setsockopt(sk, optname, optval, optlen); } -static int do_raw_getsockopt(struct sock *sk, int optname, - char __user *optval, int __user *optlen) +static int do_raw_getsockopt(struct sock *sk, int optname, sockopt_t *opt) { if (optname == ICMP_FILTER) { if (inet_sk(sk)->inet_num != IPPROTO_ICMP) return -EOPNOTSUPP; else - return raw_geticmpfilter(sk, optval, optlen); + return raw_geticmpfilter(sk, opt); } return -ENOPROTOOPT; } @@ -863,9 +857,24 @@ static int do_raw_getsockopt(struct sock *sk, int optname, static int raw_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { + sockopt_t opt; + int err; + if (level != SOL_RAW) return ip_getsockopt(sk, level, optname, optval, optlen); - return do_raw_getsockopt(sk, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = do_raw_getsockopt(sk, optname, &opt); + if (err) + return err; + + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } static int raw_ioctl(struct sock *sk, int cmd, int *karg) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 70f6cbd4ef73..59248a59358c 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -76,6 +76,7 @@ #include <linux/bpf-cgroup.h> #include <linux/uaccess.h> +#include <linux/uio.h> #include <asm/ioctls.h> #include <linux/memblock.h> #include <linux/highmem.h> @@ -2995,14 +2996,13 @@ static int udp_setsockopt(struct sock *sk, int level, int optname, sockptr_t opt } int udp_lib_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) + sockopt_t *opt) { struct udp_sock *up = udp_sk(sk); int val, len; - if (get_user(len, optlen)) - return -EFAULT; - + len = opt->optlen; + /* keep the check so direct sockopt_t callers stay covered. */ if (len < 0) return -EINVAL; @@ -3037,9 +3037,8 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname, return -ENOPROTOOPT; } - if (put_user(len, optlen)) - return -EFAULT; - if (copy_to_user(optval, &val, len)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; return 0; } @@ -3047,9 +3046,29 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname, static int udp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { - if (level == SOL_UDP) - return udp_lib_getsockopt(sk, level, optname, optval, optlen); - return ip_getsockopt(sk, level, optname, optval, optlen); + sockopt_t opt; + int err; + + /* + * keep the old __user pointers, until ip_getsockopt() moves + * to sockopt_t + */ + if (level != SOL_UDP) + return ip_getsockopt(sk, level, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = udp_lib_getsockopt(sk, level, optname, &opt); + if (err) + return err; + + /* optval was written by copy_to_iter() in udp_lib_getsockopt() */ + if (put_user(opt.optlen, optlen)) + return -EFAULT; + + return 0; } /** diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index cbe681de3818..f1fe9ede1edb 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -5264,6 +5264,7 @@ int inet6_fill_ifmcaddr(struct sk_buff *skb, put_ifaddrmsg(nlh, 128, IFA_F_PERMANENT, scope, ifindex); if (nla_put_in6_addr(skb, IFA_MULTICAST, &ifmca->mca_addr) < 0 || + nla_put_u32(skb, IFA_MC_USERS, READ_ONCE(ifmca->mca_users)) < 0 || put_cacheinfo(skb, ifmca->mca_cstamp, READ_ONCE(ifmca->mca_tstamp), INFINITY_LIFE_TIME, INFINITY_LIFE_TIME) < 0) { nlmsg_cancel(skb, nlh); diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c index e1b2b4fa6e18..04dab9329d0c 100644 --- a/net/ipv6/fib6_rules.c +++ b/net/ipv6/fib6_rules.c @@ -480,15 +480,13 @@ errout: return err; } -static int fib6_rule_delete(struct fib_rule *rule) +static void fib6_rule_delete(struct fib_rule *rule) { struct net *net = rule->fr_net; if (net->ipv6.fib6_rules_require_fldissect && fib_rule_requires_fldissect(rule)) net->ipv6.fib6_rules_require_fldissect--; - - return 0; } static int fib6_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, @@ -637,21 +635,14 @@ out_fib6_rules_ops: goto out; } -static void __net_exit fib6_rules_net_exit_batch(struct list_head *net_list) +static void __net_exit fib6_rules_net_exit(struct net *net) { - struct net *net; - - rtnl_lock(); - list_for_each_entry(net, net_list, exit_list) { - fib_rules_unregister(net->ipv6.fib6_rules_ops); - cond_resched(); - } - rtnl_unlock(); + fib_rules_unregister(net->ipv6.fib6_rules_ops); } static struct pernet_operations fib6_rules_net_ops = { .init = fib6_rules_net_init, - .exit_batch = fib6_rules_net_exit_batch, + .exit = fib6_rules_net_exit, }; int __init fib6_rules_init(void) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 4d2b9377ba2d..aaba4c2aae23 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -908,6 +908,7 @@ static void inet6_ifmcaddr_notify(struct net_device *dev, skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + nla_total_size(sizeof(struct in6_addr)) + + nla_total_size(sizeof(u32)) + nla_total_size(sizeof(struct ifa_cacheinfo)), GFP_KERNEL); if (!skb) diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 15e032194ecc..392e18b97045 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -1826,9 +1826,22 @@ static int udpv6_setsockopt(struct sock *sk, int level, int optname, static int udpv6_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen) { - if (level == SOL_UDP) - return udp_lib_getsockopt(sk, level, optname, optval, optlen); - return ipv6_getsockopt(sk, level, optname, optval, optlen); + sockopt_t opt; + int err; + + if (level != SOL_UDP) + return ipv6_getsockopt(sk, level, optname, optval, optlen); + + err = sockopt_init_user(&opt, optval, optlen); + if (err) + return err; + + err = udp_lib_getsockopt(sk, level, optname, &opt); + if (err) + return err; + if (put_user(opt.optlen, optlen)) + return -EFAULT; + return 0; } diff --git a/net/netfilter/ipvs/ip_vs_nfct.c b/net/netfilter/ipvs/ip_vs_nfct.c index 81974f69e5bb..347185fd0c8c 100644 --- a/net/netfilter/ipvs/ip_vs_nfct.c +++ b/net/netfilter/ipvs/ip_vs_nfct.c @@ -208,7 +208,7 @@ alter: * Use port 0 to expect connection from any port. */ void ip_vs_nfct_expect_related(struct sk_buff *skb, struct nf_conn *ct, - struct ip_vs_conn *cp, u_int8_t proto, + struct ip_vs_conn *cp, u8 proto, const __be16 port, int from_rs) { struct nf_conntrack_expect *exp; diff --git a/net/netfilter/nf_conntrack_amanda.c b/net/netfilter/nf_conntrack_amanda.c index ddafbdfc96dc..06d6ec12c86d 100644 --- a/net/netfilter/nf_conntrack_amanda.c +++ b/net/netfilter/nf_conntrack_amanda.c @@ -89,7 +89,7 @@ static int amanda_help(struct sk_buff *skb, struct nf_conntrack_tuple *tuple; unsigned int dataoff, start, stop, off, i; char pbuf[sizeof("65535")], *tmp; - u_int16_t len; + u16 len; __be16 port; int ret = NF_ACCEPT; nf_nat_amanda_hook_fn *nf_nat_amanda; @@ -199,10 +199,10 @@ static int __init nf_conntrack_amanda_init(void) } nf_ct_helper_init(&amanda_helper[0], AF_INET, IPPROTO_UDP, - HELPER_NAME, 10080, 10080, 10080, + HELPER_NAME, &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); nf_ct_helper_init(&amanda_helper[1], AF_INET6, IPPROTO_UDP, - HELPER_NAME, 10080, 10080, 10080, + HELPER_NAME, &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); ret = nf_conntrack_helpers_register(amanda_helper, diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c index bf78828c7549..6ff954f1bfb8 100644 --- a/net/netfilter/nf_conntrack_broadcast.c +++ b/net/netfilter/nf_conntrack_broadcast.c @@ -66,8 +66,6 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple; helper = rcu_dereference(help->helper); - if (helper) - exp->tuple.src.u.udp.port = helper->tuple.src.u.udp.port; exp->mask.src.u3.ip = mask; exp->mask.src.u.udp.port = htons(0xFFFF); diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c index 0847f845613d..f3944598c172 100644 --- a/net/netfilter/nf_conntrack_ftp.c +++ b/net/netfilter/nf_conntrack_ftp.c @@ -35,11 +35,6 @@ MODULE_ALIAS("ip_conntrack_ftp"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); static DEFINE_SPINLOCK(nf_ftp_lock); -#define MAX_PORTS 8 -static u_int16_t ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); - static bool loose; module_param(loose, bool, 0600); @@ -560,8 +555,8 @@ static int nf_ct_ftp_from_nlattr(struct nlattr *attr, struct nf_conn *ct) return 0; } -static struct nf_conntrack_helper ftp[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *ftp_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper ftp __read_mostly; +static struct nf_conntrack_helper *ftp_ptr __read_mostly; static const struct nf_conntrack_expect_policy ftp_exp_policy = { .max_expected = 1, @@ -570,32 +565,23 @@ static const struct nf_conntrack_expect_policy ftp_exp_policy = { static void __exit nf_conntrack_ftp_fini(void) { - nf_conntrack_helpers_unregister(ftp_ptr, ports_c * 2); + nf_conntrack_helper_unregister(ftp_ptr); } static int __init nf_conntrack_ftp_init(void) { - int i, ret = 0; + int ret = 0; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_ftp_master)); - if (ports_c == 0) - ports[ports_c++] = FTP_PORT; - /* FIXME should be configurable whether IPv4 and IPv6 FTP connections are tracked or not - YK */ - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&ftp[2 * i], AF_INET, IPPROTO_TCP, - HELPER_NAME, FTP_PORT, ports[i], ports[i], - &ftp_exp_policy, 0, help, - nf_ct_ftp_from_nlattr, THIS_MODULE); - nf_ct_helper_init(&ftp[2 * i + 1], AF_INET6, IPPROTO_TCP, - HELPER_NAME, FTP_PORT, ports[i], ports[i], - &ftp_exp_policy, 0, help, - nf_ct_ftp_from_nlattr, THIS_MODULE); - } + nf_ct_helper_init(&ftp, NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + &ftp_exp_policy, 0, help, + nf_ct_ftp_from_nlattr, THIS_MODULE); - ret = nf_conntrack_helpers_register(ftp, ports_c * 2, ftp_ptr); + ret = nf_conntrack_helper_register(&ftp, &ftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index 24931e379985..4cb1665bba02 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -671,7 +671,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct, static int callforward_do_filter(struct net *net, const union nf_inet_addr *src, const union nf_inet_addr *dst, - u_int8_t family) + u8 family) { int ret = 0; @@ -1713,19 +1713,19 @@ static int __init h323_helper_init(void) int ret; nf_ct_helper_init(&nf_conntrack_helper_ras[0], AF_INET, IPPROTO_UDP, - "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + "RAS", &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_ras[1], AF_INET6, IPPROTO_UDP, - "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + "RAS", &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_h245, AF_UNSPEC, IPPROTO_UDP, - "H.245", 0, 0, 0, + "H.245", &h245_exp_policy, 0, h245_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_q931[0], AF_INET, IPPROTO_TCP, - "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + "Q.931", &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); nf_ct_helper_init(&nf_conntrack_helper_q931[1], AF_INET6, IPPROTO_TCP, - "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + "Q.931", &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); ret = nf_conntrack_helper_register(&nf_conntrack_helper_h245, diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 500509b17663..506c58034761 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -40,12 +40,16 @@ static unsigned int nf_ct_helper_count __read_mostly; static DEFINE_MUTEX(nf_ct_nat_helpers_mutex); static struct list_head nf_ct_nat_helpers __read_mostly; -/* Stupid hash, but collision free for the default registrations of the - * helpers currently in the kernel. */ -static unsigned int helper_hash(const struct nf_conntrack_tuple *tuple) +static unsigned int helper_hash(const char *name, u8 protonum) { - return (((tuple->src.l3num << 8) | tuple->dst.protonum) ^ - (__force __u16)tuple->src.u.all) % nf_ct_helper_hsize; + static u32 seed; + u32 initval; + + get_random_once(&seed, sizeof(seed)); + + initval = seed ^ protonum; + + return jhash(name, strlen(name), initval) % nf_ct_helper_hsize; } struct nf_conntrack_helper * @@ -54,18 +58,18 @@ __nf_conntrack_helper_find(const char *name, u16 l3num, u8 protonum) struct nf_conntrack_helper *h; unsigned int i; - for (i = 0; i < nf_ct_helper_hsize; i++) { - hlist_for_each_entry_rcu(h, &nf_ct_helper_hash[i], hnode) { - if (strcmp(h->name, name)) - continue; + if (!nf_ct_helper_hash) + return NULL; - if (h->tuple.src.l3num != NFPROTO_UNSPEC && - h->tuple.src.l3num != l3num) - continue; + i = helper_hash(name, protonum); - if (h->tuple.dst.protonum == protonum) - return h; - } + hlist_for_each_entry_rcu(h, &nf_ct_helper_hash[i], hnode) { + if (strcmp(h->name, name)) + continue; + if (h->nfproto != NFPROTO_UNSPEC && h->nfproto != l3num) + continue; + if (h->l4proto == protonum) + return h; } return NULL; } @@ -363,9 +367,8 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_log); int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) { - struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) }; - unsigned int h = helper_hash(&me->tuple); struct nf_conntrack_helper *cur; + unsigned int h; int ret = 0, i; BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES); @@ -382,29 +385,18 @@ int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) return -EINVAL; } + h = helper_hash(me->name, me->l4proto); mutex_lock(&nf_ct_helper_mutex); - for (i = 0; i < nf_ct_helper_hsize; i++) { - hlist_for_each_entry(cur, &nf_ct_helper_hash[i], hnode) { - if (!strcmp(cur->name, me->name) && - (cur->tuple.src.l3num == NFPROTO_UNSPEC || - cur->tuple.src.l3num == me->tuple.src.l3num) && - cur->tuple.dst.protonum == me->tuple.dst.protonum) { - ret = -EBUSY; - goto out; - } + hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { + if (!strcmp(cur->name, me->name) && + (cur->nfproto == NFPROTO_UNSPEC || + cur->nfproto == me->nfproto) && + cur->l4proto == me->l4proto) { + ret = -EBUSY; + goto out; } } - /* avoid unpredictable behaviour for auto_assign_helper */ - if (!(me->flags & NF_CT_HELPER_F_USERSPACE)) { - hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) { - if (nf_ct_tuple_src_mask_cmp(&cur->tuple, &me->tuple, - &mask)) { - ret = -EBUSY; - goto out; - } - } - } refcount_set(&me->ct_refcnt, 1); hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); nf_ct_helper_count++; @@ -479,8 +471,7 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); void nf_ct_helper_init(struct nf_conntrack_helper *helper, - u16 l3num, u16 protonum, const char *name, - u16 default_port, u16 spec_port, u32 id, + u8 l3num, u16 protonum, const char *name, const struct nf_conntrack_expect_policy *exp_pol, u32 expect_class_max, int (*help)(struct sk_buff *skb, unsigned int protoff, @@ -492,9 +483,8 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, { memset(helper, 0, sizeof(*helper)); - helper->tuple.src.l3num = l3num; - helper->tuple.dst.protonum = protonum; - helper->tuple.src.u.all = htons(spec_port); + helper->nfproto = l3num; + helper->l4proto = protonum; rcu_assign_pointer(helper->help, help); helper->from_nlattr = from_nlattr; @@ -502,10 +492,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, snprintf(helper->nat_mod_name, sizeof(helper->nat_mod_name), NF_NAT_HELPER_PREFIX "%s", name); - if (spec_port == default_port) - snprintf(helper->name, sizeof(helper->name), "%s", name); - else - snprintf(helper->name, sizeof(helper->name), "%s-%u", name, id); + snprintf(helper->name, sizeof(helper->name), "%s", name); if (WARN_ON_ONCE(expect_class_max >= NF_CT_MAX_EXPECT_CLASSES)) return; diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index 193ab34db795..4e6bafe41437 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -21,9 +21,6 @@ #include <net/netfilter/nf_conntrack_helper.h> #include <linux/netfilter/nf_conntrack_irc.h> -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; static unsigned int max_dcc_channels = 8; static unsigned int dcc_timeout __read_mostly = 300; /* This is slow, but it's simple. --RR */ @@ -42,8 +39,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("ip_conntrack_irc"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "port numbers of IRC servers"); module_param(max_dcc_channels, uint, 0400); MODULE_PARM_DESC(max_dcc_channels, "max number of expected DCC channels per " "IRC session"); @@ -254,13 +249,13 @@ static int help(struct sk_buff *skb, unsigned int protoff, return ret; } -static struct nf_conntrack_helper irc[MAX_PORTS] __read_mostly; -static struct nf_conntrack_helper *irc_ptr[MAX_PORTS] __read_mostly; +static struct nf_conntrack_helper irc __read_mostly; +static struct nf_conntrack_helper *irc_ptr __read_mostly; static struct nf_conntrack_expect_policy irc_exp_policy; static int __init nf_conntrack_irc_init(void) { - int i, ret; + int ret; nf_conntrack_helper_deprecated(HELPER_NAME); @@ -282,17 +277,11 @@ static int __init nf_conntrack_irc_init(void) if (!irc_buffer) return -ENOMEM; - /* If no port given, default to standard irc port */ - if (ports_c == 0) - ports[ports_c++] = IRC_PORT; + nf_ct_helper_init(&irc, AF_INET, IPPROTO_TCP, HELPER_NAME, + &irc_exp_policy, + 0, help, NULL, THIS_MODULE); - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&irc[i], AF_INET, IPPROTO_TCP, HELPER_NAME, - IRC_PORT, ports[i], i, &irc_exp_policy, - 0, help, NULL, THIS_MODULE); - } - - ret = nf_conntrack_helpers_register(&irc[0], ports_c, irc_ptr); + ret = nf_conntrack_helper_register(&irc, &irc_ptr); if (ret) { pr_err("failed to register helpers\n"); kfree(irc_buffer); @@ -304,7 +293,7 @@ static int __init nf_conntrack_irc_init(void) static void __exit nf_conntrack_irc_fini(void) { - nf_conntrack_helpers_unregister(irc_ptr, ports_c); + nf_conntrack_helper_unregister(irc_ptr); kfree(irc_buffer); } diff --git a/net/netfilter/nf_conntrack_netbios_ns.c b/net/netfilter/nf_conntrack_netbios_ns.c index 89d1cf7d6512..caa2b101fa9e 100644 --- a/net/netfilter/nf_conntrack_netbios_ns.c +++ b/net/netfilter/nf_conntrack_netbios_ns.c @@ -21,7 +21,6 @@ #include <net/netfilter/nf_conntrack_expect.h> #define HELPER_NAME "netbios-ns" -#define NMBD_PORT 137 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); MODULE_DESCRIPTION("NetBIOS name service broadcast connection tracking helper"); @@ -54,7 +53,6 @@ static int __init nf_conntrack_netbios_ns_init(void) exp_policy.timeout = timeout; nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, HELPER_NAME, - NMBD_PORT, NMBD_PORT, NMBD_PORT, &exp_policy, 0, netbios_ns_help, NULL, THIS_MODULE); return nf_conntrack_helper_register(&helper, &helper_ptr); diff --git a/net/netfilter/nf_conntrack_ovs.c b/net/netfilter/nf_conntrack_ovs.c index 49d1511e9921..b4085af3ad1c 100644 --- a/net/netfilter/nf_conntrack_ovs.c +++ b/net/netfilter/nf_conntrack_ovs.c @@ -31,8 +31,8 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, if (!helper) return NF_ACCEPT; - if (helper->tuple.src.l3num != NFPROTO_UNSPEC && - helper->tuple.src.l3num != proto) + if (helper->nfproto != NFPROTO_UNSPEC && + helper->nfproto != proto) return NF_ACCEPT; switch (proto) { @@ -60,7 +60,7 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, return NF_DROP; } - if (helper->tuple.dst.protonum != proto) + if (helper->l4proto != proto) return NF_ACCEPT; helper_cb = rcu_dereference(helper->help); diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c index 80fc14c87ddc..cbf32a3cb1f6 100644 --- a/net/netfilter/nf_conntrack_pptp.c +++ b/net/netfilter/nf_conntrack_pptp.c @@ -540,7 +540,7 @@ static int __init nf_conntrack_pptp_init(void) NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_pptp_master)); nf_ct_helper_init(&pptp, AF_INET, IPPROTO_TCP, - "pptp", PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, + "pptp", &pptp_exp_policy, 0, conntrack_pptp_help, NULL, THIS_MODULE); pptp.destroy = gre_pptp_destroy_siblings; diff --git a/net/netfilter/nf_conntrack_sane.c b/net/netfilter/nf_conntrack_sane.c index 39085acf7a71..a0658f69d78f 100644 --- a/net/netfilter/nf_conntrack_sane.c +++ b/net/netfilter/nf_conntrack_sane.c @@ -34,11 +34,6 @@ MODULE_AUTHOR("Michal Schmidt <mschmidt@redhat.com>"); MODULE_DESCRIPTION("SANE connection tracking helper"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static u_int16_t ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); - struct sane_request { __be32 RPC_code; #define SANE_NET_START 7 /* RPC code */ @@ -169,8 +164,8 @@ static int help(struct sk_buff *skb, return ret; } -static struct nf_conntrack_helper sane[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *sane_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper sane __read_mostly; +static struct nf_conntrack_helper *sane_ptr __read_mostly; static const struct nf_conntrack_expect_policy sane_exp_policy = { .max_expected = 1, @@ -179,32 +174,21 @@ static const struct nf_conntrack_expect_policy sane_exp_policy = { static void __exit nf_conntrack_sane_fini(void) { - nf_conntrack_helpers_unregister(sane_ptr, ports_c * 2); + nf_conntrack_helper_unregister(sane_ptr); } static int __init nf_conntrack_sane_init(void) { - int i, ret = 0; + int ret = 0; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sane_master)); - if (ports_c == 0) - ports[ports_c++] = SANE_PORT; - - /* FIXME should be configurable whether IPv4 and IPv6 connections - are tracked or not - YK */ - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&sane[2 * i], AF_INET, IPPROTO_TCP, - HELPER_NAME, SANE_PORT, ports[i], ports[i], - &sane_exp_policy, 0, help, NULL, - THIS_MODULE); - nf_ct_helper_init(&sane[2 * i + 1], AF_INET6, IPPROTO_TCP, - HELPER_NAME, SANE_PORT, ports[i], ports[i], - &sane_exp_policy, 0, help, NULL, - THIS_MODULE); - } + nf_ct_helper_init(&sane, NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + &sane_exp_policy, 0, help, NULL, + THIS_MODULE); - ret = nf_conntrack_helpers_register(sane, ports_c * 2, sane_ptr); + ret = nf_conntrack_helper_register(&sane, &sane_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index f3f90a866338..0ff089e03891 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -35,12 +35,6 @@ MODULE_DESCRIPTION("SIP connection tracking helper"); MODULE_ALIAS("ip_conntrack_sip"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "port numbers of SIP servers"); - static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT; module_param(sip_timeout, uint, 0600); MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session"); @@ -1767,8 +1761,8 @@ static int sip_help_udp(struct sk_buff *skb, unsigned int protoff, return process_sip_msg(skb, ct, protoff, dataoff, &dptr, &datalen); } -static struct nf_conntrack_helper sip[MAX_PORTS * 4] __read_mostly; -static struct nf_conntrack_helper *sip_ptr[MAX_PORTS * 4] __read_mostly; +static struct nf_conntrack_helper sip[2] __read_mostly; +static struct nf_conntrack_helper *sip_ptr[2] __read_mostly; static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1] = { [SIP_EXPECT_SIGNALLING] = { @@ -1795,38 +1789,25 @@ static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1 static void __exit nf_conntrack_sip_fini(void) { - nf_conntrack_helpers_unregister(sip_ptr, ports_c * 4); + nf_conntrack_helpers_unregister(sip_ptr, 2); } static int __init nf_conntrack_sip_init(void) { - int i, ret; + int ret; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sip_master)); - if (ports_c == 0) - ports[ports_c++] = SIP_PORT; - - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&sip[4 * i], AF_INET, IPPROTO_UDP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 1], AF_INET, IPPROTO_TCP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 2], AF_INET6, IPPROTO_UDP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, - NULL, THIS_MODULE); - nf_ct_helper_init(&sip[4 * i + 3], AF_INET6, IPPROTO_TCP, - HELPER_NAME, SIP_PORT, ports[i], i, - sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, - NULL, THIS_MODULE); - } + nf_ct_helper_init(&sip[0], NFPROTO_UNSPEC, IPPROTO_UDP, + HELPER_NAME, + sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp, + NULL, THIS_MODULE); + nf_ct_helper_init(&sip[1], NFPROTO_UNSPEC, IPPROTO_TCP, + HELPER_NAME, + sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp, + NULL, THIS_MODULE); - ret = nf_conntrack_helpers_register(sip, ports_c * 4, sip_ptr); + ret = nf_conntrack_helpers_register(sip, 2, sip_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_snmp.c b/net/netfilter/nf_conntrack_snmp.c index b6fce5703fce..109986d5d55e 100644 --- a/net/netfilter/nf_conntrack_snmp.c +++ b/net/netfilter/nf_conntrack_snmp.c @@ -14,8 +14,6 @@ #include <net/netfilter/nf_conntrack_expect.h> #include <linux/netfilter/nf_conntrack_snmp.h> -#define SNMP_PORT 161 - MODULE_AUTHOR("Jiri Olsa <jolsa@redhat.com>"); MODULE_DESCRIPTION("SNMP service broadcast connection tracking helper"); MODULE_LICENSE("GPL"); @@ -55,7 +53,7 @@ static int __init nf_conntrack_snmp_init(void) exp_policy.timeout = timeout; nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, - "snmp", SNMP_PORT, SNMP_PORT, SNMP_PORT, + "snmp", &exp_policy, 0, snmp_conntrack_help, NULL, THIS_MODULE); diff --git a/net/netfilter/nf_conntrack_tftp.c b/net/netfilter/nf_conntrack_tftp.c index 4393c435aa35..a69559edf9b3 100644 --- a/net/netfilter/nf_conntrack_tftp.c +++ b/net/netfilter/nf_conntrack_tftp.c @@ -26,12 +26,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("ip_conntrack_tftp"); MODULE_ALIAS_NFCT_HELPER(HELPER_NAME); -#define MAX_PORTS 8 -static unsigned short ports[MAX_PORTS]; -static unsigned int ports_c; -module_param_array(ports, ushort, &ports_c, 0400); -MODULE_PARM_DESC(ports, "Port numbers of TFTP servers"); - nf_nat_tftp_hook_fn __rcu *nf_nat_tftp_hook __read_mostly; EXPORT_SYMBOL_GPL(nf_nat_tftp_hook); @@ -95,8 +89,8 @@ static int tftp_help(struct sk_buff *skb, return ret; } -static struct nf_conntrack_helper tftp[MAX_PORTS * 2] __read_mostly; -static struct nf_conntrack_helper *tftp_ptr[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper tftp __read_mostly; +static struct nf_conntrack_helper *tftp_ptr __read_mostly; static const struct nf_conntrack_expect_policy tftp_exp_policy = { .max_expected = 1, @@ -105,30 +99,21 @@ static const struct nf_conntrack_expect_policy tftp_exp_policy = { static void __exit nf_conntrack_tftp_fini(void) { - nf_conntrack_helpers_unregister(tftp_ptr, ports_c * 2); + nf_conntrack_helper_unregister(tftp_ptr); } static int __init nf_conntrack_tftp_init(void) { - int i, ret; + int ret; NF_CT_HELPER_BUILD_BUG_ON(0); - if (ports_c == 0) - ports[ports_c++] = TFTP_PORT; - - for (i = 0; i < ports_c; i++) { - nf_ct_helper_init(&tftp[2 * i], AF_INET, IPPROTO_UDP, - HELPER_NAME, TFTP_PORT, ports[i], i, - &tftp_exp_policy, 0, tftp_help, NULL, - THIS_MODULE); - nf_ct_helper_init(&tftp[2 * i + 1], AF_INET6, IPPROTO_UDP, - HELPER_NAME, TFTP_PORT, ports[i], i, - &tftp_exp_policy, 0, tftp_help, NULL, - THIS_MODULE); - } + nf_ct_helper_init(&tftp, NFPROTO_UNSPEC, IPPROTO_UDP, + HELPER_NAME, + &tftp_exp_policy, 0, tftp_help, NULL, + THIS_MODULE); - ret = nf_conntrack_helpers_register(tftp, ports_c * 2, tftp_ptr); + ret = nf_conntrack_helper_register(&tftp, &tftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 63ff6b4d5d21..8ac326e1eb5b 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -39,12 +39,6 @@ static struct hlist_head *nf_nat_bysource __read_mostly; static unsigned int nf_nat_htable_size __read_mostly; static siphash_aligned_key_t nf_nat_hash_rnd; -struct nf_nat_lookup_hook_priv { - struct nf_hook_entries __rcu *entries; - - struct rcu_head rcu_head; -}; - struct nf_nat_hooks_net { struct nf_hook_ops *nat_hook_ops; unsigned int users; diff --git a/net/netfilter/nf_nat_proto.c b/net/netfilter/nf_nat_proto.c index 07f51fe75fbe..64b9bac228ea 100644 --- a/net/netfilter/nf_nat_proto.c +++ b/net/netfilter/nf_nat_proto.c @@ -770,6 +770,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -777,6 +778,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* Before packet filtering, change destination */ { @@ -784,6 +786,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -791,6 +794,7 @@ static const struct nf_hook_ops nf_nat_ipv4_ops[] = { .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, }; @@ -1031,6 +1035,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP6_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -1038,6 +1043,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP6_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* Before packet filtering, change destination */ { @@ -1045,6 +1051,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP6_PRI_NAT_DST, + .hook_ops_type = NF_HOOK_OP_NAT, }, /* After packet filtering, change source */ { @@ -1052,6 +1059,7 @@ static const struct nf_hook_ops nf_nat_ipv6_ops[] = { .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_IN, .priority = NF_IP6_PRI_NAT_SRC, + .hook_ops_type = NF_HOOK_OP_NAT, }, }; diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index f062ac210343..5b6a09f46903 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -67,7 +67,7 @@ nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff, } static const struct nla_policy nfnl_cthelper_tuple_pol[NFCTH_TUPLE_MAX+1] = { - [NFCTH_TUPLE_L3PROTONUM] = { .type = NLA_U16, }, + [NFCTH_TUPLE_L3PROTONUM] = NLA_POLICY_MAX(NLA_BE16, NFPROTO_IPV6), [NFCTH_TUPLE_L4PROTONUM] = { .type = NLA_U8, }, }; @@ -256,7 +256,8 @@ nfnl_cthelper_create(const struct nlattr * const tb[], helper->data_len = size; helper->flags |= NF_CT_HELPER_F_USERSPACE; - memcpy(&helper->tuple, tuple, sizeof(struct nf_conntrack_tuple)); + helper->nfproto = tuple->src.l3num; + helper->l4proto = tuple->dst.protonum; helper->me = THIS_MODULE; helper->help = nfnl_userspace_cthelper; @@ -453,8 +454,8 @@ static int nfnl_cthelper_new(struct sk_buff *skb, const struct nfnl_info *info, if (strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN)) continue; - if ((tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + if ((tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; if (info->nlh->nlmsg_flags & NLM_F_EXCL) @@ -483,10 +484,10 @@ nfnl_cthelper_dump_tuple(struct sk_buff *skb, goto nla_put_failure; if (nla_put_be16(skb, NFCTH_TUPLE_L3PROTONUM, - htons(helper->tuple.src.l3num))) + htons(helper->nfproto))) goto nla_put_failure; - if (nla_put_u8(skb, NFCTH_TUPLE_L4PROTONUM, helper->tuple.dst.protonum)) + if (nla_put_u8(skb, NFCTH_TUPLE_L4PROTONUM, helper->l4proto)) goto nla_put_failure; nla_nest_end(skb, nest_parms); @@ -665,8 +666,8 @@ static int nfnl_cthelper_get(struct sk_buff *skb, const struct nfnl_info *info, continue; if (tuple_set && - (tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + (tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -725,8 +726,8 @@ static int nfnl_cthelper_del(struct sk_buff *skb, const struct nfnl_info *info, continue; if (tuple_set && - (tuple.src.l3num != cur->tuple.src.l3num || - tuple.dst.protonum != cur->tuple.dst.protonum)) + (tuple.src.l3num != cur->nfproto || + tuple.dst.protonum != cur->l4proto)) continue; found = true; diff --git a/net/netfilter/nfnetlink_cttimeout.c b/net/netfilter/nfnetlink_cttimeout.c index 170d3db860c5..66c2016f6049 100644 --- a/net/netfilter/nfnetlink_cttimeout.c +++ b/net/netfilter/nfnetlink_cttimeout.c @@ -168,7 +168,7 @@ static int cttimeout_new_timeout(struct sk_buff *skb, if (ret < 0) goto err_free_timeout_policy; - strcpy(timeout->name, nla_data(cda[CTA_TIMEOUT_NAME])); + nla_strscpy(timeout->name, cda[CTA_TIMEOUT_NAME], sizeof(timeout->name)); timeout->timeout->l3num = l3num; timeout->timeout->l4proto = l4proto; refcount_set(&timeout->timeout->refcnt, 1); diff --git a/net/netfilter/nfnetlink_hook.c b/net/netfilter/nfnetlink_hook.c index 5623c18fcd12..95005e9a6066 100644 --- a/net/netfilter/nfnetlink_hook.c +++ b/net/netfilter/nfnetlink_hook.c @@ -190,7 +190,7 @@ static int nfnl_hook_put_nft_ft_info(struct sk_buff *nlskb, static int nfnl_hook_dump_one(struct sk_buff *nlskb, const struct nfnl_dump_hook_data *ctx, - const struct nf_hook_ops *ops, + const struct nf_hook_ops *ops, int priority, int family, unsigned int seq) { u16 event = nfnl_msg_type(NFNL_SUBSYS_HOOK, NFNL_MSG_HOOK_GET); @@ -244,7 +244,7 @@ static int nfnl_hook_dump_one(struct sk_buff *nlskb, if (ret) goto nla_put_failure; - ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(ops->priority)); + ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(priority)); if (ret) goto nla_put_failure; @@ -337,6 +337,30 @@ nfnl_hook_entries_head(u8 pf, unsigned int hook, struct net *net, const char *de return hook_head; } +static int nfnl_hook_dump_nat(struct sk_buff *nlskb, + const struct nfnl_dump_hook_data *ctx, + const struct nf_hook_ops *ops, + int family, unsigned int seq) +{ + struct nf_nat_lookup_hook_priv *priv = ops->priv; + struct nf_hook_entries *e = rcu_dereference(priv->entries); + struct nf_hook_ops **nat_ops; + int i, err; + + if (!e) + return 0; + + nat_ops = nf_hook_entries_get_hook_ops(e); + + for (i = 0; i < e->num_hook_entries; i++) { + err = nfnl_hook_dump_one(nlskb, ctx, nat_ops[i], + ops->priority, family, seq); + if (err) + return err; + } + return 0; +} + static int nfnl_hook_dump(struct sk_buff *nlskb, struct netlink_callback *cb) { @@ -365,8 +389,13 @@ static int nfnl_hook_dump(struct sk_buff *nlskb, ops = nf_hook_entries_get_hook_ops(e); for (; i < e->num_hook_entries; i++) { - err = nfnl_hook_dump_one(nlskb, ctx, ops[i], family, - cb->nlh->nlmsg_seq); + if (ops[i]->hook_ops_type == NF_HOOK_OP_NAT) + err = nfnl_hook_dump_nat(nlskb, ctx, ops[i], family, + cb->nlh->nlmsg_seq); + else + err = nfnl_hook_dump_one(nlskb, ctx, ops[i], + ops[i]->priority, family, + cb->nlh->nlmsg_seq); if (err) break; } diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 03a88c77e0f0..358b9287e12e 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1297,6 +1297,17 @@ static int nft_ct_expect_obj_dump(struct sk_buff *skb, return 0; } +#if IS_ENABLED(CONFIG_NF_NAT) +static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) +{ + const struct nf_ct_helper_expectfn *expfn; + + expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); + if (expfn) + expfn->expectfn(ct, this); +} +#endif + static void nft_ct_expect_obj_eval(struct nft_object *obj, struct nft_regs *regs, const struct nft_pktinfo *pkt) @@ -1342,6 +1353,13 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, priv->l4proto, NULL, &priv->dport); exp->timeout += priv->timeout; +#if IS_ENABLED(CONFIG_NF_NAT) + if (ct->status & IPS_NAT_MASK) { + exp->saved_proto.tcp.port = priv->dport; + exp->dir = !dir; + exp->expectfn = nft_ct_nat_follow_master; + } +#endif if (nf_ct_expect_related(exp, 0) != 0) regs->verdict.code = NF_DROP; @@ -1375,6 +1393,13 @@ static struct nft_object_type nft_ct_expect_obj_type __read_mostly = { .owner = THIS_MODULE, }; +#if IS_ENABLED(CONFIG_NF_NAT) +static struct nf_ct_helper_expectfn nft_ct_nat __read_mostly = { + .name = "nft_ct-follow-master", + .expectfn = nft_ct_nat_follow_master, +}; +#endif + static int __init nft_ct_module_init(void) { int err; @@ -1401,6 +1426,9 @@ static int __init nft_ct_module_init(void) if (err < 0) goto err4; #endif +#if IS_ENABLED(CONFIG_NF_NAT) + nf_ct_helper_expectfn_register(&nft_ct_nat); +#endif return 0; #ifdef CONFIG_NF_CONNTRACK_TIMEOUT @@ -1425,6 +1453,13 @@ static void __exit nft_ct_module_exit(void) nft_unregister_obj(&nft_ct_helper_obj_type); nft_unregister_expr(&nft_notrack_type); nft_unregister_expr(&nft_ct_type); + +#if IS_ENABLED(CONFIG_NF_NAT) + nf_ct_helper_expectfn_unregister(&nft_ct_nat); + synchronize_rcu(); + nf_ct_helper_expectfn_destroy(&nft_ct_nat); + synchronize_rcu(); +#endif } module_init(nft_ct_module_init); diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 6222e9bb57bc..9894832281c4 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -548,8 +548,7 @@ static int nft_array_intervals_alloc(struct nft_array *array, u32 max_intervals) if (!intervals) return -ENOMEM; - if (array->intervals) - kvfree(array->intervals); + kvfree(array->intervals); array->intervals = intervals; array->max_intervals = max_intervals; diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c index 4e6708c23922..e64116bf2637 100644 --- a/net/netfilter/x_tables.c +++ b/net/netfilter/x_tables.c @@ -1920,9 +1920,9 @@ static const struct seq_operations xt_target_seq_ops = { .show = xt_target_seq_show, }; -#define FORMAT_TABLES "_tables_names" -#define FORMAT_MATCHES "_tables_matches" -#define FORMAT_TARGETS "_tables_targets" +#define FORMAT_TABLES "%s_tables_names" +#define FORMAT_MATCHES "%s_tables_matches" +#define FORMAT_TARGETS "%s_tables_targets" #endif /* CONFIG_PROC_FS */ @@ -2033,8 +2033,7 @@ int xt_proto_init(struct net *net, u_int8_t af) root_uid = make_kuid(net->user_ns, 0); root_gid = make_kgid(net->user_ns, 0); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); proc = proc_create_net_data(buf, 0440, net->proc_net, &xt_table_seq_ops, sizeof(struct seq_net_private), (void *)(unsigned long)af); @@ -2043,8 +2042,7 @@ int xt_proto_init(struct net *net, u_int8_t af) if (uid_valid(root_uid) && gid_valid(root_gid)) proc_set_user(proc, root_uid, root_gid); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); proc = proc_create_seq_private(buf, 0440, net->proc_net, &xt_match_seq_ops, sizeof(struct nf_mttg_trav), (void *)(unsigned long)af); @@ -2053,8 +2051,7 @@ int xt_proto_init(struct net *net, u_int8_t af) if (uid_valid(root_uid) && gid_valid(root_gid)) proc_set_user(proc, root_uid, root_gid); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TARGETS, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TARGETS, xt_prefix[af]); proc = proc_create_seq_private(buf, 0440, net->proc_net, &xt_target_seq_ops, sizeof(struct nf_mttg_trav), (void *)(unsigned long)af); @@ -2068,13 +2065,11 @@ int xt_proto_init(struct net *net, u_int8_t af) #ifdef CONFIG_PROC_FS out_remove_matches: - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); out_remove_tables: - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); out: return -1; @@ -2087,16 +2082,13 @@ void xt_proto_fini(struct net *net, u_int8_t af) #ifdef CONFIG_PROC_FS char buf[XT_FUNCTION_MAXNAMELEN]; - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TABLES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TABLES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_TARGETS, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_TARGETS, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); - strscpy(buf, xt_prefix[af], sizeof(buf)); - strlcat(buf, FORMAT_MATCHES, sizeof(buf)); + snprintf(buf, sizeof(buf), FORMAT_MATCHES, xt_prefix[af]); remove_proc_entry(buf, net->proc_net); #endif /*CONFIG_PROC_FS*/ } diff --git a/net/netfilter/xt_TCPOPTSTRIP.c b/net/netfilter/xt_TCPOPTSTRIP.c index 93f064306901..265d21697847 100644 --- a/net/netfilter/xt_TCPOPTSTRIP.c +++ b/net/netfilter/xt_TCPOPTSTRIP.c @@ -16,7 +16,7 @@ #include <linux/netfilter/x_tables.h> #include <linux/netfilter/xt_TCPOPTSTRIP.h> -static inline unsigned int optlen(const u_int8_t *opt, unsigned int offset) +static inline unsigned int optlen(const u8 *opt, unsigned int offset) { /* Beware zero-length options: make finite progress */ if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0) @@ -33,8 +33,8 @@ tcpoptstrip_mangle_packet(struct sk_buff *skb, const struct xt_tcpoptstrip_target_info *info = par->targinfo; struct tcphdr *tcph, _th; unsigned int optl, i, j; - u_int16_t n, o; - u_int8_t *opt; + u16 n, o; + u8 *opt; int tcp_hdrlen; /* This is a fragment, no TCP header is available */ @@ -97,7 +97,7 @@ tcpoptstrip_tg6(struct sk_buff *skb, const struct xt_action_param *par) { struct ipv6hdr *ipv6h = ipv6_hdr(skb); int tcphoff; - u_int8_t nexthdr; + u8 nexthdr; __be16 frag_off; nexthdr = ipv6h->nexthdr; diff --git a/net/netfilter/xt_dscp.c b/net/netfilter/xt_dscp.c index fb0169a8f9bb..878f27016e99 100644 --- a/net/netfilter/xt_dscp.c +++ b/net/netfilter/xt_dscp.c @@ -49,6 +49,16 @@ static int dscp_mt_check(const struct xt_mtchk_param *par) return 0; } +static int tos_mt_check(const struct xt_mtchk_param *par) +{ + const struct xt_tos_match_info *info = par->matchinfo; + + if (info->invert > 1) + return -EINVAL; + + return 0; +} + static bool tos_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_tos_match_info *info = par->matchinfo; @@ -82,6 +92,7 @@ static struct xt_match dscp_mt_reg[] __read_mostly = { .name = "tos", .revision = 1, .family = NFPROTO_IPV4, + .checkentry = tos_mt_check, .match = tos_mt, .matchsize = sizeof(struct xt_tos_match_info), .me = THIS_MODULE, @@ -90,6 +101,7 @@ static struct xt_match dscp_mt_reg[] __read_mostly = { .name = "tos", .revision = 1, .family = NFPROTO_IPV6, + .checkentry = tos_mt_check, .match = tos_mt, .matchsize = sizeof(struct xt_tos_match_info), .me = THIS_MODULE, diff --git a/net/netfilter/xt_recent.c b/net/netfilter/xt_recent.c index f72752fa4374..d34831ce3adf 100644 --- a/net/netfilter/xt_recent.c +++ b/net/netfilter/xt_recent.c @@ -400,7 +400,7 @@ static int recent_mt_check(const struct xt_mtchk_param *par, t->nstamps_max_mask = nstamp_mask; memcpy(&t->mask, &info->mask, sizeof(t->mask)); - strcpy(t->name, info->name); + strscpy(t->name, info->name); INIT_LIST_HEAD(&t->lru_list); for (i = 0; i < ip_list_hash_size; i++) INIT_LIST_HEAD(&t->iphash[i]); diff --git a/net/netfilter/xt_tcpmss.c b/net/netfilter/xt_tcpmss.c index b9da8269161d..b08b077d7f0a 100644 --- a/net/netfilter/xt_tcpmss.c +++ b/net/netfilter/xt_tcpmss.c @@ -78,10 +78,23 @@ dropit: return false; } +static int tcpmss_mt_check(const struct xt_mtchk_param *par) +{ + const struct xt_tcpmss_match_info *info = par->matchinfo; + + if (info->mss_min > info->mss_max) + return -EINVAL; + if (info->invert > 1) + return -EINVAL; + + return 0; +} + static struct xt_match tcpmss_mt_reg[] __read_mostly = { { .name = "tcpmss", .family = NFPROTO_IPV4, + .checkentry = tcpmss_mt_check, .match = tcpmss_mt, .matchsize = sizeof(struct xt_tcpmss_match_info), .proto = IPPROTO_TCP, diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index be535a261fa0..4ca7964e83c8 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -1527,8 +1527,8 @@ static int tcf_ct_dump_helper(struct sk_buff *skb, return 0; if (nla_put_string(skb, TCA_CT_HELPER_NAME, helper->name) || - nla_put_u8(skb, TCA_CT_HELPER_FAMILY, helper->tuple.src.l3num) || - nla_put_u8(skb, TCA_CT_HELPER_PROTO, helper->tuple.dst.protonum)) + nla_put_u8(skb, TCA_CT_HELPER_FAMILY, helper->nfproto) || + nla_put_u8(skb, TCA_CT_HELPER_PROTO, helper->l4proto)) return -1; return 0; diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 09475007165b..41c2a0b82a8e 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -328,38 +328,35 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk, if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW) return pkt_len; - if (info->msg) { - /* If zerocopy is not enabled by 'setsockopt()', we behave as - * there is no MSG_ZEROCOPY flag set. + if (info->msg && (info->msg->msg_flags & MSG_ZEROCOPY)) { + /* If 'info->msg' is not NULL, this is only VIRTIO_VSOCK_OP_RW. + * 'MSG_ZEROCOPY' flag handling here is based on the same flag + * handling from 'tcp_sendmsg_locked()'. */ - if (!sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) - info->msg->msg_flags &= ~MSG_ZEROCOPY; + if (info->msg->msg_ubuf) { + uarg = info->msg->msg_ubuf; + can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len); + } else if (sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) { + uarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len, + NULL, false); + if (!uarg) { + virtio_transport_put_credit(vvs, pkt_len); + return -ENOMEM; + } - if (info->msg->msg_flags & MSG_ZEROCOPY) can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len); + if (!can_zcopy) + uarg_to_msgzc(uarg)->zerocopy = 0; + have_uref = true; + } + + /* 'can_zcopy' means that this transmission will be + * in zerocopy way (e.g. using 'frags' array). + */ if (can_zcopy) max_skb_len = min_t(u32, VIRTIO_VSOCK_MAX_PKT_BUF_SIZE, (MAX_SKB_FRAGS * PAGE_SIZE)); - - if (info->msg->msg_flags & MSG_ZEROCOPY && - info->op == VIRTIO_VSOCK_OP_RW) { - uarg = info->msg->msg_ubuf; - - if (!uarg) { - uarg = msg_zerocopy_realloc(sk_vsock(vsk), - pkt_len, NULL, false); - if (!uarg) { - virtio_transport_put_credit(vvs, pkt_len); - return -ENOMEM; - } - - if (!can_zcopy) - uarg_to_msgzc(uarg)->zerocopy = 0; - - have_uref = true; - } - } } rest_len = pkt_len; diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py index e69de29bb2d1..d8f59c132ab7 100644 --- a/tools/net/ynl/pyynl/__init__.py +++ b/tools/net/ynl/pyynl/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" Python YNL (YAML Netlink) library. """ + +# Re-export the public library API so it can be imported straight from the +# package, e.g. `from pyynl import YnlFamily`. +# pylint: disable=wildcard-import,unused-wildcard-import +from .lib import * +from .lib import __all__ diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py index 8275a806cf73..b6a6ce12b4a7 100755 --- a/tools/net/ynl/pyynl/cli.py +++ b/tools/net/ynl/pyynl/cli.py @@ -17,9 +17,7 @@ import textwrap # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException - -SYS_SCHEMA_DIR='/usr/share/ynl' -RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink' +from lib import list_families # pylint: disable=too-few-public-methods,too-many-locals class Colors: @@ -48,30 +46,6 @@ def term_width(): """ Get terminal width in columns (80 if stdout is not a terminal) """ return shutil.get_terminal_size().columns -def schema_dir(): - """ - Return the effective schema directory, preferring in-tree before - system schema directory. - """ - script_dir = os.path.dirname(os.path.abspath(__file__)) - schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") - if not os.path.isdir(schema_dir_): - schema_dir_ = SYS_SCHEMA_DIR - if not os.path.isdir(schema_dir_): - raise YnlException(f"Schema directory {schema_dir_} does not exist") - return schema_dir_ - -def spec_dir(): - """ - Return the effective spec directory, relative to the effective - schema directory. - """ - spec_dir_ = schema_dir() + '/specs' - if not os.path.isdir(spec_dir_): - raise YnlException(f"Spec directory {spec_dir_} does not exist") - return spec_dir_ - - class YnlEncoder(json.JSONEncoder): """A custom encoder for emitting JSON with ynl-specific instance types""" def default(self, o): @@ -272,9 +246,8 @@ def main(): pprint.pprint(msg, width=term_width(), compact=True) if args.list_families: - for filename in sorted(os.listdir(spec_dir())): - if filename.endswith('.yaml'): - print(filename.removesuffix('.yaml')) + for family in list_families(): + print(family) return if args.no_schema: @@ -284,28 +257,23 @@ def main(): if args.json_text: attrs = json.loads(args.json_text) - if args.family: - spec = f"{spec_dir()}/{args.family}.yaml" - else: - spec = args.spec - if not os.path.isfile(spec): - raise YnlException(f"Spec file {spec} does not exist") + if args.spec and not os.path.isfile(args.spec): + raise YnlException(f"Spec file {args.spec} does not exist") + # Spec/YnlFamily will raise if both or neither spec and family are given if args.validate: + # Force validation even for installed specs (schema=True), unless the + # user explicitly picked a schema or opted out with --no-schema. + schema = True if args.schema is None else args.schema try: - SpecFamily(spec, args.schema) + SpecFamily(args.spec, schema_path=schema, family=args.family) except SpecException as error: print(error) sys.exit(1) return - if args.family: # set behaviour when using installed specs - if args.schema is None and spec.startswith(SYS_SCHEMA_DIR): - args.schema = '' # disable schema validation when installed - if args.process_unknown is None: - args.process_unknown = True - - ynl = YnlFamily(spec, args.schema, args.process_unknown, + ynl = YnlFamily(args.spec, schema=args.schema, family=args.family, + process_unknown=args.process_unknown, recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py index be741985ae4e..aa4263c8cba9 100644 --- a/tools/net/ynl/pyynl/lib/__init__.py +++ b/tools/net/ynl/pyynl/lib/__init__.py @@ -5,12 +5,13 @@ from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \ SpecException +from .specdir import list_families from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException from .doc_generator import YnlDocGenerator __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat", - "SpecException", + "SpecException", "list_families", "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException", "YnlDocGenerator"] diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py index 0469a0e270d0..b4ec59814ab1 100644 --- a/tools/net/ynl/pyynl/lib/nlspec.py +++ b/tools/net/ynl/pyynl/lib/nlspec.py @@ -12,6 +12,8 @@ import importlib import os import yaml as pyyaml +from .specdir import find_spec, SYS_SCHEMA_DIR + class SpecException(Exception): """Netlink spec exception. @@ -444,7 +446,23 @@ class SpecFamily(SpecElement): except AttributeError: _yaml_loader = pyyaml.SafeLoader - def __init__(self, spec_path, schema_path=None, exclude_ops=None): + def __init__(self, spec_path=None, schema_path=None, exclude_ops=None, + family=None): + # schema_path selects how the spec is validated: + # None -- no preference: validate against the default schema, + # but trust (skip) installed specs selected by family= + # True -- always validate against the default schema + # path -- validate against this schema + # '' -- do not validate + if (spec_path is None) == (family is None): + raise ValueError("Specify exactly one of spec path or family name") + if family is not None: + spec_path = find_spec(family) + # Installed specs are assumed correct, so skip schema validation + # to save cycles unless the caller asked to validate. + if schema_path is None and spec_path.startswith(SYS_SCHEMA_DIR): + schema_path = '' + with open(spec_path, "r", encoding='utf-8') as stream: prefix = '# SPDX-License-Identifier: ' first = stream.readline().strip() @@ -465,7 +483,7 @@ class SpecFamily(SpecElement): self.proto = self.yaml.get('protocol', 'genetlink') self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') - if schema_path is None: + if schema_path is None or schema_path is True: schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' if schema_path: with open(schema_path, "r", encoding='utf-8') as stream: diff --git a/tools/net/ynl/pyynl/lib/specdir.py b/tools/net/ynl/pyynl/lib/specdir.py new file mode 100644 index 000000000000..fcea9b9fb7b0 --- /dev/null +++ b/tools/net/ynl/pyynl/lib/specdir.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" +Locating YNL spec and schema files on disk. + +Resolves the directory holding the YAML specs (preferring an in-tree copy +over the installed system path) and maps family names to spec files. +""" + +import os + +SYS_SCHEMA_DIR='/usr/share/ynl' +RELATIVE_SCHEMA_DIR='../../../../../Documentation/netlink' + + +def schema_dir(): + """ + Return the effective schema directory, preferring in-tree before + system schema directory. + """ + script_dir = os.path.dirname(os.path.abspath(__file__)) + schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") + if not os.path.isdir(schema_dir_): + schema_dir_ = SYS_SCHEMA_DIR + if not os.path.isdir(schema_dir_): + raise FileNotFoundError(f"Schema directory {schema_dir_} does not exist") + return schema_dir_ + +def spec_dir(): + """ + Return the effective spec directory, relative to the effective + schema directory. + """ + spec_dir_ = schema_dir() + '/specs' + if not os.path.isdir(spec_dir_): + raise FileNotFoundError(f"Spec directory {spec_dir_} does not exist") + return spec_dir_ + + +def find_spec(family): + """ Return the path to the YAML spec file for a family by name. """ + spec = f"{spec_dir()}/{family}.yaml" + if not os.path.isfile(spec): + raise FileNotFoundError(f"Spec for family '{family}' not found at {spec}") + return spec + + +def list_families(): + """ Return the sorted names of all families with an installed spec. """ + return sorted(f.removesuffix('.yaml') + for f in os.listdir(spec_dir()) if f.endswith('.yaml')) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 092d132edec1..8682bf588e1f 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -661,6 +661,14 @@ class YnlFamily(SpecFamily): """ YNL family -- a Netlink interface built from a YAML spec. + The spec can be selected either by file path (def_path=) or, when it + ships in a well-known location, by family name (family="xyz"); exactly + one of the two must be given. For example: + + from pyynl import YnlFamily + + ynl = YnlFamily(family="netdev") + Primary use of the class is to execute Netlink commands: ynl.<op_name>(attrs, ...) @@ -691,11 +699,16 @@ class YnlFamily(SpecFamily): ynl.get_policy(op_name, mode) -- query kernel policy for an op """ - def __init__(self, def_path, schema=None, process_unknown=False, - recv_size=0): - super().__init__(def_path, schema) + def __init__(self, def_path=None, schema=None, process_unknown=None, + recv_size=0, family=None): + super().__init__(def_path, schema, family=family) self.include_raw = False + # Specs from /usr (selected by family=) have a higher chance of being + # stale, default to ignoring unknown attrs. In-tree users, and users + # who bundle the spec need to make a conscious decision. + if process_unknown is None: + process_unknown = family is not None self.process_unknown = process_unknown try: diff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py index db3b62c652e7..0ee0c8e87686 100755 --- a/tools/net/ynl/tests/ethtool.py +++ b/tools/net/ynl/tests/ethtool.py @@ -11,12 +11,10 @@ import pathlib import pprint import sys import re -import os # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix()) # pylint: disable=import-error -from cli import schema_dir, spec_dir from lib import YnlFamily @@ -173,10 +171,7 @@ def main(): args = parser.parse_args() - spec = os.path.join(spec_dir(), 'ethtool.yaml') - schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml') - - ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv) + ynl = YnlFamily(family='ethtool', recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 6eb9096d084c..477aedbb01ba 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -1164,8 +1164,8 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, bool test_timeout) { u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len; + struct xsk_umem_info *umem = ifobject->xsk_arr[0].umem_real; struct pkt_stream *pkt_stream = xsk->pkt_stream; - struct xsk_umem_info *umem = xsk->umem; bool use_poll = ifobject->use_poll; struct pollfd fds = { }; int ret; @@ -1514,7 +1514,7 @@ static int thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobjec umem_tx->base_addr = 0; umem_tx->next_buffer = 0; - ret = xsk_configure(test, ifobject, umem_tx, true); + ret = xsk_configure(test, ifobject, umem_rx, true); if (ret) return ret; ifobject->xsk = &ifobject->xsk_arr[0]; diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index fd0535a96d84..234db5c2c90c 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -20,6 +20,7 @@ TEST_GEN_FILES := \ TEST_PROGS = \ csum.py \ devlink_port_split.py \ + devlink_rate_cross_esw.py \ devlink_rate_tc_bw.py \ devmem.py \ ethtool.sh \ diff --git a/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py new file mode 100755 index 000000000000..4416f024cb76 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +Devlink Rate Cross-eswitch Scheduling Test Suite +================================================== + +Control-plane tests for cross-eswitch TX scheduling via devlink-rate. +Validates that VFs from different PFs on the same chip can share +rate groups using the cross-device parent-dev attribute. + +Preconditions: +- NETIF points to a bond device with exactly two interfaces. +- the interfaces must be two PFs from different devices sharing the same chip. +- (for mlx5): the two interfaces are in switchdev mode and configured in a LAG: + - devlink dev eswitch set $DEV1 mode switchdev + - devlink dev eswitch set $DEV2 mode switchdev + - devlink dev param set $DEV1 name esw_multiport value 1 cmode runtime + - devlink dev param set $DEV2 name esw_multiport value 1 cmode runtime +- test cases will be skipped if: + - the number of interfaces in the bond device is != 2. + - the kernel doesn't support devlink rates. + - the devlink API doesn't support cross-device parents (ENODEV). + - cross-esw rate scheduling returns EOPNOTSUPP. +""" + +import errno +import glob +import os +import time + +from lib.py import ksft_pr, ksft_eq, ksft_run, ksft_exit +from lib.py import KsftSkipEx, KsftFailEx +from lib.py import NetDrvEnv, DevlinkFamily +from lib.py import NlError +from lib.py import cmd, defer, ip, tool + + +# --- Discovery and setup --- + + +def get_bond_slaves(bond_ifname): + """Returns sorted list of slave netdev names for a bond.""" + pattern = f"/sys/class/net/{bond_ifname}/lower_*" + lowers = glob.glob(pattern) + if not lowers: + raise KsftSkipEx(f"No bond slaves for {bond_ifname}") + slaves = [] + for path in sorted(lowers): + name = os.path.basename(path) + if name.startswith("lower_"): + name = name[len("lower_"):] + slaves.append(name) + return slaves + + +def discover_pfs(cfg): + """Discovers both PFs from bond slaves.""" + slaves = get_bond_slaves(cfg.ifname) + if len(slaves) != 2: + raise KsftSkipEx(f"Need 2 bond slaves, found {len(slaves)}") + + pf0, pf1 = slaves[0], slaves[1] + ksft_pr(f"PF0: {pf0} PF1: {pf1}") + return pf0, pf1 + + +def get_pci_addr(ifname): + """Resolves PCI address for a network interface.""" + return os.path.basename(os.path.realpath(f"/sys/class/net/{ifname}/device")) + + +def get_vf_port_index(pf_pci): + """Finds devlink port-index for vf0 under pf_pci.""" + ports = tool("devlink", "port show", json=True)["port"] + for port_name, props in ports.items(): + if port_name.startswith(f"pci/{pf_pci}/") and props.get("vfnum") == 0: + return int(port_name.split("/")[-1]) + raise KsftSkipEx(f"VF port not found for {pf_pci}") + + +def cleanup_esw(pf): + """Removes VFs if created by tests.""" + cmd(f"echo 0 > /sys/class/net/{pf}/device/sriov_numvfs", shell=True, fail=False) + + +def setup_esw(pf): + """Creates 1 VF on 'pf'.""" + path = f"/sys/class/net/{pf}/device/sriov_numvfs" + cmd(f"echo 0 > {path}", shell=True) + cmd(f"echo 1 > {path}", shell=True) + defer(cleanup_esw, pf) + time.sleep(2) + + vf_dir = f"/sys/class/net/{pf}/device/virtfn0/net" + entries = os.listdir(vf_dir) if os.path.isdir(vf_dir) else [] + if not entries: + raise KsftSkipEx(f"VF not found for {pf}") + ip(f"link set dev {entries[0]} up") + + pf_pci = get_pci_addr(pf) + vf_idx = get_vf_port_index(pf_pci) + ksft_pr(f"Created VF {vf_idx} on PF {pf} ({pf_pci})") + return pf_pci, vf_idx + + +# --- Rate operation helpers --- + + +def rate_new(devnl, dev_pci, node_name, **kwargs): + """Creates rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + try: + devnl.rate_new(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_new not supported") from e + raise KsftFailEx("rate_new failed") from e + + +def rate_get(devnl, dev_pci, node_name): + """Gets rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + return devnl.rate_get(params) + + +def rate_get_leaf(devnl, dev_pci, port_index): + """Gets rate leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + return devnl.rate_get(params) + + +def rate_del(devnl, dev_pci, node_name): + """Deletes rate node.""" + devnl.rate_del({ + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + }) + + +def rate_set_leaf(devnl, dev_pci, port_index, **kwargs): + """Sets rate attributes on a leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + params.update(kwargs) + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_set_leaf_parent(devnl, dev_pci, port_index, + parent_name, parent_dev_pci=None): + """Sets a leaf's parent, optionally cross-esw.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + "rate-parent-node-name": parent_name, + } + if parent_dev_pci: + params["parent-dev"] = { + "bus-name": "pci", + "dev-name": parent_dev_pci, + } + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + if parent_dev_pci and e.error == errno.ENODEV: + raise KsftSkipEx("Cross-esw scheduling not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_clear_leaf_parent(devnl, dev_pci, port_index): + """Clears a leaf's parent.""" + rate_set_leaf_parent(devnl, dev_pci, port_index, "") + + +def rate_set_node(devnl, dev_pci, node_name, **kwargs): + """Sets rate attributes on a node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + devnl.rate_set(params) + + +# --- Test cases --- + + +def test_same_esw_parent(cfg): + """Assigns PF0's VF to PF0's group (same esw baseline).""" + pf0, _ = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + + rate_new(cfg.devnl, pf0_pci, "group0") + defer(rate_del, cfg.devnl, pf0_pci, "group0") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group0") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + + ksft_pr("Same-esw parent assignment succeeded") + + +def test_cross_esw_parent(cfg): + """Sets cross-esw parent, then clear it.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, _ = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group1") + defer(rate_del, cfg.devnl, pf0_pci, "group1") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group1", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + + ksft_pr("Cross-esw parent set and clear succeeded") + + +def test_tx_rates_on_cross_esw(cfg): + """Sets tx_max on group and tx_share on leaves in a cross-esw setup.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 10000000}) + defer(rate_del, cfg.devnl, pf0_pci, "group2") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group2", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + ksft_pr("set parent cross-esw succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group2") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + ksft_pr("set parent same esw succeeded") + + rate_set_leaf(cfg.devnl, pf0_pci, vf0_idx, **{"rate-tx-share": 1000000}) + rate = rate_get_leaf(cfg.devnl, pf0_pci, vf0_idx) + ksft_eq(rate["rate-tx-share"], 1000000) + rate_set_leaf(cfg.devnl, pf1_pci, vf1_idx, **{"rate-tx-share": 2000000}) + rate = rate_get_leaf(cfg.devnl, pf1_pci, vf1_idx) + ksft_eq(rate["rate-tx-share"], 2000000) + rate_set_node(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 250000000}) + rate = rate_get(cfg.devnl, pf0_pci, "group2") + ksft_eq(rate["rate-tx-max"], 250000000) + + ksft_pr("tx_max and tx_share set on cross-esw group") + + +def main() -> None: + """Main function.""" + + with NetDrvEnv(__file__, nsim_test=False) as cfg: + cfg.devnl = DevlinkFamily() + + ksft_run( + cases=[ + test_same_esw_parent, + test_cross_esw_parent, + test_tx_rates_on_cross_esw, + ], + args=(cfg,), + ) + ksft_exit() + + +if __name__ == "__main__": + main() diff --git a/tools/testing/selftests/drivers/net/hw/rss_ctx.py b/tools/testing/selftests/drivers/net/hw/rss_ctx.py index f36f76d6ca59..5b25fa89c629 100755 --- a/tools/testing/selftests/drivers/net/hw/rss_ctx.py +++ b/tools/testing/selftests/drivers/net/hw/rss_ctx.py @@ -651,9 +651,14 @@ def test_rss_context_overlap(cfg, other_ctx=0): ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") # Test the main context - cnts = _get_rx_cnts(cfg) - GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) - cnts = _get_rx_cnts(cfg, prev=cnts) + attempts = 3 + for attempt in range(attempts): + cnts = _get_rx_cnts(cfg) + GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) + cnts = _get_rx_cnts(cfg, prev=cnts) + if sum(cnts[:2]) >= 7000 and sum(cnts[2:4]) >= 7000: + break + ksft_pr(f"Skewed queue distribution, attempt {attempt + 1}/{attempts}: " + str(cnts)) ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py index cd7e080e6f84..571732198b93 100755 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.py +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py @@ -21,6 +21,8 @@ from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx ETH_RSS_HASH_TOP = 1 # Must match RPS_MAX_CPUS in toeplitz.c RPS_MAX_CPUS = 16 +# Cap Rx queues so IRQ pinning leaves free CPUs in the RPS_MAX_CPUS range +QUEUE_CAP = 8 def _check_rps_and_rfs_not_configured(cfg): @@ -48,6 +50,25 @@ def _get_cpu_for_irq(irq): return int(data) +def _cap_queue_count(cfg): + ehdr = {"header": {"dev-index": cfg.ifindex}} + chans = cfg.ethnl.channels_get(ehdr) + + config = {} + restore = {} + for key in ("combined-count", "rx-count"): + cur = chans.get(key, 0) + if cur > QUEUE_CAP: + config[key] = QUEUE_CAP + restore[key] = cur + + if not config: + return + + cfg.ethnl.channels_set(ehdr | config) + defer(cfg.ethnl.channels_set, ehdr | restore) + + def _get_irq_cpus(cfg): """ Read the list of IRQs for the device Rx queues. @@ -177,6 +198,7 @@ def test(cfg, proto_flag, ipver, grp): ] if grp: + _cap_queue_count(cfg) _check_rps_and_rfs_not_configured(cfg) if grp == "rss": irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)]) diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index e4ab99b905b1..e4acf3d8333f 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -159,13 +159,7 @@ class NetDrvEpEnv(NetDrvEnvBase): self.remote = Remote(kind, args, src_path) - self.addr_ipver = "6" if self.addr_v["6"] else "4" - self.addr = self.addr_v[self.addr_ipver] - self.remote_addr = self.remote_addr_v[self.addr_ipver] - - # Bracketed addresses, some commands need IPv6 to be inside [] - self.baddr = f"[{self.addr_v['6']}]" if self.addr_v["6"] else self.addr_v["4"] - self.remote_baddr = f"[{self.remote_addr_v['6']}]" if self.remote_addr_v["6"] else self.remote_addr_v["4"] + self.set_ipver("6" if self.addr_v["6"] else "4") self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] @@ -252,6 +246,25 @@ class NetDrvEpEnv(NetDrvEnvBase): if not self.addr_v[ipver] or not self.remote_addr_v[ipver]: raise KsftSkipEx(f"Test requires IPv{ipver} connectivity") + def set_ipver(self, ipver): + """ + Modify the IP version used by the generic address fields. + """ + if ipver == getattr(self, "addr_ipver", None): + return + + self.require_ipver(ipver) + + self.addr_ipver = ipver + self.addr = self.addr_v[ipver] + self.remote_addr = self.remote_addr_v[ipver] + + # Bracketed addresses, some commands need IPv6 to be inside [] + self.baddr = (f"[{self.addr_v['6']}]" if ipver == "6" + else self.addr_v["4"]) + self.remote_baddr = (f"[{self.remote_addr_v['6']}]" if ipver == "6" + else self.remote_addr_v["4"]) + def require_nsim(self, nsim_test=True): """Require or exclude netdevsim for this test""" if nsim_test and self._ns is None: diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py index 2ad5932299e8..0369929f3c51 100755 --- a/tools/testing/selftests/drivers/net/xdp.py +++ b/tools/testing/selftests/drivers/net/xdp.py @@ -172,25 +172,45 @@ def _test_pass(cfg, bpf_info, msg_sz): ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.PASS.value], "RX and PASS stats mismatch") -def test_xdp_native_pass_sb(cfg): +_ipvers = [ + KsftNamedVariant("ipv4", "4"), + KsftNamedVariant("ipv6", "6"), +] + + +def _set_ipver_defer_restore(cfg, ipver): + old_ipver = cfg.addr_ipver + cfg.set_ipver(ipver) + defer(cfg.set_ipver, old_ipver) + + +@ksft_variants(_ipvers) +def test_xdp_native_pass_sb(cfg, ipver): """ Tests the XDP_PASS action for single buffer case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) _test_pass(cfg, bpf_info, 256) -def test_xdp_native_pass_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_pass_mb(cfg, ipver): """ Tests the XDP_PASS action for a multi-buff size. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) _test_pass(cfg, bpf_info, 8000) @@ -219,25 +239,33 @@ def _test_drop(cfg, bpf_info, msg_sz): ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch") -def test_xdp_native_drop_sb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_drop_sb(cfg, ipver): """ Tests the XDP_DROP action for a signle-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) _test_drop(cfg, bpf_info, 256) -def test_xdp_native_drop_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_drop_mb(cfg, ipver): """ Tests the XDP_DROP action for a multi-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) _test_drop(cfg, bpf_info, 8000) @@ -287,13 +315,17 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens): ksft_eq(stats[XDPStats.TX.value], expected_pkts, "TX stats mismatch") -def test_xdp_native_tx_sb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_tx_sb(cfg, ipver): """ Tests the XDP_TX action for a single-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) # Ensure there's enough room for an ETH / IP / UDP header @@ -302,13 +334,17 @@ def test_xdp_native_tx_sb(cfg): _test_xdp_native_tx(cfg, bpf_info, [0, 1500 // 2, 1500 - pkt_hdr_len]) -def test_xdp_native_tx_mb(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_tx_mb(cfg, ipver): """ Tests the XDP_TX action for a multi-buff case. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) # The first packet ensures we exercise the fragmented code path. @@ -447,13 +483,17 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst): return {"status": "pass"} -def test_xdp_native_adjst_tail_grow_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_tail_grow_data(cfg, ipver): """ Tests the XDP tail adjustment by growing packet data. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] offset_lst = [1, 16, 32, 64, 128, 256] res = _test_xdp_native_tail_adjst( @@ -465,13 +505,17 @@ def test_xdp_native_adjst_tail_grow_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -def test_xdp_native_adjst_tail_shrnk_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_tail_shrnk_data(cfg, ipver): """ Tests the XDP tail adjustment by shrinking packet data. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] offset_lst = [-16, -32, -64, -128, -256] res = _test_xdp_native_tail_adjst( @@ -535,7 +579,7 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst): # after we eat into it. We send large-enough packets, but if HDS # is enabled head will only contain headers. Don't try to eat # more than 28 bytes (UDPv4 + eth hdr left: (14 + 20 + 8) - 14) - l2_cut_off = 28 if cfg.addr_ipver == 4 else 48 + l2_cut_off = 28 if cfg.addr_ipver == "4" else 48 if pkt_sz > hds_thresh and offset > l2_cut_off: ksft_pr( f"Failed run: pkt_sz ({pkt_sz}) > HDS threshold ({hds_thresh}) and " @@ -579,18 +623,22 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst): return {"status": "pass"} -def test_xdp_native_adjst_head_grow_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_head_grow_data(cfg, ipver): """ Tests the XDP headroom growth support. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). This function sets up the packet size and offset lists, then calls the _test_xdp_native_head_adjst_mb function to perform the actual test. The test is passed if the headroom is successfully extended for given packet sizes and offsets. """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] # Negative values result in headroom shrinking, resulting in growing of payload @@ -600,18 +648,22 @@ def test_xdp_native_adjst_head_grow_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -def test_xdp_native_adjst_head_shrnk_data(cfg): +@ksft_variants(_ipvers) +def test_xdp_native_adjst_head_shrnk_data(cfg, ipver): """ Tests the XDP headroom shrinking support. Args: cfg: Configuration object containing network settings. + ipver: IP version to use ("4" or "6"). This function sets up the packet size and offset lists, then calls the _test_xdp_native_head_adjst_mb function to perform the actual test. The test is passed if the headroom is successfully shrunk for given packet sizes and offsets. """ + _set_ipver_defer_restore(cfg, ipver) + pkt_sz_lst = [512, 1024, 2048] # Positive values result in headroom growing, resulting in shrinking of payload @@ -621,12 +673,19 @@ def test_xdp_native_adjst_head_shrnk_data(cfg): _validate_res(res, offset_lst, pkt_sz_lst) -@ksft_variants([ - KsftNamedVariant("pass", XDPAction.PASS), - KsftNamedVariant("drop", XDPAction.DROP), - KsftNamedVariant("tx", XDPAction.TX), -]) -def test_xdp_native_qstats(cfg, act): +def _qstats_variants(): + actions = [ + ("pass", XDPAction.PASS), + ("drop", XDPAction.DROP), + ("tx", XDPAction.TX), + ] + for ipver in ["4", "6"]: + for name, act in actions: + yield KsftNamedVariant(f"{name}_ipv{ipver}", act, ipver) + + +@ksft_variants(_qstats_variants()) +def test_xdp_native_qstats(cfg, act, ipver): """ Send 1000 messages. Expect XDP action specified in @act. Make sure the packets were counted to interface level qstats @@ -634,6 +693,7 @@ def test_xdp_native_qstats(cfg, act): """ cfg.require_cmd("socat") + _set_ipver_defer_restore(cfg, ipver) bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) prog_info = _load_xdp_prog(cfg, bpf_info) diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c index 209569354d0e..fe5a5268bc34 100644 --- a/tools/testing/selftests/net/getsockopt_iter.c +++ b/tools/testing/selftests/net/getsockopt_iter.c @@ -11,6 +11,8 @@ * that always reports the required buffer length back via optlen, * even when the user buffer is too small to receive any group bits. * - vsock: SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path. + * - raw: ICMP_FILTER covers a fixed-size struct payload that clamps + * the length down on a short buffer instead of failing. * * Author: Breno Leitao <leitao@debian.org> */ @@ -24,12 +26,20 @@ #include <linux/rtnetlink.h> #include <linux/time_types.h> #include <linux/vm_sockets.h> +#include <linux/icmp.h> +#include <netinet/in.h> #include <sys/socket.h> #include "kselftest_harness.h" #ifndef AF_VSOCK #define AF_VSOCK 40 #endif +#ifndef SOL_RAW +#define SOL_RAW 255 +#endif +#ifndef ICMP_FILTER +#define ICMP_FILTER 1 +#endif /* ---------- netlink ---------- */ @@ -297,4 +307,91 @@ TEST_F(vsock, connect_timeout_old_exact) ASSERT_EQ(sizeof(tv), optlen); } +/* ---------- raw (ipv4) ---------- */ + +FIXTURE(raw) +{ + int fd; +}; + +FIXTURE_SETUP(raw) +{ + struct icmp_filter filt = { .data = 0xdeadbeef }; + + self->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (self->fd < 0) + SKIP(return, "SOCK_RAW/ICMP socket: %s", strerror(errno)); + + if (setsockopt(self->fd, SOL_RAW, ICMP_FILTER, &filt, sizeof(filt)) < 0) + SKIP(return, "set ICMP_FILTER: %s", strerror(errno)); +} + +FIXTURE_TEARDOWN(raw) +{ + if (self->fd >= 0) + close(self->fd); +} + +TEST_F(raw, icmpfilter_exact) +{ + struct icmp_filter filt = {}; + socklen_t optlen = sizeof(filt); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + &filt, &optlen)); + ASSERT_EQ(sizeof(filt), optlen); + ASSERT_EQ(0xdeadbeef, filt.data); +} + +TEST_F(raw, icmpfilter_oversize_clamped) +{ + char buf[16] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + buf, &optlen)); + ASSERT_EQ(sizeof(struct icmp_filter), optlen); +} + +/* Unlike the int/u64 options above, ICMP_FILTER clamps the length down + * to the user buffer instead of returning EINVAL: a short buffer + * succeeds and reports the truncated length back via optlen. + */ +TEST_F(raw, icmpfilter_undersize_clamped) +{ + char buf[2] = {}; + socklen_t optlen = sizeof(buf); + + ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER, + buf, &optlen)); + ASSERT_EQ(sizeof(buf), optlen); +} + +TEST_F(raw, icmpfilter_wrong_proto) +{ + struct icmp_filter filt; + socklen_t optlen = sizeof(filt); + int fd; + + fd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + if (fd < 0) + SKIP(return, "SOCK_RAW/UDP socket: %s", strerror(errno)); + + ASSERT_EQ(-1, getsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, &optlen)); + ASSERT_EQ(EOPNOTSUPP, errno); + close(fd); +} + +TEST_F(raw, bad_optname) +{ + socklen_t optlen; + int val; + + optlen = sizeof(val); + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen)); + ASSERT_EQ(ENOPROTOOPT, errno); + ASSERT_EQ(sizeof(val), optlen); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/lib.sh b/tools/testing/selftests/net/lib.sh index d46d2cec89e4..f13672c985bf 100644 --- a/tools/testing/selftests/net/lib.sh +++ b/tools/testing/selftests/net/lib.sh @@ -72,13 +72,9 @@ ksft_exit_status_merge() timestamp_ms() { - local now - local seconds - local nanoseconds - - now=$(date -u +%s:%N) || return - seconds=${now%:*} - nanoseconds=${now#*:} + local now=$(date -u +%s:%N) + local seconds=${now%:*} + local nanoseconds=${now#*:} if [[ $nanoseconds =~ ^[0-9]+$ ]]; then nanoseconds=${nanoseconds:0:9} @@ -93,10 +89,9 @@ loopy_wait() { local sleep_cmd=$1; shift local timeout_ms=$1; shift - local start_time local current_time - start_time=$(timestamp_ms) || return + local start_time=$(timestamp_ms) while true do local out @@ -105,7 +100,7 @@ loopy_wait() return 0 fi - current_time=$(timestamp_ms) || return + local current_time=$(timestamp_ms) if ((current_time - start_time > timeout_ms)); then echo -n "$out" return 1 diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index 2954245129a2..f75ee723415a 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -32,6 +32,7 @@ tests=" dec_ttl ttl: dec_ttl decrements IP TTL flow_set flow-set: Flow modify action_set set: SET action rewrites fields + trunc trunc: output truncation psample psample: Sampling packets with psample" info() { @@ -443,6 +444,92 @@ test_action_set() { return 0 } +# trunc test +# - trunc(14): truncate to ETH_HLEN, strips IP payload, ping fails +# - trunc(1) and trunc(13): kernel rejects below ETH_HLEN (EINVAL) +# - restore normal forwarding and verify recovery +test_trunc() { + sbx_add "test_trunc" || return $? + ovs_add_dp "test_trunc" trunctest || return 1 + + info "create namespaces" + for ns in client server; do + ovs_add_netns_and_veths "test_trunc" "trunctest" \ + "$ns" "${ns:0:1}0" "${ns:0:1}1" || return 1 + done + + ip netns exec client ip addr add 10.0.0.1/24 dev c1 + ip netns exec client ip link set c1 up + ip netns exec server ip addr add 10.0.0.2/24 dev s1 + ip netns exec server ip link set s1 up + + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify connectivity without truncation" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 || return 1 + + # trunc below ETH_HLEN must be rejected by the kernel + info "verify trunc(1) is rejected" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(1),2' &> /dev/null \ + && { info "trunc(1) should be rejected"; return 1; } + + info "verify trunc(13) is rejected" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(13),2' &> /dev/null \ + && { info "trunc(13) should be rejected"; return 1; } + + ovs_del_flows "test_trunc" trunctest + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + + info "add trunc(14) forwarding flow" + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + 'trunc(14),2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify ping fails with trunc(14)" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 >/dev/null 2>&1 \ + && { info "ping should fail with trunc(14)" + return 1; } + + ovs_del_flows "test_trunc" trunctest + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(1),eth(),eth_type(0x0800),ipv4()' \ + '2' || return 1 + ovs_add_flow "test_trunc" trunctest \ + 'in_port(2),eth(),eth_type(0x0800),ipv4()' \ + '1' || return 1 + + info "verify connectivity restored" + ovs_sbx "test_trunc" ip netns exec client \ + ping -c 1 -W 2 10.0.0.2 || return 1 + + return 0 +} + # psample test # - use psample to observe packets test_psample() { diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py index 3622413d793d..0c67c7c00d84 100755 --- a/tools/testing/selftests/net/rtnetlink.py +++ b/tools/testing/selftests/net/rtnetlink.py @@ -2,27 +2,106 @@ # SPDX-License-Identifier: GPL-2.0 import socket +import struct import time -from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_ge, ksft_true, KsftSkipEx +from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01' +IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01' +IPV6_TEST_MULTICAST = bytes.fromhex('ff020000000000000000000000000123') + + +def _users_for(rtnl: RtnlAddrFamily, family: int, grp: bytes, ifindex: int): + """Return mc-users for grp on ifindex, or 0 if absent.""" + + addrs = rtnl.getmulticast({"ifa-family": family}, dump=True) + matches = [addr for addr in addrs + if addr['multicast'] == grp and addr['ifa-index'] == ifindex] + if not matches: + return 0 + if 'mc-users' not in matches[0]: + return None + + return matches[0]['mc-users'] + def dump_mcaddr_check() -> None: """ - Verify that at least one interface has the IPv4 all-hosts multicast address. - At least the loopback interface should have this address. + Verify IPv4 multicast addresses and their user counts in RTM_GETMULTICAST. + """ + + with NetNS() as ns: + with NetNSEnter(str(ns)): + ip("link set lo up") + rtnl = RtnlAddrFamily() + lo_idx = socket.if_nametoindex('lo') + addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) + + all_host_multicasts = [ + addr for addr in addresses + if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST + ] + + ksft_ge(len(all_host_multicasts), 1, + "No interface found with the IPv4 all-hosts multicast address") + + mreq = IPV4_TEST_MULTICAST + socket.inet_aton('127.0.0.1') + before = _users_for(rtnl, socket.AF_INET, IPV4_TEST_MULTICAST, lo_idx) + if before is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS") + + s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + s1.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + s2.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + + after_join = _users_for(rtnl, socket.AF_INET, + IPV4_TEST_MULTICAST, lo_idx) + if after_join is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS") + ksft_eq(after_join - before, 2, + f"users delta != 2 after two joins " + f"(before={before}, after={after_join})") + finally: + s1.close() + s2.close() + + +def dump_mcaddr6_check() -> None: + """ + Verify IPv6 multicast addresses and their user counts in RTM_GETMULTICAST. """ - rtnl = RtnlAddrFamily() - addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) + with NetNS() as ns: + with NetNSEnter(str(ns)): + ip("link set lo up") + rtnl = RtnlAddrFamily() + lo_idx = socket.if_nametoindex('lo') + before = _users_for(rtnl, socket.AF_INET6, + IPV6_TEST_MULTICAST, lo_idx) + if before is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") + + mreq = IPV6_TEST_MULTICAST + struct.pack('=I', lo_idx) + s1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + s2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + try: + s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) + s2.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) - all_host_multicasts = [ - addr for addr in addresses if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST - ] + after_join = _users_for(rtnl, socket.AF_INET6, + IPV6_TEST_MULTICAST, lo_idx) + if after_join is None: + raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") + ksft_eq(after_join - before, 2, + f"IPv6 users delta != 2 after two joins " + f"(before={before}, after={after_join})") + finally: + s1.close() + s2.close() - ksft_ge(len(all_host_multicasts), 1, - "No interface found with the IPv4 all-hosts multicast address") def ipv4_devconf_notify() -> None: """ @@ -56,7 +135,7 @@ def ipv4_devconf_notify() -> None: f"No 'forwarding on' notificiation found for interface {ifname}") def main() -> None: - ksft_run([dump_mcaddr_check, ipv4_devconf_notify]) + ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify]) ksft_exit() if __name__ == "__main__": diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json index a1f97a4b606e..0cf12c50fb74 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json +++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json @@ -1540,5 +1540,169 @@ "$TC qdisc del dev $DUMMY root", "$IP addr del 10.10.10.10/24 dev $DUMMY || true" ] + }, + { + "id": "fb6c", + "name": "Force multiq to dequeue from its child's gso_skb with qfq leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "qfq" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$IP addr add 10.10.11.10/24 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b", + "$TC qdisc add dev $ETH parent 1: handle 2: multiq", + "$TC qdisc add dev $ETH parent 2:1 handle 3: qfq", + "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1", + "$TC filter add dev $ETH parent 2: protocol all prio 1 matchall action skbedit queue_mapping 0", + "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -s -j qdisc ls dev $ETH parent 1:", + "matchJSON": [ + { + "kind": "multiq", + "handle": "2:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "1922", + "name": "Force multiq to dequeue from its child's gso_skb with dualpi2 leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "dualpi2" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$IP addr add 10.10.11.10/24 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b", + "$TC qdisc add dev $ETH parent 1: handle 2: multiq", + "$TC qdisc add dev $ETH parent 2:1 handle 3: dualpi2", + "$TC filter add dev $ETH parent 2: protocol ip prio 1 u32 match ip dst 10.10.11.1 action skbedit queue_mapping 0", + "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:", + "matchJSON": [ + { + "kind": "dualpi2", + "handle": "3:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "476f", + "name": "Force taprio to dequeue from its child's gso_skb with qfq leaf", + "category": [ + "qdisc", + "tbf", + "multiq", + "qfq" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI", + "$TC qdisc add dev $ETH parent 1:1 handle 3: qfq", + "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1", + "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -s -j qdisc ls dev $ETH", + "matchJSON": [ + { + "kind": "taprio", + "handle": "1:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] + }, + { + "id": "0235", + "name": "Force taprio to dequeue from its child's gso_skb with dualpi2 leaf", + "category": [ + "qdisc", + "tbf", + "taprio", + "dualpi2" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", + "$IP link set dev $ETH up || true", + "$IP l set addr 01:02:03:04:05:06 dev $ETH || true", + "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true", + "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI", + "$TC qdisc replace dev $ETH parent 1:1 handle 3: dualpi2", + "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok" + ], + "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true", + "expExitCode": "0", + "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:", + "matchJSON": [ + { + "kind": "dualpi2", + "handle": "3:", + "bytes": 98, + "packets": 1, + "backlog": 0, + "qlen": 0 + } + ], + "teardown": [ + "$TC qdisc del dev $ETH handle 1: root", + "echo \"1\" > /sys/bus/netdevsim/del_device" + ] } ] |
