diff options
author | Mike Pagano <mpagano@gentoo.org> | 2023-08-30 10:42:12 -0400 |
---|---|---|
committer | Mike Pagano <mpagano@gentoo.org> | 2023-08-30 10:42:12 -0400 |
commit | bcbc0e39da9f3b81b92bc26417a3a3ebf0395db5 (patch) | |
tree | 84f50abb352430e59e7476e1bb59963255ddec5d | |
parent | Linux patch 6.1.49 (diff) | |
download | linux-patches-bcbc0e39da9f3b81b92bc26417a3a3ebf0395db5.tar.gz linux-patches-bcbc0e39da9f3b81b92bc26417a3a3ebf0395db5.tar.bz2 linux-patches-bcbc0e39da9f3b81b92bc26417a3a3ebf0395db5.zip |
Linux patch 6.1.506.1-56
Signed-off-by: Mike Pagano <mpagano@gentoo.org>
-rw-r--r-- | 0000_README | 4 | ||||
-rw-r--r-- | 1049_linux-6.1.50.patch | 31691 |
2 files changed, 31695 insertions, 0 deletions
diff --git a/0000_README b/0000_README index 6ce2c13b..870654fc 100644 --- a/0000_README +++ b/0000_README @@ -239,6 +239,10 @@ Patch: 1048_linux-6.1.49.patch From: https://www.kernel.org Desc: Linux 6.1.49 +Patch: 1049_linux-6.1.50.patch +From: https://www.kernel.org +Desc: Linux 6.1.50 + Patch: 1500_XATTR_USER_PREFIX.patch From: https://bugs.gentoo.org/show_bug.cgi?id=470644 Desc: Support for namespace user.pax.* on tmpfs. diff --git a/1049_linux-6.1.50.patch b/1049_linux-6.1.50.patch new file mode 100644 index 00000000..4a052c91 --- /dev/null +++ b/1049_linux-6.1.50.patch @@ -0,0 +1,31691 @@ +diff --git a/MAINTAINERS b/MAINTAINERS +index 379387e20a96d..07a9c274c0e29 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -6027,7 +6027,7 @@ S: Supported + F: Documentation/networking/devlink + F: include/net/devlink.h + F: include/uapi/linux/devlink.h +-F: net/core/devlink.c ++F: net/devlink/ + + DH ELECTRONICS IMX6 DHCOM BOARD SUPPORT + M: Christoph Niedermaier <cniedermaier@dh-electronics.com> +diff --git a/Makefile b/Makefile +index 61ebd54aba899..e5e1fdeef8bf0 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,7 +1,7 @@ + # SPDX-License-Identifier: GPL-2.0 + VERSION = 6 + PATCHLEVEL = 1 +-SUBLEVEL = 49 ++SUBLEVEL = 50 + EXTRAVERSION = + NAME = Curry Ramen + +diff --git a/arch/mips/include/asm/cpu-features.h b/arch/mips/include/asm/cpu-features.h +index c0983130a44c9..e0a4da4cfd8bc 100644 +--- a/arch/mips/include/asm/cpu-features.h ++++ b/arch/mips/include/asm/cpu-features.h +@@ -121,7 +121,24 @@ + #define cpu_has_4k_cache __isa_ge_or_opt(1, MIPS_CPU_4K_CACHE) + #endif + #ifndef cpu_has_octeon_cache +-#define cpu_has_octeon_cache 0 ++#define cpu_has_octeon_cache \ ++({ \ ++ int __res; \ ++ \ ++ switch (boot_cpu_type()) { \ ++ case CPU_CAVIUM_OCTEON: \ ++ case CPU_CAVIUM_OCTEON_PLUS: \ ++ case CPU_CAVIUM_OCTEON2: \ ++ case CPU_CAVIUM_OCTEON3: \ ++ __res = 1; \ ++ break; \ ++ \ ++ default: \ ++ __res = 0; \ ++ } \ ++ \ ++ __res; \ ++}) + #endif + /* Don't override `cpu_has_fpu' to 1 or the "nofpu" option won't work. */ + #ifndef cpu_has_fpu +@@ -351,7 +368,7 @@ + ({ \ + int __res; \ + \ +- switch (current_cpu_type()) { \ ++ switch (boot_cpu_type()) { \ + case CPU_M14KC: \ + case CPU_74K: \ + case CPU_1074K: \ +diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig +index 6bf8dc0b8f935..d702359f8ab5e 100644 +--- a/arch/riscv/Kconfig ++++ b/arch/riscv/Kconfig +@@ -447,24 +447,30 @@ config TOOLCHAIN_HAS_ZIHINTPAUSE + config TOOLCHAIN_NEEDS_EXPLICIT_ZICSR_ZIFENCEI + def_bool y + # https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=aed44286efa8ae8717a77d94b51ac3614e2ca6dc +- depends on AS_IS_GNU && AS_VERSION >= 23800 ++ # https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=98416dbb0a62579d4a7a4a76bab51b5b52fec2cd ++ depends on AS_IS_GNU && AS_VERSION >= 23600 + help +- Newer binutils versions default to ISA spec version 20191213 which +- moves some instructions from the I extension to the Zicsr and Zifencei +- extensions. ++ Binutils-2.38 and GCC-12.1.0 bumped the default ISA spec to the newer ++ 20191213 version, which moves some instructions from the I extension to ++ the Zicsr and Zifencei extensions. This requires explicitly specifying ++ Zicsr and Zifencei when binutils >= 2.38 or GCC >= 12.1.0. Zicsr ++ and Zifencei are supported in binutils from version 2.36 onwards. ++ To make life easier, and avoid forcing toolchains that default to a ++ newer ISA spec to version 2.2, relax the check to binutils >= 2.36. ++ For clang < 17 or GCC < 11.3.0, for which this is not possible or need ++ special treatment, this is dealt with in TOOLCHAIN_NEEDS_OLD_ISA_SPEC. + + config TOOLCHAIN_NEEDS_OLD_ISA_SPEC + def_bool y + depends on TOOLCHAIN_NEEDS_EXPLICIT_ZICSR_ZIFENCEI + # https://github.com/llvm/llvm-project/commit/22e199e6afb1263c943c0c0d4498694e15bf8a16 +- depends on CC_IS_CLANG && CLANG_VERSION < 170000 ++ # https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=d29f5d6ab513c52fd872f532c492e35ae9fd6671 ++ depends on (CC_IS_CLANG && CLANG_VERSION < 170000) || (CC_IS_GCC && GCC_VERSION < 110300) + help +- Certain versions of clang do not support zicsr and zifencei via -march +- but newer versions of binutils require it for the reasons noted in the +- help text of CONFIG_TOOLCHAIN_NEEDS_EXPLICIT_ZICSR_ZIFENCEI. This +- option causes an older ISA spec compatible with these older versions +- of clang to be passed to GAS, which has the same result as passing zicsr +- and zifencei to -march. ++ Certain versions of clang and GCC do not support zicsr and zifencei via ++ -march. This option causes an older ISA spec compatible with these older ++ versions of clang and GCC to be passed to GAS, which has the same result ++ as passing zicsr and zifencei to -march. + + config FPU + bool "FPU support" +diff --git a/arch/riscv/kernel/compat_vdso/Makefile b/arch/riscv/kernel/compat_vdso/Makefile +index 7f34f3c7c8827..737c0857b14cd 100644 +--- a/arch/riscv/kernel/compat_vdso/Makefile ++++ b/arch/riscv/kernel/compat_vdso/Makefile +@@ -11,7 +11,13 @@ compat_vdso-syms += flush_icache + COMPAT_CC := $(CC) + COMPAT_LD := $(LD) + +-COMPAT_CC_FLAGS := -march=rv32g -mabi=ilp32 ++# binutils 2.35 does not support the zifencei extension, but in the ISA ++# spec 20191213, G stands for IMAFD_ZICSR_ZIFENCEI. ++ifdef CONFIG_TOOLCHAIN_NEEDS_EXPLICIT_ZICSR_ZIFENCEI ++ COMPAT_CC_FLAGS := -march=rv32g -mabi=ilp32 ++else ++ COMPAT_CC_FLAGS := -march=rv32imafd -mabi=ilp32 ++endif + COMPAT_LD_FLAGS := -melf32lriscv + + # Disable attributes, as they're useless and break the build. +diff --git a/arch/x86/kernel/fpu/context.h b/arch/x86/kernel/fpu/context.h +index 9fcfa5c4dad79..71b5059e092ab 100644 +--- a/arch/x86/kernel/fpu/context.h ++++ b/arch/x86/kernel/fpu/context.h +@@ -19,8 +19,7 @@ + * FPU state for a task MUST let the rest of the kernel know that the + * FPU registers are no longer valid for this task. + * +- * Either one of these invalidation functions is enough. Invalidate +- * a resource you control: CPU if using the CPU for something else ++ * Invalidate a resource you control: CPU if using the CPU for something else + * (with preemption disabled), FPU for the current task, or a task that + * is prevented from running by the current task. + */ +diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c +index caf33486dc5ee..a083f9ac9e4f6 100644 +--- a/arch/x86/kernel/fpu/core.c ++++ b/arch/x86/kernel/fpu/core.c +@@ -679,7 +679,7 @@ static void fpu_reset_fpregs(void) + struct fpu *fpu = ¤t->thread.fpu; + + fpregs_lock(); +- fpu__drop(fpu); ++ __fpu_invalidate_fpregs_state(fpu); + /* + * This does not change the actual hardware registers. It just + * resets the memory image and sets TIF_NEED_FPU_LOAD so a +diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c +index 0bab497c94369..1afbc4866b100 100644 +--- a/arch/x86/kernel/fpu/xstate.c ++++ b/arch/x86/kernel/fpu/xstate.c +@@ -882,6 +882,13 @@ void __init fpu__init_system_xstate(unsigned int legacy_size) + goto out_disable; + } + ++ /* ++ * CPU capabilities initialization runs before FPU init. So ++ * X86_FEATURE_OSXSAVE is not set. Now that XSAVE is completely ++ * functional, set the feature bit so depending code works. ++ */ ++ setup_force_cpu_cap(X86_FEATURE_OSXSAVE); ++ + print_xstate_offset_size(); + pr_info("x86/fpu: Enabled xstate features 0x%llx, context size is %d bytes, using '%s' format.\n", + fpu_kernel_cfg.max_features, +diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c +index 230108a90cf39..beca03556379d 100644 +--- a/arch/x86/kvm/mmu/mmu.c ++++ b/arch/x86/kvm/mmu/mmu.c +@@ -4212,7 +4212,8 @@ static int kvm_faultin_pfn(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault) + * root was invalidated by a memslot update or a relevant mmu_notifier fired. + */ + static bool is_page_fault_stale(struct kvm_vcpu *vcpu, +- struct kvm_page_fault *fault, int mmu_seq) ++ struct kvm_page_fault *fault, ++ unsigned long mmu_seq) + { + struct kvm_mmu_page *sp = to_shadow_page(vcpu->arch.mmu->root.hpa); + +diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c +index 672f0432d7777..70945f00ec412 100644 +--- a/arch/x86/kvm/mmu/tdp_mmu.c ++++ b/arch/x86/kvm/mmu/tdp_mmu.c +@@ -51,7 +51,17 @@ void kvm_mmu_uninit_tdp_mmu(struct kvm *kvm) + if (!kvm->arch.tdp_mmu_enabled) + return; + +- /* Also waits for any queued work items. */ ++ /* ++ * Invalidate all roots, which besides the obvious, schedules all roots ++ * for zapping and thus puts the TDP MMU's reference to each root, i.e. ++ * ultimately frees all roots. ++ */ ++ kvm_tdp_mmu_invalidate_all_roots(kvm); ++ ++ /* ++ * Destroying a workqueue also first flushes the workqueue, i.e. no ++ * need to invoke kvm_tdp_mmu_zap_invalidated_roots(). ++ */ + destroy_workqueue(kvm->arch.tdp_mmu_zap_wq); + + WARN_ON(!list_empty(&kvm->arch.tdp_mmu_pages)); +@@ -127,16 +137,6 @@ static void tdp_mmu_schedule_zap_root(struct kvm *kvm, struct kvm_mmu_page *root + queue_work(kvm->arch.tdp_mmu_zap_wq, &root->tdp_mmu_async_work); + } + +-static inline bool kvm_tdp_root_mark_invalid(struct kvm_mmu_page *page) +-{ +- union kvm_mmu_page_role role = page->role; +- role.invalid = true; +- +- /* No need to use cmpxchg, only the invalid bit can change. */ +- role.word = xchg(&page->role.word, role.word); +- return role.invalid; +-} +- + void kvm_tdp_mmu_put_root(struct kvm *kvm, struct kvm_mmu_page *root, + bool shared) + { +@@ -145,45 +145,12 @@ void kvm_tdp_mmu_put_root(struct kvm *kvm, struct kvm_mmu_page *root, + if (!refcount_dec_and_test(&root->tdp_mmu_root_count)) + return; + +- WARN_ON(!root->tdp_mmu_page); +- + /* +- * The root now has refcount=0. It is valid, but readers already +- * cannot acquire a reference to it because kvm_tdp_mmu_get_root() +- * rejects it. This remains true for the rest of the execution +- * of this function, because readers visit valid roots only +- * (except for tdp_mmu_zap_root_work(), which however +- * does not acquire any reference itself). +- * +- * Even though there are flows that need to visit all roots for +- * correctness, they all take mmu_lock for write, so they cannot yet +- * run concurrently. The same is true after kvm_tdp_root_mark_invalid, +- * since the root still has refcount=0. +- * +- * However, tdp_mmu_zap_root can yield, and writers do not expect to +- * see refcount=0 (see for example kvm_tdp_mmu_invalidate_all_roots()). +- * So the root temporarily gets an extra reference, going to refcount=1 +- * while staying invalid. Readers still cannot acquire any reference; +- * but writers are now allowed to run if tdp_mmu_zap_root yields and +- * they might take an extra reference if they themselves yield. +- * Therefore, when the reference is given back by the worker, +- * there is no guarantee that the refcount is still 1. If not, whoever +- * puts the last reference will free the page, but they will not have to +- * zap the root because a root cannot go from invalid to valid. ++ * The TDP MMU itself holds a reference to each root until the root is ++ * explicitly invalidated, i.e. the final reference should be never be ++ * put for a valid root. + */ +- if (!kvm_tdp_root_mark_invalid(root)) { +- refcount_set(&root->tdp_mmu_root_count, 1); +- +- /* +- * Zapping the root in a worker is not just "nice to have"; +- * it is required because kvm_tdp_mmu_invalidate_all_roots() +- * skips already-invalid roots. If kvm_tdp_mmu_put_root() did +- * not add the root to the workqueue, kvm_tdp_mmu_zap_all_fast() +- * might return with some roots not zapped yet. +- */ +- tdp_mmu_schedule_zap_root(kvm, root); +- return; +- } ++ KVM_BUG_ON(!is_tdp_mmu_page(root) || !root->role.invalid, kvm); + + spin_lock(&kvm->arch.tdp_mmu_pages_lock); + list_del_rcu(&root->link); +@@ -329,7 +296,14 @@ hpa_t kvm_tdp_mmu_get_vcpu_root_hpa(struct kvm_vcpu *vcpu) + root = tdp_mmu_alloc_sp(vcpu); + tdp_mmu_init_sp(root, NULL, 0, role); + +- refcount_set(&root->tdp_mmu_root_count, 1); ++ /* ++ * TDP MMU roots are kept until they are explicitly invalidated, either ++ * by a memslot update or by the destruction of the VM. Initialize the ++ * refcount to two; one reference for the vCPU, and one reference for ++ * the TDP MMU itself, which is held until the root is invalidated and ++ * is ultimately put by tdp_mmu_zap_root_work(). ++ */ ++ refcount_set(&root->tdp_mmu_root_count, 2); + + spin_lock(&kvm->arch.tdp_mmu_pages_lock); + list_add_rcu(&root->link, &kvm->arch.tdp_mmu_roots); +@@ -1027,32 +1001,49 @@ void kvm_tdp_mmu_zap_invalidated_roots(struct kvm *kvm) + /* + * Mark each TDP MMU root as invalid to prevent vCPUs from reusing a root that + * is about to be zapped, e.g. in response to a memslots update. The actual +- * zapping is performed asynchronously, so a reference is taken on all roots. +- * Using a separate workqueue makes it easy to ensure that the destruction is +- * performed before the "fast zap" completes, without keeping a separate list +- * of invalidated roots; the list is effectively the list of work items in +- * the workqueue. +- * +- * Get a reference even if the root is already invalid, the asynchronous worker +- * assumes it was gifted a reference to the root it processes. Because mmu_lock +- * is held for write, it should be impossible to observe a root with zero refcount, +- * i.e. the list of roots cannot be stale. ++ * zapping is performed asynchronously. Using a separate workqueue makes it ++ * easy to ensure that the destruction is performed before the "fast zap" ++ * completes, without keeping a separate list of invalidated roots; the list is ++ * effectively the list of work items in the workqueue. + * +- * This has essentially the same effect for the TDP MMU +- * as updating mmu_valid_gen does for the shadow MMU. ++ * Note, the asynchronous worker is gifted the TDP MMU's reference. ++ * See kvm_tdp_mmu_get_vcpu_root_hpa(). + */ + void kvm_tdp_mmu_invalidate_all_roots(struct kvm *kvm) + { + struct kvm_mmu_page *root; + +- lockdep_assert_held_write(&kvm->mmu_lock); +- list_for_each_entry(root, &kvm->arch.tdp_mmu_roots, link) { +- if (!root->role.invalid && +- !WARN_ON_ONCE(!kvm_tdp_mmu_get_root(root))) { ++ /* ++ * mmu_lock must be held for write to ensure that a root doesn't become ++ * invalid while there are active readers (invalidating a root while ++ * there are active readers may or may not be problematic in practice, ++ * but it's uncharted territory and not supported). ++ * ++ * Waive the assertion if there are no users of @kvm, i.e. the VM is ++ * being destroyed after all references have been put, or if no vCPUs ++ * have been created (which means there are no roots), i.e. the VM is ++ * being destroyed in an error path of KVM_CREATE_VM. ++ */ ++ if (IS_ENABLED(CONFIG_PROVE_LOCKING) && ++ refcount_read(&kvm->users_count) && kvm->created_vcpus) ++ lockdep_assert_held_write(&kvm->mmu_lock); ++ ++ /* ++ * As above, mmu_lock isn't held when destroying the VM! There can't ++ * be other references to @kvm, i.e. nothing else can invalidate roots ++ * or be consuming roots, but walking the list of roots does need to be ++ * guarded against roots being deleted by the asynchronous zap worker. ++ */ ++ rcu_read_lock(); ++ ++ list_for_each_entry_rcu(root, &kvm->arch.tdp_mmu_roots, link) { ++ if (!root->role.invalid) { + root->role.invalid = true; + tdp_mmu_schedule_zap_root(kvm, root); + } + } ++ ++ rcu_read_unlock(); + } + + /* +diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c +index 4459cfbdbcb18..c2f0f74193f0e 100644 +--- a/drivers/block/ublk_drv.c ++++ b/drivers/block/ublk_drv.c +@@ -1223,9 +1223,6 @@ static int __ublk_ch_uring_cmd(struct io_uring_cmd *cmd, + __func__, cmd->cmd_op, ub_cmd->q_id, tag, + ub_cmd->result); + +- if (!(issue_flags & IO_URING_F_SQE128)) +- goto out; +- + if (ub_cmd->q_id >= ub->dev_info.nr_hw_queues) + goto out; + +diff --git a/drivers/clk/clk-devres.c b/drivers/clk/clk-devres.c +index 4fb4fd4b06bda..737aa70e2cb3d 100644 +--- a/drivers/clk/clk-devres.c ++++ b/drivers/clk/clk-devres.c +@@ -205,18 +205,19 @@ EXPORT_SYMBOL(devm_clk_put); + struct clk *devm_get_clk_from_child(struct device *dev, + struct device_node *np, const char *con_id) + { +- struct clk **ptr, *clk; ++ struct devm_clk_state *state; ++ struct clk *clk; + +- ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL); +- if (!ptr) ++ state = devres_alloc(devm_clk_release, sizeof(*state), GFP_KERNEL); ++ if (!state) + return ERR_PTR(-ENOMEM); + + clk = of_clk_get_by_name(np, con_id); + if (!IS_ERR(clk)) { +- *ptr = clk; +- devres_add(dev, ptr); ++ state->clk = clk; ++ devres_add(dev, state); + } else { +- devres_free(ptr); ++ devres_free(state); + } + + return clk; +diff --git a/drivers/dma-buf/sw_sync.c b/drivers/dma-buf/sw_sync.c +index 348b3a9170fa4..7f5ed1aa7a9f8 100644 +--- a/drivers/dma-buf/sw_sync.c ++++ b/drivers/dma-buf/sw_sync.c +@@ -191,6 +191,7 @@ static const struct dma_fence_ops timeline_fence_ops = { + */ + static void sync_timeline_signal(struct sync_timeline *obj, unsigned int inc) + { ++ LIST_HEAD(signalled); + struct sync_pt *pt, *next; + + trace_sync_timeline(obj); +@@ -203,21 +204,20 @@ static void sync_timeline_signal(struct sync_timeline *obj, unsigned int inc) + if (!timeline_fence_signaled(&pt->base)) + break; + +- list_del_init(&pt->link); ++ dma_fence_get(&pt->base); ++ ++ list_move_tail(&pt->link, &signalled); + rb_erase(&pt->node, &obj->pt_tree); + +- /* +- * A signal callback may release the last reference to this +- * fence, causing it to be freed. That operation has to be +- * last to avoid a use after free inside this loop, and must +- * be after we remove the fence from the timeline in order to +- * prevent deadlocking on timeline->lock inside +- * timeline_fence_release(). +- */ + dma_fence_signal_locked(&pt->base); + } + + spin_unlock_irq(&obj->lock); ++ ++ list_for_each_entry_safe(pt, next, &signalled, link) { ++ list_del_init(&pt->link); ++ dma_fence_put(&pt->base); ++ } + } + + /** +diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c +index fef12e57b1f13..b352775e5e0b8 100644 +--- a/drivers/gpio/gpio-sim.c ++++ b/drivers/gpio/gpio-sim.c +@@ -290,6 +290,15 @@ static void gpio_sim_mutex_destroy(void *data) + mutex_destroy(lock); + } + ++static void gpio_sim_dispose_mappings(void *data) ++{ ++ struct gpio_sim_chip *chip = data; ++ unsigned int i; ++ ++ for (i = 0; i < chip->gc.ngpio; i++) ++ irq_dispose_mapping(irq_find_mapping(chip->irq_sim, i)); ++} ++ + static void gpio_sim_sysfs_remove(void *data) + { + struct gpio_sim_chip *chip = data; +@@ -398,10 +407,14 @@ static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev) + if (!chip->pull_map) + return -ENOMEM; + +- chip->irq_sim = devm_irq_domain_create_sim(dev, NULL, num_lines); ++ chip->irq_sim = devm_irq_domain_create_sim(dev, swnode, num_lines); + if (IS_ERR(chip->irq_sim)) + return PTR_ERR(chip->irq_sim); + ++ ret = devm_add_action_or_reset(dev, gpio_sim_dispose_mappings, chip); ++ if (ret) ++ return ret; ++ + mutex_init(&chip->lock); + ret = devm_add_action_or_reset(dev, gpio_sim_mutex_destroy, + &chip->lock); +diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c +index a032003c340cc..d6ea47873627f 100644 +--- a/drivers/gpu/drm/arm/hdlcd_drv.c ++++ b/drivers/gpu/drm/arm/hdlcd_drv.c +@@ -290,7 +290,7 @@ static int hdlcd_drm_bind(struct device *dev) + */ + if (hdlcd_read(hdlcd, HDLCD_REG_COMMAND)) { + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); +- drm_aperture_remove_framebuffers(false, &hdlcd_driver); ++ drm_aperture_remove_framebuffers(&hdlcd_driver); + } + + drm_mode_config_reset(drm); +diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c +index 142668cd6d7cd..688ba358f5319 100644 +--- a/drivers/gpu/drm/armada/armada_drv.c ++++ b/drivers/gpu/drm/armada/armada_drv.c +@@ -95,7 +95,7 @@ static int armada_drm_bind(struct device *dev) + } + + /* Remove early framebuffers */ +- ret = drm_aperture_remove_framebuffers(false, &armada_drm_driver); ++ ret = drm_aperture_remove_framebuffers(&armada_drm_driver); + if (ret) { + dev_err(dev, "[" DRM_NAME ":%s] can't kick out simple-fb: %d\n", + __func__, ret); +diff --git a/drivers/gpu/drm/ast/ast_drv.c b/drivers/gpu/drm/ast/ast_drv.c +index b9392f31e6291..800471f2a2037 100644 +--- a/drivers/gpu/drm/ast/ast_drv.c ++++ b/drivers/gpu/drm/ast/ast_drv.c +@@ -89,27 +89,13 @@ static const struct pci_device_id ast_pciidlist[] = { + + MODULE_DEVICE_TABLE(pci, ast_pciidlist); + +-static int ast_remove_conflicting_framebuffers(struct pci_dev *pdev) +-{ +- bool primary = false; +- resource_size_t base, size; +- +- base = pci_resource_start(pdev, 0); +- size = pci_resource_len(pdev, 0); +-#ifdef CONFIG_X86 +- primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; +-#endif +- +- return drm_aperture_remove_conflicting_framebuffers(base, size, primary, &ast_driver); +-} +- + static int ast_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) + { + struct ast_private *ast; + struct drm_device *dev; + int ret; + +- ret = ast_remove_conflicting_framebuffers(pdev); ++ ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &ast_driver); + if (ret) + return ret; + +diff --git a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c +index 3b8fdeeafd53a..697cffbfd6037 100644 +--- a/drivers/gpu/drm/drm_aperture.c ++++ b/drivers/gpu/drm/drm_aperture.c +@@ -32,17 +32,13 @@ + * + * static int remove_conflicting_framebuffers(struct pci_dev *pdev) + * { +- * bool primary = false; + * resource_size_t base, size; + * int ret; + * + * base = pci_resource_start(pdev, 0); + * size = pci_resource_len(pdev, 0); +- * #ifdef CONFIG_X86 +- * primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; +- * #endif + * +- * return drm_aperture_remove_conflicting_framebuffers(base, size, primary, ++ * return drm_aperture_remove_conflicting_framebuffers(base, size, + * &example_driver); + * } + * +@@ -161,7 +157,6 @@ EXPORT_SYMBOL(devm_aperture_acquire_from_firmware); + * drm_aperture_remove_conflicting_framebuffers - remove existing framebuffers in the given range + * @base: the aperture's base address in physical memory + * @size: aperture size in bytes +- * @primary: also kick vga16fb if present + * @req_driver: requesting DRM driver + * + * This function removes graphics device drivers which use the memory range described by +@@ -171,9 +166,9 @@ EXPORT_SYMBOL(devm_aperture_acquire_from_firmware); + * 0 on success, or a negative errno code otherwise + */ + int drm_aperture_remove_conflicting_framebuffers(resource_size_t base, resource_size_t size, +- bool primary, const struct drm_driver *req_driver) ++ const struct drm_driver *req_driver) + { +- return aperture_remove_conflicting_devices(base, size, primary, req_driver->name); ++ return aperture_remove_conflicting_devices(base, size, false, req_driver->name); + } + EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers); + +diff --git a/drivers/gpu/drm/gma500/psb_drv.c b/drivers/gpu/drm/gma500/psb_drv.c +index cd9c73f5a64ab..738eb558a97e9 100644 +--- a/drivers/gpu/drm/gma500/psb_drv.c ++++ b/drivers/gpu/drm/gma500/psb_drv.c +@@ -424,12 +424,17 @@ static int psb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) + + /* + * We cannot yet easily find the framebuffer's location in memory. So +- * remove all framebuffers here. ++ * remove all framebuffers here. Note that we still want the pci special ++ * handling to kick out vgacon. + * + * TODO: Refactor psb_driver_load() to map vdc_reg earlier. Then we + * might be able to read the framebuffer range from the device. + */ +- ret = drm_aperture_remove_framebuffers(true, &driver); ++ ret = drm_aperture_remove_framebuffers(&driver); ++ if (ret) ++ return ret; ++ ++ ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &driver); + if (ret) + return ret; + +diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c +index ca127ff797f75..29ee0814bccc8 100644 +--- a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c ++++ b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c +@@ -74,7 +74,6 @@ static int hyperv_setup_vram(struct hyperv_drm_device *hv, + + drm_aperture_remove_conflicting_framebuffers(screen_info.lfb_base, + screen_info.lfb_size, +- false, + &hyperv_driver); + + hv->fb_size = (unsigned long)hv->mmio_megabytes * 1024 * 1024; +diff --git a/drivers/gpu/drm/i915/gt/gen8_engine_cs.c b/drivers/gpu/drm/i915/gt/gen8_engine_cs.c +index b2838732ac936..cc84685368715 100644 +--- a/drivers/gpu/drm/i915/gt/gen8_engine_cs.c ++++ b/drivers/gpu/drm/i915/gt/gen8_engine_cs.c +@@ -165,14 +165,60 @@ static u32 preparser_disable(bool state) + return MI_ARB_CHECK | 1 << 8 | state; + } + +-u32 *gen12_emit_aux_table_inv(struct intel_gt *gt, u32 *cs, const i915_reg_t inv_reg) ++static i915_reg_t gen12_get_aux_inv_reg(struct intel_engine_cs *engine) + { +- u32 gsi_offset = gt->uncore->gsi_offset; ++ switch (engine->id) { ++ case RCS0: ++ return GEN12_CCS_AUX_INV; ++ case BCS0: ++ return GEN12_BCS0_AUX_INV; ++ case VCS0: ++ return GEN12_VD0_AUX_INV; ++ case VCS2: ++ return GEN12_VD2_AUX_INV; ++ case VECS0: ++ return GEN12_VE0_AUX_INV; ++ case CCS0: ++ return GEN12_CCS0_AUX_INV; ++ default: ++ return INVALID_MMIO_REG; ++ } ++} ++ ++static bool gen12_needs_ccs_aux_inv(struct intel_engine_cs *engine) ++{ ++ i915_reg_t reg = gen12_get_aux_inv_reg(engine); ++ ++ if (IS_PONTEVECCHIO(engine->i915)) ++ return false; ++ ++ /* ++ * So far platforms supported by i915 having flat ccs do not require ++ * AUX invalidation. Check also whether the engine requires it. ++ */ ++ return i915_mmio_reg_valid(reg) && !HAS_FLAT_CCS(engine->i915); ++} ++ ++u32 *gen12_emit_aux_table_inv(struct intel_engine_cs *engine, u32 *cs) ++{ ++ i915_reg_t inv_reg = gen12_get_aux_inv_reg(engine); ++ u32 gsi_offset = engine->gt->uncore->gsi_offset; ++ ++ if (!gen12_needs_ccs_aux_inv(engine)) ++ return cs; + + *cs++ = MI_LOAD_REGISTER_IMM(1) | MI_LRI_MMIO_REMAP_EN; + *cs++ = i915_mmio_reg_offset(inv_reg) + gsi_offset; + *cs++ = AUX_INV; +- *cs++ = MI_NOOP; ++ ++ *cs++ = MI_SEMAPHORE_WAIT_TOKEN | ++ MI_SEMAPHORE_REGISTER_POLL | ++ MI_SEMAPHORE_POLL | ++ MI_SEMAPHORE_SAD_EQ_SDD; ++ *cs++ = 0; ++ *cs++ = i915_mmio_reg_offset(inv_reg) + gsi_offset; ++ *cs++ = 0; ++ *cs++ = 0; + + return cs; + } +@@ -181,7 +227,11 @@ int gen12_emit_flush_rcs(struct i915_request *rq, u32 mode) + { + struct intel_engine_cs *engine = rq->engine; + +- if (mode & EMIT_FLUSH) { ++ /* ++ * On Aux CCS platforms the invalidation of the Aux ++ * table requires quiescing memory traffic beforehand ++ */ ++ if (mode & EMIT_FLUSH || gen12_needs_ccs_aux_inv(engine)) { + u32 flags = 0; + u32 *cs; + +@@ -236,10 +286,9 @@ int gen12_emit_flush_rcs(struct i915_request *rq, u32 mode) + else if (engine->class == COMPUTE_CLASS) + flags &= ~PIPE_CONTROL_3D_ENGINE_FLAGS; + +- if (!HAS_FLAT_CCS(rq->engine->i915)) +- count = 8 + 4; +- else +- count = 8; ++ count = 8; ++ if (gen12_needs_ccs_aux_inv(rq->engine)) ++ count += 8; + + cs = intel_ring_begin(rq, count); + if (IS_ERR(cs)) +@@ -254,11 +303,7 @@ int gen12_emit_flush_rcs(struct i915_request *rq, u32 mode) + + cs = gen8_emit_pipe_control(cs, flags, LRC_PPHWSP_SCRATCH_ADDR); + +- if (!HAS_FLAT_CCS(rq->engine->i915)) { +- /* hsdes: 1809175790 */ +- cs = gen12_emit_aux_table_inv(rq->engine->gt, cs, +- GEN12_CCS_AUX_INV); +- } ++ cs = gen12_emit_aux_table_inv(engine, cs); + + *cs++ = preparser_disable(false); + intel_ring_advance(rq, cs); +@@ -269,21 +314,14 @@ int gen12_emit_flush_rcs(struct i915_request *rq, u32 mode) + + int gen12_emit_flush_xcs(struct i915_request *rq, u32 mode) + { +- intel_engine_mask_t aux_inv = 0; +- u32 cmd, *cs; ++ u32 cmd = 4; ++ u32 *cs; + +- cmd = 4; + if (mode & EMIT_INVALIDATE) { + cmd += 2; + +- if (!HAS_FLAT_CCS(rq->engine->i915) && +- (rq->engine->class == VIDEO_DECODE_CLASS || +- rq->engine->class == VIDEO_ENHANCEMENT_CLASS)) { +- aux_inv = rq->engine->mask & +- ~GENMASK(_BCS(I915_MAX_BCS - 1), BCS0); +- if (aux_inv) +- cmd += 4; +- } ++ if (gen12_needs_ccs_aux_inv(rq->engine)) ++ cmd += 8; + } + + cs = intel_ring_begin(rq, cmd); +@@ -314,14 +352,7 @@ int gen12_emit_flush_xcs(struct i915_request *rq, u32 mode) + *cs++ = 0; /* upper addr */ + *cs++ = 0; /* value */ + +- if (aux_inv) { /* hsdes: 1809175790 */ +- if (rq->engine->class == VIDEO_DECODE_CLASS) +- cs = gen12_emit_aux_table_inv(rq->engine->gt, +- cs, GEN12_VD0_AUX_INV); +- else +- cs = gen12_emit_aux_table_inv(rq->engine->gt, +- cs, GEN12_VE0_AUX_INV); +- } ++ cs = gen12_emit_aux_table_inv(rq->engine, cs); + + if (mode & EMIT_INVALIDATE) + *cs++ = preparser_disable(false); +diff --git a/drivers/gpu/drm/i915/gt/gen8_engine_cs.h b/drivers/gpu/drm/i915/gt/gen8_engine_cs.h +index e4d24c811dd61..651eb786e930c 100644 +--- a/drivers/gpu/drm/i915/gt/gen8_engine_cs.h ++++ b/drivers/gpu/drm/i915/gt/gen8_engine_cs.h +@@ -13,6 +13,7 @@ + #include "intel_gt_regs.h" + #include "intel_gpu_commands.h" + ++struct intel_engine_cs; + struct intel_gt; + struct i915_request; + +@@ -46,7 +47,7 @@ u32 *gen8_emit_fini_breadcrumb_rcs(struct i915_request *rq, u32 *cs); + u32 *gen11_emit_fini_breadcrumb_rcs(struct i915_request *rq, u32 *cs); + u32 *gen12_emit_fini_breadcrumb_rcs(struct i915_request *rq, u32 *cs); + +-u32 *gen12_emit_aux_table_inv(struct intel_gt *gt, u32 *cs, const i915_reg_t inv_reg); ++u32 *gen12_emit_aux_table_inv(struct intel_engine_cs *engine, u32 *cs); + + static inline u32 * + __gen8_emit_pipe_control(u32 *batch, u32 flags0, u32 flags1, u32 offset) +diff --git a/drivers/gpu/drm/i915/gt/intel_gpu_commands.h b/drivers/gpu/drm/i915/gt/intel_gpu_commands.h +index d4e9702d3c8e7..25ea5f8a464a4 100644 +--- a/drivers/gpu/drm/i915/gt/intel_gpu_commands.h ++++ b/drivers/gpu/drm/i915/gt/intel_gpu_commands.h +@@ -120,6 +120,7 @@ + #define MI_SEMAPHORE_TARGET(engine) ((engine)<<15) + #define MI_SEMAPHORE_WAIT MI_INSTR(0x1c, 2) /* GEN8+ */ + #define MI_SEMAPHORE_WAIT_TOKEN MI_INSTR(0x1c, 3) /* GEN12+ */ ++#define MI_SEMAPHORE_REGISTER_POLL (1 << 16) + #define MI_SEMAPHORE_POLL (1 << 15) + #define MI_SEMAPHORE_SAD_GT_SDD (0 << 12) + #define MI_SEMAPHORE_SAD_GTE_SDD (1 << 12) +diff --git a/drivers/gpu/drm/i915/gt/intel_lrc.c b/drivers/gpu/drm/i915/gt/intel_lrc.c +index 137e41e37ea54..7eb01ff17d89b 100644 +--- a/drivers/gpu/drm/i915/gt/intel_lrc.c ++++ b/drivers/gpu/drm/i915/gt/intel_lrc.c +@@ -1296,10 +1296,7 @@ gen12_emit_indirect_ctx_rcs(const struct intel_context *ce, u32 *cs) + IS_DG2_G11(ce->engine->i915)) + cs = gen8_emit_pipe_control(cs, PIPE_CONTROL_INSTRUCTION_CACHE_INVALIDATE, 0); + +- /* hsdes: 1809175790 */ +- if (!HAS_FLAT_CCS(ce->engine->i915)) +- cs = gen12_emit_aux_table_inv(ce->engine->gt, +- cs, GEN12_CCS_AUX_INV); ++ cs = gen12_emit_aux_table_inv(ce->engine, cs); + + /* Wa_16014892111 */ + if (IS_DG2(ce->engine->i915)) +@@ -1322,17 +1319,7 @@ gen12_emit_indirect_ctx_xcs(const struct intel_context *ce, u32 *cs) + PIPE_CONTROL_INSTRUCTION_CACHE_INVALIDATE, + 0); + +- /* hsdes: 1809175790 */ +- if (!HAS_FLAT_CCS(ce->engine->i915)) { +- if (ce->engine->class == VIDEO_DECODE_CLASS) +- cs = gen12_emit_aux_table_inv(ce->engine->gt, +- cs, GEN12_VD0_AUX_INV); +- else if (ce->engine->class == VIDEO_ENHANCEMENT_CLASS) +- cs = gen12_emit_aux_table_inv(ce->engine->gt, +- cs, GEN12_VE0_AUX_INV); +- } +- +- return cs; ++ return gen12_emit_aux_table_inv(ce->engine, cs); + } + + static void +diff --git a/drivers/gpu/drm/i915/i915_driver.c b/drivers/gpu/drm/i915/i915_driver.c +index 35bc2a3fa811c..75a93951fe429 100644 +--- a/drivers/gpu/drm/i915/i915_driver.c ++++ b/drivers/gpu/drm/i915/i915_driver.c +@@ -574,7 +574,6 @@ static int i915_pcode_init(struct drm_i915_private *i915) + static int i915_driver_hw_probe(struct drm_i915_private *dev_priv) + { + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); +- struct pci_dev *root_pdev; + int ret; + + if (i915_inject_probe_failure(dev_priv)) +@@ -686,15 +685,6 @@ static int i915_driver_hw_probe(struct drm_i915_private *dev_priv) + + intel_bw_init_hw(dev_priv); + +- /* +- * FIXME: Temporary hammer to avoid freezing the machine on our DGFX +- * This should be totally removed when we handle the pci states properly +- * on runtime PM and on s2idle cases. +- */ +- root_pdev = pcie_find_root_port(pdev); +- if (root_pdev) +- pci_d3cold_disable(root_pdev); +- + return 0; + + err_msi: +@@ -718,16 +708,11 @@ err_perf: + static void i915_driver_hw_remove(struct drm_i915_private *dev_priv) + { + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); +- struct pci_dev *root_pdev; + + i915_perf_fini(dev_priv); + + if (pdev->msi_enabled) + pci_disable_msi(pdev); +- +- root_pdev = pcie_find_root_port(pdev); +- if (root_pdev) +- pci_d3cold_enable(root_pdev); + } + + /** +@@ -1625,6 +1610,8 @@ static int intel_runtime_suspend(struct device *kdev) + { + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + struct intel_runtime_pm *rpm = &dev_priv->runtime_pm; ++ struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); ++ struct pci_dev *root_pdev; + struct intel_gt *gt; + int ret, i; + +@@ -1674,6 +1661,15 @@ static int intel_runtime_suspend(struct device *kdev) + drm_err(&dev_priv->drm, + "Unclaimed access detected prior to suspending\n"); + ++ /* ++ * FIXME: Temporary hammer to avoid freezing the machine on our DGFX ++ * This should be totally removed when we handle the pci states properly ++ * on runtime PM. ++ */ ++ root_pdev = pcie_find_root_port(pdev); ++ if (root_pdev) ++ pci_d3cold_disable(root_pdev); ++ + rpm->suspended = true; + + /* +@@ -1712,6 +1708,8 @@ static int intel_runtime_resume(struct device *kdev) + { + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + struct intel_runtime_pm *rpm = &dev_priv->runtime_pm; ++ struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); ++ struct pci_dev *root_pdev; + struct intel_gt *gt; + int ret, i; + +@@ -1725,6 +1723,11 @@ static int intel_runtime_resume(struct device *kdev) + + intel_opregion_notify_adapter(dev_priv, PCI_D0); + rpm->suspended = false; ++ ++ root_pdev = pcie_find_root_port(pdev); ++ if (root_pdev) ++ pci_d3cold_enable(root_pdev); ++ + if (intel_uncore_unclaimed_mmio(&dev_priv->uncore)) + drm_dbg(&dev_priv->drm, + "Unclaimed access during suspend, bios?\n"); +diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c +index eea433ade79d0..119544d88b586 100644 +--- a/drivers/gpu/drm/meson/meson_drv.c ++++ b/drivers/gpu/drm/meson/meson_drv.c +@@ -285,7 +285,7 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + * Remove early framebuffers (ie. simplefb). The framebuffer can be + * located anywhere in RAM + */ +- ret = drm_aperture_remove_framebuffers(false, &meson_driver); ++ ret = drm_aperture_remove_framebuffers(&meson_driver); + if (ret) + goto free_drm; + +diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c +index 46168eccfac4a..d4a9b501e1bcc 100644 +--- a/drivers/gpu/drm/msm/msm_fbdev.c ++++ b/drivers/gpu/drm/msm/msm_fbdev.c +@@ -157,7 +157,7 @@ struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev) + } + + /* the fw fb could be anywhere in memory */ +- ret = drm_aperture_remove_framebuffers(false, dev->driver); ++ ret = drm_aperture_remove_framebuffers(dev->driver); + if (ret) + goto fini; + +diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +index 813f9f8c86982..8e12053a220b0 100644 +--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c ++++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +@@ -140,7 +140,7 @@ static int rockchip_drm_bind(struct device *dev) + int ret; + + /* Remove existing drivers that may own the framebuffer memory. */ +- ret = drm_aperture_remove_framebuffers(false, &rockchip_drm_driver); ++ ret = drm_aperture_remove_framebuffers(&rockchip_drm_driver); + if (ret) { + DRM_DEV_ERROR(dev, + "Failed to remove existing framebuffers - %d.\n", +diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c +index d7914f5122dff..0a09a85ac9d69 100644 +--- a/drivers/gpu/drm/stm/drv.c ++++ b/drivers/gpu/drm/stm/drv.c +@@ -185,7 +185,7 @@ static int stm_drm_platform_probe(struct platform_device *pdev) + + DRM_DEBUG("%s\n", __func__); + +- ret = drm_aperture_remove_framebuffers(false, &drv_driver); ++ ret = drm_aperture_remove_framebuffers(&drv_driver); + if (ret) + return ret; + +diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c +index 7910c5853f0a8..5c483bbccbbbc 100644 +--- a/drivers/gpu/drm/sun4i/sun4i_drv.c ++++ b/drivers/gpu/drm/sun4i/sun4i_drv.c +@@ -98,7 +98,7 @@ static int sun4i_drv_bind(struct device *dev) + goto unbind_all; + + /* Remove early framebuffers (ie. simplefb) */ +- ret = drm_aperture_remove_framebuffers(false, &sun4i_drv_driver); ++ ret = drm_aperture_remove_framebuffers(&sun4i_drv_driver); + if (ret) + goto unbind_all; + +diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c +index a1f909dac89a7..5fc55b9777cbf 100644 +--- a/drivers/gpu/drm/tegra/drm.c ++++ b/drivers/gpu/drm/tegra/drm.c +@@ -1252,7 +1252,7 @@ static int host1x_drm_probe(struct host1x_device *dev) + + drm_mode_config_reset(drm); + +- err = drm_aperture_remove_framebuffers(false, &tegra_drm_driver); ++ err = drm_aperture_remove_framebuffers(&tegra_drm_driver); + if (err < 0) + goto hub; + +diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c +index 8c329c071c62d..b6384a5dfdbc1 100644 +--- a/drivers/gpu/drm/vc4/vc4_drv.c ++++ b/drivers/gpu/drm/vc4/vc4_drv.c +@@ -351,7 +351,7 @@ static int vc4_drm_bind(struct device *dev) + return -EPROBE_DEFER; + } + +- ret = drm_aperture_remove_framebuffers(false, driver); ++ ret = drm_aperture_remove_framebuffers(driver); + if (ret) + return ret; + +diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h +index 1ec9c53a7bf43..8459fab9d9797 100644 +--- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h ++++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h +@@ -1683,4 +1683,16 @@ static inline bool vmw_has_fences(struct vmw_private *vmw) + return (vmw_fifo_caps(vmw) & SVGA_FIFO_CAP_FENCE) != 0; + } + ++static inline bool vmw_shadertype_is_valid(enum vmw_sm_type shader_model, ++ u32 shader_type) ++{ ++ SVGA3dShaderType max_allowed = SVGA3D_SHADERTYPE_PREDX_MAX; ++ ++ if (shader_model >= VMW_SM_5) ++ max_allowed = SVGA3D_SHADERTYPE_MAX; ++ else if (shader_model >= VMW_SM_4) ++ max_allowed = SVGA3D_SHADERTYPE_DX10_MAX; ++ return shader_type >= SVGA3D_SHADERTYPE_MIN && shader_type < max_allowed; ++} ++ + #endif +diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c +index 1c88b74d68cf0..58ca9adf09871 100644 +--- a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c ++++ b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c +@@ -1985,7 +1985,7 @@ static int vmw_cmd_set_shader(struct vmw_private *dev_priv, + + cmd = container_of(header, typeof(*cmd), header); + +- if (cmd->body.type >= SVGA3D_SHADERTYPE_PREDX_MAX) { ++ if (!vmw_shadertype_is_valid(VMW_SM_LEGACY, cmd->body.type)) { + VMW_DEBUG_USER("Illegal shader type %u.\n", + (unsigned int) cmd->body.type); + return -EINVAL; +@@ -2108,8 +2108,6 @@ vmw_cmd_dx_set_single_constant_buffer(struct vmw_private *dev_priv, + SVGA3dCmdHeader *header) + { + VMW_DECLARE_CMD_VAR(*cmd, SVGA3dCmdDXSetSingleConstantBuffer); +- SVGA3dShaderType max_shader_num = has_sm5_context(dev_priv) ? +- SVGA3D_NUM_SHADERTYPE : SVGA3D_NUM_SHADERTYPE_DX10; + + struct vmw_resource *res = NULL; + struct vmw_ctx_validation_info *ctx_node = VMW_GET_CTX_NODE(sw_context); +@@ -2126,6 +2124,14 @@ vmw_cmd_dx_set_single_constant_buffer(struct vmw_private *dev_priv, + if (unlikely(ret != 0)) + return ret; + ++ if (!vmw_shadertype_is_valid(dev_priv->sm_type, cmd->body.type) || ++ cmd->body.slot >= SVGA3D_DX_MAX_CONSTBUFFERS) { ++ VMW_DEBUG_USER("Illegal const buffer shader %u slot %u.\n", ++ (unsigned int) cmd->body.type, ++ (unsigned int) cmd->body.slot); ++ return -EINVAL; ++ } ++ + binding.bi.ctx = ctx_node->ctx; + binding.bi.res = res; + binding.bi.bt = vmw_ctx_binding_cb; +@@ -2134,14 +2140,6 @@ vmw_cmd_dx_set_single_constant_buffer(struct vmw_private *dev_priv, + binding.size = cmd->body.sizeInBytes; + binding.slot = cmd->body.slot; + +- if (binding.shader_slot >= max_shader_num || +- binding.slot >= SVGA3D_DX_MAX_CONSTBUFFERS) { +- VMW_DEBUG_USER("Illegal const buffer shader %u slot %u.\n", +- (unsigned int) cmd->body.type, +- (unsigned int) binding.slot); +- return -EINVAL; +- } +- + vmw_binding_add(ctx_node->staged, &binding.bi, binding.shader_slot, + binding.slot); + +@@ -2200,15 +2198,13 @@ static int vmw_cmd_dx_set_shader_res(struct vmw_private *dev_priv, + { + VMW_DECLARE_CMD_VAR(*cmd, SVGA3dCmdDXSetShaderResources) = + container_of(header, typeof(*cmd), header); +- SVGA3dShaderType max_allowed = has_sm5_context(dev_priv) ? +- SVGA3D_SHADERTYPE_MAX : SVGA3D_SHADERTYPE_DX10_MAX; + + u32 num_sr_view = (cmd->header.size - sizeof(cmd->body)) / + sizeof(SVGA3dShaderResourceViewId); + + if ((u64) cmd->body.startView + (u64) num_sr_view > + (u64) SVGA3D_DX_MAX_SRVIEWS || +- cmd->body.type >= max_allowed) { ++ !vmw_shadertype_is_valid(dev_priv->sm_type, cmd->body.type)) { + VMW_DEBUG_USER("Invalid shader binding.\n"); + return -EINVAL; + } +@@ -2232,8 +2228,6 @@ static int vmw_cmd_dx_set_shader(struct vmw_private *dev_priv, + SVGA3dCmdHeader *header) + { + VMW_DECLARE_CMD_VAR(*cmd, SVGA3dCmdDXSetShader); +- SVGA3dShaderType max_allowed = has_sm5_context(dev_priv) ? +- SVGA3D_SHADERTYPE_MAX : SVGA3D_SHADERTYPE_DX10_MAX; + struct vmw_resource *res = NULL; + struct vmw_ctx_validation_info *ctx_node = VMW_GET_CTX_NODE(sw_context); + struct vmw_ctx_bindinfo_shader binding; +@@ -2244,8 +2238,7 @@ static int vmw_cmd_dx_set_shader(struct vmw_private *dev_priv, + + cmd = container_of(header, typeof(*cmd), header); + +- if (cmd->body.type >= max_allowed || +- cmd->body.type < SVGA3D_SHADERTYPE_MIN) { ++ if (!vmw_shadertype_is_valid(dev_priv->sm_type, cmd->body.type)) { + VMW_DEBUG_USER("Illegal shader type %u.\n", + (unsigned int) cmd->body.type); + return -EINVAL; +diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c +index c51a2678f0eb5..8c7796d3fdd2d 100644 +--- a/drivers/hwmon/aquacomputer_d5next.c ++++ b/drivers/hwmon/aquacomputer_d5next.c +@@ -12,9 +12,11 @@ + + #include <linux/crc16.h> + #include <linux/debugfs.h> ++#include <linux/delay.h> + #include <linux/hid.h> + #include <linux/hwmon.h> + #include <linux/jiffies.h> ++#include <linux/ktime.h> + #include <linux/module.h> + #include <linux/mutex.h> + #include <linux/seq_file.h> +@@ -49,6 +51,8 @@ static const char *const aqc_device_names[] = { + + #define CTRL_REPORT_ID 0x03 + ++#define CTRL_REPORT_DELAY 200 /* ms */ ++ + /* The HID report that the official software always sends + * after writing values, currently same for all devices + */ +@@ -269,6 +273,9 @@ struct aqc_data { + enum kinds kind; + const char *name; + ++ ktime_t last_ctrl_report_op; ++ int ctrl_report_delay; /* Delay between two ctrl report operations, in ms */ ++ + int buffer_size; + u8 *buffer; + int checksum_start; +@@ -325,17 +332,35 @@ static int aqc_pwm_to_percent(long val) + return DIV_ROUND_CLOSEST(val * 100 * 100, 255); + } + ++static void aqc_delay_ctrl_report(struct aqc_data *priv) ++{ ++ /* ++ * If previous read or write is too close to this one, delay the current operation ++ * to give the device enough time to process the previous one. ++ */ ++ if (priv->ctrl_report_delay) { ++ s64 delta = ktime_ms_delta(ktime_get(), priv->last_ctrl_report_op); ++ ++ if (delta < priv->ctrl_report_delay) ++ msleep(priv->ctrl_report_delay - delta); ++ } ++} ++ + /* Expects the mutex to be locked */ + static int aqc_get_ctrl_data(struct aqc_data *priv) + { + int ret; + ++ aqc_delay_ctrl_report(priv); ++ + memset(priv->buffer, 0x00, priv->buffer_size); + ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret < 0) + ret = -ENODATA; + ++ priv->last_ctrl_report_op = ktime_get(); ++ + return ret; + } + +@@ -345,6 +370,8 @@ static int aqc_send_ctrl_data(struct aqc_data *priv) + int ret; + u16 checksum; + ++ aqc_delay_ctrl_report(priv); ++ + /* Init and xorout value for CRC-16/USB is 0xffff */ + checksum = crc16(0xffff, priv->buffer + priv->checksum_start, priv->checksum_length); + checksum ^= 0xffff; +@@ -356,12 +383,16 @@ static int aqc_send_ctrl_data(struct aqc_data *priv) + ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret < 0) +- return ret; ++ goto record_access_and_ret; + + /* The official software sends this report after every change, so do it here as well */ + ret = hid_hw_raw_request(priv->hdev, SECONDARY_CTRL_REPORT_ID, secondary_ctrl_report, + SECONDARY_CTRL_REPORT_SIZE, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); ++ ++record_access_and_ret: ++ priv->last_ctrl_report_op = ktime_get(); ++ + return ret; + } + +@@ -853,6 +884,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) + priv->virtual_temp_sensor_start_offset = D5NEXT_VIRTUAL_SENSORS_START; + priv->power_cycle_count_offset = D5NEXT_POWER_CYCLES; + priv->buffer_size = D5NEXT_CTRL_REPORT_SIZE; ++ priv->ctrl_report_delay = CTRL_REPORT_DELAY; + + priv->temp_label = label_d5next_temp; + priv->virtual_temp_label = label_virtual_temp_sensors; +@@ -893,6 +925,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) + priv->virtual_temp_sensor_start_offset = OCTO_VIRTUAL_SENSORS_START; + priv->power_cycle_count_offset = OCTO_POWER_CYCLES; + priv->buffer_size = OCTO_CTRL_REPORT_SIZE; ++ priv->ctrl_report_delay = CTRL_REPORT_DELAY; + + priv->temp_label = label_temp_sensors; + priv->virtual_temp_label = label_virtual_temp_sensors; +@@ -913,6 +946,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) + priv->virtual_temp_sensor_start_offset = QUADRO_VIRTUAL_SENSORS_START; + priv->power_cycle_count_offset = QUADRO_POWER_CYCLES; + priv->buffer_size = QUADRO_CTRL_REPORT_SIZE; ++ priv->ctrl_report_delay = CTRL_REPORT_DELAY; + priv->flow_sensor_offset = QUADRO_FLOW_SENSOR_OFFSET; + + priv->temp_label = label_temp_sensors; +diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c +index d810a78dde51d..31e3c37662185 100644 +--- a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c ++++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c +@@ -821,6 +821,8 @@ static int vb2ops_venc_queue_setup(struct vb2_queue *vq, + return -EINVAL; + + if (*nplanes) { ++ if (*nplanes != q_data->fmt->num_planes) ++ return -EINVAL; + for (i = 0; i < *nplanes; i++) + if (sizes[i] < q_data->sizeimage[i]) + return -EINVAL; +diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c +index b9dbad3a8af82..fc5da5d7744da 100644 +--- a/drivers/net/bonding/bond_alb.c ++++ b/drivers/net/bonding/bond_alb.c +@@ -660,10 +660,10 @@ static struct slave *rlb_arp_xmit(struct sk_buff *skb, struct bonding *bond) + return NULL; + arp = (struct arp_pkt *)skb_network_header(skb); + +- /* Don't modify or load balance ARPs that do not originate locally +- * (e.g.,arrive via a bridge). ++ /* Don't modify or load balance ARPs that do not originate ++ * from the bond itself or a VLAN directly above the bond. + */ +- if (!bond_slave_has_mac_rx(bond, arp->mac_src)) ++ if (!bond_slave_has_mac_rcu(bond, arp->mac_src)) + return NULL; + + dev = ip_dev_find(dev_net(bond->dev), arp->ip_src); +diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c +index 26a472d2ea583..6d549dbdb4674 100644 +--- a/drivers/net/can/vxcan.c ++++ b/drivers/net/can/vxcan.c +@@ -192,12 +192,7 @@ static int vxcan_newlink(struct net *net, struct net_device *dev, + + nla_peer = data[VXCAN_INFO_PEER]; + ifmp = nla_data(nla_peer); +- err = rtnl_nla_parse_ifla(peer_tb, +- nla_data(nla_peer) + +- sizeof(struct ifinfomsg), +- nla_len(nla_peer) - +- sizeof(struct ifinfomsg), +- NULL); ++ err = rtnl_nla_parse_ifinfomsg(peer_tb, nla_peer, extack); + if (err < 0) + return err; + +diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c +index 51d2ef0dc835c..b988c8a40d536 100644 +--- a/drivers/net/dsa/mt7530.c ++++ b/drivers/net/dsa/mt7530.c +@@ -1005,6 +1005,10 @@ mt753x_trap_frames(struct mt7530_priv *priv) + mt7530_rmw(priv, MT753X_BPC, MT753X_BPDU_PORT_FW_MASK, + MT753X_BPDU_CPU_ONLY); + ++ /* Trap 802.1X PAE frames to the CPU port(s) */ ++ mt7530_rmw(priv, MT753X_BPC, MT753X_PAE_PORT_FW_MASK, ++ MT753X_PAE_PORT_FW(MT753X_BPDU_CPU_ONLY)); ++ + /* Trap LLDP frames with :0E MAC DA to the CPU port(s) */ + mt7530_rmw(priv, MT753X_RGAC2, MT753X_R0E_PORT_FW_MASK, + MT753X_R0E_PORT_FW(MT753X_BPDU_CPU_ONLY)); +diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h +index 9a45663d8b4ef..6202b0f8c3f34 100644 +--- a/drivers/net/dsa/mt7530.h ++++ b/drivers/net/dsa/mt7530.h +@@ -64,6 +64,8 @@ enum mt753x_id { + /* Registers for BPDU and PAE frame control*/ + #define MT753X_BPC 0x24 + #define MT753X_BPDU_PORT_FW_MASK GENMASK(2, 0) ++#define MT753X_PAE_PORT_FW_MASK GENMASK(18, 16) ++#define MT753X_PAE_PORT_FW(x) FIELD_PREP(MT753X_PAE_PORT_FW_MASK, x) + + /* Register for :03 and :0E MAC DA frame control */ + #define MT753X_RGAC2 0x2c +diff --git a/drivers/net/dsa/ocelot/felix_vsc9959.c b/drivers/net/dsa/ocelot/felix_vsc9959.c +index 5f6af0870dfd6..0186482194d20 100644 +--- a/drivers/net/dsa/ocelot/felix_vsc9959.c ++++ b/drivers/net/dsa/ocelot/felix_vsc9959.c +@@ -1071,6 +1071,9 @@ static u64 vsc9959_tas_remaining_gate_len_ps(u64 gate_len_ns) + if (gate_len_ns == U64_MAX) + return U64_MAX; + ++ if (gate_len_ns < VSC9959_TAS_MIN_GATE_LEN_NS) ++ return 0; ++ + return (gate_len_ns - VSC9959_TAS_MIN_GATE_LEN_NS) * PSEC_PER_NSEC; + } + +diff --git a/drivers/net/ethernet/broadcom/bgmac.c b/drivers/net/ethernet/broadcom/bgmac.c +index 10c7c232cc4ec..52ee3751187a2 100644 +--- a/drivers/net/ethernet/broadcom/bgmac.c ++++ b/drivers/net/ethernet/broadcom/bgmac.c +@@ -1448,7 +1448,7 @@ int bgmac_phy_connect_direct(struct bgmac *bgmac) + int err; + + phy_dev = fixed_phy_register(PHY_POLL, &fphy_status, NULL); +- if (!phy_dev || IS_ERR(phy_dev)) { ++ if (IS_ERR(phy_dev)) { + dev_err(bgmac->dev, "Failed to register fixed PHY device\n"); + return -ENODEV; + } +diff --git a/drivers/net/ethernet/broadcom/genet/bcmmii.c b/drivers/net/ethernet/broadcom/genet/bcmmii.c +index 1fe8038587ac8..1779ee524dac7 100644 +--- a/drivers/net/ethernet/broadcom/genet/bcmmii.c ++++ b/drivers/net/ethernet/broadcom/genet/bcmmii.c +@@ -608,7 +608,7 @@ static int bcmgenet_mii_pd_init(struct bcmgenet_priv *priv) + }; + + phydev = fixed_phy_register(PHY_POLL, &fphy_status, NULL); +- if (!phydev || IS_ERR(phydev)) { ++ if (IS_ERR(phydev)) { + dev_err(kdev, "failed to register fixed PHY device\n"); + return -ENODEV; + } +diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c +index c2e7037c7ba1c..7750702900fa6 100644 +--- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c ++++ b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c +@@ -1466,7 +1466,7 @@ static void make_established(struct sock *sk, u32 snd_isn, unsigned int opt) + tp->write_seq = snd_isn; + tp->snd_nxt = snd_isn; + tp->snd_una = snd_isn; +- inet_sk(sk)->inet_id = get_random_u16(); ++ atomic_set(&inet_sk(sk)->inet_id, get_random_u16()); + assign_rxopt(sk, opt); + + if (tp->rcv_wnd > (RCV_BUFSIZ_M << 10)) +diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c +index 5b96cd94dcd24..0b4ec6e41eb41 100644 +--- a/drivers/net/ethernet/ibm/ibmveth.c ++++ b/drivers/net/ethernet/ibm/ibmveth.c +@@ -203,7 +203,7 @@ static inline void ibmveth_flush_buffer(void *addr, unsigned long length) + unsigned long offset; + + for (offset = 0; offset < length; offset += SMP_CACHE_BYTES) +- asm("dcbfl %0,%1" :: "b" (addr), "r" (offset)); ++ asm("dcbf %0,%1,1" :: "b" (addr), "r" (offset)); + } + + /* replenish the buffers for a pool. note that we don't need to +diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c +index 0e01b1927c1c6..08ccf0024ce1a 100644 +--- a/drivers/net/ethernet/intel/i40e/i40e_main.c ++++ b/drivers/net/ethernet/intel/i40e/i40e_main.c +@@ -2615,7 +2615,7 @@ int i40e_sync_vsi_filters(struct i40e_vsi *vsi) + retval = i40e_correct_mac_vlan_filters + (vsi, &tmp_add_list, &tmp_del_list, + vlan_filters); +- else ++ else if (pf->vf) + retval = i40e_correct_vf_mac_vlan_filters + (vsi, &tmp_add_list, &tmp_del_list, + vlan_filters, pf->vf[vsi->vf_id].trusted); +@@ -2788,7 +2788,8 @@ int i40e_sync_vsi_filters(struct i40e_vsi *vsi) + } + + /* if the VF is not trusted do not do promisc */ +- if ((vsi->type == I40E_VSI_SRIOV) && !pf->vf[vsi->vf_id].trusted) { ++ if (vsi->type == I40E_VSI_SRIOV && pf->vf && ++ !pf->vf[vsi->vf_id].trusted) { + clear_bit(__I40E_VSI_OVERFLOW_PROMISC, vsi->state); + goto out; + } +diff --git a/drivers/net/ethernet/intel/ice/ice_base.c b/drivers/net/ethernet/intel/ice/ice_base.c +index e864634d66bc6..818eca6aa4a41 100644 +--- a/drivers/net/ethernet/intel/ice/ice_base.c ++++ b/drivers/net/ethernet/intel/ice/ice_base.c +@@ -396,7 +396,8 @@ static int ice_setup_rx_ctx(struct ice_rx_ring *ring) + /* Receive Packet Data Buffer Size. + * The Packet Data Buffer Size is defined in 128 byte units. + */ +- rlan_ctx.dbuf = ring->rx_buf_len >> ICE_RLAN_CTX_DBUF_S; ++ rlan_ctx.dbuf = DIV_ROUND_UP(ring->rx_buf_len, ++ BIT_ULL(ICE_RLAN_CTX_DBUF_S)); + + /* use 32 byte descriptors */ + rlan_ctx.dsize = 1; +diff --git a/drivers/net/ethernet/intel/ice/ice_sriov.c b/drivers/net/ethernet/intel/ice/ice_sriov.c +index b8c31bf721ad1..b719e9a771e36 100644 +--- a/drivers/net/ethernet/intel/ice/ice_sriov.c ++++ b/drivers/net/ethernet/intel/ice/ice_sriov.c +@@ -1240,7 +1240,7 @@ int ice_set_vf_spoofchk(struct net_device *netdev, int vf_id, bool ena) + if (!vf) + return -EINVAL; + +- ret = ice_check_vf_ready_for_reset(vf); ++ ret = ice_check_vf_ready_for_cfg(vf); + if (ret) + goto out_put_vf; + +@@ -1355,7 +1355,7 @@ int ice_set_vf_mac(struct net_device *netdev, int vf_id, u8 *mac) + goto out_put_vf; + } + +- ret = ice_check_vf_ready_for_reset(vf); ++ ret = ice_check_vf_ready_for_cfg(vf); + if (ret) + goto out_put_vf; + +@@ -1409,7 +1409,7 @@ int ice_set_vf_trust(struct net_device *netdev, int vf_id, bool trusted) + return -EOPNOTSUPP; + } + +- ret = ice_check_vf_ready_for_reset(vf); ++ ret = ice_check_vf_ready_for_cfg(vf); + if (ret) + goto out_put_vf; + +@@ -1722,7 +1722,7 @@ ice_set_vf_port_vlan(struct net_device *netdev, int vf_id, u16 vlan_id, u8 qos, + if (!vf) + return -EINVAL; + +- ret = ice_check_vf_ready_for_reset(vf); ++ ret = ice_check_vf_ready_for_cfg(vf); + if (ret) + goto out_put_vf; + +diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.c b/drivers/net/ethernet/intel/ice/ice_vf_lib.c +index 71047fc341392..9dbe6e9bb1f79 100644 +--- a/drivers/net/ethernet/intel/ice/ice_vf_lib.c ++++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.c +@@ -185,25 +185,6 @@ int ice_check_vf_ready_for_cfg(struct ice_vf *vf) + return 0; + } + +-/** +- * ice_check_vf_ready_for_reset - check if VF is ready to be reset +- * @vf: VF to check if it's ready to be reset +- * +- * The purpose of this function is to ensure that the VF is not in reset, +- * disabled, and is both initialized and active, thus enabling us to safely +- * initialize another reset. +- */ +-int ice_check_vf_ready_for_reset(struct ice_vf *vf) +-{ +- int ret; +- +- ret = ice_check_vf_ready_for_cfg(vf); +- if (!ret && !test_bit(ICE_VF_STATE_ACTIVE, vf->vf_states)) +- ret = -EAGAIN; +- +- return ret; +-} +- + /** + * ice_trigger_vf_reset - Reset a VF on HW + * @vf: pointer to the VF structure +@@ -588,11 +569,17 @@ int ice_reset_vf(struct ice_vf *vf, u32 flags) + return 0; + } + ++ if (flags & ICE_VF_RESET_LOCK) ++ mutex_lock(&vf->cfg_lock); ++ else ++ lockdep_assert_held(&vf->cfg_lock); ++ + if (ice_is_vf_disabled(vf)) { + vsi = ice_get_vf_vsi(vf); + if (!vsi) { + dev_dbg(dev, "VF is already removed\n"); +- return -EINVAL; ++ err = -EINVAL; ++ goto out_unlock; + } + ice_vsi_stop_lan_tx_rings(vsi, ICE_NO_RESET, vf->vf_id); + +@@ -601,14 +588,9 @@ int ice_reset_vf(struct ice_vf *vf, u32 flags) + + dev_dbg(dev, "VF is already disabled, there is no need for resetting it, telling VM, all is fine %d\n", + vf->vf_id); +- return 0; ++ goto out_unlock; + } + +- if (flags & ICE_VF_RESET_LOCK) +- mutex_lock(&vf->cfg_lock); +- else +- lockdep_assert_held(&vf->cfg_lock); +- + /* Set VF disable bit state here, before triggering reset */ + set_bit(ICE_VF_STATE_DIS, vf->vf_states); + ice_trigger_vf_reset(vf, flags & ICE_VF_RESET_VFLR, false); +diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.h b/drivers/net/ethernet/intel/ice/ice_vf_lib.h +index e5bed85724622..9f7fcd8e5714b 100644 +--- a/drivers/net/ethernet/intel/ice/ice_vf_lib.h ++++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.h +@@ -214,7 +214,6 @@ u16 ice_get_num_vfs(struct ice_pf *pf); + struct ice_vsi *ice_get_vf_vsi(struct ice_vf *vf); + bool ice_is_vf_disabled(struct ice_vf *vf); + int ice_check_vf_ready_for_cfg(struct ice_vf *vf); +-int ice_check_vf_ready_for_reset(struct ice_vf *vf); + void ice_set_vf_state_dis(struct ice_vf *vf); + bool ice_is_any_vf_in_unicast_promisc(struct ice_pf *pf); + void +diff --git a/drivers/net/ethernet/intel/ice/ice_virtchnl.c b/drivers/net/ethernet/intel/ice/ice_virtchnl.c +index ef3c709d6a750..2b4c791b6cbad 100644 +--- a/drivers/net/ethernet/intel/ice/ice_virtchnl.c ++++ b/drivers/net/ethernet/intel/ice/ice_virtchnl.c +@@ -3722,7 +3722,6 @@ error_handler: + ice_vc_notify_vf_link_state(vf); + break; + case VIRTCHNL_OP_RESET_VF: +- clear_bit(ICE_VF_STATE_ACTIVE, vf->vf_states); + ops->reset_vf(vf); + break; + case VIRTCHNL_OP_ADD_ETH_ADDR: +diff --git a/drivers/net/ethernet/intel/igb/igb_ptp.c b/drivers/net/ethernet/intel/igb/igb_ptp.c +index 15e57460e19ea..07171e574e7d7 100644 +--- a/drivers/net/ethernet/intel/igb/igb_ptp.c ++++ b/drivers/net/ethernet/intel/igb/igb_ptp.c +@@ -1404,18 +1404,6 @@ void igb_ptp_init(struct igb_adapter *adapter) + return; + } + +- spin_lock_init(&adapter->tmreg_lock); +- INIT_WORK(&adapter->ptp_tx_work, igb_ptp_tx_work); +- +- if (adapter->ptp_flags & IGB_PTP_OVERFLOW_CHECK) +- INIT_DELAYED_WORK(&adapter->ptp_overflow_work, +- igb_ptp_overflow_check); +- +- adapter->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE; +- adapter->tstamp_config.tx_type = HWTSTAMP_TX_OFF; +- +- igb_ptp_reset(adapter); +- + adapter->ptp_clock = ptp_clock_register(&adapter->ptp_caps, + &adapter->pdev->dev); + if (IS_ERR(adapter->ptp_clock)) { +@@ -1425,6 +1413,18 @@ void igb_ptp_init(struct igb_adapter *adapter) + dev_info(&adapter->pdev->dev, "added PHC on %s\n", + adapter->netdev->name); + adapter->ptp_flags |= IGB_PTP_ENABLED; ++ ++ spin_lock_init(&adapter->tmreg_lock); ++ INIT_WORK(&adapter->ptp_tx_work, igb_ptp_tx_work); ++ ++ if (adapter->ptp_flags & IGB_PTP_OVERFLOW_CHECK) ++ INIT_DELAYED_WORK(&adapter->ptp_overflow_work, ++ igb_ptp_overflow_check); ++ ++ adapter->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE; ++ adapter->tstamp_config.tx_type = HWTSTAMP_TX_OFF; ++ ++ igb_ptp_reset(adapter); + } + } + +diff --git a/drivers/net/ethernet/intel/igc/igc_defines.h b/drivers/net/ethernet/intel/igc/igc_defines.h +index dbfa4b9dee066..90ca01889cd82 100644 +--- a/drivers/net/ethernet/intel/igc/igc_defines.h ++++ b/drivers/net/ethernet/intel/igc/igc_defines.h +@@ -536,7 +536,7 @@ + #define IGC_PTM_CTRL_START_NOW BIT(29) /* Start PTM Now */ + #define IGC_PTM_CTRL_EN BIT(30) /* Enable PTM */ + #define IGC_PTM_CTRL_TRIG BIT(31) /* PTM Cycle trigger */ +-#define IGC_PTM_CTRL_SHRT_CYC(usec) (((usec) & 0x2f) << 2) ++#define IGC_PTM_CTRL_SHRT_CYC(usec) (((usec) & 0x3f) << 2) + #define IGC_PTM_CTRL_PTM_TO(usec) (((usec) & 0xff) << 8) + + #define IGC_PTM_SHORT_CYC_DEFAULT 10 /* Default Short/interrupted cycle interval */ +diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +index 705325431dec3..5541e284cd3f0 100644 +--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c ++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +@@ -4005,9 +4005,10 @@ rx_frscfg: + if (link < 0) + return NIX_AF_ERR_RX_LINK_INVALID; + +- nix_find_link_frs(rvu, req, pcifunc); + + linkcfg: ++ nix_find_link_frs(rvu, req, pcifunc); ++ + cfg = rvu_read64(rvu, blkaddr, NIX_AF_RX_LINKX_CFG(link)); + cfg = (cfg & ~(0xFFFFULL << 16)) | ((u64)req->maxlen << 16); + if (req->update_minlen) +diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_keys.c b/drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_keys.c +index bd1a51a0a5408..f208a237d0b52 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_keys.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_keys.c +@@ -32,8 +32,8 @@ static const struct mlxsw_afk_element_info mlxsw_afk_element_infos[] = { + MLXSW_AFK_ELEMENT_INFO_U32(IP_TTL_, 0x18, 0, 8), + MLXSW_AFK_ELEMENT_INFO_U32(IP_ECN, 0x18, 9, 2), + MLXSW_AFK_ELEMENT_INFO_U32(IP_DSCP, 0x18, 11, 6), +- MLXSW_AFK_ELEMENT_INFO_U32(VIRT_ROUTER_MSB, 0x18, 17, 3), +- MLXSW_AFK_ELEMENT_INFO_U32(VIRT_ROUTER_LSB, 0x18, 20, 8), ++ MLXSW_AFK_ELEMENT_INFO_U32(VIRT_ROUTER_MSB, 0x18, 17, 4), ++ MLXSW_AFK_ELEMENT_INFO_U32(VIRT_ROUTER_LSB, 0x18, 21, 8), + MLXSW_AFK_ELEMENT_INFO_BUF(SRC_IP_96_127, 0x20, 4), + MLXSW_AFK_ELEMENT_INFO_BUF(SRC_IP_64_95, 0x24, 4), + MLXSW_AFK_ELEMENT_INFO_BUF(SRC_IP_32_63, 0x28, 4), +diff --git a/drivers/net/ethernet/mellanox/mlxsw/pci.c b/drivers/net/ethernet/mellanox/mlxsw/pci.c +index c968309657dd1..51eea1f0529c8 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/pci.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/pci.c +@@ -517,11 +517,15 @@ static void mlxsw_pci_skb_cb_ts_set(struct mlxsw_pci *mlxsw_pci, + struct sk_buff *skb, + enum mlxsw_pci_cqe_v cqe_v, char *cqe) + { ++ u8 ts_type; ++ + if (cqe_v != MLXSW_PCI_CQE_V2) + return; + +- if (mlxsw_pci_cqe2_time_stamp_type_get(cqe) != +- MLXSW_PCI_CQE_TIME_STAMP_TYPE_UTC) ++ ts_type = mlxsw_pci_cqe2_time_stamp_type_get(cqe); ++ ++ if (ts_type != MLXSW_PCI_CQE_TIME_STAMP_TYPE_UTC && ++ ts_type != MLXSW_PCI_CQE_TIME_STAMP_TYPE_MIRROR_UTC) + return; + + mlxsw_skb_cb(skb)->cqe_ts.sec = mlxsw_pci_cqe2_time_stamp_sec_get(cqe); +diff --git a/drivers/net/ethernet/mellanox/mlxsw/reg.h b/drivers/net/ethernet/mellanox/mlxsw/reg.h +index 0777bed5bb1af..a34ff19c58bd2 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/reg.h ++++ b/drivers/net/ethernet/mellanox/mlxsw/reg.h +@@ -97,14 +97,6 @@ MLXSW_ITEM32(reg, sspr, m, 0x00, 31, 1); + */ + MLXSW_ITEM32_LP(reg, sspr, 0x00, 16, 0x00, 12); + +-/* reg_sspr_sub_port +- * Virtual port within the physical port. +- * Should be set to 0 when virtual ports are not enabled on the port. +- * +- * Access: RW +- */ +-MLXSW_ITEM32(reg, sspr, sub_port, 0x00, 8, 8); +- + /* reg_sspr_system_port + * Unique identifier within the stacking domain that represents all the ports + * that are available in the system (external ports). +@@ -120,7 +112,6 @@ static inline void mlxsw_reg_sspr_pack(char *payload, u16 local_port) + MLXSW_REG_ZERO(sspr, payload); + mlxsw_reg_sspr_m_set(payload, 1); + mlxsw_reg_sspr_local_port_set(payload, local_port); +- mlxsw_reg_sspr_sub_port_set(payload, 0); + mlxsw_reg_sspr_system_port_set(payload, local_port); + } + +diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum2_mr_tcam.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum2_mr_tcam.c +index e4f4cded2b6f9..b1178b7a7f51a 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum2_mr_tcam.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum2_mr_tcam.c +@@ -193,7 +193,7 @@ mlxsw_sp2_mr_tcam_rule_parse(struct mlxsw_sp_acl_rule *rule, + key->vrid, GENMASK(7, 0)); + mlxsw_sp_acl_rulei_keymask_u32(rulei, + MLXSW_AFK_ELEMENT_VIRT_ROUTER_MSB, +- key->vrid >> 8, GENMASK(2, 0)); ++ key->vrid >> 8, GENMASK(3, 0)); + switch (key->proto) { + case MLXSW_SP_L3_PROTO_IPV4: + return mlxsw_sp2_mr_tcam_rule_parse4(rulei, key); +diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_flex_keys.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_flex_keys.c +index 00c32320f8915..173808c096bab 100644 +--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_flex_keys.c ++++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_flex_keys.c +@@ -169,7 +169,7 @@ static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv4_2[] = { + + static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv4_4[] = { + MLXSW_AFK_ELEMENT_INST_U32(VIRT_ROUTER_LSB, 0x04, 24, 8), +- MLXSW_AFK_ELEMENT_INST_U32(VIRT_ROUTER_MSB, 0x00, 0, 3), ++ MLXSW_AFK_ELEMENT_INST_EXT_U32(VIRT_ROUTER_MSB, 0x00, 0, 3, 0, true), + }; + + static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv6_0[] = { +@@ -319,7 +319,7 @@ static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_mac_5b[] = { + + static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv4_4b[] = { + MLXSW_AFK_ELEMENT_INST_U32(VIRT_ROUTER_LSB, 0x04, 13, 8), +- MLXSW_AFK_ELEMENT_INST_EXT_U32(VIRT_ROUTER_MSB, 0x04, 21, 4, 0, true), ++ MLXSW_AFK_ELEMENT_INST_U32(VIRT_ROUTER_MSB, 0x04, 21, 4), + }; + + static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv6_2b[] = { +diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c +index 796a38f9d7b24..cd16bc8bf154c 100644 +--- a/drivers/net/ipvlan/ipvlan_main.c ++++ b/drivers/net/ipvlan/ipvlan_main.c +@@ -748,7 +748,8 @@ static int ipvlan_device_event(struct notifier_block *unused, + + write_pnet(&port->pnet, newnet); + +- ipvlan_migrate_l3s_hook(oldnet, newnet); ++ if (port->mode == IPVLAN_MODE_L3S) ++ ipvlan_migrate_l3s_hook(oldnet, newnet); + break; + } + case NETDEV_UNREGISTER: +diff --git a/drivers/net/veth.c b/drivers/net/veth.c +index a71786b3e7ba7..727b9278b9fe5 100644 +--- a/drivers/net/veth.c ++++ b/drivers/net/veth.c +@@ -1716,10 +1716,7 @@ static int veth_newlink(struct net *src_net, struct net_device *dev, + + nla_peer = data[VETH_INFO_PEER]; + ifmp = nla_data(nla_peer); +- err = rtnl_nla_parse_ifla(peer_tb, +- nla_data(nla_peer) + sizeof(struct ifinfomsg), +- nla_len(nla_peer) - sizeof(struct ifinfomsg), +- NULL); ++ err = rtnl_nla_parse_ifinfomsg(peer_tb, nla_peer, extack); + if (err < 0) + return err; + +diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c +index cd3821a6444f0..4e436f2d13aeb 100644 +--- a/drivers/of/dynamic.c ++++ b/drivers/of/dynamic.c +@@ -63,15 +63,14 @@ int of_reconfig_notifier_unregister(struct notifier_block *nb) + } + EXPORT_SYMBOL_GPL(of_reconfig_notifier_unregister); + +-#ifdef DEBUG +-const char *action_names[] = { ++static const char *action_names[] = { ++ [0] = "INVALID", + [OF_RECONFIG_ATTACH_NODE] = "ATTACH_NODE", + [OF_RECONFIG_DETACH_NODE] = "DETACH_NODE", + [OF_RECONFIG_ADD_PROPERTY] = "ADD_PROPERTY", + [OF_RECONFIG_REMOVE_PROPERTY] = "REMOVE_PROPERTY", + [OF_RECONFIG_UPDATE_PROPERTY] = "UPDATE_PROPERTY", + }; +-#endif + + int of_reconfig_notify(unsigned long action, struct of_reconfig_data *p) + { +@@ -594,21 +593,9 @@ static int __of_changeset_entry_apply(struct of_changeset_entry *ce) + } + + ret = __of_add_property(ce->np, ce->prop); +- if (ret) { +- pr_err("changeset: add_property failed @%pOF/%s\n", +- ce->np, +- ce->prop->name); +- break; +- } + break; + case OF_RECONFIG_REMOVE_PROPERTY: + ret = __of_remove_property(ce->np, ce->prop); +- if (ret) { +- pr_err("changeset: remove_property failed @%pOF/%s\n", +- ce->np, +- ce->prop->name); +- break; +- } + break; + + case OF_RECONFIG_UPDATE_PROPERTY: +@@ -622,20 +609,17 @@ static int __of_changeset_entry_apply(struct of_changeset_entry *ce) + } + + ret = __of_update_property(ce->np, ce->prop, &old_prop); +- if (ret) { +- pr_err("changeset: update_property failed @%pOF/%s\n", +- ce->np, +- ce->prop->name); +- break; +- } + break; + default: + ret = -EINVAL; + } + raw_spin_unlock_irqrestore(&devtree_lock, flags); + +- if (ret) ++ if (ret) { ++ pr_err("changeset: apply failed: %-15s %pOF:%s\n", ++ action_names[ce->action], ce->np, ce->prop->name); + return ret; ++ } + + switch (ce->action) { + case OF_RECONFIG_ATTACH_NODE: +@@ -921,6 +905,9 @@ int of_changeset_action(struct of_changeset *ocs, unsigned long action, + if (!ce) + return -ENOMEM; + ++ if (WARN_ON(action >= ARRAY_SIZE(action_names))) ++ return -EINVAL; ++ + /* get a reference to the node */ + ce->action = action; + ce->np = of_node_get(np); +diff --git a/drivers/of/kexec.c b/drivers/of/kexec.c +index f26d2ba8a3715..68278340cecfe 100644 +--- a/drivers/of/kexec.c ++++ b/drivers/of/kexec.c +@@ -184,7 +184,8 @@ int __init ima_free_kexec_buffer(void) + if (ret) + return ret; + +- return memblock_phys_free(addr, size); ++ memblock_free_late(addr, size); ++ return 0; + } + #endif + +diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c +index b89ab5d9fea55..9be6ed47a1ce4 100644 +--- a/drivers/of/unittest.c ++++ b/drivers/of/unittest.c +@@ -657,12 +657,12 @@ static void __init of_unittest_parse_phandle_with_args_map(void) + memset(&args, 0, sizeof(args)); + + EXPECT_BEGIN(KERN_INFO, +- "OF: /testcase-data/phandle-tests/consumer-b: could not find phandle"); ++ "OF: /testcase-data/phandle-tests/consumer-b: could not find phandle 12345678"); + + rc = of_parse_phandle_with_args_map(np, "phandle-list-bad-phandle", + "phandle", 0, &args); + EXPECT_END(KERN_INFO, +- "OF: /testcase-data/phandle-tests/consumer-b: could not find phandle"); ++ "OF: /testcase-data/phandle-tests/consumer-b: could not find phandle 12345678"); + + unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc); + +diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c +index 6efa3d8db9a56..ea0195337bab9 100644 +--- a/drivers/pci/hotplug/acpiphp_glue.c ++++ b/drivers/pci/hotplug/acpiphp_glue.c +@@ -504,12 +504,15 @@ static void enable_slot(struct acpiphp_slot *slot, bool bridge) + if (pass && dev->subordinate) { + check_hotplug_bridge(slot, dev); + pcibios_resource_survey_bus(dev->subordinate); +- __pci_bus_size_bridges(dev->subordinate, +- &add_list); ++ if (pci_is_root_bus(bus)) ++ __pci_bus_size_bridges(dev->subordinate, &add_list); + } + } + } +- __pci_bus_assign_resources(bus, &add_list, NULL); ++ if (pci_is_root_bus(bus)) ++ __pci_bus_assign_resources(bus, &add_list, NULL); ++ else ++ pci_assign_unassigned_bridge_resources(bus->self); + } + + acpiphp_sanitize_bus(bus); +diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c +index a8df77e80549c..be6838c252f09 100644 +--- a/drivers/pinctrl/pinctrl-amd.c ++++ b/drivers/pinctrl/pinctrl-amd.c +@@ -862,6 +862,33 @@ static const struct pinconf_ops amd_pinconf_ops = { + .pin_config_group_set = amd_pinconf_group_set, + }; + ++static void amd_gpio_irq_init(struct amd_gpio *gpio_dev) ++{ ++ struct pinctrl_desc *desc = gpio_dev->pctrl->desc; ++ unsigned long flags; ++ u32 pin_reg, mask; ++ int i; ++ ++ mask = BIT(WAKE_CNTRL_OFF_S0I3) | BIT(WAKE_CNTRL_OFF_S3) | ++ BIT(WAKE_CNTRL_OFF_S4); ++ ++ for (i = 0; i < desc->npins; i++) { ++ int pin = desc->pins[i].number; ++ const struct pin_desc *pd = pin_desc_get(gpio_dev->pctrl, pin); ++ ++ if (!pd) ++ continue; ++ ++ raw_spin_lock_irqsave(&gpio_dev->lock, flags); ++ ++ pin_reg = readl(gpio_dev->base + pin * 4); ++ pin_reg &= ~mask; ++ writel(pin_reg, gpio_dev->base + pin * 4); ++ ++ raw_spin_unlock_irqrestore(&gpio_dev->lock, flags); ++ } ++} ++ + #ifdef CONFIG_PM_SLEEP + static bool amd_gpio_should_save(struct amd_gpio *gpio_dev, unsigned int pin) + { +@@ -1099,6 +1126,9 @@ static int amd_gpio_probe(struct platform_device *pdev) + return PTR_ERR(gpio_dev->pctrl); + } + ++ /* Disable and mask interrupts */ ++ amd_gpio_irq_init(gpio_dev); ++ + girq = &gpio_dev->gc.irq; + gpio_irq_chip_set_chip(girq, &amd_gpio_irqchip); + /* This will let us handle the parent IRQ in the driver */ +diff --git a/drivers/pinctrl/renesas/pinctrl-rza2.c b/drivers/pinctrl/renesas/pinctrl-rza2.c +index c0a04f1ee994e..12126e30dc20f 100644 +--- a/drivers/pinctrl/renesas/pinctrl-rza2.c ++++ b/drivers/pinctrl/renesas/pinctrl-rza2.c +@@ -14,6 +14,7 @@ + #include <linux/gpio/driver.h> + #include <linux/io.h> + #include <linux/module.h> ++#include <linux/mutex.h> + #include <linux/of_device.h> + #include <linux/pinctrl/pinmux.h> + +@@ -46,6 +47,7 @@ struct rza2_pinctrl_priv { + struct pinctrl_dev *pctl; + struct pinctrl_gpio_range gpio_range; + int npins; ++ struct mutex mutex; /* serialize adding groups and functions */ + }; + + #define RZA2_PDR(port) (0x0000 + (port) * 2) /* Direction 16-bit */ +@@ -358,10 +360,14 @@ static int rza2_dt_node_to_map(struct pinctrl_dev *pctldev, + psel_val[i] = MUX_FUNC(value); + } + ++ mutex_lock(&priv->mutex); ++ + /* Register a single pin group listing all the pins we read from DT */ + gsel = pinctrl_generic_add_group(pctldev, np->name, pins, npins, NULL); +- if (gsel < 0) +- return gsel; ++ if (gsel < 0) { ++ ret = gsel; ++ goto unlock; ++ } + + /* + * Register a single group function where the 'data' is an array PSEL +@@ -390,6 +396,8 @@ static int rza2_dt_node_to_map(struct pinctrl_dev *pctldev, + (*map)->data.mux.function = np->name; + *num_maps = 1; + ++ mutex_unlock(&priv->mutex); ++ + return 0; + + remove_function: +@@ -398,6 +406,9 @@ remove_function: + remove_group: + pinctrl_generic_remove_group(pctldev, gsel); + ++unlock: ++ mutex_unlock(&priv->mutex); ++ + dev_err(priv->dev, "Unable to parse DT node %s\n", np->name); + + return ret; +@@ -473,6 +484,8 @@ static int rza2_pinctrl_probe(struct platform_device *pdev) + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + ++ mutex_init(&priv->mutex); ++ + platform_set_drvdata(pdev, priv); + + priv->npins = (int)(uintptr_t)of_device_get_match_data(&pdev->dev) * +diff --git a/drivers/pinctrl/renesas/pinctrl-rzg2l.c b/drivers/pinctrl/renesas/pinctrl-rzg2l.c +index fd11d28e5a1e4..2a617832a7e60 100644 +--- a/drivers/pinctrl/renesas/pinctrl-rzg2l.c ++++ b/drivers/pinctrl/renesas/pinctrl-rzg2l.c +@@ -11,6 +11,7 @@ + #include <linux/io.h> + #include <linux/interrupt.h> + #include <linux/module.h> ++#include <linux/mutex.h> + #include <linux/of_device.h> + #include <linux/of_irq.h> + #include <linux/pinctrl/pinconf-generic.h> +@@ -146,10 +147,11 @@ struct rzg2l_pinctrl { + struct gpio_chip gpio_chip; + struct pinctrl_gpio_range gpio_range; + DECLARE_BITMAP(tint_slot, RZG2L_TINT_MAX_INTERRUPT); +- spinlock_t bitmap_lock; ++ spinlock_t bitmap_lock; /* protect tint_slot bitmap */ + unsigned int hwirq[RZG2L_TINT_MAX_INTERRUPT]; + +- spinlock_t lock; ++ spinlock_t lock; /* lock read/write registers */ ++ struct mutex mutex; /* serialize adding groups and functions */ + }; + + static const unsigned int iolh_groupa_mA[] = { 2, 4, 8, 12 }; +@@ -359,11 +361,13 @@ static int rzg2l_dt_subnode_to_map(struct pinctrl_dev *pctldev, + name = np->name; + } + ++ mutex_lock(&pctrl->mutex); ++ + /* Register a single pin group listing all the pins we read from DT */ + gsel = pinctrl_generic_add_group(pctldev, name, pins, num_pinmux, NULL); + if (gsel < 0) { + ret = gsel; +- goto done; ++ goto unlock; + } + + /* +@@ -377,6 +381,8 @@ static int rzg2l_dt_subnode_to_map(struct pinctrl_dev *pctldev, + goto remove_group; + } + ++ mutex_unlock(&pctrl->mutex); ++ + maps[idx].type = PIN_MAP_TYPE_MUX_GROUP; + maps[idx].data.mux.group = name; + maps[idx].data.mux.function = name; +@@ -388,6 +394,8 @@ static int rzg2l_dt_subnode_to_map(struct pinctrl_dev *pctldev, + + remove_group: + pinctrl_generic_remove_group(pctldev, gsel); ++unlock: ++ mutex_unlock(&pctrl->mutex); + done: + *index = idx; + kfree(configs); +@@ -1501,6 +1509,7 @@ static int rzg2l_pinctrl_probe(struct platform_device *pdev) + + spin_lock_init(&pctrl->lock); + spin_lock_init(&pctrl->bitmap_lock); ++ mutex_init(&pctrl->mutex); + + platform_set_drvdata(pdev, pctrl); + +diff --git a/drivers/pinctrl/renesas/pinctrl-rzv2m.c b/drivers/pinctrl/renesas/pinctrl-rzv2m.c +index 35f382b055e83..2858800288bb7 100644 +--- a/drivers/pinctrl/renesas/pinctrl-rzv2m.c ++++ b/drivers/pinctrl/renesas/pinctrl-rzv2m.c +@@ -14,6 +14,7 @@ + #include <linux/gpio/driver.h> + #include <linux/io.h> + #include <linux/module.h> ++#include <linux/mutex.h> + #include <linux/of_device.h> + #include <linux/pinctrl/pinconf-generic.h> + #include <linux/pinctrl/pinconf.h> +@@ -121,7 +122,8 @@ struct rzv2m_pinctrl { + struct gpio_chip gpio_chip; + struct pinctrl_gpio_range gpio_range; + +- spinlock_t lock; ++ spinlock_t lock; /* lock read/write registers */ ++ struct mutex mutex; /* serialize adding groups and functions */ + }; + + static const unsigned int drv_1_8V_group2_uA[] = { 1800, 3800, 7800, 11000 }; +@@ -320,11 +322,13 @@ static int rzv2m_dt_subnode_to_map(struct pinctrl_dev *pctldev, + name = np->name; + } + ++ mutex_lock(&pctrl->mutex); ++ + /* Register a single pin group listing all the pins we read from DT */ + gsel = pinctrl_generic_add_group(pctldev, name, pins, num_pinmux, NULL); + if (gsel < 0) { + ret = gsel; +- goto done; ++ goto unlock; + } + + /* +@@ -338,6 +342,8 @@ static int rzv2m_dt_subnode_to_map(struct pinctrl_dev *pctldev, + goto remove_group; + } + ++ mutex_unlock(&pctrl->mutex); ++ + maps[idx].type = PIN_MAP_TYPE_MUX_GROUP; + maps[idx].data.mux.group = name; + maps[idx].data.mux.function = name; +@@ -349,6 +355,8 @@ static int rzv2m_dt_subnode_to_map(struct pinctrl_dev *pctldev, + + remove_group: + pinctrl_generic_remove_group(pctldev, gsel); ++unlock: ++ mutex_unlock(&pctrl->mutex); + done: + *index = idx; + kfree(configs); +@@ -1070,6 +1078,7 @@ static int rzv2m_pinctrl_probe(struct platform_device *pdev) + } + + spin_lock_init(&pctrl->lock); ++ mutex_init(&pctrl->mutex); + + platform_set_drvdata(pdev, pctrl); + +diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c +index bd38c7dcae347..de03b8889e9d3 100644 +--- a/drivers/platform/x86/ideapad-laptop.c ++++ b/drivers/platform/x86/ideapad-laptop.c +@@ -1176,6 +1176,11 @@ static const struct key_entry ideapad_keymap[] = { + { KE_IGNORE, 0x03 | IDEAPAD_WMI_KEY }, + /* Customizable Lenovo Hotkey ("star" with 'S' inside) */ + { KE_KEY, 0x01 | IDEAPAD_WMI_KEY, { KEY_FAVORITES } }, ++ { KE_KEY, 0x04 | IDEAPAD_WMI_KEY, { KEY_SELECTIVE_SCREENSHOT } }, ++ /* Lenovo Support */ ++ { KE_KEY, 0x07 | IDEAPAD_WMI_KEY, { KEY_HELP } }, ++ { KE_KEY, 0x0e | IDEAPAD_WMI_KEY, { KEY_PICKUP_PHONE } }, ++ { KE_KEY, 0x0f | IDEAPAD_WMI_KEY, { KEY_HANGUP_PHONE } }, + /* Dark mode toggle */ + { KE_KEY, 0x13 | IDEAPAD_WMI_KEY, { KEY_PROG1 } }, + /* Sound profile switch */ +diff --git a/drivers/s390/crypto/zcrypt_msgtype6.c b/drivers/s390/crypto/zcrypt_msgtype6.c +index f99a9ef42116f..84e3ad290f6ba 100644 +--- a/drivers/s390/crypto/zcrypt_msgtype6.c ++++ b/drivers/s390/crypto/zcrypt_msgtype6.c +@@ -926,8 +926,7 @@ static void zcrypt_msgtype6_receive(struct ap_queue *aq, + .type = TYPE82_RSP_CODE, + .reply_code = REP82_ERROR_MACHINE_FAILURE, + }; +- struct response_type *resp_type = +- (struct response_type *)msg->private; ++ struct response_type *resp_type = msg->private; + struct type86x_reply *t86r; + int len; + +@@ -982,8 +981,7 @@ static void zcrypt_msgtype6_receive_ep11(struct ap_queue *aq, + .type = TYPE82_RSP_CODE, + .reply_code = REP82_ERROR_MACHINE_FAILURE, + }; +- struct response_type *resp_type = +- (struct response_type *)msg->private; ++ struct response_type *resp_type = msg->private; + struct type86_ep11_reply *t86r; + int len; + +@@ -1156,23 +1154,36 @@ static long zcrypt_msgtype6_send_cprb(bool userspace, struct zcrypt_queue *zq, + struct ica_xcRB *xcrb, + struct ap_message *ap_msg) + { +- int rc; +- struct response_type *rtype = (struct response_type *)(ap_msg->private); ++ struct response_type *rtype = ap_msg->private; + struct { + struct type6_hdr hdr; + struct CPRBX cprbx; + /* ... more data blocks ... */ + } __packed * msg = ap_msg->msg; +- +- /* +- * Set the queue's reply buffer length minus 128 byte padding +- * as reply limit for the card firmware. +- */ +- msg->hdr.fromcardlen1 = min_t(unsigned int, msg->hdr.fromcardlen1, +- zq->reply.bufsize - 128); +- if (msg->hdr.fromcardlen2) +- msg->hdr.fromcardlen2 = +- zq->reply.bufsize - msg->hdr.fromcardlen1 - 128; ++ unsigned int max_payload_size; ++ int rc, delta; ++ ++ /* calculate maximum payload for this card and msg type */ ++ max_payload_size = zq->reply.bufsize - sizeof(struct type86_fmt2_msg); ++ ++ /* limit each of the two from fields to the maximum payload size */ ++ msg->hdr.fromcardlen1 = min(msg->hdr.fromcardlen1, max_payload_size); ++ msg->hdr.fromcardlen2 = min(msg->hdr.fromcardlen2, max_payload_size); ++ ++ /* calculate delta if the sum of both exceeds max payload size */ ++ delta = msg->hdr.fromcardlen1 + msg->hdr.fromcardlen2 ++ - max_payload_size; ++ if (delta > 0) { ++ /* ++ * Sum exceeds maximum payload size, prune fromcardlen1 ++ * (always trust fromcardlen2) ++ */ ++ if (delta > msg->hdr.fromcardlen1) { ++ rc = -EINVAL; ++ goto out; ++ } ++ msg->hdr.fromcardlen1 -= delta; ++ } + + init_completion(&rtype->work); + rc = ap_queue_message(zq->queue, ap_msg); +@@ -1243,7 +1254,7 @@ static long zcrypt_msgtype6_send_ep11_cprb(bool userspace, struct zcrypt_queue * + { + int rc; + unsigned int lfmt; +- struct response_type *rtype = (struct response_type *)(ap_msg->private); ++ struct response_type *rtype = ap_msg->private; + struct { + struct type6_hdr hdr; + struct ep11_cprb cprbx; +@@ -1365,7 +1376,7 @@ static long zcrypt_msgtype6_rng(struct zcrypt_queue *zq, + short int verb_length; + short int key_length; + } __packed * msg = ap_msg->msg; +- struct response_type *rtype = (struct response_type *)(ap_msg->private); ++ struct response_type *rtype = ap_msg->private; + int rc; + + msg->cprbx.domain = AP_QID_QUEUE(zq->queue->qid); +diff --git a/drivers/scsi/raid_class.c b/drivers/scsi/raid_class.c +index 711252e52d8e1..95a86e0dfd77a 100644 +--- a/drivers/scsi/raid_class.c ++++ b/drivers/scsi/raid_class.c +@@ -209,54 +209,6 @@ raid_attr_ro_state(level); + raid_attr_ro_fn(resync); + raid_attr_ro_state_fn(state); + +-static void raid_component_release(struct device *dev) +-{ +- struct raid_component *rc = +- container_of(dev, struct raid_component, dev); +- dev_printk(KERN_ERR, rc->dev.parent, "COMPONENT RELEASE\n"); +- put_device(rc->dev.parent); +- kfree(rc); +-} +- +-int raid_component_add(struct raid_template *r,struct device *raid_dev, +- struct device *component_dev) +-{ +- struct device *cdev = +- attribute_container_find_class_device(&r->raid_attrs.ac, +- raid_dev); +- struct raid_component *rc; +- struct raid_data *rd = dev_get_drvdata(cdev); +- int err; +- +- rc = kzalloc(sizeof(*rc), GFP_KERNEL); +- if (!rc) +- return -ENOMEM; +- +- INIT_LIST_HEAD(&rc->node); +- device_initialize(&rc->dev); +- rc->dev.release = raid_component_release; +- rc->dev.parent = get_device(component_dev); +- rc->num = rd->component_count++; +- +- dev_set_name(&rc->dev, "component-%d", rc->num); +- list_add_tail(&rc->node, &rd->component_list); +- rc->dev.class = &raid_class.class; +- err = device_add(&rc->dev); +- if (err) +- goto err_out; +- +- return 0; +- +-err_out: +- put_device(&rc->dev); +- list_del(&rc->node); +- rd->component_count--; +- put_device(component_dev); +- kfree(rc); +- return err; +-} +-EXPORT_SYMBOL(raid_component_add); +- + struct raid_template * + raid_class_attach(struct raid_function_template *ft) + { +diff --git a/drivers/scsi/snic/snic_disc.c b/drivers/scsi/snic/snic_disc.c +index cd27562ec922e..6c529b37f3b46 100644 +--- a/drivers/scsi/snic/snic_disc.c ++++ b/drivers/scsi/snic/snic_disc.c +@@ -303,12 +303,11 @@ snic_tgt_create(struct snic *snic, struct snic_tgt_id *tgtid) + "Snic Tgt: device_add, with err = %d\n", + ret); + +- put_device(&tgt->dev); + put_device(&snic->shost->shost_gendev); + spin_lock_irqsave(snic->shost->host_lock, flags); + list_del(&tgt->list); + spin_unlock_irqrestore(snic->shost->host_lock, flags); +- kfree(tgt); ++ put_device(&tgt->dev); + tgt = NULL; + + return tgt; +diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c +index 626aca3124b1c..d9544600b3867 100644 +--- a/drivers/thunderbolt/tmu.c ++++ b/drivers/thunderbolt/tmu.c +@@ -415,7 +415,8 @@ int tb_switch_tmu_disable(struct tb_switch *sw) + * uni-directional mode and we don't want to change it's TMU + * mode. + */ +- tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); ++ ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); ++ return ret; + + tb_port_tmu_time_sync_disable(up); + ret = tb_port_tmu_time_sync_disable(down); +diff --git a/drivers/video/aperture.c b/drivers/video/aperture.c +index 5c94abdb1ad6d..3e4a1f55f51b3 100644 +--- a/drivers/video/aperture.c ++++ b/drivers/video/aperture.c +@@ -298,14 +298,6 @@ int aperture_remove_conflicting_devices(resource_size_t base, resource_size_t si + + aperture_detach_devices(base, size); + +- /* +- * If this is the primary adapter, there could be a VGA device +- * that consumes the VGA framebuffer I/O range. Remove this device +- * as well. +- */ +- if (primary) +- aperture_detach_devices(VGA_FB_PHYS_BASE, VGA_FB_PHYS_SIZE); +- + return 0; + } + EXPORT_SYMBOL(aperture_remove_conflicting_devices); +@@ -344,13 +336,22 @@ int aperture_remove_conflicting_pci_devices(struct pci_dev *pdev, const char *na + aperture_detach_devices(base, size); + } + +- /* +- * WARNING: Apparently we must kick fbdev drivers before vgacon, +- * otherwise the vga fbdev driver falls over. +- */ +- ret = vga_remove_vgacon(pdev); +- if (ret) +- return ret; ++ if (primary) { ++ /* ++ * If this is the primary adapter, there could be a VGA device ++ * that consumes the VGA framebuffer I/O range. Remove this ++ * device as well. ++ */ ++ aperture_detach_devices(VGA_FB_PHYS_BASE, VGA_FB_PHYS_SIZE); ++ ++ /* ++ * WARNING: Apparently we must kick fbdev drivers before vgacon, ++ * otherwise the vga fbdev driver falls over. ++ */ ++ ret = vga_remove_vgacon(pdev); ++ if (ret) ++ return ret; ++ } + + return 0; + +diff --git a/drivers/video/fbdev/aty/radeon_base.c b/drivers/video/fbdev/aty/radeon_base.c +index 8b28c9bddd974..50c384ce28837 100644 +--- a/drivers/video/fbdev/aty/radeon_base.c ++++ b/drivers/video/fbdev/aty/radeon_base.c +@@ -2238,14 +2238,6 @@ static const struct bin_attribute edid2_attr = { + .read = radeon_show_edid2, + }; + +-static int radeon_kick_out_firmware_fb(struct pci_dev *pdev) +-{ +- resource_size_t base = pci_resource_start(pdev, 0); +- resource_size_t size = pci_resource_len(pdev, 0); +- +- return aperture_remove_conflicting_devices(base, size, false, KBUILD_MODNAME); +-} +- + static int radeonfb_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) + { +@@ -2296,7 +2288,7 @@ static int radeonfb_pci_register(struct pci_dev *pdev, + rinfo->fb_base_phys = pci_resource_start (pdev, 0); + rinfo->mmio_base_phys = pci_resource_start (pdev, 2); + +- ret = radeon_kick_out_firmware_fb(pdev); ++ ret = aperture_remove_conflicting_pci_devices(pdev, KBUILD_MODNAME); + if (ret) + goto err_release_fb; + +diff --git a/fs/attr.c b/fs/attr.c +index b45f30e516fad..9b9a70e0cc54f 100644 +--- a/fs/attr.c ++++ b/fs/attr.c +@@ -47,6 +47,7 @@ int setattr_should_drop_sgid(struct user_namespace *mnt_userns, + return ATTR_KILL_SGID; + return 0; + } ++EXPORT_SYMBOL(setattr_should_drop_sgid); + + /** + * setattr_should_drop_suidgid - determine whether the set{g,u}id bit needs to +diff --git a/fs/internal.h b/fs/internal.h +index 46caa33373a48..42df013f7fe76 100644 +--- a/fs/internal.h ++++ b/fs/internal.h +@@ -242,5 +242,3 @@ ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *po + /* + * fs/attr.c + */ +-int setattr_should_drop_sgid(struct user_namespace *mnt_userns, +- const struct inode *inode); +diff --git a/fs/jbd2/checkpoint.c b/fs/jbd2/checkpoint.c +index c4e0da6db7195..9ec91017a7f3c 100644 +--- a/fs/jbd2/checkpoint.c ++++ b/fs/jbd2/checkpoint.c +@@ -27,7 +27,7 @@ + * + * Called with j_list_lock held. + */ +-static inline void __buffer_unlink_first(struct journal_head *jh) ++static inline void __buffer_unlink(struct journal_head *jh) + { + transaction_t *transaction = jh->b_cp_transaction; + +@@ -40,23 +40,6 @@ static inline void __buffer_unlink_first(struct journal_head *jh) + } + } + +-/* +- * Unlink a buffer from a transaction checkpoint(io) list. +- * +- * Called with j_list_lock held. +- */ +-static inline void __buffer_unlink(struct journal_head *jh) +-{ +- transaction_t *transaction = jh->b_cp_transaction; +- +- __buffer_unlink_first(jh); +- if (transaction->t_checkpoint_io_list == jh) { +- transaction->t_checkpoint_io_list = jh->b_cpnext; +- if (transaction->t_checkpoint_io_list == jh) +- transaction->t_checkpoint_io_list = NULL; +- } +-} +- + /* + * Check a checkpoint buffer could be release or not. + * +@@ -366,50 +349,10 @@ int jbd2_cleanup_journal_tail(journal_t *journal) + + /* Checkpoint list management */ + +-/* +- * journal_clean_one_cp_list +- * +- * Find all the written-back checkpoint buffers in the given list and +- * release them. If 'destroy' is set, clean all buffers unconditionally. +- * +- * Called with j_list_lock held. +- * Returns 1 if we freed the transaction, 0 otherwise. +- */ +-static int journal_clean_one_cp_list(struct journal_head *jh, bool destroy) +-{ +- struct journal_head *last_jh; +- struct journal_head *next_jh = jh; +- +- if (!jh) +- return 0; +- +- last_jh = jh->b_cpprev; +- do { +- jh = next_jh; +- next_jh = jh->b_cpnext; +- +- if (!destroy && __cp_buffer_busy(jh)) +- return 0; +- +- if (__jbd2_journal_remove_checkpoint(jh)) +- return 1; +- /* +- * This function only frees up some memory +- * if possible so we dont have an obligation +- * to finish processing. Bail out if preemption +- * requested: +- */ +- if (need_resched()) +- return 0; +- } while (jh != last_jh); +- +- return 0; +-} +- + /* + * journal_shrink_one_cp_list + * +- * Find 'nr_to_scan' written-back checkpoint buffers in the given list ++ * Find all the written-back checkpoint buffers in the given list + * and try to release them. If the whole transaction is released, set + * the 'released' parameter. Return the number of released checkpointed + * buffers. +@@ -417,15 +360,15 @@ static int journal_clean_one_cp_list(struct journal_head *jh, bool destroy) + * Called with j_list_lock held. + */ + static unsigned long journal_shrink_one_cp_list(struct journal_head *jh, +- unsigned long *nr_to_scan, +- bool *released) ++ bool destroy, bool *released) + { + struct journal_head *last_jh; + struct journal_head *next_jh = jh; + unsigned long nr_freed = 0; + int ret; + +- if (!jh || *nr_to_scan == 0) ++ *released = false; ++ if (!jh) + return 0; + + last_jh = jh->b_cpprev; +@@ -433,12 +376,15 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh, + jh = next_jh; + next_jh = jh->b_cpnext; + +- (*nr_to_scan)--; +- if (__cp_buffer_busy(jh)) +- continue; ++ if (destroy) { ++ ret = __jbd2_journal_remove_checkpoint(jh); ++ } else { ++ ret = jbd2_journal_try_remove_checkpoint(jh); ++ if (ret < 0) ++ continue; ++ } + + nr_freed++; +- ret = __jbd2_journal_remove_checkpoint(jh); + if (ret) { + *released = true; + break; +@@ -446,7 +392,7 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh, + + if (need_resched()) + break; +- } while (jh != last_jh && *nr_to_scan); ++ } while (jh != last_jh); + + return nr_freed; + } +@@ -464,11 +410,11 @@ unsigned long jbd2_journal_shrink_checkpoint_list(journal_t *journal, + unsigned long *nr_to_scan) + { + transaction_t *transaction, *last_transaction, *next_transaction; +- bool released; ++ bool __maybe_unused released; + tid_t first_tid = 0, last_tid = 0, next_tid = 0; + tid_t tid = 0; + unsigned long nr_freed = 0; +- unsigned long nr_scanned = *nr_to_scan; ++ unsigned long freed; + + again: + spin_lock(&journal->j_list_lock); +@@ -497,19 +443,11 @@ again: + transaction = next_transaction; + next_transaction = transaction->t_cpnext; + tid = transaction->t_tid; +- released = false; + +- nr_freed += journal_shrink_one_cp_list(transaction->t_checkpoint_list, +- nr_to_scan, &released); +- if (*nr_to_scan == 0) +- break; +- if (need_resched() || spin_needbreak(&journal->j_list_lock)) +- break; +- if (released) +- continue; +- +- nr_freed += journal_shrink_one_cp_list(transaction->t_checkpoint_io_list, +- nr_to_scan, &released); ++ freed = journal_shrink_one_cp_list(transaction->t_checkpoint_list, ++ false, &released); ++ nr_freed += freed; ++ (*nr_to_scan) -= min(*nr_to_scan, freed); + if (*nr_to_scan == 0) + break; + if (need_resched() || spin_needbreak(&journal->j_list_lock)) +@@ -530,9 +468,8 @@ again: + if (*nr_to_scan && next_tid) + goto again; + out: +- nr_scanned -= *nr_to_scan; + trace_jbd2_shrink_checkpoint_list(journal, first_tid, tid, last_tid, +- nr_freed, nr_scanned, next_tid); ++ nr_freed, next_tid); + + return nr_freed; + } +@@ -548,7 +485,7 @@ out: + void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy) + { + transaction_t *transaction, *last_transaction, *next_transaction; +- int ret; ++ bool released; + + transaction = journal->j_checkpoint_transactions; + if (!transaction) +@@ -559,8 +496,8 @@ void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy) + do { + transaction = next_transaction; + next_transaction = transaction->t_cpnext; +- ret = journal_clean_one_cp_list(transaction->t_checkpoint_list, +- destroy); ++ journal_shrink_one_cp_list(transaction->t_checkpoint_list, ++ destroy, &released); + /* + * This function only frees up some memory if possible so we + * dont have an obligation to finish processing. Bail out if +@@ -568,23 +505,12 @@ void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy) + */ + if (need_resched()) + return; +- if (ret) +- continue; +- /* +- * It is essential that we are as careful as in the case of +- * t_checkpoint_list with removing the buffer from the list as +- * we can possibly see not yet submitted buffers on io_list +- */ +- ret = journal_clean_one_cp_list(transaction-> +- t_checkpoint_io_list, destroy); +- if (need_resched()) +- return; + /* + * Stop scanning if we couldn't free the transaction. This + * avoids pointless scanning of transactions which still + * weren't checkpointed. + */ +- if (!ret) ++ if (!released) + return; + } while (transaction != last_transaction); + } +@@ -663,7 +589,7 @@ int __jbd2_journal_remove_checkpoint(struct journal_head *jh) + jbd2_journal_put_journal_head(jh); + + /* Is this transaction empty? */ +- if (transaction->t_checkpoint_list || transaction->t_checkpoint_io_list) ++ if (transaction->t_checkpoint_list) + return 0; + + /* +@@ -694,6 +620,34 @@ int __jbd2_journal_remove_checkpoint(struct journal_head *jh) + return 1; + } + ++/* ++ * Check the checkpoint buffer and try to remove it from the checkpoint ++ * list if it's clean. Returns -EBUSY if it is not clean, returns 1 if ++ * it frees the transaction, 0 otherwise. ++ * ++ * This function is called with j_list_lock held. ++ */ ++int jbd2_journal_try_remove_checkpoint(struct journal_head *jh) ++{ ++ struct buffer_head *bh = jh2bh(jh); ++ ++ if (!trylock_buffer(bh)) ++ return -EBUSY; ++ if (buffer_dirty(bh)) { ++ unlock_buffer(bh); ++ return -EBUSY; ++ } ++ unlock_buffer(bh); ++ ++ /* ++ * Buffer is clean and the IO has finished (we held the buffer ++ * lock) so the checkpoint is done. We can safely remove the ++ * buffer from this transaction. ++ */ ++ JBUFFER_TRACE(jh, "remove from checkpoint list"); ++ return __jbd2_journal_remove_checkpoint(jh); ++} ++ + /* + * journal_insert_checkpoint: put a committed buffer onto a checkpoint + * list so that we know when it is safe to clean the transaction out of +@@ -755,7 +709,6 @@ void __jbd2_journal_drop_transaction(journal_t *journal, transaction_t *transact + J_ASSERT(transaction->t_forget == NULL); + J_ASSERT(transaction->t_shadow_list == NULL); + J_ASSERT(transaction->t_checkpoint_list == NULL); +- J_ASSERT(transaction->t_checkpoint_io_list == NULL); + J_ASSERT(atomic_read(&transaction->t_updates) == 0); + J_ASSERT(journal->j_committing_transaction != transaction); + J_ASSERT(journal->j_running_transaction != transaction); +diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c +index 885a7a6cc53e6..f1d9db6686e31 100644 +--- a/fs/jbd2/commit.c ++++ b/fs/jbd2/commit.c +@@ -1171,8 +1171,7 @@ restart_loop: + spin_lock(&journal->j_list_lock); + commit_transaction->t_state = T_FINISHED; + /* Check if the transaction can be dropped now that we are finished */ +- if (commit_transaction->t_checkpoint_list == NULL && +- commit_transaction->t_checkpoint_io_list == NULL) { ++ if (commit_transaction->t_checkpoint_list == NULL) { + __jbd2_journal_drop_transaction(journal, commit_transaction); + jbd2_journal_free_transaction(commit_transaction); + } +diff --git a/fs/jbd2/transaction.c b/fs/jbd2/transaction.c +index 18611241f4513..6ef5022949c46 100644 +--- a/fs/jbd2/transaction.c ++++ b/fs/jbd2/transaction.c +@@ -1784,8 +1784,7 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh) + * Otherwise, if the buffer has been written to disk, + * it is safe to remove the checkpoint and drop it. + */ +- if (!buffer_dirty(bh)) { +- __jbd2_journal_remove_checkpoint(jh); ++ if (jbd2_journal_try_remove_checkpoint(jh) >= 0) { + spin_unlock(&journal->j_list_lock); + goto drop; + } +@@ -2112,20 +2111,14 @@ __journal_try_to_free_buffer(journal_t *journal, struct buffer_head *bh) + + jh = bh2jh(bh); + +- if (buffer_locked(bh) || buffer_dirty(bh)) +- goto out; +- + if (jh->b_next_transaction != NULL || jh->b_transaction != NULL) +- goto out; ++ return; + + spin_lock(&journal->j_list_lock); +- if (jh->b_cp_transaction != NULL) { +- /* written-back checkpointed metadata buffer */ +- JBUFFER_TRACE(jh, "remove from checkpoint list"); +- __jbd2_journal_remove_checkpoint(jh); +- } ++ /* Remove written-back checkpointed metadata buffer */ ++ if (jh->b_cp_transaction != NULL) ++ jbd2_journal_try_remove_checkpoint(jh); + spin_unlock(&journal->j_list_lock); +-out: + return; + } + +diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c +index 1707f46b1335c..cf34d0c309459 100644 +--- a/fs/nfs/direct.c ++++ b/fs/nfs/direct.c +@@ -474,20 +474,26 @@ out: + return result; + } + +-static void +-nfs_direct_join_group(struct list_head *list, struct inode *inode) ++static void nfs_direct_join_group(struct list_head *list, struct inode *inode) + { +- struct nfs_page *req, *next; ++ struct nfs_page *req, *subreq; + + list_for_each_entry(req, list, wb_list) { +- if (req->wb_head != req || req->wb_this_page == req) ++ if (req->wb_head != req) + continue; +- for (next = req->wb_this_page; +- next != req->wb_head; +- next = next->wb_this_page) { +- nfs_list_remove_request(next); +- nfs_release_request(next); +- } ++ subreq = req->wb_this_page; ++ if (subreq == req) ++ continue; ++ do { ++ /* ++ * Remove subrequests from this list before freeing ++ * them in the call to nfs_join_page_group(). ++ */ ++ if (!list_empty(&subreq->wb_list)) { ++ nfs_list_remove_request(subreq); ++ nfs_release_request(subreq); ++ } ++ } while ((subreq = subreq->wb_this_page) != req); + nfs_join_page_group(req, inode); + } + } +diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c +index 6b2cfa59a1a2b..e0c1fb98f907a 100644 +--- a/fs/nfs/inode.c ++++ b/fs/nfs/inode.c +@@ -717,9 +717,7 @@ void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr, + if ((attr->ia_valid & ATTR_KILL_SUID) != 0 && + inode->i_mode & S_ISUID) + inode->i_mode &= ~S_ISUID; +- if ((attr->ia_valid & ATTR_KILL_SGID) != 0 && +- (inode->i_mode & (S_ISGID | S_IXGRP)) == +- (S_ISGID | S_IXGRP)) ++ if (setattr_should_drop_sgid(&init_user_ns, inode)) + inode->i_mode &= ~S_ISGID; + if ((attr->ia_valid & ATTR_MODE) != 0) { + int mode = attr->ia_mode & S_IALLUGO; +diff --git a/fs/nfs/nfs42proc.c b/fs/nfs/nfs42proc.c +index ecb428512fe1a..7c33bba179d2f 100644 +--- a/fs/nfs/nfs42proc.c ++++ b/fs/nfs/nfs42proc.c +@@ -1359,7 +1359,6 @@ ssize_t nfs42_proc_getxattr(struct inode *inode, const char *name, + for (i = 0; i < np; i++) { + pages[i] = alloc_page(GFP_KERNEL); + if (!pages[i]) { +- np = i + 1; + err = -ENOMEM; + goto out; + } +@@ -1383,8 +1382,8 @@ ssize_t nfs42_proc_getxattr(struct inode *inode, const char *name, + } while (exception.retry); + + out: +- while (--np >= 0) +- __free_page(pages[np]); ++ while (--i >= 0) ++ __free_page(pages[i]); + kfree(pages); + + return err; +diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c +index 177cb7b089b9a..1044305e77996 100644 +--- a/fs/nfs/nfs4proc.c ++++ b/fs/nfs/nfs4proc.c +@@ -5995,9 +5995,8 @@ static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, + out_ok: + ret = res.acl_len; + out_free: +- for (i = 0; i < npages; i++) +- if (pages[i]) +- __free_page(pages[i]); ++ while (--i >= 0) ++ __free_page(pages[i]); + if (res.acl_scratch) + __free_page(res.acl_scratch); + kfree(pages); +@@ -7171,8 +7170,15 @@ static void nfs4_lock_done(struct rpc_task *task, void *calldata) + } else if (!nfs4_update_lock_stateid(lsp, &data->res.stateid)) + goto out_restart; + break; +- case -NFS4ERR_BAD_STATEID: + case -NFS4ERR_OLD_STATEID: ++ if (data->arg.new_lock_owner != 0 && ++ nfs4_refresh_open_old_stateid(&data->arg.open_stateid, ++ lsp->ls_state)) ++ goto out_restart; ++ if (nfs4_refresh_lock_old_stateid(&data->arg.lock_stateid, lsp)) ++ goto out_restart; ++ fallthrough; ++ case -NFS4ERR_BAD_STATEID: + case -NFS4ERR_STALE_STATEID: + case -NFS4ERR_EXPIRED: + if (data->arg.new_lock_owner != 0) { +diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c +index c5dc0cd6f7031..96714e105d7bf 100644 +--- a/fs/nfsd/nfs4state.c ++++ b/fs/nfsd/nfs4state.c +@@ -1368,9 +1368,9 @@ static void revoke_delegation(struct nfs4_delegation *dp) + WARN_ON(!list_empty(&dp->dl_recall_lru)); + + if (clp->cl_minorversion) { ++ spin_lock(&clp->cl_lock); + dp->dl_stid.sc_type = NFS4_REVOKED_DELEG_STID; + refcount_inc(&dp->dl_stid.sc_count); +- spin_lock(&clp->cl_lock); + list_add(&dp->dl_recall_lru, &clp->cl_revoked); + spin_unlock(&clp->cl_lock); + } +diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c +index 155b34c4683c2..4c11046800ab4 100644 +--- a/fs/nfsd/vfs.c ++++ b/fs/nfsd/vfs.c +@@ -321,7 +321,9 @@ nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap) + iap->ia_mode &= ~S_ISGID; + } else { + /* set ATTR_KILL_* bits and let VFS handle it */ +- iap->ia_valid |= (ATTR_KILL_SUID | ATTR_KILL_SGID); ++ iap->ia_valid |= ATTR_KILL_SUID; ++ iap->ia_valid |= ++ setattr_should_drop_sgid(&init_user_ns, inode); + } + } + } +diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h +index 05f2cc03d03d9..b235d6833e27d 100644 +--- a/include/drm/display/drm_dp.h ++++ b/include/drm/display/drm_dp.h +@@ -1525,7 +1525,7 @@ enum drm_dp_phy { + + #define DP_BRANCH_OUI_HEADER_SIZE 0xc + #define DP_RECEIVER_CAP_SIZE 0xf +-#define DP_DSC_RECEIVER_CAP_SIZE 0xf ++#define DP_DSC_RECEIVER_CAP_SIZE 0x10 /* DSC Capabilities 0x60 through 0x6F */ + #define EDP_PSR_RECEIVER_CAP_SIZE 2 + #define EDP_DISPLAY_CTL_CAP_SIZE 3 + #define DP_LTTPR_COMMON_CAP_SIZE 8 +diff --git a/include/drm/drm_aperture.h b/include/drm/drm_aperture.h +index 7096703c39493..cbe33b49fd5dc 100644 +--- a/include/drm/drm_aperture.h ++++ b/include/drm/drm_aperture.h +@@ -13,14 +13,13 @@ int devm_aperture_acquire_from_firmware(struct drm_device *dev, resource_size_t + resource_size_t size); + + int drm_aperture_remove_conflicting_framebuffers(resource_size_t base, resource_size_t size, +- bool primary, const struct drm_driver *req_driver); ++ const struct drm_driver *req_driver); + + int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, + const struct drm_driver *req_driver); + + /** + * drm_aperture_remove_framebuffers - remove all existing framebuffers +- * @primary: also kick vga16fb if present + * @req_driver: requesting DRM driver + * + * This function removes all graphics device drivers. Use this function on systems +@@ -30,9 +29,9 @@ int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, + * 0 on success, or a negative errno code otherwise + */ + static inline int +-drm_aperture_remove_framebuffers(bool primary, const struct drm_driver *req_driver) ++drm_aperture_remove_framebuffers(const struct drm_driver *req_driver) + { +- return drm_aperture_remove_conflicting_framebuffers(0, (resource_size_t)-1, primary, ++ return drm_aperture_remove_conflicting_framebuffers(0, (resource_size_t)-1, + req_driver); + } + +diff --git a/include/linux/clk.h b/include/linux/clk.h +index 1ef0133242374..06f1b292f8a00 100644 +--- a/include/linux/clk.h ++++ b/include/linux/clk.h +@@ -183,6 +183,39 @@ int clk_get_scaled_duty_cycle(struct clk *clk, unsigned int scale); + */ + bool clk_is_match(const struct clk *p, const struct clk *q); + ++/** ++ * clk_rate_exclusive_get - get exclusivity over the rate control of a ++ * producer ++ * @clk: clock source ++ * ++ * This function allows drivers to get exclusive control over the rate of a ++ * provider. It prevents any other consumer to execute, even indirectly, ++ * opereation which could alter the rate of the provider or cause glitches ++ * ++ * If exlusivity is claimed more than once on clock, even by the same driver, ++ * the rate effectively gets locked as exclusivity can't be preempted. ++ * ++ * Must not be called from within atomic context. ++ * ++ * Returns success (0) or negative errno. ++ */ ++int clk_rate_exclusive_get(struct clk *clk); ++ ++/** ++ * clk_rate_exclusive_put - release exclusivity over the rate control of a ++ * producer ++ * @clk: clock source ++ * ++ * This function allows drivers to release the exclusivity it previously got ++ * from clk_rate_exclusive_get() ++ * ++ * The caller must balance the number of clk_rate_exclusive_get() and ++ * clk_rate_exclusive_put() calls. ++ * ++ * Must not be called from within atomic context. ++ */ ++void clk_rate_exclusive_put(struct clk *clk); ++ + #else + + static inline int clk_notifier_register(struct clk *clk, +@@ -236,6 +269,13 @@ static inline bool clk_is_match(const struct clk *p, const struct clk *q) + return p == q; + } + ++static inline int clk_rate_exclusive_get(struct clk *clk) ++{ ++ return 0; ++} ++ ++static inline void clk_rate_exclusive_put(struct clk *clk) {} ++ + #endif + + #ifdef CONFIG_HAVE_CLK_PREPARE +@@ -583,38 +623,6 @@ struct clk *devm_clk_get_optional_enabled(struct device *dev, const char *id); + */ + struct clk *devm_get_clk_from_child(struct device *dev, + struct device_node *np, const char *con_id); +-/** +- * clk_rate_exclusive_get - get exclusivity over the rate control of a +- * producer +- * @clk: clock source +- * +- * This function allows drivers to get exclusive control over the rate of a +- * provider. It prevents any other consumer to execute, even indirectly, +- * opereation which could alter the rate of the provider or cause glitches +- * +- * If exlusivity is claimed more than once on clock, even by the same driver, +- * the rate effectively gets locked as exclusivity can't be preempted. +- * +- * Must not be called from within atomic context. +- * +- * Returns success (0) or negative errno. +- */ +-int clk_rate_exclusive_get(struct clk *clk); +- +-/** +- * clk_rate_exclusive_put - release exclusivity over the rate control of a +- * producer +- * @clk: clock source +- * +- * This function allows drivers to release the exclusivity it previously got +- * from clk_rate_exclusive_get() +- * +- * The caller must balance the number of clk_rate_exclusive_get() and +- * clk_rate_exclusive_put() calls. +- * +- * Must not be called from within atomic context. +- */ +-void clk_rate_exclusive_put(struct clk *clk); + + /** + * clk_enable - inform the system when the clock source should be running. +@@ -974,14 +982,6 @@ static inline void clk_bulk_put_all(int num_clks, struct clk_bulk_data *clks) {} + + static inline void devm_clk_put(struct device *dev, struct clk *clk) {} + +- +-static inline int clk_rate_exclusive_get(struct clk *clk) +-{ +- return 0; +-} +- +-static inline void clk_rate_exclusive_put(struct clk *clk) {} +- + static inline int clk_enable(struct clk *clk) + { + return 0; +diff --git a/include/linux/cpuset.h b/include/linux/cpuset.h +index d58e0476ee8e3..0348dba5680ef 100644 +--- a/include/linux/cpuset.h ++++ b/include/linux/cpuset.h +@@ -71,8 +71,10 @@ extern void cpuset_init_smp(void); + extern void cpuset_force_rebuild(void); + extern void cpuset_update_active_cpus(void); + extern void cpuset_wait_for_hotplug(void); +-extern void cpuset_read_lock(void); +-extern void cpuset_read_unlock(void); ++extern void inc_dl_tasks_cs(struct task_struct *task); ++extern void dec_dl_tasks_cs(struct task_struct *task); ++extern void cpuset_lock(void); ++extern void cpuset_unlock(void); + extern void cpuset_cpus_allowed(struct task_struct *p, struct cpumask *mask); + extern bool cpuset_cpus_allowed_fallback(struct task_struct *p); + extern nodemask_t cpuset_mems_allowed(struct task_struct *p); +@@ -196,8 +198,10 @@ static inline void cpuset_update_active_cpus(void) + + static inline void cpuset_wait_for_hotplug(void) { } + +-static inline void cpuset_read_lock(void) { } +-static inline void cpuset_read_unlock(void) { } ++static inline void inc_dl_tasks_cs(struct task_struct *task) { } ++static inline void dec_dl_tasks_cs(struct task_struct *task) { } ++static inline void cpuset_lock(void) { } ++static inline void cpuset_unlock(void) { } + + static inline void cpuset_cpus_allowed(struct task_struct *p, + struct cpumask *mask) +diff --git a/include/linux/fs.h b/include/linux/fs.h +index a2b5592c68284..26ea1a0a59a10 100644 +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -3120,6 +3120,8 @@ extern struct inode *new_inode(struct super_block *sb); + extern void free_inode_nonrcu(struct inode *inode); + extern int setattr_should_drop_suidgid(struct user_namespace *, struct inode *); + extern int file_remove_privs(struct file *); ++int setattr_should_drop_sgid(struct user_namespace *mnt_userns, ++ const struct inode *inode); + + /* + * This must be used for allocating filesystems specific inodes to set +diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h +index 0b7242370b567..ebb1608d9dcd2 100644 +--- a/include/linux/jbd2.h ++++ b/include/linux/jbd2.h +@@ -622,12 +622,6 @@ struct transaction_s + */ + struct journal_head *t_checkpoint_list; + +- /* +- * Doubly-linked circular list of all buffers submitted for IO while +- * checkpointing. [j_list_lock] +- */ +- struct journal_head *t_checkpoint_io_list; +- + /* + * Doubly-linked circular list of metadata buffers being + * shadowed by log IO. The IO buffers on the iobuf list and +@@ -1441,6 +1435,7 @@ extern void jbd2_journal_commit_transaction(journal_t *); + void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy); + unsigned long jbd2_journal_shrink_checkpoint_list(journal_t *journal, unsigned long *nr_to_scan); + int __jbd2_journal_remove_checkpoint(struct journal_head *); ++int jbd2_journal_try_remove_checkpoint(struct journal_head *jh); + void jbd2_journal_destroy_checkpoint(journal_t *journal); + void __jbd2_journal_insert_checkpoint(struct journal_head *, transaction_t *); + +diff --git a/include/linux/mm.h b/include/linux/mm.h +index b8ed44f401b58..104ec00823da8 100644 +--- a/include/linux/mm.h ++++ b/include/linux/mm.h +@@ -1727,6 +1727,25 @@ static inline size_t folio_size(struct folio *folio) + return PAGE_SIZE << folio_order(folio); + } + ++/** ++ * folio_estimated_sharers - Estimate the number of sharers of a folio. ++ * @folio: The folio. ++ * ++ * folio_estimated_sharers() aims to serve as a function to efficiently ++ * estimate the number of processes sharing a folio. This is done by ++ * looking at the precise mapcount of the first subpage in the folio, and ++ * assuming the other subpages are the same. This may not be true for large ++ * folios. If you want exact mapcounts for exact calculations, look at ++ * page_mapcount() or folio_total_mapcount(). ++ * ++ * Return: The estimated number of processes sharing a folio. ++ */ ++static inline int folio_estimated_sharers(struct folio *folio) ++{ ++ return page_mapcount(folio_page(folio, 0)); ++} ++ ++ + #ifndef HAVE_ARCH_MAKE_PAGE_ACCESSIBLE + static inline int arch_make_page_accessible(struct page *page) + { +@@ -3091,6 +3110,16 @@ static inline bool gup_must_unshare(unsigned int flags, struct page *page) + if (IS_ENABLED(CONFIG_HAVE_FAST_GUP)) + smp_rmb(); + ++ /* ++ * During GUP-fast we might not get called on the head page for a ++ * hugetlb page that is mapped using cont-PTE, because GUP-fast does ++ * not work with the abstracted hugetlb PTEs that always point at the ++ * head page. For hugetlb, PageAnonExclusive only applies on the head ++ * page (as it cannot be partially COW-shared), so lookup the head page. ++ */ ++ if (unlikely(!PageHead(page) && PageHuge(page))) ++ page = compound_head(page); ++ + /* + * Note that PageKsm() pages cannot be exclusive, and consequently, + * cannot get pinned. +diff --git a/include/linux/raid_class.h b/include/linux/raid_class.h +index 5cdfcb873a8f0..772d45b2a60a0 100644 +--- a/include/linux/raid_class.h ++++ b/include/linux/raid_class.h +@@ -77,7 +77,3 @@ DEFINE_RAID_ATTRIBUTE(enum raid_state, state) + + struct raid_template *raid_class_attach(struct raid_function_template *); + void raid_class_release(struct raid_template *); +- +-int __must_check raid_component_add(struct raid_template *, struct device *, +- struct device *); +- +diff --git a/include/linux/sched.h b/include/linux/sched.h +index ffb6eb55cd135..0cac69902ec58 100644 +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -1846,7 +1846,9 @@ current_restore_flags(unsigned long orig_flags, unsigned long flags) + } + + extern int cpuset_cpumask_can_shrink(const struct cpumask *cur, const struct cpumask *trial); +-extern int task_can_attach(struct task_struct *p, const struct cpumask *cs_effective_cpus); ++extern int task_can_attach(struct task_struct *p); ++extern int dl_bw_alloc(int cpu, u64 dl_bw); ++extern void dl_bw_free(int cpu, u64 dl_bw); + #ifdef CONFIG_SMP + extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask); + extern int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask); +diff --git a/include/net/bonding.h b/include/net/bonding.h +index 17329a19f0c64..9a3ac960dfe15 100644 +--- a/include/net/bonding.h ++++ b/include/net/bonding.h +@@ -727,23 +727,14 @@ static inline struct slave *bond_slave_has_mac(struct bonding *bond, + } + + /* Caller must hold rcu_read_lock() for read */ +-static inline bool bond_slave_has_mac_rx(struct bonding *bond, const u8 *mac) ++static inline bool bond_slave_has_mac_rcu(struct bonding *bond, const u8 *mac) + { + struct list_head *iter; + struct slave *tmp; +- struct netdev_hw_addr *ha; + + bond_for_each_slave_rcu(bond, tmp, iter) + if (ether_addr_equal_64bits(mac, tmp->dev->dev_addr)) + return true; +- +- if (netdev_uc_empty(bond->dev)) +- return false; +- +- netdev_for_each_uc_addr(ha, bond->dev) +- if (ether_addr_equal_64bits(mac, ha->addr)) +- return true; +- + return false; + } + +diff --git a/include/net/inet_sock.h b/include/net/inet_sock.h +index c8ef3b881f03d..c2432c2addc82 100644 +--- a/include/net/inet_sock.h ++++ b/include/net/inet_sock.h +@@ -222,8 +222,8 @@ struct inet_sock { + __s16 uc_ttl; + __u16 cmsg_flags; + struct ip_options_rcu __rcu *inet_opt; ++ atomic_t inet_id; + __be16 inet_sport; +- __u16 inet_id; + + __u8 tos; + __u8 min_ttl; +diff --git a/include/net/ip.h b/include/net/ip.h +index 530e7257e4389..1872f570abeda 100644 +--- a/include/net/ip.h ++++ b/include/net/ip.h +@@ -532,8 +532,19 @@ static inline void ip_select_ident_segs(struct net *net, struct sk_buff *skb, + * generator as much as we can. + */ + if (sk && inet_sk(sk)->inet_daddr) { +- iph->id = htons(inet_sk(sk)->inet_id); +- inet_sk(sk)->inet_id += segs; ++ int val; ++ ++ /* avoid atomic operations for TCP, ++ * as we hold socket lock at this point. ++ */ ++ if (sk_is_tcp(sk)) { ++ sock_owned_by_me(sk); ++ val = atomic_read(&inet_sk(sk)->inet_id); ++ atomic_set(&inet_sk(sk)->inet_id, val + segs); ++ } else { ++ val = atomic_add_return(segs, &inet_sk(sk)->inet_id); ++ } ++ iph->id = htons(val); + return; + } + if ((iph->frag_off & htons(IP_DF)) && !skb->ignore_df) { +diff --git a/include/net/mac80211.h b/include/net/mac80211.h +index 72b739dc6d530..8a338c33118f9 100644 +--- a/include/net/mac80211.h ++++ b/include/net/mac80211.h +@@ -6444,6 +6444,7 @@ void ieee80211_stop_rx_ba_session(struct ieee80211_vif *vif, u16 ba_rx_bitmap, + * marks frames marked in the bitmap as having been filtered. Afterwards, it + * checks if any frames in the window starting from @ssn can now be released + * (in case they were only waiting for frames that were filtered.) ++ * (Only work correctly if @max_rx_aggregation_subframes <= 64 frames) + */ + void ieee80211_mark_rx_ba_filtered_frames(struct ieee80211_sta *pubsta, u8 tid, + u16 ssn, u64 filtered, +diff --git a/include/net/rtnetlink.h b/include/net/rtnetlink.h +index bf8bb33578250..9f881b74f32ed 100644 +--- a/include/net/rtnetlink.h ++++ b/include/net/rtnetlink.h +@@ -189,8 +189,8 @@ struct net_device *rtnl_create_link(struct net *net, const char *ifname, + int rtnl_delete_link(struct net_device *dev); + int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm); + +-int rtnl_nla_parse_ifla(struct nlattr **tb, const struct nlattr *head, int len, +- struct netlink_ext_ack *exterr); ++int rtnl_nla_parse_ifinfomsg(struct nlattr **tb, const struct nlattr *nla_peer, ++ struct netlink_ext_ack *exterr); + struct net *rtnl_get_net_ns_capable(struct sock *sk, int netnsid); + + #define MODULE_ALIAS_RTNL_LINK(kind) MODULE_ALIAS("rtnl-link-" kind) +diff --git a/include/net/sock.h b/include/net/sock.h +index 699408944952c..d1f936ed97556 100644 +--- a/include/net/sock.h ++++ b/include/net/sock.h +@@ -1320,6 +1320,7 @@ struct proto { + /* + * Pressure flag: try to collapse. + * Technical note: it is used by multiple contexts non atomically. ++ * Make sure to use READ_ONCE()/WRITE_ONCE() for all reads/writes. + * All the __sk_mem_schedule() is of this nature: accounting + * is strict, actions are advisory and have some latency. + */ +@@ -1448,7 +1449,7 @@ static inline bool sk_has_memory_pressure(const struct sock *sk) + static inline bool sk_under_global_memory_pressure(const struct sock *sk) + { + return sk->sk_prot->memory_pressure && +- !!*sk->sk_prot->memory_pressure; ++ !!READ_ONCE(*sk->sk_prot->memory_pressure); + } + + static inline bool sk_under_memory_pressure(const struct sock *sk) +@@ -1460,7 +1461,7 @@ static inline bool sk_under_memory_pressure(const struct sock *sk) + mem_cgroup_under_socket_pressure(sk->sk_memcg)) + return true; + +- return !!*sk->sk_prot->memory_pressure; ++ return !!READ_ONCE(*sk->sk_prot->memory_pressure); + } + + static inline long +@@ -1537,7 +1538,7 @@ proto_memory_pressure(struct proto *prot) + { + if (!prot->memory_pressure) + return false; +- return !!*prot->memory_pressure; ++ return !!READ_ONCE(*prot->memory_pressure); + } + + +diff --git a/include/trace/events/jbd2.h b/include/trace/events/jbd2.h +index 8f5ee380d3093..5646ae15a957a 100644 +--- a/include/trace/events/jbd2.h ++++ b/include/trace/events/jbd2.h +@@ -462,11 +462,9 @@ TRACE_EVENT(jbd2_shrink_scan_exit, + TRACE_EVENT(jbd2_shrink_checkpoint_list, + + TP_PROTO(journal_t *journal, tid_t first_tid, tid_t tid, tid_t last_tid, +- unsigned long nr_freed, unsigned long nr_scanned, +- tid_t next_tid), ++ unsigned long nr_freed, tid_t next_tid), + +- TP_ARGS(journal, first_tid, tid, last_tid, nr_freed, +- nr_scanned, next_tid), ++ TP_ARGS(journal, first_tid, tid, last_tid, nr_freed, next_tid), + + TP_STRUCT__entry( + __field(dev_t, dev) +@@ -474,7 +472,6 @@ TRACE_EVENT(jbd2_shrink_checkpoint_list, + __field(tid_t, tid) + __field(tid_t, last_tid) + __field(unsigned long, nr_freed) +- __field(unsigned long, nr_scanned) + __field(tid_t, next_tid) + ), + +@@ -484,15 +481,14 @@ TRACE_EVENT(jbd2_shrink_checkpoint_list, + __entry->tid = tid; + __entry->last_tid = last_tid; + __entry->nr_freed = nr_freed; +- __entry->nr_scanned = nr_scanned; + __entry->next_tid = next_tid; + ), + + TP_printk("dev %d,%d shrink transaction %u-%u(%u) freed %lu " +- "scanned %lu next transaction %u", ++ "next transaction %u", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->first_tid, __entry->tid, __entry->last_tid, +- __entry->nr_freed, __entry->nr_scanned, __entry->next_tid) ++ __entry->nr_freed, __entry->next_tid) + ); + + #endif /* _TRACE_JBD2_H */ +diff --git a/io_uring/msg_ring.c b/io_uring/msg_ring.c +index 3526389ac2180..cd922d2bef5f5 100644 +--- a/io_uring/msg_ring.c ++++ b/io_uring/msg_ring.c +@@ -15,6 +15,7 @@ + + struct io_msg { + struct file *file; ++ struct file *src_file; + u64 user_data; + u32 len; + u32 cmd; +@@ -23,33 +24,12 @@ struct io_msg { + u32 flags; + }; + +-static int io_msg_ring_data(struct io_kiocb *req) ++static void io_double_unlock_ctx(struct io_ring_ctx *octx) + { +- struct io_ring_ctx *target_ctx = req->file->private_data; +- struct io_msg *msg = io_kiocb_to_cmd(req, struct io_msg); +- +- if (msg->src_fd || msg->dst_fd || msg->flags) +- return -EINVAL; +- if (target_ctx->flags & IORING_SETUP_R_DISABLED) +- return -EBADFD; +- +- if (io_post_aux_cqe(target_ctx, msg->user_data, msg->len, 0, true)) +- return 0; +- +- return -EOVERFLOW; +-} +- +-static void io_double_unlock_ctx(struct io_ring_ctx *ctx, +- struct io_ring_ctx *octx, +- unsigned int issue_flags) +-{ +- if (issue_flags & IO_URING_F_UNLOCKED) +- mutex_unlock(&ctx->uring_lock); + mutex_unlock(&octx->uring_lock); + } + +-static int io_double_lock_ctx(struct io_ring_ctx *ctx, +- struct io_ring_ctx *octx, ++static int io_double_lock_ctx(struct io_ring_ctx *octx, + unsigned int issue_flags) + { + /* +@@ -62,60 +42,86 @@ static int io_double_lock_ctx(struct io_ring_ctx *ctx, + return -EAGAIN; + return 0; + } ++ mutex_lock(&octx->uring_lock); ++ return 0; ++} + +- /* Always grab smallest value ctx first. We know ctx != octx. */ +- if (ctx < octx) { +- mutex_lock(&ctx->uring_lock); +- mutex_lock(&octx->uring_lock); +- } else { +- mutex_lock(&octx->uring_lock); +- mutex_lock(&ctx->uring_lock); +- } ++void io_msg_ring_cleanup(struct io_kiocb *req) ++{ ++ struct io_msg *msg = io_kiocb_to_cmd(req, struct io_msg); + +- return 0; ++ if (WARN_ON_ONCE(!msg->src_file)) ++ return; ++ ++ fput(msg->src_file); ++ msg->src_file = NULL; + } + +-static int io_msg_send_fd(struct io_kiocb *req, unsigned int issue_flags) ++static int io_msg_ring_data(struct io_kiocb *req, unsigned int issue_flags) + { + struct io_ring_ctx *target_ctx = req->file->private_data; + struct io_msg *msg = io_kiocb_to_cmd(req, struct io_msg); +- struct io_ring_ctx *ctx = req->ctx; +- unsigned long file_ptr; +- struct file *src_file; + int ret; + +- if (msg->len) +- return -EINVAL; +- if (target_ctx == ctx) ++ if (msg->src_fd || msg->dst_fd || msg->flags) + return -EINVAL; + if (target_ctx->flags & IORING_SETUP_R_DISABLED) + return -EBADFD; + +- ret = io_double_lock_ctx(ctx, target_ctx, issue_flags); +- if (unlikely(ret)) +- return ret; ++ ret = -EOVERFLOW; ++ if (target_ctx->flags & IORING_SETUP_IOPOLL) { ++ if (unlikely(io_double_lock_ctx(target_ctx, issue_flags))) ++ return -EAGAIN; ++ if (io_post_aux_cqe(target_ctx, msg->user_data, msg->len, 0, true)) ++ ret = 0; ++ io_double_unlock_ctx(target_ctx); ++ } else { ++ if (io_post_aux_cqe(target_ctx, msg->user_data, msg->len, 0, true)) ++ ret = 0; ++ } + +- ret = -EBADF; +- if (unlikely(msg->src_fd >= ctx->nr_user_files)) +- goto out_unlock; ++ return ret; ++} + +- msg->src_fd = array_index_nospec(msg->src_fd, ctx->nr_user_files); +- file_ptr = io_fixed_file_slot(&ctx->file_table, msg->src_fd)->file_ptr; +- if (!file_ptr) +- goto out_unlock; ++static struct file *io_msg_grab_file(struct io_kiocb *req, unsigned int issue_flags) ++{ ++ struct io_msg *msg = io_kiocb_to_cmd(req, struct io_msg); ++ struct io_ring_ctx *ctx = req->ctx; ++ struct file *file = NULL; ++ unsigned long file_ptr; ++ int idx = msg->src_fd; ++ ++ io_ring_submit_lock(ctx, issue_flags); ++ if (likely(idx < ctx->nr_user_files)) { ++ idx = array_index_nospec(idx, ctx->nr_user_files); ++ file_ptr = io_fixed_file_slot(&ctx->file_table, idx)->file_ptr; ++ file = (struct file *) (file_ptr & FFS_MASK); ++ if (file) ++ get_file(file); ++ } ++ io_ring_submit_unlock(ctx, issue_flags); ++ return file; ++} + +- src_file = (struct file *) (file_ptr & FFS_MASK); +- get_file(src_file); ++static int io_msg_install_complete(struct io_kiocb *req, unsigned int issue_flags) ++{ ++ struct io_ring_ctx *target_ctx = req->file->private_data; ++ struct io_msg *msg = io_kiocb_to_cmd(req, struct io_msg); ++ struct file *src_file = msg->src_file; ++ int ret; ++ ++ if (unlikely(io_double_lock_ctx(target_ctx, issue_flags))) ++ return -EAGAIN; + + ret = __io_fixed_fd_install(target_ctx, src_file, msg->dst_fd); +- if (ret < 0) { +- fput(src_file); ++ if (ret < 0) + goto out_unlock; +- } ++ ++ msg->src_file = NULL; ++ req->flags &= ~REQ_F_NEED_CLEANUP; + + if (msg->flags & IORING_MSG_RING_CQE_SKIP) + goto out_unlock; +- + /* + * If this fails, the target still received the file descriptor but + * wasn't notified of the fact. This means that if this request +@@ -125,10 +131,29 @@ static int io_msg_send_fd(struct io_kiocb *req, unsigned int issue_flags) + if (!io_post_aux_cqe(target_ctx, msg->user_data, ret, 0, true)) + ret = -EOVERFLOW; + out_unlock: +- io_double_unlock_ctx(ctx, target_ctx, issue_flags); ++ io_double_unlock_ctx(target_ctx); + return ret; + } + ++static int io_msg_send_fd(struct io_kiocb *req, unsigned int issue_flags) ++{ ++ struct io_ring_ctx *target_ctx = req->file->private_data; ++ struct io_msg *msg = io_kiocb_to_cmd(req, struct io_msg); ++ struct io_ring_ctx *ctx = req->ctx; ++ struct file *src_file = msg->src_file; ++ ++ if (target_ctx == ctx) ++ return -EINVAL; ++ if (!src_file) { ++ src_file = io_msg_grab_file(req, issue_flags); ++ if (!src_file) ++ return -EBADF; ++ msg->src_file = src_file; ++ req->flags |= REQ_F_NEED_CLEANUP; ++ } ++ return io_msg_install_complete(req, issue_flags); ++} ++ + int io_msg_ring_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) + { + struct io_msg *msg = io_kiocb_to_cmd(req, struct io_msg); +@@ -136,6 +161,7 @@ int io_msg_ring_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) + if (unlikely(sqe->buf_index || sqe->personality)) + return -EINVAL; + ++ msg->src_file = NULL; + msg->user_data = READ_ONCE(sqe->off); + msg->len = READ_ONCE(sqe->len); + msg->cmd = READ_ONCE(sqe->addr); +@@ -159,7 +185,7 @@ int io_msg_ring(struct io_kiocb *req, unsigned int issue_flags) + + switch (msg->cmd) { + case IORING_MSG_DATA: +- ret = io_msg_ring_data(req); ++ ret = io_msg_ring_data(req, issue_flags); + break; + case IORING_MSG_SEND_FD: + ret = io_msg_send_fd(req, issue_flags); +diff --git a/io_uring/msg_ring.h b/io_uring/msg_ring.h +index fb9601f202d07..3987ee6c0e5f1 100644 +--- a/io_uring/msg_ring.h ++++ b/io_uring/msg_ring.h +@@ -2,3 +2,4 @@ + + int io_msg_ring_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); + int io_msg_ring(struct io_kiocb *req, unsigned int issue_flags); ++void io_msg_ring_cleanup(struct io_kiocb *req); +diff --git a/io_uring/opdef.c b/io_uring/opdef.c +index 04dd2c983fce4..3aa0d65c50e34 100644 +--- a/io_uring/opdef.c ++++ b/io_uring/opdef.c +@@ -445,6 +445,7 @@ const struct io_op_def io_op_defs[] = { + .name = "MSG_RING", + .prep = io_msg_ring_prep, + .issue = io_msg_ring, ++ .cleanup = io_msg_ring_cleanup, + }, + [IORING_OP_FSETXATTR] = { + .needs_file = 1, +diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c +index 73f11e4db3a4d..97ecca43386d9 100644 +--- a/kernel/cgroup/cgroup.c ++++ b/kernel/cgroup/cgroup.c +@@ -57,6 +57,7 @@ + #include <linux/file.h> + #include <linux/fs_parser.h> + #include <linux/sched/cputime.h> ++#include <linux/sched/deadline.h> + #include <linux/psi.h> + #include <net/sock.h> + +@@ -6681,6 +6682,9 @@ void cgroup_exit(struct task_struct *tsk) + list_add_tail(&tsk->cg_list, &cset->dying_tasks); + cset->nr_tasks--; + ++ if (dl_task(tsk)) ++ dec_dl_tasks_cs(tsk); ++ + WARN_ON_ONCE(cgroup_task_frozen(tsk)); + if (unlikely(!(tsk->flags & PF_KTHREAD) && + test_bit(CGRP_FREEZE, &task_dfl_cgroup(tsk)->flags))) +diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c +index e276db7228451..db3e05b6b4dd2 100644 +--- a/kernel/cgroup/cpuset.c ++++ b/kernel/cgroup/cpuset.c +@@ -193,6 +193,14 @@ struct cpuset { + int use_parent_ecpus; + int child_ecpus_count; + ++ /* ++ * number of SCHED_DEADLINE tasks attached to this cpuset, so that we ++ * know when to rebuild associated root domain bandwidth information. ++ */ ++ int nr_deadline_tasks; ++ int nr_migrate_dl_tasks; ++ u64 sum_migrate_dl_bw; ++ + /* Invalid partition error code, not lock protected */ + enum prs_errcode prs_err; + +@@ -245,6 +253,20 @@ static inline struct cpuset *parent_cs(struct cpuset *cs) + return css_cs(cs->css.parent); + } + ++void inc_dl_tasks_cs(struct task_struct *p) ++{ ++ struct cpuset *cs = task_cs(p); ++ ++ cs->nr_deadline_tasks++; ++} ++ ++void dec_dl_tasks_cs(struct task_struct *p) ++{ ++ struct cpuset *cs = task_cs(p); ++ ++ cs->nr_deadline_tasks--; ++} ++ + /* bits in struct cpuset flags field */ + typedef enum { + CS_ONLINE, +@@ -366,22 +388,23 @@ static struct cpuset top_cpuset = { + if (is_cpuset_online(((des_cs) = css_cs((pos_css))))) + + /* +- * There are two global locks guarding cpuset structures - cpuset_rwsem and ++ * There are two global locks guarding cpuset structures - cpuset_mutex and + * callback_lock. We also require taking task_lock() when dereferencing a + * task's cpuset pointer. See "The task_lock() exception", at the end of this +- * comment. The cpuset code uses only cpuset_rwsem write lock. Other +- * kernel subsystems can use cpuset_read_lock()/cpuset_read_unlock() to +- * prevent change to cpuset structures. ++ * comment. The cpuset code uses only cpuset_mutex. Other kernel subsystems ++ * can use cpuset_lock()/cpuset_unlock() to prevent change to cpuset ++ * structures. Note that cpuset_mutex needs to be a mutex as it is used in ++ * paths that rely on priority inheritance (e.g. scheduler - on RT) for ++ * correctness. + * + * A task must hold both locks to modify cpusets. If a task holds +- * cpuset_rwsem, it blocks others wanting that rwsem, ensuring that it +- * is the only task able to also acquire callback_lock and be able to +- * modify cpusets. It can perform various checks on the cpuset structure +- * first, knowing nothing will change. It can also allocate memory while +- * just holding cpuset_rwsem. While it is performing these checks, various +- * callback routines can briefly acquire callback_lock to query cpusets. +- * Once it is ready to make the changes, it takes callback_lock, blocking +- * everyone else. ++ * cpuset_mutex, it blocks others, ensuring that it is the only task able to ++ * also acquire callback_lock and be able to modify cpusets. It can perform ++ * various checks on the cpuset structure first, knowing nothing will change. ++ * It can also allocate memory while just holding cpuset_mutex. While it is ++ * performing these checks, various callback routines can briefly acquire ++ * callback_lock to query cpusets. Once it is ready to make the changes, it ++ * takes callback_lock, blocking everyone else. + * + * Calls to the kernel memory allocator can not be made while holding + * callback_lock, as that would risk double tripping on callback_lock +@@ -403,16 +426,16 @@ static struct cpuset top_cpuset = { + * guidelines for accessing subsystem state in kernel/cgroup.c + */ + +-DEFINE_STATIC_PERCPU_RWSEM(cpuset_rwsem); ++static DEFINE_MUTEX(cpuset_mutex); + +-void cpuset_read_lock(void) ++void cpuset_lock(void) + { +- percpu_down_read(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + } + +-void cpuset_read_unlock(void) ++void cpuset_unlock(void) + { +- percpu_up_read(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + } + + static DEFINE_SPINLOCK(callback_lock); +@@ -496,7 +519,7 @@ static inline bool partition_is_populated(struct cpuset *cs, + * One way or another, we guarantee to return some non-empty subset + * of cpu_online_mask. + * +- * Call with callback_lock or cpuset_rwsem held. ++ * Call with callback_lock or cpuset_mutex held. + */ + static void guarantee_online_cpus(struct task_struct *tsk, + struct cpumask *pmask) +@@ -538,7 +561,7 @@ out_unlock: + * One way or another, we guarantee to return some non-empty subset + * of node_states[N_MEMORY]. + * +- * Call with callback_lock or cpuset_rwsem held. ++ * Call with callback_lock or cpuset_mutex held. + */ + static void guarantee_online_mems(struct cpuset *cs, nodemask_t *pmask) + { +@@ -550,7 +573,7 @@ static void guarantee_online_mems(struct cpuset *cs, nodemask_t *pmask) + /* + * update task's spread flag if cpuset's page/slab spread flag is set + * +- * Call with callback_lock or cpuset_rwsem held. The check can be skipped ++ * Call with callback_lock or cpuset_mutex held. The check can be skipped + * if on default hierarchy. + */ + static void cpuset_update_task_spread_flags(struct cpuset *cs, +@@ -575,7 +598,7 @@ static void cpuset_update_task_spread_flags(struct cpuset *cs, + * + * One cpuset is a subset of another if all its allowed CPUs and + * Memory Nodes are a subset of the other, and its exclusive flags +- * are only set if the other's are set. Call holding cpuset_rwsem. ++ * are only set if the other's are set. Call holding cpuset_mutex. + */ + + static int is_cpuset_subset(const struct cpuset *p, const struct cpuset *q) +@@ -713,7 +736,7 @@ out: + * If we replaced the flag and mask values of the current cpuset + * (cur) with those values in the trial cpuset (trial), would + * our various subset and exclusive rules still be valid? Presumes +- * cpuset_rwsem held. ++ * cpuset_mutex held. + * + * 'cur' is the address of an actual, in-use cpuset. Operations + * such as list traversal that depend on the actual address of the +@@ -829,7 +852,7 @@ static void update_domain_attr_tree(struct sched_domain_attr *dattr, + rcu_read_unlock(); + } + +-/* Must be called with cpuset_rwsem held. */ ++/* Must be called with cpuset_mutex held. */ + static inline int nr_cpusets(void) + { + /* jump label reference count + the top-level cpuset */ +@@ -855,7 +878,7 @@ static inline int nr_cpusets(void) + * domains when operating in the severe memory shortage situations + * that could cause allocation failures below. + * +- * Must be called with cpuset_rwsem held. ++ * Must be called with cpuset_mutex held. + * + * The three key local variables below are: + * cp - cpuset pointer, used (together with pos_css) to perform a +@@ -1066,11 +1089,14 @@ done: + return ndoms; + } + +-static void update_tasks_root_domain(struct cpuset *cs) ++static void dl_update_tasks_root_domain(struct cpuset *cs) + { + struct css_task_iter it; + struct task_struct *task; + ++ if (cs->nr_deadline_tasks == 0) ++ return; ++ + css_task_iter_start(&cs->css, 0, &it); + + while ((task = css_task_iter_next(&it))) +@@ -1079,12 +1105,12 @@ static void update_tasks_root_domain(struct cpuset *cs) + css_task_iter_end(&it); + } + +-static void rebuild_root_domains(void) ++static void dl_rebuild_rd_accounting(void) + { + struct cpuset *cs = NULL; + struct cgroup_subsys_state *pos_css; + +- percpu_rwsem_assert_held(&cpuset_rwsem); ++ lockdep_assert_held(&cpuset_mutex); + lockdep_assert_cpus_held(); + lockdep_assert_held(&sched_domains_mutex); + +@@ -1107,7 +1133,7 @@ static void rebuild_root_domains(void) + + rcu_read_unlock(); + +- update_tasks_root_domain(cs); ++ dl_update_tasks_root_domain(cs); + + rcu_read_lock(); + css_put(&cs->css); +@@ -1121,7 +1147,7 @@ partition_and_rebuild_sched_domains(int ndoms_new, cpumask_var_t doms_new[], + { + mutex_lock(&sched_domains_mutex); + partition_sched_domains_locked(ndoms_new, doms_new, dattr_new); +- rebuild_root_domains(); ++ dl_rebuild_rd_accounting(); + mutex_unlock(&sched_domains_mutex); + } + +@@ -1134,7 +1160,7 @@ partition_and_rebuild_sched_domains(int ndoms_new, cpumask_var_t doms_new[], + * 'cpus' is removed, then call this routine to rebuild the + * scheduler's dynamic sched domains. + * +- * Call with cpuset_rwsem held. Takes cpus_read_lock(). ++ * Call with cpuset_mutex held. Takes cpus_read_lock(). + */ + static void rebuild_sched_domains_locked(void) + { +@@ -1145,7 +1171,7 @@ static void rebuild_sched_domains_locked(void) + int ndoms; + + lockdep_assert_cpus_held(); +- percpu_rwsem_assert_held(&cpuset_rwsem); ++ lockdep_assert_held(&cpuset_mutex); + + /* + * If we have raced with CPU hotplug, return early to avoid +@@ -1196,9 +1222,9 @@ static void rebuild_sched_domains_locked(void) + void rebuild_sched_domains(void) + { + cpus_read_lock(); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + rebuild_sched_domains_locked(); +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + cpus_read_unlock(); + } + +@@ -1208,7 +1234,7 @@ void rebuild_sched_domains(void) + * @new_cpus: the temp variable for the new effective_cpus mask + * + * Iterate through each task of @cs updating its cpus_allowed to the +- * effective cpuset's. As this function is called with cpuset_rwsem held, ++ * effective cpuset's. As this function is called with cpuset_mutex held, + * cpuset membership stays stable. + */ + static void update_tasks_cpumask(struct cpuset *cs, struct cpumask *new_cpus) +@@ -1317,7 +1343,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd, + int old_prs, new_prs; + int part_error = PERR_NONE; /* Partition error? */ + +- percpu_rwsem_assert_held(&cpuset_rwsem); ++ lockdep_assert_held(&cpuset_mutex); + + /* + * The parent must be a partition root. +@@ -1540,7 +1566,7 @@ static int update_parent_subparts_cpumask(struct cpuset *cs, int cmd, + * + * On legacy hierarchy, effective_cpus will be the same with cpu_allowed. + * +- * Called with cpuset_rwsem held ++ * Called with cpuset_mutex held + */ + static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp, + bool force) +@@ -1700,7 +1726,7 @@ static void update_sibling_cpumasks(struct cpuset *parent, struct cpuset *cs, + struct cpuset *sibling; + struct cgroup_subsys_state *pos_css; + +- percpu_rwsem_assert_held(&cpuset_rwsem); ++ lockdep_assert_held(&cpuset_mutex); + + /* + * Check all its siblings and call update_cpumasks_hier() +@@ -1950,12 +1976,12 @@ static void *cpuset_being_rebound; + * @cs: the cpuset in which each task's mems_allowed mask needs to be changed + * + * Iterate through each task of @cs updating its mems_allowed to the +- * effective cpuset's. As this function is called with cpuset_rwsem held, ++ * effective cpuset's. As this function is called with cpuset_mutex held, + * cpuset membership stays stable. + */ + static void update_tasks_nodemask(struct cpuset *cs) + { +- static nodemask_t newmems; /* protected by cpuset_rwsem */ ++ static nodemask_t newmems; /* protected by cpuset_mutex */ + struct css_task_iter it; + struct task_struct *task; + +@@ -1968,7 +1994,7 @@ static void update_tasks_nodemask(struct cpuset *cs) + * take while holding tasklist_lock. Forks can happen - the + * mpol_dup() cpuset_being_rebound check will catch such forks, + * and rebind their vma mempolicies too. Because we still hold +- * the global cpuset_rwsem, we know that no other rebind effort ++ * the global cpuset_mutex, we know that no other rebind effort + * will be contending for the global variable cpuset_being_rebound. + * It's ok if we rebind the same mm twice; mpol_rebind_mm() + * is idempotent. Also migrate pages in each mm to new nodes. +@@ -2014,7 +2040,7 @@ static void update_tasks_nodemask(struct cpuset *cs) + * + * On legacy hierarchy, effective_mems will be the same with mems_allowed. + * +- * Called with cpuset_rwsem held ++ * Called with cpuset_mutex held + */ + static void update_nodemasks_hier(struct cpuset *cs, nodemask_t *new_mems) + { +@@ -2067,7 +2093,7 @@ static void update_nodemasks_hier(struct cpuset *cs, nodemask_t *new_mems) + * mempolicies and if the cpuset is marked 'memory_migrate', + * migrate the tasks pages to the new memory. + * +- * Call with cpuset_rwsem held. May take callback_lock during call. ++ * Call with cpuset_mutex held. May take callback_lock during call. + * Will take tasklist_lock, scan tasklist for tasks in cpuset cs, + * lock each such tasks mm->mmap_lock, scan its vma's and rebind + * their mempolicies to the cpusets new mems_allowed. +@@ -2159,7 +2185,7 @@ static int update_relax_domain_level(struct cpuset *cs, s64 val) + * @cs: the cpuset in which each task's spread flags needs to be changed + * + * Iterate through each task of @cs updating its spread flags. As this +- * function is called with cpuset_rwsem held, cpuset membership stays ++ * function is called with cpuset_mutex held, cpuset membership stays + * stable. + */ + static void update_tasks_flags(struct cpuset *cs) +@@ -2179,7 +2205,7 @@ static void update_tasks_flags(struct cpuset *cs) + * cs: the cpuset to update + * turning_on: whether the flag is being set or cleared + * +- * Call with cpuset_rwsem held. ++ * Call with cpuset_mutex held. + */ + + static int update_flag(cpuset_flagbits_t bit, struct cpuset *cs, +@@ -2229,7 +2255,7 @@ out: + * @new_prs: new partition root state + * Return: 0 if successful, != 0 if error + * +- * Call with cpuset_rwsem held. ++ * Call with cpuset_mutex held. + */ + static int update_prstate(struct cpuset *cs, int new_prs) + { +@@ -2467,19 +2493,26 @@ static int cpuset_can_attach_check(struct cpuset *cs) + return 0; + } + +-/* Called by cgroups to determine if a cpuset is usable; cpuset_rwsem held */ ++static void reset_migrate_dl_data(struct cpuset *cs) ++{ ++ cs->nr_migrate_dl_tasks = 0; ++ cs->sum_migrate_dl_bw = 0; ++} ++ ++/* Called by cgroups to determine if a cpuset is usable; cpuset_mutex held */ + static int cpuset_can_attach(struct cgroup_taskset *tset) + { + struct cgroup_subsys_state *css; +- struct cpuset *cs; ++ struct cpuset *cs, *oldcs; + struct task_struct *task; + int ret; + + /* used later by cpuset_attach() */ + cpuset_attach_old_cs = task_cs(cgroup_taskset_first(tset, &css)); ++ oldcs = cpuset_attach_old_cs; + cs = css_cs(css); + +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + + /* Check to see if task is allowed in the cpuset */ + ret = cpuset_can_attach_check(cs); +@@ -2487,21 +2520,46 @@ static int cpuset_can_attach(struct cgroup_taskset *tset) + goto out_unlock; + + cgroup_taskset_for_each(task, css, tset) { +- ret = task_can_attach(task, cs->effective_cpus); ++ ret = task_can_attach(task); + if (ret) + goto out_unlock; + ret = security_task_setscheduler(task); + if (ret) + goto out_unlock; ++ ++ if (dl_task(task)) { ++ cs->nr_migrate_dl_tasks++; ++ cs->sum_migrate_dl_bw += task->dl.dl_bw; ++ } + } + ++ if (!cs->nr_migrate_dl_tasks) ++ goto out_success; ++ ++ if (!cpumask_intersects(oldcs->effective_cpus, cs->effective_cpus)) { ++ int cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus); ++ ++ if (unlikely(cpu >= nr_cpu_ids)) { ++ reset_migrate_dl_data(cs); ++ ret = -EINVAL; ++ goto out_unlock; ++ } ++ ++ ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw); ++ if (ret) { ++ reset_migrate_dl_data(cs); ++ goto out_unlock; ++ } ++ } ++ ++out_success: + /* + * Mark attach is in progress. This makes validate_change() fail + * changes which zero cpus/mems_allowed. + */ + cs->attach_in_progress++; + out_unlock: +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + return ret; + } + +@@ -2513,15 +2571,23 @@ static void cpuset_cancel_attach(struct cgroup_taskset *tset) + cgroup_taskset_first(tset, &css); + cs = css_cs(css); + +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + cs->attach_in_progress--; + if (!cs->attach_in_progress) + wake_up(&cpuset_attach_wq); +- percpu_up_write(&cpuset_rwsem); ++ ++ if (cs->nr_migrate_dl_tasks) { ++ int cpu = cpumask_any(cs->effective_cpus); ++ ++ dl_bw_free(cpu, cs->sum_migrate_dl_bw); ++ reset_migrate_dl_data(cs); ++ } ++ ++ mutex_unlock(&cpuset_mutex); + } + + /* +- * Protected by cpuset_rwsem. cpus_attach is used only by cpuset_attach_task() ++ * Protected by cpuset_mutex. cpus_attach is used only by cpuset_attach_task() + * but we can't allocate it dynamically there. Define it global and + * allocate from cpuset_init(). + */ +@@ -2530,7 +2596,7 @@ static nodemask_t cpuset_attach_nodemask_to; + + static void cpuset_attach_task(struct cpuset *cs, struct task_struct *task) + { +- percpu_rwsem_assert_held(&cpuset_rwsem); ++ lockdep_assert_held(&cpuset_mutex); + + if (cs != &top_cpuset) + guarantee_online_cpus(task, cpus_attach); +@@ -2558,7 +2624,7 @@ static void cpuset_attach(struct cgroup_taskset *tset) + cs = css_cs(css); + + lockdep_assert_cpus_held(); /* see cgroup_attach_lock() */ +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + + guarantee_online_mems(cs, &cpuset_attach_nodemask_to); + +@@ -2594,11 +2660,17 @@ static void cpuset_attach(struct cgroup_taskset *tset) + + cs->old_mems_allowed = cpuset_attach_nodemask_to; + ++ if (cs->nr_migrate_dl_tasks) { ++ cs->nr_deadline_tasks += cs->nr_migrate_dl_tasks; ++ oldcs->nr_deadline_tasks -= cs->nr_migrate_dl_tasks; ++ reset_migrate_dl_data(cs); ++ } ++ + cs->attach_in_progress--; + if (!cs->attach_in_progress) + wake_up(&cpuset_attach_wq); + +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + } + + /* The various types of files and directories in a cpuset file system */ +@@ -2630,7 +2702,7 @@ static int cpuset_write_u64(struct cgroup_subsys_state *css, struct cftype *cft, + int retval = 0; + + cpus_read_lock(); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + if (!is_cpuset_online(cs)) { + retval = -ENODEV; + goto out_unlock; +@@ -2666,7 +2738,7 @@ static int cpuset_write_u64(struct cgroup_subsys_state *css, struct cftype *cft, + break; + } + out_unlock: +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + cpus_read_unlock(); + return retval; + } +@@ -2679,7 +2751,7 @@ static int cpuset_write_s64(struct cgroup_subsys_state *css, struct cftype *cft, + int retval = -ENODEV; + + cpus_read_lock(); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + if (!is_cpuset_online(cs)) + goto out_unlock; + +@@ -2692,7 +2764,7 @@ static int cpuset_write_s64(struct cgroup_subsys_state *css, struct cftype *cft, + break; + } + out_unlock: +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + cpus_read_unlock(); + return retval; + } +@@ -2725,7 +2797,7 @@ static ssize_t cpuset_write_resmask(struct kernfs_open_file *of, + * operation like this one can lead to a deadlock through kernfs + * active_ref protection. Let's break the protection. Losing the + * protection is okay as we check whether @cs is online after +- * grabbing cpuset_rwsem anyway. This only happens on the legacy ++ * grabbing cpuset_mutex anyway. This only happens on the legacy + * hierarchies. + */ + css_get(&cs->css); +@@ -2733,7 +2805,7 @@ static ssize_t cpuset_write_resmask(struct kernfs_open_file *of, + flush_work(&cpuset_hotplug_work); + + cpus_read_lock(); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + if (!is_cpuset_online(cs)) + goto out_unlock; + +@@ -2757,7 +2829,7 @@ static ssize_t cpuset_write_resmask(struct kernfs_open_file *of, + + free_cpuset(trialcs); + out_unlock: +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + cpus_read_unlock(); + kernfs_unbreak_active_protection(of->kn); + css_put(&cs->css); +@@ -2905,13 +2977,13 @@ static ssize_t sched_partition_write(struct kernfs_open_file *of, char *buf, + + css_get(&cs->css); + cpus_read_lock(); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + if (!is_cpuset_online(cs)) + goto out_unlock; + + retval = update_prstate(cs, val); + out_unlock: +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + cpus_read_unlock(); + css_put(&cs->css); + return retval ?: nbytes; +@@ -3124,7 +3196,7 @@ static int cpuset_css_online(struct cgroup_subsys_state *css) + return 0; + + cpus_read_lock(); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + + set_bit(CS_ONLINE, &cs->flags); + if (is_spread_page(parent)) +@@ -3175,7 +3247,7 @@ static int cpuset_css_online(struct cgroup_subsys_state *css) + cpumask_copy(cs->effective_cpus, parent->cpus_allowed); + spin_unlock_irq(&callback_lock); + out_unlock: +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + cpus_read_unlock(); + return 0; + } +@@ -3196,7 +3268,7 @@ static void cpuset_css_offline(struct cgroup_subsys_state *css) + struct cpuset *cs = css_cs(css); + + cpus_read_lock(); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + + if (is_partition_valid(cs)) + update_prstate(cs, 0); +@@ -3215,7 +3287,7 @@ static void cpuset_css_offline(struct cgroup_subsys_state *css) + cpuset_dec(); + clear_bit(CS_ONLINE, &cs->flags); + +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + cpus_read_unlock(); + } + +@@ -3228,7 +3300,7 @@ static void cpuset_css_free(struct cgroup_subsys_state *css) + + static void cpuset_bind(struct cgroup_subsys_state *root_css) + { +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + spin_lock_irq(&callback_lock); + + if (is_in_v2_mode()) { +@@ -3241,7 +3313,7 @@ static void cpuset_bind(struct cgroup_subsys_state *root_css) + } + + spin_unlock_irq(&callback_lock); +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + } + + /* +@@ -3262,14 +3334,14 @@ static int cpuset_can_fork(struct task_struct *task, struct css_set *cset) + return 0; + + lockdep_assert_held(&cgroup_mutex); +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + + /* Check to see if task is allowed in the cpuset */ + ret = cpuset_can_attach_check(cs); + if (ret) + goto out_unlock; + +- ret = task_can_attach(task, cs->effective_cpus); ++ ret = task_can_attach(task); + if (ret) + goto out_unlock; + +@@ -3283,7 +3355,7 @@ static int cpuset_can_fork(struct task_struct *task, struct css_set *cset) + */ + cs->attach_in_progress++; + out_unlock: +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + return ret; + } + +@@ -3299,11 +3371,11 @@ static void cpuset_cancel_fork(struct task_struct *task, struct css_set *cset) + if (same_cs) + return; + +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + cs->attach_in_progress--; + if (!cs->attach_in_progress) + wake_up(&cpuset_attach_wq); +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + } + + /* +@@ -3331,7 +3403,7 @@ static void cpuset_fork(struct task_struct *task) + } + + /* CLONE_INTO_CGROUP */ +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + guarantee_online_mems(cs, &cpuset_attach_nodemask_to); + cpuset_attach_task(cs, task); + +@@ -3339,7 +3411,7 @@ static void cpuset_fork(struct task_struct *task) + if (!cs->attach_in_progress) + wake_up(&cpuset_attach_wq); + +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + } + + struct cgroup_subsys cpuset_cgrp_subsys = { +@@ -3369,8 +3441,6 @@ struct cgroup_subsys cpuset_cgrp_subsys = { + + int __init cpuset_init(void) + { +- BUG_ON(percpu_init_rwsem(&cpuset_rwsem)); +- + BUG_ON(!alloc_cpumask_var(&top_cpuset.cpus_allowed, GFP_KERNEL)); + BUG_ON(!alloc_cpumask_var(&top_cpuset.effective_cpus, GFP_KERNEL)); + BUG_ON(!zalloc_cpumask_var(&top_cpuset.subparts_cpus, GFP_KERNEL)); +@@ -3442,7 +3512,7 @@ hotplug_update_tasks_legacy(struct cpuset *cs, + is_empty = cpumask_empty(cs->cpus_allowed) || + nodes_empty(cs->mems_allowed); + +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + + /* + * Move tasks to the nearest ancestor with execution resources, +@@ -3452,7 +3522,7 @@ hotplug_update_tasks_legacy(struct cpuset *cs, + if (is_empty) + remove_tasks_in_empty_cpuset(cs); + +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + } + + static void +@@ -3503,14 +3573,14 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp) + retry: + wait_event(cpuset_attach_wq, cs->attach_in_progress == 0); + +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + + /* + * We have raced with task attaching. We wait until attaching + * is finished, so we won't attach a task to an empty cpuset. + */ + if (cs->attach_in_progress) { +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + goto retry; + } + +@@ -3604,7 +3674,7 @@ update_tasks: + hotplug_update_tasks_legacy(cs, &new_cpus, &new_mems, + cpus_updated, mems_updated); + +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + } + + /** +@@ -3634,7 +3704,7 @@ static void cpuset_hotplug_workfn(struct work_struct *work) + if (on_dfl && !alloc_cpumasks(NULL, &tmp)) + ptmp = &tmp; + +- percpu_down_write(&cpuset_rwsem); ++ mutex_lock(&cpuset_mutex); + + /* fetch the available cpus/mems and find out which changed how */ + cpumask_copy(&new_cpus, cpu_active_mask); +@@ -3691,7 +3761,7 @@ static void cpuset_hotplug_workfn(struct work_struct *work) + update_tasks_nodemask(&top_cpuset); + } + +- percpu_up_write(&cpuset_rwsem); ++ mutex_unlock(&cpuset_mutex); + + /* if cpus or mems changed, we need to propagate to descendants */ + if (cpus_updated || mems_updated) { +@@ -4101,7 +4171,7 @@ void __cpuset_memory_pressure_bump(void) + * - Used for /proc/<pid>/cpuset. + * - No need to task_lock(tsk) on this tsk->cpuset reference, as it + * doesn't really matter if tsk->cpuset changes after we read it, +- * and we take cpuset_rwsem, keeping cpuset_attach() from changing it ++ * and we take cpuset_mutex, keeping cpuset_attach() from changing it + * anyway. + */ + int proc_cpuset_show(struct seq_file *m, struct pid_namespace *ns, +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +index b23dcbeacdf33..0f6a92737c912 100644 +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -7475,6 +7475,7 @@ static int __sched_setscheduler(struct task_struct *p, + int reset_on_fork; + int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK; + struct rq *rq; ++ bool cpuset_locked = false; + + /* The pi code expects interrupts enabled */ + BUG_ON(pi && in_interrupt()); +@@ -7524,8 +7525,14 @@ recheck: + return retval; + } + +- if (pi) +- cpuset_read_lock(); ++ /* ++ * SCHED_DEADLINE bandwidth accounting relies on stable cpusets ++ * information. ++ */ ++ if (dl_policy(policy) || dl_policy(p->policy)) { ++ cpuset_locked = true; ++ cpuset_lock(); ++ } + + /* + * Make sure no PI-waiters arrive (or leave) while we are +@@ -7601,8 +7608,8 @@ change: + if (unlikely(oldpolicy != -1 && oldpolicy != p->policy)) { + policy = oldpolicy = -1; + task_rq_unlock(rq, p, &rf); +- if (pi) +- cpuset_read_unlock(); ++ if (cpuset_locked) ++ cpuset_unlock(); + goto recheck; + } + +@@ -7669,7 +7676,8 @@ change: + task_rq_unlock(rq, p, &rf); + + if (pi) { +- cpuset_read_unlock(); ++ if (cpuset_locked) ++ cpuset_unlock(); + rt_mutex_adjust_pi(p); + } + +@@ -7681,8 +7689,8 @@ change: + + unlock: + task_rq_unlock(rq, p, &rf); +- if (pi) +- cpuset_read_unlock(); ++ if (cpuset_locked) ++ cpuset_unlock(); + return retval; + } + +@@ -9075,8 +9083,7 @@ int cpuset_cpumask_can_shrink(const struct cpumask *cur, + return ret; + } + +-int task_can_attach(struct task_struct *p, +- const struct cpumask *cs_effective_cpus) ++int task_can_attach(struct task_struct *p) + { + int ret = 0; + +@@ -9089,21 +9096,9 @@ int task_can_attach(struct task_struct *p, + * success of set_cpus_allowed_ptr() on all attached tasks + * before cpus_mask may be changed. + */ +- if (p->flags & PF_NO_SETAFFINITY) { ++ if (p->flags & PF_NO_SETAFFINITY) + ret = -EINVAL; +- goto out; +- } + +- if (dl_task(p) && !cpumask_intersects(task_rq(p)->rd->span, +- cs_effective_cpus)) { +- int cpu = cpumask_any_and(cpu_active_mask, cs_effective_cpus); +- +- if (unlikely(cpu >= nr_cpu_ids)) +- return -EINVAL; +- ret = dl_cpu_busy(cpu, p); +- } +- +-out: + return ret; + } + +@@ -9385,7 +9380,7 @@ static void cpuset_cpu_active(void) + static int cpuset_cpu_inactive(unsigned int cpu) + { + if (!cpuhp_tasks_frozen) { +- int ret = dl_cpu_busy(cpu, NULL); ++ int ret = dl_bw_check_overflow(cpu); + + if (ret) + return ret; +diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c +index f7d381b6c3133..9ce9810861ba5 100644 +--- a/kernel/sched/deadline.c ++++ b/kernel/sched/deadline.c +@@ -16,6 +16,8 @@ + * Fabio Checconi <fchecconi@gmail.com> + */ + ++#include <linux/cpuset.h> ++ + /* + * Default limits for DL period; on the top end we guard against small util + * tasks still getting ridiculously long effective runtimes, on the bottom end we +@@ -2597,6 +2599,12 @@ static void switched_from_dl(struct rq *rq, struct task_struct *p) + if (task_on_rq_queued(p) && p->dl.dl_runtime) + task_non_contending(p); + ++ /* ++ * In case a task is setscheduled out from SCHED_DEADLINE we need to ++ * keep track of that on its cpuset (for correct bandwidth tracking). ++ */ ++ dec_dl_tasks_cs(p); ++ + if (!task_on_rq_queued(p)) { + /* + * Inactive timer is armed. However, p is leaving DEADLINE and +@@ -2637,6 +2645,12 @@ static void switched_to_dl(struct rq *rq, struct task_struct *p) + if (hrtimer_try_to_cancel(&p->dl.inactive_timer) == 1) + put_task_struct(p); + ++ /* ++ * In case a task is setscheduled to SCHED_DEADLINE we need to keep ++ * track of that on its cpuset (for correct bandwidth tracking). ++ */ ++ inc_dl_tasks_cs(p); ++ + /* If p is not queued we will update its parameters at next wakeup. */ + if (!task_on_rq_queued(p)) { + add_rq_bw(&p->dl, &rq->dl); +@@ -3023,26 +3037,38 @@ int dl_cpuset_cpumask_can_shrink(const struct cpumask *cur, + return ret; + } + +-int dl_cpu_busy(int cpu, struct task_struct *p) ++enum dl_bw_request { ++ dl_bw_req_check_overflow = 0, ++ dl_bw_req_alloc, ++ dl_bw_req_free ++}; ++ ++static int dl_bw_manage(enum dl_bw_request req, int cpu, u64 dl_bw) + { +- unsigned long flags, cap; ++ unsigned long flags; + struct dl_bw *dl_b; +- bool overflow; ++ bool overflow = 0; + + rcu_read_lock_sched(); + dl_b = dl_bw_of(cpu); + raw_spin_lock_irqsave(&dl_b->lock, flags); +- cap = dl_bw_capacity(cpu); +- overflow = __dl_overflow(dl_b, cap, 0, p ? p->dl.dl_bw : 0); + +- if (!overflow && p) { +- /* +- * We reserve space for this task in the destination +- * root_domain, as we can't fail after this point. +- * We will free resources in the source root_domain +- * later on (see set_cpus_allowed_dl()). +- */ +- __dl_add(dl_b, p->dl.dl_bw, dl_bw_cpus(cpu)); ++ if (req == dl_bw_req_free) { ++ __dl_sub(dl_b, dl_bw, dl_bw_cpus(cpu)); ++ } else { ++ unsigned long cap = dl_bw_capacity(cpu); ++ ++ overflow = __dl_overflow(dl_b, cap, 0, dl_bw); ++ ++ if (req == dl_bw_req_alloc && !overflow) { ++ /* ++ * We reserve space in the destination ++ * root_domain, as we can't fail after this point. ++ * We will free resources in the source root_domain ++ * later on (see set_cpus_allowed_dl()). ++ */ ++ __dl_add(dl_b, dl_bw, dl_bw_cpus(cpu)); ++ } + } + + raw_spin_unlock_irqrestore(&dl_b->lock, flags); +@@ -3050,6 +3076,21 @@ int dl_cpu_busy(int cpu, struct task_struct *p) + + return overflow ? -EBUSY : 0; + } ++ ++int dl_bw_check_overflow(int cpu) ++{ ++ return dl_bw_manage(dl_bw_req_check_overflow, cpu, 0); ++} ++ ++int dl_bw_alloc(int cpu, u64 dl_bw) ++{ ++ return dl_bw_manage(dl_bw_req_alloc, cpu, dl_bw); ++} ++ ++void dl_bw_free(int cpu, u64 dl_bw) ++{ ++ dl_bw_manage(dl_bw_req_free, cpu, dl_bw); ++} + #endif + + #ifdef CONFIG_SCHED_DEBUG +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +index d6d488e8eb554..b62d53d7c264f 100644 +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -330,7 +330,7 @@ extern void __getparam_dl(struct task_struct *p, struct sched_attr *attr); + extern bool __checkparam_dl(const struct sched_attr *attr); + extern bool dl_param_changed(struct task_struct *p, const struct sched_attr *attr); + extern int dl_cpuset_cpumask_can_shrink(const struct cpumask *cur, const struct cpumask *trial); +-extern int dl_cpu_busy(int cpu, struct task_struct *p); ++extern int dl_bw_check_overflow(int cpu); + + #ifdef CONFIG_CGROUP_SCHED + +diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c +index af33c5a4166d4..1a87cb70f1eb5 100644 +--- a/kernel/trace/trace.c ++++ b/kernel/trace/trace.c +@@ -4128,8 +4128,15 @@ static void *s_start(struct seq_file *m, loff_t *pos) + * will point to the same string as current_trace->name. + */ + mutex_lock(&trace_types_lock); +- if (unlikely(tr->current_trace && iter->trace->name != tr->current_trace->name)) ++ if (unlikely(tr->current_trace && iter->trace->name != tr->current_trace->name)) { ++ /* Close iter->trace before switching to the new current tracer */ ++ if (iter->trace->close) ++ iter->trace->close(iter); + *iter->trace = *tr->current_trace; ++ /* Reopen the new current tracer */ ++ if (iter->trace->open) ++ iter->trace->open(iter); ++ } + mutex_unlock(&trace_types_lock); + + #ifdef CONFIG_TRACER_MAX_TRACE +@@ -5189,11 +5196,17 @@ int tracing_set_cpumask(struct trace_array *tr, + !cpumask_test_cpu(cpu, tracing_cpumask_new)) { + atomic_inc(&per_cpu_ptr(tr->array_buffer.data, cpu)->disabled); + ring_buffer_record_disable_cpu(tr->array_buffer.buffer, cpu); ++#ifdef CONFIG_TRACER_MAX_TRACE ++ ring_buffer_record_disable_cpu(tr->max_buffer.buffer, cpu); ++#endif + } + if (!cpumask_test_cpu(cpu, tr->tracing_cpumask) && + cpumask_test_cpu(cpu, tracing_cpumask_new)) { + atomic_dec(&per_cpu_ptr(tr->array_buffer.data, cpu)->disabled); + ring_buffer_record_enable_cpu(tr->array_buffer.buffer, cpu); ++#ifdef CONFIG_TRACER_MAX_TRACE ++ ring_buffer_record_enable_cpu(tr->max_buffer.buffer, cpu); ++#endif + } + } + arch_spin_unlock(&tr->max_lock); +diff --git a/kernel/trace/trace_irqsoff.c b/kernel/trace/trace_irqsoff.c +index 590b3d51afae9..ba37f768e2f27 100644 +--- a/kernel/trace/trace_irqsoff.c ++++ b/kernel/trace/trace_irqsoff.c +@@ -231,7 +231,8 @@ static void irqsoff_trace_open(struct trace_iterator *iter) + { + if (is_graph(iter->tr)) + graph_trace_open(iter); +- ++ else ++ iter->private = NULL; + } + + static void irqsoff_trace_close(struct trace_iterator *iter) +diff --git a/kernel/trace/trace_sched_wakeup.c b/kernel/trace/trace_sched_wakeup.c +index 330aee1c1a49e..0469a04a355f2 100644 +--- a/kernel/trace/trace_sched_wakeup.c ++++ b/kernel/trace/trace_sched_wakeup.c +@@ -168,6 +168,8 @@ static void wakeup_trace_open(struct trace_iterator *iter) + { + if (is_graph(iter->tr)) + graph_trace_open(iter); ++ else ++ iter->private = NULL; + } + + static void wakeup_trace_close(struct trace_iterator *iter) +diff --git a/lib/clz_ctz.c b/lib/clz_ctz.c +index 0d3a686b5ba29..fb8c0c5c2bd27 100644 +--- a/lib/clz_ctz.c ++++ b/lib/clz_ctz.c +@@ -28,36 +28,16 @@ int __weak __clzsi2(int val) + } + EXPORT_SYMBOL(__clzsi2); + +-int __weak __clzdi2(long val); +-int __weak __ctzdi2(long val); +-#if BITS_PER_LONG == 32 +- +-int __weak __clzdi2(long val) ++int __weak __clzdi2(u64 val); ++int __weak __clzdi2(u64 val) + { +- return 32 - fls((int)val); ++ return 64 - fls64(val); + } + EXPORT_SYMBOL(__clzdi2); + +-int __weak __ctzdi2(long val) ++int __weak __ctzdi2(u64 val); ++int __weak __ctzdi2(u64 val) + { +- return __ffs((u32)val); ++ return __ffs64(val); + } + EXPORT_SYMBOL(__ctzdi2); +- +-#elif BITS_PER_LONG == 64 +- +-int __weak __clzdi2(long val) +-{ +- return 64 - fls64((u64)val); +-} +-EXPORT_SYMBOL(__clzdi2); +- +-int __weak __ctzdi2(long val) +-{ +- return __ffs64((u64)val); +-} +-EXPORT_SYMBOL(__ctzdi2); +- +-#else +-#error BITS_PER_LONG not 32 or 64 +-#endif +diff --git a/lib/maple_tree.c b/lib/maple_tree.c +index 47d0c95b9a01e..250b4c67fac8f 100644 +--- a/lib/maple_tree.c ++++ b/lib/maple_tree.c +@@ -4333,6 +4333,9 @@ static inline bool mas_wr_append(struct ma_wr_state *wr_mas) + struct ma_state *mas = wr_mas->mas; + unsigned char node_pivots = mt_pivots[wr_mas->type]; + ++ if (mt_in_rcu(mas->tree)) ++ return false; ++ + if ((mas->index != wr_mas->r_min) && (mas->last == wr_mas->r_max)) { + if (new_end < node_pivots) + wr_mas->pivots[new_end] = wr_mas->pivots[end]; +diff --git a/lib/radix-tree.c b/lib/radix-tree.c +index 3c78e1e8b2ad6..2ec38f08e4f0e 100644 +--- a/lib/radix-tree.c ++++ b/lib/radix-tree.c +@@ -1134,7 +1134,6 @@ static void set_iter_tags(struct radix_tree_iter *iter, + void __rcu **radix_tree_iter_resume(void __rcu **slot, + struct radix_tree_iter *iter) + { +- slot++; + iter->index = __radix_tree_iter_add(iter, 1); + iter->next_index = iter->index; + iter->tags = 0; +diff --git a/mm/madvise.c b/mm/madvise.c +index d03e149ffe6e8..5973399b2f9b7 100644 +--- a/mm/madvise.c ++++ b/mm/madvise.c +@@ -654,8 +654,8 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr, + * deactivate all pages. + */ + if (folio_test_large(folio)) { +- if (folio_mapcount(folio) != 1) +- goto out; ++ if (folio_estimated_sharers(folio) != 1) ++ break; + folio_get(folio); + if (!folio_trylock(folio)) { + folio_put(folio); +diff --git a/mm/memory-failure.c b/mm/memory-failure.c +index 4457f9423e2c1..99de0328d1bed 100644 +--- a/mm/memory-failure.c ++++ b/mm/memory-failure.c +@@ -2591,10 +2591,13 @@ retry: + if (ret > 0) { + ret = soft_offline_in_use_page(page); + } else if (ret == 0) { +- if (!page_handle_poison(page, true, false) && try_again) { +- try_again = false; +- flags &= ~MF_COUNT_INCREASED; +- goto retry; ++ if (!page_handle_poison(page, true, false)) { ++ if (try_again) { ++ try_again = false; ++ flags &= ~MF_COUNT_INCREASED; ++ goto retry; ++ } ++ ret = -EBUSY; + } + } + +diff --git a/mm/shmem.c b/mm/shmem.c +index aba041a3df739..10365ced5b1fc 100644 +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -800,14 +800,16 @@ unsigned long shmem_partial_swap_usage(struct address_space *mapping, + XA_STATE(xas, &mapping->i_pages, start); + struct page *page; + unsigned long swapped = 0; ++ unsigned long max = end - 1; + + rcu_read_lock(); +- xas_for_each(&xas, page, end - 1) { ++ xas_for_each(&xas, page, max) { + if (xas_retry(&xas, page)) + continue; + if (xa_is_value(page)) + swapped++; +- ++ if (xas.xa_index == max) ++ break; + if (need_resched()) { + xas_pause(&xas); + cond_resched_rcu(); +diff --git a/mm/vmalloc.c b/mm/vmalloc.c +index d5dc361dc104d..80bd104a4d42e 100644 +--- a/mm/vmalloc.c ++++ b/mm/vmalloc.c +@@ -2909,6 +2909,10 @@ void *vmap_pfn(unsigned long *pfns, unsigned int count, pgprot_t prot) + free_vm_area(area); + return NULL; + } ++ ++ flush_cache_vmap((unsigned long)area->addr, ++ (unsigned long)area->addr + count * PAGE_SIZE); ++ + return area->addr; + } + EXPORT_SYMBOL_GPL(vmap_pfn); +diff --git a/net/Makefile b/net/Makefile +index 6a62e5b273781..0914bea9c335f 100644 +--- a/net/Makefile ++++ b/net/Makefile +@@ -23,6 +23,7 @@ obj-$(CONFIG_BPFILTER) += bpfilter/ + obj-$(CONFIG_PACKET) += packet/ + obj-$(CONFIG_NET_KEY) += key/ + obj-$(CONFIG_BRIDGE) += bridge/ ++obj-$(CONFIG_NET_DEVLINK) += devlink/ + obj-$(CONFIG_NET_DSA) += dsa/ + obj-$(CONFIG_ATALK) += appletalk/ + obj-$(CONFIG_X25) += x25/ +diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c +index f1741fbfb6178..98a624f32b946 100644 +--- a/net/batman-adv/bat_v_elp.c ++++ b/net/batman-adv/bat_v_elp.c +@@ -506,7 +506,7 @@ int batadv_v_elp_packet_recv(struct sk_buff *skb, + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + struct batadv_elp_packet *elp_packet; + struct batadv_hard_iface *primary_if; +- struct ethhdr *ethhdr = (struct ethhdr *)skb_mac_header(skb); ++ struct ethhdr *ethhdr; + bool res; + int ret = NET_RX_DROP; + +@@ -514,6 +514,7 @@ int batadv_v_elp_packet_recv(struct sk_buff *skb, + if (!res) + goto free_skb; + ++ ethhdr = eth_hdr(skb); + if (batadv_is_my_mac(bat_priv, ethhdr->h_source)) + goto free_skb; + +diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c +index 033639df96d85..9f4815f4c8e89 100644 +--- a/net/batman-adv/bat_v_ogm.c ++++ b/net/batman-adv/bat_v_ogm.c +@@ -124,8 +124,10 @@ static void batadv_v_ogm_send_to_if(struct sk_buff *skb, + { + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + +- if (hard_iface->if_status != BATADV_IF_ACTIVE) ++ if (hard_iface->if_status != BATADV_IF_ACTIVE) { ++ kfree_skb(skb); + return; ++ } + + batadv_inc_counter(bat_priv, BATADV_CNT_MGMT_TX); + batadv_add_counter(bat_priv, BATADV_CNT_MGMT_TX_BYTES, +@@ -986,7 +988,7 @@ int batadv_v_ogm_packet_recv(struct sk_buff *skb, + { + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + struct batadv_ogm2_packet *ogm_packet; +- struct ethhdr *ethhdr = eth_hdr(skb); ++ struct ethhdr *ethhdr; + int ogm_offset; + u8 *packet_pos; + int ret = NET_RX_DROP; +@@ -1000,6 +1002,7 @@ int batadv_v_ogm_packet_recv(struct sk_buff *skb, + if (!batadv_check_management_packet(skb, if_incoming, BATADV_OGM2_HLEN)) + goto free_skb; + ++ ethhdr = eth_hdr(skb); + if (batadv_is_my_mac(bat_priv, ethhdr->h_source)) + goto free_skb; + +diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c +index 41c1ad33d009f..24c9c0c3f3166 100644 +--- a/net/batman-adv/hard-interface.c ++++ b/net/batman-adv/hard-interface.c +@@ -630,7 +630,19 @@ out: + */ + void batadv_update_min_mtu(struct net_device *soft_iface) + { +- soft_iface->mtu = batadv_hardif_min_mtu(soft_iface); ++ struct batadv_priv *bat_priv = netdev_priv(soft_iface); ++ int limit_mtu; ++ int mtu; ++ ++ mtu = batadv_hardif_min_mtu(soft_iface); ++ ++ if (bat_priv->mtu_set_by_user) ++ limit_mtu = bat_priv->mtu_set_by_user; ++ else ++ limit_mtu = ETH_DATA_LEN; ++ ++ mtu = min(mtu, limit_mtu); ++ dev_set_mtu(soft_iface, mtu); + + /* Check if the local translate table should be cleaned up to match a + * new (and smaller) MTU. +diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c +index a5e4a4e976cf3..86e0664e0511b 100644 +--- a/net/batman-adv/netlink.c ++++ b/net/batman-adv/netlink.c +@@ -495,7 +495,10 @@ static int batadv_netlink_set_mesh(struct sk_buff *skb, struct genl_info *info) + attr = info->attrs[BATADV_ATTR_FRAGMENTATION_ENABLED]; + + atomic_set(&bat_priv->fragmentation, !!nla_get_u8(attr)); ++ ++ rtnl_lock(); + batadv_update_min_mtu(bat_priv->soft_iface); ++ rtnl_unlock(); + } + + if (info->attrs[BATADV_ATTR_GW_BANDWIDTH_DOWN]) { +diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c +index 0f5c0679b55a2..38d411a52f331 100644 +--- a/net/batman-adv/soft-interface.c ++++ b/net/batman-adv/soft-interface.c +@@ -154,11 +154,14 @@ static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) + + static int batadv_interface_change_mtu(struct net_device *dev, int new_mtu) + { ++ struct batadv_priv *bat_priv = netdev_priv(dev); ++ + /* check ranges */ + if (new_mtu < 68 || new_mtu > batadv_hardif_min_mtu(dev)) + return -EINVAL; + + dev->mtu = new_mtu; ++ bat_priv->mtu_set_by_user = new_mtu; + + return 0; + } +diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c +index 01d30c1e412c7..5d8cee74772fe 100644 +--- a/net/batman-adv/translation-table.c ++++ b/net/batman-adv/translation-table.c +@@ -774,7 +774,6 @@ check_roaming: + if (roamed_back) { + batadv_tt_global_free(bat_priv, tt_global, + "Roaming canceled"); +- tt_global = NULL; + } else { + /* The global entry has to be marked as ROAMING and + * has to be kept for consistency purpose +diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h +index 758cd797a063b..76791815b26ba 100644 +--- a/net/batman-adv/types.h ++++ b/net/batman-adv/types.h +@@ -1546,6 +1546,12 @@ struct batadv_priv { + /** @soft_iface: net device which holds this struct as private data */ + struct net_device *soft_iface; + ++ /** ++ * @mtu_set_by_user: MTU was set once by user ++ * protected by rtnl_lock ++ */ ++ int mtu_set_by_user; ++ + /** + * @bat_counters: mesh internal traffic statistic counters (see + * batadv_counters) +diff --git a/net/can/isotp.c b/net/can/isotp.c +index b3c2a49b189cc..8c97f4061ffd7 100644 +--- a/net/can/isotp.c ++++ b/net/can/isotp.c +@@ -175,12 +175,6 @@ static bool isotp_register_rxid(struct isotp_sock *so) + return (isotp_bc_flags(so) == 0); + } + +-static bool isotp_register_txecho(struct isotp_sock *so) +-{ +- /* all modes but SF_BROADCAST register for tx echo skbs */ +- return (isotp_bc_flags(so) != CAN_ISOTP_SF_BROADCAST); +-} +- + static enum hrtimer_restart isotp_rx_timer_handler(struct hrtimer *hrtimer) + { + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, +@@ -1176,7 +1170,7 @@ static int isotp_release(struct socket *sock) + lock_sock(sk); + + /* remove current filters & unregister */ +- if (so->bound && isotp_register_txecho(so)) { ++ if (so->bound) { + if (so->ifindex) { + struct net_device *dev; + +@@ -1293,14 +1287,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) + can_rx_register(net, dev, rx_id, SINGLE_MASK(rx_id), + isotp_rcv, sk, "isotp", sk); + +- if (isotp_register_txecho(so)) { +- /* no consecutive frame echo skb in flight */ +- so->cfecho = 0; ++ /* no consecutive frame echo skb in flight */ ++ so->cfecho = 0; + +- /* register for echo skb's */ +- can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id), +- isotp_rcv_echo, sk, "isotpe", sk); +- } ++ /* register for echo skb's */ ++ can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id), ++ isotp_rcv_echo, sk, "isotpe", sk); + + dev_put(dev); + +@@ -1521,7 +1513,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, + case NETDEV_UNREGISTER: + lock_sock(sk); + /* remove current filters & unregister */ +- if (so->bound && isotp_register_txecho(so)) { ++ if (so->bound) { + if (isotp_register_rxid(so)) + can_rx_unregister(dev_net(dev), dev, so->rxid, + SINGLE_MASK(so->rxid), +diff --git a/net/can/raw.c b/net/can/raw.c +index 4abab2c3011a3..8c104339d538d 100644 +--- a/net/can/raw.c ++++ b/net/can/raw.c +@@ -84,6 +84,8 @@ struct raw_sock { + struct sock sk; + int bound; + int ifindex; ++ struct net_device *dev; ++ netdevice_tracker dev_tracker; + struct list_head notifier; + int loopback; + int recv_own_msgs; +@@ -277,21 +279,24 @@ static void raw_notify(struct raw_sock *ro, unsigned long msg, + if (!net_eq(dev_net(dev), sock_net(sk))) + return; + +- if (ro->ifindex != dev->ifindex) ++ if (ro->dev != dev) + return; + + switch (msg) { + case NETDEV_UNREGISTER: + lock_sock(sk); + /* remove current filters & unregister */ +- if (ro->bound) ++ if (ro->bound) { + raw_disable_allfilters(dev_net(dev), dev, sk); ++ netdev_put(dev, &ro->dev_tracker); ++ } + + if (ro->count > 1) + kfree(ro->filter); + + ro->ifindex = 0; + ro->bound = 0; ++ ro->dev = NULL; + ro->count = 0; + release_sock(sk); + +@@ -337,6 +342,7 @@ static int raw_init(struct sock *sk) + + ro->bound = 0; + ro->ifindex = 0; ++ ro->dev = NULL; + + /* set default filter to single entry dfilter */ + ro->dfilter.can_id = 0; +@@ -383,18 +389,14 @@ static int raw_release(struct socket *sock) + list_del(&ro->notifier); + spin_unlock(&raw_notifier_lock); + ++ rtnl_lock(); + lock_sock(sk); + + /* remove current filters & unregister */ + if (ro->bound) { +- if (ro->ifindex) { +- struct net_device *dev; +- +- dev = dev_get_by_index(sock_net(sk), ro->ifindex); +- if (dev) { +- raw_disable_allfilters(dev_net(dev), dev, sk); +- dev_put(dev); +- } ++ if (ro->dev) { ++ raw_disable_allfilters(dev_net(ro->dev), ro->dev, sk); ++ netdev_put(ro->dev, &ro->dev_tracker); + } else { + raw_disable_allfilters(sock_net(sk), NULL, sk); + } +@@ -405,6 +407,7 @@ static int raw_release(struct socket *sock) + + ro->ifindex = 0; + ro->bound = 0; ++ ro->dev = NULL; + ro->count = 0; + free_percpu(ro->uniq); + +@@ -412,6 +415,8 @@ static int raw_release(struct socket *sock) + sock->sk = NULL; + + release_sock(sk); ++ rtnl_unlock(); ++ + sock_put(sk); + + return 0; +@@ -422,6 +427,7 @@ static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len) + struct sockaddr_can *addr = (struct sockaddr_can *)uaddr; + struct sock *sk = sock->sk; + struct raw_sock *ro = raw_sk(sk); ++ struct net_device *dev = NULL; + int ifindex; + int err = 0; + int notify_enetdown = 0; +@@ -431,24 +437,23 @@ static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len) + if (addr->can_family != AF_CAN) + return -EINVAL; + ++ rtnl_lock(); + lock_sock(sk); + + if (ro->bound && addr->can_ifindex == ro->ifindex) + goto out; + + if (addr->can_ifindex) { +- struct net_device *dev; +- + dev = dev_get_by_index(sock_net(sk), addr->can_ifindex); + if (!dev) { + err = -ENODEV; + goto out; + } + if (dev->type != ARPHRD_CAN) { +- dev_put(dev); + err = -ENODEV; +- goto out; ++ goto out_put_dev; + } ++ + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; + +@@ -456,7 +461,9 @@ static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len) + + /* filters set by default/setsockopt */ + err = raw_enable_allfilters(sock_net(sk), dev, sk); +- dev_put(dev); ++ if (err) ++ goto out_put_dev; ++ + } else { + ifindex = 0; + +@@ -467,26 +474,30 @@ static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len) + if (!err) { + if (ro->bound) { + /* unregister old filters */ +- if (ro->ifindex) { +- struct net_device *dev; +- +- dev = dev_get_by_index(sock_net(sk), +- ro->ifindex); +- if (dev) { +- raw_disable_allfilters(dev_net(dev), +- dev, sk); +- dev_put(dev); +- } ++ if (ro->dev) { ++ raw_disable_allfilters(dev_net(ro->dev), ++ ro->dev, sk); ++ /* drop reference to old ro->dev */ ++ netdev_put(ro->dev, &ro->dev_tracker); + } else { + raw_disable_allfilters(sock_net(sk), NULL, sk); + } + } + ro->ifindex = ifindex; + ro->bound = 1; ++ /* bind() ok -> hold a reference for new ro->dev */ ++ ro->dev = dev; ++ if (ro->dev) ++ netdev_hold(ro->dev, &ro->dev_tracker, GFP_KERNEL); + } + +- out: ++out_put_dev: ++ /* remove potential reference from dev_get_by_index() */ ++ if (dev) ++ dev_put(dev); ++out: + release_sock(sk); ++ rtnl_unlock(); + + if (notify_enetdown) { + sk->sk_err = ENETDOWN; +@@ -552,9 +563,9 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, + rtnl_lock(); + lock_sock(sk); + +- if (ro->bound && ro->ifindex) { +- dev = dev_get_by_index(sock_net(sk), ro->ifindex); +- if (!dev) { ++ dev = ro->dev; ++ if (ro->bound && dev) { ++ if (dev->reg_state != NETREG_REGISTERED) { + if (count > 1) + kfree(filter); + err = -ENODEV; +@@ -595,7 +606,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, + ro->count = count; + + out_fil: +- dev_put(dev); + release_sock(sk); + rtnl_unlock(); + +@@ -613,9 +623,9 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, + rtnl_lock(); + lock_sock(sk); + +- if (ro->bound && ro->ifindex) { +- dev = dev_get_by_index(sock_net(sk), ro->ifindex); +- if (!dev) { ++ dev = ro->dev; ++ if (ro->bound && dev) { ++ if (dev->reg_state != NETREG_REGISTERED) { + err = -ENODEV; + goto out_err; + } +@@ -639,7 +649,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, + ro->err_mask = err_mask; + + out_err: +- dev_put(dev); + release_sock(sk); + rtnl_unlock(); + +diff --git a/net/core/Makefile b/net/core/Makefile +index 5857cec87b839..10edd66a8a372 100644 +--- a/net/core/Makefile ++++ b/net/core/Makefile +@@ -33,7 +33,6 @@ obj-$(CONFIG_LWTUNNEL) += lwtunnel.o + obj-$(CONFIG_LWTUNNEL_BPF) += lwt_bpf.o + obj-$(CONFIG_DST_CACHE) += dst_cache.o + obj-$(CONFIG_HWBM) += hwbm.o +-obj-$(CONFIG_NET_DEVLINK) += devlink.o + obj-$(CONFIG_GRO_CELLS) += gro_cells.o + obj-$(CONFIG_FAILOVER) += failover.o + obj-$(CONFIG_NET_SOCK_MSG) += skmsg.o +diff --git a/net/core/devlink.c b/net/core/devlink.c +deleted file mode 100644 +index 5a4a4b34ac15c..0000000000000 +--- a/net/core/devlink.c ++++ /dev/null +@@ -1,12547 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * net/core/devlink.c - Network physical/parent device Netlink interface +- * +- * Heavily inspired by net/wireless/ +- * Copyright (c) 2016 Mellanox Technologies. All rights reserved. +- * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com> +- */ +- +-#include <linux/etherdevice.h> +-#include <linux/kernel.h> +-#include <linux/module.h> +-#include <linux/types.h> +-#include <linux/slab.h> +-#include <linux/gfp.h> +-#include <linux/device.h> +-#include <linux/list.h> +-#include <linux/netdevice.h> +-#include <linux/spinlock.h> +-#include <linux/refcount.h> +-#include <linux/workqueue.h> +-#include <linux/u64_stats_sync.h> +-#include <linux/timekeeping.h> +-#include <rdma/ib_verbs.h> +-#include <net/netlink.h> +-#include <net/genetlink.h> +-#include <net/rtnetlink.h> +-#include <net/net_namespace.h> +-#include <net/sock.h> +-#include <net/devlink.h> +-#define CREATE_TRACE_POINTS +-#include <trace/events/devlink.h> +- +-#define DEVLINK_RELOAD_STATS_ARRAY_SIZE \ +- (__DEVLINK_RELOAD_LIMIT_MAX * __DEVLINK_RELOAD_ACTION_MAX) +- +-struct devlink_dev_stats { +- u32 reload_stats[DEVLINK_RELOAD_STATS_ARRAY_SIZE]; +- u32 remote_reload_stats[DEVLINK_RELOAD_STATS_ARRAY_SIZE]; +-}; +- +-struct devlink { +- u32 index; +- struct list_head port_list; +- struct list_head rate_list; +- struct list_head sb_list; +- struct list_head dpipe_table_list; +- struct list_head resource_list; +- struct list_head param_list; +- struct list_head region_list; +- struct list_head reporter_list; +- struct mutex reporters_lock; /* protects reporter_list */ +- struct devlink_dpipe_headers *dpipe_headers; +- struct list_head trap_list; +- struct list_head trap_group_list; +- struct list_head trap_policer_list; +- struct list_head linecard_list; +- struct mutex linecards_lock; /* protects linecard_list */ +- const struct devlink_ops *ops; +- u64 features; +- struct xarray snapshot_ids; +- struct devlink_dev_stats stats; +- struct device *dev; +- possible_net_t _net; +- /* Serializes access to devlink instance specific objects such as +- * port, sb, dpipe, resource, params, region, traps and more. +- */ +- struct mutex lock; +- struct lock_class_key lock_key; +- u8 reload_failed:1; +- refcount_t refcount; +- struct completion comp; +- struct rcu_head rcu; +- char priv[] __aligned(NETDEV_ALIGN); +-}; +- +-struct devlink_linecard_ops; +-struct devlink_linecard_type; +- +-struct devlink_linecard { +- struct list_head list; +- struct devlink *devlink; +- unsigned int index; +- refcount_t refcount; +- const struct devlink_linecard_ops *ops; +- void *priv; +- enum devlink_linecard_state state; +- struct mutex state_lock; /* Protects state */ +- const char *type; +- struct devlink_linecard_type *types; +- unsigned int types_count; +- struct devlink *nested_devlink; +-}; +- +-/** +- * struct devlink_resource - devlink resource +- * @name: name of the resource +- * @id: id, per devlink instance +- * @size: size of the resource +- * @size_new: updated size of the resource, reload is needed +- * @size_valid: valid in case the total size of the resource is valid +- * including its children +- * @parent: parent resource +- * @size_params: size parameters +- * @list: parent list +- * @resource_list: list of child resources +- * @occ_get: occupancy getter callback +- * @occ_get_priv: occupancy getter callback priv +- */ +-struct devlink_resource { +- const char *name; +- u64 id; +- u64 size; +- u64 size_new; +- bool size_valid; +- struct devlink_resource *parent; +- struct devlink_resource_size_params size_params; +- struct list_head list; +- struct list_head resource_list; +- devlink_resource_occ_get_t *occ_get; +- void *occ_get_priv; +-}; +- +-void *devlink_priv(struct devlink *devlink) +-{ +- return &devlink->priv; +-} +-EXPORT_SYMBOL_GPL(devlink_priv); +- +-struct devlink *priv_to_devlink(void *priv) +-{ +- return container_of(priv, struct devlink, priv); +-} +-EXPORT_SYMBOL_GPL(priv_to_devlink); +- +-struct device *devlink_to_dev(const struct devlink *devlink) +-{ +- return devlink->dev; +-} +-EXPORT_SYMBOL_GPL(devlink_to_dev); +- +-static struct devlink_dpipe_field devlink_dpipe_fields_ethernet[] = { +- { +- .name = "destination mac", +- .id = DEVLINK_DPIPE_FIELD_ETHERNET_DST_MAC, +- .bitwidth = 48, +- }, +-}; +- +-struct devlink_dpipe_header devlink_dpipe_header_ethernet = { +- .name = "ethernet", +- .id = DEVLINK_DPIPE_HEADER_ETHERNET, +- .fields = devlink_dpipe_fields_ethernet, +- .fields_count = ARRAY_SIZE(devlink_dpipe_fields_ethernet), +- .global = true, +-}; +-EXPORT_SYMBOL_GPL(devlink_dpipe_header_ethernet); +- +-static struct devlink_dpipe_field devlink_dpipe_fields_ipv4[] = { +- { +- .name = "destination ip", +- .id = DEVLINK_DPIPE_FIELD_IPV4_DST_IP, +- .bitwidth = 32, +- }, +-}; +- +-struct devlink_dpipe_header devlink_dpipe_header_ipv4 = { +- .name = "ipv4", +- .id = DEVLINK_DPIPE_HEADER_IPV4, +- .fields = devlink_dpipe_fields_ipv4, +- .fields_count = ARRAY_SIZE(devlink_dpipe_fields_ipv4), +- .global = true, +-}; +-EXPORT_SYMBOL_GPL(devlink_dpipe_header_ipv4); +- +-static struct devlink_dpipe_field devlink_dpipe_fields_ipv6[] = { +- { +- .name = "destination ip", +- .id = DEVLINK_DPIPE_FIELD_IPV6_DST_IP, +- .bitwidth = 128, +- }, +-}; +- +-struct devlink_dpipe_header devlink_dpipe_header_ipv6 = { +- .name = "ipv6", +- .id = DEVLINK_DPIPE_HEADER_IPV6, +- .fields = devlink_dpipe_fields_ipv6, +- .fields_count = ARRAY_SIZE(devlink_dpipe_fields_ipv6), +- .global = true, +-}; +-EXPORT_SYMBOL_GPL(devlink_dpipe_header_ipv6); +- +-EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_hwmsg); +-EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_hwerr); +-EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_trap_report); +- +-static const struct nla_policy devlink_function_nl_policy[DEVLINK_PORT_FUNCTION_ATTR_MAX + 1] = { +- [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR] = { .type = NLA_BINARY }, +- [DEVLINK_PORT_FN_ATTR_STATE] = +- NLA_POLICY_RANGE(NLA_U8, DEVLINK_PORT_FN_STATE_INACTIVE, +- DEVLINK_PORT_FN_STATE_ACTIVE), +-}; +- +-static const struct nla_policy devlink_selftest_nl_policy[DEVLINK_ATTR_SELFTEST_ID_MAX + 1] = { +- [DEVLINK_ATTR_SELFTEST_ID_FLASH] = { .type = NLA_FLAG }, +-}; +- +-static DEFINE_XARRAY_FLAGS(devlinks, XA_FLAGS_ALLOC); +-#define DEVLINK_REGISTERED XA_MARK_1 +-#define DEVLINK_UNREGISTERING XA_MARK_2 +- +-/* devlink instances are open to the access from the user space after +- * devlink_register() call. Such logical barrier allows us to have certain +- * expectations related to locking. +- * +- * Before *_register() - we are in initialization stage and no parallel +- * access possible to the devlink instance. All drivers perform that phase +- * by implicitly holding device_lock. +- * +- * After *_register() - users and driver can access devlink instance at +- * the same time. +- */ +-#define ASSERT_DEVLINK_REGISTERED(d) \ +- WARN_ON_ONCE(!xa_get_mark(&devlinks, (d)->index, DEVLINK_REGISTERED)) +-#define ASSERT_DEVLINK_NOT_REGISTERED(d) \ +- WARN_ON_ONCE(xa_get_mark(&devlinks, (d)->index, DEVLINK_REGISTERED)) +- +-struct net *devlink_net(const struct devlink *devlink) +-{ +- return read_pnet(&devlink->_net); +-} +-EXPORT_SYMBOL_GPL(devlink_net); +- +-static void __devlink_put_rcu(struct rcu_head *head) +-{ +- struct devlink *devlink = container_of(head, struct devlink, rcu); +- +- complete(&devlink->comp); +-} +- +-void devlink_put(struct devlink *devlink) +-{ +- if (refcount_dec_and_test(&devlink->refcount)) +- /* Make sure unregister operation that may await the completion +- * is unblocked only after all users are after the end of +- * RCU grace period. +- */ +- call_rcu(&devlink->rcu, __devlink_put_rcu); +-} +- +-struct devlink *__must_check devlink_try_get(struct devlink *devlink) +-{ +- if (refcount_inc_not_zero(&devlink->refcount)) +- return devlink; +- return NULL; +-} +- +-void devl_assert_locked(struct devlink *devlink) +-{ +- lockdep_assert_held(&devlink->lock); +-} +-EXPORT_SYMBOL_GPL(devl_assert_locked); +- +-#ifdef CONFIG_LOCKDEP +-/* For use in conjunction with LOCKDEP only e.g. rcu_dereference_protected() */ +-bool devl_lock_is_held(struct devlink *devlink) +-{ +- return lockdep_is_held(&devlink->lock); +-} +-EXPORT_SYMBOL_GPL(devl_lock_is_held); +-#endif +- +-void devl_lock(struct devlink *devlink) +-{ +- mutex_lock(&devlink->lock); +-} +-EXPORT_SYMBOL_GPL(devl_lock); +- +-int devl_trylock(struct devlink *devlink) +-{ +- return mutex_trylock(&devlink->lock); +-} +-EXPORT_SYMBOL_GPL(devl_trylock); +- +-void devl_unlock(struct devlink *devlink) +-{ +- mutex_unlock(&devlink->lock); +-} +-EXPORT_SYMBOL_GPL(devl_unlock); +- +-static struct devlink * +-devlinks_xa_find_get(struct net *net, unsigned long *indexp, xa_mark_t filter, +- void * (*xa_find_fn)(struct xarray *, unsigned long *, +- unsigned long, xa_mark_t)) +-{ +- struct devlink *devlink; +- +- rcu_read_lock(); +-retry: +- devlink = xa_find_fn(&devlinks, indexp, ULONG_MAX, DEVLINK_REGISTERED); +- if (!devlink) +- goto unlock; +- +- /* In case devlink_unregister() was already called and "unregistering" +- * mark was set, do not allow to get a devlink reference here. +- * This prevents live-lock of devlink_unregister() wait for completion. +- */ +- if (xa_get_mark(&devlinks, *indexp, DEVLINK_UNREGISTERING)) +- goto retry; +- +- /* For a possible retry, the xa_find_after() should be always used */ +- xa_find_fn = xa_find_after; +- if (!devlink_try_get(devlink)) +- goto retry; +- if (!net_eq(devlink_net(devlink), net)) { +- devlink_put(devlink); +- goto retry; +- } +-unlock: +- rcu_read_unlock(); +- return devlink; +-} +- +-static struct devlink *devlinks_xa_find_get_first(struct net *net, +- unsigned long *indexp, +- xa_mark_t filter) +-{ +- return devlinks_xa_find_get(net, indexp, filter, xa_find); +-} +- +-static struct devlink *devlinks_xa_find_get_next(struct net *net, +- unsigned long *indexp, +- xa_mark_t filter) +-{ +- return devlinks_xa_find_get(net, indexp, filter, xa_find_after); +-} +- +-/* Iterate over devlink pointers which were possible to get reference to. +- * devlink_put() needs to be called for each iterated devlink pointer +- * in loop body in order to release the reference. +- */ +-#define devlinks_xa_for_each_get(net, index, devlink, filter) \ +- for (index = 0, \ +- devlink = devlinks_xa_find_get_first(net, &index, filter); \ +- devlink; devlink = devlinks_xa_find_get_next(net, &index, filter)) +- +-#define devlinks_xa_for_each_registered_get(net, index, devlink) \ +- devlinks_xa_for_each_get(net, index, devlink, DEVLINK_REGISTERED) +- +-static struct devlink *devlink_get_from_attrs(struct net *net, +- struct nlattr **attrs) +-{ +- struct devlink *devlink; +- unsigned long index; +- char *busname; +- char *devname; +- +- if (!attrs[DEVLINK_ATTR_BUS_NAME] || !attrs[DEVLINK_ATTR_DEV_NAME]) +- return ERR_PTR(-EINVAL); +- +- busname = nla_data(attrs[DEVLINK_ATTR_BUS_NAME]); +- devname = nla_data(attrs[DEVLINK_ATTR_DEV_NAME]); +- +- devlinks_xa_for_each_registered_get(net, index, devlink) { +- if (strcmp(devlink->dev->bus->name, busname) == 0 && +- strcmp(dev_name(devlink->dev), devname) == 0) +- return devlink; +- devlink_put(devlink); +- } +- +- return ERR_PTR(-ENODEV); +-} +- +-#define ASSERT_DEVLINK_PORT_REGISTERED(devlink_port) \ +- WARN_ON_ONCE(!(devlink_port)->registered) +-#define ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port) \ +- WARN_ON_ONCE((devlink_port)->registered) +-#define ASSERT_DEVLINK_PORT_INITIALIZED(devlink_port) \ +- WARN_ON_ONCE(!(devlink_port)->initialized) +- +-static struct devlink_port *devlink_port_get_by_index(struct devlink *devlink, +- unsigned int port_index) +-{ +- struct devlink_port *devlink_port; +- +- list_for_each_entry(devlink_port, &devlink->port_list, list) { +- if (devlink_port->index == port_index) +- return devlink_port; +- } +- return NULL; +-} +- +-static bool devlink_port_index_exists(struct devlink *devlink, +- unsigned int port_index) +-{ +- return devlink_port_get_by_index(devlink, port_index); +-} +- +-static struct devlink_port *devlink_port_get_from_attrs(struct devlink *devlink, +- struct nlattr **attrs) +-{ +- if (attrs[DEVLINK_ATTR_PORT_INDEX]) { +- u32 port_index = nla_get_u32(attrs[DEVLINK_ATTR_PORT_INDEX]); +- struct devlink_port *devlink_port; +- +- devlink_port = devlink_port_get_by_index(devlink, port_index); +- if (!devlink_port) +- return ERR_PTR(-ENODEV); +- return devlink_port; +- } +- return ERR_PTR(-EINVAL); +-} +- +-static struct devlink_port *devlink_port_get_from_info(struct devlink *devlink, +- struct genl_info *info) +-{ +- return devlink_port_get_from_attrs(devlink, info->attrs); +-} +- +-static inline bool +-devlink_rate_is_leaf(struct devlink_rate *devlink_rate) +-{ +- return devlink_rate->type == DEVLINK_RATE_TYPE_LEAF; +-} +- +-static inline bool +-devlink_rate_is_node(struct devlink_rate *devlink_rate) +-{ +- return devlink_rate->type == DEVLINK_RATE_TYPE_NODE; +-} +- +-static struct devlink_rate * +-devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info) +-{ +- struct devlink_rate *devlink_rate; +- struct devlink_port *devlink_port; +- +- devlink_port = devlink_port_get_from_attrs(devlink, info->attrs); +- if (IS_ERR(devlink_port)) +- return ERR_CAST(devlink_port); +- devlink_rate = devlink_port->devlink_rate; +- return devlink_rate ?: ERR_PTR(-ENODEV); +-} +- +-static struct devlink_rate * +-devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) +-{ +- static struct devlink_rate *devlink_rate; +- +- list_for_each_entry(devlink_rate, &devlink->rate_list, list) { +- if (devlink_rate_is_node(devlink_rate) && +- !strcmp(node_name, devlink_rate->name)) +- return devlink_rate; +- } +- return ERR_PTR(-ENODEV); +-} +- +-static struct devlink_rate * +-devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) +-{ +- const char *rate_node_name; +- size_t len; +- +- if (!attrs[DEVLINK_ATTR_RATE_NODE_NAME]) +- return ERR_PTR(-EINVAL); +- rate_node_name = nla_data(attrs[DEVLINK_ATTR_RATE_NODE_NAME]); +- len = strlen(rate_node_name); +- /* Name cannot be empty or decimal number */ +- if (!len || strspn(rate_node_name, "0123456789") == len) +- return ERR_PTR(-EINVAL); +- +- return devlink_rate_node_get_by_name(devlink, rate_node_name); +-} +- +-static struct devlink_rate * +-devlink_rate_node_get_from_info(struct devlink *devlink, struct genl_info *info) +-{ +- return devlink_rate_node_get_from_attrs(devlink, info->attrs); +-} +- +-static struct devlink_rate * +-devlink_rate_get_from_info(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); +- else +- return ERR_PTR(-EINVAL); +-} +- +-static struct devlink_linecard * +-devlink_linecard_get_by_index(struct devlink *devlink, +- unsigned int linecard_index) +-{ +- struct devlink_linecard *devlink_linecard; +- +- list_for_each_entry(devlink_linecard, &devlink->linecard_list, list) { +- if (devlink_linecard->index == linecard_index) +- return devlink_linecard; +- } +- return NULL; +-} +- +-static bool devlink_linecard_index_exists(struct devlink *devlink, +- unsigned int linecard_index) +-{ +- return devlink_linecard_get_by_index(devlink, linecard_index); +-} +- +-static struct devlink_linecard * +-devlink_linecard_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) +-{ +- if (attrs[DEVLINK_ATTR_LINECARD_INDEX]) { +- u32 linecard_index = nla_get_u32(attrs[DEVLINK_ATTR_LINECARD_INDEX]); +- struct devlink_linecard *linecard; +- +- mutex_lock(&devlink->linecards_lock); +- linecard = devlink_linecard_get_by_index(devlink, linecard_index); +- if (linecard) +- refcount_inc(&linecard->refcount); +- mutex_unlock(&devlink->linecards_lock); +- if (!linecard) +- return ERR_PTR(-ENODEV); +- return linecard; +- } +- return ERR_PTR(-EINVAL); +-} +- +-static struct devlink_linecard * +-devlink_linecard_get_from_info(struct devlink *devlink, struct genl_info *info) +-{ +- return devlink_linecard_get_from_attrs(devlink, info->attrs); +-} +- +-static void devlink_linecard_put(struct devlink_linecard *linecard) +-{ +- if (refcount_dec_and_test(&linecard->refcount)) { +- mutex_destroy(&linecard->state_lock); +- kfree(linecard); +- } +-} +- +-struct devlink_sb { +- struct list_head list; +- unsigned int index; +- u32 size; +- u16 ingress_pools_count; +- u16 egress_pools_count; +- u16 ingress_tc_count; +- u16 egress_tc_count; +-}; +- +-static u16 devlink_sb_pool_count(struct devlink_sb *devlink_sb) +-{ +- return devlink_sb->ingress_pools_count + devlink_sb->egress_pools_count; +-} +- +-static struct devlink_sb *devlink_sb_get_by_index(struct devlink *devlink, +- unsigned int sb_index) +-{ +- struct devlink_sb *devlink_sb; +- +- list_for_each_entry(devlink_sb, &devlink->sb_list, list) { +- if (devlink_sb->index == sb_index) +- return devlink_sb; +- } +- return NULL; +-} +- +-static bool devlink_sb_index_exists(struct devlink *devlink, +- unsigned int sb_index) +-{ +- return devlink_sb_get_by_index(devlink, sb_index); +-} +- +-static struct devlink_sb *devlink_sb_get_from_attrs(struct devlink *devlink, +- struct nlattr **attrs) +-{ +- if (attrs[DEVLINK_ATTR_SB_INDEX]) { +- u32 sb_index = nla_get_u32(attrs[DEVLINK_ATTR_SB_INDEX]); +- struct devlink_sb *devlink_sb; +- +- devlink_sb = devlink_sb_get_by_index(devlink, sb_index); +- if (!devlink_sb) +- return ERR_PTR(-ENODEV); +- return devlink_sb; +- } +- return ERR_PTR(-EINVAL); +-} +- +-static struct devlink_sb *devlink_sb_get_from_info(struct devlink *devlink, +- struct genl_info *info) +-{ +- return devlink_sb_get_from_attrs(devlink, info->attrs); +-} +- +-static int devlink_sb_pool_index_get_from_attrs(struct devlink_sb *devlink_sb, +- struct nlattr **attrs, +- u16 *p_pool_index) +-{ +- u16 val; +- +- if (!attrs[DEVLINK_ATTR_SB_POOL_INDEX]) +- return -EINVAL; +- +- val = nla_get_u16(attrs[DEVLINK_ATTR_SB_POOL_INDEX]); +- if (val >= devlink_sb_pool_count(devlink_sb)) +- return -EINVAL; +- *p_pool_index = val; +- return 0; +-} +- +-static int devlink_sb_pool_index_get_from_info(struct devlink_sb *devlink_sb, +- struct genl_info *info, +- u16 *p_pool_index) +-{ +- return devlink_sb_pool_index_get_from_attrs(devlink_sb, info->attrs, +- p_pool_index); +-} +- +-static int +-devlink_sb_pool_type_get_from_attrs(struct nlattr **attrs, +- enum devlink_sb_pool_type *p_pool_type) +-{ +- u8 val; +- +- if (!attrs[DEVLINK_ATTR_SB_POOL_TYPE]) +- return -EINVAL; +- +- val = nla_get_u8(attrs[DEVLINK_ATTR_SB_POOL_TYPE]); +- if (val != DEVLINK_SB_POOL_TYPE_INGRESS && +- val != DEVLINK_SB_POOL_TYPE_EGRESS) +- return -EINVAL; +- *p_pool_type = val; +- return 0; +-} +- +-static int +-devlink_sb_pool_type_get_from_info(struct genl_info *info, +- enum devlink_sb_pool_type *p_pool_type) +-{ +- return devlink_sb_pool_type_get_from_attrs(info->attrs, p_pool_type); +-} +- +-static int +-devlink_sb_th_type_get_from_attrs(struct nlattr **attrs, +- enum devlink_sb_threshold_type *p_th_type) +-{ +- u8 val; +- +- if (!attrs[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE]) +- return -EINVAL; +- +- val = nla_get_u8(attrs[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE]); +- if (val != DEVLINK_SB_THRESHOLD_TYPE_STATIC && +- val != DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC) +- return -EINVAL; +- *p_th_type = val; +- return 0; +-} +- +-static int +-devlink_sb_th_type_get_from_info(struct genl_info *info, +- enum devlink_sb_threshold_type *p_th_type) +-{ +- return devlink_sb_th_type_get_from_attrs(info->attrs, p_th_type); +-} +- +-static int +-devlink_sb_tc_index_get_from_attrs(struct devlink_sb *devlink_sb, +- struct nlattr **attrs, +- enum devlink_sb_pool_type pool_type, +- u16 *p_tc_index) +-{ +- u16 val; +- +- if (!attrs[DEVLINK_ATTR_SB_TC_INDEX]) +- return -EINVAL; +- +- val = nla_get_u16(attrs[DEVLINK_ATTR_SB_TC_INDEX]); +- if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS && +- val >= devlink_sb->ingress_tc_count) +- return -EINVAL; +- if (pool_type == DEVLINK_SB_POOL_TYPE_EGRESS && +- val >= devlink_sb->egress_tc_count) +- return -EINVAL; +- *p_tc_index = val; +- return 0; +-} +- +-static int +-devlink_sb_tc_index_get_from_info(struct devlink_sb *devlink_sb, +- struct genl_info *info, +- enum devlink_sb_pool_type pool_type, +- u16 *p_tc_index) +-{ +- return devlink_sb_tc_index_get_from_attrs(devlink_sb, info->attrs, +- pool_type, p_tc_index); +-} +- +-struct devlink_region { +- struct devlink *devlink; +- struct devlink_port *port; +- struct list_head list; +- union { +- const struct devlink_region_ops *ops; +- const struct devlink_port_region_ops *port_ops; +- }; +- struct mutex snapshot_lock; /* protects snapshot_list, +- * max_snapshots and cur_snapshots +- * consistency. +- */ +- struct list_head snapshot_list; +- u32 max_snapshots; +- u32 cur_snapshots; +- u64 size; +-}; +- +-struct devlink_snapshot { +- struct list_head list; +- struct devlink_region *region; +- u8 *data; +- u32 id; +-}; +- +-static struct devlink_region * +-devlink_region_get_by_name(struct devlink *devlink, const char *region_name) +-{ +- struct devlink_region *region; +- +- list_for_each_entry(region, &devlink->region_list, list) +- if (!strcmp(region->ops->name, region_name)) +- return region; +- +- return NULL; +-} +- +-static struct devlink_region * +-devlink_port_region_get_by_name(struct devlink_port *port, +- const char *region_name) +-{ +- struct devlink_region *region; +- +- list_for_each_entry(region, &port->region_list, list) +- if (!strcmp(region->ops->name, region_name)) +- return region; +- +- return NULL; +-} +- +-static struct devlink_snapshot * +-devlink_region_snapshot_get_by_id(struct devlink_region *region, u32 id) +-{ +- struct devlink_snapshot *snapshot; +- +- list_for_each_entry(snapshot, ®ion->snapshot_list, list) +- if (snapshot->id == id) +- return snapshot; +- +- return NULL; +-} +- +-#define DEVLINK_NL_FLAG_NEED_PORT BIT(0) +-#define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1) +-#define DEVLINK_NL_FLAG_NEED_RATE BIT(2) +-#define DEVLINK_NL_FLAG_NEED_RATE_NODE BIT(3) +-#define DEVLINK_NL_FLAG_NEED_LINECARD BIT(4) +- +-static int devlink_nl_pre_doit(const struct genl_ops *ops, +- struct sk_buff *skb, struct genl_info *info) +-{ +- struct devlink_linecard *linecard; +- struct devlink_port *devlink_port; +- struct devlink *devlink; +- int err; +- +- devlink = devlink_get_from_attrs(genl_info_net(info), info->attrs); +- if (IS_ERR(devlink)) +- return PTR_ERR(devlink); +- devl_lock(devlink); +- info->user_ptr[0] = devlink; +- if (ops->internal_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; +- } else if (ops->internal_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; +- } else if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_RATE) { +- struct devlink_rate *devlink_rate; +- +- devlink_rate = devlink_rate_get_from_info(devlink, info); +- if (IS_ERR(devlink_rate)) { +- err = PTR_ERR(devlink_rate); +- goto unlock; +- } +- info->user_ptr[1] = devlink_rate; +- } else if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_RATE_NODE) { +- struct devlink_rate *rate_node; +- +- rate_node = devlink_rate_node_get_from_info(devlink, info); +- if (IS_ERR(rate_node)) { +- err = PTR_ERR(rate_node); +- goto unlock; +- } +- info->user_ptr[1] = rate_node; +- } else if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_LINECARD) { +- linecard = devlink_linecard_get_from_info(devlink, info); +- if (IS_ERR(linecard)) { +- err = PTR_ERR(linecard); +- goto unlock; +- } +- info->user_ptr[1] = linecard; +- } +- return 0; +- +-unlock: +- devl_unlock(devlink); +- devlink_put(devlink); +- return err; +-} +- +-static void devlink_nl_post_doit(const struct genl_ops *ops, +- struct sk_buff *skb, struct genl_info *info) +-{ +- struct devlink_linecard *linecard; +- struct devlink *devlink; +- +- devlink = info->user_ptr[0]; +- if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_LINECARD) { +- linecard = info->user_ptr[1]; +- devlink_linecard_put(linecard); +- } +- devl_unlock(devlink); +- devlink_put(devlink); +-} +- +-static struct genl_family devlink_nl_family; +- +-enum devlink_multicast_groups { +- DEVLINK_MCGRP_CONFIG, +-}; +- +-static const struct genl_multicast_group devlink_nl_mcgrps[] = { +- [DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME }, +-}; +- +-static int devlink_nl_put_handle(struct sk_buff *msg, struct devlink *devlink) +-{ +- if (nla_put_string(msg, DEVLINK_ATTR_BUS_NAME, devlink->dev->bus->name)) +- return -EMSGSIZE; +- if (nla_put_string(msg, DEVLINK_ATTR_DEV_NAME, dev_name(devlink->dev))) +- return -EMSGSIZE; +- return 0; +-} +- +-static int devlink_nl_put_nested_handle(struct sk_buff *msg, struct devlink *devlink) +-{ +- struct nlattr *nested_attr; +- +- nested_attr = nla_nest_start(msg, DEVLINK_ATTR_NESTED_DEVLINK); +- if (!nested_attr) +- return -EMSGSIZE; +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- +- nla_nest_end(msg, nested_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, nested_attr); +- return -EMSGSIZE; +-} +- +-struct devlink_reload_combination { +- enum devlink_reload_action action; +- enum devlink_reload_limit limit; +-}; +- +-static const struct devlink_reload_combination devlink_reload_invalid_combinations[] = { +- { +- /* can't reinitialize driver with no down time */ +- .action = DEVLINK_RELOAD_ACTION_DRIVER_REINIT, +- .limit = DEVLINK_RELOAD_LIMIT_NO_RESET, +- }, +-}; +- +-static bool +-devlink_reload_combination_is_invalid(enum devlink_reload_action action, +- enum devlink_reload_limit limit) +-{ +- int i; +- +- for (i = 0; i < ARRAY_SIZE(devlink_reload_invalid_combinations); i++) +- if (devlink_reload_invalid_combinations[i].action == action && +- devlink_reload_invalid_combinations[i].limit == limit) +- return true; +- return false; +-} +- +-static bool +-devlink_reload_action_is_supported(struct devlink *devlink, enum devlink_reload_action action) +-{ +- return test_bit(action, &devlink->ops->reload_actions); +-} +- +-static bool +-devlink_reload_limit_is_supported(struct devlink *devlink, enum devlink_reload_limit limit) +-{ +- return test_bit(limit, &devlink->ops->reload_limits); +-} +- +-static int devlink_reload_stat_put(struct sk_buff *msg, +- enum devlink_reload_limit limit, u32 value) +-{ +- struct nlattr *reload_stats_entry; +- +- reload_stats_entry = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_STATS_ENTRY); +- if (!reload_stats_entry) +- return -EMSGSIZE; +- +- if (nla_put_u8(msg, DEVLINK_ATTR_RELOAD_STATS_LIMIT, limit) || +- nla_put_u32(msg, DEVLINK_ATTR_RELOAD_STATS_VALUE, value)) +- goto nla_put_failure; +- nla_nest_end(msg, reload_stats_entry); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, reload_stats_entry); +- return -EMSGSIZE; +-} +- +-static int devlink_reload_stats_put(struct sk_buff *msg, struct devlink *devlink, bool is_remote) +-{ +- struct nlattr *reload_stats_attr, *act_info, *act_stats; +- int i, j, stat_idx; +- u32 value; +- +- if (!is_remote) +- reload_stats_attr = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_STATS); +- else +- reload_stats_attr = nla_nest_start(msg, DEVLINK_ATTR_REMOTE_RELOAD_STATS); +- +- if (!reload_stats_attr) +- return -EMSGSIZE; +- +- for (i = 0; i <= DEVLINK_RELOAD_ACTION_MAX; i++) { +- if ((!is_remote && +- !devlink_reload_action_is_supported(devlink, i)) || +- i == DEVLINK_RELOAD_ACTION_UNSPEC) +- continue; +- act_info = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_ACTION_INFO); +- if (!act_info) +- goto nla_put_failure; +- +- if (nla_put_u8(msg, DEVLINK_ATTR_RELOAD_ACTION, i)) +- goto action_info_nest_cancel; +- act_stats = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_ACTION_STATS); +- if (!act_stats) +- goto action_info_nest_cancel; +- +- for (j = 0; j <= DEVLINK_RELOAD_LIMIT_MAX; j++) { +- /* Remote stats are shown even if not locally supported. +- * Stats of actions with unspecified limit are shown +- * though drivers don't need to register unspecified +- * limit. +- */ +- if ((!is_remote && j != DEVLINK_RELOAD_LIMIT_UNSPEC && +- !devlink_reload_limit_is_supported(devlink, j)) || +- devlink_reload_combination_is_invalid(i, j)) +- continue; +- +- stat_idx = j * __DEVLINK_RELOAD_ACTION_MAX + i; +- if (!is_remote) +- value = devlink->stats.reload_stats[stat_idx]; +- else +- value = devlink->stats.remote_reload_stats[stat_idx]; +- if (devlink_reload_stat_put(msg, j, value)) +- goto action_stats_nest_cancel; +- } +- nla_nest_end(msg, act_stats); +- nla_nest_end(msg, act_info); +- } +- nla_nest_end(msg, reload_stats_attr); +- return 0; +- +-action_stats_nest_cancel: +- nla_nest_cancel(msg, act_stats); +-action_info_nest_cancel: +- nla_nest_cancel(msg, act_info); +-nla_put_failure: +- nla_nest_cancel(msg, reload_stats_attr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_fill(struct sk_buff *msg, struct devlink *devlink, +- enum devlink_command cmd, u32 portid, +- u32 seq, int flags) +-{ +- struct nlattr *dev_stats; +- void *hdr; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- if (nla_put_u8(msg, DEVLINK_ATTR_RELOAD_FAILED, devlink->reload_failed)) +- goto nla_put_failure; +- +- dev_stats = nla_nest_start(msg, DEVLINK_ATTR_DEV_STATS); +- if (!dev_stats) +- goto nla_put_failure; +- +- if (devlink_reload_stats_put(msg, devlink, false)) +- goto dev_stats_nest_cancel; +- if (devlink_reload_stats_put(msg, devlink, true)) +- goto dev_stats_nest_cancel; +- +- nla_nest_end(msg, dev_stats); +- genlmsg_end(msg, hdr); +- return 0; +- +-dev_stats_nest_cancel: +- nla_nest_cancel(msg, dev_stats); +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static void devlink_notify(struct devlink *devlink, enum devlink_command cmd) +-{ +- struct sk_buff *msg; +- int err; +- +- WARN_ON(cmd != DEVLINK_CMD_NEW && cmd != DEVLINK_CMD_DEL); +- WARN_ON(!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)); +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_fill(msg, devlink, cmd, 0, 0, 0); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), +- msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static int devlink_nl_port_attrs_put(struct sk_buff *msg, +- struct devlink_port *devlink_port) +-{ +- struct devlink_port_attrs *attrs = &devlink_port->attrs; +- +- if (!devlink_port->attrs_set) +- return 0; +- if (attrs->lanes) { +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_LANES, attrs->lanes)) +- return -EMSGSIZE; +- } +- if (nla_put_u8(msg, DEVLINK_ATTR_PORT_SPLITTABLE, attrs->splittable)) +- return -EMSGSIZE; +- if (nla_put_u16(msg, DEVLINK_ATTR_PORT_FLAVOUR, attrs->flavour)) +- return -EMSGSIZE; +- switch (devlink_port->attrs.flavour) { +- case DEVLINK_PORT_FLAVOUR_PCI_PF: +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, +- attrs->pci_pf.controller) || +- nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_PF_NUMBER, attrs->pci_pf.pf)) +- return -EMSGSIZE; +- if (nla_put_u8(msg, DEVLINK_ATTR_PORT_EXTERNAL, attrs->pci_pf.external)) +- return -EMSGSIZE; +- break; +- case DEVLINK_PORT_FLAVOUR_PCI_VF: +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, +- attrs->pci_vf.controller) || +- nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_PF_NUMBER, attrs->pci_vf.pf) || +- nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_VF_NUMBER, attrs->pci_vf.vf)) +- return -EMSGSIZE; +- if (nla_put_u8(msg, DEVLINK_ATTR_PORT_EXTERNAL, attrs->pci_vf.external)) +- return -EMSGSIZE; +- break; +- case DEVLINK_PORT_FLAVOUR_PCI_SF: +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, +- attrs->pci_sf.controller) || +- nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_PF_NUMBER, +- attrs->pci_sf.pf) || +- nla_put_u32(msg, DEVLINK_ATTR_PORT_PCI_SF_NUMBER, +- attrs->pci_sf.sf)) +- return -EMSGSIZE; +- break; +- case DEVLINK_PORT_FLAVOUR_PHYSICAL: +- case DEVLINK_PORT_FLAVOUR_CPU: +- case DEVLINK_PORT_FLAVOUR_DSA: +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_NUMBER, +- attrs->phys.port_number)) +- return -EMSGSIZE; +- if (!attrs->split) +- return 0; +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_SPLIT_GROUP, +- attrs->phys.port_number)) +- return -EMSGSIZE; +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_SPLIT_SUBPORT_NUMBER, +- attrs->phys.split_subport_number)) +- return -EMSGSIZE; +- break; +- default: +- break; +- } +- return 0; +-} +- +-static int devlink_port_fn_hw_addr_fill(const struct devlink_ops *ops, +- struct devlink_port *port, +- struct sk_buff *msg, +- struct netlink_ext_ack *extack, +- bool *msg_updated) +-{ +- u8 hw_addr[MAX_ADDR_LEN]; +- int hw_addr_len; +- int err; +- +- if (!ops->port_function_hw_addr_get) +- return 0; +- +- err = ops->port_function_hw_addr_get(port, hw_addr, &hw_addr_len, +- extack); +- if (err) { +- if (err == -EOPNOTSUPP) +- return 0; +- return err; +- } +- err = nla_put(msg, DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR, hw_addr_len, hw_addr); +- if (err) +- return err; +- *msg_updated = true; +- 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, +- int flags, struct netlink_ext_ack *extack) +-{ +- struct devlink *devlink = devlink_rate->devlink; +- void *hdr; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- +- if (nla_put_u16(msg, DEVLINK_ATTR_RATE_TYPE, devlink_rate->type)) +- goto nla_put_failure; +- +- if (devlink_rate_is_leaf(devlink_rate)) { +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, +- devlink_rate->devlink_port->index)) +- goto nla_put_failure; +- } else if (devlink_rate_is_node(devlink_rate)) { +- if (nla_put_string(msg, DEVLINK_ATTR_RATE_NODE_NAME, +- devlink_rate->name)) +- goto nla_put_failure; +- } +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_RATE_TX_SHARE, +- devlink_rate->tx_share, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_RATE_TX_MAX, +- devlink_rate->tx_max, DEVLINK_ATTR_PAD)) +- 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; +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static bool +-devlink_port_fn_state_valid(enum devlink_port_fn_state state) +-{ +- return state == DEVLINK_PORT_FN_STATE_INACTIVE || +- state == DEVLINK_PORT_FN_STATE_ACTIVE; +-} +- +-static bool +-devlink_port_fn_opstate_valid(enum devlink_port_fn_opstate opstate) +-{ +- return opstate == DEVLINK_PORT_FN_OPSTATE_DETACHED || +- opstate == DEVLINK_PORT_FN_OPSTATE_ATTACHED; +-} +- +-static int devlink_port_fn_state_fill(const struct devlink_ops *ops, +- struct devlink_port *port, +- struct sk_buff *msg, +- struct netlink_ext_ack *extack, +- bool *msg_updated) +-{ +- enum devlink_port_fn_opstate opstate; +- enum devlink_port_fn_state state; +- int err; +- +- if (!ops->port_fn_state_get) +- return 0; +- +- err = ops->port_fn_state_get(port, &state, &opstate, extack); +- if (err) { +- if (err == -EOPNOTSUPP) +- return 0; +- return err; +- } +- if (!devlink_port_fn_state_valid(state)) { +- WARN_ON_ONCE(1); +- NL_SET_ERR_MSG_MOD(extack, "Invalid state read from driver"); +- return -EINVAL; +- } +- if (!devlink_port_fn_opstate_valid(opstate)) { +- WARN_ON_ONCE(1); +- NL_SET_ERR_MSG_MOD(extack, +- "Invalid operational state read from driver"); +- return -EINVAL; +- } +- if (nla_put_u8(msg, DEVLINK_PORT_FN_ATTR_STATE, state) || +- nla_put_u8(msg, DEVLINK_PORT_FN_ATTR_OPSTATE, opstate)) +- return -EMSGSIZE; +- *msg_updated = true; +- return 0; +-} +- +-static int +-devlink_nl_port_function_attrs_put(struct sk_buff *msg, struct devlink_port *port, +- struct netlink_ext_ack *extack) +-{ +- const struct devlink_ops *ops; +- struct nlattr *function_attr; +- bool msg_updated = false; +- int err; +- +- function_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_PORT_FUNCTION); +- if (!function_attr) +- return -EMSGSIZE; +- +- ops = port->devlink->ops; +- err = devlink_port_fn_hw_addr_fill(ops, port, msg, extack, +- &msg_updated); +- if (err) +- goto out; +- err = devlink_port_fn_state_fill(ops, port, msg, extack, &msg_updated); +-out: +- if (err || !msg_updated) +- nla_nest_cancel(msg, function_attr); +- else +- nla_nest_end(msg, function_attr); +- return err; +-} +- +-static int devlink_nl_port_fill(struct sk_buff *msg, +- struct devlink_port *devlink_port, +- enum devlink_command cmd, u32 portid, u32 seq, +- int flags, struct netlink_ext_ack *extack) +-{ +- struct devlink *devlink = devlink_port->devlink; +- void *hdr; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) +- goto nla_put_failure; +- +- /* Hold rtnl lock while accessing port's netdev attributes. */ +- rtnl_lock(); +- spin_lock_bh(&devlink_port->type_lock); +- if (nla_put_u16(msg, DEVLINK_ATTR_PORT_TYPE, devlink_port->type)) +- goto nla_put_failure_type_locked; +- if (devlink_port->desired_type != DEVLINK_PORT_TYPE_NOTSET && +- nla_put_u16(msg, DEVLINK_ATTR_PORT_DESIRED_TYPE, +- devlink_port->desired_type)) +- goto nla_put_failure_type_locked; +- if (devlink_port->type == DEVLINK_PORT_TYPE_ETH) { +- struct net *net = devlink_net(devlink_port->devlink); +- struct net_device *netdev = devlink_port->type_dev; +- +- if (netdev && net_eq(net, dev_net(netdev)) && +- (nla_put_u32(msg, DEVLINK_ATTR_PORT_NETDEV_IFINDEX, +- netdev->ifindex) || +- nla_put_string(msg, DEVLINK_ATTR_PORT_NETDEV_NAME, +- netdev->name))) +- goto nla_put_failure_type_locked; +- } +- if (devlink_port->type == DEVLINK_PORT_TYPE_IB) { +- struct ib_device *ibdev = devlink_port->type_dev; +- +- if (ibdev && +- nla_put_string(msg, DEVLINK_ATTR_PORT_IBDEV_NAME, +- ibdev->name)) +- goto nla_put_failure_type_locked; +- } +- spin_unlock_bh(&devlink_port->type_lock); +- rtnl_unlock(); +- if (devlink_nl_port_attrs_put(msg, devlink_port)) +- goto nla_put_failure; +- if (devlink_nl_port_function_attrs_put(msg, devlink_port, extack)) +- goto nla_put_failure; +- if (devlink_port->linecard && +- nla_put_u32(msg, DEVLINK_ATTR_LINECARD_INDEX, +- devlink_port->linecard->index)) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure_type_locked: +- spin_unlock_bh(&devlink_port->type_lock); +- rtnl_unlock(); +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static void devlink_port_notify(struct devlink_port *devlink_port, +- enum devlink_command cmd) +-{ +- struct devlink *devlink = devlink_port->devlink; +- struct sk_buff *msg; +- int err; +- +- WARN_ON(cmd != DEVLINK_CMD_PORT_NEW && cmd != DEVLINK_CMD_PORT_DEL); +- +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_port_fill(msg, devlink_port, cmd, 0, 0, 0, NULL); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, +- 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static void devlink_rate_notify(struct devlink_rate *devlink_rate, +- enum devlink_command cmd) +-{ +- struct devlink *devlink = devlink_rate->devlink; +- struct sk_buff *msg; +- int err; +- +- WARN_ON(cmd != DEVLINK_CMD_RATE_NEW && cmd != DEVLINK_CMD_RATE_DEL); +- +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_rate_fill(msg, devlink_rate, cmd, 0, 0, 0, NULL); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, +- 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static int devlink_nl_cmd_rate_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink_rate *devlink_rate; +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(devlink_rate, &devlink->rate_list, list) { +- enum devlink_command cmd = DEVLINK_CMD_RATE_NEW; +- u32 id = NETLINK_CB(cb->skb).portid; +- +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_rate_fill(msg, devlink_rate, cmd, id, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI, NULL); +- if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- if (err != -EMSGSIZE) +- return err; +- +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_nl_cmd_rate_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_rate *devlink_rate = info->user_ptr[1]; +- struct sk_buff *msg; +- int err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- 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; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static bool +-devlink_rate_is_parent_node(struct devlink_rate *devlink_rate, +- struct devlink_rate *parent) +-{ +- while (parent) { +- if (parent == devlink_rate) +- return true; +- parent = parent->parent; +- } +- return false; +-} +- +-static int devlink_nl_cmd_get_doit(struct sk_buff *skb, struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct sk_buff *msg; +- int err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_fill(msg, devlink, DEVLINK_CMD_NEW, +- info->snd_portid, info->snd_seq, 0); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_nl_cmd_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- if (idx < start) { +- idx++; +- devlink_put(devlink); +- continue; +- } +- +- devl_lock(devlink); +- err = devlink_nl_fill(msg, devlink, DEVLINK_CMD_NEW, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, NLM_F_MULTI); +- devl_unlock(devlink); +- devlink_put(devlink); +- +- if (err) +- goto out; +- idx++; +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_nl_cmd_port_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_port *devlink_port = info->user_ptr[1]; +- struct sk_buff *msg; +- int err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_port_fill(msg, devlink_port, DEVLINK_CMD_PORT_NEW, +- info->snd_portid, info->snd_seq, 0, +- info->extack); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_nl_cmd_port_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- struct devlink_port *devlink_port; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(devlink_port, &devlink->port_list, list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_port_fill(msg, devlink_port, +- DEVLINK_CMD_NEW, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI, cb->extack); +- if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_port_type_set(struct devlink_port *devlink_port, +- enum devlink_port_type port_type) +- +-{ +- int err; +- +- if (!devlink_port->devlink->ops->port_type_set) +- return -EOPNOTSUPP; +- +- if (port_type == devlink_port->type) +- return 0; +- +- err = devlink_port->devlink->ops->port_type_set(devlink_port, +- port_type); +- if (err) +- return err; +- +- devlink_port->desired_type = port_type; +- devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); +- return 0; +-} +- +-static int devlink_port_function_hw_addr_set(struct devlink_port *port, +- const struct nlattr *attr, +- struct netlink_ext_ack *extack) +-{ +- const struct devlink_ops *ops = port->devlink->ops; +- const u8 *hw_addr; +- int hw_addr_len; +- +- hw_addr = nla_data(attr); +- hw_addr_len = nla_len(attr); +- if (hw_addr_len > MAX_ADDR_LEN) { +- NL_SET_ERR_MSG_MOD(extack, "Port function hardware address too long"); +- return -EINVAL; +- } +- if (port->type == DEVLINK_PORT_TYPE_ETH) { +- if (hw_addr_len != ETH_ALEN) { +- NL_SET_ERR_MSG_MOD(extack, "Address must be 6 bytes for Ethernet device"); +- return -EINVAL; +- } +- if (!is_unicast_ether_addr(hw_addr)) { +- NL_SET_ERR_MSG_MOD(extack, "Non-unicast hardware address unsupported"); +- return -EINVAL; +- } +- } +- +- if (!ops->port_function_hw_addr_set) { +- NL_SET_ERR_MSG_MOD(extack, "Port doesn't support function attributes"); +- return -EOPNOTSUPP; +- } +- +- return ops->port_function_hw_addr_set(port, hw_addr, hw_addr_len, +- extack); +-} +- +-static int devlink_port_fn_state_set(struct devlink_port *port, +- const struct nlattr *attr, +- struct netlink_ext_ack *extack) +-{ +- enum devlink_port_fn_state state; +- const struct devlink_ops *ops; +- +- state = nla_get_u8(attr); +- ops = port->devlink->ops; +- if (!ops->port_fn_state_set) { +- NL_SET_ERR_MSG_MOD(extack, +- "Function does not support state setting"); +- return -EOPNOTSUPP; +- } +- return ops->port_fn_state_set(port, state, extack); +-} +- +-static int devlink_port_function_set(struct devlink_port *port, +- const struct nlattr *attr, +- struct netlink_ext_ack *extack) +-{ +- struct nlattr *tb[DEVLINK_PORT_FUNCTION_ATTR_MAX + 1]; +- int err; +- +- err = nla_parse_nested(tb, DEVLINK_PORT_FUNCTION_ATTR_MAX, attr, +- devlink_function_nl_policy, extack); +- if (err < 0) { +- NL_SET_ERR_MSG_MOD(extack, "Fail to parse port function attributes"); +- return err; +- } +- +- attr = tb[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]; +- if (attr) { +- err = devlink_port_function_hw_addr_set(port, attr, extack); +- if (err) +- return err; +- } +- /* Keep this as the last function attribute set, so that when +- * multiple port function attributes are set along with state, +- * Those can be applied first before activating the state. +- */ +- attr = tb[DEVLINK_PORT_FN_ATTR_STATE]; +- if (attr) +- err = devlink_port_fn_state_set(port, attr, extack); +- +- if (!err) +- devlink_port_notify(port, DEVLINK_CMD_PORT_NEW); +- return err; +-} +- +-static int devlink_nl_cmd_port_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_port *devlink_port = info->user_ptr[1]; +- int err; +- +- if (info->attrs[DEVLINK_ATTR_PORT_TYPE]) { +- enum devlink_port_type port_type; +- +- port_type = nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_TYPE]); +- err = devlink_port_type_set(devlink_port, port_type); +- if (err) +- return err; +- } +- +- if (info->attrs[DEVLINK_ATTR_PORT_FUNCTION]) { +- struct nlattr *attr = info->attrs[DEVLINK_ATTR_PORT_FUNCTION]; +- struct netlink_ext_ack *extack = info->extack; +- +- err = devlink_port_function_set(devlink_port, attr, extack); +- if (err) +- return err; +- } +- +- return 0; +-} +- +-static int devlink_nl_cmd_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]; +- u32 count; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_SPLIT_COUNT)) +- return -EINVAL; +- if (!devlink->ops->port_split) +- return -EOPNOTSUPP; +- +- count = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_SPLIT_COUNT]); +- +- if (!devlink_port->attrs.splittable) { +- /* Split ports cannot be split. */ +- if (devlink_port->attrs.split) +- NL_SET_ERR_MSG_MOD(info->extack, "Port cannot be split further"); +- else +- NL_SET_ERR_MSG_MOD(info->extack, "Port cannot be split"); +- return -EINVAL; +- } +- +- if (count < 2 || !is_power_of_2(count) || count > devlink_port->attrs.lanes) { +- NL_SET_ERR_MSG_MOD(info->extack, "Invalid split count"); +- return -EINVAL; +- } +- +- return devlink->ops->port_split(devlink, devlink_port, count, +- info->extack); +-} +- +-static int devlink_nl_cmd_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]; +- +- if (!devlink->ops->port_unsplit) +- return -EOPNOTSUPP; +- return devlink->ops->port_unsplit(devlink, devlink_port, info->extack); +-} +- +-static int devlink_port_new_notify(struct devlink *devlink, +- unsigned int port_index, +- struct genl_info *info) +-{ +- struct devlink_port *devlink_port; +- struct sk_buff *msg; +- int err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- lockdep_assert_held(&devlink->lock); +- devlink_port = devlink_port_get_by_index(devlink, port_index); +- if (!devlink_port) { +- err = -ENODEV; +- goto out; +- } +- +- err = devlink_nl_port_fill(msg, devlink_port, DEVLINK_CMD_NEW, +- info->snd_portid, info->snd_seq, 0, NULL); +- if (err) +- goto out; +- +- return genlmsg_reply(msg, info); +- +-out: +- nlmsg_free(msg); +- return err; +-} +- +-static int devlink_nl_cmd_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]; +- unsigned int new_port_index; +- int err; +- +- if (!devlink->ops->port_new || !devlink->ops->port_del) +- return -EOPNOTSUPP; +- +- if (!info->attrs[DEVLINK_ATTR_PORT_FLAVOUR] || +- !info->attrs[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]) { +- NL_SET_ERR_MSG_MOD(extack, "Port flavour or PCI PF are not specified"); +- return -EINVAL; +- } +- new_attrs.flavour = nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_FLAVOUR]); +- new_attrs.pfnum = +- nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]); +- +- if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { +- /* Port index of the new port being created by driver. */ +- new_attrs.port_index = +- nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); +- new_attrs.port_index_valid = true; +- } +- if (info->attrs[DEVLINK_ATTR_PORT_CONTROLLER_NUMBER]) { +- new_attrs.controller = +- nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_CONTROLLER_NUMBER]); +- new_attrs.controller_valid = true; +- } +- if (new_attrs.flavour == DEVLINK_PORT_FLAVOUR_PCI_SF && +- info->attrs[DEVLINK_ATTR_PORT_PCI_SF_NUMBER]) { +- new_attrs.sfnum = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_PCI_SF_NUMBER]); +- new_attrs.sfnum_valid = true; +- } +- +- err = devlink->ops->port_new(devlink, &new_attrs, extack, +- &new_port_index); +- if (err) +- return err; +- +- err = devlink_port_new_notify(devlink, new_port_index, info); +- if (err && err != -ENODEV) { +- /* Fail to send the response; destroy newly created port. */ +- devlink->ops->port_del(devlink, new_port_index, extack); +- } +- return err; +-} +- +-static int devlink_nl_cmd_port_del_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct netlink_ext_ack *extack = info->extack; +- struct devlink *devlink = info->user_ptr[0]; +- unsigned int port_index; +- +- if (!devlink->ops->port_del) +- return -EOPNOTSUPP; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_INDEX)) { +- NL_SET_ERR_MSG_MOD(extack, "Port index is not specified"); +- return -EINVAL; +- } +- port_index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); +- +- return devlink->ops->port_del(devlink, port_index, extack); +-} +- +-static int +-devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, +- struct genl_info *info, +- struct nlattr *nla_parent) +-{ +- struct devlink *devlink = devlink_rate->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_rate->parent; +- if (parent && len) { +- NL_SET_ERR_MSG_MOD(info->extack, "Rate object already has parent."); +- return -EBUSY; +- } else if (parent && !len) { +- if (devlink_rate_is_leaf(devlink_rate)) +- err = ops->rate_leaf_parent_set(devlink_rate, NULL, +- devlink_rate->priv, NULL, +- info->extack); +- else if (devlink_rate_is_node(devlink_rate)) +- err = ops->rate_node_parent_set(devlink_rate, NULL, +- devlink_rate->priv, NULL, +- info->extack); +- if (err) +- return err; +- +- refcount_dec(&parent->refcnt); +- devlink_rate->parent = NULL; +- } else if (!parent && len) { +- parent = devlink_rate_node_get_by_name(devlink, parent_name); +- if (IS_ERR(parent)) +- return -ENODEV; +- +- if (parent == devlink_rate) { +- NL_SET_ERR_MSG_MOD(info->extack, "Parent to self is not allowed"); +- return -EINVAL; +- } +- +- if (devlink_rate_is_node(devlink_rate) && +- devlink_rate_is_parent_node(devlink_rate, parent->parent)) { +- NL_SET_ERR_MSG_MOD(info->extack, "Node is already a parent of parent node."); +- return -EEXIST; +- } +- +- if (devlink_rate_is_leaf(devlink_rate)) +- err = ops->rate_leaf_parent_set(devlink_rate, parent, +- devlink_rate->priv, parent->priv, +- info->extack); +- else if (devlink_rate_is_node(devlink_rate)) +- err = ops->rate_node_parent_set(devlink_rate, parent, +- devlink_rate->priv, parent->priv, +- info->extack); +- if (err) +- return err; +- +- refcount_inc(&parent->refcnt); +- devlink_rate->parent = parent; +- } +- +- return 0; +-} +- +-static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, +- const struct devlink_ops *ops, +- struct genl_info *info) +-{ +- struct nlattr *nla_parent, **attrs = info->attrs; +- int err = -EOPNOTSUPP; +- u64 rate; +- +- if (attrs[DEVLINK_ATTR_RATE_TX_SHARE]) { +- rate = nla_get_u64(attrs[DEVLINK_ATTR_RATE_TX_SHARE]); +- if (devlink_rate_is_leaf(devlink_rate)) +- err = ops->rate_leaf_tx_share_set(devlink_rate, devlink_rate->priv, +- rate, info->extack); +- else if (devlink_rate_is_node(devlink_rate)) +- err = ops->rate_node_tx_share_set(devlink_rate, devlink_rate->priv, +- rate, info->extack); +- if (err) +- return err; +- devlink_rate->tx_share = rate; +- } +- +- if (attrs[DEVLINK_ATTR_RATE_TX_MAX]) { +- rate = nla_get_u64(attrs[DEVLINK_ATTR_RATE_TX_MAX]); +- if (devlink_rate_is_leaf(devlink_rate)) +- err = ops->rate_leaf_tx_max_set(devlink_rate, devlink_rate->priv, +- rate, info->extack); +- else if (devlink_rate_is_node(devlink_rate)) +- err = ops->rate_node_tx_max_set(devlink_rate, devlink_rate->priv, +- rate, info->extack); +- if (err) +- return err; +- devlink_rate->tx_max = rate; +- } +- +- nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; +- if (nla_parent) { +- err = devlink_nl_rate_parent_node_set(devlink_rate, info, +- nla_parent); +- if (err) +- return err; +- } +- +- return 0; +-} +- +-static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, +- struct genl_info *info, +- enum devlink_rate_type type) +-{ +- struct nlattr **attrs = info->attrs; +- +- if (type == DEVLINK_RATE_TYPE_LEAF) { +- if (attrs[DEVLINK_ATTR_RATE_TX_SHARE] && !ops->rate_leaf_tx_share_set) { +- NL_SET_ERR_MSG_MOD(info->extack, "TX share set isn't supported for the leafs"); +- return false; +- } +- if (attrs[DEVLINK_ATTR_RATE_TX_MAX] && !ops->rate_leaf_tx_max_set) { +- NL_SET_ERR_MSG_MOD(info->extack, "TX max set isn't supported for the leafs"); +- return false; +- } +- if (attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME] && +- !ops->rate_leaf_parent_set) { +- NL_SET_ERR_MSG_MOD(info->extack, "Parent set isn't supported for the leafs"); +- return false; +- } +- } else if (type == DEVLINK_RATE_TYPE_NODE) { +- if (attrs[DEVLINK_ATTR_RATE_TX_SHARE] && !ops->rate_node_tx_share_set) { +- NL_SET_ERR_MSG_MOD(info->extack, "TX share set isn't supported for the nodes"); +- return false; +- } +- if (attrs[DEVLINK_ATTR_RATE_TX_MAX] && !ops->rate_node_tx_max_set) { +- NL_SET_ERR_MSG_MOD(info->extack, "TX max set isn't supported for the nodes"); +- return false; +- } +- if (attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME] && +- !ops->rate_node_parent_set) { +- NL_SET_ERR_MSG_MOD(info->extack, "Parent set isn't supported for the nodes"); +- return false; +- } +- } else { +- WARN(1, "Unknown type of rate object"); +- return false; +- } +- +- return true; +-} +- +-static int devlink_nl_cmd_rate_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_rate *devlink_rate = info->user_ptr[1]; +- struct devlink *devlink = devlink_rate->devlink; +- const struct devlink_ops *ops = devlink->ops; +- int err; +- +- if (!ops || !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) +- return -EOPNOTSUPP; +- +- err = devlink_nl_rate_set(devlink_rate, ops, info); +- +- if (!err) +- devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); +- return err; +-} +- +-static int devlink_nl_cmd_rate_new_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_rate *rate_node; +- const struct devlink_ops *ops; +- int err; +- +- ops = devlink->ops; +- if (!ops || !ops->rate_node_new || !ops->rate_node_del) { +- NL_SET_ERR_MSG_MOD(info->extack, "Rate nodes aren't supported"); +- return -EOPNOTSUPP; +- } +- +- 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; +- +- rate_node = kzalloc(sizeof(*rate_node), GFP_KERNEL); +- if (!rate_node) +- return -ENOMEM; +- +- rate_node->devlink = devlink; +- rate_node->type = DEVLINK_RATE_TYPE_NODE; +- rate_node->name = nla_strdup(info->attrs[DEVLINK_ATTR_RATE_NODE_NAME], GFP_KERNEL); +- if (!rate_node->name) { +- err = -ENOMEM; +- goto err_strdup; +- } +- +- err = ops->rate_node_new(rate_node, &rate_node->priv, info->extack); +- if (err) +- goto err_node_new; +- +- err = devlink_nl_rate_set(rate_node, ops, info); +- if (err) +- goto err_rate_set; +- +- refcount_set(&rate_node->refcnt, 1); +- list_add(&rate_node->list, &devlink->rate_list); +- devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); +- return 0; +- +-err_rate_set: +- ops->rate_node_del(rate_node, rate_node->priv, info->extack); +-err_node_new: +- kfree(rate_node->name); +-err_strdup: +- kfree(rate_node); +- return err; +-} +- +-static int devlink_nl_cmd_rate_del_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_rate *rate_node = info->user_ptr[1]; +- struct devlink *devlink = rate_node->devlink; +- const struct devlink_ops *ops = devlink->ops; +- int err; +- +- if (refcount_read(&rate_node->refcnt) > 1) { +- NL_SET_ERR_MSG_MOD(info->extack, "Node has children. Cannot delete node."); +- return -EBUSY; +- } +- +- devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); +- err = ops->rate_node_del(rate_node, rate_node->priv, info->extack); +- if (rate_node->parent) +- refcount_dec(&rate_node->parent->refcnt); +- list_del(&rate_node->list); +- kfree(rate_node->name); +- kfree(rate_node); +- return err; +-} +- +-struct devlink_linecard_type { +- const char *type; +- const void *priv; +-}; +- +-static int devlink_nl_linecard_fill(struct sk_buff *msg, +- struct devlink *devlink, +- struct devlink_linecard *linecard, +- enum devlink_command cmd, u32 portid, +- u32 seq, int flags, +- struct netlink_ext_ack *extack) +-{ +- struct devlink_linecard_type *linecard_type; +- struct nlattr *attr; +- void *hdr; +- int i; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_LINECARD_INDEX, linecard->index)) +- goto nla_put_failure; +- if (nla_put_u8(msg, DEVLINK_ATTR_LINECARD_STATE, linecard->state)) +- goto nla_put_failure; +- if (linecard->type && +- nla_put_string(msg, DEVLINK_ATTR_LINECARD_TYPE, linecard->type)) +- goto nla_put_failure; +- +- if (linecard->types_count) { +- attr = nla_nest_start(msg, +- DEVLINK_ATTR_LINECARD_SUPPORTED_TYPES); +- if (!attr) +- goto nla_put_failure; +- for (i = 0; i < linecard->types_count; i++) { +- linecard_type = &linecard->types[i]; +- if (nla_put_string(msg, DEVLINK_ATTR_LINECARD_TYPE, +- linecard_type->type)) { +- nla_nest_cancel(msg, attr); +- goto nla_put_failure; +- } +- } +- nla_nest_end(msg, attr); +- } +- +- if (linecard->nested_devlink && +- devlink_nl_put_nested_handle(msg, linecard->nested_devlink)) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static void devlink_linecard_notify(struct devlink_linecard *linecard, +- enum devlink_command cmd) +-{ +- struct devlink *devlink = linecard->devlink; +- struct sk_buff *msg; +- int err; +- +- WARN_ON(cmd != DEVLINK_CMD_LINECARD_NEW && +- cmd != DEVLINK_CMD_LINECARD_DEL); +- +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_linecard_fill(msg, devlink, linecard, cmd, 0, 0, 0, +- NULL); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), +- msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static int devlink_nl_cmd_linecard_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_linecard *linecard = info->user_ptr[1]; +- struct devlink *devlink = linecard->devlink; +- struct sk_buff *msg; +- int err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- mutex_lock(&linecard->state_lock); +- err = devlink_nl_linecard_fill(msg, devlink, linecard, +- DEVLINK_CMD_LINECARD_NEW, +- info->snd_portid, info->snd_seq, 0, +- info->extack); +- mutex_unlock(&linecard->state_lock); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_nl_cmd_linecard_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink_linecard *linecard; +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- mutex_lock(&devlink->linecards_lock); +- list_for_each_entry(linecard, &devlink->linecard_list, list) { +- if (idx < start) { +- idx++; +- continue; +- } +- mutex_lock(&linecard->state_lock); +- err = devlink_nl_linecard_fill(msg, devlink, linecard, +- DEVLINK_CMD_LINECARD_NEW, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI, +- cb->extack); +- mutex_unlock(&linecard->state_lock); +- if (err) { +- mutex_unlock(&devlink->linecards_lock); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- mutex_unlock(&devlink->linecards_lock); +- devlink_put(devlink); +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static struct devlink_linecard_type * +-devlink_linecard_type_lookup(struct devlink_linecard *linecard, +- const char *type) +-{ +- struct devlink_linecard_type *linecard_type; +- int i; +- +- for (i = 0; i < linecard->types_count; i++) { +- linecard_type = &linecard->types[i]; +- if (!strcmp(type, linecard_type->type)) +- return linecard_type; +- } +- return NULL; +-} +- +-static int devlink_linecard_type_set(struct devlink_linecard *linecard, +- const char *type, +- struct netlink_ext_ack *extack) +-{ +- const struct devlink_linecard_ops *ops = linecard->ops; +- struct devlink_linecard_type *linecard_type; +- int err; +- +- mutex_lock(&linecard->state_lock); +- if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING) { +- NL_SET_ERR_MSG_MOD(extack, "Line card is currently being provisioned"); +- err = -EBUSY; +- goto out; +- } +- if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONING) { +- NL_SET_ERR_MSG_MOD(extack, "Line card is currently being unprovisioned"); +- err = -EBUSY; +- goto out; +- } +- +- linecard_type = devlink_linecard_type_lookup(linecard, type); +- if (!linecard_type) { +- NL_SET_ERR_MSG_MOD(extack, "Unsupported line card type provided"); +- err = -EINVAL; +- goto out; +- } +- +- if (linecard->state != DEVLINK_LINECARD_STATE_UNPROVISIONED && +- linecard->state != DEVLINK_LINECARD_STATE_PROVISIONING_FAILED) { +- NL_SET_ERR_MSG_MOD(extack, "Line card already provisioned"); +- err = -EBUSY; +- /* Check if the line card is provisioned in the same +- * way the user asks. In case it is, make the operation +- * to return success. +- */ +- if (ops->same_provision && +- ops->same_provision(linecard, linecard->priv, +- linecard_type->type, +- linecard_type->priv)) +- err = 0; +- goto out; +- } +- +- linecard->state = DEVLINK_LINECARD_STATE_PROVISIONING; +- linecard->type = linecard_type->type; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +- err = ops->provision(linecard, linecard->priv, linecard_type->type, +- linecard_type->priv, extack); +- if (err) { +- /* Provisioning failed. Assume the linecard is unprovisioned +- * for future operations. +- */ +- mutex_lock(&linecard->state_lock); +- linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; +- linecard->type = NULL; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +- } +- return err; +- +-out: +- mutex_unlock(&linecard->state_lock); +- return err; +-} +- +-static int devlink_linecard_type_unset(struct devlink_linecard *linecard, +- struct netlink_ext_ack *extack) +-{ +- int err; +- +- mutex_lock(&linecard->state_lock); +- if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING) { +- NL_SET_ERR_MSG_MOD(extack, "Line card is currently being provisioned"); +- err = -EBUSY; +- goto out; +- } +- if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONING) { +- NL_SET_ERR_MSG_MOD(extack, "Line card is currently being unprovisioned"); +- err = -EBUSY; +- goto out; +- } +- if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING_FAILED) { +- linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; +- linecard->type = NULL; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- err = 0; +- goto out; +- } +- +- if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONED) { +- NL_SET_ERR_MSG_MOD(extack, "Line card is not provisioned"); +- err = 0; +- goto out; +- } +- linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONING; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +- err = linecard->ops->unprovision(linecard, linecard->priv, +- extack); +- if (err) { +- /* Unprovisioning failed. Assume the linecard is unprovisioned +- * for future operations. +- */ +- mutex_lock(&linecard->state_lock); +- linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; +- linecard->type = NULL; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +- } +- return err; +- +-out: +- mutex_unlock(&linecard->state_lock); +- return err; +-} +- +-static int devlink_nl_cmd_linecard_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_linecard *linecard = info->user_ptr[1]; +- struct netlink_ext_ack *extack = info->extack; +- int err; +- +- if (info->attrs[DEVLINK_ATTR_LINECARD_TYPE]) { +- const char *type; +- +- type = nla_data(info->attrs[DEVLINK_ATTR_LINECARD_TYPE]); +- if (strcmp(type, "")) { +- err = devlink_linecard_type_set(linecard, type, extack); +- if (err) +- return err; +- } else { +- err = devlink_linecard_type_unset(linecard, extack); +- if (err) +- return err; +- } +- } +- +- return 0; +-} +- +-static int devlink_nl_sb_fill(struct sk_buff *msg, struct devlink *devlink, +- struct devlink_sb *devlink_sb, +- enum devlink_command cmd, u32 portid, +- u32 seq, int flags) +-{ +- void *hdr; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_SIZE, devlink_sb->size)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_INGRESS_POOL_COUNT, +- devlink_sb->ingress_pools_count)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_EGRESS_POOL_COUNT, +- devlink_sb->egress_pools_count)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_INGRESS_TC_COUNT, +- devlink_sb->ingress_tc_count)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_EGRESS_TC_COUNT, +- devlink_sb->egress_tc_count)) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_sb_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_sb *devlink_sb; +- struct sk_buff *msg; +- int err; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_sb_fill(msg, devlink, devlink_sb, +- DEVLINK_CMD_SB_NEW, +- info->snd_portid, info->snd_seq, 0); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_nl_cmd_sb_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- struct devlink_sb *devlink_sb; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(devlink_sb, &devlink->sb_list, list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_sb_fill(msg, devlink, devlink_sb, +- DEVLINK_CMD_SB_NEW, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI); +- if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_nl_sb_pool_fill(struct sk_buff *msg, struct devlink *devlink, +- struct devlink_sb *devlink_sb, +- u16 pool_index, enum devlink_command cmd, +- u32 portid, u32 seq, int flags) +-{ +- struct devlink_sb_pool_info pool_info; +- void *hdr; +- int err; +- +- err = devlink->ops->sb_pool_get(devlink, devlink_sb->index, +- pool_index, &pool_info); +- if (err) +- return err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_POOL_INDEX, pool_index)) +- goto nla_put_failure; +- if (nla_put_u8(msg, DEVLINK_ATTR_SB_POOL_TYPE, pool_info.pool_type)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_POOL_SIZE, pool_info.size)) +- goto nla_put_failure; +- if (nla_put_u8(msg, DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE, +- pool_info.threshold_type)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_POOL_CELL_SIZE, +- pool_info.cell_size)) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_sb_pool_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_sb *devlink_sb; +- struct sk_buff *msg; +- u16 pool_index; +- int err; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- err = devlink_sb_pool_index_get_from_info(devlink_sb, info, +- &pool_index); +- if (err) +- return err; +- +- if (!devlink->ops->sb_pool_get) +- return -EOPNOTSUPP; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_sb_pool_fill(msg, devlink, devlink_sb, pool_index, +- DEVLINK_CMD_SB_POOL_NEW, +- info->snd_portid, info->snd_seq, 0); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int __sb_pool_get_dumpit(struct sk_buff *msg, int start, int *p_idx, +- struct devlink *devlink, +- struct devlink_sb *devlink_sb, +- u32 portid, u32 seq) +-{ +- u16 pool_count = devlink_sb_pool_count(devlink_sb); +- u16 pool_index; +- int err; +- +- for (pool_index = 0; pool_index < pool_count; pool_index++) { +- if (*p_idx < start) { +- (*p_idx)++; +- continue; +- } +- err = devlink_nl_sb_pool_fill(msg, devlink, +- devlink_sb, +- pool_index, +- DEVLINK_CMD_SB_POOL_NEW, +- portid, seq, NLM_F_MULTI); +- if (err) +- return err; +- (*p_idx)++; +- } +- return 0; +-} +- +-static int devlink_nl_cmd_sb_pool_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- struct devlink_sb *devlink_sb; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- if (!devlink->ops->sb_pool_get) +- goto retry; +- +- devl_lock(devlink); +- list_for_each_entry(devlink_sb, &devlink->sb_list, list) { +- err = __sb_pool_get_dumpit(msg, start, &idx, devlink, +- devlink_sb, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq); +- if (err == -EOPNOTSUPP) { +- err = 0; +- } else if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- } +- devl_unlock(devlink); +-retry: +- devlink_put(devlink); +- } +-out: +- if (err != -EMSGSIZE) +- return err; +- +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index, +- u16 pool_index, u32 size, +- enum devlink_sb_threshold_type threshold_type, +- struct netlink_ext_ack *extack) +- +-{ +- const struct devlink_ops *ops = devlink->ops; +- +- if (ops->sb_pool_set) +- return ops->sb_pool_set(devlink, sb_index, pool_index, +- size, threshold_type, extack); +- return -EOPNOTSUPP; +-} +- +-static int devlink_nl_cmd_sb_pool_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- enum devlink_sb_threshold_type threshold_type; +- struct devlink_sb *devlink_sb; +- u16 pool_index; +- u32 size; +- int err; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- err = devlink_sb_pool_index_get_from_info(devlink_sb, info, +- &pool_index); +- if (err) +- return err; +- +- err = devlink_sb_th_type_get_from_info(info, &threshold_type); +- if (err) +- return err; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_POOL_SIZE)) +- return -EINVAL; +- +- size = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_POOL_SIZE]); +- return devlink_sb_pool_set(devlink, devlink_sb->index, +- pool_index, size, threshold_type, +- info->extack); +-} +- +-static int devlink_nl_sb_port_pool_fill(struct sk_buff *msg, +- struct devlink *devlink, +- struct devlink_port *devlink_port, +- struct devlink_sb *devlink_sb, +- u16 pool_index, +- enum devlink_command cmd, +- u32 portid, u32 seq, int flags) +-{ +- const struct devlink_ops *ops = devlink->ops; +- u32 threshold; +- void *hdr; +- int err; +- +- err = ops->sb_port_pool_get(devlink_port, devlink_sb->index, +- pool_index, &threshold); +- if (err) +- return err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_POOL_INDEX, pool_index)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_THRESHOLD, threshold)) +- goto nla_put_failure; +- +- if (ops->sb_occ_port_pool_get) { +- u32 cur; +- u32 max; +- +- err = ops->sb_occ_port_pool_get(devlink_port, devlink_sb->index, +- pool_index, &cur, &max); +- if (err && err != -EOPNOTSUPP) +- goto sb_occ_get_failure; +- if (!err) { +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_CUR, cur)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_MAX, max)) +- goto nla_put_failure; +- } +- } +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- err = -EMSGSIZE; +-sb_occ_get_failure: +- genlmsg_cancel(msg, hdr); +- return err; +-} +- +-static int devlink_nl_cmd_sb_port_pool_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_port *devlink_port = info->user_ptr[1]; +- struct devlink *devlink = devlink_port->devlink; +- struct devlink_sb *devlink_sb; +- struct sk_buff *msg; +- u16 pool_index; +- int err; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- err = devlink_sb_pool_index_get_from_info(devlink_sb, info, +- &pool_index); +- if (err) +- return err; +- +- if (!devlink->ops->sb_port_pool_get) +- return -EOPNOTSUPP; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_sb_port_pool_fill(msg, devlink, devlink_port, +- devlink_sb, pool_index, +- DEVLINK_CMD_SB_PORT_POOL_NEW, +- info->snd_portid, info->snd_seq, 0); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int __sb_port_pool_get_dumpit(struct sk_buff *msg, int start, int *p_idx, +- struct devlink *devlink, +- struct devlink_sb *devlink_sb, +- u32 portid, u32 seq) +-{ +- struct devlink_port *devlink_port; +- u16 pool_count = devlink_sb_pool_count(devlink_sb); +- u16 pool_index; +- int err; +- +- list_for_each_entry(devlink_port, &devlink->port_list, list) { +- for (pool_index = 0; pool_index < pool_count; pool_index++) { +- if (*p_idx < start) { +- (*p_idx)++; +- continue; +- } +- err = devlink_nl_sb_port_pool_fill(msg, devlink, +- devlink_port, +- devlink_sb, +- pool_index, +- DEVLINK_CMD_SB_PORT_POOL_NEW, +- portid, seq, +- NLM_F_MULTI); +- if (err) +- return err; +- (*p_idx)++; +- } +- } +- return 0; +-} +- +-static int devlink_nl_cmd_sb_port_pool_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- struct devlink_sb *devlink_sb; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- if (!devlink->ops->sb_port_pool_get) +- goto retry; +- +- devl_lock(devlink); +- list_for_each_entry(devlink_sb, &devlink->sb_list, list) { +- err = __sb_port_pool_get_dumpit(msg, start, &idx, +- devlink, devlink_sb, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq); +- if (err == -EOPNOTSUPP) { +- err = 0; +- } else if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- } +- devl_unlock(devlink); +-retry: +- devlink_put(devlink); +- } +-out: +- if (err != -EMSGSIZE) +- return err; +- +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_sb_port_pool_set(struct devlink_port *devlink_port, +- unsigned int sb_index, u16 pool_index, +- u32 threshold, +- struct netlink_ext_ack *extack) +- +-{ +- const struct devlink_ops *ops = devlink_port->devlink->ops; +- +- if (ops->sb_port_pool_set) +- return ops->sb_port_pool_set(devlink_port, sb_index, +- pool_index, threshold, extack); +- return -EOPNOTSUPP; +-} +- +-static int devlink_nl_cmd_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_sb *devlink_sb; +- u16 pool_index; +- u32 threshold; +- int err; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- err = devlink_sb_pool_index_get_from_info(devlink_sb, info, +- &pool_index); +- if (err) +- return err; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_THRESHOLD)) +- return -EINVAL; +- +- threshold = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_THRESHOLD]); +- return devlink_sb_port_pool_set(devlink_port, devlink_sb->index, +- pool_index, threshold, info->extack); +-} +- +-static int +-devlink_nl_sb_tc_pool_bind_fill(struct sk_buff *msg, struct devlink *devlink, +- struct devlink_port *devlink_port, +- struct devlink_sb *devlink_sb, u16 tc_index, +- enum devlink_sb_pool_type pool_type, +- enum devlink_command cmd, +- u32 portid, u32 seq, int flags) +-{ +- const struct devlink_ops *ops = devlink->ops; +- u16 pool_index; +- u32 threshold; +- void *hdr; +- int err; +- +- err = ops->sb_tc_pool_bind_get(devlink_port, devlink_sb->index, +- tc_index, pool_type, +- &pool_index, &threshold); +- if (err) +- return err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_TC_INDEX, tc_index)) +- goto nla_put_failure; +- if (nla_put_u8(msg, DEVLINK_ATTR_SB_POOL_TYPE, pool_type)) +- goto nla_put_failure; +- if (nla_put_u16(msg, DEVLINK_ATTR_SB_POOL_INDEX, pool_index)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_THRESHOLD, threshold)) +- goto nla_put_failure; +- +- if (ops->sb_occ_tc_port_bind_get) { +- u32 cur; +- u32 max; +- +- err = ops->sb_occ_tc_port_bind_get(devlink_port, +- devlink_sb->index, +- tc_index, pool_type, +- &cur, &max); +- if (err && err != -EOPNOTSUPP) +- return err; +- if (!err) { +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_CUR, cur)) +- goto nla_put_failure; +- if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_MAX, max)) +- goto nla_put_failure; +- } +- } +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_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 *devlink = devlink_port->devlink; +- struct devlink_sb *devlink_sb; +- struct sk_buff *msg; +- enum devlink_sb_pool_type pool_type; +- u16 tc_index; +- int err; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- err = devlink_sb_pool_type_get_from_info(info, &pool_type); +- if (err) +- return err; +- +- err = devlink_sb_tc_index_get_from_info(devlink_sb, info, +- pool_type, &tc_index); +- if (err) +- return err; +- +- if (!devlink->ops->sb_tc_pool_bind_get) +- return -EOPNOTSUPP; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_sb_tc_pool_bind_fill(msg, devlink, devlink_port, +- devlink_sb, tc_index, pool_type, +- DEVLINK_CMD_SB_TC_POOL_BIND_NEW, +- info->snd_portid, +- info->snd_seq, 0); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int __sb_tc_pool_bind_get_dumpit(struct sk_buff *msg, +- int start, int *p_idx, +- struct devlink *devlink, +- struct devlink_sb *devlink_sb, +- u32 portid, u32 seq) +-{ +- struct devlink_port *devlink_port; +- u16 tc_index; +- int err; +- +- list_for_each_entry(devlink_port, &devlink->port_list, list) { +- for (tc_index = 0; +- tc_index < devlink_sb->ingress_tc_count; tc_index++) { +- if (*p_idx < start) { +- (*p_idx)++; +- continue; +- } +- err = devlink_nl_sb_tc_pool_bind_fill(msg, devlink, +- devlink_port, +- devlink_sb, +- tc_index, +- DEVLINK_SB_POOL_TYPE_INGRESS, +- DEVLINK_CMD_SB_TC_POOL_BIND_NEW, +- portid, seq, +- NLM_F_MULTI); +- if (err) +- return err; +- (*p_idx)++; +- } +- for (tc_index = 0; +- tc_index < devlink_sb->egress_tc_count; tc_index++) { +- if (*p_idx < start) { +- (*p_idx)++; +- continue; +- } +- err = devlink_nl_sb_tc_pool_bind_fill(msg, devlink, +- devlink_port, +- devlink_sb, +- tc_index, +- DEVLINK_SB_POOL_TYPE_EGRESS, +- DEVLINK_CMD_SB_TC_POOL_BIND_NEW, +- portid, seq, +- NLM_F_MULTI); +- if (err) +- return err; +- (*p_idx)++; +- } +- } +- return 0; +-} +- +-static int +-devlink_nl_cmd_sb_tc_pool_bind_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- struct devlink_sb *devlink_sb; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- if (!devlink->ops->sb_tc_pool_bind_get) +- goto retry; +- +- devl_lock(devlink); +- list_for_each_entry(devlink_sb, &devlink->sb_list, list) { +- err = __sb_tc_pool_bind_get_dumpit(msg, start, &idx, +- devlink, +- devlink_sb, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq); +- if (err == -EOPNOTSUPP) { +- err = 0; +- } else if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- } +- devl_unlock(devlink); +-retry: +- devlink_put(devlink); +- } +-out: +- if (err != -EMSGSIZE) +- return err; +- +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port, +- unsigned int sb_index, u16 tc_index, +- enum devlink_sb_pool_type pool_type, +- u16 pool_index, u32 threshold, +- struct netlink_ext_ack *extack) +- +-{ +- const struct devlink_ops *ops = devlink_port->devlink->ops; +- +- if (ops->sb_tc_pool_bind_set) +- return ops->sb_tc_pool_bind_set(devlink_port, sb_index, +- tc_index, pool_type, +- pool_index, threshold, extack); +- return -EOPNOTSUPP; +-} +- +-static int devlink_nl_cmd_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]; +- enum devlink_sb_pool_type pool_type; +- struct devlink_sb *devlink_sb; +- u16 tc_index; +- u16 pool_index; +- u32 threshold; +- int err; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- err = devlink_sb_pool_type_get_from_info(info, &pool_type); +- if (err) +- return err; +- +- err = devlink_sb_tc_index_get_from_info(devlink_sb, info, +- pool_type, &tc_index); +- if (err) +- return err; +- +- err = devlink_sb_pool_index_get_from_info(devlink_sb, info, +- &pool_index); +- if (err) +- return err; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_THRESHOLD)) +- return -EINVAL; +- +- threshold = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_THRESHOLD]); +- return devlink_sb_tc_pool_bind_set(devlink_port, devlink_sb->index, +- tc_index, pool_type, +- pool_index, threshold, info->extack); +-} +- +-static int devlink_nl_cmd_sb_occ_snapshot_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- const struct devlink_ops *ops = devlink->ops; +- struct devlink_sb *devlink_sb; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- if (ops->sb_occ_snapshot) +- return ops->sb_occ_snapshot(devlink, devlink_sb->index); +- return -EOPNOTSUPP; +-} +- +-static int devlink_nl_cmd_sb_occ_max_clear_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- const struct devlink_ops *ops = devlink->ops; +- struct devlink_sb *devlink_sb; +- +- devlink_sb = devlink_sb_get_from_info(devlink, info); +- if (IS_ERR(devlink_sb)) +- return PTR_ERR(devlink_sb); +- +- if (ops->sb_occ_max_clear) +- return ops->sb_occ_max_clear(devlink, devlink_sb->index); +- return -EOPNOTSUPP; +-} +- +-static int devlink_nl_eswitch_fill(struct sk_buff *msg, struct devlink *devlink, +- enum devlink_command cmd, u32 portid, +- u32 seq, int flags) +-{ +- const struct devlink_ops *ops = devlink->ops; +- enum devlink_eswitch_encap_mode encap_mode; +- u8 inline_mode; +- void *hdr; +- int err = 0; +- u16 mode; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- err = devlink_nl_put_handle(msg, devlink); +- if (err) +- goto nla_put_failure; +- +- if (ops->eswitch_mode_get) { +- err = ops->eswitch_mode_get(devlink, &mode); +- if (err) +- goto nla_put_failure; +- err = nla_put_u16(msg, DEVLINK_ATTR_ESWITCH_MODE, mode); +- if (err) +- goto nla_put_failure; +- } +- +- if (ops->eswitch_inline_mode_get) { +- err = ops->eswitch_inline_mode_get(devlink, &inline_mode); +- if (err) +- goto nla_put_failure; +- err = nla_put_u8(msg, DEVLINK_ATTR_ESWITCH_INLINE_MODE, +- inline_mode); +- if (err) +- goto nla_put_failure; +- } +- +- if (ops->eswitch_encap_mode_get) { +- err = ops->eswitch_encap_mode_get(devlink, &encap_mode); +- if (err) +- goto nla_put_failure; +- err = nla_put_u8(msg, DEVLINK_ATTR_ESWITCH_ENCAP_MODE, encap_mode); +- if (err) +- goto nla_put_failure; +- } +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return err; +-} +- +-static int devlink_nl_cmd_eswitch_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct sk_buff *msg; +- int err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_eswitch_fill(msg, devlink, DEVLINK_CMD_ESWITCH_GET, +- info->snd_portid, info->snd_seq, 0); +- +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_rate_nodes_check(struct devlink *devlink, u16 mode, +- struct netlink_ext_ack *extack) +-{ +- struct devlink_rate *devlink_rate; +- +- list_for_each_entry(devlink_rate, &devlink->rate_list, list) +- if (devlink_rate_is_node(devlink_rate)) { +- NL_SET_ERR_MSG_MOD(extack, "Rate node(s) exists."); +- return -EBUSY; +- } +- return 0; +-} +- +-static int devlink_nl_cmd_eswitch_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- const struct devlink_ops *ops = devlink->ops; +- enum devlink_eswitch_encap_mode encap_mode; +- u8 inline_mode; +- int err = 0; +- u16 mode; +- +- if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) { +- if (!ops->eswitch_mode_set) +- return -EOPNOTSUPP; +- mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]); +- err = devlink_rate_nodes_check(devlink, mode, info->extack); +- if (err) +- return err; +- err = ops->eswitch_mode_set(devlink, mode, info->extack); +- if (err) +- return err; +- } +- +- if (info->attrs[DEVLINK_ATTR_ESWITCH_INLINE_MODE]) { +- if (!ops->eswitch_inline_mode_set) +- return -EOPNOTSUPP; +- inline_mode = nla_get_u8( +- info->attrs[DEVLINK_ATTR_ESWITCH_INLINE_MODE]); +- err = ops->eswitch_inline_mode_set(devlink, inline_mode, +- info->extack); +- if (err) +- return err; +- } +- +- if (info->attrs[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]) { +- if (!ops->eswitch_encap_mode_set) +- return -EOPNOTSUPP; +- encap_mode = nla_get_u8(info->attrs[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]); +- err = ops->eswitch_encap_mode_set(devlink, encap_mode, +- info->extack); +- if (err) +- return err; +- } +- +- return 0; +-} +- +-int devlink_dpipe_match_put(struct sk_buff *skb, +- struct devlink_dpipe_match *match) +-{ +- struct devlink_dpipe_header *header = match->header; +- struct devlink_dpipe_field *field = &header->fields[match->field_id]; +- struct nlattr *match_attr; +- +- match_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_MATCH); +- if (!match_attr) +- return -EMSGSIZE; +- +- if (nla_put_u32(skb, DEVLINK_ATTR_DPIPE_MATCH_TYPE, match->type) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_INDEX, match->header_index) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_ID, header->id) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_ID, field->id) || +- nla_put_u8(skb, DEVLINK_ATTR_DPIPE_HEADER_GLOBAL, header->global)) +- goto nla_put_failure; +- +- nla_nest_end(skb, match_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(skb, match_attr); +- return -EMSGSIZE; +-} +-EXPORT_SYMBOL_GPL(devlink_dpipe_match_put); +- +-static int devlink_dpipe_matches_put(struct devlink_dpipe_table *table, +- struct sk_buff *skb) +-{ +- struct nlattr *matches_attr; +- +- matches_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_TABLE_MATCHES); +- if (!matches_attr) +- return -EMSGSIZE; +- +- if (table->table_ops->matches_dump(table->priv, skb)) +- goto nla_put_failure; +- +- nla_nest_end(skb, matches_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(skb, matches_attr); +- return -EMSGSIZE; +-} +- +-int devlink_dpipe_action_put(struct sk_buff *skb, +- struct devlink_dpipe_action *action) +-{ +- struct devlink_dpipe_header *header = action->header; +- struct devlink_dpipe_field *field = &header->fields[action->field_id]; +- struct nlattr *action_attr; +- +- action_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_ACTION); +- if (!action_attr) +- return -EMSGSIZE; +- +- if (nla_put_u32(skb, DEVLINK_ATTR_DPIPE_ACTION_TYPE, action->type) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_INDEX, action->header_index) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_ID, header->id) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_ID, field->id) || +- nla_put_u8(skb, DEVLINK_ATTR_DPIPE_HEADER_GLOBAL, header->global)) +- goto nla_put_failure; +- +- nla_nest_end(skb, action_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(skb, action_attr); +- return -EMSGSIZE; +-} +-EXPORT_SYMBOL_GPL(devlink_dpipe_action_put); +- +-static int devlink_dpipe_actions_put(struct devlink_dpipe_table *table, +- struct sk_buff *skb) +-{ +- struct nlattr *actions_attr; +- +- actions_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_TABLE_ACTIONS); +- if (!actions_attr) +- return -EMSGSIZE; +- +- if (table->table_ops->actions_dump(table->priv, skb)) +- goto nla_put_failure; +- +- nla_nest_end(skb, actions_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(skb, actions_attr); +- return -EMSGSIZE; +-} +- +-static int devlink_dpipe_table_put(struct sk_buff *skb, +- struct devlink_dpipe_table *table) +-{ +- struct nlattr *table_attr; +- u64 table_size; +- +- table_size = table->table_ops->size_get(table->priv); +- table_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_TABLE); +- if (!table_attr) +- return -EMSGSIZE; +- +- if (nla_put_string(skb, DEVLINK_ATTR_DPIPE_TABLE_NAME, table->name) || +- nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_TABLE_SIZE, table_size, +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- if (nla_put_u8(skb, DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED, +- table->counters_enabled)) +- goto nla_put_failure; +- +- if (table->resource_valid) { +- if (nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID, +- table->resource_id, DEVLINK_ATTR_PAD) || +- nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS, +- table->resource_units, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- } +- if (devlink_dpipe_matches_put(table, skb)) +- goto nla_put_failure; +- +- if (devlink_dpipe_actions_put(table, skb)) +- goto nla_put_failure; +- +- nla_nest_end(skb, table_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(skb, table_attr); +- return -EMSGSIZE; +-} +- +-static int devlink_dpipe_send_and_alloc_skb(struct sk_buff **pskb, +- struct genl_info *info) +-{ +- int err; +- +- if (*pskb) { +- err = genlmsg_reply(*pskb, info); +- if (err) +- return err; +- } +- *pskb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!*pskb) +- return -ENOMEM; +- return 0; +-} +- +-static int devlink_dpipe_tables_fill(struct genl_info *info, +- enum devlink_command cmd, int flags, +- struct list_head *dpipe_tables, +- const char *table_name) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_dpipe_table *table; +- struct nlattr *tables_attr; +- struct sk_buff *skb = NULL; +- struct nlmsghdr *nlh; +- bool incomplete; +- void *hdr; +- int i; +- int err; +- +- table = list_first_entry(dpipe_tables, +- struct devlink_dpipe_table, list); +-start_again: +- err = devlink_dpipe_send_and_alloc_skb(&skb, info); +- if (err) +- return err; +- +- hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, +- &devlink_nl_family, NLM_F_MULTI, cmd); +- if (!hdr) { +- nlmsg_free(skb); +- return -EMSGSIZE; +- } +- +- if (devlink_nl_put_handle(skb, devlink)) +- goto nla_put_failure; +- tables_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_TABLES); +- if (!tables_attr) +- goto nla_put_failure; +- +- i = 0; +- incomplete = false; +- list_for_each_entry_from(table, dpipe_tables, list) { +- if (!table_name) { +- err = devlink_dpipe_table_put(skb, table); +- if (err) { +- if (!i) +- goto err_table_put; +- incomplete = true; +- break; +- } +- } else { +- if (!strcmp(table->name, table_name)) { +- err = devlink_dpipe_table_put(skb, table); +- if (err) +- break; +- } +- } +- i++; +- } +- +- nla_nest_end(skb, tables_attr); +- genlmsg_end(skb, hdr); +- if (incomplete) +- goto start_again; +- +-send_done: +- nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, +- NLMSG_DONE, 0, flags | NLM_F_MULTI); +- if (!nlh) { +- err = devlink_dpipe_send_and_alloc_skb(&skb, info); +- if (err) +- return err; +- goto send_done; +- } +- +- return genlmsg_reply(skb, info); +- +-nla_put_failure: +- err = -EMSGSIZE; +-err_table_put: +- nlmsg_free(skb); +- return err; +-} +- +-static int devlink_nl_cmd_dpipe_table_get(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- const char *table_name = NULL; +- +- if (info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]) +- table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]); +- +- return devlink_dpipe_tables_fill(info, DEVLINK_CMD_DPIPE_TABLE_GET, 0, +- &devlink->dpipe_table_list, +- table_name); +-} +- +-static int devlink_dpipe_value_put(struct sk_buff *skb, +- struct devlink_dpipe_value *value) +-{ +- if (nla_put(skb, DEVLINK_ATTR_DPIPE_VALUE, +- value->value_size, value->value)) +- return -EMSGSIZE; +- if (value->mask) +- if (nla_put(skb, DEVLINK_ATTR_DPIPE_VALUE_MASK, +- value->value_size, value->mask)) +- return -EMSGSIZE; +- if (value->mapping_valid) +- if (nla_put_u32(skb, DEVLINK_ATTR_DPIPE_VALUE_MAPPING, +- value->mapping_value)) +- return -EMSGSIZE; +- return 0; +-} +- +-static int devlink_dpipe_action_value_put(struct sk_buff *skb, +- struct devlink_dpipe_value *value) +-{ +- if (!value->action) +- return -EINVAL; +- if (devlink_dpipe_action_put(skb, value->action)) +- return -EMSGSIZE; +- if (devlink_dpipe_value_put(skb, value)) +- return -EMSGSIZE; +- return 0; +-} +- +-static int devlink_dpipe_action_values_put(struct sk_buff *skb, +- struct devlink_dpipe_value *values, +- unsigned int values_count) +-{ +- struct nlattr *action_attr; +- int i; +- int err; +- +- for (i = 0; i < values_count; i++) { +- action_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_ACTION_VALUE); +- if (!action_attr) +- return -EMSGSIZE; +- err = devlink_dpipe_action_value_put(skb, &values[i]); +- if (err) +- goto err_action_value_put; +- nla_nest_end(skb, action_attr); +- } +- return 0; +- +-err_action_value_put: +- nla_nest_cancel(skb, action_attr); +- return err; +-} +- +-static int devlink_dpipe_match_value_put(struct sk_buff *skb, +- struct devlink_dpipe_value *value) +-{ +- if (!value->match) +- return -EINVAL; +- if (devlink_dpipe_match_put(skb, value->match)) +- return -EMSGSIZE; +- if (devlink_dpipe_value_put(skb, value)) +- return -EMSGSIZE; +- return 0; +-} +- +-static int devlink_dpipe_match_values_put(struct sk_buff *skb, +- struct devlink_dpipe_value *values, +- unsigned int values_count) +-{ +- struct nlattr *match_attr; +- int i; +- int err; +- +- for (i = 0; i < values_count; i++) { +- match_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_MATCH_VALUE); +- if (!match_attr) +- return -EMSGSIZE; +- err = devlink_dpipe_match_value_put(skb, &values[i]); +- if (err) +- goto err_match_value_put; +- nla_nest_end(skb, match_attr); +- } +- return 0; +- +-err_match_value_put: +- nla_nest_cancel(skb, match_attr); +- return err; +-} +- +-static int devlink_dpipe_entry_put(struct sk_buff *skb, +- struct devlink_dpipe_entry *entry) +-{ +- struct nlattr *entry_attr, *matches_attr, *actions_attr; +- int err; +- +- entry_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_ENTRY); +- if (!entry_attr) +- return -EMSGSIZE; +- +- if (nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_ENTRY_INDEX, entry->index, +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- if (entry->counter_valid) +- if (nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_ENTRY_COUNTER, +- entry->counter, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- matches_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES); +- if (!matches_attr) +- goto nla_put_failure; +- +- err = devlink_dpipe_match_values_put(skb, entry->match_values, +- entry->match_values_count); +- if (err) { +- nla_nest_cancel(skb, matches_attr); +- goto err_match_values_put; +- } +- nla_nest_end(skb, matches_attr); +- +- actions_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES); +- if (!actions_attr) +- goto nla_put_failure; +- +- err = devlink_dpipe_action_values_put(skb, entry->action_values, +- entry->action_values_count); +- if (err) { +- nla_nest_cancel(skb, actions_attr); +- goto err_action_values_put; +- } +- nla_nest_end(skb, actions_attr); +- +- nla_nest_end(skb, entry_attr); +- return 0; +- +-nla_put_failure: +- err = -EMSGSIZE; +-err_match_values_put: +-err_action_values_put: +- nla_nest_cancel(skb, entry_attr); +- return err; +-} +- +-static struct devlink_dpipe_table * +-devlink_dpipe_table_find(struct list_head *dpipe_tables, +- const char *table_name, struct devlink *devlink) +-{ +- struct devlink_dpipe_table *table; +- list_for_each_entry_rcu(table, dpipe_tables, list, +- lockdep_is_held(&devlink->lock)) { +- if (!strcmp(table->name, table_name)) +- return table; +- } +- return NULL; +-} +- +-int devlink_dpipe_entry_ctx_prepare(struct devlink_dpipe_dump_ctx *dump_ctx) +-{ +- struct devlink *devlink; +- int err; +- +- err = devlink_dpipe_send_and_alloc_skb(&dump_ctx->skb, +- dump_ctx->info); +- if (err) +- return err; +- +- dump_ctx->hdr = genlmsg_put(dump_ctx->skb, +- dump_ctx->info->snd_portid, +- dump_ctx->info->snd_seq, +- &devlink_nl_family, NLM_F_MULTI, +- dump_ctx->cmd); +- if (!dump_ctx->hdr) +- goto nla_put_failure; +- +- devlink = dump_ctx->info->user_ptr[0]; +- if (devlink_nl_put_handle(dump_ctx->skb, devlink)) +- goto nla_put_failure; +- dump_ctx->nest = nla_nest_start_noflag(dump_ctx->skb, +- DEVLINK_ATTR_DPIPE_ENTRIES); +- if (!dump_ctx->nest) +- goto nla_put_failure; +- return 0; +- +-nla_put_failure: +- nlmsg_free(dump_ctx->skb); +- return -EMSGSIZE; +-} +-EXPORT_SYMBOL_GPL(devlink_dpipe_entry_ctx_prepare); +- +-int devlink_dpipe_entry_ctx_append(struct devlink_dpipe_dump_ctx *dump_ctx, +- struct devlink_dpipe_entry *entry) +-{ +- return devlink_dpipe_entry_put(dump_ctx->skb, entry); +-} +-EXPORT_SYMBOL_GPL(devlink_dpipe_entry_ctx_append); +- +-int devlink_dpipe_entry_ctx_close(struct devlink_dpipe_dump_ctx *dump_ctx) +-{ +- nla_nest_end(dump_ctx->skb, dump_ctx->nest); +- genlmsg_end(dump_ctx->skb, dump_ctx->hdr); +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_dpipe_entry_ctx_close); +- +-void devlink_dpipe_entry_clear(struct devlink_dpipe_entry *entry) +- +-{ +- unsigned int value_count, value_index; +- struct devlink_dpipe_value *value; +- +- value = entry->action_values; +- value_count = entry->action_values_count; +- for (value_index = 0; value_index < value_count; value_index++) { +- kfree(value[value_index].value); +- kfree(value[value_index].mask); +- } +- +- value = entry->match_values; +- value_count = entry->match_values_count; +- for (value_index = 0; value_index < value_count; value_index++) { +- kfree(value[value_index].value); +- kfree(value[value_index].mask); +- } +-} +-EXPORT_SYMBOL_GPL(devlink_dpipe_entry_clear); +- +-static int devlink_dpipe_entries_fill(struct genl_info *info, +- enum devlink_command cmd, int flags, +- struct devlink_dpipe_table *table) +-{ +- struct devlink_dpipe_dump_ctx dump_ctx; +- struct nlmsghdr *nlh; +- int err; +- +- dump_ctx.skb = NULL; +- dump_ctx.cmd = cmd; +- dump_ctx.info = info; +- +- err = table->table_ops->entries_dump(table->priv, +- table->counters_enabled, +- &dump_ctx); +- if (err) +- return err; +- +-send_done: +- nlh = nlmsg_put(dump_ctx.skb, info->snd_portid, info->snd_seq, +- NLMSG_DONE, 0, flags | NLM_F_MULTI); +- if (!nlh) { +- err = devlink_dpipe_send_and_alloc_skb(&dump_ctx.skb, info); +- if (err) +- return err; +- goto send_done; +- } +- return genlmsg_reply(dump_ctx.skb, info); +-} +- +-static int devlink_nl_cmd_dpipe_entries_get(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_dpipe_table *table; +- const char *table_name; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_DPIPE_TABLE_NAME)) +- return -EINVAL; +- +- table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]); +- table = devlink_dpipe_table_find(&devlink->dpipe_table_list, +- table_name, devlink); +- if (!table) +- return -EINVAL; +- +- if (!table->table_ops->entries_dump) +- return -EINVAL; +- +- return devlink_dpipe_entries_fill(info, DEVLINK_CMD_DPIPE_ENTRIES_GET, +- 0, table); +-} +- +-static int devlink_dpipe_fields_put(struct sk_buff *skb, +- const struct devlink_dpipe_header *header) +-{ +- struct devlink_dpipe_field *field; +- struct nlattr *field_attr; +- int i; +- +- for (i = 0; i < header->fields_count; i++) { +- field = &header->fields[i]; +- field_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_FIELD); +- if (!field_attr) +- return -EMSGSIZE; +- if (nla_put_string(skb, DEVLINK_ATTR_DPIPE_FIELD_NAME, field->name) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_ID, field->id) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH, field->bitwidth) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE, field->mapping_type)) +- goto nla_put_failure; +- nla_nest_end(skb, field_attr); +- } +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(skb, field_attr); +- return -EMSGSIZE; +-} +- +-static int devlink_dpipe_header_put(struct sk_buff *skb, +- struct devlink_dpipe_header *header) +-{ +- struct nlattr *fields_attr, *header_attr; +- int err; +- +- header_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_HEADER); +- if (!header_attr) +- return -EMSGSIZE; +- +- if (nla_put_string(skb, DEVLINK_ATTR_DPIPE_HEADER_NAME, header->name) || +- nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_ID, header->id) || +- nla_put_u8(skb, DEVLINK_ATTR_DPIPE_HEADER_GLOBAL, header->global)) +- goto nla_put_failure; +- +- fields_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_DPIPE_HEADER_FIELDS); +- if (!fields_attr) +- goto nla_put_failure; +- +- err = devlink_dpipe_fields_put(skb, header); +- if (err) { +- nla_nest_cancel(skb, fields_attr); +- goto nla_put_failure; +- } +- nla_nest_end(skb, fields_attr); +- nla_nest_end(skb, header_attr); +- return 0; +- +-nla_put_failure: +- err = -EMSGSIZE; +- nla_nest_cancel(skb, header_attr); +- return err; +-} +- +-static int devlink_dpipe_headers_fill(struct genl_info *info, +- enum devlink_command cmd, int flags, +- struct devlink_dpipe_headers * +- dpipe_headers) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct nlattr *headers_attr; +- struct sk_buff *skb = NULL; +- struct nlmsghdr *nlh; +- void *hdr; +- int i, j; +- int err; +- +- i = 0; +-start_again: +- err = devlink_dpipe_send_and_alloc_skb(&skb, info); +- if (err) +- return err; +- +- hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, +- &devlink_nl_family, NLM_F_MULTI, cmd); +- if (!hdr) { +- nlmsg_free(skb); +- return -EMSGSIZE; +- } +- +- if (devlink_nl_put_handle(skb, devlink)) +- goto nla_put_failure; +- headers_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_HEADERS); +- if (!headers_attr) +- goto nla_put_failure; +- +- j = 0; +- for (; i < dpipe_headers->headers_count; i++) { +- err = devlink_dpipe_header_put(skb, dpipe_headers->headers[i]); +- if (err) { +- if (!j) +- goto err_table_put; +- break; +- } +- j++; +- } +- nla_nest_end(skb, headers_attr); +- genlmsg_end(skb, hdr); +- if (i != dpipe_headers->headers_count) +- goto start_again; +- +-send_done: +- nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, +- NLMSG_DONE, 0, flags | NLM_F_MULTI); +- if (!nlh) { +- err = devlink_dpipe_send_and_alloc_skb(&skb, info); +- if (err) +- return err; +- goto send_done; +- } +- return genlmsg_reply(skb, info); +- +-nla_put_failure: +- err = -EMSGSIZE; +-err_table_put: +- nlmsg_free(skb); +- return err; +-} +- +-static int devlink_nl_cmd_dpipe_headers_get(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- +- if (!devlink->dpipe_headers) +- return -EOPNOTSUPP; +- return devlink_dpipe_headers_fill(info, DEVLINK_CMD_DPIPE_HEADERS_GET, +- 0, devlink->dpipe_headers); +-} +- +-static int devlink_dpipe_table_counters_set(struct devlink *devlink, +- const char *table_name, +- bool enable) +-{ +- struct devlink_dpipe_table *table; +- +- table = devlink_dpipe_table_find(&devlink->dpipe_table_list, +- table_name, devlink); +- if (!table) +- return -EINVAL; +- +- if (table->counter_control_extern) +- return -EOPNOTSUPP; +- +- if (!(table->counters_enabled ^ enable)) +- return 0; +- +- table->counters_enabled = enable; +- if (table->table_ops->counters_set_update) +- table->table_ops->counters_set_update(table->priv, enable); +- return 0; +-} +- +-static int devlink_nl_cmd_dpipe_table_counters_set(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- const char *table_name; +- bool counters_enable; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_DPIPE_TABLE_NAME) || +- GENL_REQ_ATTR_CHECK(info, +- DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED)) +- return -EINVAL; +- +- table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]); +- counters_enable = !!nla_get_u8(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED]); +- +- return devlink_dpipe_table_counters_set(devlink, table_name, +- counters_enable); +-} +- +-static struct devlink_resource * +-devlink_resource_find(struct devlink *devlink, +- struct devlink_resource *resource, u64 resource_id) +-{ +- struct list_head *resource_list; +- +- if (resource) +- resource_list = &resource->resource_list; +- else +- resource_list = &devlink->resource_list; +- +- list_for_each_entry(resource, resource_list, list) { +- struct devlink_resource *child_resource; +- +- if (resource->id == resource_id) +- return resource; +- +- child_resource = devlink_resource_find(devlink, resource, +- resource_id); +- if (child_resource) +- return child_resource; +- } +- return NULL; +-} +- +-static void +-devlink_resource_validate_children(struct devlink_resource *resource) +-{ +- struct devlink_resource *child_resource; +- bool size_valid = true; +- u64 parts_size = 0; +- +- if (list_empty(&resource->resource_list)) +- goto out; +- +- list_for_each_entry(child_resource, &resource->resource_list, list) +- parts_size += child_resource->size_new; +- +- if (parts_size > resource->size_new) +- size_valid = false; +-out: +- resource->size_valid = size_valid; +-} +- +-static int +-devlink_resource_validate_size(struct devlink_resource *resource, u64 size, +- struct netlink_ext_ack *extack) +-{ +- u64 reminder; +- int err = 0; +- +- if (size > resource->size_params.size_max) { +- NL_SET_ERR_MSG_MOD(extack, "Size larger than maximum"); +- err = -EINVAL; +- } +- +- if (size < resource->size_params.size_min) { +- NL_SET_ERR_MSG_MOD(extack, "Size smaller than minimum"); +- err = -EINVAL; +- } +- +- div64_u64_rem(size, resource->size_params.size_granularity, &reminder); +- if (reminder) { +- NL_SET_ERR_MSG_MOD(extack, "Wrong granularity"); +- err = -EINVAL; +- } +- +- return err; +-} +- +-static int devlink_nl_cmd_resource_set(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_resource *resource; +- u64 resource_id; +- u64 size; +- int err; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_ID) || +- GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_SIZE)) +- return -EINVAL; +- resource_id = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_ID]); +- +- resource = devlink_resource_find(devlink, NULL, resource_id); +- if (!resource) +- return -EINVAL; +- +- size = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_SIZE]); +- err = devlink_resource_validate_size(resource, size, info->extack); +- if (err) +- return err; +- +- resource->size_new = size; +- devlink_resource_validate_children(resource); +- if (resource->parent) +- devlink_resource_validate_children(resource->parent); +- return 0; +-} +- +-static int +-devlink_resource_size_params_put(struct devlink_resource *resource, +- struct sk_buff *skb) +-{ +- struct devlink_resource_size_params *size_params; +- +- size_params = &resource->size_params; +- if (nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_GRAN, +- size_params->size_granularity, DEVLINK_ATTR_PAD) || +- nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_MAX, +- size_params->size_max, DEVLINK_ATTR_PAD) || +- nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_MIN, +- size_params->size_min, DEVLINK_ATTR_PAD) || +- nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_UNIT, size_params->unit)) +- return -EMSGSIZE; +- return 0; +-} +- +-static int devlink_resource_occ_put(struct devlink_resource *resource, +- struct sk_buff *skb) +-{ +- if (!resource->occ_get) +- return 0; +- return nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_OCC, +- resource->occ_get(resource->occ_get_priv), +- DEVLINK_ATTR_PAD); +-} +- +-static int devlink_resource_put(struct devlink *devlink, struct sk_buff *skb, +- struct devlink_resource *resource) +-{ +- struct devlink_resource *child_resource; +- struct nlattr *child_resource_attr; +- struct nlattr *resource_attr; +- +- resource_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_RESOURCE); +- if (!resource_attr) +- return -EMSGSIZE; +- +- if (nla_put_string(skb, DEVLINK_ATTR_RESOURCE_NAME, resource->name) || +- nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE, resource->size, +- DEVLINK_ATTR_PAD) || +- nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_ID, resource->id, +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- if (resource->size != resource->size_new) +- nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_NEW, +- resource->size_new, DEVLINK_ATTR_PAD); +- if (devlink_resource_occ_put(resource, skb)) +- goto nla_put_failure; +- if (devlink_resource_size_params_put(resource, skb)) +- goto nla_put_failure; +- if (list_empty(&resource->resource_list)) +- goto out; +- +- if (nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_SIZE_VALID, +- resource->size_valid)) +- goto nla_put_failure; +- +- child_resource_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_RESOURCE_LIST); +- if (!child_resource_attr) +- goto nla_put_failure; +- +- list_for_each_entry(child_resource, &resource->resource_list, list) { +- if (devlink_resource_put(devlink, skb, child_resource)) +- goto resource_put_failure; +- } +- +- nla_nest_end(skb, child_resource_attr); +-out: +- nla_nest_end(skb, resource_attr); +- return 0; +- +-resource_put_failure: +- nla_nest_cancel(skb, child_resource_attr); +-nla_put_failure: +- nla_nest_cancel(skb, resource_attr); +- return -EMSGSIZE; +-} +- +-static int devlink_resource_fill(struct genl_info *info, +- enum devlink_command cmd, int flags) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_resource *resource; +- struct nlattr *resources_attr; +- struct sk_buff *skb = NULL; +- struct nlmsghdr *nlh; +- bool incomplete; +- void *hdr; +- int i; +- int err; +- +- resource = list_first_entry(&devlink->resource_list, +- struct devlink_resource, list); +-start_again: +- err = devlink_dpipe_send_and_alloc_skb(&skb, info); +- if (err) +- return err; +- +- hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, +- &devlink_nl_family, NLM_F_MULTI, cmd); +- if (!hdr) { +- nlmsg_free(skb); +- return -EMSGSIZE; +- } +- +- if (devlink_nl_put_handle(skb, devlink)) +- goto nla_put_failure; +- +- resources_attr = nla_nest_start_noflag(skb, +- DEVLINK_ATTR_RESOURCE_LIST); +- if (!resources_attr) +- goto nla_put_failure; +- +- incomplete = false; +- i = 0; +- list_for_each_entry_from(resource, &devlink->resource_list, list) { +- err = devlink_resource_put(devlink, skb, resource); +- if (err) { +- if (!i) +- goto err_resource_put; +- incomplete = true; +- break; +- } +- i++; +- } +- nla_nest_end(skb, resources_attr); +- genlmsg_end(skb, hdr); +- if (incomplete) +- goto start_again; +-send_done: +- nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, +- NLMSG_DONE, 0, flags | NLM_F_MULTI); +- if (!nlh) { +- err = devlink_dpipe_send_and_alloc_skb(&skb, info); +- if (err) +- return err; +- goto send_done; +- } +- return genlmsg_reply(skb, info); +- +-nla_put_failure: +- err = -EMSGSIZE; +-err_resource_put: +- nlmsg_free(skb); +- return err; +-} +- +-static int devlink_nl_cmd_resource_dump(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- +- if (list_empty(&devlink->resource_list)) +- return -EOPNOTSUPP; +- +- return devlink_resource_fill(info, DEVLINK_CMD_RESOURCE_DUMP, 0); +-} +- +-static int +-devlink_resources_validate(struct devlink *devlink, +- struct devlink_resource *resource, +- struct genl_info *info) +-{ +- struct list_head *resource_list; +- int err = 0; +- +- if (resource) +- resource_list = &resource->resource_list; +- else +- resource_list = &devlink->resource_list; +- +- list_for_each_entry(resource, resource_list, list) { +- if (!resource->size_valid) +- return -EINVAL; +- err = devlink_resources_validate(devlink, resource, info); +- if (err) +- return err; +- } +- return err; +-} +- +-static struct net *devlink_netns_get(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct nlattr *netns_pid_attr = info->attrs[DEVLINK_ATTR_NETNS_PID]; +- struct nlattr *netns_fd_attr = info->attrs[DEVLINK_ATTR_NETNS_FD]; +- struct nlattr *netns_id_attr = info->attrs[DEVLINK_ATTR_NETNS_ID]; +- struct net *net; +- +- if (!!netns_pid_attr + !!netns_fd_attr + !!netns_id_attr > 1) { +- NL_SET_ERR_MSG_MOD(info->extack, "multiple netns identifying attributes specified"); +- return ERR_PTR(-EINVAL); +- } +- +- if (netns_pid_attr) { +- net = get_net_ns_by_pid(nla_get_u32(netns_pid_attr)); +- } else if (netns_fd_attr) { +- net = get_net_ns_by_fd(nla_get_u32(netns_fd_attr)); +- } else if (netns_id_attr) { +- net = get_net_ns_by_id(sock_net(skb->sk), +- nla_get_u32(netns_id_attr)); +- if (!net) +- net = ERR_PTR(-EINVAL); +- } else { +- WARN_ON(1); +- net = ERR_PTR(-EINVAL); +- } +- if (IS_ERR(net)) { +- NL_SET_ERR_MSG_MOD(info->extack, "Unknown network namespace"); +- return ERR_PTR(-EINVAL); +- } +- if (!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN)) { +- put_net(net); +- return ERR_PTR(-EPERM); +- } +- return net; +-} +- +-static void devlink_param_notify(struct devlink *devlink, +- unsigned int port_index, +- struct devlink_param_item *param_item, +- enum devlink_command cmd); +- +-static void devlink_ns_change_notify(struct devlink *devlink, +- struct net *dest_net, struct net *curr_net, +- bool new) +-{ +- struct devlink_param_item *param_item; +- enum devlink_command cmd; +- +- /* Userspace needs to be notified about devlink objects +- * removed from original and entering new network namespace. +- * The rest of the devlink objects are re-created during +- * reload process so the notifications are generated separatelly. +- */ +- +- if (!dest_net || net_eq(dest_net, curr_net)) +- return; +- +- if (new) +- devlink_notify(devlink, DEVLINK_CMD_NEW); +- +- cmd = new ? DEVLINK_CMD_PARAM_NEW : DEVLINK_CMD_PARAM_DEL; +- list_for_each_entry(param_item, &devlink->param_list, list) +- devlink_param_notify(devlink, 0, param_item, cmd); +- +- if (!new) +- devlink_notify(devlink, DEVLINK_CMD_DEL); +-} +- +-static bool devlink_reload_supported(const struct devlink_ops *ops) +-{ +- return ops->reload_down && ops->reload_up; +-} +- +-static void devlink_reload_failed_set(struct devlink *devlink, +- bool reload_failed) +-{ +- if (devlink->reload_failed == reload_failed) +- return; +- devlink->reload_failed = reload_failed; +- devlink_notify(devlink, DEVLINK_CMD_NEW); +-} +- +-bool devlink_is_reload_failed(const struct devlink *devlink) +-{ +- return devlink->reload_failed; +-} +-EXPORT_SYMBOL_GPL(devlink_is_reload_failed); +- +-static void +-__devlink_reload_stats_update(struct devlink *devlink, u32 *reload_stats, +- enum devlink_reload_limit limit, u32 actions_performed) +-{ +- unsigned long actions = actions_performed; +- int stat_idx; +- int action; +- +- for_each_set_bit(action, &actions, __DEVLINK_RELOAD_ACTION_MAX) { +- stat_idx = limit * __DEVLINK_RELOAD_ACTION_MAX + action; +- reload_stats[stat_idx]++; +- } +- devlink_notify(devlink, DEVLINK_CMD_NEW); +-} +- +-static void +-devlink_reload_stats_update(struct devlink *devlink, enum devlink_reload_limit limit, +- u32 actions_performed) +-{ +- __devlink_reload_stats_update(devlink, devlink->stats.reload_stats, limit, +- actions_performed); +-} +- +-/** +- * devlink_remote_reload_actions_performed - Update devlink on reload actions +- * performed which are not a direct result of devlink reload call. +- * +- * This should be called by a driver after performing reload actions in case it was not +- * a result of devlink reload call. For example fw_activate was performed as a result +- * of devlink reload triggered fw_activate on another host. +- * The motivation for this function is to keep data on reload actions performed on this +- * function whether it was done due to direct devlink reload call or not. +- * +- * @devlink: devlink +- * @limit: reload limit +- * @actions_performed: bitmask of actions performed +- */ +-void devlink_remote_reload_actions_performed(struct devlink *devlink, +- enum devlink_reload_limit limit, +- u32 actions_performed) +-{ +- if (WARN_ON(!actions_performed || +- actions_performed & BIT(DEVLINK_RELOAD_ACTION_UNSPEC) || +- actions_performed >= BIT(__DEVLINK_RELOAD_ACTION_MAX) || +- limit > DEVLINK_RELOAD_LIMIT_MAX)) +- return; +- +- __devlink_reload_stats_update(devlink, devlink->stats.remote_reload_stats, limit, +- actions_performed); +-} +-EXPORT_SYMBOL_GPL(devlink_remote_reload_actions_performed); +- +-static int devlink_reload(struct devlink *devlink, struct net *dest_net, +- enum devlink_reload_action action, enum devlink_reload_limit limit, +- u32 *actions_performed, struct netlink_ext_ack *extack) +-{ +- u32 remote_reload_stats[DEVLINK_RELOAD_STATS_ARRAY_SIZE]; +- struct net *curr_net; +- int err; +- +- memcpy(remote_reload_stats, devlink->stats.remote_reload_stats, +- sizeof(remote_reload_stats)); +- +- curr_net = devlink_net(devlink); +- devlink_ns_change_notify(devlink, dest_net, curr_net, false); +- err = devlink->ops->reload_down(devlink, !!dest_net, action, limit, extack); +- if (err) +- return err; +- +- if (dest_net && !net_eq(dest_net, curr_net)) +- write_pnet(&devlink->_net, dest_net); +- +- err = devlink->ops->reload_up(devlink, action, limit, actions_performed, extack); +- devlink_reload_failed_set(devlink, !!err); +- if (err) +- return err; +- +- devlink_ns_change_notify(devlink, dest_net, curr_net, true); +- WARN_ON(!(*actions_performed & BIT(action))); +- /* Catch driver on updating the remote action within devlink reload */ +- WARN_ON(memcmp(remote_reload_stats, devlink->stats.remote_reload_stats, +- sizeof(remote_reload_stats))); +- devlink_reload_stats_update(devlink, limit, *actions_performed); +- return 0; +-} +- +-static int +-devlink_nl_reload_actions_performed_snd(struct devlink *devlink, u32 actions_performed, +- enum devlink_command cmd, struct genl_info *info) +-{ +- struct sk_buff *msg; +- void *hdr; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &devlink_nl_family, 0, cmd); +- if (!hdr) +- goto free_msg; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- +- if (nla_put_bitfield32(msg, DEVLINK_ATTR_RELOAD_ACTIONS_PERFORMED, actions_performed, +- actions_performed)) +- goto nla_put_failure; +- genlmsg_end(msg, hdr); +- +- return genlmsg_reply(msg, info); +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +-free_msg: +- nlmsg_free(msg); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_reload(struct sk_buff *skb, struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- enum devlink_reload_action action; +- enum devlink_reload_limit limit; +- struct net *dest_net = NULL; +- u32 actions_performed; +- int err; +- +- if (!(devlink->features & DEVLINK_F_RELOAD)) +- return -EOPNOTSUPP; +- +- err = devlink_resources_validate(devlink, NULL, info); +- if (err) { +- NL_SET_ERR_MSG_MOD(info->extack, "resources size validation failed"); +- return err; +- } +- +- if (info->attrs[DEVLINK_ATTR_RELOAD_ACTION]) +- action = nla_get_u8(info->attrs[DEVLINK_ATTR_RELOAD_ACTION]); +- else +- action = DEVLINK_RELOAD_ACTION_DRIVER_REINIT; +- +- if (!devlink_reload_action_is_supported(devlink, action)) { +- NL_SET_ERR_MSG_MOD(info->extack, +- "Requested reload action is not supported by the driver"); +- return -EOPNOTSUPP; +- } +- +- limit = DEVLINK_RELOAD_LIMIT_UNSPEC; +- if (info->attrs[DEVLINK_ATTR_RELOAD_LIMITS]) { +- struct nla_bitfield32 limits; +- u32 limits_selected; +- +- limits = nla_get_bitfield32(info->attrs[DEVLINK_ATTR_RELOAD_LIMITS]); +- limits_selected = limits.value & limits.selector; +- if (!limits_selected) { +- NL_SET_ERR_MSG_MOD(info->extack, "Invalid limit selected"); +- return -EINVAL; +- } +- for (limit = 0 ; limit <= DEVLINK_RELOAD_LIMIT_MAX ; limit++) +- if (limits_selected & BIT(limit)) +- break; +- /* UAPI enables multiselection, but currently it is not used */ +- if (limits_selected != BIT(limit)) { +- NL_SET_ERR_MSG_MOD(info->extack, +- "Multiselection of limit is not supported"); +- return -EOPNOTSUPP; +- } +- if (!devlink_reload_limit_is_supported(devlink, limit)) { +- NL_SET_ERR_MSG_MOD(info->extack, +- "Requested limit is not supported by the driver"); +- return -EOPNOTSUPP; +- } +- if (devlink_reload_combination_is_invalid(action, limit)) { +- NL_SET_ERR_MSG_MOD(info->extack, +- "Requested limit is invalid for this action"); +- return -EINVAL; +- } +- } +- if (info->attrs[DEVLINK_ATTR_NETNS_PID] || +- info->attrs[DEVLINK_ATTR_NETNS_FD] || +- info->attrs[DEVLINK_ATTR_NETNS_ID]) { +- dest_net = devlink_netns_get(skb, info); +- if (IS_ERR(dest_net)) +- return PTR_ERR(dest_net); +- } +- +- err = devlink_reload(devlink, dest_net, action, limit, &actions_performed, info->extack); +- +- if (dest_net) +- put_net(dest_net); +- +- if (err) +- return err; +- /* For backward compatibility generate reply only if attributes used by user */ +- if (!info->attrs[DEVLINK_ATTR_RELOAD_ACTION] && !info->attrs[DEVLINK_ATTR_RELOAD_LIMITS]) +- return 0; +- +- return devlink_nl_reload_actions_performed_snd(devlink, actions_performed, +- DEVLINK_CMD_RELOAD, info); +-} +- +-static int devlink_nl_flash_update_fill(struct sk_buff *msg, +- struct devlink *devlink, +- enum devlink_command cmd, +- struct devlink_flash_notify *params) +-{ +- void *hdr; +- +- hdr = genlmsg_put(msg, 0, 0, &devlink_nl_family, 0, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- +- if (cmd != DEVLINK_CMD_FLASH_UPDATE_STATUS) +- goto out; +- +- if (params->status_msg && +- nla_put_string(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG, +- params->status_msg)) +- goto nla_put_failure; +- if (params->component && +- nla_put_string(msg, DEVLINK_ATTR_FLASH_UPDATE_COMPONENT, +- params->component)) +- goto nla_put_failure; +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE, +- params->done, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL, +- params->total, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_TIMEOUT, +- params->timeout, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +-out: +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static void __devlink_flash_update_notify(struct devlink *devlink, +- enum devlink_command cmd, +- struct devlink_flash_notify *params) +-{ +- struct sk_buff *msg; +- int err; +- +- WARN_ON(cmd != DEVLINK_CMD_FLASH_UPDATE && +- cmd != DEVLINK_CMD_FLASH_UPDATE_END && +- cmd != DEVLINK_CMD_FLASH_UPDATE_STATUS); +- +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_flash_update_fill(msg, devlink, cmd, params); +- if (err) +- goto out_free_msg; +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), +- msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +- return; +- +-out_free_msg: +- nlmsg_free(msg); +-} +- +-static void devlink_flash_update_begin_notify(struct devlink *devlink) +-{ +- struct devlink_flash_notify params = {}; +- +- __devlink_flash_update_notify(devlink, +- DEVLINK_CMD_FLASH_UPDATE, +- ¶ms); +-} +- +-static void devlink_flash_update_end_notify(struct devlink *devlink) +-{ +- struct devlink_flash_notify params = {}; +- +- __devlink_flash_update_notify(devlink, +- DEVLINK_CMD_FLASH_UPDATE_END, +- ¶ms); +-} +- +-void devlink_flash_update_status_notify(struct devlink *devlink, +- const char *status_msg, +- const char *component, +- unsigned long done, +- unsigned long total) +-{ +- struct devlink_flash_notify params = { +- .status_msg = status_msg, +- .component = component, +- .done = done, +- .total = total, +- }; +- +- __devlink_flash_update_notify(devlink, +- DEVLINK_CMD_FLASH_UPDATE_STATUS, +- ¶ms); +-} +-EXPORT_SYMBOL_GPL(devlink_flash_update_status_notify); +- +-void devlink_flash_update_timeout_notify(struct devlink *devlink, +- const char *status_msg, +- const char *component, +- unsigned long timeout) +-{ +- struct devlink_flash_notify params = { +- .status_msg = status_msg, +- .component = component, +- .timeout = timeout, +- }; +- +- __devlink_flash_update_notify(devlink, +- DEVLINK_CMD_FLASH_UPDATE_STATUS, +- ¶ms); +-} +-EXPORT_SYMBOL_GPL(devlink_flash_update_timeout_notify); +- +-struct devlink_info_req { +- struct sk_buff *msg; +- void (*version_cb)(const char *version_name, +- enum devlink_info_version_type version_type, +- void *version_cb_priv); +- void *version_cb_priv; +-}; +- +-struct devlink_flash_component_lookup_ctx { +- const char *lookup_name; +- bool lookup_name_found; +-}; +- +-static void +-devlink_flash_component_lookup_cb(const char *version_name, +- enum devlink_info_version_type version_type, +- void *version_cb_priv) +-{ +- struct devlink_flash_component_lookup_ctx *lookup_ctx = version_cb_priv; +- +- if (version_type != DEVLINK_INFO_VERSION_TYPE_COMPONENT || +- lookup_ctx->lookup_name_found) +- return; +- +- lookup_ctx->lookup_name_found = +- !strcmp(lookup_ctx->lookup_name, version_name); +-} +- +-static int devlink_flash_component_get(struct devlink *devlink, +- struct nlattr *nla_component, +- const char **p_component, +- struct netlink_ext_ack *extack) +-{ +- struct devlink_flash_component_lookup_ctx lookup_ctx = {}; +- struct devlink_info_req req = {}; +- const char *component; +- int ret; +- +- if (!nla_component) +- return 0; +- +- component = nla_data(nla_component); +- +- if (!devlink->ops->info_get) { +- NL_SET_ERR_MSG_ATTR(extack, nla_component, +- "component update is not supported by this device"); +- return -EOPNOTSUPP; +- } +- +- lookup_ctx.lookup_name = component; +- req.version_cb = devlink_flash_component_lookup_cb; +- req.version_cb_priv = &lookup_ctx; +- +- ret = devlink->ops->info_get(devlink, &req, NULL); +- if (ret) +- return ret; +- +- if (!lookup_ctx.lookup_name_found) { +- NL_SET_ERR_MSG_ATTR(extack, nla_component, +- "selected component is not supported by this device"); +- return -EINVAL; +- } +- *p_component = component; +- return 0; +-} +- +-static int devlink_nl_cmd_flash_update(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]; +- const char *file_name; +- u32 supported_params; +- int ret; +- +- if (!devlink->ops->flash_update) +- return -EOPNOTSUPP; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME)) +- return -EINVAL; +- +- ret = devlink_flash_component_get(devlink, +- info->attrs[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT], +- ¶ms.component, info->extack); +- if (ret) +- return ret; +- +- supported_params = devlink->ops->supported_flash_update_params; +- +- nla_overwrite_mask = info->attrs[DEVLINK_ATTR_FLASH_UPDATE_OVERWRITE_MASK]; +- if (nla_overwrite_mask) { +- struct nla_bitfield32 sections; +- +- if (!(supported_params & DEVLINK_SUPPORT_FLASH_UPDATE_OVERWRITE_MASK)) { +- NL_SET_ERR_MSG_ATTR(info->extack, nla_overwrite_mask, +- "overwrite settings are not supported by this device"); +- return -EOPNOTSUPP; +- } +- sections = nla_get_bitfield32(nla_overwrite_mask); +- params.overwrite_mask = sections.value & sections.selector; +- } +- +- nla_file_name = info->attrs[DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME]; +- file_name = nla_data(nla_file_name); +- ret = request_firmware(¶ms.fw, file_name, devlink->dev); +- if (ret) { +- NL_SET_ERR_MSG_ATTR(info->extack, nla_file_name, "failed to locate the requested firmware file"); +- return ret; +- } +- +- devlink_flash_update_begin_notify(devlink); +- ret = devlink->ops->flash_update(devlink, ¶ms, info->extack); +- devlink_flash_update_end_notify(devlink); +- +- release_firmware(params.fw); +- +- return ret; +-} +- +-static int +-devlink_nl_selftests_fill(struct sk_buff *msg, struct devlink *devlink, +- u32 portid, u32 seq, int flags, +- struct netlink_ext_ack *extack) +-{ +- struct nlattr *selftests; +- void *hdr; +- int err; +- int i; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, +- DEVLINK_CMD_SELFTESTS_GET); +- if (!hdr) +- return -EMSGSIZE; +- +- err = -EMSGSIZE; +- if (devlink_nl_put_handle(msg, devlink)) +- goto err_cancel_msg; +- +- selftests = nla_nest_start(msg, DEVLINK_ATTR_SELFTESTS); +- if (!selftests) +- goto err_cancel_msg; +- +- for (i = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1; +- i <= DEVLINK_ATTR_SELFTEST_ID_MAX; i++) { +- if (devlink->ops->selftest_check(devlink, i, extack)) { +- err = nla_put_flag(msg, i); +- if (err) +- goto err_cancel_msg; +- } +- } +- +- nla_nest_end(msg, selftests); +- genlmsg_end(msg, hdr); +- return 0; +- +-err_cancel_msg: +- genlmsg_cancel(msg, hdr); +- return err; +-} +- +-static int devlink_nl_cmd_selftests_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct sk_buff *msg; +- int err; +- +- if (!devlink->ops->selftest_check) +- return -EOPNOTSUPP; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_selftests_fill(msg, devlink, info->snd_portid, +- info->snd_seq, 0, info->extack); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_nl_cmd_selftests_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- if (idx < start || !devlink->ops->selftest_check) +- goto inc; +- +- devl_lock(devlink); +- err = devlink_nl_selftests_fill(msg, devlink, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, NLM_F_MULTI, +- cb->extack); +- devl_unlock(devlink); +- if (err) { +- devlink_put(devlink); +- break; +- } +-inc: +- idx++; +- devlink_put(devlink); +- } +- +- if (err != -EMSGSIZE) +- return err; +- +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_selftest_result_put(struct sk_buff *skb, unsigned int id, +- enum devlink_selftest_status test_status) +-{ +- struct nlattr *result_attr; +- +- result_attr = nla_nest_start(skb, DEVLINK_ATTR_SELFTEST_RESULT); +- if (!result_attr) +- return -EMSGSIZE; +- +- if (nla_put_u32(skb, DEVLINK_ATTR_SELFTEST_RESULT_ID, id) || +- nla_put_u8(skb, DEVLINK_ATTR_SELFTEST_RESULT_STATUS, +- test_status)) +- goto nla_put_failure; +- +- nla_nest_end(skb, result_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(skb, result_attr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_selftests_run(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 nlattr *attrs, *selftests; +- struct sk_buff *msg; +- void *hdr; +- int err; +- int i; +- +- if (!devlink->ops->selftest_run || !devlink->ops->selftest_check) +- return -EOPNOTSUPP; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SELFTESTS)) +- return -EINVAL; +- +- attrs = info->attrs[DEVLINK_ATTR_SELFTESTS]; +- +- err = nla_parse_nested(tb, DEVLINK_ATTR_SELFTEST_ID_MAX, attrs, +- devlink_selftest_nl_policy, info->extack); +- if (err < 0) +- return err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = -EMSGSIZE; +- hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, +- &devlink_nl_family, 0, DEVLINK_CMD_SELFTESTS_RUN); +- if (!hdr) +- goto free_msg; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto genlmsg_cancel; +- +- selftests = nla_nest_start(msg, DEVLINK_ATTR_SELFTESTS); +- if (!selftests) +- goto genlmsg_cancel; +- +- for (i = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1; +- i <= DEVLINK_ATTR_SELFTEST_ID_MAX; i++) { +- enum devlink_selftest_status test_status; +- +- if (nla_get_flag(tb[i])) { +- if (!devlink->ops->selftest_check(devlink, i, +- info->extack)) { +- if (devlink_selftest_result_put(msg, i, +- DEVLINK_SELFTEST_STATUS_SKIP)) +- goto selftests_nest_cancel; +- continue; +- } +- +- test_status = devlink->ops->selftest_run(devlink, i, +- info->extack); +- if (devlink_selftest_result_put(msg, i, test_status)) +- goto selftests_nest_cancel; +- } +- } +- +- nla_nest_end(msg, selftests); +- genlmsg_end(msg, hdr); +- return genlmsg_reply(msg, info); +- +-selftests_nest_cancel: +- nla_nest_cancel(msg, selftests); +-genlmsg_cancel: +- genlmsg_cancel(msg, hdr); +-free_msg: +- nlmsg_free(msg); +- return err; +-} +- +-static const struct devlink_param devlink_param_generic[] = { +- { +- .id = DEVLINK_PARAM_GENERIC_ID_INT_ERR_RESET, +- .name = DEVLINK_PARAM_GENERIC_INT_ERR_RESET_NAME, +- .type = DEVLINK_PARAM_GENERIC_INT_ERR_RESET_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_MAX_MACS, +- .name = DEVLINK_PARAM_GENERIC_MAX_MACS_NAME, +- .type = DEVLINK_PARAM_GENERIC_MAX_MACS_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_SRIOV, +- .name = DEVLINK_PARAM_GENERIC_ENABLE_SRIOV_NAME, +- .type = DEVLINK_PARAM_GENERIC_ENABLE_SRIOV_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_REGION_SNAPSHOT, +- .name = DEVLINK_PARAM_GENERIC_REGION_SNAPSHOT_NAME, +- .type = DEVLINK_PARAM_GENERIC_REGION_SNAPSHOT_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_IGNORE_ARI, +- .name = DEVLINK_PARAM_GENERIC_IGNORE_ARI_NAME, +- .type = DEVLINK_PARAM_GENERIC_IGNORE_ARI_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_MSIX_VEC_PER_PF_MAX, +- .name = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MAX_NAME, +- .type = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MAX_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_MSIX_VEC_PER_PF_MIN, +- .name = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MIN_NAME, +- .type = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MIN_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_FW_LOAD_POLICY, +- .name = DEVLINK_PARAM_GENERIC_FW_LOAD_POLICY_NAME, +- .type = DEVLINK_PARAM_GENERIC_FW_LOAD_POLICY_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_RESET_DEV_ON_DRV_PROBE, +- .name = DEVLINK_PARAM_GENERIC_RESET_DEV_ON_DRV_PROBE_NAME, +- .type = DEVLINK_PARAM_GENERIC_RESET_DEV_ON_DRV_PROBE_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_ROCE, +- .name = DEVLINK_PARAM_GENERIC_ENABLE_ROCE_NAME, +- .type = DEVLINK_PARAM_GENERIC_ENABLE_ROCE_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_REMOTE_DEV_RESET, +- .name = DEVLINK_PARAM_GENERIC_ENABLE_REMOTE_DEV_RESET_NAME, +- .type = DEVLINK_PARAM_GENERIC_ENABLE_REMOTE_DEV_RESET_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_ETH, +- .name = DEVLINK_PARAM_GENERIC_ENABLE_ETH_NAME, +- .type = DEVLINK_PARAM_GENERIC_ENABLE_ETH_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_RDMA, +- .name = DEVLINK_PARAM_GENERIC_ENABLE_RDMA_NAME, +- .type = DEVLINK_PARAM_GENERIC_ENABLE_RDMA_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_VNET, +- .name = DEVLINK_PARAM_GENERIC_ENABLE_VNET_NAME, +- .type = DEVLINK_PARAM_GENERIC_ENABLE_VNET_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_IWARP, +- .name = DEVLINK_PARAM_GENERIC_ENABLE_IWARP_NAME, +- .type = DEVLINK_PARAM_GENERIC_ENABLE_IWARP_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_IO_EQ_SIZE, +- .name = DEVLINK_PARAM_GENERIC_IO_EQ_SIZE_NAME, +- .type = DEVLINK_PARAM_GENERIC_IO_EQ_SIZE_TYPE, +- }, +- { +- .id = DEVLINK_PARAM_GENERIC_ID_EVENT_EQ_SIZE, +- .name = DEVLINK_PARAM_GENERIC_EVENT_EQ_SIZE_NAME, +- .type = DEVLINK_PARAM_GENERIC_EVENT_EQ_SIZE_TYPE, +- }, +-}; +- +-static int devlink_param_generic_verify(const struct devlink_param *param) +-{ +- /* verify it match generic parameter by id and name */ +- if (param->id > DEVLINK_PARAM_GENERIC_ID_MAX) +- return -EINVAL; +- if (strcmp(param->name, devlink_param_generic[param->id].name)) +- return -ENOENT; +- +- WARN_ON(param->type != devlink_param_generic[param->id].type); +- +- return 0; +-} +- +-static int devlink_param_driver_verify(const struct devlink_param *param) +-{ +- int i; +- +- if (param->id <= DEVLINK_PARAM_GENERIC_ID_MAX) +- return -EINVAL; +- /* verify no such name in generic params */ +- for (i = 0; i <= DEVLINK_PARAM_GENERIC_ID_MAX; i++) +- if (!strcmp(param->name, devlink_param_generic[i].name)) +- return -EEXIST; +- +- return 0; +-} +- +-static struct devlink_param_item * +-devlink_param_find_by_name(struct list_head *param_list, +- const char *param_name) +-{ +- struct devlink_param_item *param_item; +- +- list_for_each_entry(param_item, param_list, list) +- if (!strcmp(param_item->param->name, param_name)) +- return param_item; +- return NULL; +-} +- +-static struct devlink_param_item * +-devlink_param_find_by_id(struct list_head *param_list, u32 param_id) +-{ +- struct devlink_param_item *param_item; +- +- list_for_each_entry(param_item, param_list, list) +- if (param_item->param->id == param_id) +- return param_item; +- return NULL; +-} +- +-static bool +-devlink_param_cmode_is_supported(const struct devlink_param *param, +- enum devlink_param_cmode cmode) +-{ +- return test_bit(cmode, ¶m->supported_cmodes); +-} +- +-static int devlink_param_get(struct devlink *devlink, +- const struct devlink_param *param, +- struct devlink_param_gset_ctx *ctx) +-{ +- if (!param->get || devlink->reload_failed) +- return -EOPNOTSUPP; +- return param->get(devlink, param->id, ctx); +-} +- +-static int devlink_param_set(struct devlink *devlink, +- const struct devlink_param *param, +- struct devlink_param_gset_ctx *ctx) +-{ +- if (!param->set || devlink->reload_failed) +- return -EOPNOTSUPP; +- return param->set(devlink, param->id, ctx); +-} +- +-static int +-devlink_param_type_to_nla_type(enum devlink_param_type param_type) +-{ +- switch (param_type) { +- case DEVLINK_PARAM_TYPE_U8: +- return NLA_U8; +- case DEVLINK_PARAM_TYPE_U16: +- return NLA_U16; +- case DEVLINK_PARAM_TYPE_U32: +- return NLA_U32; +- case DEVLINK_PARAM_TYPE_STRING: +- return NLA_STRING; +- case DEVLINK_PARAM_TYPE_BOOL: +- return NLA_FLAG; +- default: +- return -EINVAL; +- } +-} +- +-static int +-devlink_nl_param_value_fill_one(struct sk_buff *msg, +- enum devlink_param_type type, +- enum devlink_param_cmode cmode, +- union devlink_param_value val) +-{ +- struct nlattr *param_value_attr; +- +- param_value_attr = nla_nest_start_noflag(msg, +- DEVLINK_ATTR_PARAM_VALUE); +- if (!param_value_attr) +- goto nla_put_failure; +- +- if (nla_put_u8(msg, DEVLINK_ATTR_PARAM_VALUE_CMODE, cmode)) +- goto value_nest_cancel; +- +- switch (type) { +- case DEVLINK_PARAM_TYPE_U8: +- if (nla_put_u8(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, val.vu8)) +- goto value_nest_cancel; +- break; +- case DEVLINK_PARAM_TYPE_U16: +- if (nla_put_u16(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, val.vu16)) +- goto value_nest_cancel; +- break; +- case DEVLINK_PARAM_TYPE_U32: +- if (nla_put_u32(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, val.vu32)) +- goto value_nest_cancel; +- break; +- case DEVLINK_PARAM_TYPE_STRING: +- if (nla_put_string(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, +- val.vstr)) +- goto value_nest_cancel; +- break; +- case DEVLINK_PARAM_TYPE_BOOL: +- if (val.vbool && +- nla_put_flag(msg, DEVLINK_ATTR_PARAM_VALUE_DATA)) +- goto value_nest_cancel; +- break; +- } +- +- nla_nest_end(msg, param_value_attr); +- return 0; +- +-value_nest_cancel: +- nla_nest_cancel(msg, param_value_attr); +-nla_put_failure: +- return -EMSGSIZE; +-} +- +-static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink, +- unsigned int port_index, +- struct devlink_param_item *param_item, +- enum devlink_command cmd, +- u32 portid, u32 seq, int flags) +-{ +- union devlink_param_value param_value[DEVLINK_PARAM_CMODE_MAX + 1]; +- bool param_value_set[DEVLINK_PARAM_CMODE_MAX + 1] = {}; +- const struct devlink_param *param = param_item->param; +- struct devlink_param_gset_ctx ctx; +- struct nlattr *param_values_list; +- struct nlattr *param_attr; +- int nla_type; +- void *hdr; +- int err; +- int i; +- +- /* Get value from driver part to driverinit configuration mode */ +- for (i = 0; i <= DEVLINK_PARAM_CMODE_MAX; i++) { +- if (!devlink_param_cmode_is_supported(param, i)) +- continue; +- if (i == DEVLINK_PARAM_CMODE_DRIVERINIT) { +- if (!param_item->driverinit_value_valid) +- return -EOPNOTSUPP; +- param_value[i] = param_item->driverinit_value; +- } else { +- ctx.cmode = i; +- err = devlink_param_get(devlink, param, &ctx); +- if (err) +- return err; +- param_value[i] = ctx.val; +- } +- param_value_set[i] = true; +- } +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto genlmsg_cancel; +- +- if (cmd == DEVLINK_CMD_PORT_PARAM_GET || +- cmd == DEVLINK_CMD_PORT_PARAM_NEW || +- cmd == DEVLINK_CMD_PORT_PARAM_DEL) +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, port_index)) +- goto genlmsg_cancel; +- +- param_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_PARAM); +- if (!param_attr) +- goto genlmsg_cancel; +- if (nla_put_string(msg, DEVLINK_ATTR_PARAM_NAME, param->name)) +- goto param_nest_cancel; +- if (param->generic && nla_put_flag(msg, DEVLINK_ATTR_PARAM_GENERIC)) +- goto param_nest_cancel; +- +- nla_type = devlink_param_type_to_nla_type(param->type); +- if (nla_type < 0) +- goto param_nest_cancel; +- if (nla_put_u8(msg, DEVLINK_ATTR_PARAM_TYPE, nla_type)) +- goto param_nest_cancel; +- +- param_values_list = nla_nest_start_noflag(msg, +- DEVLINK_ATTR_PARAM_VALUES_LIST); +- if (!param_values_list) +- goto param_nest_cancel; +- +- for (i = 0; i <= DEVLINK_PARAM_CMODE_MAX; i++) { +- if (!param_value_set[i]) +- continue; +- err = devlink_nl_param_value_fill_one(msg, param->type, +- i, param_value[i]); +- if (err) +- goto values_list_nest_cancel; +- } +- +- nla_nest_end(msg, param_values_list); +- nla_nest_end(msg, param_attr); +- genlmsg_end(msg, hdr); +- return 0; +- +-values_list_nest_cancel: +- nla_nest_end(msg, param_values_list); +-param_nest_cancel: +- nla_nest_cancel(msg, param_attr); +-genlmsg_cancel: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static void devlink_param_notify(struct devlink *devlink, +- unsigned int port_index, +- struct devlink_param_item *param_item, +- enum devlink_command cmd) +-{ +- struct sk_buff *msg; +- int err; +- +- WARN_ON(cmd != DEVLINK_CMD_PARAM_NEW && cmd != DEVLINK_CMD_PARAM_DEL && +- cmd != DEVLINK_CMD_PORT_PARAM_NEW && +- cmd != DEVLINK_CMD_PORT_PARAM_DEL); +- ASSERT_DEVLINK_REGISTERED(devlink); +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- err = devlink_nl_param_fill(msg, devlink, port_index, param_item, cmd, +- 0, 0, 0); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), +- msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static int devlink_nl_cmd_param_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink_param_item *param_item; +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(param_item, &devlink->param_list, list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_param_fill(msg, devlink, 0, param_item, +- DEVLINK_CMD_PARAM_GET, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI); +- if (err == -EOPNOTSUPP) { +- err = 0; +- } else if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- if (err != -EMSGSIZE) +- return err; +- +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int +-devlink_param_type_get_from_info(struct genl_info *info, +- enum devlink_param_type *param_type) +-{ +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_TYPE)) +- return -EINVAL; +- +- switch (nla_get_u8(info->attrs[DEVLINK_ATTR_PARAM_TYPE])) { +- case NLA_U8: +- *param_type = DEVLINK_PARAM_TYPE_U8; +- break; +- case NLA_U16: +- *param_type = DEVLINK_PARAM_TYPE_U16; +- break; +- case NLA_U32: +- *param_type = DEVLINK_PARAM_TYPE_U32; +- break; +- case NLA_STRING: +- *param_type = DEVLINK_PARAM_TYPE_STRING; +- break; +- case NLA_FLAG: +- *param_type = DEVLINK_PARAM_TYPE_BOOL; +- break; +- default: +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int +-devlink_param_value_get_from_info(const struct devlink_param *param, +- struct genl_info *info, +- union devlink_param_value *value) +-{ +- struct nlattr *param_data; +- int len; +- +- param_data = info->attrs[DEVLINK_ATTR_PARAM_VALUE_DATA]; +- +- if (param->type != DEVLINK_PARAM_TYPE_BOOL && !param_data) +- return -EINVAL; +- +- switch (param->type) { +- case DEVLINK_PARAM_TYPE_U8: +- if (nla_len(param_data) != sizeof(u8)) +- return -EINVAL; +- value->vu8 = nla_get_u8(param_data); +- break; +- case DEVLINK_PARAM_TYPE_U16: +- if (nla_len(param_data) != sizeof(u16)) +- return -EINVAL; +- value->vu16 = nla_get_u16(param_data); +- break; +- case DEVLINK_PARAM_TYPE_U32: +- if (nla_len(param_data) != sizeof(u32)) +- return -EINVAL; +- value->vu32 = nla_get_u32(param_data); +- break; +- case DEVLINK_PARAM_TYPE_STRING: +- len = strnlen(nla_data(param_data), nla_len(param_data)); +- if (len == nla_len(param_data) || +- len >= __DEVLINK_PARAM_MAX_STRING_VALUE) +- return -EINVAL; +- strcpy(value->vstr, nla_data(param_data)); +- break; +- case DEVLINK_PARAM_TYPE_BOOL: +- if (param_data && nla_len(param_data)) +- return -EINVAL; +- value->vbool = nla_get_flag(param_data); +- break; +- } +- return 0; +-} +- +-static struct devlink_param_item * +-devlink_param_get_from_info(struct list_head *param_list, +- struct genl_info *info) +-{ +- char *param_name; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_NAME)) +- return NULL; +- +- param_name = nla_data(info->attrs[DEVLINK_ATTR_PARAM_NAME]); +- return devlink_param_find_by_name(param_list, param_name); +-} +- +-static int devlink_nl_cmd_param_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_param_item *param_item; +- struct sk_buff *msg; +- int err; +- +- param_item = devlink_param_get_from_info(&devlink->param_list, info); +- if (!param_item) +- return -EINVAL; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_param_fill(msg, devlink, 0, param_item, +- DEVLINK_CMD_PARAM_GET, +- info->snd_portid, info->snd_seq, 0); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, +- unsigned int port_index, +- struct list_head *param_list, +- struct genl_info *info, +- enum devlink_command cmd) +-{ +- enum devlink_param_type param_type; +- struct devlink_param_gset_ctx ctx; +- enum devlink_param_cmode cmode; +- struct devlink_param_item *param_item; +- const struct devlink_param *param; +- union devlink_param_value value; +- int err = 0; +- +- param_item = devlink_param_get_from_info(param_list, info); +- if (!param_item) +- return -EINVAL; +- param = param_item->param; +- err = devlink_param_type_get_from_info(info, ¶m_type); +- if (err) +- return err; +- if (param_type != param->type) +- return -EINVAL; +- err = devlink_param_value_get_from_info(param, info, &value); +- if (err) +- return err; +- if (param->validate) { +- err = param->validate(devlink, param->id, value, info->extack); +- if (err) +- return err; +- } +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_VALUE_CMODE)) +- return -EINVAL; +- cmode = nla_get_u8(info->attrs[DEVLINK_ATTR_PARAM_VALUE_CMODE]); +- if (!devlink_param_cmode_is_supported(param, cmode)) +- return -EOPNOTSUPP; +- +- if (cmode == DEVLINK_PARAM_CMODE_DRIVERINIT) { +- if (param->type == DEVLINK_PARAM_TYPE_STRING) +- strcpy(param_item->driverinit_value.vstr, value.vstr); +- else +- param_item->driverinit_value = value; +- param_item->driverinit_value_valid = true; +- } else { +- if (!param->set) +- return -EOPNOTSUPP; +- ctx.val = value; +- ctx.cmode = cmode; +- err = devlink_param_set(devlink, param, &ctx); +- if (err) +- return err; +- } +- +- devlink_param_notify(devlink, port_index, param_item, cmd); +- return 0; +-} +- +-static int devlink_nl_cmd_param_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- +- return __devlink_nl_cmd_param_set_doit(devlink, 0, &devlink->param_list, +- info, DEVLINK_CMD_PARAM_NEW); +-} +- +-static int devlink_nl_cmd_port_param_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- NL_SET_ERR_MSG_MOD(cb->extack, "Port params are not supported"); +- return msg->len; +-} +- +-static int devlink_nl_cmd_port_param_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- NL_SET_ERR_MSG_MOD(info->extack, "Port params are not supported"); +- return -EINVAL; +-} +- +-static int devlink_nl_cmd_port_param_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- NL_SET_ERR_MSG_MOD(info->extack, "Port params are not supported"); +- return -EINVAL; +-} +- +-static int devlink_nl_region_snapshot_id_put(struct sk_buff *msg, +- struct devlink *devlink, +- struct devlink_snapshot *snapshot) +-{ +- struct nlattr *snap_attr; +- int err; +- +- snap_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_REGION_SNAPSHOT); +- if (!snap_attr) +- return -EINVAL; +- +- err = nla_put_u32(msg, DEVLINK_ATTR_REGION_SNAPSHOT_ID, snapshot->id); +- if (err) +- goto nla_put_failure; +- +- nla_nest_end(msg, snap_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, snap_attr); +- return err; +-} +- +-static int devlink_nl_region_snapshots_id_put(struct sk_buff *msg, +- struct devlink *devlink, +- struct devlink_region *region) +-{ +- struct devlink_snapshot *snapshot; +- struct nlattr *snapshots_attr; +- int err; +- +- snapshots_attr = nla_nest_start_noflag(msg, +- DEVLINK_ATTR_REGION_SNAPSHOTS); +- if (!snapshots_attr) +- return -EINVAL; +- +- list_for_each_entry(snapshot, ®ion->snapshot_list, list) { +- err = devlink_nl_region_snapshot_id_put(msg, devlink, snapshot); +- if (err) +- goto nla_put_failure; +- } +- +- nla_nest_end(msg, snapshots_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, snapshots_attr); +- return err; +-} +- +-static int devlink_nl_region_fill(struct sk_buff *msg, struct devlink *devlink, +- enum devlink_command cmd, u32 portid, +- u32 seq, int flags, +- struct devlink_region *region) +-{ +- void *hdr; +- int err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- err = devlink_nl_put_handle(msg, devlink); +- if (err) +- goto nla_put_failure; +- +- if (region->port) { +- err = nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, +- region->port->index); +- if (err) +- goto nla_put_failure; +- } +- +- err = nla_put_string(msg, DEVLINK_ATTR_REGION_NAME, region->ops->name); +- if (err) +- goto nla_put_failure; +- +- err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_SIZE, +- region->size, +- DEVLINK_ATTR_PAD); +- if (err) +- goto nla_put_failure; +- +- err = nla_put_u32(msg, DEVLINK_ATTR_REGION_MAX_SNAPSHOTS, +- region->max_snapshots); +- if (err) +- goto nla_put_failure; +- +- err = devlink_nl_region_snapshots_id_put(msg, devlink, region); +- if (err) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return err; +-} +- +-static struct sk_buff * +-devlink_nl_region_notify_build(struct devlink_region *region, +- struct devlink_snapshot *snapshot, +- enum devlink_command cmd, u32 portid, u32 seq) +-{ +- struct devlink *devlink = region->devlink; +- struct sk_buff *msg; +- void *hdr; +- int err; +- +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return ERR_PTR(-ENOMEM); +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, 0, cmd); +- if (!hdr) { +- err = -EMSGSIZE; +- goto out_free_msg; +- } +- +- err = devlink_nl_put_handle(msg, devlink); +- if (err) +- goto out_cancel_msg; +- +- if (region->port) { +- err = nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, +- region->port->index); +- if (err) +- goto out_cancel_msg; +- } +- +- err = nla_put_string(msg, DEVLINK_ATTR_REGION_NAME, +- region->ops->name); +- if (err) +- goto out_cancel_msg; +- +- if (snapshot) { +- err = nla_put_u32(msg, DEVLINK_ATTR_REGION_SNAPSHOT_ID, +- snapshot->id); +- if (err) +- goto out_cancel_msg; +- } else { +- err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_SIZE, +- region->size, DEVLINK_ATTR_PAD); +- if (err) +- goto out_cancel_msg; +- } +- genlmsg_end(msg, hdr); +- +- return msg; +- +-out_cancel_msg: +- genlmsg_cancel(msg, hdr); +-out_free_msg: +- nlmsg_free(msg); +- return ERR_PTR(err); +-} +- +-static void devlink_nl_region_notify(struct devlink_region *region, +- struct devlink_snapshot *snapshot, +- enum devlink_command cmd) +-{ +- struct devlink *devlink = region->devlink; +- struct sk_buff *msg; +- +- WARN_ON(cmd != DEVLINK_CMD_REGION_NEW && cmd != DEVLINK_CMD_REGION_DEL); +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = devlink_nl_region_notify_build(region, snapshot, cmd, 0, 0); +- if (IS_ERR(msg)) +- return; +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, +- 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-/** +- * __devlink_snapshot_id_increment - Increment number of snapshots using an id +- * @devlink: devlink instance +- * @id: the snapshot id +- * +- * Track when a new snapshot begins using an id. Load the count for the +- * given id from the snapshot xarray, increment it, and store it back. +- * +- * Called when a new snapshot is created with the given id. +- * +- * The id *must* have been previously allocated by +- * devlink_region_snapshot_id_get(). +- * +- * Returns 0 on success, or an error on failure. +- */ +-static int __devlink_snapshot_id_increment(struct devlink *devlink, u32 id) +-{ +- unsigned long count; +- void *p; +- int err; +- +- xa_lock(&devlink->snapshot_ids); +- p = xa_load(&devlink->snapshot_ids, id); +- if (WARN_ON(!p)) { +- err = -EINVAL; +- goto unlock; +- } +- +- if (WARN_ON(!xa_is_value(p))) { +- err = -EINVAL; +- goto unlock; +- } +- +- count = xa_to_value(p); +- count++; +- +- err = xa_err(__xa_store(&devlink->snapshot_ids, id, xa_mk_value(count), +- GFP_ATOMIC)); +-unlock: +- xa_unlock(&devlink->snapshot_ids); +- return err; +-} +- +-/** +- * __devlink_snapshot_id_decrement - Decrease number of snapshots using an id +- * @devlink: devlink instance +- * @id: the snapshot id +- * +- * Track when a snapshot is deleted and stops using an id. Load the count +- * for the given id from the snapshot xarray, decrement it, and store it +- * back. +- * +- * If the count reaches zero, erase this id from the xarray, freeing it +- * up for future re-use by devlink_region_snapshot_id_get(). +- * +- * Called when a snapshot using the given id is deleted, and when the +- * initial allocator of the id is finished using it. +- */ +-static void __devlink_snapshot_id_decrement(struct devlink *devlink, u32 id) +-{ +- unsigned long count; +- void *p; +- +- xa_lock(&devlink->snapshot_ids); +- p = xa_load(&devlink->snapshot_ids, id); +- if (WARN_ON(!p)) +- goto unlock; +- +- if (WARN_ON(!xa_is_value(p))) +- goto unlock; +- +- count = xa_to_value(p); +- +- if (count > 1) { +- count--; +- __xa_store(&devlink->snapshot_ids, id, xa_mk_value(count), +- GFP_ATOMIC); +- } else { +- /* If this was the last user, we can erase this id */ +- __xa_erase(&devlink->snapshot_ids, id); +- } +-unlock: +- xa_unlock(&devlink->snapshot_ids); +-} +- +-/** +- * __devlink_snapshot_id_insert - Insert a specific snapshot ID +- * @devlink: devlink instance +- * @id: the snapshot id +- * +- * Mark the given snapshot id as used by inserting a zero value into the +- * snapshot xarray. +- * +- * This must be called while holding the devlink instance lock. Unlike +- * devlink_snapshot_id_get, the initial reference count is zero, not one. +- * It is expected that the id will immediately be used before +- * releasing the devlink instance lock. +- * +- * Returns zero on success, or an error code if the snapshot id could not +- * be inserted. +- */ +-static int __devlink_snapshot_id_insert(struct devlink *devlink, u32 id) +-{ +- int err; +- +- xa_lock(&devlink->snapshot_ids); +- if (xa_load(&devlink->snapshot_ids, id)) { +- xa_unlock(&devlink->snapshot_ids); +- return -EEXIST; +- } +- err = xa_err(__xa_store(&devlink->snapshot_ids, id, xa_mk_value(0), +- GFP_ATOMIC)); +- xa_unlock(&devlink->snapshot_ids); +- return err; +-} +- +-/** +- * __devlink_region_snapshot_id_get - get snapshot ID +- * @devlink: devlink instance +- * @id: storage to return snapshot id +- * +- * Allocates a new snapshot id. Returns zero on success, or a negative +- * error on failure. Must be called while holding the devlink instance +- * lock. +- * +- * Snapshot IDs are tracked using an xarray which stores the number of +- * users of the snapshot id. +- * +- * Note that the caller of this function counts as a 'user', in order to +- * avoid race conditions. The caller must release its hold on the +- * snapshot by using devlink_region_snapshot_id_put. +- */ +-static int __devlink_region_snapshot_id_get(struct devlink *devlink, u32 *id) +-{ +- return xa_alloc(&devlink->snapshot_ids, id, xa_mk_value(1), +- xa_limit_32b, GFP_KERNEL); +-} +- +-/** +- * __devlink_region_snapshot_create - create a new snapshot +- * This will add a new snapshot of a region. The snapshot +- * will be stored on the region struct and can be accessed +- * from devlink. This is useful for future analyses of snapshots. +- * Multiple snapshots can be created on a region. +- * The @snapshot_id should be obtained using the getter function. +- * +- * Must be called only while holding the region snapshot lock. +- * +- * @region: devlink region of the snapshot +- * @data: snapshot data +- * @snapshot_id: snapshot id to be created +- */ +-static int +-__devlink_region_snapshot_create(struct devlink_region *region, +- u8 *data, u32 snapshot_id) +-{ +- struct devlink *devlink = region->devlink; +- struct devlink_snapshot *snapshot; +- int err; +- +- lockdep_assert_held(®ion->snapshot_lock); +- +- /* check if region can hold one more snapshot */ +- if (region->cur_snapshots == region->max_snapshots) +- return -ENOSPC; +- +- if (devlink_region_snapshot_get_by_id(region, snapshot_id)) +- return -EEXIST; +- +- snapshot = kzalloc(sizeof(*snapshot), GFP_KERNEL); +- if (!snapshot) +- return -ENOMEM; +- +- err = __devlink_snapshot_id_increment(devlink, snapshot_id); +- if (err) +- goto err_snapshot_id_increment; +- +- snapshot->id = snapshot_id; +- snapshot->region = region; +- snapshot->data = data; +- +- list_add_tail(&snapshot->list, ®ion->snapshot_list); +- +- region->cur_snapshots++; +- +- devlink_nl_region_notify(region, snapshot, DEVLINK_CMD_REGION_NEW); +- return 0; +- +-err_snapshot_id_increment: +- kfree(snapshot); +- return err; +-} +- +-static void devlink_region_snapshot_del(struct devlink_region *region, +- struct devlink_snapshot *snapshot) +-{ +- struct devlink *devlink = region->devlink; +- +- lockdep_assert_held(®ion->snapshot_lock); +- +- devlink_nl_region_notify(region, snapshot, DEVLINK_CMD_REGION_DEL); +- region->cur_snapshots--; +- list_del(&snapshot->list); +- region->ops->destructor(snapshot->data); +- __devlink_snapshot_id_decrement(devlink, snapshot->id); +- kfree(snapshot); +-} +- +-static int devlink_nl_cmd_region_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_port *port = NULL; +- struct devlink_region *region; +- const char *region_name; +- struct sk_buff *msg; +- unsigned int index; +- int err; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME)) +- return -EINVAL; +- +- if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { +- index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); +- +- port = devlink_port_get_by_index(devlink, index); +- if (!port) +- return -ENODEV; +- } +- +- region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]); +- if (port) +- region = devlink_port_region_get_by_name(port, region_name); +- else +- region = devlink_region_get_by_name(devlink, region_name); +- +- if (!region) +- return -EINVAL; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_region_fill(msg, devlink, DEVLINK_CMD_REGION_GET, +- info->snd_portid, info->snd_seq, 0, +- region); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_nl_cmd_region_get_port_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb, +- struct devlink_port *port, +- int *idx, +- int start) +-{ +- struct devlink_region *region; +- int err = 0; +- +- list_for_each_entry(region, &port->region_list, list) { +- if (*idx < start) { +- (*idx)++; +- continue; +- } +- err = devlink_nl_region_fill(msg, port->devlink, +- DEVLINK_CMD_REGION_GET, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI, region); +- if (err) +- goto out; +- (*idx)++; +- } +- +-out: +- return err; +-} +- +-static int devlink_nl_cmd_region_get_devlink_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb, +- struct devlink *devlink, +- int *idx, +- int start) +-{ +- struct devlink_region *region; +- struct devlink_port *port; +- int err = 0; +- +- devl_lock(devlink); +- list_for_each_entry(region, &devlink->region_list, list) { +- if (*idx < start) { +- (*idx)++; +- continue; +- } +- err = devlink_nl_region_fill(msg, devlink, +- DEVLINK_CMD_REGION_GET, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI, region); +- if (err) +- goto out; +- (*idx)++; +- } +- +- list_for_each_entry(port, &devlink->port_list, list) { +- err = devlink_nl_cmd_region_get_port_dumpit(msg, cb, port, idx, +- start); +- if (err) +- goto out; +- } +- +-out: +- devl_unlock(devlink); +- return err; +-} +- +-static int devlink_nl_cmd_region_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- err = devlink_nl_cmd_region_get_devlink_dumpit(msg, cb, devlink, +- &idx, start); +- devlink_put(devlink); +- if (err) +- goto out; +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int devlink_nl_cmd_region_del(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_snapshot *snapshot; +- struct devlink_port *port = NULL; +- struct devlink_region *region; +- const char *region_name; +- unsigned int index; +- u32 snapshot_id; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME) || +- GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_SNAPSHOT_ID)) +- return -EINVAL; +- +- region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]); +- snapshot_id = nla_get_u32(info->attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]); +- +- if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { +- index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); +- +- port = devlink_port_get_by_index(devlink, index); +- if (!port) +- return -ENODEV; +- } +- +- if (port) +- region = devlink_port_region_get_by_name(port, region_name); +- else +- region = devlink_region_get_by_name(devlink, region_name); +- +- if (!region) +- return -EINVAL; +- +- mutex_lock(®ion->snapshot_lock); +- snapshot = devlink_region_snapshot_get_by_id(region, snapshot_id); +- if (!snapshot) { +- mutex_unlock(®ion->snapshot_lock); +- return -EINVAL; +- } +- +- devlink_region_snapshot_del(region, snapshot); +- mutex_unlock(®ion->snapshot_lock); +- return 0; +-} +- +-static int +-devlink_nl_cmd_region_new(struct sk_buff *skb, struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_snapshot *snapshot; +- struct devlink_port *port = NULL; +- struct nlattr *snapshot_id_attr; +- struct devlink_region *region; +- const char *region_name; +- unsigned int index; +- u32 snapshot_id; +- u8 *data; +- int err; +- +- if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME)) { +- NL_SET_ERR_MSG_MOD(info->extack, "No region name provided"); +- return -EINVAL; +- } +- +- region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]); +- +- if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { +- index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); +- +- port = devlink_port_get_by_index(devlink, index); +- if (!port) +- return -ENODEV; +- } +- +- if (port) +- region = devlink_port_region_get_by_name(port, region_name); +- else +- region = devlink_region_get_by_name(devlink, region_name); +- +- if (!region) { +- NL_SET_ERR_MSG_MOD(info->extack, "The requested region does not exist"); +- return -EINVAL; +- } +- +- if (!region->ops->snapshot) { +- NL_SET_ERR_MSG_MOD(info->extack, "The requested region does not support taking an immediate snapshot"); +- return -EOPNOTSUPP; +- } +- +- mutex_lock(®ion->snapshot_lock); +- +- if (region->cur_snapshots == region->max_snapshots) { +- NL_SET_ERR_MSG_MOD(info->extack, "The region has reached the maximum number of stored snapshots"); +- err = -ENOSPC; +- goto unlock; +- } +- +- snapshot_id_attr = info->attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]; +- if (snapshot_id_attr) { +- snapshot_id = nla_get_u32(snapshot_id_attr); +- +- if (devlink_region_snapshot_get_by_id(region, snapshot_id)) { +- NL_SET_ERR_MSG_MOD(info->extack, "The requested snapshot id is already in use"); +- err = -EEXIST; +- goto unlock; +- } +- +- err = __devlink_snapshot_id_insert(devlink, snapshot_id); +- if (err) +- goto unlock; +- } else { +- err = __devlink_region_snapshot_id_get(devlink, &snapshot_id); +- if (err) { +- NL_SET_ERR_MSG_MOD(info->extack, "Failed to allocate a new snapshot id"); +- goto unlock; +- } +- } +- +- if (port) +- err = region->port_ops->snapshot(port, region->port_ops, +- info->extack, &data); +- else +- err = region->ops->snapshot(devlink, region->ops, +- info->extack, &data); +- if (err) +- goto err_snapshot_capture; +- +- err = __devlink_region_snapshot_create(region, data, snapshot_id); +- if (err) +- goto err_snapshot_create; +- +- if (!snapshot_id_attr) { +- struct sk_buff *msg; +- +- snapshot = devlink_region_snapshot_get_by_id(region, +- snapshot_id); +- if (WARN_ON(!snapshot)) { +- err = -EINVAL; +- goto unlock; +- } +- +- msg = devlink_nl_region_notify_build(region, snapshot, +- DEVLINK_CMD_REGION_NEW, +- info->snd_portid, +- info->snd_seq); +- err = PTR_ERR_OR_ZERO(msg); +- if (err) +- goto err_notify; +- +- err = genlmsg_reply(msg, info); +- if (err) +- goto err_notify; +- } +- +- mutex_unlock(®ion->snapshot_lock); +- return 0; +- +-err_snapshot_create: +- region->ops->destructor(data); +-err_snapshot_capture: +- __devlink_snapshot_id_decrement(devlink, snapshot_id); +- mutex_unlock(®ion->snapshot_lock); +- return err; +- +-err_notify: +- devlink_region_snapshot_del(region, snapshot); +-unlock: +- mutex_unlock(®ion->snapshot_lock); +- return err; +-} +- +-static int devlink_nl_cmd_region_read_chunk_fill(struct sk_buff *msg, +- struct devlink *devlink, +- u8 *chunk, u32 chunk_size, +- u64 addr) +-{ +- struct nlattr *chunk_attr; +- int err; +- +- chunk_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_REGION_CHUNK); +- if (!chunk_attr) +- return -EINVAL; +- +- err = nla_put(msg, DEVLINK_ATTR_REGION_CHUNK_DATA, chunk_size, chunk); +- if (err) +- goto nla_put_failure; +- +- err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_CHUNK_ADDR, addr, +- DEVLINK_ATTR_PAD); +- if (err) +- goto nla_put_failure; +- +- nla_nest_end(msg, chunk_attr); +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, chunk_attr); +- return err; +-} +- +-#define DEVLINK_REGION_READ_CHUNK_SIZE 256 +- +-static int devlink_nl_region_read_snapshot_fill(struct sk_buff *skb, +- struct devlink *devlink, +- struct devlink_region *region, +- struct nlattr **attrs, +- u64 start_offset, +- u64 end_offset, +- u64 *new_offset) +-{ +- struct devlink_snapshot *snapshot; +- u64 curr_offset = start_offset; +- u32 snapshot_id; +- int err = 0; +- +- *new_offset = start_offset; +- +- snapshot_id = nla_get_u32(attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]); +- snapshot = devlink_region_snapshot_get_by_id(region, snapshot_id); +- if (!snapshot) +- return -EINVAL; +- +- while (curr_offset < end_offset) { +- u32 data_size; +- u8 *data; +- +- if (end_offset - curr_offset < DEVLINK_REGION_READ_CHUNK_SIZE) +- data_size = end_offset - curr_offset; +- else +- data_size = DEVLINK_REGION_READ_CHUNK_SIZE; +- +- data = &snapshot->data[curr_offset]; +- err = devlink_nl_cmd_region_read_chunk_fill(skb, devlink, +- data, data_size, +- curr_offset); +- if (err) +- break; +- +- curr_offset += data_size; +- } +- *new_offset = curr_offset; +- +- return err; +-} +- +-static int devlink_nl_cmd_region_read_dumpit(struct sk_buff *skb, +- struct netlink_callback *cb) +-{ +- const struct genl_dumpit_info *info = genl_dumpit_info(cb); +- u64 ret_offset, start_offset, end_offset = U64_MAX; +- struct nlattr **attrs = info->attrs; +- struct devlink_port *port = NULL; +- struct devlink_region *region; +- struct nlattr *chunks_attr; +- const char *region_name; +- struct devlink *devlink; +- unsigned int index; +- void *hdr; +- int err; +- +- start_offset = *((u64 *)&cb->args[0]); +- +- devlink = devlink_get_from_attrs(sock_net(cb->skb->sk), attrs); +- if (IS_ERR(devlink)) +- return PTR_ERR(devlink); +- +- devl_lock(devlink); +- +- if (!attrs[DEVLINK_ATTR_REGION_NAME] || +- !attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]) { +- err = -EINVAL; +- goto out_unlock; +- } +- +- if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { +- index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); +- +- port = devlink_port_get_by_index(devlink, index); +- if (!port) { +- err = -ENODEV; +- goto out_unlock; +- } +- } +- +- region_name = nla_data(attrs[DEVLINK_ATTR_REGION_NAME]); +- +- if (port) +- region = devlink_port_region_get_by_name(port, region_name); +- else +- region = devlink_region_get_by_name(devlink, region_name); +- +- if (!region) { +- err = -EINVAL; +- goto out_unlock; +- } +- +- if (attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR] && +- attrs[DEVLINK_ATTR_REGION_CHUNK_LEN]) { +- if (!start_offset) +- start_offset = +- nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR]); +- +- end_offset = nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR]); +- end_offset += nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_LEN]); +- } +- +- if (end_offset > region->size) +- end_offset = region->size; +- +- /* return 0 if there is no further data to read */ +- if (start_offset == end_offset) { +- err = 0; +- goto out_unlock; +- } +- +- hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, +- &devlink_nl_family, NLM_F_ACK | NLM_F_MULTI, +- DEVLINK_CMD_REGION_READ); +- if (!hdr) { +- err = -EMSGSIZE; +- goto out_unlock; +- } +- +- err = devlink_nl_put_handle(skb, devlink); +- if (err) +- goto nla_put_failure; +- +- if (region->port) { +- err = nla_put_u32(skb, DEVLINK_ATTR_PORT_INDEX, +- region->port->index); +- if (err) +- goto nla_put_failure; +- } +- +- err = nla_put_string(skb, DEVLINK_ATTR_REGION_NAME, region_name); +- if (err) +- goto nla_put_failure; +- +- chunks_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_REGION_CHUNKS); +- if (!chunks_attr) { +- err = -EMSGSIZE; +- goto nla_put_failure; +- } +- +- err = devlink_nl_region_read_snapshot_fill(skb, devlink, +- region, attrs, +- start_offset, +- end_offset, &ret_offset); +- +- if (err && err != -EMSGSIZE) +- goto nla_put_failure; +- +- /* Check if there was any progress done to prevent infinite loop */ +- if (ret_offset == start_offset) { +- err = -EINVAL; +- goto nla_put_failure; +- } +- +- *((u64 *)&cb->args[0]) = ret_offset; +- +- nla_nest_end(skb, chunks_attr); +- genlmsg_end(skb, hdr); +- devl_unlock(devlink); +- devlink_put(devlink); +- return skb->len; +- +-nla_put_failure: +- genlmsg_cancel(skb, hdr); +-out_unlock: +- devl_unlock(devlink); +- devlink_put(devlink); +- return err; +-} +- +-int devlink_info_driver_name_put(struct devlink_info_req *req, const char *name) +-{ +- if (!req->msg) +- return 0; +- return nla_put_string(req->msg, DEVLINK_ATTR_INFO_DRIVER_NAME, name); +-} +-EXPORT_SYMBOL_GPL(devlink_info_driver_name_put); +- +-int devlink_info_serial_number_put(struct devlink_info_req *req, const char *sn) +-{ +- if (!req->msg) +- return 0; +- return nla_put_string(req->msg, DEVLINK_ATTR_INFO_SERIAL_NUMBER, sn); +-} +-EXPORT_SYMBOL_GPL(devlink_info_serial_number_put); +- +-int devlink_info_board_serial_number_put(struct devlink_info_req *req, +- const char *bsn) +-{ +- if (!req->msg) +- return 0; +- return nla_put_string(req->msg, DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER, +- bsn); +-} +-EXPORT_SYMBOL_GPL(devlink_info_board_serial_number_put); +- +-static int devlink_info_version_put(struct devlink_info_req *req, int attr, +- const char *version_name, +- const char *version_value, +- enum devlink_info_version_type version_type) +-{ +- struct nlattr *nest; +- int err; +- +- if (req->version_cb) +- req->version_cb(version_name, version_type, +- req->version_cb_priv); +- +- if (!req->msg) +- return 0; +- +- nest = nla_nest_start_noflag(req->msg, attr); +- if (!nest) +- return -EMSGSIZE; +- +- err = nla_put_string(req->msg, DEVLINK_ATTR_INFO_VERSION_NAME, +- version_name); +- if (err) +- goto nla_put_failure; +- +- err = nla_put_string(req->msg, DEVLINK_ATTR_INFO_VERSION_VALUE, +- version_value); +- if (err) +- goto nla_put_failure; +- +- nla_nest_end(req->msg, nest); +- +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(req->msg, nest); +- return err; +-} +- +-int devlink_info_version_fixed_put(struct devlink_info_req *req, +- const char *version_name, +- const char *version_value) +-{ +- return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_FIXED, +- version_name, version_value, +- DEVLINK_INFO_VERSION_TYPE_NONE); +-} +-EXPORT_SYMBOL_GPL(devlink_info_version_fixed_put); +- +-int devlink_info_version_stored_put(struct devlink_info_req *req, +- const char *version_name, +- const char *version_value) +-{ +- return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_STORED, +- version_name, version_value, +- DEVLINK_INFO_VERSION_TYPE_NONE); +-} +-EXPORT_SYMBOL_GPL(devlink_info_version_stored_put); +- +-int devlink_info_version_stored_put_ext(struct devlink_info_req *req, +- const char *version_name, +- const char *version_value, +- enum devlink_info_version_type version_type) +-{ +- return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_STORED, +- version_name, version_value, +- version_type); +-} +-EXPORT_SYMBOL_GPL(devlink_info_version_stored_put_ext); +- +-int devlink_info_version_running_put(struct devlink_info_req *req, +- const char *version_name, +- const char *version_value) +-{ +- return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_RUNNING, +- version_name, version_value, +- DEVLINK_INFO_VERSION_TYPE_NONE); +-} +-EXPORT_SYMBOL_GPL(devlink_info_version_running_put); +- +-int devlink_info_version_running_put_ext(struct devlink_info_req *req, +- const char *version_name, +- const char *version_value, +- enum devlink_info_version_type version_type) +-{ +- return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_RUNNING, +- version_name, version_value, +- version_type); +-} +-EXPORT_SYMBOL_GPL(devlink_info_version_running_put_ext); +- +-static int +-devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink, +- enum devlink_command cmd, u32 portid, +- u32 seq, int flags, struct netlink_ext_ack *extack) +-{ +- struct devlink_info_req req = {}; +- void *hdr; +- int err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- err = -EMSGSIZE; +- if (devlink_nl_put_handle(msg, devlink)) +- goto err_cancel_msg; +- +- req.msg = msg; +- err = devlink->ops->info_get(devlink, &req, extack); +- if (err) +- goto err_cancel_msg; +- +- genlmsg_end(msg, hdr); +- return 0; +- +-err_cancel_msg: +- genlmsg_cancel(msg, hdr); +- return err; +-} +- +-static int devlink_nl_cmd_info_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct sk_buff *msg; +- int err; +- +- if (!devlink->ops->info_get) +- return -EOPNOTSUPP; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_info_fill(msg, devlink, DEVLINK_CMD_INFO_GET, +- info->snd_portid, info->snd_seq, 0, +- info->extack); +- if (err) { +- nlmsg_free(msg); +- return err; +- } +- +- return genlmsg_reply(msg, info); +-} +- +-static int devlink_nl_cmd_info_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err = 0; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- if (idx < start || !devlink->ops->info_get) +- goto inc; +- +- devl_lock(devlink); +- err = devlink_nl_info_fill(msg, devlink, DEVLINK_CMD_INFO_GET, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, NLM_F_MULTI, +- cb->extack); +- devl_unlock(devlink); +- if (err == -EOPNOTSUPP) +- err = 0; +- else if (err) { +- devlink_put(devlink); +- break; +- } +-inc: +- idx++; +- devlink_put(devlink); +- } +- +- if (err != -EMSGSIZE) +- return err; +- +- cb->args[0] = idx; +- return msg->len; +-} +- +-struct devlink_fmsg_item { +- struct list_head list; +- int attrtype; +- u8 nla_type; +- u16 len; +- int value[]; +-}; +- +-struct devlink_fmsg { +- struct list_head item_list; +- bool putting_binary; /* This flag forces enclosing of binary data +- * in an array brackets. It forces using +- * of designated API: +- * devlink_fmsg_binary_pair_nest_start() +- * devlink_fmsg_binary_pair_nest_end() +- */ +-}; +- +-static struct devlink_fmsg *devlink_fmsg_alloc(void) +-{ +- struct devlink_fmsg *fmsg; +- +- fmsg = kzalloc(sizeof(*fmsg), GFP_KERNEL); +- if (!fmsg) +- return NULL; +- +- INIT_LIST_HEAD(&fmsg->item_list); +- +- return fmsg; +-} +- +-static void devlink_fmsg_free(struct devlink_fmsg *fmsg) +-{ +- struct devlink_fmsg_item *item, *tmp; +- +- list_for_each_entry_safe(item, tmp, &fmsg->item_list, list) { +- list_del(&item->list); +- kfree(item); +- } +- kfree(fmsg); +-} +- +-static int devlink_fmsg_nest_common(struct devlink_fmsg *fmsg, +- int attrtype) +-{ +- struct devlink_fmsg_item *item; +- +- item = kzalloc(sizeof(*item), GFP_KERNEL); +- if (!item) +- return -ENOMEM; +- +- item->attrtype = attrtype; +- list_add_tail(&item->list, &fmsg->item_list); +- +- return 0; +-} +- +-int devlink_fmsg_obj_nest_start(struct devlink_fmsg *fmsg) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_OBJ_NEST_START); +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_start); +- +-static int devlink_fmsg_nest_end(struct devlink_fmsg *fmsg) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_NEST_END); +-} +- +-int devlink_fmsg_obj_nest_end(struct devlink_fmsg *fmsg) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_nest_end(fmsg); +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_end); +- +-#define DEVLINK_FMSG_MAX_SIZE (GENLMSG_DEFAULT_SIZE - GENL_HDRLEN - NLA_HDRLEN) +- +-static int devlink_fmsg_put_name(struct devlink_fmsg *fmsg, const char *name) +-{ +- struct devlink_fmsg_item *item; +- +- if (fmsg->putting_binary) +- return -EINVAL; +- +- if (strlen(name) + 1 > DEVLINK_FMSG_MAX_SIZE) +- return -EMSGSIZE; +- +- item = kzalloc(sizeof(*item) + strlen(name) + 1, GFP_KERNEL); +- if (!item) +- return -ENOMEM; +- +- item->nla_type = NLA_NUL_STRING; +- item->len = strlen(name) + 1; +- item->attrtype = DEVLINK_ATTR_FMSG_OBJ_NAME; +- memcpy(&item->value, name, item->len); +- list_add_tail(&item->list, &fmsg->item_list); +- +- return 0; +-} +- +-int devlink_fmsg_pair_nest_start(struct devlink_fmsg *fmsg, const char *name) +-{ +- int err; +- +- if (fmsg->putting_binary) +- return -EINVAL; +- +- err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_PAIR_NEST_START); +- if (err) +- return err; +- +- err = devlink_fmsg_put_name(fmsg, name); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_start); +- +-int devlink_fmsg_pair_nest_end(struct devlink_fmsg *fmsg) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_nest_end(fmsg); +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_end); +- +-int devlink_fmsg_arr_pair_nest_start(struct devlink_fmsg *fmsg, +- const char *name) +-{ +- int err; +- +- if (fmsg->putting_binary) +- return -EINVAL; +- +- err = devlink_fmsg_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_ARR_NEST_START); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_start); +- +-int devlink_fmsg_arr_pair_nest_end(struct devlink_fmsg *fmsg) +-{ +- int err; +- +- if (fmsg->putting_binary) +- return -EINVAL; +- +- err = devlink_fmsg_nest_end(fmsg); +- if (err) +- return err; +- +- err = devlink_fmsg_nest_end(fmsg); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_end); +- +-int devlink_fmsg_binary_pair_nest_start(struct devlink_fmsg *fmsg, +- const char *name) +-{ +- int err; +- +- err = devlink_fmsg_arr_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- fmsg->putting_binary = true; +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_nest_start); +- +-int devlink_fmsg_binary_pair_nest_end(struct devlink_fmsg *fmsg) +-{ +- if (!fmsg->putting_binary) +- return -EINVAL; +- +- fmsg->putting_binary = false; +- return devlink_fmsg_arr_pair_nest_end(fmsg); +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_nest_end); +- +-static int devlink_fmsg_put_value(struct devlink_fmsg *fmsg, +- const void *value, u16 value_len, +- u8 value_nla_type) +-{ +- struct devlink_fmsg_item *item; +- +- if (value_len > DEVLINK_FMSG_MAX_SIZE) +- return -EMSGSIZE; +- +- item = kzalloc(sizeof(*item) + value_len, GFP_KERNEL); +- if (!item) +- return -ENOMEM; +- +- item->nla_type = value_nla_type; +- item->len = value_len; +- item->attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA; +- memcpy(&item->value, value, item->len); +- list_add_tail(&item->list, &fmsg->item_list); +- +- return 0; +-} +- +-static int devlink_fmsg_bool_put(struct devlink_fmsg *fmsg, bool value) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_FLAG); +-} +- +-static int devlink_fmsg_u8_put(struct devlink_fmsg *fmsg, u8 value) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U8); +-} +- +-int devlink_fmsg_u32_put(struct devlink_fmsg *fmsg, u32 value) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U32); +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_u32_put); +- +-static int devlink_fmsg_u64_put(struct devlink_fmsg *fmsg, u64 value) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U64); +-} +- +-int devlink_fmsg_string_put(struct devlink_fmsg *fmsg, const char *value) +-{ +- if (fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_put_value(fmsg, value, strlen(value) + 1, +- NLA_NUL_STRING); +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_string_put); +- +-int devlink_fmsg_binary_put(struct devlink_fmsg *fmsg, const void *value, +- u16 value_len) +-{ +- if (!fmsg->putting_binary) +- return -EINVAL; +- +- return devlink_fmsg_put_value(fmsg, value, value_len, NLA_BINARY); +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_binary_put); +- +-int devlink_fmsg_bool_pair_put(struct devlink_fmsg *fmsg, const char *name, +- bool value) +-{ +- int err; +- +- err = devlink_fmsg_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- err = devlink_fmsg_bool_put(fmsg, value); +- if (err) +- return err; +- +- err = devlink_fmsg_pair_nest_end(fmsg); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_bool_pair_put); +- +-int devlink_fmsg_u8_pair_put(struct devlink_fmsg *fmsg, const char *name, +- u8 value) +-{ +- int err; +- +- err = devlink_fmsg_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- err = devlink_fmsg_u8_put(fmsg, value); +- if (err) +- return err; +- +- err = devlink_fmsg_pair_nest_end(fmsg); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_u8_pair_put); +- +-int devlink_fmsg_u32_pair_put(struct devlink_fmsg *fmsg, const char *name, +- u32 value) +-{ +- int err; +- +- err = devlink_fmsg_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- err = devlink_fmsg_u32_put(fmsg, value); +- if (err) +- return err; +- +- err = devlink_fmsg_pair_nest_end(fmsg); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_u32_pair_put); +- +-int devlink_fmsg_u64_pair_put(struct devlink_fmsg *fmsg, const char *name, +- u64 value) +-{ +- int err; +- +- err = devlink_fmsg_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- err = devlink_fmsg_u64_put(fmsg, value); +- if (err) +- return err; +- +- err = devlink_fmsg_pair_nest_end(fmsg); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_u64_pair_put); +- +-int devlink_fmsg_string_pair_put(struct devlink_fmsg *fmsg, const char *name, +- const char *value) +-{ +- int err; +- +- err = devlink_fmsg_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- err = devlink_fmsg_string_put(fmsg, value); +- if (err) +- return err; +- +- err = devlink_fmsg_pair_nest_end(fmsg); +- if (err) +- return err; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_string_pair_put); +- +-int devlink_fmsg_binary_pair_put(struct devlink_fmsg *fmsg, const char *name, +- const void *value, u32 value_len) +-{ +- u32 data_size; +- int end_err; +- u32 offset; +- int err; +- +- err = devlink_fmsg_binary_pair_nest_start(fmsg, name); +- if (err) +- return err; +- +- for (offset = 0; offset < value_len; offset += data_size) { +- data_size = value_len - offset; +- if (data_size > DEVLINK_FMSG_MAX_SIZE) +- data_size = DEVLINK_FMSG_MAX_SIZE; +- err = devlink_fmsg_binary_put(fmsg, value + offset, data_size); +- if (err) +- break; +- /* Exit from loop with a break (instead of +- * return) to make sure putting_binary is turned off in +- * devlink_fmsg_binary_pair_nest_end +- */ +- } +- +- end_err = devlink_fmsg_binary_pair_nest_end(fmsg); +- if (end_err) +- err = end_err; +- +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_put); +- +-static int +-devlink_fmsg_item_fill_type(struct devlink_fmsg_item *msg, struct sk_buff *skb) +-{ +- switch (msg->nla_type) { +- case NLA_FLAG: +- case NLA_U8: +- case NLA_U32: +- case NLA_U64: +- case NLA_NUL_STRING: +- case NLA_BINARY: +- return nla_put_u8(skb, DEVLINK_ATTR_FMSG_OBJ_VALUE_TYPE, +- msg->nla_type); +- default: +- return -EINVAL; +- } +-} +- +-static int +-devlink_fmsg_item_fill_data(struct devlink_fmsg_item *msg, struct sk_buff *skb) +-{ +- int attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA; +- u8 tmp; +- +- switch (msg->nla_type) { +- case NLA_FLAG: +- /* Always provide flag data, regardless of its value */ +- tmp = *(bool *) msg->value; +- +- return nla_put_u8(skb, attrtype, tmp); +- case NLA_U8: +- return nla_put_u8(skb, attrtype, *(u8 *) msg->value); +- case NLA_U32: +- return nla_put_u32(skb, attrtype, *(u32 *) msg->value); +- case NLA_U64: +- return nla_put_u64_64bit(skb, attrtype, *(u64 *) msg->value, +- DEVLINK_ATTR_PAD); +- case NLA_NUL_STRING: +- return nla_put_string(skb, attrtype, (char *) &msg->value); +- case NLA_BINARY: +- return nla_put(skb, attrtype, msg->len, (void *) &msg->value); +- default: +- return -EINVAL; +- } +-} +- +-static int +-devlink_fmsg_prepare_skb(struct devlink_fmsg *fmsg, struct sk_buff *skb, +- int *start) +-{ +- struct devlink_fmsg_item *item; +- struct nlattr *fmsg_nlattr; +- int i = 0; +- int err; +- +- fmsg_nlattr = nla_nest_start_noflag(skb, DEVLINK_ATTR_FMSG); +- if (!fmsg_nlattr) +- return -EMSGSIZE; +- +- list_for_each_entry(item, &fmsg->item_list, list) { +- if (i < *start) { +- i++; +- continue; +- } +- +- switch (item->attrtype) { +- case DEVLINK_ATTR_FMSG_OBJ_NEST_START: +- case DEVLINK_ATTR_FMSG_PAIR_NEST_START: +- case DEVLINK_ATTR_FMSG_ARR_NEST_START: +- case DEVLINK_ATTR_FMSG_NEST_END: +- err = nla_put_flag(skb, item->attrtype); +- break; +- case DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA: +- err = devlink_fmsg_item_fill_type(item, skb); +- if (err) +- break; +- err = devlink_fmsg_item_fill_data(item, skb); +- break; +- case DEVLINK_ATTR_FMSG_OBJ_NAME: +- err = nla_put_string(skb, item->attrtype, +- (char *) &item->value); +- break; +- default: +- err = -EINVAL; +- break; +- } +- if (!err) +- *start = ++i; +- else +- break; +- } +- +- nla_nest_end(skb, fmsg_nlattr); +- return err; +-} +- +-static int devlink_fmsg_snd(struct devlink_fmsg *fmsg, +- struct genl_info *info, +- enum devlink_command cmd, int flags) +-{ +- struct nlmsghdr *nlh; +- struct sk_buff *skb; +- bool last = false; +- int index = 0; +- void *hdr; +- int err; +- +- while (!last) { +- int tmp_index = index; +- +- skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!skb) +- return -ENOMEM; +- +- hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, +- &devlink_nl_family, flags | NLM_F_MULTI, cmd); +- if (!hdr) { +- err = -EMSGSIZE; +- goto nla_put_failure; +- } +- +- err = devlink_fmsg_prepare_skb(fmsg, skb, &index); +- if (!err) +- last = true; +- else if (err != -EMSGSIZE || tmp_index == index) +- goto nla_put_failure; +- +- genlmsg_end(skb, hdr); +- err = genlmsg_reply(skb, info); +- if (err) +- return err; +- } +- +- skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!skb) +- return -ENOMEM; +- nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, +- NLMSG_DONE, 0, flags | NLM_F_MULTI); +- if (!nlh) { +- err = -EMSGSIZE; +- goto nla_put_failure; +- } +- +- return genlmsg_reply(skb, info); +- +-nla_put_failure: +- nlmsg_free(skb); +- return err; +-} +- +-static int devlink_fmsg_dumpit(struct devlink_fmsg *fmsg, struct sk_buff *skb, +- struct netlink_callback *cb, +- enum devlink_command cmd) +-{ +- int index = cb->args[0]; +- int tmp_index = index; +- void *hdr; +- int err; +- +- hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, +- &devlink_nl_family, NLM_F_ACK | NLM_F_MULTI, cmd); +- if (!hdr) { +- err = -EMSGSIZE; +- goto nla_put_failure; +- } +- +- err = devlink_fmsg_prepare_skb(fmsg, skb, &index); +- if ((err && err != -EMSGSIZE) || tmp_index == index) +- goto nla_put_failure; +- +- cb->args[0] = index; +- genlmsg_end(skb, hdr); +- return skb->len; +- +-nla_put_failure: +- genlmsg_cancel(skb, hdr); +- return err; +-} +- +-struct devlink_health_reporter { +- struct list_head list; +- void *priv; +- const struct devlink_health_reporter_ops *ops; +- struct devlink *devlink; +- struct devlink_port *devlink_port; +- struct devlink_fmsg *dump_fmsg; +- struct mutex dump_lock; /* lock parallel read/write from dump buffers */ +- u64 graceful_period; +- bool auto_recover; +- bool auto_dump; +- u8 health_state; +- u64 dump_ts; +- u64 dump_real_ts; +- u64 error_count; +- u64 recovery_count; +- u64 last_recovery_ts; +- refcount_t refcount; +-}; +- +-void * +-devlink_health_reporter_priv(struct devlink_health_reporter *reporter) +-{ +- return reporter->priv; +-} +-EXPORT_SYMBOL_GPL(devlink_health_reporter_priv); +- +-static struct devlink_health_reporter * +-__devlink_health_reporter_find_by_name(struct list_head *reporter_list, +- struct mutex *list_lock, +- const char *reporter_name) +-{ +- struct devlink_health_reporter *reporter; +- +- lockdep_assert_held(list_lock); +- list_for_each_entry(reporter, reporter_list, list) +- if (!strcmp(reporter->ops->name, reporter_name)) +- return reporter; +- return NULL; +-} +- +-static struct devlink_health_reporter * +-devlink_health_reporter_find_by_name(struct devlink *devlink, +- const char *reporter_name) +-{ +- return __devlink_health_reporter_find_by_name(&devlink->reporter_list, +- &devlink->reporters_lock, +- reporter_name); +-} +- +-static struct devlink_health_reporter * +-devlink_port_health_reporter_find_by_name(struct devlink_port *devlink_port, +- const char *reporter_name) +-{ +- return __devlink_health_reporter_find_by_name(&devlink_port->reporter_list, +- &devlink_port->reporters_lock, +- reporter_name); +-} +- +-static struct devlink_health_reporter * +-__devlink_health_reporter_create(struct devlink *devlink, +- const struct devlink_health_reporter_ops *ops, +- u64 graceful_period, void *priv) +-{ +- struct devlink_health_reporter *reporter; +- +- if (WARN_ON(graceful_period && !ops->recover)) +- return ERR_PTR(-EINVAL); +- +- reporter = kzalloc(sizeof(*reporter), GFP_KERNEL); +- if (!reporter) +- return ERR_PTR(-ENOMEM); +- +- reporter->priv = priv; +- reporter->ops = ops; +- reporter->devlink = devlink; +- reporter->graceful_period = graceful_period; +- reporter->auto_recover = !!ops->recover; +- reporter->auto_dump = !!ops->dump; +- mutex_init(&reporter->dump_lock); +- refcount_set(&reporter->refcount, 1); +- return reporter; +-} +- +-/** +- * devlink_port_health_reporter_create - create devlink health reporter for +- * specified port instance +- * +- * @port: devlink_port which should contain the new reporter +- * @ops: ops +- * @graceful_period: to avoid recovery loops, in msecs +- * @priv: priv +- */ +-struct devlink_health_reporter * +-devlink_port_health_reporter_create(struct devlink_port *port, +- const struct devlink_health_reporter_ops *ops, +- u64 graceful_period, void *priv) +-{ +- struct devlink_health_reporter *reporter; +- +- mutex_lock(&port->reporters_lock); +- if (__devlink_health_reporter_find_by_name(&port->reporter_list, +- &port->reporters_lock, ops->name)) { +- reporter = ERR_PTR(-EEXIST); +- goto unlock; +- } +- +- reporter = __devlink_health_reporter_create(port->devlink, ops, +- graceful_period, priv); +- if (IS_ERR(reporter)) +- goto unlock; +- +- reporter->devlink_port = port; +- list_add_tail(&reporter->list, &port->reporter_list); +-unlock: +- mutex_unlock(&port->reporters_lock); +- return reporter; +-} +-EXPORT_SYMBOL_GPL(devlink_port_health_reporter_create); +- +-/** +- * devlink_health_reporter_create - create devlink health reporter +- * +- * @devlink: devlink +- * @ops: ops +- * @graceful_period: to avoid recovery loops, in msecs +- * @priv: priv +- */ +-struct devlink_health_reporter * +-devlink_health_reporter_create(struct devlink *devlink, +- const struct devlink_health_reporter_ops *ops, +- u64 graceful_period, void *priv) +-{ +- struct devlink_health_reporter *reporter; +- +- mutex_lock(&devlink->reporters_lock); +- if (devlink_health_reporter_find_by_name(devlink, ops->name)) { +- reporter = ERR_PTR(-EEXIST); +- goto unlock; +- } +- +- reporter = __devlink_health_reporter_create(devlink, ops, +- graceful_period, priv); +- if (IS_ERR(reporter)) +- goto unlock; +- +- list_add_tail(&reporter->list, &devlink->reporter_list); +-unlock: +- mutex_unlock(&devlink->reporters_lock); +- return reporter; +-} +-EXPORT_SYMBOL_GPL(devlink_health_reporter_create); +- +-static void +-devlink_health_reporter_free(struct devlink_health_reporter *reporter) +-{ +- mutex_destroy(&reporter->dump_lock); +- if (reporter->dump_fmsg) +- devlink_fmsg_free(reporter->dump_fmsg); +- kfree(reporter); +-} +- +-static void +-devlink_health_reporter_put(struct devlink_health_reporter *reporter) +-{ +- if (refcount_dec_and_test(&reporter->refcount)) +- devlink_health_reporter_free(reporter); +-} +- +-static void +-__devlink_health_reporter_destroy(struct devlink_health_reporter *reporter) +-{ +- list_del(&reporter->list); +- devlink_health_reporter_put(reporter); +-} +- +-/** +- * devlink_health_reporter_destroy - destroy devlink health reporter +- * +- * @reporter: devlink health reporter to destroy +- */ +-void +-devlink_health_reporter_destroy(struct devlink_health_reporter *reporter) +-{ +- struct mutex *lock = &reporter->devlink->reporters_lock; +- +- mutex_lock(lock); +- __devlink_health_reporter_destroy(reporter); +- mutex_unlock(lock); +-} +-EXPORT_SYMBOL_GPL(devlink_health_reporter_destroy); +- +-/** +- * devlink_port_health_reporter_destroy - destroy devlink port health reporter +- * +- * @reporter: devlink health reporter to destroy +- */ +-void +-devlink_port_health_reporter_destroy(struct devlink_health_reporter *reporter) +-{ +- struct mutex *lock = &reporter->devlink_port->reporters_lock; +- +- mutex_lock(lock); +- __devlink_health_reporter_destroy(reporter); +- mutex_unlock(lock); +-} +-EXPORT_SYMBOL_GPL(devlink_port_health_reporter_destroy); +- +-static int +-devlink_nl_health_reporter_fill(struct sk_buff *msg, +- struct devlink_health_reporter *reporter, +- enum devlink_command cmd, u32 portid, +- u32 seq, int flags) +-{ +- struct devlink *devlink = reporter->devlink; +- struct nlattr *reporter_attr; +- void *hdr; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto genlmsg_cancel; +- +- if (reporter->devlink_port) { +- if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, reporter->devlink_port->index)) +- goto genlmsg_cancel; +- } +- reporter_attr = nla_nest_start_noflag(msg, +- DEVLINK_ATTR_HEALTH_REPORTER); +- if (!reporter_attr) +- goto genlmsg_cancel; +- if (nla_put_string(msg, DEVLINK_ATTR_HEALTH_REPORTER_NAME, +- reporter->ops->name)) +- goto reporter_nest_cancel; +- if (nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_STATE, +- reporter->health_state)) +- goto reporter_nest_cancel; +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT, +- reporter->error_count, DEVLINK_ATTR_PAD)) +- goto reporter_nest_cancel; +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT, +- reporter->recovery_count, DEVLINK_ATTR_PAD)) +- goto reporter_nest_cancel; +- if (reporter->ops->recover && +- nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD, +- reporter->graceful_period, +- DEVLINK_ATTR_PAD)) +- goto reporter_nest_cancel; +- if (reporter->ops->recover && +- nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER, +- reporter->auto_recover)) +- goto reporter_nest_cancel; +- if (reporter->dump_fmsg && +- nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS, +- jiffies_to_msecs(reporter->dump_ts), +- DEVLINK_ATTR_PAD)) +- goto reporter_nest_cancel; +- if (reporter->dump_fmsg && +- nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS_NS, +- reporter->dump_real_ts, DEVLINK_ATTR_PAD)) +- goto reporter_nest_cancel; +- if (reporter->ops->dump && +- nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP, +- reporter->auto_dump)) +- goto reporter_nest_cancel; +- +- nla_nest_end(msg, reporter_attr); +- genlmsg_end(msg, hdr); +- return 0; +- +-reporter_nest_cancel: +- nla_nest_end(msg, reporter_attr); +-genlmsg_cancel: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static void devlink_recover_notify(struct devlink_health_reporter *reporter, +- enum devlink_command cmd) +-{ +- struct devlink *devlink = reporter->devlink; +- struct sk_buff *msg; +- int err; +- +- WARN_ON(cmd != DEVLINK_CMD_HEALTH_REPORTER_RECOVER); +- WARN_ON(!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)); +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_health_reporter_fill(msg, reporter, cmd, 0, 0, 0); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, +- 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-void +-devlink_health_reporter_recovery_done(struct devlink_health_reporter *reporter) +-{ +- reporter->recovery_count++; +- reporter->last_recovery_ts = jiffies; +-} +-EXPORT_SYMBOL_GPL(devlink_health_reporter_recovery_done); +- +-static int +-devlink_health_reporter_recover(struct devlink_health_reporter *reporter, +- void *priv_ctx, struct netlink_ext_ack *extack) +-{ +- int err; +- +- if (reporter->health_state == DEVLINK_HEALTH_REPORTER_STATE_HEALTHY) +- return 0; +- +- if (!reporter->ops->recover) +- return -EOPNOTSUPP; +- +- err = reporter->ops->recover(reporter, priv_ctx, extack); +- if (err) +- return err; +- +- devlink_health_reporter_recovery_done(reporter); +- reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_HEALTHY; +- devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER); +- +- return 0; +-} +- +-static void +-devlink_health_dump_clear(struct devlink_health_reporter *reporter) +-{ +- if (!reporter->dump_fmsg) +- return; +- devlink_fmsg_free(reporter->dump_fmsg); +- reporter->dump_fmsg = NULL; +-} +- +-static int devlink_health_do_dump(struct devlink_health_reporter *reporter, +- void *priv_ctx, +- struct netlink_ext_ack *extack) +-{ +- int err; +- +- if (!reporter->ops->dump) +- return 0; +- +- if (reporter->dump_fmsg) +- return 0; +- +- reporter->dump_fmsg = devlink_fmsg_alloc(); +- if (!reporter->dump_fmsg) { +- err = -ENOMEM; +- return err; +- } +- +- err = devlink_fmsg_obj_nest_start(reporter->dump_fmsg); +- if (err) +- goto dump_err; +- +- err = reporter->ops->dump(reporter, reporter->dump_fmsg, +- priv_ctx, extack); +- if (err) +- goto dump_err; +- +- err = devlink_fmsg_obj_nest_end(reporter->dump_fmsg); +- if (err) +- goto dump_err; +- +- reporter->dump_ts = jiffies; +- reporter->dump_real_ts = ktime_get_real_ns(); +- +- return 0; +- +-dump_err: +- devlink_health_dump_clear(reporter); +- return err; +-} +- +-int devlink_health_report(struct devlink_health_reporter *reporter, +- const char *msg, void *priv_ctx) +-{ +- enum devlink_health_reporter_state prev_health_state; +- struct devlink *devlink = reporter->devlink; +- unsigned long recover_ts_threshold; +- int ret; +- +- /* write a log message of the current error */ +- WARN_ON(!msg); +- trace_devlink_health_report(devlink, reporter->ops->name, msg); +- reporter->error_count++; +- prev_health_state = reporter->health_state; +- reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_ERROR; +- devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER); +- +- /* abort if the previous error wasn't recovered */ +- recover_ts_threshold = reporter->last_recovery_ts + +- msecs_to_jiffies(reporter->graceful_period); +- if (reporter->auto_recover && +- (prev_health_state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY || +- (reporter->last_recovery_ts && reporter->recovery_count && +- time_is_after_jiffies(recover_ts_threshold)))) { +- trace_devlink_health_recover_aborted(devlink, +- reporter->ops->name, +- reporter->health_state, +- jiffies - +- reporter->last_recovery_ts); +- return -ECANCELED; +- } +- +- reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_ERROR; +- +- if (reporter->auto_dump) { +- mutex_lock(&reporter->dump_lock); +- /* store current dump of current error, for later analysis */ +- devlink_health_do_dump(reporter, priv_ctx, NULL); +- mutex_unlock(&reporter->dump_lock); +- } +- +- if (!reporter->auto_recover) +- return 0; +- +- devl_lock(devlink); +- ret = devlink_health_reporter_recover(reporter, priv_ctx, NULL); +- devl_unlock(devlink); +- +- return ret; +-} +-EXPORT_SYMBOL_GPL(devlink_health_report); +- +-static struct devlink_health_reporter * +-devlink_health_reporter_get_from_attrs(struct devlink *devlink, +- struct nlattr **attrs) +-{ +- struct devlink_health_reporter *reporter; +- struct devlink_port *devlink_port; +- char *reporter_name; +- +- if (!attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME]) +- return NULL; +- +- reporter_name = nla_data(attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME]); +- devlink_port = devlink_port_get_from_attrs(devlink, attrs); +- if (IS_ERR(devlink_port)) { +- mutex_lock(&devlink->reporters_lock); +- reporter = devlink_health_reporter_find_by_name(devlink, reporter_name); +- if (reporter) +- refcount_inc(&reporter->refcount); +- mutex_unlock(&devlink->reporters_lock); +- } else { +- mutex_lock(&devlink_port->reporters_lock); +- reporter = devlink_port_health_reporter_find_by_name(devlink_port, reporter_name); +- if (reporter) +- refcount_inc(&reporter->refcount); +- mutex_unlock(&devlink_port->reporters_lock); +- } +- +- return reporter; +-} +- +-static struct devlink_health_reporter * +-devlink_health_reporter_get_from_info(struct devlink *devlink, +- struct genl_info *info) +-{ +- return devlink_health_reporter_get_from_attrs(devlink, info->attrs); +-} +- +-static struct devlink_health_reporter * +-devlink_health_reporter_get_from_cb(struct netlink_callback *cb) +-{ +- const struct genl_dumpit_info *info = genl_dumpit_info(cb); +- struct devlink_health_reporter *reporter; +- struct nlattr **attrs = info->attrs; +- struct devlink *devlink; +- +- devlink = devlink_get_from_attrs(sock_net(cb->skb->sk), attrs); +- if (IS_ERR(devlink)) +- return NULL; +- +- reporter = devlink_health_reporter_get_from_attrs(devlink, attrs); +- devlink_put(devlink); +- return reporter; +-} +- +-void +-devlink_health_reporter_state_update(struct devlink_health_reporter *reporter, +- enum devlink_health_reporter_state state) +-{ +- if (WARN_ON(state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY && +- state != DEVLINK_HEALTH_REPORTER_STATE_ERROR)) +- return; +- +- if (reporter->health_state == state) +- return; +- +- reporter->health_state = state; +- trace_devlink_health_reporter_state_update(reporter->devlink, +- reporter->ops->name, state); +- devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER); +-} +-EXPORT_SYMBOL_GPL(devlink_health_reporter_state_update); +- +-static int devlink_nl_cmd_health_reporter_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_health_reporter *reporter; +- struct sk_buff *msg; +- int err; +- +- reporter = devlink_health_reporter_get_from_info(devlink, info); +- if (!reporter) +- return -EINVAL; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) { +- err = -ENOMEM; +- goto out; +- } +- +- err = devlink_nl_health_reporter_fill(msg, reporter, +- DEVLINK_CMD_HEALTH_REPORTER_GET, +- info->snd_portid, info->snd_seq, +- 0); +- if (err) { +- nlmsg_free(msg); +- goto out; +- } +- +- err = genlmsg_reply(msg, info); +-out: +- devlink_health_reporter_put(reporter); +- return err; +-} +- +-static int +-devlink_nl_cmd_health_reporter_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink_health_reporter *reporter; +- struct devlink_port *port; +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- mutex_lock(&devlink->reporters_lock); +- list_for_each_entry(reporter, &devlink->reporter_list, +- list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_health_reporter_fill( +- msg, reporter, DEVLINK_CMD_HEALTH_REPORTER_GET, +- NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, +- NLM_F_MULTI); +- if (err) { +- mutex_unlock(&devlink->reporters_lock); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- mutex_unlock(&devlink->reporters_lock); +- devlink_put(devlink); +- } +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(port, &devlink->port_list, list) { +- mutex_lock(&port->reporters_lock); +- list_for_each_entry(reporter, &port->reporter_list, list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_health_reporter_fill( +- msg, reporter, +- DEVLINK_CMD_HEALTH_REPORTER_GET, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, NLM_F_MULTI); +- if (err) { +- mutex_unlock(&port->reporters_lock); +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- mutex_unlock(&port->reporters_lock); +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int +-devlink_nl_cmd_health_reporter_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_health_reporter *reporter; +- int err; +- +- reporter = devlink_health_reporter_get_from_info(devlink, info); +- if (!reporter) +- return -EINVAL; +- +- if (!reporter->ops->recover && +- (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] || +- info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER])) { +- err = -EOPNOTSUPP; +- goto out; +- } +- if (!reporter->ops->dump && +- info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP]) { +- err = -EOPNOTSUPP; +- goto out; +- } +- +- if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD]) +- reporter->graceful_period = +- nla_get_u64(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD]); +- +- if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]) +- reporter->auto_recover = +- nla_get_u8(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]); +- +- if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP]) +- reporter->auto_dump = +- nla_get_u8(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP]); +- +- devlink_health_reporter_put(reporter); +- return 0; +-out: +- devlink_health_reporter_put(reporter); +- return err; +-} +- +-static int devlink_nl_cmd_health_reporter_recover_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_health_reporter *reporter; +- int err; +- +- reporter = devlink_health_reporter_get_from_info(devlink, info); +- if (!reporter) +- return -EINVAL; +- +- err = devlink_health_reporter_recover(reporter, NULL, info->extack); +- +- devlink_health_reporter_put(reporter); +- return err; +-} +- +-static int devlink_nl_cmd_health_reporter_diagnose_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_health_reporter *reporter; +- struct devlink_fmsg *fmsg; +- int err; +- +- reporter = devlink_health_reporter_get_from_info(devlink, info); +- if (!reporter) +- return -EINVAL; +- +- if (!reporter->ops->diagnose) { +- devlink_health_reporter_put(reporter); +- return -EOPNOTSUPP; +- } +- +- fmsg = devlink_fmsg_alloc(); +- if (!fmsg) { +- devlink_health_reporter_put(reporter); +- return -ENOMEM; +- } +- +- err = devlink_fmsg_obj_nest_start(fmsg); +- if (err) +- goto out; +- +- err = reporter->ops->diagnose(reporter, fmsg, info->extack); +- if (err) +- goto out; +- +- err = devlink_fmsg_obj_nest_end(fmsg); +- if (err) +- goto out; +- +- err = devlink_fmsg_snd(fmsg, info, +- DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE, 0); +- +-out: +- devlink_fmsg_free(fmsg); +- devlink_health_reporter_put(reporter); +- return err; +-} +- +-static int +-devlink_nl_cmd_health_reporter_dump_get_dumpit(struct sk_buff *skb, +- struct netlink_callback *cb) +-{ +- struct devlink_health_reporter *reporter; +- u64 start = cb->args[0]; +- int err; +- +- reporter = devlink_health_reporter_get_from_cb(cb); +- if (!reporter) +- return -EINVAL; +- +- if (!reporter->ops->dump) { +- err = -EOPNOTSUPP; +- goto out; +- } +- mutex_lock(&reporter->dump_lock); +- if (!start) { +- err = devlink_health_do_dump(reporter, NULL, cb->extack); +- if (err) +- goto unlock; +- cb->args[1] = reporter->dump_ts; +- } +- if (!reporter->dump_fmsg || cb->args[1] != reporter->dump_ts) { +- NL_SET_ERR_MSG_MOD(cb->extack, "Dump trampled, please retry"); +- err = -EAGAIN; +- goto unlock; +- } +- +- err = devlink_fmsg_dumpit(reporter->dump_fmsg, skb, cb, +- DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET); +-unlock: +- mutex_unlock(&reporter->dump_lock); +-out: +- devlink_health_reporter_put(reporter); +- return err; +-} +- +-static int +-devlink_nl_cmd_health_reporter_dump_clear_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_health_reporter *reporter; +- +- reporter = devlink_health_reporter_get_from_info(devlink, info); +- if (!reporter) +- return -EINVAL; +- +- if (!reporter->ops->dump) { +- devlink_health_reporter_put(reporter); +- return -EOPNOTSUPP; +- } +- +- mutex_lock(&reporter->dump_lock); +- devlink_health_dump_clear(reporter); +- mutex_unlock(&reporter->dump_lock); +- devlink_health_reporter_put(reporter); +- return 0; +-} +- +-static int devlink_nl_cmd_health_reporter_test_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink *devlink = info->user_ptr[0]; +- struct devlink_health_reporter *reporter; +- int err; +- +- reporter = devlink_health_reporter_get_from_info(devlink, info); +- if (!reporter) +- return -EINVAL; +- +- if (!reporter->ops->test) { +- devlink_health_reporter_put(reporter); +- return -EOPNOTSUPP; +- } +- +- err = reporter->ops->test(reporter, info->extack); +- +- devlink_health_reporter_put(reporter); +- return err; +-} +- +-struct devlink_stats { +- u64_stats_t rx_bytes; +- u64_stats_t rx_packets; +- struct u64_stats_sync syncp; +-}; +- +-/** +- * struct devlink_trap_policer_item - Packet trap policer attributes. +- * @policer: Immutable packet trap policer attributes. +- * @rate: Rate in packets / sec. +- * @burst: Burst size in packets. +- * @list: trap_policer_list member. +- * +- * Describes packet trap policer attributes. Created by devlink during trap +- * policer registration. +- */ +-struct devlink_trap_policer_item { +- const struct devlink_trap_policer *policer; +- u64 rate; +- u64 burst; +- struct list_head list; +-}; +- +-/** +- * struct devlink_trap_group_item - Packet trap group attributes. +- * @group: Immutable packet trap group attributes. +- * @policer_item: Associated policer item. Can be NULL. +- * @list: trap_group_list member. +- * @stats: Trap group statistics. +- * +- * Describes packet trap group attributes. Created by devlink during trap +- * group registration. +- */ +-struct devlink_trap_group_item { +- const struct devlink_trap_group *group; +- struct devlink_trap_policer_item *policer_item; +- struct list_head list; +- struct devlink_stats __percpu *stats; +-}; +- +-/** +- * struct devlink_trap_item - Packet trap attributes. +- * @trap: Immutable packet trap attributes. +- * @group_item: Associated group item. +- * @list: trap_list member. +- * @action: Trap action. +- * @stats: Trap statistics. +- * @priv: Driver private information. +- * +- * Describes both mutable and immutable packet trap attributes. Created by +- * devlink during trap registration and used for all trap related operations. +- */ +-struct devlink_trap_item { +- const struct devlink_trap *trap; +- struct devlink_trap_group_item *group_item; +- struct list_head list; +- enum devlink_trap_action action; +- struct devlink_stats __percpu *stats; +- void *priv; +-}; +- +-static struct devlink_trap_policer_item * +-devlink_trap_policer_item_lookup(struct devlink *devlink, u32 id) +-{ +- struct devlink_trap_policer_item *policer_item; +- +- list_for_each_entry(policer_item, &devlink->trap_policer_list, list) { +- if (policer_item->policer->id == id) +- return policer_item; +- } +- +- return NULL; +-} +- +-static struct devlink_trap_item * +-devlink_trap_item_lookup(struct devlink *devlink, const char *name) +-{ +- struct devlink_trap_item *trap_item; +- +- list_for_each_entry(trap_item, &devlink->trap_list, list) { +- if (!strcmp(trap_item->trap->name, name)) +- return trap_item; +- } +- +- return NULL; +-} +- +-static struct devlink_trap_item * +-devlink_trap_item_get_from_info(struct devlink *devlink, +- struct genl_info *info) +-{ +- struct nlattr *attr; +- +- if (!info->attrs[DEVLINK_ATTR_TRAP_NAME]) +- return NULL; +- attr = info->attrs[DEVLINK_ATTR_TRAP_NAME]; +- +- return devlink_trap_item_lookup(devlink, nla_data(attr)); +-} +- +-static int +-devlink_trap_action_get_from_info(struct genl_info *info, +- enum devlink_trap_action *p_trap_action) +-{ +- u8 val; +- +- val = nla_get_u8(info->attrs[DEVLINK_ATTR_TRAP_ACTION]); +- switch (val) { +- case DEVLINK_TRAP_ACTION_DROP: +- case DEVLINK_TRAP_ACTION_TRAP: +- case DEVLINK_TRAP_ACTION_MIRROR: +- *p_trap_action = val; +- break; +- default: +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int devlink_trap_metadata_put(struct sk_buff *msg, +- const struct devlink_trap *trap) +-{ +- struct nlattr *attr; +- +- attr = nla_nest_start(msg, DEVLINK_ATTR_TRAP_METADATA); +- if (!attr) +- return -EMSGSIZE; +- +- if ((trap->metadata_cap & DEVLINK_TRAP_METADATA_TYPE_F_IN_PORT) && +- nla_put_flag(msg, DEVLINK_ATTR_TRAP_METADATA_TYPE_IN_PORT)) +- goto nla_put_failure; +- if ((trap->metadata_cap & DEVLINK_TRAP_METADATA_TYPE_F_FA_COOKIE) && +- nla_put_flag(msg, DEVLINK_ATTR_TRAP_METADATA_TYPE_FA_COOKIE)) +- goto nla_put_failure; +- +- nla_nest_end(msg, attr); +- +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, attr); +- return -EMSGSIZE; +-} +- +-static void devlink_trap_stats_read(struct devlink_stats __percpu *trap_stats, +- struct devlink_stats *stats) +-{ +- int i; +- +- memset(stats, 0, sizeof(*stats)); +- for_each_possible_cpu(i) { +- struct devlink_stats *cpu_stats; +- u64 rx_packets, rx_bytes; +- unsigned int start; +- +- cpu_stats = per_cpu_ptr(trap_stats, i); +- do { +- start = u64_stats_fetch_begin_irq(&cpu_stats->syncp); +- rx_packets = u64_stats_read(&cpu_stats->rx_packets); +- rx_bytes = u64_stats_read(&cpu_stats->rx_bytes); +- } while (u64_stats_fetch_retry_irq(&cpu_stats->syncp, start)); +- +- u64_stats_add(&stats->rx_packets, rx_packets); +- u64_stats_add(&stats->rx_bytes, rx_bytes); +- } +-} +- +-static int +-devlink_trap_group_stats_put(struct sk_buff *msg, +- struct devlink_stats __percpu *trap_stats) +-{ +- struct devlink_stats stats; +- struct nlattr *attr; +- +- devlink_trap_stats_read(trap_stats, &stats); +- +- attr = nla_nest_start(msg, DEVLINK_ATTR_STATS); +- if (!attr) +- return -EMSGSIZE; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_PACKETS, +- u64_stats_read(&stats.rx_packets), +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_BYTES, +- u64_stats_read(&stats.rx_bytes), +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- nla_nest_end(msg, attr); +- +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, attr); +- return -EMSGSIZE; +-} +- +-static int devlink_trap_stats_put(struct sk_buff *msg, struct devlink *devlink, +- const struct devlink_trap_item *trap_item) +-{ +- struct devlink_stats stats; +- struct nlattr *attr; +- u64 drops = 0; +- int err; +- +- if (devlink->ops->trap_drop_counter_get) { +- err = devlink->ops->trap_drop_counter_get(devlink, +- trap_item->trap, +- &drops); +- if (err) +- return err; +- } +- +- devlink_trap_stats_read(trap_item->stats, &stats); +- +- attr = nla_nest_start(msg, DEVLINK_ATTR_STATS); +- if (!attr) +- return -EMSGSIZE; +- +- if (devlink->ops->trap_drop_counter_get && +- nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_DROPPED, drops, +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_PACKETS, +- u64_stats_read(&stats.rx_packets), +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_BYTES, +- u64_stats_read(&stats.rx_bytes), +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- nla_nest_end(msg, attr); +- +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, attr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_trap_fill(struct sk_buff *msg, struct devlink *devlink, +- const struct devlink_trap_item *trap_item, +- enum devlink_command cmd, u32 portid, u32 seq, +- int flags) +-{ +- struct devlink_trap_group_item *group_item = trap_item->group_item; +- void *hdr; +- int err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- +- if (nla_put_string(msg, DEVLINK_ATTR_TRAP_GROUP_NAME, +- group_item->group->name)) +- goto nla_put_failure; +- +- if (nla_put_string(msg, DEVLINK_ATTR_TRAP_NAME, trap_item->trap->name)) +- goto nla_put_failure; +- +- if (nla_put_u8(msg, DEVLINK_ATTR_TRAP_TYPE, trap_item->trap->type)) +- goto nla_put_failure; +- +- if (trap_item->trap->generic && +- nla_put_flag(msg, DEVLINK_ATTR_TRAP_GENERIC)) +- goto nla_put_failure; +- +- if (nla_put_u8(msg, DEVLINK_ATTR_TRAP_ACTION, trap_item->action)) +- goto nla_put_failure; +- +- err = devlink_trap_metadata_put(msg, trap_item->trap); +- if (err) +- goto nla_put_failure; +- +- err = devlink_trap_stats_put(msg, devlink, trap_item); +- if (err) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_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_trap_item *trap_item; +- struct sk_buff *msg; +- int err; +- +- if (list_empty(&devlink->trap_list)) +- return -EOPNOTSUPP; +- +- trap_item = devlink_trap_item_get_from_info(devlink, info); +- if (!trap_item) { +- NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap"); +- return -ENOENT; +- } +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_trap_fill(msg, devlink, trap_item, +- DEVLINK_CMD_TRAP_NEW, info->snd_portid, +- info->snd_seq, 0); +- if (err) +- goto err_trap_fill; +- +- return genlmsg_reply(msg, info); +- +-err_trap_fill: +- nlmsg_free(msg); +- return err; +-} +- +-static int devlink_nl_cmd_trap_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- struct devlink_trap_item *trap_item; +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(trap_item, &devlink->trap_list, list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_trap_fill(msg, devlink, trap_item, +- DEVLINK_CMD_TRAP_NEW, +- NETLINK_CB(cb->skb).portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI); +- if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int __devlink_trap_action_set(struct devlink *devlink, +- struct devlink_trap_item *trap_item, +- enum devlink_trap_action trap_action, +- struct netlink_ext_ack *extack) +-{ +- int err; +- +- if (trap_item->action != trap_action && +- trap_item->trap->type != DEVLINK_TRAP_TYPE_DROP) { +- NL_SET_ERR_MSG_MOD(extack, "Cannot change action of non-drop traps. Skipping"); +- return 0; +- } +- +- err = devlink->ops->trap_action_set(devlink, trap_item->trap, +- trap_action, extack); +- if (err) +- return err; +- +- trap_item->action = trap_action; +- +- return 0; +-} +- +-static int devlink_trap_action_set(struct devlink *devlink, +- struct devlink_trap_item *trap_item, +- struct genl_info *info) +-{ +- enum devlink_trap_action trap_action; +- int err; +- +- if (!info->attrs[DEVLINK_ATTR_TRAP_ACTION]) +- return 0; +- +- err = devlink_trap_action_get_from_info(info, &trap_action); +- if (err) { +- NL_SET_ERR_MSG_MOD(info->extack, "Invalid trap action"); +- return -EINVAL; +- } +- +- return __devlink_trap_action_set(devlink, trap_item, trap_action, +- info->extack); +-} +- +-static int devlink_nl_cmd_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_trap_item *trap_item; +- +- if (list_empty(&devlink->trap_list)) +- return -EOPNOTSUPP; +- +- trap_item = devlink_trap_item_get_from_info(devlink, info); +- if (!trap_item) { +- NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap"); +- return -ENOENT; +- } +- +- return devlink_trap_action_set(devlink, trap_item, info); +-} +- +-static struct devlink_trap_group_item * +-devlink_trap_group_item_lookup(struct devlink *devlink, const char *name) +-{ +- struct devlink_trap_group_item *group_item; +- +- list_for_each_entry(group_item, &devlink->trap_group_list, list) { +- if (!strcmp(group_item->group->name, name)) +- return group_item; +- } +- +- return NULL; +-} +- +-static struct devlink_trap_group_item * +-devlink_trap_group_item_lookup_by_id(struct devlink *devlink, u16 id) +-{ +- struct devlink_trap_group_item *group_item; +- +- list_for_each_entry(group_item, &devlink->trap_group_list, list) { +- if (group_item->group->id == id) +- return group_item; +- } +- +- return NULL; +-} +- +-static struct devlink_trap_group_item * +-devlink_trap_group_item_get_from_info(struct devlink *devlink, +- struct genl_info *info) +-{ +- char *name; +- +- if (!info->attrs[DEVLINK_ATTR_TRAP_GROUP_NAME]) +- return NULL; +- name = nla_data(info->attrs[DEVLINK_ATTR_TRAP_GROUP_NAME]); +- +- return devlink_trap_group_item_lookup(devlink, name); +-} +- +-static int +-devlink_nl_trap_group_fill(struct sk_buff *msg, struct devlink *devlink, +- const struct devlink_trap_group_item *group_item, +- enum devlink_command cmd, u32 portid, u32 seq, +- int flags) +-{ +- void *hdr; +- int err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- +- if (nla_put_string(msg, DEVLINK_ATTR_TRAP_GROUP_NAME, +- group_item->group->name)) +- goto nla_put_failure; +- +- if (group_item->group->generic && +- nla_put_flag(msg, DEVLINK_ATTR_TRAP_GENERIC)) +- goto nla_put_failure; +- +- if (group_item->policer_item && +- nla_put_u32(msg, DEVLINK_ATTR_TRAP_POLICER_ID, +- group_item->policer_item->policer->id)) +- goto nla_put_failure; +- +- err = devlink_trap_group_stats_put(msg, group_item->stats); +- if (err) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_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_trap_group_item *group_item; +- struct sk_buff *msg; +- int err; +- +- if (list_empty(&devlink->trap_group_list)) +- return -EOPNOTSUPP; +- +- group_item = devlink_trap_group_item_get_from_info(devlink, info); +- if (!group_item) { +- NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap group"); +- return -ENOENT; +- } +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_trap_group_fill(msg, devlink, group_item, +- DEVLINK_CMD_TRAP_GROUP_NEW, +- info->snd_portid, info->snd_seq, 0); +- if (err) +- goto err_trap_group_fill; +- +- return genlmsg_reply(msg, info); +- +-err_trap_group_fill: +- nlmsg_free(msg); +- return err; +-} +- +-static int devlink_nl_cmd_trap_group_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- enum devlink_command cmd = DEVLINK_CMD_TRAP_GROUP_NEW; +- struct devlink_trap_group_item *group_item; +- u32 portid = NETLINK_CB(cb->skb).portid; +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(group_item, &devlink->trap_group_list, +- list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_trap_group_fill(msg, devlink, +- group_item, cmd, +- portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI); +- if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int +-__devlink_trap_group_action_set(struct devlink *devlink, +- struct devlink_trap_group_item *group_item, +- enum devlink_trap_action trap_action, +- struct netlink_ext_ack *extack) +-{ +- const char *group_name = group_item->group->name; +- struct devlink_trap_item *trap_item; +- int err; +- +- if (devlink->ops->trap_group_action_set) { +- err = devlink->ops->trap_group_action_set(devlink, group_item->group, +- trap_action, extack); +- if (err) +- return err; +- +- list_for_each_entry(trap_item, &devlink->trap_list, list) { +- if (strcmp(trap_item->group_item->group->name, group_name)) +- continue; +- if (trap_item->action != trap_action && +- trap_item->trap->type != DEVLINK_TRAP_TYPE_DROP) +- continue; +- trap_item->action = trap_action; +- } +- +- return 0; +- } +- +- list_for_each_entry(trap_item, &devlink->trap_list, list) { +- if (strcmp(trap_item->group_item->group->name, group_name)) +- continue; +- err = __devlink_trap_action_set(devlink, trap_item, +- trap_action, extack); +- if (err) +- return err; +- } +- +- return 0; +-} +- +-static int +-devlink_trap_group_action_set(struct devlink *devlink, +- struct devlink_trap_group_item *group_item, +- struct genl_info *info, bool *p_modified) +-{ +- enum devlink_trap_action trap_action; +- int err; +- +- if (!info->attrs[DEVLINK_ATTR_TRAP_ACTION]) +- return 0; +- +- err = devlink_trap_action_get_from_info(info, &trap_action); +- if (err) { +- NL_SET_ERR_MSG_MOD(info->extack, "Invalid trap action"); +- return -EINVAL; +- } +- +- err = __devlink_trap_group_action_set(devlink, group_item, trap_action, +- info->extack); +- if (err) +- return err; +- +- *p_modified = true; +- +- return 0; +-} +- +-static int devlink_trap_group_set(struct devlink *devlink, +- struct devlink_trap_group_item *group_item, +- struct genl_info *info) +-{ +- struct devlink_trap_policer_item *policer_item; +- struct netlink_ext_ack *extack = info->extack; +- const struct devlink_trap_policer *policer; +- struct nlattr **attrs = info->attrs; +- int err; +- +- if (!attrs[DEVLINK_ATTR_TRAP_POLICER_ID]) +- return 0; +- +- if (!devlink->ops->trap_group_set) +- return -EOPNOTSUPP; +- +- policer_item = group_item->policer_item; +- if (attrs[DEVLINK_ATTR_TRAP_POLICER_ID]) { +- u32 policer_id; +- +- policer_id = nla_get_u32(attrs[DEVLINK_ATTR_TRAP_POLICER_ID]); +- policer_item = devlink_trap_policer_item_lookup(devlink, +- policer_id); +- if (policer_id && !policer_item) { +- NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap policer"); +- return -ENOENT; +- } +- } +- policer = policer_item ? policer_item->policer : NULL; +- +- err = devlink->ops->trap_group_set(devlink, group_item->group, policer, +- extack); +- if (err) +- return err; +- +- group_item->policer_item = policer_item; +- +- return 0; +-} +- +-static int devlink_nl_cmd_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_trap_group_item *group_item; +- bool modified = false; +- int err; +- +- if (list_empty(&devlink->trap_group_list)) +- return -EOPNOTSUPP; +- +- group_item = devlink_trap_group_item_get_from_info(devlink, info); +- if (!group_item) { +- NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap group"); +- return -ENOENT; +- } +- +- err = devlink_trap_group_action_set(devlink, group_item, info, +- &modified); +- if (err) +- return err; +- +- err = devlink_trap_group_set(devlink, group_item, info); +- if (err) +- goto err_trap_group_set; +- +- return 0; +- +-err_trap_group_set: +- if (modified) +- NL_SET_ERR_MSG_MOD(extack, "Trap group set failed, but some changes were committed already"); +- return err; +-} +- +-static struct devlink_trap_policer_item * +-devlink_trap_policer_item_get_from_info(struct devlink *devlink, +- struct genl_info *info) +-{ +- u32 id; +- +- if (!info->attrs[DEVLINK_ATTR_TRAP_POLICER_ID]) +- return NULL; +- id = nla_get_u32(info->attrs[DEVLINK_ATTR_TRAP_POLICER_ID]); +- +- return devlink_trap_policer_item_lookup(devlink, id); +-} +- +-static int +-devlink_trap_policer_stats_put(struct sk_buff *msg, struct devlink *devlink, +- const struct devlink_trap_policer *policer) +-{ +- struct nlattr *attr; +- u64 drops; +- int err; +- +- if (!devlink->ops->trap_policer_counter_get) +- return 0; +- +- err = devlink->ops->trap_policer_counter_get(devlink, policer, &drops); +- if (err) +- return err; +- +- attr = nla_nest_start(msg, DEVLINK_ATTR_STATS); +- if (!attr) +- return -EMSGSIZE; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_DROPPED, drops, +- DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- nla_nest_end(msg, attr); +- +- return 0; +- +-nla_put_failure: +- nla_nest_cancel(msg, attr); +- return -EMSGSIZE; +-} +- +-static int +-devlink_nl_trap_policer_fill(struct sk_buff *msg, struct devlink *devlink, +- const struct devlink_trap_policer_item *policer_item, +- enum devlink_command cmd, u32 portid, u32 seq, +- int flags) +-{ +- void *hdr; +- int err; +- +- hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); +- if (!hdr) +- return -EMSGSIZE; +- +- if (devlink_nl_put_handle(msg, devlink)) +- goto nla_put_failure; +- +- if (nla_put_u32(msg, DEVLINK_ATTR_TRAP_POLICER_ID, +- policer_item->policer->id)) +- goto nla_put_failure; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_TRAP_POLICER_RATE, +- policer_item->rate, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- if (nla_put_u64_64bit(msg, DEVLINK_ATTR_TRAP_POLICER_BURST, +- policer_item->burst, DEVLINK_ATTR_PAD)) +- goto nla_put_failure; +- +- err = devlink_trap_policer_stats_put(msg, devlink, +- policer_item->policer); +- if (err) +- goto nla_put_failure; +- +- genlmsg_end(msg, hdr); +- +- return 0; +- +-nla_put_failure: +- genlmsg_cancel(msg, hdr); +- return -EMSGSIZE; +-} +- +-static int devlink_nl_cmd_trap_policer_get_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_trap_policer_item *policer_item; +- struct netlink_ext_ack *extack = info->extack; +- struct devlink *devlink = info->user_ptr[0]; +- struct sk_buff *msg; +- int err; +- +- if (list_empty(&devlink->trap_policer_list)) +- return -EOPNOTSUPP; +- +- policer_item = devlink_trap_policer_item_get_from_info(devlink, info); +- if (!policer_item) { +- NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap policer"); +- return -ENOENT; +- } +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return -ENOMEM; +- +- err = devlink_nl_trap_policer_fill(msg, devlink, policer_item, +- DEVLINK_CMD_TRAP_POLICER_NEW, +- info->snd_portid, info->snd_seq, 0); +- if (err) +- goto err_trap_policer_fill; +- +- return genlmsg_reply(msg, info); +- +-err_trap_policer_fill: +- nlmsg_free(msg); +- return err; +-} +- +-static int devlink_nl_cmd_trap_policer_get_dumpit(struct sk_buff *msg, +- struct netlink_callback *cb) +-{ +- enum devlink_command cmd = DEVLINK_CMD_TRAP_POLICER_NEW; +- struct devlink_trap_policer_item *policer_item; +- u32 portid = NETLINK_CB(cb->skb).portid; +- struct devlink *devlink; +- int start = cb->args[0]; +- unsigned long index; +- int idx = 0; +- int err; +- +- devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { +- devl_lock(devlink); +- list_for_each_entry(policer_item, &devlink->trap_policer_list, +- list) { +- if (idx < start) { +- idx++; +- continue; +- } +- err = devlink_nl_trap_policer_fill(msg, devlink, +- policer_item, cmd, +- portid, +- cb->nlh->nlmsg_seq, +- NLM_F_MULTI); +- if (err) { +- devl_unlock(devlink); +- devlink_put(devlink); +- goto out; +- } +- idx++; +- } +- devl_unlock(devlink); +- devlink_put(devlink); +- } +-out: +- cb->args[0] = idx; +- return msg->len; +-} +- +-static int +-devlink_trap_policer_set(struct devlink *devlink, +- struct devlink_trap_policer_item *policer_item, +- struct genl_info *info) +-{ +- struct netlink_ext_ack *extack = info->extack; +- struct nlattr **attrs = info->attrs; +- u64 rate, burst; +- int err; +- +- rate = policer_item->rate; +- burst = policer_item->burst; +- +- if (attrs[DEVLINK_ATTR_TRAP_POLICER_RATE]) +- rate = nla_get_u64(attrs[DEVLINK_ATTR_TRAP_POLICER_RATE]); +- +- if (attrs[DEVLINK_ATTR_TRAP_POLICER_BURST]) +- burst = nla_get_u64(attrs[DEVLINK_ATTR_TRAP_POLICER_BURST]); +- +- if (rate < policer_item->policer->min_rate) { +- NL_SET_ERR_MSG_MOD(extack, "Policer rate lower than limit"); +- return -EINVAL; +- } +- +- if (rate > policer_item->policer->max_rate) { +- NL_SET_ERR_MSG_MOD(extack, "Policer rate higher than limit"); +- return -EINVAL; +- } +- +- if (burst < policer_item->policer->min_burst) { +- NL_SET_ERR_MSG_MOD(extack, "Policer burst size lower than limit"); +- return -EINVAL; +- } +- +- if (burst > policer_item->policer->max_burst) { +- NL_SET_ERR_MSG_MOD(extack, "Policer burst size higher than limit"); +- return -EINVAL; +- } +- +- err = devlink->ops->trap_policer_set(devlink, policer_item->policer, +- rate, burst, info->extack); +- if (err) +- return err; +- +- policer_item->rate = rate; +- policer_item->burst = burst; +- +- return 0; +-} +- +-static int devlink_nl_cmd_trap_policer_set_doit(struct sk_buff *skb, +- struct genl_info *info) +-{ +- struct devlink_trap_policer_item *policer_item; +- struct netlink_ext_ack *extack = info->extack; +- struct devlink *devlink = info->user_ptr[0]; +- +- if (list_empty(&devlink->trap_policer_list)) +- return -EOPNOTSUPP; +- +- if (!devlink->ops->trap_policer_set) +- return -EOPNOTSUPP; +- +- policer_item = devlink_trap_policer_item_get_from_info(devlink, info); +- if (!policer_item) { +- NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap policer"); +- return -ENOENT; +- } +- +- return devlink_trap_policer_set(devlink, policer_item, info); +-} +- +-static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = { +- [DEVLINK_ATTR_UNSPEC] = { .strict_start_type = +- DEVLINK_ATTR_TRAP_POLICER_ID }, +- [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_PORT_INDEX] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_PORT_TYPE] = NLA_POLICY_RANGE(NLA_U16, DEVLINK_PORT_TYPE_AUTO, +- DEVLINK_PORT_TYPE_IB), +- [DEVLINK_ATTR_PORT_SPLIT_COUNT] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_SB_INDEX] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_SB_POOL_INDEX] = { .type = NLA_U16 }, +- [DEVLINK_ATTR_SB_POOL_TYPE] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_SB_POOL_SIZE] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_SB_THRESHOLD] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_SB_TC_INDEX] = { .type = NLA_U16 }, +- [DEVLINK_ATTR_ESWITCH_MODE] = NLA_POLICY_RANGE(NLA_U16, DEVLINK_ESWITCH_MODE_LEGACY, +- DEVLINK_ESWITCH_MODE_SWITCHDEV), +- [DEVLINK_ATTR_ESWITCH_INLINE_MODE] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_ESWITCH_ENCAP_MODE] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_DPIPE_TABLE_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_RESOURCE_ID] = { .type = NLA_U64}, +- [DEVLINK_ATTR_RESOURCE_SIZE] = { .type = NLA_U64}, +- [DEVLINK_ATTR_PARAM_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_PARAM_TYPE] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_PARAM_VALUE_CMODE] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_REGION_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_REGION_SNAPSHOT_ID] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_REGION_CHUNK_ADDR] = { .type = NLA_U64 }, +- [DEVLINK_ATTR_REGION_CHUNK_LEN] = { .type = NLA_U64 }, +- [DEVLINK_ATTR_HEALTH_REPORTER_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] = { .type = NLA_U64 }, +- [DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_FLASH_UPDATE_COMPONENT] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_FLASH_UPDATE_OVERWRITE_MASK] = +- NLA_POLICY_BITFIELD32(DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS), +- [DEVLINK_ATTR_TRAP_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_TRAP_ACTION] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_TRAP_GROUP_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_NETNS_PID] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_NETNS_FD] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_NETNS_ID] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP] = { .type = NLA_U8 }, +- [DEVLINK_ATTR_TRAP_POLICER_ID] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_TRAP_POLICER_RATE] = { .type = NLA_U64 }, +- [DEVLINK_ATTR_TRAP_POLICER_BURST] = { .type = NLA_U64 }, +- [DEVLINK_ATTR_PORT_FUNCTION] = { .type = NLA_NESTED }, +- [DEVLINK_ATTR_RELOAD_ACTION] = NLA_POLICY_RANGE(NLA_U8, DEVLINK_RELOAD_ACTION_DRIVER_REINIT, +- DEVLINK_RELOAD_ACTION_MAX), +- [DEVLINK_ATTR_RELOAD_LIMITS] = NLA_POLICY_BITFIELD32(DEVLINK_RELOAD_LIMITS_VALID_MASK), +- [DEVLINK_ATTR_PORT_FLAVOUR] = { .type = NLA_U16 }, +- [DEVLINK_ATTR_PORT_PCI_PF_NUMBER] = { .type = NLA_U16 }, +- [DEVLINK_ATTR_PORT_PCI_SF_NUMBER] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_PORT_CONTROLLER_NUMBER] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_RATE_TYPE] = { .type = NLA_U16 }, +- [DEVLINK_ATTR_RATE_TX_SHARE] = { .type = NLA_U64 }, +- [DEVLINK_ATTR_RATE_TX_MAX] = { .type = NLA_U64 }, +- [DEVLINK_ATTR_RATE_NODE_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_LINECARD_INDEX] = { .type = NLA_U32 }, +- [DEVLINK_ATTR_LINECARD_TYPE] = { .type = NLA_NUL_STRING }, +- [DEVLINK_ATTR_SELFTESTS] = { .type = NLA_NESTED }, +-}; +- +-static const struct genl_small_ops devlink_nl_ops[] = { +- { +- .cmd = DEVLINK_CMD_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_get_doit, +- .dumpit = devlink_nl_cmd_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_PORT_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_port_get_doit, +- .dumpit = devlink_nl_cmd_port_get_dumpit, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_PORT_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_port_set_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_RATE_GET, +- .doit = devlink_nl_cmd_rate_get_doit, +- .dumpit = devlink_nl_cmd_rate_get_dumpit, +- .internal_flags = DEVLINK_NL_FLAG_NEED_RATE, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_RATE_SET, +- .doit = devlink_nl_cmd_rate_set_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_RATE, +- }, +- { +- .cmd = DEVLINK_CMD_RATE_NEW, +- .doit = devlink_nl_cmd_rate_new_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_RATE_DEL, +- .doit = devlink_nl_cmd_rate_del_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_RATE_NODE, +- }, +- { +- .cmd = DEVLINK_CMD_PORT_SPLIT, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_port_split_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_PORT_UNSPLIT, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_port_unsplit_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_PORT_NEW, +- .doit = devlink_nl_cmd_port_new_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_PORT_DEL, +- .doit = devlink_nl_cmd_port_del_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_LINECARD_GET, +- .doit = devlink_nl_cmd_linecard_get_doit, +- .dumpit = devlink_nl_cmd_linecard_get_dumpit, +- .internal_flags = DEVLINK_NL_FLAG_NEED_LINECARD, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_LINECARD_SET, +- .doit = devlink_nl_cmd_linecard_set_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_LINECARD, +- }, +- { +- .cmd = DEVLINK_CMD_SB_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_get_doit, +- .dumpit = devlink_nl_cmd_sb_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_SB_POOL_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_pool_get_doit, +- .dumpit = devlink_nl_cmd_sb_pool_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_SB_POOL_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_pool_set_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_SB_PORT_POOL_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_port_pool_get_doit, +- .dumpit = devlink_nl_cmd_sb_port_pool_get_dumpit, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_SB_PORT_POOL_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_port_pool_set_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_SB_TC_POOL_BIND_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_tc_pool_bind_get_doit, +- .dumpit = devlink_nl_cmd_sb_tc_pool_bind_get_dumpit, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_SB_TC_POOL_BIND_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_tc_pool_bind_set_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_SB_OCC_SNAPSHOT, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_occ_snapshot_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_SB_OCC_MAX_CLEAR, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_sb_occ_max_clear_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_ESWITCH_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_eswitch_get_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_ESWITCH_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_eswitch_set_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_DPIPE_TABLE_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_dpipe_table_get, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_DPIPE_ENTRIES_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_dpipe_entries_get, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_DPIPE_HEADERS_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_dpipe_headers_get, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_DPIPE_TABLE_COUNTERS_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_dpipe_table_counters_set, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_RESOURCE_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_resource_set, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_RESOURCE_DUMP, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_resource_dump, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_RELOAD, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_reload, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_PARAM_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_param_get_doit, +- .dumpit = devlink_nl_cmd_param_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_PARAM_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_param_set_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_PORT_PARAM_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_port_param_get_doit, +- .dumpit = devlink_nl_cmd_port_param_get_dumpit, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_PORT_PARAM_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_port_param_set_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_REGION_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_region_get_doit, +- .dumpit = devlink_nl_cmd_region_get_dumpit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_REGION_NEW, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_region_new, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_REGION_DEL, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_region_del, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_REGION_READ, +- .validate = GENL_DONT_VALIDATE_STRICT | +- GENL_DONT_VALIDATE_DUMP_STRICT, +- .dumpit = devlink_nl_cmd_region_read_dumpit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_INFO_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_info_get_doit, +- .dumpit = devlink_nl_cmd_info_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_HEALTH_REPORTER_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_health_reporter_get_doit, +- .dumpit = devlink_nl_cmd_health_reporter_get_dumpit, +- .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_HEALTH_REPORTER_SET, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_health_reporter_set_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_HEALTH_REPORTER_RECOVER, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_health_reporter_recover_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_health_reporter_diagnose_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET, +- .validate = GENL_DONT_VALIDATE_STRICT | +- GENL_DONT_VALIDATE_DUMP_STRICT, +- .dumpit = devlink_nl_cmd_health_reporter_dump_get_dumpit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_HEALTH_REPORTER_DUMP_CLEAR, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_health_reporter_dump_clear_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_HEALTH_REPORTER_TEST, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_health_reporter_test_doit, +- .flags = GENL_ADMIN_PERM, +- .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, +- }, +- { +- .cmd = DEVLINK_CMD_FLASH_UPDATE, +- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, +- .doit = devlink_nl_cmd_flash_update, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_TRAP_GET, +- .doit = devlink_nl_cmd_trap_get_doit, +- .dumpit = devlink_nl_cmd_trap_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_TRAP_SET, +- .doit = devlink_nl_cmd_trap_set_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_TRAP_GROUP_GET, +- .doit = devlink_nl_cmd_trap_group_get_doit, +- .dumpit = devlink_nl_cmd_trap_group_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_TRAP_GROUP_SET, +- .doit = devlink_nl_cmd_trap_group_set_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_TRAP_POLICER_GET, +- .doit = devlink_nl_cmd_trap_policer_get_doit, +- .dumpit = devlink_nl_cmd_trap_policer_get_dumpit, +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_TRAP_POLICER_SET, +- .doit = devlink_nl_cmd_trap_policer_set_doit, +- .flags = GENL_ADMIN_PERM, +- }, +- { +- .cmd = DEVLINK_CMD_SELFTESTS_GET, +- .doit = devlink_nl_cmd_selftests_get_doit, +- .dumpit = devlink_nl_cmd_selftests_get_dumpit +- /* can be retrieved by unprivileged users */ +- }, +- { +- .cmd = DEVLINK_CMD_SELFTESTS_RUN, +- .doit = devlink_nl_cmd_selftests_run, +- .flags = GENL_ADMIN_PERM, +- }, +-}; +- +-static struct genl_family devlink_nl_family __ro_after_init = { +- .name = DEVLINK_GENL_NAME, +- .version = DEVLINK_GENL_VERSION, +- .maxattr = DEVLINK_ATTR_MAX, +- .policy = devlink_nl_policy, +- .netnsok = true, +- .parallel_ops = true, +- .pre_doit = devlink_nl_pre_doit, +- .post_doit = devlink_nl_post_doit, +- .module = THIS_MODULE, +- .small_ops = devlink_nl_ops, +- .n_small_ops = ARRAY_SIZE(devlink_nl_ops), +- .resv_start_op = DEVLINK_CMD_SELFTESTS_RUN + 1, +- .mcgrps = devlink_nl_mcgrps, +- .n_mcgrps = ARRAY_SIZE(devlink_nl_mcgrps), +-}; +- +-static bool devlink_reload_actions_valid(const struct devlink_ops *ops) +-{ +- const struct devlink_reload_combination *comb; +- int i; +- +- if (!devlink_reload_supported(ops)) { +- if (WARN_ON(ops->reload_actions)) +- return false; +- return true; +- } +- +- if (WARN_ON(!ops->reload_actions || +- ops->reload_actions & BIT(DEVLINK_RELOAD_ACTION_UNSPEC) || +- ops->reload_actions >= BIT(__DEVLINK_RELOAD_ACTION_MAX))) +- return false; +- +- if (WARN_ON(ops->reload_limits & BIT(DEVLINK_RELOAD_LIMIT_UNSPEC) || +- ops->reload_limits >= BIT(__DEVLINK_RELOAD_LIMIT_MAX))) +- return false; +- +- for (i = 0; i < ARRAY_SIZE(devlink_reload_invalid_combinations); i++) { +- comb = &devlink_reload_invalid_combinations[i]; +- if (ops->reload_actions == BIT(comb->action) && +- ops->reload_limits == BIT(comb->limit)) +- return false; +- } +- return true; +-} +- +-/** +- * devlink_set_features - Set devlink supported features +- * +- * @devlink: devlink +- * @features: devlink support features +- * +- * This interface allows us to set reload ops separatelly from +- * the devlink_alloc. +- */ +-void devlink_set_features(struct devlink *devlink, u64 features) +-{ +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- +- WARN_ON(features & DEVLINK_F_RELOAD && +- !devlink_reload_supported(devlink->ops)); +- devlink->features = features; +-} +-EXPORT_SYMBOL_GPL(devlink_set_features); +- +-/** +- * devlink_alloc_ns - Allocate new devlink instance resources +- * in specific namespace +- * +- * @ops: ops +- * @priv_size: size of user private data +- * @net: net namespace +- * @dev: parent device +- * +- * Allocate new devlink instance resources, including devlink index +- * and name. +- */ +-struct devlink *devlink_alloc_ns(const struct devlink_ops *ops, +- size_t priv_size, struct net *net, +- struct device *dev) +-{ +- struct devlink *devlink; +- static u32 last_id; +- int ret; +- +- WARN_ON(!ops || !dev); +- if (!devlink_reload_actions_valid(ops)) +- return NULL; +- +- devlink = kzalloc(sizeof(*devlink) + priv_size, GFP_KERNEL); +- if (!devlink) +- return NULL; +- +- ret = xa_alloc_cyclic(&devlinks, &devlink->index, devlink, xa_limit_31b, +- &last_id, GFP_KERNEL); +- if (ret < 0) { +- kfree(devlink); +- return NULL; +- } +- +- devlink->dev = dev; +- devlink->ops = ops; +- xa_init_flags(&devlink->snapshot_ids, XA_FLAGS_ALLOC); +- write_pnet(&devlink->_net, net); +- INIT_LIST_HEAD(&devlink->port_list); +- INIT_LIST_HEAD(&devlink->rate_list); +- INIT_LIST_HEAD(&devlink->linecard_list); +- INIT_LIST_HEAD(&devlink->sb_list); +- INIT_LIST_HEAD_RCU(&devlink->dpipe_table_list); +- INIT_LIST_HEAD(&devlink->resource_list); +- INIT_LIST_HEAD(&devlink->param_list); +- INIT_LIST_HEAD(&devlink->region_list); +- INIT_LIST_HEAD(&devlink->reporter_list); +- INIT_LIST_HEAD(&devlink->trap_list); +- INIT_LIST_HEAD(&devlink->trap_group_list); +- INIT_LIST_HEAD(&devlink->trap_policer_list); +- lockdep_register_key(&devlink->lock_key); +- mutex_init(&devlink->lock); +- lockdep_set_class(&devlink->lock, &devlink->lock_key); +- mutex_init(&devlink->reporters_lock); +- mutex_init(&devlink->linecards_lock); +- refcount_set(&devlink->refcount, 1); +- init_completion(&devlink->comp); +- +- return devlink; +-} +-EXPORT_SYMBOL_GPL(devlink_alloc_ns); +- +-static void +-devlink_trap_policer_notify(struct devlink *devlink, +- const struct devlink_trap_policer_item *policer_item, +- enum devlink_command cmd); +-static void +-devlink_trap_group_notify(struct devlink *devlink, +- const struct devlink_trap_group_item *group_item, +- enum devlink_command cmd); +-static void devlink_trap_notify(struct devlink *devlink, +- const struct devlink_trap_item *trap_item, +- enum devlink_command cmd); +- +-static void devlink_notify_register(struct devlink *devlink) +-{ +- struct devlink_trap_policer_item *policer_item; +- struct devlink_trap_group_item *group_item; +- struct devlink_param_item *param_item; +- struct devlink_trap_item *trap_item; +- struct devlink_port *devlink_port; +- struct devlink_linecard *linecard; +- struct devlink_rate *rate_node; +- struct devlink_region *region; +- +- devlink_notify(devlink, DEVLINK_CMD_NEW); +- list_for_each_entry(linecard, &devlink->linecard_list, list) +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- +- list_for_each_entry(devlink_port, &devlink->port_list, list) +- devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); +- +- list_for_each_entry(policer_item, &devlink->trap_policer_list, list) +- devlink_trap_policer_notify(devlink, policer_item, +- DEVLINK_CMD_TRAP_POLICER_NEW); +- +- list_for_each_entry(group_item, &devlink->trap_group_list, list) +- devlink_trap_group_notify(devlink, group_item, +- DEVLINK_CMD_TRAP_GROUP_NEW); +- +- list_for_each_entry(trap_item, &devlink->trap_list, list) +- devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_NEW); +- +- list_for_each_entry(rate_node, &devlink->rate_list, list) +- devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); +- +- list_for_each_entry(region, &devlink->region_list, list) +- devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW); +- +- list_for_each_entry(param_item, &devlink->param_list, list) +- devlink_param_notify(devlink, 0, param_item, +- DEVLINK_CMD_PARAM_NEW); +-} +- +-static void devlink_notify_unregister(struct devlink *devlink) +-{ +- struct devlink_trap_policer_item *policer_item; +- struct devlink_trap_group_item *group_item; +- struct devlink_param_item *param_item; +- struct devlink_trap_item *trap_item; +- struct devlink_port *devlink_port; +- struct devlink_rate *rate_node; +- struct devlink_region *region; +- +- list_for_each_entry_reverse(param_item, &devlink->param_list, list) +- devlink_param_notify(devlink, 0, param_item, +- DEVLINK_CMD_PARAM_DEL); +- +- list_for_each_entry_reverse(region, &devlink->region_list, list) +- devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_DEL); +- +- list_for_each_entry_reverse(rate_node, &devlink->rate_list, list) +- devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); +- +- list_for_each_entry_reverse(trap_item, &devlink->trap_list, list) +- devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_DEL); +- +- list_for_each_entry_reverse(group_item, &devlink->trap_group_list, list) +- devlink_trap_group_notify(devlink, group_item, +- DEVLINK_CMD_TRAP_GROUP_DEL); +- list_for_each_entry_reverse(policer_item, &devlink->trap_policer_list, +- list) +- devlink_trap_policer_notify(devlink, policer_item, +- DEVLINK_CMD_TRAP_POLICER_DEL); +- +- list_for_each_entry_reverse(devlink_port, &devlink->port_list, list) +- devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_DEL); +- devlink_notify(devlink, DEVLINK_CMD_DEL); +-} +- +-/** +- * devlink_register - Register devlink instance +- * +- * @devlink: devlink +- */ +-void devlink_register(struct devlink *devlink) +-{ +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- /* Make sure that we are in .probe() routine */ +- +- xa_set_mark(&devlinks, devlink->index, DEVLINK_REGISTERED); +- devlink_notify_register(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_register); +- +-/** +- * devlink_unregister - Unregister devlink instance +- * +- * @devlink: devlink +- */ +-void devlink_unregister(struct devlink *devlink) +-{ +- ASSERT_DEVLINK_REGISTERED(devlink); +- /* Make sure that we are in .remove() routine */ +- +- xa_set_mark(&devlinks, devlink->index, DEVLINK_UNREGISTERING); +- devlink_put(devlink); +- wait_for_completion(&devlink->comp); +- +- devlink_notify_unregister(devlink); +- xa_clear_mark(&devlinks, devlink->index, DEVLINK_REGISTERED); +- xa_clear_mark(&devlinks, devlink->index, DEVLINK_UNREGISTERING); +-} +-EXPORT_SYMBOL_GPL(devlink_unregister); +- +-/** +- * devlink_free - Free devlink instance resources +- * +- * @devlink: devlink +- */ +-void devlink_free(struct devlink *devlink) +-{ +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- +- mutex_destroy(&devlink->linecards_lock); +- mutex_destroy(&devlink->reporters_lock); +- mutex_destroy(&devlink->lock); +- lockdep_unregister_key(&devlink->lock_key); +- WARN_ON(!list_empty(&devlink->trap_policer_list)); +- WARN_ON(!list_empty(&devlink->trap_group_list)); +- WARN_ON(!list_empty(&devlink->trap_list)); +- WARN_ON(!list_empty(&devlink->reporter_list)); +- WARN_ON(!list_empty(&devlink->region_list)); +- WARN_ON(!list_empty(&devlink->param_list)); +- WARN_ON(!list_empty(&devlink->resource_list)); +- WARN_ON(!list_empty(&devlink->dpipe_table_list)); +- WARN_ON(!list_empty(&devlink->sb_list)); +- WARN_ON(!list_empty(&devlink->rate_list)); +- WARN_ON(!list_empty(&devlink->linecard_list)); +- WARN_ON(!list_empty(&devlink->port_list)); +- +- xa_destroy(&devlink->snapshot_ids); +- xa_erase(&devlinks, devlink->index); +- +- kfree(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_free); +- +-static void devlink_port_type_warn(struct work_struct *work) +-{ +- struct devlink_port *port = container_of(to_delayed_work(work), +- struct devlink_port, +- type_warn_dw); +- dev_warn(port->devlink->dev, "Type was not set for devlink port."); +-} +- +-static bool devlink_port_type_should_warn(struct devlink_port *devlink_port) +-{ +- /* Ignore CPU and DSA flavours. */ +- return devlink_port->attrs.flavour != DEVLINK_PORT_FLAVOUR_CPU && +- devlink_port->attrs.flavour != DEVLINK_PORT_FLAVOUR_DSA && +- devlink_port->attrs.flavour != DEVLINK_PORT_FLAVOUR_UNUSED; +-} +- +-#define DEVLINK_PORT_TYPE_WARN_TIMEOUT (HZ * 3600) +- +-static void devlink_port_type_warn_schedule(struct devlink_port *devlink_port) +-{ +- if (!devlink_port_type_should_warn(devlink_port)) +- return; +- /* Schedule a work to WARN in case driver does not set port +- * type within timeout. +- */ +- schedule_delayed_work(&devlink_port->type_warn_dw, +- DEVLINK_PORT_TYPE_WARN_TIMEOUT); +-} +- +-static void devlink_port_type_warn_cancel(struct devlink_port *devlink_port) +-{ +- if (!devlink_port_type_should_warn(devlink_port)) +- return; +- cancel_delayed_work_sync(&devlink_port->type_warn_dw); +-} +- +-/** +- * devlink_port_init() - Init devlink port +- * +- * @devlink: devlink +- * @devlink_port: devlink port +- * +- * Initialize essencial stuff that is needed for functions +- * that may be called before devlink port registration. +- * Call to this function is optional and not needed +- * in case the driver does not use such functions. +- */ +-void devlink_port_init(struct devlink *devlink, +- struct devlink_port *devlink_port) +-{ +- if (devlink_port->initialized) +- return; +- devlink_port->devlink = devlink; +- INIT_LIST_HEAD(&devlink_port->region_list); +- devlink_port->initialized = true; +-} +-EXPORT_SYMBOL_GPL(devlink_port_init); +- +-/** +- * devlink_port_fini() - Deinitialize devlink port +- * +- * @devlink_port: devlink port +- * +- * Deinitialize essencial stuff that is in use for functions +- * that may be called after devlink port unregistration. +- * Call to this function is optional and not needed +- * in case the driver does not use such functions. +- */ +-void devlink_port_fini(struct devlink_port *devlink_port) +-{ +- WARN_ON(!list_empty(&devlink_port->region_list)); +-} +-EXPORT_SYMBOL_GPL(devlink_port_fini); +- +-/** +- * devl_port_register() - Register devlink port +- * +- * @devlink: devlink +- * @devlink_port: devlink port +- * @port_index: driver-specific numerical identifier of the port +- * +- * Register devlink port with provided port index. User can use +- * any indexing, even hw-related one. devlink_port structure +- * is convenient to be embedded inside user driver private structure. +- * Note that the caller should take care of zeroing the devlink_port +- * structure. +- */ +-int devl_port_register(struct devlink *devlink, +- struct devlink_port *devlink_port, +- unsigned int port_index) +-{ +- devl_assert_locked(devlink); +- +- if (devlink_port_index_exists(devlink, port_index)) +- return -EEXIST; +- +- ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); +- +- devlink_port_init(devlink, devlink_port); +- devlink_port->registered = true; +- devlink_port->index = port_index; +- spin_lock_init(&devlink_port->type_lock); +- INIT_LIST_HEAD(&devlink_port->reporter_list); +- mutex_init(&devlink_port->reporters_lock); +- list_add_tail(&devlink_port->list, &devlink->port_list); +- +- INIT_DELAYED_WORK(&devlink_port->type_warn_dw, &devlink_port_type_warn); +- devlink_port_type_warn_schedule(devlink_port); +- devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); +- return 0; +-} +-EXPORT_SYMBOL_GPL(devl_port_register); +- +-/** +- * devlink_port_register - Register devlink port +- * +- * @devlink: devlink +- * @devlink_port: devlink port +- * @port_index: driver-specific numerical identifier of the port +- * +- * Register devlink port with provided port index. User can use +- * any indexing, even hw-related one. devlink_port structure +- * is convenient to be embedded inside user driver private structure. +- * Note that the caller should take care of zeroing the devlink_port +- * structure. +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-int devlink_port_register(struct devlink *devlink, +- struct devlink_port *devlink_port, +- unsigned int port_index) +-{ +- int err; +- +- devl_lock(devlink); +- err = devl_port_register(devlink, devlink_port, port_index); +- devl_unlock(devlink); +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_port_register); +- +-/** +- * devl_port_unregister() - Unregister devlink port +- * +- * @devlink_port: devlink port +- */ +-void devl_port_unregister(struct devlink_port *devlink_port) +-{ +- lockdep_assert_held(&devlink_port->devlink->lock); +- +- devlink_port_type_warn_cancel(devlink_port); +- devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_DEL); +- list_del(&devlink_port->list); +- WARN_ON(!list_empty(&devlink_port->reporter_list)); +- mutex_destroy(&devlink_port->reporters_lock); +- devlink_port->registered = false; +-} +-EXPORT_SYMBOL_GPL(devl_port_unregister); +- +-/** +- * devlink_port_unregister - Unregister devlink port +- * +- * @devlink_port: devlink port +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-void devlink_port_unregister(struct devlink_port *devlink_port) +-{ +- struct devlink *devlink = devlink_port->devlink; +- +- devl_lock(devlink); +- devl_port_unregister(devlink_port); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_port_unregister); +- +-static void __devlink_port_type_set(struct devlink_port *devlink_port, +- enum devlink_port_type type, +- void *type_dev) +-{ +- ASSERT_DEVLINK_PORT_REGISTERED(devlink_port); +- +- devlink_port_type_warn_cancel(devlink_port); +- spin_lock_bh(&devlink_port->type_lock); +- devlink_port->type = type; +- devlink_port->type_dev = type_dev; +- spin_unlock_bh(&devlink_port->type_lock); +- devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); +-} +- +-static void devlink_port_type_netdev_checks(struct devlink_port *devlink_port, +- struct net_device *netdev) +-{ +- const struct net_device_ops *ops = netdev->netdev_ops; +- +- /* If driver registers devlink port, it should set devlink port +- * attributes accordingly so the compat functions are called +- * and the original ops are not used. +- */ +- if (ops->ndo_get_phys_port_name) { +- /* Some drivers use the same set of ndos for netdevs +- * that have devlink_port registered and also for +- * those who don't. Make sure that ndo_get_phys_port_name +- * returns -EOPNOTSUPP here in case it is defined. +- * Warn if not. +- */ +- char name[IFNAMSIZ]; +- int err; +- +- err = ops->ndo_get_phys_port_name(netdev, name, sizeof(name)); +- WARN_ON(err != -EOPNOTSUPP); +- } +- if (ops->ndo_get_port_parent_id) { +- /* Some drivers use the same set of ndos for netdevs +- * that have devlink_port registered and also for +- * those who don't. Make sure that ndo_get_port_parent_id +- * returns -EOPNOTSUPP here in case it is defined. +- * Warn if not. +- */ +- struct netdev_phys_item_id ppid; +- int err; +- +- err = ops->ndo_get_port_parent_id(netdev, &ppid); +- WARN_ON(err != -EOPNOTSUPP); +- } +-} +- +-/** +- * devlink_port_type_eth_set - Set port type to Ethernet +- * +- * @devlink_port: devlink port +- * @netdev: related netdevice +- */ +-void devlink_port_type_eth_set(struct devlink_port *devlink_port, +- struct net_device *netdev) +-{ +- if (netdev) +- devlink_port_type_netdev_checks(devlink_port, netdev); +- else +- dev_warn(devlink_port->devlink->dev, +- "devlink port type for port %d set to Ethernet without a software interface reference, device type not supported by the kernel?\n", +- devlink_port->index); +- +- __devlink_port_type_set(devlink_port, DEVLINK_PORT_TYPE_ETH, netdev); +-} +-EXPORT_SYMBOL_GPL(devlink_port_type_eth_set); +- +-/** +- * devlink_port_type_ib_set - Set port type to InfiniBand +- * +- * @devlink_port: devlink port +- * @ibdev: related IB device +- */ +-void devlink_port_type_ib_set(struct devlink_port *devlink_port, +- struct ib_device *ibdev) +-{ +- __devlink_port_type_set(devlink_port, DEVLINK_PORT_TYPE_IB, ibdev); +-} +-EXPORT_SYMBOL_GPL(devlink_port_type_ib_set); +- +-/** +- * devlink_port_type_clear - Clear port type +- * +- * @devlink_port: devlink port +- */ +-void devlink_port_type_clear(struct devlink_port *devlink_port) +-{ +- __devlink_port_type_set(devlink_port, DEVLINK_PORT_TYPE_NOTSET, NULL); +- devlink_port_type_warn_schedule(devlink_port); +-} +-EXPORT_SYMBOL_GPL(devlink_port_type_clear); +- +-static int __devlink_port_attrs_set(struct devlink_port *devlink_port, +- enum devlink_port_flavour flavour) +-{ +- struct devlink_port_attrs *attrs = &devlink_port->attrs; +- +- devlink_port->attrs_set = true; +- attrs->flavour = flavour; +- if (attrs->switch_id.id_len) { +- devlink_port->switch_port = true; +- if (WARN_ON(attrs->switch_id.id_len > MAX_PHYS_ITEM_ID_LEN)) +- attrs->switch_id.id_len = MAX_PHYS_ITEM_ID_LEN; +- } else { +- devlink_port->switch_port = false; +- } +- return 0; +-} +- +-/** +- * devlink_port_attrs_set - Set port attributes +- * +- * @devlink_port: devlink port +- * @attrs: devlink port attrs +- */ +-void devlink_port_attrs_set(struct devlink_port *devlink_port, +- struct devlink_port_attrs *attrs) +-{ +- int ret; +- +- ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); +- +- devlink_port->attrs = *attrs; +- ret = __devlink_port_attrs_set(devlink_port, attrs->flavour); +- if (ret) +- return; +- WARN_ON(attrs->splittable && attrs->split); +-} +-EXPORT_SYMBOL_GPL(devlink_port_attrs_set); +- +-/** +- * devlink_port_attrs_pci_pf_set - Set PCI PF port attributes +- * +- * @devlink_port: devlink port +- * @controller: associated controller number for the devlink port instance +- * @pf: associated PF for the devlink port instance +- * @external: indicates if the port is for an external controller +- */ +-void devlink_port_attrs_pci_pf_set(struct devlink_port *devlink_port, u32 controller, +- u16 pf, bool external) +-{ +- struct devlink_port_attrs *attrs = &devlink_port->attrs; +- int ret; +- +- ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); +- +- ret = __devlink_port_attrs_set(devlink_port, +- DEVLINK_PORT_FLAVOUR_PCI_PF); +- if (ret) +- return; +- attrs->pci_pf.controller = controller; +- attrs->pci_pf.pf = pf; +- attrs->pci_pf.external = external; +-} +-EXPORT_SYMBOL_GPL(devlink_port_attrs_pci_pf_set); +- +-/** +- * devlink_port_attrs_pci_vf_set - Set PCI VF port attributes +- * +- * @devlink_port: devlink port +- * @controller: associated controller number for the devlink port instance +- * @pf: associated PF for the devlink port instance +- * @vf: associated VF of a PF for the devlink port instance +- * @external: indicates if the port is for an external controller +- */ +-void devlink_port_attrs_pci_vf_set(struct devlink_port *devlink_port, u32 controller, +- u16 pf, u16 vf, bool external) +-{ +- struct devlink_port_attrs *attrs = &devlink_port->attrs; +- int ret; +- +- ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); +- +- ret = __devlink_port_attrs_set(devlink_port, +- DEVLINK_PORT_FLAVOUR_PCI_VF); +- if (ret) +- return; +- attrs->pci_vf.controller = controller; +- attrs->pci_vf.pf = pf; +- attrs->pci_vf.vf = vf; +- attrs->pci_vf.external = external; +-} +-EXPORT_SYMBOL_GPL(devlink_port_attrs_pci_vf_set); +- +-/** +- * devlink_port_attrs_pci_sf_set - Set PCI SF port attributes +- * +- * @devlink_port: devlink port +- * @controller: associated controller number for the devlink port instance +- * @pf: associated PF for the devlink port instance +- * @sf: associated SF of a PF for the devlink port instance +- * @external: indicates if the port is for an external controller +- */ +-void devlink_port_attrs_pci_sf_set(struct devlink_port *devlink_port, u32 controller, +- u16 pf, u32 sf, bool external) +-{ +- struct devlink_port_attrs *attrs = &devlink_port->attrs; +- int ret; +- +- ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); +- +- ret = __devlink_port_attrs_set(devlink_port, +- DEVLINK_PORT_FLAVOUR_PCI_SF); +- if (ret) +- return; +- attrs->pci_sf.controller = controller; +- attrs->pci_sf.pf = pf; +- attrs->pci_sf.sf = sf; +- attrs->pci_sf.external = external; +-} +-EXPORT_SYMBOL_GPL(devlink_port_attrs_pci_sf_set); +- +-/** +- * devl_rate_leaf_create - create devlink rate leaf +- * @devlink_port: devlink port object to create rate object on +- * @priv: driver private data +- * +- * Create devlink rate object of type leaf on provided @devlink_port. +- */ +-int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv) +-{ +- struct devlink *devlink = devlink_port->devlink; +- struct devlink_rate *devlink_rate; +- +- devl_assert_locked(devlink_port->devlink); +- +- if (WARN_ON(devlink_port->devlink_rate)) +- return -EBUSY; +- +- devlink_rate = kzalloc(sizeof(*devlink_rate), GFP_KERNEL); +- if (!devlink_rate) +- return -ENOMEM; +- +- devlink_rate->type = DEVLINK_RATE_TYPE_LEAF; +- devlink_rate->devlink = devlink; +- devlink_rate->devlink_port = devlink_port; +- devlink_rate->priv = priv; +- list_add_tail(&devlink_rate->list, &devlink->rate_list); +- devlink_port->devlink_rate = devlink_rate; +- devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devl_rate_leaf_create); +- +-/** +- * devl_rate_leaf_destroy - destroy devlink rate leaf +- * +- * @devlink_port: devlink port linked to the rate object +- * +- * Destroy the devlink rate object of type leaf on provided @devlink_port. +- */ +-void devl_rate_leaf_destroy(struct devlink_port *devlink_port) +-{ +- struct devlink_rate *devlink_rate = devlink_port->devlink_rate; +- +- devl_assert_locked(devlink_port->devlink); +- if (!devlink_rate) +- return; +- +- 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; +- kfree(devlink_rate); +-} +-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. +- */ +-void devl_rate_nodes_destroy(struct devlink *devlink) +-{ +- static struct devlink_rate *devlink_rate, *tmp; +- const struct devlink_ops *ops = devlink->ops; +- +- devl_assert_locked(devlink); +- +- list_for_each_entry(devlink_rate, &devlink->rate_list, list) { +- if (!devlink_rate->parent) +- continue; +- +- refcount_dec(&devlink_rate->parent->refcnt); +- if (devlink_rate_is_leaf(devlink_rate)) +- ops->rate_leaf_parent_set(devlink_rate, NULL, devlink_rate->priv, +- NULL, NULL); +- else if (devlink_rate_is_node(devlink_rate)) +- ops->rate_node_parent_set(devlink_rate, NULL, devlink_rate->priv, +- NULL, NULL); +- } +- list_for_each_entry_safe(devlink_rate, tmp, &devlink->rate_list, list) { +- if (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); +- } +- } +-} +-EXPORT_SYMBOL_GPL(devl_rate_nodes_destroy); +- +-/** +- * devlink_port_linecard_set - Link port with a linecard +- * +- * @devlink_port: devlink port +- * @linecard: devlink linecard +- */ +-void devlink_port_linecard_set(struct devlink_port *devlink_port, +- struct devlink_linecard *linecard) +-{ +- ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); +- +- devlink_port->linecard = linecard; +-} +-EXPORT_SYMBOL_GPL(devlink_port_linecard_set); +- +-static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, +- char *name, size_t len) +-{ +- struct devlink_port_attrs *attrs = &devlink_port->attrs; +- int n = 0; +- +- if (!devlink_port->attrs_set) +- return -EOPNOTSUPP; +- +- switch (attrs->flavour) { +- case DEVLINK_PORT_FLAVOUR_PHYSICAL: +- if (devlink_port->linecard) +- n = snprintf(name, len, "l%u", +- devlink_port->linecard->index); +- if (n < len) +- n += snprintf(name + n, len - n, "p%u", +- attrs->phys.port_number); +- if (n < len && attrs->split) +- n += snprintf(name + n, len - n, "s%u", +- attrs->phys.split_subport_number); +- break; +- case DEVLINK_PORT_FLAVOUR_CPU: +- case DEVLINK_PORT_FLAVOUR_DSA: +- case DEVLINK_PORT_FLAVOUR_UNUSED: +- /* As CPU and DSA ports do not have a netdevice associated +- * case should not ever happen. +- */ +- WARN_ON(1); +- return -EINVAL; +- case DEVLINK_PORT_FLAVOUR_PCI_PF: +- if (attrs->pci_pf.external) { +- n = snprintf(name, len, "c%u", attrs->pci_pf.controller); +- if (n >= len) +- return -EINVAL; +- len -= n; +- name += n; +- } +- n = snprintf(name, len, "pf%u", attrs->pci_pf.pf); +- break; +- case DEVLINK_PORT_FLAVOUR_PCI_VF: +- if (attrs->pci_vf.external) { +- n = snprintf(name, len, "c%u", attrs->pci_vf.controller); +- if (n >= len) +- return -EINVAL; +- len -= n; +- name += n; +- } +- n = snprintf(name, len, "pf%uvf%u", +- attrs->pci_vf.pf, attrs->pci_vf.vf); +- break; +- case DEVLINK_PORT_FLAVOUR_PCI_SF: +- if (attrs->pci_sf.external) { +- n = snprintf(name, len, "c%u", attrs->pci_sf.controller); +- if (n >= len) +- return -EINVAL; +- len -= n; +- name += n; +- } +- n = snprintf(name, len, "pf%usf%u", attrs->pci_sf.pf, +- attrs->pci_sf.sf); +- break; +- case DEVLINK_PORT_FLAVOUR_VIRTUAL: +- return -EOPNOTSUPP; +- } +- +- if (n >= len) +- return -EINVAL; +- +- return 0; +-} +- +-static int devlink_linecard_types_init(struct devlink_linecard *linecard) +-{ +- struct devlink_linecard_type *linecard_type; +- unsigned int count; +- int i; +- +- count = linecard->ops->types_count(linecard, linecard->priv); +- linecard->types = kmalloc_array(count, sizeof(*linecard_type), +- GFP_KERNEL); +- if (!linecard->types) +- return -ENOMEM; +- linecard->types_count = count; +- +- for (i = 0; i < count; i++) { +- linecard_type = &linecard->types[i]; +- linecard->ops->types_get(linecard, linecard->priv, i, +- &linecard_type->type, +- &linecard_type->priv); +- } +- return 0; +-} +- +-static void devlink_linecard_types_fini(struct devlink_linecard *linecard) +-{ +- kfree(linecard->types); +-} +- +-/** +- * devlink_linecard_create - Create devlink linecard +- * +- * @devlink: devlink +- * @linecard_index: driver-specific numerical identifier of the linecard +- * @ops: linecards ops +- * @priv: user priv pointer +- * +- * Create devlink linecard instance with provided linecard index. +- * Caller can use any indexing, even hw-related one. +- * +- * Return: Line card structure or an ERR_PTR() encoded error code. +- */ +-struct devlink_linecard * +-devlink_linecard_create(struct devlink *devlink, unsigned int linecard_index, +- const struct devlink_linecard_ops *ops, void *priv) +-{ +- struct devlink_linecard *linecard; +- int err; +- +- if (WARN_ON(!ops || !ops->provision || !ops->unprovision || +- !ops->types_count || !ops->types_get)) +- return ERR_PTR(-EINVAL); +- +- mutex_lock(&devlink->linecards_lock); +- if (devlink_linecard_index_exists(devlink, linecard_index)) { +- mutex_unlock(&devlink->linecards_lock); +- return ERR_PTR(-EEXIST); +- } +- +- linecard = kzalloc(sizeof(*linecard), GFP_KERNEL); +- if (!linecard) { +- mutex_unlock(&devlink->linecards_lock); +- return ERR_PTR(-ENOMEM); +- } +- +- linecard->devlink = devlink; +- linecard->index = linecard_index; +- linecard->ops = ops; +- linecard->priv = priv; +- linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; +- mutex_init(&linecard->state_lock); +- +- err = devlink_linecard_types_init(linecard); +- if (err) { +- mutex_destroy(&linecard->state_lock); +- kfree(linecard); +- mutex_unlock(&devlink->linecards_lock); +- return ERR_PTR(err); +- } +- +- list_add_tail(&linecard->list, &devlink->linecard_list); +- refcount_set(&linecard->refcount, 1); +- mutex_unlock(&devlink->linecards_lock); +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- return linecard; +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_create); +- +-/** +- * devlink_linecard_destroy - Destroy devlink linecard +- * +- * @linecard: devlink linecard +- */ +-void devlink_linecard_destroy(struct devlink_linecard *linecard) +-{ +- struct devlink *devlink = linecard->devlink; +- +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_DEL); +- mutex_lock(&devlink->linecards_lock); +- list_del(&linecard->list); +- devlink_linecard_types_fini(linecard); +- mutex_unlock(&devlink->linecards_lock); +- devlink_linecard_put(linecard); +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_destroy); +- +-/** +- * devlink_linecard_provision_set - Set provisioning on linecard +- * +- * @linecard: devlink linecard +- * @type: linecard type +- * +- * This is either called directly from the provision() op call or +- * as a result of the provision() op call asynchronously. +- */ +-void devlink_linecard_provision_set(struct devlink_linecard *linecard, +- const char *type) +-{ +- mutex_lock(&linecard->state_lock); +- WARN_ON(linecard->type && strcmp(linecard->type, type)); +- linecard->state = DEVLINK_LINECARD_STATE_PROVISIONED; +- linecard->type = type; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_provision_set); +- +-/** +- * devlink_linecard_provision_clear - Clear provisioning on linecard +- * +- * @linecard: devlink linecard +- * +- * This is either called directly from the unprovision() op call or +- * as a result of the unprovision() op call asynchronously. +- */ +-void devlink_linecard_provision_clear(struct devlink_linecard *linecard) +-{ +- mutex_lock(&linecard->state_lock); +- WARN_ON(linecard->nested_devlink); +- linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; +- linecard->type = NULL; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_provision_clear); +- +-/** +- * devlink_linecard_provision_fail - Fail provisioning on linecard +- * +- * @linecard: devlink linecard +- * +- * This is either called directly from the provision() op call or +- * as a result of the provision() op call asynchronously. +- */ +-void devlink_linecard_provision_fail(struct devlink_linecard *linecard) +-{ +- mutex_lock(&linecard->state_lock); +- WARN_ON(linecard->nested_devlink); +- linecard->state = DEVLINK_LINECARD_STATE_PROVISIONING_FAILED; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_provision_fail); +- +-/** +- * devlink_linecard_activate - Set linecard active +- * +- * @linecard: devlink linecard +- */ +-void devlink_linecard_activate(struct devlink_linecard *linecard) +-{ +- mutex_lock(&linecard->state_lock); +- WARN_ON(linecard->state != DEVLINK_LINECARD_STATE_PROVISIONED); +- linecard->state = DEVLINK_LINECARD_STATE_ACTIVE; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_activate); +- +-/** +- * devlink_linecard_deactivate - Set linecard inactive +- * +- * @linecard: devlink linecard +- */ +-void devlink_linecard_deactivate(struct devlink_linecard *linecard) +-{ +- mutex_lock(&linecard->state_lock); +- switch (linecard->state) { +- case DEVLINK_LINECARD_STATE_ACTIVE: +- linecard->state = DEVLINK_LINECARD_STATE_PROVISIONED; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- break; +- case DEVLINK_LINECARD_STATE_UNPROVISIONING: +- /* Line card is being deactivated as part +- * of unprovisioning flow. +- */ +- break; +- default: +- WARN_ON(1); +- break; +- } +- mutex_unlock(&linecard->state_lock); +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_deactivate); +- +-/** +- * devlink_linecard_nested_dl_set - Attach/detach nested devlink +- * instance to linecard. +- * +- * @linecard: devlink linecard +- * @nested_devlink: devlink instance to attach or NULL to detach +- */ +-void devlink_linecard_nested_dl_set(struct devlink_linecard *linecard, +- struct devlink *nested_devlink) +-{ +- mutex_lock(&linecard->state_lock); +- linecard->nested_devlink = nested_devlink; +- devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); +- mutex_unlock(&linecard->state_lock); +-} +-EXPORT_SYMBOL_GPL(devlink_linecard_nested_dl_set); +- +-int devl_sb_register(struct devlink *devlink, unsigned int sb_index, +- u32 size, u16 ingress_pools_count, +- u16 egress_pools_count, u16 ingress_tc_count, +- u16 egress_tc_count) +-{ +- struct devlink_sb *devlink_sb; +- +- lockdep_assert_held(&devlink->lock); +- +- if (devlink_sb_index_exists(devlink, sb_index)) +- return -EEXIST; +- +- devlink_sb = kzalloc(sizeof(*devlink_sb), GFP_KERNEL); +- if (!devlink_sb) +- return -ENOMEM; +- devlink_sb->index = sb_index; +- devlink_sb->size = size; +- devlink_sb->ingress_pools_count = ingress_pools_count; +- devlink_sb->egress_pools_count = egress_pools_count; +- devlink_sb->ingress_tc_count = ingress_tc_count; +- devlink_sb->egress_tc_count = egress_tc_count; +- list_add_tail(&devlink_sb->list, &devlink->sb_list); +- return 0; +-} +-EXPORT_SYMBOL_GPL(devl_sb_register); +- +-int devlink_sb_register(struct devlink *devlink, unsigned int sb_index, +- u32 size, u16 ingress_pools_count, +- u16 egress_pools_count, u16 ingress_tc_count, +- u16 egress_tc_count) +-{ +- int err; +- +- devl_lock(devlink); +- err = devl_sb_register(devlink, sb_index, size, ingress_pools_count, +- egress_pools_count, ingress_tc_count, +- egress_tc_count); +- devl_unlock(devlink); +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_sb_register); +- +-void devl_sb_unregister(struct devlink *devlink, unsigned int sb_index) +-{ +- struct devlink_sb *devlink_sb; +- +- lockdep_assert_held(&devlink->lock); +- +- devlink_sb = devlink_sb_get_by_index(devlink, sb_index); +- WARN_ON(!devlink_sb); +- list_del(&devlink_sb->list); +- kfree(devlink_sb); +-} +-EXPORT_SYMBOL_GPL(devl_sb_unregister); +- +-void devlink_sb_unregister(struct devlink *devlink, unsigned int sb_index) +-{ +- devl_lock(devlink); +- devl_sb_unregister(devlink, sb_index); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_sb_unregister); +- +-/** +- * devl_dpipe_headers_register - register dpipe headers +- * +- * @devlink: devlink +- * @dpipe_headers: dpipe header array +- * +- * Register the headers supported by hardware. +- */ +-void devl_dpipe_headers_register(struct devlink *devlink, +- struct devlink_dpipe_headers *dpipe_headers) +-{ +- lockdep_assert_held(&devlink->lock); +- +- devlink->dpipe_headers = dpipe_headers; +-} +-EXPORT_SYMBOL_GPL(devl_dpipe_headers_register); +- +-/** +- * devl_dpipe_headers_unregister - unregister dpipe headers +- * +- * @devlink: devlink +- * +- * Unregister the headers supported by hardware. +- */ +-void devl_dpipe_headers_unregister(struct devlink *devlink) +-{ +- lockdep_assert_held(&devlink->lock); +- +- devlink->dpipe_headers = NULL; +-} +-EXPORT_SYMBOL_GPL(devl_dpipe_headers_unregister); +- +-/** +- * devlink_dpipe_table_counter_enabled - check if counter allocation +- * required +- * @devlink: devlink +- * @table_name: tables name +- * +- * Used by driver to check if counter allocation is required. +- * After counter allocation is turned on the table entries +- * are updated to include counter statistics. +- * +- * After that point on the driver must respect the counter +- * state so that each entry added to the table is added +- * with a counter. +- */ +-bool devlink_dpipe_table_counter_enabled(struct devlink *devlink, +- const char *table_name) +-{ +- struct devlink_dpipe_table *table; +- bool enabled; +- +- rcu_read_lock(); +- table = devlink_dpipe_table_find(&devlink->dpipe_table_list, +- table_name, devlink); +- enabled = false; +- if (table) +- enabled = table->counters_enabled; +- rcu_read_unlock(); +- return enabled; +-} +-EXPORT_SYMBOL_GPL(devlink_dpipe_table_counter_enabled); +- +-/** +- * devl_dpipe_table_register - register dpipe table +- * +- * @devlink: devlink +- * @table_name: table name +- * @table_ops: table ops +- * @priv: priv +- * @counter_control_extern: external control for counters +- */ +-int devl_dpipe_table_register(struct devlink *devlink, +- const char *table_name, +- struct devlink_dpipe_table_ops *table_ops, +- void *priv, bool counter_control_extern) +-{ +- struct devlink_dpipe_table *table; +- +- lockdep_assert_held(&devlink->lock); +- +- if (WARN_ON(!table_ops->size_get)) +- return -EINVAL; +- +- if (devlink_dpipe_table_find(&devlink->dpipe_table_list, table_name, +- devlink)) +- return -EEXIST; +- +- table = kzalloc(sizeof(*table), GFP_KERNEL); +- if (!table) +- return -ENOMEM; +- +- table->name = table_name; +- table->table_ops = table_ops; +- table->priv = priv; +- table->counter_control_extern = counter_control_extern; +- +- list_add_tail_rcu(&table->list, &devlink->dpipe_table_list); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devl_dpipe_table_register); +- +-/** +- * devl_dpipe_table_unregister - unregister dpipe table +- * +- * @devlink: devlink +- * @table_name: table name +- */ +-void devl_dpipe_table_unregister(struct devlink *devlink, +- const char *table_name) +-{ +- struct devlink_dpipe_table *table; +- +- lockdep_assert_held(&devlink->lock); +- +- table = devlink_dpipe_table_find(&devlink->dpipe_table_list, +- table_name, devlink); +- if (!table) +- return; +- list_del_rcu(&table->list); +- kfree_rcu(table, rcu); +-} +-EXPORT_SYMBOL_GPL(devl_dpipe_table_unregister); +- +-/** +- * devl_resource_register - devlink resource register +- * +- * @devlink: devlink +- * @resource_name: resource's name +- * @resource_size: resource's size +- * @resource_id: resource's id +- * @parent_resource_id: resource's parent id +- * @size_params: size parameters +- * +- * Generic resources should reuse the same names across drivers. +- * Please see the generic resources list at: +- * Documentation/networking/devlink/devlink-resource.rst +- */ +-int devl_resource_register(struct devlink *devlink, +- const char *resource_name, +- u64 resource_size, +- u64 resource_id, +- u64 parent_resource_id, +- const struct devlink_resource_size_params *size_params) +-{ +- struct devlink_resource *resource; +- struct list_head *resource_list; +- bool top_hierarchy; +- +- lockdep_assert_held(&devlink->lock); +- +- top_hierarchy = parent_resource_id == DEVLINK_RESOURCE_ID_PARENT_TOP; +- +- resource = devlink_resource_find(devlink, NULL, resource_id); +- if (resource) +- return -EINVAL; +- +- resource = kzalloc(sizeof(*resource), GFP_KERNEL); +- if (!resource) +- return -ENOMEM; +- +- if (top_hierarchy) { +- resource_list = &devlink->resource_list; +- } else { +- struct devlink_resource *parent_resource; +- +- parent_resource = devlink_resource_find(devlink, NULL, +- parent_resource_id); +- if (parent_resource) { +- resource_list = &parent_resource->resource_list; +- resource->parent = parent_resource; +- } else { +- kfree(resource); +- return -EINVAL; +- } +- } +- +- resource->name = resource_name; +- resource->size = resource_size; +- resource->size_new = resource_size; +- resource->id = resource_id; +- resource->size_valid = true; +- memcpy(&resource->size_params, size_params, +- sizeof(resource->size_params)); +- INIT_LIST_HEAD(&resource->resource_list); +- list_add_tail(&resource->list, resource_list); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devl_resource_register); +- +-/** +- * devlink_resource_register - devlink resource register +- * +- * @devlink: devlink +- * @resource_name: resource's name +- * @resource_size: resource's size +- * @resource_id: resource's id +- * @parent_resource_id: resource's parent id +- * @size_params: size parameters +- * +- * Generic resources should reuse the same names across drivers. +- * Please see the generic resources list at: +- * Documentation/networking/devlink/devlink-resource.rst +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-int devlink_resource_register(struct devlink *devlink, +- const char *resource_name, +- u64 resource_size, +- u64 resource_id, +- u64 parent_resource_id, +- const struct devlink_resource_size_params *size_params) +-{ +- int err; +- +- devl_lock(devlink); +- err = devl_resource_register(devlink, resource_name, resource_size, +- resource_id, parent_resource_id, size_params); +- devl_unlock(devlink); +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_resource_register); +- +-static void devlink_resource_unregister(struct devlink *devlink, +- struct devlink_resource *resource) +-{ +- struct devlink_resource *tmp, *child_resource; +- +- list_for_each_entry_safe(child_resource, tmp, &resource->resource_list, +- list) { +- devlink_resource_unregister(devlink, child_resource); +- list_del(&child_resource->list); +- kfree(child_resource); +- } +-} +- +-/** +- * devl_resources_unregister - free all resources +- * +- * @devlink: devlink +- */ +-void devl_resources_unregister(struct devlink *devlink) +-{ +- struct devlink_resource *tmp, *child_resource; +- +- lockdep_assert_held(&devlink->lock); +- +- list_for_each_entry_safe(child_resource, tmp, &devlink->resource_list, +- list) { +- devlink_resource_unregister(devlink, child_resource); +- list_del(&child_resource->list); +- kfree(child_resource); +- } +-} +-EXPORT_SYMBOL_GPL(devl_resources_unregister); +- +-/** +- * devlink_resources_unregister - free all resources +- * +- * @devlink: devlink +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-void devlink_resources_unregister(struct devlink *devlink) +-{ +- devl_lock(devlink); +- devl_resources_unregister(devlink); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_resources_unregister); +- +-/** +- * devl_resource_size_get - get and update size +- * +- * @devlink: devlink +- * @resource_id: the requested resource id +- * @p_resource_size: ptr to update +- */ +-int devl_resource_size_get(struct devlink *devlink, +- u64 resource_id, +- u64 *p_resource_size) +-{ +- struct devlink_resource *resource; +- +- lockdep_assert_held(&devlink->lock); +- +- resource = devlink_resource_find(devlink, NULL, resource_id); +- if (!resource) +- return -EINVAL; +- *p_resource_size = resource->size_new; +- resource->size = resource->size_new; +- return 0; +-} +-EXPORT_SYMBOL_GPL(devl_resource_size_get); +- +-/** +- * devl_dpipe_table_resource_set - set the resource id +- * +- * @devlink: devlink +- * @table_name: table name +- * @resource_id: resource id +- * @resource_units: number of resource's units consumed per table's entry +- */ +-int devl_dpipe_table_resource_set(struct devlink *devlink, +- const char *table_name, u64 resource_id, +- u64 resource_units) +-{ +- struct devlink_dpipe_table *table; +- +- table = devlink_dpipe_table_find(&devlink->dpipe_table_list, +- table_name, devlink); +- if (!table) +- return -EINVAL; +- +- table->resource_id = resource_id; +- table->resource_units = resource_units; +- table->resource_valid = true; +- return 0; +-} +-EXPORT_SYMBOL_GPL(devl_dpipe_table_resource_set); +- +-/** +- * devl_resource_occ_get_register - register occupancy getter +- * +- * @devlink: devlink +- * @resource_id: resource id +- * @occ_get: occupancy getter callback +- * @occ_get_priv: occupancy getter callback priv +- */ +-void devl_resource_occ_get_register(struct devlink *devlink, +- u64 resource_id, +- devlink_resource_occ_get_t *occ_get, +- void *occ_get_priv) +-{ +- struct devlink_resource *resource; +- +- lockdep_assert_held(&devlink->lock); +- +- resource = devlink_resource_find(devlink, NULL, resource_id); +- if (WARN_ON(!resource)) +- return; +- WARN_ON(resource->occ_get); +- +- resource->occ_get = occ_get; +- resource->occ_get_priv = occ_get_priv; +-} +-EXPORT_SYMBOL_GPL(devl_resource_occ_get_register); +- +-/** +- * devlink_resource_occ_get_register - register occupancy getter +- * +- * @devlink: devlink +- * @resource_id: resource id +- * @occ_get: occupancy getter callback +- * @occ_get_priv: occupancy getter callback priv +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-void devlink_resource_occ_get_register(struct devlink *devlink, +- u64 resource_id, +- devlink_resource_occ_get_t *occ_get, +- void *occ_get_priv) +-{ +- devl_lock(devlink); +- devl_resource_occ_get_register(devlink, resource_id, +- occ_get, occ_get_priv); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_resource_occ_get_register); +- +-/** +- * devl_resource_occ_get_unregister - unregister occupancy getter +- * +- * @devlink: devlink +- * @resource_id: resource id +- */ +-void devl_resource_occ_get_unregister(struct devlink *devlink, +- u64 resource_id) +-{ +- struct devlink_resource *resource; +- +- lockdep_assert_held(&devlink->lock); +- +- resource = devlink_resource_find(devlink, NULL, resource_id); +- if (WARN_ON(!resource)) +- return; +- WARN_ON(!resource->occ_get); +- +- resource->occ_get = NULL; +- resource->occ_get_priv = NULL; +-} +-EXPORT_SYMBOL_GPL(devl_resource_occ_get_unregister); +- +-/** +- * devlink_resource_occ_get_unregister - unregister occupancy getter +- * +- * @devlink: devlink +- * @resource_id: resource id +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-void devlink_resource_occ_get_unregister(struct devlink *devlink, +- u64 resource_id) +-{ +- devl_lock(devlink); +- devl_resource_occ_get_unregister(devlink, resource_id); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_resource_occ_get_unregister); +- +-static int devlink_param_verify(const struct devlink_param *param) +-{ +- if (!param || !param->name || !param->supported_cmodes) +- return -EINVAL; +- if (param->generic) +- return devlink_param_generic_verify(param); +- else +- return devlink_param_driver_verify(param); +-} +- +-/** +- * devlink_params_register - register configuration parameters +- * +- * @devlink: devlink +- * @params: configuration parameters array +- * @params_count: number of parameters provided +- * +- * Register the configuration parameters supported by the driver. +- */ +-int devlink_params_register(struct devlink *devlink, +- const struct devlink_param *params, +- size_t params_count) +-{ +- const struct devlink_param *param = params; +- int i, err; +- +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- +- for (i = 0; i < params_count; i++, param++) { +- err = devlink_param_register(devlink, param); +- if (err) +- goto rollback; +- } +- return 0; +- +-rollback: +- if (!i) +- return err; +- +- for (param--; i > 0; i--, param--) +- devlink_param_unregister(devlink, param); +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_params_register); +- +-/** +- * devlink_params_unregister - unregister configuration parameters +- * @devlink: devlink +- * @params: configuration parameters to unregister +- * @params_count: number of parameters provided +- */ +-void devlink_params_unregister(struct devlink *devlink, +- const struct devlink_param *params, +- size_t params_count) +-{ +- const struct devlink_param *param = params; +- int i; +- +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- +- for (i = 0; i < params_count; i++, param++) +- devlink_param_unregister(devlink, param); +-} +-EXPORT_SYMBOL_GPL(devlink_params_unregister); +- +-/** +- * devlink_param_register - register one configuration parameter +- * +- * @devlink: devlink +- * @param: one configuration parameter +- * +- * Register the configuration parameter supported by the driver. +- * Return: returns 0 on successful registration or error code otherwise. +- */ +-int devlink_param_register(struct devlink *devlink, +- const struct devlink_param *param) +-{ +- struct devlink_param_item *param_item; +- +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- +- WARN_ON(devlink_param_verify(param)); +- WARN_ON(devlink_param_find_by_name(&devlink->param_list, param->name)); +- +- if (param->supported_cmodes == BIT(DEVLINK_PARAM_CMODE_DRIVERINIT)) +- WARN_ON(param->get || param->set); +- else +- WARN_ON(!param->get || !param->set); +- +- param_item = kzalloc(sizeof(*param_item), GFP_KERNEL); +- if (!param_item) +- return -ENOMEM; +- +- param_item->param = param; +- +- list_add_tail(¶m_item->list, &devlink->param_list); +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_param_register); +- +-/** +- * devlink_param_unregister - unregister one configuration parameter +- * @devlink: devlink +- * @param: configuration parameter to unregister +- */ +-void devlink_param_unregister(struct devlink *devlink, +- const struct devlink_param *param) +-{ +- struct devlink_param_item *param_item; +- +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- +- param_item = +- devlink_param_find_by_name(&devlink->param_list, param->name); +- WARN_ON(!param_item); +- list_del(¶m_item->list); +- kfree(param_item); +-} +-EXPORT_SYMBOL_GPL(devlink_param_unregister); +- +-/** +- * devlink_param_driverinit_value_get - get configuration parameter +- * value for driver initializing +- * +- * @devlink: devlink +- * @param_id: parameter ID +- * @init_val: value of parameter in driverinit configuration mode +- * +- * This function should be used by the driver to get driverinit +- * configuration for initialization after reload command. +- */ +-int devlink_param_driverinit_value_get(struct devlink *devlink, u32 param_id, +- union devlink_param_value *init_val) +-{ +- struct devlink_param_item *param_item; +- +- if (!devlink_reload_supported(devlink->ops)) +- return -EOPNOTSUPP; +- +- param_item = devlink_param_find_by_id(&devlink->param_list, param_id); +- if (!param_item) +- return -EINVAL; +- +- if (!param_item->driverinit_value_valid || +- !devlink_param_cmode_is_supported(param_item->param, +- DEVLINK_PARAM_CMODE_DRIVERINIT)) +- return -EOPNOTSUPP; +- +- if (param_item->param->type == DEVLINK_PARAM_TYPE_STRING) +- strcpy(init_val->vstr, param_item->driverinit_value.vstr); +- else +- *init_val = param_item->driverinit_value; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_get); +- +-/** +- * devlink_param_driverinit_value_set - set value of configuration +- * parameter for driverinit +- * configuration mode +- * +- * @devlink: devlink +- * @param_id: parameter ID +- * @init_val: value of parameter to set for driverinit configuration mode +- * +- * This function should be used by the driver to set driverinit +- * configuration mode default value. +- */ +-int devlink_param_driverinit_value_set(struct devlink *devlink, u32 param_id, +- union devlink_param_value init_val) +-{ +- struct devlink_param_item *param_item; +- +- ASSERT_DEVLINK_NOT_REGISTERED(devlink); +- +- param_item = devlink_param_find_by_id(&devlink->param_list, param_id); +- if (!param_item) +- return -EINVAL; +- +- if (!devlink_param_cmode_is_supported(param_item->param, +- DEVLINK_PARAM_CMODE_DRIVERINIT)) +- return -EOPNOTSUPP; +- +- if (param_item->param->type == DEVLINK_PARAM_TYPE_STRING) +- strcpy(param_item->driverinit_value.vstr, init_val.vstr); +- else +- param_item->driverinit_value = init_val; +- param_item->driverinit_value_valid = true; +- return 0; +-} +-EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_set); +- +-/** +- * devlink_param_value_changed - notify devlink on a parameter's value +- * change. Should be called by the driver +- * right after the change. +- * +- * @devlink: devlink +- * @param_id: parameter ID +- * +- * This function should be used by the driver to notify devlink on value +- * change, excluding driverinit configuration mode. +- * For driverinit configuration mode driver should use the function +- */ +-void devlink_param_value_changed(struct devlink *devlink, u32 param_id) +-{ +- struct devlink_param_item *param_item; +- +- param_item = devlink_param_find_by_id(&devlink->param_list, param_id); +- WARN_ON(!param_item); +- +- devlink_param_notify(devlink, 0, param_item, DEVLINK_CMD_PARAM_NEW); +-} +-EXPORT_SYMBOL_GPL(devlink_param_value_changed); +- +-/** +- * devl_region_create - create a new address region +- * +- * @devlink: devlink +- * @ops: region operations and name +- * @region_max_snapshots: Maximum supported number of snapshots for region +- * @region_size: size of region +- */ +-struct devlink_region *devl_region_create(struct devlink *devlink, +- const struct devlink_region_ops *ops, +- u32 region_max_snapshots, +- u64 region_size) +-{ +- struct devlink_region *region; +- +- devl_assert_locked(devlink); +- +- if (WARN_ON(!ops) || WARN_ON(!ops->destructor)) +- return ERR_PTR(-EINVAL); +- +- if (devlink_region_get_by_name(devlink, ops->name)) +- return ERR_PTR(-EEXIST); +- +- region = kzalloc(sizeof(*region), GFP_KERNEL); +- if (!region) +- return ERR_PTR(-ENOMEM); +- +- region->devlink = devlink; +- region->max_snapshots = region_max_snapshots; +- region->ops = ops; +- region->size = region_size; +- INIT_LIST_HEAD(®ion->snapshot_list); +- mutex_init(®ion->snapshot_lock); +- list_add_tail(®ion->list, &devlink->region_list); +- devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW); +- +- return region; +-} +-EXPORT_SYMBOL_GPL(devl_region_create); +- +-/** +- * devlink_region_create - create a new address region +- * +- * @devlink: devlink +- * @ops: region operations and name +- * @region_max_snapshots: Maximum supported number of snapshots for region +- * @region_size: size of region +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-struct devlink_region * +-devlink_region_create(struct devlink *devlink, +- const struct devlink_region_ops *ops, +- u32 region_max_snapshots, u64 region_size) +-{ +- struct devlink_region *region; +- +- devl_lock(devlink); +- region = devl_region_create(devlink, ops, region_max_snapshots, +- region_size); +- devl_unlock(devlink); +- return region; +-} +-EXPORT_SYMBOL_GPL(devlink_region_create); +- +-/** +- * devlink_port_region_create - create a new address region for a port +- * +- * @port: devlink port +- * @ops: region operations and name +- * @region_max_snapshots: Maximum supported number of snapshots for region +- * @region_size: size of region +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-struct devlink_region * +-devlink_port_region_create(struct devlink_port *port, +- const struct devlink_port_region_ops *ops, +- u32 region_max_snapshots, u64 region_size) +-{ +- struct devlink *devlink = port->devlink; +- struct devlink_region *region; +- int err = 0; +- +- ASSERT_DEVLINK_PORT_INITIALIZED(port); +- +- if (WARN_ON(!ops) || WARN_ON(!ops->destructor)) +- return ERR_PTR(-EINVAL); +- +- devl_lock(devlink); +- +- if (devlink_port_region_get_by_name(port, ops->name)) { +- err = -EEXIST; +- goto unlock; +- } +- +- region = kzalloc(sizeof(*region), GFP_KERNEL); +- if (!region) { +- err = -ENOMEM; +- goto unlock; +- } +- +- region->devlink = devlink; +- region->port = port; +- region->max_snapshots = region_max_snapshots; +- region->port_ops = ops; +- region->size = region_size; +- INIT_LIST_HEAD(®ion->snapshot_list); +- mutex_init(®ion->snapshot_lock); +- list_add_tail(®ion->list, &port->region_list); +- devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW); +- +- devl_unlock(devlink); +- return region; +- +-unlock: +- devl_unlock(devlink); +- return ERR_PTR(err); +-} +-EXPORT_SYMBOL_GPL(devlink_port_region_create); +- +-/** +- * devl_region_destroy - destroy address region +- * +- * @region: devlink region to destroy +- */ +-void devl_region_destroy(struct devlink_region *region) +-{ +- struct devlink *devlink = region->devlink; +- struct devlink_snapshot *snapshot, *ts; +- +- devl_assert_locked(devlink); +- +- /* Free all snapshots of region */ +- mutex_lock(®ion->snapshot_lock); +- list_for_each_entry_safe(snapshot, ts, ®ion->snapshot_list, list) +- devlink_region_snapshot_del(region, snapshot); +- mutex_unlock(®ion->snapshot_lock); +- +- list_del(®ion->list); +- mutex_destroy(®ion->snapshot_lock); +- +- devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_DEL); +- kfree(region); +-} +-EXPORT_SYMBOL_GPL(devl_region_destroy); +- +-/** +- * devlink_region_destroy - destroy address region +- * +- * @region: devlink region to destroy +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-void devlink_region_destroy(struct devlink_region *region) +-{ +- struct devlink *devlink = region->devlink; +- +- devl_lock(devlink); +- devl_region_destroy(region); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_region_destroy); +- +-/** +- * devlink_region_snapshot_id_get - get snapshot ID +- * +- * This callback should be called when adding a new snapshot, +- * Driver should use the same id for multiple snapshots taken +- * on multiple regions at the same time/by the same trigger. +- * +- * The caller of this function must use devlink_region_snapshot_id_put +- * when finished creating regions using this id. +- * +- * Returns zero on success, or a negative error code on failure. +- * +- * @devlink: devlink +- * @id: storage to return id +- */ +-int devlink_region_snapshot_id_get(struct devlink *devlink, u32 *id) +-{ +- return __devlink_region_snapshot_id_get(devlink, id); +-} +-EXPORT_SYMBOL_GPL(devlink_region_snapshot_id_get); +- +-/** +- * devlink_region_snapshot_id_put - put snapshot ID reference +- * +- * This should be called by a driver after finishing creating snapshots +- * with an id. Doing so ensures that the ID can later be released in the +- * event that all snapshots using it have been destroyed. +- * +- * @devlink: devlink +- * @id: id to release reference on +- */ +-void devlink_region_snapshot_id_put(struct devlink *devlink, u32 id) +-{ +- __devlink_snapshot_id_decrement(devlink, id); +-} +-EXPORT_SYMBOL_GPL(devlink_region_snapshot_id_put); +- +-/** +- * devlink_region_snapshot_create - create a new snapshot +- * This will add a new snapshot of a region. The snapshot +- * will be stored on the region struct and can be accessed +- * from devlink. This is useful for future analyses of snapshots. +- * Multiple snapshots can be created on a region. +- * The @snapshot_id should be obtained using the getter function. +- * +- * @region: devlink region of the snapshot +- * @data: snapshot data +- * @snapshot_id: snapshot id to be created +- */ +-int devlink_region_snapshot_create(struct devlink_region *region, +- u8 *data, u32 snapshot_id) +-{ +- int err; +- +- mutex_lock(®ion->snapshot_lock); +- err = __devlink_region_snapshot_create(region, data, snapshot_id); +- mutex_unlock(®ion->snapshot_lock); +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_region_snapshot_create); +- +-#define DEVLINK_TRAP(_id, _type) \ +- { \ +- .type = DEVLINK_TRAP_TYPE_##_type, \ +- .id = DEVLINK_TRAP_GENERIC_ID_##_id, \ +- .name = DEVLINK_TRAP_GENERIC_NAME_##_id, \ +- } +- +-static const struct devlink_trap devlink_trap_generic[] = { +- DEVLINK_TRAP(SMAC_MC, DROP), +- DEVLINK_TRAP(VLAN_TAG_MISMATCH, DROP), +- DEVLINK_TRAP(INGRESS_VLAN_FILTER, DROP), +- DEVLINK_TRAP(INGRESS_STP_FILTER, DROP), +- DEVLINK_TRAP(EMPTY_TX_LIST, DROP), +- DEVLINK_TRAP(PORT_LOOPBACK_FILTER, DROP), +- DEVLINK_TRAP(BLACKHOLE_ROUTE, DROP), +- DEVLINK_TRAP(TTL_ERROR, EXCEPTION), +- DEVLINK_TRAP(TAIL_DROP, DROP), +- DEVLINK_TRAP(NON_IP_PACKET, DROP), +- DEVLINK_TRAP(UC_DIP_MC_DMAC, DROP), +- DEVLINK_TRAP(DIP_LB, DROP), +- DEVLINK_TRAP(SIP_MC, DROP), +- DEVLINK_TRAP(SIP_LB, DROP), +- DEVLINK_TRAP(CORRUPTED_IP_HDR, DROP), +- DEVLINK_TRAP(IPV4_SIP_BC, DROP), +- DEVLINK_TRAP(IPV6_MC_DIP_RESERVED_SCOPE, DROP), +- DEVLINK_TRAP(IPV6_MC_DIP_INTERFACE_LOCAL_SCOPE, DROP), +- DEVLINK_TRAP(MTU_ERROR, EXCEPTION), +- DEVLINK_TRAP(UNRESOLVED_NEIGH, EXCEPTION), +- DEVLINK_TRAP(RPF, EXCEPTION), +- DEVLINK_TRAP(REJECT_ROUTE, EXCEPTION), +- DEVLINK_TRAP(IPV4_LPM_UNICAST_MISS, EXCEPTION), +- DEVLINK_TRAP(IPV6_LPM_UNICAST_MISS, EXCEPTION), +- DEVLINK_TRAP(NON_ROUTABLE, DROP), +- DEVLINK_TRAP(DECAP_ERROR, EXCEPTION), +- DEVLINK_TRAP(OVERLAY_SMAC_MC, DROP), +- DEVLINK_TRAP(INGRESS_FLOW_ACTION_DROP, DROP), +- DEVLINK_TRAP(EGRESS_FLOW_ACTION_DROP, DROP), +- DEVLINK_TRAP(STP, CONTROL), +- DEVLINK_TRAP(LACP, CONTROL), +- DEVLINK_TRAP(LLDP, CONTROL), +- DEVLINK_TRAP(IGMP_QUERY, CONTROL), +- DEVLINK_TRAP(IGMP_V1_REPORT, CONTROL), +- DEVLINK_TRAP(IGMP_V2_REPORT, CONTROL), +- DEVLINK_TRAP(IGMP_V3_REPORT, CONTROL), +- DEVLINK_TRAP(IGMP_V2_LEAVE, CONTROL), +- DEVLINK_TRAP(MLD_QUERY, CONTROL), +- DEVLINK_TRAP(MLD_V1_REPORT, CONTROL), +- DEVLINK_TRAP(MLD_V2_REPORT, CONTROL), +- DEVLINK_TRAP(MLD_V1_DONE, CONTROL), +- DEVLINK_TRAP(IPV4_DHCP, CONTROL), +- DEVLINK_TRAP(IPV6_DHCP, CONTROL), +- DEVLINK_TRAP(ARP_REQUEST, CONTROL), +- DEVLINK_TRAP(ARP_RESPONSE, CONTROL), +- DEVLINK_TRAP(ARP_OVERLAY, CONTROL), +- DEVLINK_TRAP(IPV6_NEIGH_SOLICIT, CONTROL), +- DEVLINK_TRAP(IPV6_NEIGH_ADVERT, CONTROL), +- DEVLINK_TRAP(IPV4_BFD, CONTROL), +- DEVLINK_TRAP(IPV6_BFD, CONTROL), +- DEVLINK_TRAP(IPV4_OSPF, CONTROL), +- DEVLINK_TRAP(IPV6_OSPF, CONTROL), +- DEVLINK_TRAP(IPV4_BGP, CONTROL), +- DEVLINK_TRAP(IPV6_BGP, CONTROL), +- DEVLINK_TRAP(IPV4_VRRP, CONTROL), +- DEVLINK_TRAP(IPV6_VRRP, CONTROL), +- DEVLINK_TRAP(IPV4_PIM, CONTROL), +- DEVLINK_TRAP(IPV6_PIM, CONTROL), +- DEVLINK_TRAP(UC_LB, CONTROL), +- DEVLINK_TRAP(LOCAL_ROUTE, CONTROL), +- DEVLINK_TRAP(EXTERNAL_ROUTE, CONTROL), +- DEVLINK_TRAP(IPV6_UC_DIP_LINK_LOCAL_SCOPE, CONTROL), +- DEVLINK_TRAP(IPV6_DIP_ALL_NODES, CONTROL), +- DEVLINK_TRAP(IPV6_DIP_ALL_ROUTERS, CONTROL), +- DEVLINK_TRAP(IPV6_ROUTER_SOLICIT, CONTROL), +- DEVLINK_TRAP(IPV6_ROUTER_ADVERT, CONTROL), +- DEVLINK_TRAP(IPV6_REDIRECT, CONTROL), +- DEVLINK_TRAP(IPV4_ROUTER_ALERT, CONTROL), +- DEVLINK_TRAP(IPV6_ROUTER_ALERT, CONTROL), +- DEVLINK_TRAP(PTP_EVENT, CONTROL), +- DEVLINK_TRAP(PTP_GENERAL, CONTROL), +- DEVLINK_TRAP(FLOW_ACTION_SAMPLE, CONTROL), +- DEVLINK_TRAP(FLOW_ACTION_TRAP, CONTROL), +- DEVLINK_TRAP(EARLY_DROP, DROP), +- DEVLINK_TRAP(VXLAN_PARSING, DROP), +- DEVLINK_TRAP(LLC_SNAP_PARSING, DROP), +- DEVLINK_TRAP(VLAN_PARSING, DROP), +- DEVLINK_TRAP(PPPOE_PPP_PARSING, DROP), +- DEVLINK_TRAP(MPLS_PARSING, DROP), +- DEVLINK_TRAP(ARP_PARSING, DROP), +- DEVLINK_TRAP(IP_1_PARSING, DROP), +- DEVLINK_TRAP(IP_N_PARSING, DROP), +- DEVLINK_TRAP(GRE_PARSING, DROP), +- DEVLINK_TRAP(UDP_PARSING, DROP), +- DEVLINK_TRAP(TCP_PARSING, DROP), +- DEVLINK_TRAP(IPSEC_PARSING, DROP), +- DEVLINK_TRAP(SCTP_PARSING, DROP), +- DEVLINK_TRAP(DCCP_PARSING, DROP), +- DEVLINK_TRAP(GTP_PARSING, DROP), +- DEVLINK_TRAP(ESP_PARSING, DROP), +- DEVLINK_TRAP(BLACKHOLE_NEXTHOP, DROP), +- DEVLINK_TRAP(DMAC_FILTER, DROP), +-}; +- +-#define DEVLINK_TRAP_GROUP(_id) \ +- { \ +- .id = DEVLINK_TRAP_GROUP_GENERIC_ID_##_id, \ +- .name = DEVLINK_TRAP_GROUP_GENERIC_NAME_##_id, \ +- } +- +-static const struct devlink_trap_group devlink_trap_group_generic[] = { +- DEVLINK_TRAP_GROUP(L2_DROPS), +- DEVLINK_TRAP_GROUP(L3_DROPS), +- DEVLINK_TRAP_GROUP(L3_EXCEPTIONS), +- DEVLINK_TRAP_GROUP(BUFFER_DROPS), +- DEVLINK_TRAP_GROUP(TUNNEL_DROPS), +- DEVLINK_TRAP_GROUP(ACL_DROPS), +- DEVLINK_TRAP_GROUP(STP), +- DEVLINK_TRAP_GROUP(LACP), +- DEVLINK_TRAP_GROUP(LLDP), +- DEVLINK_TRAP_GROUP(MC_SNOOPING), +- DEVLINK_TRAP_GROUP(DHCP), +- DEVLINK_TRAP_GROUP(NEIGH_DISCOVERY), +- DEVLINK_TRAP_GROUP(BFD), +- DEVLINK_TRAP_GROUP(OSPF), +- DEVLINK_TRAP_GROUP(BGP), +- DEVLINK_TRAP_GROUP(VRRP), +- DEVLINK_TRAP_GROUP(PIM), +- DEVLINK_TRAP_GROUP(UC_LB), +- DEVLINK_TRAP_GROUP(LOCAL_DELIVERY), +- DEVLINK_TRAP_GROUP(EXTERNAL_DELIVERY), +- DEVLINK_TRAP_GROUP(IPV6), +- DEVLINK_TRAP_GROUP(PTP_EVENT), +- DEVLINK_TRAP_GROUP(PTP_GENERAL), +- DEVLINK_TRAP_GROUP(ACL_SAMPLE), +- DEVLINK_TRAP_GROUP(ACL_TRAP), +- DEVLINK_TRAP_GROUP(PARSER_ERROR_DROPS), +-}; +- +-static int devlink_trap_generic_verify(const struct devlink_trap *trap) +-{ +- if (trap->id > DEVLINK_TRAP_GENERIC_ID_MAX) +- return -EINVAL; +- +- if (strcmp(trap->name, devlink_trap_generic[trap->id].name)) +- return -EINVAL; +- +- if (trap->type != devlink_trap_generic[trap->id].type) +- return -EINVAL; +- +- return 0; +-} +- +-static int devlink_trap_driver_verify(const struct devlink_trap *trap) +-{ +- int i; +- +- if (trap->id <= DEVLINK_TRAP_GENERIC_ID_MAX) +- return -EINVAL; +- +- for (i = 0; i < ARRAY_SIZE(devlink_trap_generic); i++) { +- if (!strcmp(trap->name, devlink_trap_generic[i].name)) +- return -EEXIST; +- } +- +- return 0; +-} +- +-static int devlink_trap_verify(const struct devlink_trap *trap) +-{ +- if (!trap || !trap->name) +- return -EINVAL; +- +- if (trap->generic) +- return devlink_trap_generic_verify(trap); +- else +- return devlink_trap_driver_verify(trap); +-} +- +-static int +-devlink_trap_group_generic_verify(const struct devlink_trap_group *group) +-{ +- if (group->id > DEVLINK_TRAP_GROUP_GENERIC_ID_MAX) +- return -EINVAL; +- +- if (strcmp(group->name, devlink_trap_group_generic[group->id].name)) +- return -EINVAL; +- +- return 0; +-} +- +-static int +-devlink_trap_group_driver_verify(const struct devlink_trap_group *group) +-{ +- int i; +- +- if (group->id <= DEVLINK_TRAP_GROUP_GENERIC_ID_MAX) +- return -EINVAL; +- +- for (i = 0; i < ARRAY_SIZE(devlink_trap_group_generic); i++) { +- if (!strcmp(group->name, devlink_trap_group_generic[i].name)) +- return -EEXIST; +- } +- +- return 0; +-} +- +-static int devlink_trap_group_verify(const struct devlink_trap_group *group) +-{ +- if (group->generic) +- return devlink_trap_group_generic_verify(group); +- else +- return devlink_trap_group_driver_verify(group); +-} +- +-static void +-devlink_trap_group_notify(struct devlink *devlink, +- const struct devlink_trap_group_item *group_item, +- enum devlink_command cmd) +-{ +- struct sk_buff *msg; +- int err; +- +- WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_GROUP_NEW && +- cmd != DEVLINK_CMD_TRAP_GROUP_DEL); +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_trap_group_fill(msg, devlink, group_item, cmd, 0, 0, +- 0); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), +- msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static int +-devlink_trap_item_group_link(struct devlink *devlink, +- struct devlink_trap_item *trap_item) +-{ +- u16 group_id = trap_item->trap->init_group_id; +- struct devlink_trap_group_item *group_item; +- +- group_item = devlink_trap_group_item_lookup_by_id(devlink, group_id); +- if (WARN_ON_ONCE(!group_item)) +- return -EINVAL; +- +- trap_item->group_item = group_item; +- +- return 0; +-} +- +-static void devlink_trap_notify(struct devlink *devlink, +- const struct devlink_trap_item *trap_item, +- enum devlink_command cmd) +-{ +- struct sk_buff *msg; +- int err; +- +- WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_NEW && +- cmd != DEVLINK_CMD_TRAP_DEL); +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_trap_fill(msg, devlink, trap_item, cmd, 0, 0, 0); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), +- msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static int +-devlink_trap_register(struct devlink *devlink, +- const struct devlink_trap *trap, void *priv) +-{ +- struct devlink_trap_item *trap_item; +- int err; +- +- if (devlink_trap_item_lookup(devlink, trap->name)) +- return -EEXIST; +- +- trap_item = kzalloc(sizeof(*trap_item), GFP_KERNEL); +- if (!trap_item) +- return -ENOMEM; +- +- trap_item->stats = netdev_alloc_pcpu_stats(struct devlink_stats); +- if (!trap_item->stats) { +- err = -ENOMEM; +- goto err_stats_alloc; +- } +- +- trap_item->trap = trap; +- trap_item->action = trap->init_action; +- trap_item->priv = priv; +- +- err = devlink_trap_item_group_link(devlink, trap_item); +- if (err) +- goto err_group_link; +- +- err = devlink->ops->trap_init(devlink, trap, trap_item); +- if (err) +- goto err_trap_init; +- +- list_add_tail(&trap_item->list, &devlink->trap_list); +- devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_NEW); +- +- return 0; +- +-err_trap_init: +-err_group_link: +- free_percpu(trap_item->stats); +-err_stats_alloc: +- kfree(trap_item); +- return err; +-} +- +-static void devlink_trap_unregister(struct devlink *devlink, +- const struct devlink_trap *trap) +-{ +- struct devlink_trap_item *trap_item; +- +- trap_item = devlink_trap_item_lookup(devlink, trap->name); +- if (WARN_ON_ONCE(!trap_item)) +- return; +- +- devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_DEL); +- list_del(&trap_item->list); +- if (devlink->ops->trap_fini) +- devlink->ops->trap_fini(devlink, trap, trap_item); +- free_percpu(trap_item->stats); +- kfree(trap_item); +-} +- +-static void devlink_trap_disable(struct devlink *devlink, +- const struct devlink_trap *trap) +-{ +- struct devlink_trap_item *trap_item; +- +- trap_item = devlink_trap_item_lookup(devlink, trap->name); +- if (WARN_ON_ONCE(!trap_item)) +- return; +- +- devlink->ops->trap_action_set(devlink, trap, DEVLINK_TRAP_ACTION_DROP, +- NULL); +- trap_item->action = DEVLINK_TRAP_ACTION_DROP; +-} +- +-/** +- * devl_traps_register - Register packet traps with devlink. +- * @devlink: devlink. +- * @traps: Packet traps. +- * @traps_count: Count of provided packet traps. +- * @priv: Driver private information. +- * +- * Return: Non-zero value on failure. +- */ +-int devl_traps_register(struct devlink *devlink, +- const struct devlink_trap *traps, +- size_t traps_count, void *priv) +-{ +- int i, err; +- +- if (!devlink->ops->trap_init || !devlink->ops->trap_action_set) +- return -EINVAL; +- +- devl_assert_locked(devlink); +- for (i = 0; i < traps_count; i++) { +- const struct devlink_trap *trap = &traps[i]; +- +- err = devlink_trap_verify(trap); +- if (err) +- goto err_trap_verify; +- +- err = devlink_trap_register(devlink, trap, priv); +- if (err) +- goto err_trap_register; +- } +- +- return 0; +- +-err_trap_register: +-err_trap_verify: +- for (i--; i >= 0; i--) +- devlink_trap_unregister(devlink, &traps[i]); +- return err; +-} +-EXPORT_SYMBOL_GPL(devl_traps_register); +- +-/** +- * devlink_traps_register - Register packet traps with devlink. +- * @devlink: devlink. +- * @traps: Packet traps. +- * @traps_count: Count of provided packet traps. +- * @priv: Driver private information. +- * +- * Context: Takes and release devlink->lock <mutex>. +- * +- * Return: Non-zero value on failure. +- */ +-int devlink_traps_register(struct devlink *devlink, +- const struct devlink_trap *traps, +- size_t traps_count, void *priv) +-{ +- int err; +- +- devl_lock(devlink); +- err = devl_traps_register(devlink, traps, traps_count, priv); +- devl_unlock(devlink); +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_traps_register); +- +-/** +- * devl_traps_unregister - Unregister packet traps from devlink. +- * @devlink: devlink. +- * @traps: Packet traps. +- * @traps_count: Count of provided packet traps. +- */ +-void devl_traps_unregister(struct devlink *devlink, +- const struct devlink_trap *traps, +- size_t traps_count) +-{ +- int i; +- +- devl_assert_locked(devlink); +- /* Make sure we do not have any packets in-flight while unregistering +- * traps by disabling all of them and waiting for a grace period. +- */ +- for (i = traps_count - 1; i >= 0; i--) +- devlink_trap_disable(devlink, &traps[i]); +- synchronize_rcu(); +- for (i = traps_count - 1; i >= 0; i--) +- devlink_trap_unregister(devlink, &traps[i]); +-} +-EXPORT_SYMBOL_GPL(devl_traps_unregister); +- +-/** +- * devlink_traps_unregister - Unregister packet traps from devlink. +- * @devlink: devlink. +- * @traps: Packet traps. +- * @traps_count: Count of provided packet traps. +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-void devlink_traps_unregister(struct devlink *devlink, +- const struct devlink_trap *traps, +- size_t traps_count) +-{ +- devl_lock(devlink); +- devl_traps_unregister(devlink, traps, traps_count); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_traps_unregister); +- +-static void +-devlink_trap_stats_update(struct devlink_stats __percpu *trap_stats, +- size_t skb_len) +-{ +- struct devlink_stats *stats; +- +- stats = this_cpu_ptr(trap_stats); +- u64_stats_update_begin(&stats->syncp); +- u64_stats_add(&stats->rx_bytes, skb_len); +- u64_stats_inc(&stats->rx_packets); +- u64_stats_update_end(&stats->syncp); +-} +- +-static void +-devlink_trap_report_metadata_set(struct devlink_trap_metadata *metadata, +- const struct devlink_trap_item *trap_item, +- struct devlink_port *in_devlink_port, +- const struct flow_action_cookie *fa_cookie) +-{ +- metadata->trap_name = trap_item->trap->name; +- metadata->trap_group_name = trap_item->group_item->group->name; +- metadata->fa_cookie = fa_cookie; +- metadata->trap_type = trap_item->trap->type; +- +- spin_lock(&in_devlink_port->type_lock); +- if (in_devlink_port->type == DEVLINK_PORT_TYPE_ETH) +- metadata->input_dev = in_devlink_port->type_dev; +- spin_unlock(&in_devlink_port->type_lock); +-} +- +-/** +- * devlink_trap_report - Report trapped packet to drop monitor. +- * @devlink: devlink. +- * @skb: Trapped packet. +- * @trap_ctx: Trap context. +- * @in_devlink_port: Input devlink port. +- * @fa_cookie: Flow action cookie. Could be NULL. +- */ +-void devlink_trap_report(struct devlink *devlink, struct sk_buff *skb, +- void *trap_ctx, struct devlink_port *in_devlink_port, +- const struct flow_action_cookie *fa_cookie) +- +-{ +- struct devlink_trap_item *trap_item = trap_ctx; +- +- devlink_trap_stats_update(trap_item->stats, skb->len); +- devlink_trap_stats_update(trap_item->group_item->stats, skb->len); +- +- if (trace_devlink_trap_report_enabled()) { +- struct devlink_trap_metadata metadata = {}; +- +- devlink_trap_report_metadata_set(&metadata, trap_item, +- in_devlink_port, fa_cookie); +- trace_devlink_trap_report(devlink, skb, &metadata); +- } +-} +-EXPORT_SYMBOL_GPL(devlink_trap_report); +- +-/** +- * devlink_trap_ctx_priv - Trap context to driver private information. +- * @trap_ctx: Trap context. +- * +- * Return: Driver private information passed during registration. +- */ +-void *devlink_trap_ctx_priv(void *trap_ctx) +-{ +- struct devlink_trap_item *trap_item = trap_ctx; +- +- return trap_item->priv; +-} +-EXPORT_SYMBOL_GPL(devlink_trap_ctx_priv); +- +-static int +-devlink_trap_group_item_policer_link(struct devlink *devlink, +- struct devlink_trap_group_item *group_item) +-{ +- u32 policer_id = group_item->group->init_policer_id; +- struct devlink_trap_policer_item *policer_item; +- +- if (policer_id == 0) +- return 0; +- +- policer_item = devlink_trap_policer_item_lookup(devlink, policer_id); +- if (WARN_ON_ONCE(!policer_item)) +- return -EINVAL; +- +- group_item->policer_item = policer_item; +- +- return 0; +-} +- +-static int +-devlink_trap_group_register(struct devlink *devlink, +- const struct devlink_trap_group *group) +-{ +- struct devlink_trap_group_item *group_item; +- int err; +- +- if (devlink_trap_group_item_lookup(devlink, group->name)) +- return -EEXIST; +- +- group_item = kzalloc(sizeof(*group_item), GFP_KERNEL); +- if (!group_item) +- return -ENOMEM; +- +- group_item->stats = netdev_alloc_pcpu_stats(struct devlink_stats); +- if (!group_item->stats) { +- err = -ENOMEM; +- goto err_stats_alloc; +- } +- +- group_item->group = group; +- +- err = devlink_trap_group_item_policer_link(devlink, group_item); +- if (err) +- goto err_policer_link; +- +- if (devlink->ops->trap_group_init) { +- err = devlink->ops->trap_group_init(devlink, group); +- if (err) +- goto err_group_init; +- } +- +- list_add_tail(&group_item->list, &devlink->trap_group_list); +- devlink_trap_group_notify(devlink, group_item, +- DEVLINK_CMD_TRAP_GROUP_NEW); +- +- return 0; +- +-err_group_init: +-err_policer_link: +- free_percpu(group_item->stats); +-err_stats_alloc: +- kfree(group_item); +- return err; +-} +- +-static void +-devlink_trap_group_unregister(struct devlink *devlink, +- const struct devlink_trap_group *group) +-{ +- struct devlink_trap_group_item *group_item; +- +- group_item = devlink_trap_group_item_lookup(devlink, group->name); +- if (WARN_ON_ONCE(!group_item)) +- return; +- +- devlink_trap_group_notify(devlink, group_item, +- DEVLINK_CMD_TRAP_GROUP_DEL); +- list_del(&group_item->list); +- free_percpu(group_item->stats); +- kfree(group_item); +-} +- +-/** +- * devl_trap_groups_register - Register packet trap groups with devlink. +- * @devlink: devlink. +- * @groups: Packet trap groups. +- * @groups_count: Count of provided packet trap groups. +- * +- * Return: Non-zero value on failure. +- */ +-int devl_trap_groups_register(struct devlink *devlink, +- const struct devlink_trap_group *groups, +- size_t groups_count) +-{ +- int i, err; +- +- devl_assert_locked(devlink); +- for (i = 0; i < groups_count; i++) { +- const struct devlink_trap_group *group = &groups[i]; +- +- err = devlink_trap_group_verify(group); +- if (err) +- goto err_trap_group_verify; +- +- err = devlink_trap_group_register(devlink, group); +- if (err) +- goto err_trap_group_register; +- } +- +- return 0; +- +-err_trap_group_register: +-err_trap_group_verify: +- for (i--; i >= 0; i--) +- devlink_trap_group_unregister(devlink, &groups[i]); +- return err; +-} +-EXPORT_SYMBOL_GPL(devl_trap_groups_register); +- +-/** +- * devlink_trap_groups_register - Register packet trap groups with devlink. +- * @devlink: devlink. +- * @groups: Packet trap groups. +- * @groups_count: Count of provided packet trap groups. +- * +- * Context: Takes and release devlink->lock <mutex>. +- * +- * Return: Non-zero value on failure. +- */ +-int devlink_trap_groups_register(struct devlink *devlink, +- const struct devlink_trap_group *groups, +- size_t groups_count) +-{ +- int err; +- +- devl_lock(devlink); +- err = devl_trap_groups_register(devlink, groups, groups_count); +- devl_unlock(devlink); +- return err; +-} +-EXPORT_SYMBOL_GPL(devlink_trap_groups_register); +- +-/** +- * devl_trap_groups_unregister - Unregister packet trap groups from devlink. +- * @devlink: devlink. +- * @groups: Packet trap groups. +- * @groups_count: Count of provided packet trap groups. +- */ +-void devl_trap_groups_unregister(struct devlink *devlink, +- const struct devlink_trap_group *groups, +- size_t groups_count) +-{ +- int i; +- +- devl_assert_locked(devlink); +- for (i = groups_count - 1; i >= 0; i--) +- devlink_trap_group_unregister(devlink, &groups[i]); +-} +-EXPORT_SYMBOL_GPL(devl_trap_groups_unregister); +- +-/** +- * devlink_trap_groups_unregister - Unregister packet trap groups from devlink. +- * @devlink: devlink. +- * @groups: Packet trap groups. +- * @groups_count: Count of provided packet trap groups. +- * +- * Context: Takes and release devlink->lock <mutex>. +- */ +-void devlink_trap_groups_unregister(struct devlink *devlink, +- const struct devlink_trap_group *groups, +- size_t groups_count) +-{ +- devl_lock(devlink); +- devl_trap_groups_unregister(devlink, groups, groups_count); +- devl_unlock(devlink); +-} +-EXPORT_SYMBOL_GPL(devlink_trap_groups_unregister); +- +-static void +-devlink_trap_policer_notify(struct devlink *devlink, +- const struct devlink_trap_policer_item *policer_item, +- enum devlink_command cmd) +-{ +- struct sk_buff *msg; +- int err; +- +- WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_POLICER_NEW && +- cmd != DEVLINK_CMD_TRAP_POLICER_DEL); +- if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) +- return; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- err = devlink_nl_trap_policer_fill(msg, devlink, policer_item, cmd, 0, +- 0, 0); +- if (err) { +- nlmsg_free(msg); +- return; +- } +- +- genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), +- msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); +-} +- +-static int +-devlink_trap_policer_register(struct devlink *devlink, +- const struct devlink_trap_policer *policer) +-{ +- struct devlink_trap_policer_item *policer_item; +- int err; +- +- if (devlink_trap_policer_item_lookup(devlink, policer->id)) +- return -EEXIST; +- +- policer_item = kzalloc(sizeof(*policer_item), GFP_KERNEL); +- if (!policer_item) +- return -ENOMEM; +- +- policer_item->policer = policer; +- policer_item->rate = policer->init_rate; +- policer_item->burst = policer->init_burst; +- +- if (devlink->ops->trap_policer_init) { +- err = devlink->ops->trap_policer_init(devlink, policer); +- if (err) +- goto err_policer_init; +- } +- +- list_add_tail(&policer_item->list, &devlink->trap_policer_list); +- devlink_trap_policer_notify(devlink, policer_item, +- DEVLINK_CMD_TRAP_POLICER_NEW); +- +- return 0; +- +-err_policer_init: +- kfree(policer_item); +- return err; +-} +- +-static void +-devlink_trap_policer_unregister(struct devlink *devlink, +- const struct devlink_trap_policer *policer) +-{ +- struct devlink_trap_policer_item *policer_item; +- +- policer_item = devlink_trap_policer_item_lookup(devlink, policer->id); +- if (WARN_ON_ONCE(!policer_item)) +- return; +- +- devlink_trap_policer_notify(devlink, policer_item, +- DEVLINK_CMD_TRAP_POLICER_DEL); +- list_del(&policer_item->list); +- if (devlink->ops->trap_policer_fini) +- devlink->ops->trap_policer_fini(devlink, policer); +- kfree(policer_item); +-} +- +-/** +- * devl_trap_policers_register - Register packet trap policers with devlink. +- * @devlink: devlink. +- * @policers: Packet trap policers. +- * @policers_count: Count of provided packet trap policers. +- * +- * Return: Non-zero value on failure. +- */ +-int +-devl_trap_policers_register(struct devlink *devlink, +- const struct devlink_trap_policer *policers, +- size_t policers_count) +-{ +- int i, err; +- +- devl_assert_locked(devlink); +- for (i = 0; i < policers_count; i++) { +- const struct devlink_trap_policer *policer = &policers[i]; +- +- if (WARN_ON(policer->id == 0 || +- policer->max_rate < policer->min_rate || +- policer->max_burst < policer->min_burst)) { +- err = -EINVAL; +- goto err_trap_policer_verify; +- } +- +- err = devlink_trap_policer_register(devlink, policer); +- if (err) +- goto err_trap_policer_register; +- } +- return 0; +- +-err_trap_policer_register: +-err_trap_policer_verify: +- for (i--; i >= 0; i--) +- devlink_trap_policer_unregister(devlink, &policers[i]); +- return err; +-} +-EXPORT_SYMBOL_GPL(devl_trap_policers_register); +- +-/** +- * devl_trap_policers_unregister - Unregister packet trap policers from devlink. +- * @devlink: devlink. +- * @policers: Packet trap policers. +- * @policers_count: Count of provided packet trap policers. +- */ +-void +-devl_trap_policers_unregister(struct devlink *devlink, +- const struct devlink_trap_policer *policers, +- size_t policers_count) +-{ +- int i; +- +- devl_assert_locked(devlink); +- for (i = policers_count - 1; i >= 0; i--) +- devlink_trap_policer_unregister(devlink, &policers[i]); +-} +-EXPORT_SYMBOL_GPL(devl_trap_policers_unregister); +- +-static void __devlink_compat_running_version(struct devlink *devlink, +- char *buf, size_t len) +-{ +- struct devlink_info_req req = {}; +- const struct nlattr *nlattr; +- struct sk_buff *msg; +- int rem, err; +- +- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); +- if (!msg) +- return; +- +- req.msg = msg; +- err = devlink->ops->info_get(devlink, &req, NULL); +- if (err) +- goto free_msg; +- +- nla_for_each_attr(nlattr, (void *)msg->data, msg->len, rem) { +- const struct nlattr *kv; +- int rem_kv; +- +- if (nla_type(nlattr) != DEVLINK_ATTR_INFO_VERSION_RUNNING) +- continue; +- +- nla_for_each_nested(kv, nlattr, rem_kv) { +- if (nla_type(kv) != DEVLINK_ATTR_INFO_VERSION_VALUE) +- continue; +- +- strlcat(buf, nla_data(kv), len); +- strlcat(buf, " ", len); +- } +- } +-free_msg: +- nlmsg_free(msg); +-} +- +-static struct devlink_port *netdev_to_devlink_port(struct net_device *dev) +-{ +- if (!dev->netdev_ops->ndo_get_devlink_port) +- return NULL; +- +- return dev->netdev_ops->ndo_get_devlink_port(dev); +-} +- +-void devlink_compat_running_version(struct devlink *devlink, +- char *buf, size_t len) +-{ +- if (!devlink->ops->info_get) +- return; +- +- devl_lock(devlink); +- __devlink_compat_running_version(devlink, buf, len); +- devl_unlock(devlink); +-} +- +-int devlink_compat_flash_update(struct devlink *devlink, const char *file_name) +-{ +- struct devlink_flash_update_params params = {}; +- int ret; +- +- if (!devlink->ops->flash_update) +- return -EOPNOTSUPP; +- +- ret = request_firmware(¶ms.fw, file_name, devlink->dev); +- if (ret) +- return ret; +- +- devl_lock(devlink); +- devlink_flash_update_begin_notify(devlink); +- ret = devlink->ops->flash_update(devlink, ¶ms, NULL); +- devlink_flash_update_end_notify(devlink); +- devl_unlock(devlink); +- +- release_firmware(params.fw); +- +- return ret; +-} +- +-int devlink_compat_phys_port_name_get(struct net_device *dev, +- char *name, size_t len) +-{ +- struct devlink_port *devlink_port; +- +- /* RTNL mutex is held here which ensures that devlink_port +- * instance cannot disappear in the middle. No need to take +- * any devlink lock as only permanent values are accessed. +- */ +- ASSERT_RTNL(); +- +- devlink_port = netdev_to_devlink_port(dev); +- if (!devlink_port) +- return -EOPNOTSUPP; +- +- return __devlink_port_phys_port_name_get(devlink_port, name, len); +-} +- +-int devlink_compat_switch_id_get(struct net_device *dev, +- struct netdev_phys_item_id *ppid) +-{ +- struct devlink_port *devlink_port; +- +- /* Caller must hold RTNL mutex or reference to dev, which ensures that +- * devlink_port instance cannot disappear in the middle. No need to take +- * any devlink lock as only permanent values are accessed. +- */ +- devlink_port = netdev_to_devlink_port(dev); +- if (!devlink_port || !devlink_port->switch_port) +- return -EOPNOTSUPP; +- +- memcpy(ppid, &devlink_port->attrs.switch_id, sizeof(*ppid)); +- +- return 0; +-} +- +-static void __net_exit devlink_pernet_pre_exit(struct net *net) +-{ +- struct devlink *devlink; +- u32 actions_performed; +- unsigned long index; +- int err; +- +- /* In case network namespace is getting destroyed, reload +- * all devlink instances from this namespace into init_net. +- */ +- devlinks_xa_for_each_registered_get(net, index, devlink) { +- WARN_ON(!(devlink->features & DEVLINK_F_RELOAD)); +- mutex_lock(&devlink->lock); +- err = devlink_reload(devlink, &init_net, +- DEVLINK_RELOAD_ACTION_DRIVER_REINIT, +- DEVLINK_RELOAD_LIMIT_UNSPEC, +- &actions_performed, NULL); +- mutex_unlock(&devlink->lock); +- if (err && err != -EOPNOTSUPP) +- pr_warn("Failed to reload devlink instance into init_net\n"); +- devlink_put(devlink); +- } +-} +- +-static struct pernet_operations devlink_pernet_ops __net_initdata = { +- .pre_exit = devlink_pernet_pre_exit, +-}; +- +-static int __init devlink_init(void) +-{ +- int err; +- +- err = genl_register_family(&devlink_nl_family); +- if (err) +- goto out; +- err = register_pernet_subsys(&devlink_pernet_ops); +- +-out: +- WARN_ON(err); +- return err; +-} +- +-subsys_initcall(devlink_init); +diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c +index 2758b3f7c0214..9d4507aa736b7 100644 +--- a/net/core/rtnetlink.c ++++ b/net/core/rtnetlink.c +@@ -2220,13 +2220,27 @@ out_err: + return err; + } + +-int rtnl_nla_parse_ifla(struct nlattr **tb, const struct nlattr *head, int len, +- struct netlink_ext_ack *exterr) ++int rtnl_nla_parse_ifinfomsg(struct nlattr **tb, const struct nlattr *nla_peer, ++ struct netlink_ext_ack *exterr) + { +- return nla_parse_deprecated(tb, IFLA_MAX, head, len, ifla_policy, ++ const struct ifinfomsg *ifmp; ++ const struct nlattr *attrs; ++ size_t len; ++ ++ ifmp = nla_data(nla_peer); ++ attrs = nla_data(nla_peer) + sizeof(struct ifinfomsg); ++ len = nla_len(nla_peer) - sizeof(struct ifinfomsg); ++ ++ if (ifmp->ifi_index < 0) { ++ NL_SET_ERR_MSG_ATTR(exterr, nla_peer, ++ "ifindex can't be negative"); ++ return -EINVAL; ++ } ++ ++ return nla_parse_deprecated(tb, IFLA_MAX, attrs, len, ifla_policy, + exterr); + } +-EXPORT_SYMBOL(rtnl_nla_parse_ifla); ++EXPORT_SYMBOL(rtnl_nla_parse_ifinfomsg); + + struct net *rtnl_link_get_net(struct net *src_net, struct nlattr *tb[]) + { +@@ -3451,6 +3465,9 @@ replay: + if (ifm->ifi_index > 0) { + link_specified = true; + dev = __dev_get_by_index(net, ifm->ifi_index); ++ } else if (ifm->ifi_index < 0) { ++ NL_SET_ERR_MSG(extack, "ifindex can't be negative"); ++ return -EINVAL; + } else if (tb[IFLA_IFNAME] || tb[IFLA_ALT_IFNAME]) { + link_specified = true; + dev = rtnl_dev_get(net, tb); +diff --git a/net/dccp/ipv4.c b/net/dccp/ipv4.c +index b780827f5e0a5..bfececa9e244e 100644 +--- a/net/dccp/ipv4.c ++++ b/net/dccp/ipv4.c +@@ -130,7 +130,7 @@ int dccp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) + inet->inet_daddr, + inet->inet_sport, + inet->inet_dport); +- inet->inet_id = get_random_u16(); ++ atomic_set(&inet->inet_id, get_random_u16()); + + err = dccp_connect(sk); + rt = NULL; +@@ -430,7 +430,7 @@ struct sock *dccp_v4_request_recv_sock(const struct sock *sk, + RCU_INIT_POINTER(newinet->inet_opt, rcu_dereference(ireq->ireq_opt)); + newinet->mc_index = inet_iif(skb); + newinet->mc_ttl = ip_hdr(skb)->ttl; +- newinet->inet_id = get_random_u16(); ++ atomic_set(&newinet->inet_id, get_random_u16()); + + if (dst == NULL && (dst = inet_csk_route_child_sock(sk, newsk, req)) == NULL) + goto put_and_exit; +diff --git a/net/dccp/proto.c b/net/dccp/proto.c +index abc02d25edc14..c522c76a9f89f 100644 +--- a/net/dccp/proto.c ++++ b/net/dccp/proto.c +@@ -312,11 +312,15 @@ EXPORT_SYMBOL_GPL(dccp_disconnect); + __poll_t dccp_poll(struct file *file, struct socket *sock, + poll_table *wait) + { +- __poll_t mask; + struct sock *sk = sock->sk; ++ __poll_t mask; ++ u8 shutdown; ++ int state; + + sock_poll_wait(file, sock, wait); +- if (sk->sk_state == DCCP_LISTEN) ++ ++ state = inet_sk_state_load(sk); ++ if (state == DCCP_LISTEN) + return inet_csk_listen_poll(sk); + + /* Socket is not locked. We are protected from async events +@@ -325,20 +329,21 @@ __poll_t dccp_poll(struct file *file, struct socket *sock, + */ + + mask = 0; +- if (sk->sk_err) ++ if (READ_ONCE(sk->sk_err)) + mask = EPOLLERR; ++ shutdown = READ_ONCE(sk->sk_shutdown); + +- if (sk->sk_shutdown == SHUTDOWN_MASK || sk->sk_state == DCCP_CLOSED) ++ if (shutdown == SHUTDOWN_MASK || state == DCCP_CLOSED) + mask |= EPOLLHUP; +- if (sk->sk_shutdown & RCV_SHUTDOWN) ++ if (shutdown & RCV_SHUTDOWN) + mask |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP; + + /* Connected? */ +- if ((1 << sk->sk_state) & ~(DCCPF_REQUESTING | DCCPF_RESPOND)) { ++ if ((1 << state) & ~(DCCPF_REQUESTING | DCCPF_RESPOND)) { + if (atomic_read(&sk->sk_rmem_alloc) > 0) + mask |= EPOLLIN | EPOLLRDNORM; + +- if (!(sk->sk_shutdown & SEND_SHUTDOWN)) { ++ if (!(shutdown & SEND_SHUTDOWN)) { + if (sk_stream_is_writeable(sk)) { + mask |= EPOLLOUT | EPOLLWRNORM; + } else { /* send SIGIO later */ +@@ -356,7 +361,6 @@ __poll_t dccp_poll(struct file *file, struct socket *sock, + } + return mask; + } +- + EXPORT_SYMBOL_GPL(dccp_poll); + + int dccp_ioctl(struct sock *sk, int cmd, unsigned long arg) +diff --git a/net/devlink/Makefile b/net/devlink/Makefile +new file mode 100644 +index 0000000000000..3a60959f71eea +--- /dev/null ++++ b/net/devlink/Makefile +@@ -0,0 +1,3 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ ++obj-y := leftover.o +diff --git a/net/devlink/leftover.c b/net/devlink/leftover.c +new file mode 100644 +index 0000000000000..63188d6a50fe9 +--- /dev/null ++++ b/net/devlink/leftover.c +@@ -0,0 +1,12550 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * net/core/devlink.c - Network physical/parent device Netlink interface ++ * ++ * Heavily inspired by net/wireless/ ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com> ++ */ ++ ++#include <linux/etherdevice.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/types.h> ++#include <linux/slab.h> ++#include <linux/gfp.h> ++#include <linux/device.h> ++#include <linux/list.h> ++#include <linux/netdevice.h> ++#include <linux/spinlock.h> ++#include <linux/refcount.h> ++#include <linux/workqueue.h> ++#include <linux/u64_stats_sync.h> ++#include <linux/timekeeping.h> ++#include <rdma/ib_verbs.h> ++#include <net/netlink.h> ++#include <net/genetlink.h> ++#include <net/rtnetlink.h> ++#include <net/net_namespace.h> ++#include <net/sock.h> ++#include <net/devlink.h> ++#define CREATE_TRACE_POINTS ++#include <trace/events/devlink.h> ++ ++#define DEVLINK_RELOAD_STATS_ARRAY_SIZE \ ++ (__DEVLINK_RELOAD_LIMIT_MAX * __DEVLINK_RELOAD_ACTION_MAX) ++ ++struct devlink_dev_stats { ++ u32 reload_stats[DEVLINK_RELOAD_STATS_ARRAY_SIZE]; ++ u32 remote_reload_stats[DEVLINK_RELOAD_STATS_ARRAY_SIZE]; ++}; ++ ++struct devlink { ++ u32 index; ++ struct list_head port_list; ++ struct list_head rate_list; ++ struct list_head sb_list; ++ struct list_head dpipe_table_list; ++ struct list_head resource_list; ++ struct list_head param_list; ++ struct list_head region_list; ++ struct list_head reporter_list; ++ struct mutex reporters_lock; /* protects reporter_list */ ++ struct devlink_dpipe_headers *dpipe_headers; ++ struct list_head trap_list; ++ struct list_head trap_group_list; ++ struct list_head trap_policer_list; ++ struct list_head linecard_list; ++ struct mutex linecards_lock; /* protects linecard_list */ ++ const struct devlink_ops *ops; ++ u64 features; ++ struct xarray snapshot_ids; ++ struct devlink_dev_stats stats; ++ struct device *dev; ++ possible_net_t _net; ++ /* Serializes access to devlink instance specific objects such as ++ * port, sb, dpipe, resource, params, region, traps and more. ++ */ ++ struct mutex lock; ++ struct lock_class_key lock_key; ++ u8 reload_failed:1; ++ refcount_t refcount; ++ struct completion comp; ++ struct rcu_head rcu; ++ char priv[] __aligned(NETDEV_ALIGN); ++}; ++ ++struct devlink_linecard_ops; ++struct devlink_linecard_type; ++ ++struct devlink_linecard { ++ struct list_head list; ++ struct devlink *devlink; ++ unsigned int index; ++ refcount_t refcount; ++ const struct devlink_linecard_ops *ops; ++ void *priv; ++ enum devlink_linecard_state state; ++ struct mutex state_lock; /* Protects state */ ++ const char *type; ++ struct devlink_linecard_type *types; ++ unsigned int types_count; ++ struct devlink *nested_devlink; ++}; ++ ++/** ++ * struct devlink_resource - devlink resource ++ * @name: name of the resource ++ * @id: id, per devlink instance ++ * @size: size of the resource ++ * @size_new: updated size of the resource, reload is needed ++ * @size_valid: valid in case the total size of the resource is valid ++ * including its children ++ * @parent: parent resource ++ * @size_params: size parameters ++ * @list: parent list ++ * @resource_list: list of child resources ++ * @occ_get: occupancy getter callback ++ * @occ_get_priv: occupancy getter callback priv ++ */ ++struct devlink_resource { ++ const char *name; ++ u64 id; ++ u64 size; ++ u64 size_new; ++ bool size_valid; ++ struct devlink_resource *parent; ++ struct devlink_resource_size_params size_params; ++ struct list_head list; ++ struct list_head resource_list; ++ devlink_resource_occ_get_t *occ_get; ++ void *occ_get_priv; ++}; ++ ++void *devlink_priv(struct devlink *devlink) ++{ ++ return &devlink->priv; ++} ++EXPORT_SYMBOL_GPL(devlink_priv); ++ ++struct devlink *priv_to_devlink(void *priv) ++{ ++ return container_of(priv, struct devlink, priv); ++} ++EXPORT_SYMBOL_GPL(priv_to_devlink); ++ ++struct device *devlink_to_dev(const struct devlink *devlink) ++{ ++ return devlink->dev; ++} ++EXPORT_SYMBOL_GPL(devlink_to_dev); ++ ++static struct devlink_dpipe_field devlink_dpipe_fields_ethernet[] = { ++ { ++ .name = "destination mac", ++ .id = DEVLINK_DPIPE_FIELD_ETHERNET_DST_MAC, ++ .bitwidth = 48, ++ }, ++}; ++ ++struct devlink_dpipe_header devlink_dpipe_header_ethernet = { ++ .name = "ethernet", ++ .id = DEVLINK_DPIPE_HEADER_ETHERNET, ++ .fields = devlink_dpipe_fields_ethernet, ++ .fields_count = ARRAY_SIZE(devlink_dpipe_fields_ethernet), ++ .global = true, ++}; ++EXPORT_SYMBOL_GPL(devlink_dpipe_header_ethernet); ++ ++static struct devlink_dpipe_field devlink_dpipe_fields_ipv4[] = { ++ { ++ .name = "destination ip", ++ .id = DEVLINK_DPIPE_FIELD_IPV4_DST_IP, ++ .bitwidth = 32, ++ }, ++}; ++ ++struct devlink_dpipe_header devlink_dpipe_header_ipv4 = { ++ .name = "ipv4", ++ .id = DEVLINK_DPIPE_HEADER_IPV4, ++ .fields = devlink_dpipe_fields_ipv4, ++ .fields_count = ARRAY_SIZE(devlink_dpipe_fields_ipv4), ++ .global = true, ++}; ++EXPORT_SYMBOL_GPL(devlink_dpipe_header_ipv4); ++ ++static struct devlink_dpipe_field devlink_dpipe_fields_ipv6[] = { ++ { ++ .name = "destination ip", ++ .id = DEVLINK_DPIPE_FIELD_IPV6_DST_IP, ++ .bitwidth = 128, ++ }, ++}; ++ ++struct devlink_dpipe_header devlink_dpipe_header_ipv6 = { ++ .name = "ipv6", ++ .id = DEVLINK_DPIPE_HEADER_IPV6, ++ .fields = devlink_dpipe_fields_ipv6, ++ .fields_count = ARRAY_SIZE(devlink_dpipe_fields_ipv6), ++ .global = true, ++}; ++EXPORT_SYMBOL_GPL(devlink_dpipe_header_ipv6); ++ ++EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_hwmsg); ++EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_hwerr); ++EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_trap_report); ++ ++static const struct nla_policy devlink_function_nl_policy[DEVLINK_PORT_FUNCTION_ATTR_MAX + 1] = { ++ [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR] = { .type = NLA_BINARY }, ++ [DEVLINK_PORT_FN_ATTR_STATE] = ++ NLA_POLICY_RANGE(NLA_U8, DEVLINK_PORT_FN_STATE_INACTIVE, ++ DEVLINK_PORT_FN_STATE_ACTIVE), ++}; ++ ++static const struct nla_policy devlink_selftest_nl_policy[DEVLINK_ATTR_SELFTEST_ID_MAX + 1] = { ++ [DEVLINK_ATTR_SELFTEST_ID_FLASH] = { .type = NLA_FLAG }, ++}; ++ ++static DEFINE_XARRAY_FLAGS(devlinks, XA_FLAGS_ALLOC); ++#define DEVLINK_REGISTERED XA_MARK_1 ++#define DEVLINK_UNREGISTERING XA_MARK_2 ++ ++/* devlink instances are open to the access from the user space after ++ * devlink_register() call. Such logical barrier allows us to have certain ++ * expectations related to locking. ++ * ++ * Before *_register() - we are in initialization stage and no parallel ++ * access possible to the devlink instance. All drivers perform that phase ++ * by implicitly holding device_lock. ++ * ++ * After *_register() - users and driver can access devlink instance at ++ * the same time. ++ */ ++#define ASSERT_DEVLINK_REGISTERED(d) \ ++ WARN_ON_ONCE(!xa_get_mark(&devlinks, (d)->index, DEVLINK_REGISTERED)) ++#define ASSERT_DEVLINK_NOT_REGISTERED(d) \ ++ WARN_ON_ONCE(xa_get_mark(&devlinks, (d)->index, DEVLINK_REGISTERED)) ++ ++struct net *devlink_net(const struct devlink *devlink) ++{ ++ return read_pnet(&devlink->_net); ++} ++EXPORT_SYMBOL_GPL(devlink_net); ++ ++static void __devlink_put_rcu(struct rcu_head *head) ++{ ++ struct devlink *devlink = container_of(head, struct devlink, rcu); ++ ++ complete(&devlink->comp); ++} ++ ++void devlink_put(struct devlink *devlink) ++{ ++ if (refcount_dec_and_test(&devlink->refcount)) ++ /* Make sure unregister operation that may await the completion ++ * is unblocked only after all users are after the end of ++ * RCU grace period. ++ */ ++ call_rcu(&devlink->rcu, __devlink_put_rcu); ++} ++ ++struct devlink *__must_check devlink_try_get(struct devlink *devlink) ++{ ++ if (refcount_inc_not_zero(&devlink->refcount)) ++ return devlink; ++ return NULL; ++} ++ ++void devl_assert_locked(struct devlink *devlink) ++{ ++ lockdep_assert_held(&devlink->lock); ++} ++EXPORT_SYMBOL_GPL(devl_assert_locked); ++ ++#ifdef CONFIG_LOCKDEP ++/* For use in conjunction with LOCKDEP only e.g. rcu_dereference_protected() */ ++bool devl_lock_is_held(struct devlink *devlink) ++{ ++ return lockdep_is_held(&devlink->lock); ++} ++EXPORT_SYMBOL_GPL(devl_lock_is_held); ++#endif ++ ++void devl_lock(struct devlink *devlink) ++{ ++ mutex_lock(&devlink->lock); ++} ++EXPORT_SYMBOL_GPL(devl_lock); ++ ++int devl_trylock(struct devlink *devlink) ++{ ++ return mutex_trylock(&devlink->lock); ++} ++EXPORT_SYMBOL_GPL(devl_trylock); ++ ++void devl_unlock(struct devlink *devlink) ++{ ++ mutex_unlock(&devlink->lock); ++} ++EXPORT_SYMBOL_GPL(devl_unlock); ++ ++static struct devlink * ++devlinks_xa_find_get(struct net *net, unsigned long *indexp, xa_mark_t filter, ++ void * (*xa_find_fn)(struct xarray *, unsigned long *, ++ unsigned long, xa_mark_t)) ++{ ++ struct devlink *devlink; ++ ++ rcu_read_lock(); ++retry: ++ devlink = xa_find_fn(&devlinks, indexp, ULONG_MAX, DEVLINK_REGISTERED); ++ if (!devlink) ++ goto unlock; ++ ++ /* In case devlink_unregister() was already called and "unregistering" ++ * mark was set, do not allow to get a devlink reference here. ++ * This prevents live-lock of devlink_unregister() wait for completion. ++ */ ++ if (xa_get_mark(&devlinks, *indexp, DEVLINK_UNREGISTERING)) ++ goto retry; ++ ++ /* For a possible retry, the xa_find_after() should be always used */ ++ xa_find_fn = xa_find_after; ++ if (!devlink_try_get(devlink)) ++ goto retry; ++ if (!net_eq(devlink_net(devlink), net)) { ++ devlink_put(devlink); ++ goto retry; ++ } ++unlock: ++ rcu_read_unlock(); ++ return devlink; ++} ++ ++static struct devlink *devlinks_xa_find_get_first(struct net *net, ++ unsigned long *indexp, ++ xa_mark_t filter) ++{ ++ return devlinks_xa_find_get(net, indexp, filter, xa_find); ++} ++ ++static struct devlink *devlinks_xa_find_get_next(struct net *net, ++ unsigned long *indexp, ++ xa_mark_t filter) ++{ ++ return devlinks_xa_find_get(net, indexp, filter, xa_find_after); ++} ++ ++/* Iterate over devlink pointers which were possible to get reference to. ++ * devlink_put() needs to be called for each iterated devlink pointer ++ * in loop body in order to release the reference. ++ */ ++#define devlinks_xa_for_each_get(net, index, devlink, filter) \ ++ for (index = 0, \ ++ devlink = devlinks_xa_find_get_first(net, &index, filter); \ ++ devlink; devlink = devlinks_xa_find_get_next(net, &index, filter)) ++ ++#define devlinks_xa_for_each_registered_get(net, index, devlink) \ ++ devlinks_xa_for_each_get(net, index, devlink, DEVLINK_REGISTERED) ++ ++static struct devlink *devlink_get_from_attrs(struct net *net, ++ struct nlattr **attrs) ++{ ++ struct devlink *devlink; ++ unsigned long index; ++ char *busname; ++ char *devname; ++ ++ if (!attrs[DEVLINK_ATTR_BUS_NAME] || !attrs[DEVLINK_ATTR_DEV_NAME]) ++ return ERR_PTR(-EINVAL); ++ ++ busname = nla_data(attrs[DEVLINK_ATTR_BUS_NAME]); ++ devname = nla_data(attrs[DEVLINK_ATTR_DEV_NAME]); ++ ++ devlinks_xa_for_each_registered_get(net, index, devlink) { ++ if (strcmp(devlink->dev->bus->name, busname) == 0 && ++ strcmp(dev_name(devlink->dev), devname) == 0) ++ return devlink; ++ devlink_put(devlink); ++ } ++ ++ return ERR_PTR(-ENODEV); ++} ++ ++#define ASSERT_DEVLINK_PORT_REGISTERED(devlink_port) \ ++ WARN_ON_ONCE(!(devlink_port)->registered) ++#define ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port) \ ++ WARN_ON_ONCE((devlink_port)->registered) ++#define ASSERT_DEVLINK_PORT_INITIALIZED(devlink_port) \ ++ WARN_ON_ONCE(!(devlink_port)->initialized) ++ ++static struct devlink_port *devlink_port_get_by_index(struct devlink *devlink, ++ unsigned int port_index) ++{ ++ struct devlink_port *devlink_port; ++ ++ list_for_each_entry(devlink_port, &devlink->port_list, list) { ++ if (devlink_port->index == port_index) ++ return devlink_port; ++ } ++ return NULL; ++} ++ ++static bool devlink_port_index_exists(struct devlink *devlink, ++ unsigned int port_index) ++{ ++ return devlink_port_get_by_index(devlink, port_index); ++} ++ ++static struct devlink_port *devlink_port_get_from_attrs(struct devlink *devlink, ++ struct nlattr **attrs) ++{ ++ if (attrs[DEVLINK_ATTR_PORT_INDEX]) { ++ u32 port_index = nla_get_u32(attrs[DEVLINK_ATTR_PORT_INDEX]); ++ struct devlink_port *devlink_port; ++ ++ devlink_port = devlink_port_get_by_index(devlink, port_index); ++ if (!devlink_port) ++ return ERR_PTR(-ENODEV); ++ return devlink_port; ++ } ++ return ERR_PTR(-EINVAL); ++} ++ ++static struct devlink_port *devlink_port_get_from_info(struct devlink *devlink, ++ struct genl_info *info) ++{ ++ return devlink_port_get_from_attrs(devlink, info->attrs); ++} ++ ++static inline bool ++devlink_rate_is_leaf(struct devlink_rate *devlink_rate) ++{ ++ return devlink_rate->type == DEVLINK_RATE_TYPE_LEAF; ++} ++ ++static inline bool ++devlink_rate_is_node(struct devlink_rate *devlink_rate) ++{ ++ return devlink_rate->type == DEVLINK_RATE_TYPE_NODE; ++} ++ ++static struct devlink_rate * ++devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info) ++{ ++ struct devlink_rate *devlink_rate; ++ struct devlink_port *devlink_port; ++ ++ devlink_port = devlink_port_get_from_attrs(devlink, info->attrs); ++ if (IS_ERR(devlink_port)) ++ return ERR_CAST(devlink_port); ++ devlink_rate = devlink_port->devlink_rate; ++ return devlink_rate ?: ERR_PTR(-ENODEV); ++} ++ ++static struct devlink_rate * ++devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) ++{ ++ static struct devlink_rate *devlink_rate; ++ ++ list_for_each_entry(devlink_rate, &devlink->rate_list, list) { ++ if (devlink_rate_is_node(devlink_rate) && ++ !strcmp(node_name, devlink_rate->name)) ++ return devlink_rate; ++ } ++ return ERR_PTR(-ENODEV); ++} ++ ++static struct devlink_rate * ++devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) ++{ ++ const char *rate_node_name; ++ size_t len; ++ ++ if (!attrs[DEVLINK_ATTR_RATE_NODE_NAME]) ++ return ERR_PTR(-EINVAL); ++ rate_node_name = nla_data(attrs[DEVLINK_ATTR_RATE_NODE_NAME]); ++ len = strlen(rate_node_name); ++ /* Name cannot be empty or decimal number */ ++ if (!len || strspn(rate_node_name, "0123456789") == len) ++ return ERR_PTR(-EINVAL); ++ ++ return devlink_rate_node_get_by_name(devlink, rate_node_name); ++} ++ ++static struct devlink_rate * ++devlink_rate_node_get_from_info(struct devlink *devlink, struct genl_info *info) ++{ ++ return devlink_rate_node_get_from_attrs(devlink, info->attrs); ++} ++ ++static struct devlink_rate * ++devlink_rate_get_from_info(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); ++ else ++ return ERR_PTR(-EINVAL); ++} ++ ++static struct devlink_linecard * ++devlink_linecard_get_by_index(struct devlink *devlink, ++ unsigned int linecard_index) ++{ ++ struct devlink_linecard *devlink_linecard; ++ ++ list_for_each_entry(devlink_linecard, &devlink->linecard_list, list) { ++ if (devlink_linecard->index == linecard_index) ++ return devlink_linecard; ++ } ++ return NULL; ++} ++ ++static bool devlink_linecard_index_exists(struct devlink *devlink, ++ unsigned int linecard_index) ++{ ++ return devlink_linecard_get_by_index(devlink, linecard_index); ++} ++ ++static struct devlink_linecard * ++devlink_linecard_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) ++{ ++ if (attrs[DEVLINK_ATTR_LINECARD_INDEX]) { ++ u32 linecard_index = nla_get_u32(attrs[DEVLINK_ATTR_LINECARD_INDEX]); ++ struct devlink_linecard *linecard; ++ ++ mutex_lock(&devlink->linecards_lock); ++ linecard = devlink_linecard_get_by_index(devlink, linecard_index); ++ if (linecard) ++ refcount_inc(&linecard->refcount); ++ mutex_unlock(&devlink->linecards_lock); ++ if (!linecard) ++ return ERR_PTR(-ENODEV); ++ return linecard; ++ } ++ return ERR_PTR(-EINVAL); ++} ++ ++static struct devlink_linecard * ++devlink_linecard_get_from_info(struct devlink *devlink, struct genl_info *info) ++{ ++ return devlink_linecard_get_from_attrs(devlink, info->attrs); ++} ++ ++static void devlink_linecard_put(struct devlink_linecard *linecard) ++{ ++ if (refcount_dec_and_test(&linecard->refcount)) { ++ mutex_destroy(&linecard->state_lock); ++ kfree(linecard); ++ } ++} ++ ++struct devlink_sb { ++ struct list_head list; ++ unsigned int index; ++ u32 size; ++ u16 ingress_pools_count; ++ u16 egress_pools_count; ++ u16 ingress_tc_count; ++ u16 egress_tc_count; ++}; ++ ++static u16 devlink_sb_pool_count(struct devlink_sb *devlink_sb) ++{ ++ return devlink_sb->ingress_pools_count + devlink_sb->egress_pools_count; ++} ++ ++static struct devlink_sb *devlink_sb_get_by_index(struct devlink *devlink, ++ unsigned int sb_index) ++{ ++ struct devlink_sb *devlink_sb; ++ ++ list_for_each_entry(devlink_sb, &devlink->sb_list, list) { ++ if (devlink_sb->index == sb_index) ++ return devlink_sb; ++ } ++ return NULL; ++} ++ ++static bool devlink_sb_index_exists(struct devlink *devlink, ++ unsigned int sb_index) ++{ ++ return devlink_sb_get_by_index(devlink, sb_index); ++} ++ ++static struct devlink_sb *devlink_sb_get_from_attrs(struct devlink *devlink, ++ struct nlattr **attrs) ++{ ++ if (attrs[DEVLINK_ATTR_SB_INDEX]) { ++ u32 sb_index = nla_get_u32(attrs[DEVLINK_ATTR_SB_INDEX]); ++ struct devlink_sb *devlink_sb; ++ ++ devlink_sb = devlink_sb_get_by_index(devlink, sb_index); ++ if (!devlink_sb) ++ return ERR_PTR(-ENODEV); ++ return devlink_sb; ++ } ++ return ERR_PTR(-EINVAL); ++} ++ ++static struct devlink_sb *devlink_sb_get_from_info(struct devlink *devlink, ++ struct genl_info *info) ++{ ++ return devlink_sb_get_from_attrs(devlink, info->attrs); ++} ++ ++static int devlink_sb_pool_index_get_from_attrs(struct devlink_sb *devlink_sb, ++ struct nlattr **attrs, ++ u16 *p_pool_index) ++{ ++ u16 val; ++ ++ if (!attrs[DEVLINK_ATTR_SB_POOL_INDEX]) ++ return -EINVAL; ++ ++ val = nla_get_u16(attrs[DEVLINK_ATTR_SB_POOL_INDEX]); ++ if (val >= devlink_sb_pool_count(devlink_sb)) ++ return -EINVAL; ++ *p_pool_index = val; ++ return 0; ++} ++ ++static int devlink_sb_pool_index_get_from_info(struct devlink_sb *devlink_sb, ++ struct genl_info *info, ++ u16 *p_pool_index) ++{ ++ return devlink_sb_pool_index_get_from_attrs(devlink_sb, info->attrs, ++ p_pool_index); ++} ++ ++static int ++devlink_sb_pool_type_get_from_attrs(struct nlattr **attrs, ++ enum devlink_sb_pool_type *p_pool_type) ++{ ++ u8 val; ++ ++ if (!attrs[DEVLINK_ATTR_SB_POOL_TYPE]) ++ return -EINVAL; ++ ++ val = nla_get_u8(attrs[DEVLINK_ATTR_SB_POOL_TYPE]); ++ if (val != DEVLINK_SB_POOL_TYPE_INGRESS && ++ val != DEVLINK_SB_POOL_TYPE_EGRESS) ++ return -EINVAL; ++ *p_pool_type = val; ++ return 0; ++} ++ ++static int ++devlink_sb_pool_type_get_from_info(struct genl_info *info, ++ enum devlink_sb_pool_type *p_pool_type) ++{ ++ return devlink_sb_pool_type_get_from_attrs(info->attrs, p_pool_type); ++} ++ ++static int ++devlink_sb_th_type_get_from_attrs(struct nlattr **attrs, ++ enum devlink_sb_threshold_type *p_th_type) ++{ ++ u8 val; ++ ++ if (!attrs[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE]) ++ return -EINVAL; ++ ++ val = nla_get_u8(attrs[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE]); ++ if (val != DEVLINK_SB_THRESHOLD_TYPE_STATIC && ++ val != DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC) ++ return -EINVAL; ++ *p_th_type = val; ++ return 0; ++} ++ ++static int ++devlink_sb_th_type_get_from_info(struct genl_info *info, ++ enum devlink_sb_threshold_type *p_th_type) ++{ ++ return devlink_sb_th_type_get_from_attrs(info->attrs, p_th_type); ++} ++ ++static int ++devlink_sb_tc_index_get_from_attrs(struct devlink_sb *devlink_sb, ++ struct nlattr **attrs, ++ enum devlink_sb_pool_type pool_type, ++ u16 *p_tc_index) ++{ ++ u16 val; ++ ++ if (!attrs[DEVLINK_ATTR_SB_TC_INDEX]) ++ return -EINVAL; ++ ++ val = nla_get_u16(attrs[DEVLINK_ATTR_SB_TC_INDEX]); ++ if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS && ++ val >= devlink_sb->ingress_tc_count) ++ return -EINVAL; ++ if (pool_type == DEVLINK_SB_POOL_TYPE_EGRESS && ++ val >= devlink_sb->egress_tc_count) ++ return -EINVAL; ++ *p_tc_index = val; ++ return 0; ++} ++ ++static int ++devlink_sb_tc_index_get_from_info(struct devlink_sb *devlink_sb, ++ struct genl_info *info, ++ enum devlink_sb_pool_type pool_type, ++ u16 *p_tc_index) ++{ ++ return devlink_sb_tc_index_get_from_attrs(devlink_sb, info->attrs, ++ pool_type, p_tc_index); ++} ++ ++struct devlink_region { ++ struct devlink *devlink; ++ struct devlink_port *port; ++ struct list_head list; ++ union { ++ const struct devlink_region_ops *ops; ++ const struct devlink_port_region_ops *port_ops; ++ }; ++ struct mutex snapshot_lock; /* protects snapshot_list, ++ * max_snapshots and cur_snapshots ++ * consistency. ++ */ ++ struct list_head snapshot_list; ++ u32 max_snapshots; ++ u32 cur_snapshots; ++ u64 size; ++}; ++ ++struct devlink_snapshot { ++ struct list_head list; ++ struct devlink_region *region; ++ u8 *data; ++ u32 id; ++}; ++ ++static struct devlink_region * ++devlink_region_get_by_name(struct devlink *devlink, const char *region_name) ++{ ++ struct devlink_region *region; ++ ++ list_for_each_entry(region, &devlink->region_list, list) ++ if (!strcmp(region->ops->name, region_name)) ++ return region; ++ ++ return NULL; ++} ++ ++static struct devlink_region * ++devlink_port_region_get_by_name(struct devlink_port *port, ++ const char *region_name) ++{ ++ struct devlink_region *region; ++ ++ list_for_each_entry(region, &port->region_list, list) ++ if (!strcmp(region->ops->name, region_name)) ++ return region; ++ ++ return NULL; ++} ++ ++static struct devlink_snapshot * ++devlink_region_snapshot_get_by_id(struct devlink_region *region, u32 id) ++{ ++ struct devlink_snapshot *snapshot; ++ ++ list_for_each_entry(snapshot, ®ion->snapshot_list, list) ++ if (snapshot->id == id) ++ return snapshot; ++ ++ return NULL; ++} ++ ++#define DEVLINK_NL_FLAG_NEED_PORT BIT(0) ++#define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1) ++#define DEVLINK_NL_FLAG_NEED_RATE BIT(2) ++#define DEVLINK_NL_FLAG_NEED_RATE_NODE BIT(3) ++#define DEVLINK_NL_FLAG_NEED_LINECARD BIT(4) ++ ++static int devlink_nl_pre_doit(const struct genl_ops *ops, ++ struct sk_buff *skb, struct genl_info *info) ++{ ++ struct devlink_linecard *linecard; ++ struct devlink_port *devlink_port; ++ struct devlink *devlink; ++ int err; ++ ++ devlink = devlink_get_from_attrs(genl_info_net(info), info->attrs); ++ if (IS_ERR(devlink)) ++ return PTR_ERR(devlink); ++ devl_lock(devlink); ++ info->user_ptr[0] = devlink; ++ if (ops->internal_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; ++ } else if (ops->internal_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; ++ } else if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_RATE) { ++ struct devlink_rate *devlink_rate; ++ ++ devlink_rate = devlink_rate_get_from_info(devlink, info); ++ if (IS_ERR(devlink_rate)) { ++ err = PTR_ERR(devlink_rate); ++ goto unlock; ++ } ++ info->user_ptr[1] = devlink_rate; ++ } else if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_RATE_NODE) { ++ struct devlink_rate *rate_node; ++ ++ rate_node = devlink_rate_node_get_from_info(devlink, info); ++ if (IS_ERR(rate_node)) { ++ err = PTR_ERR(rate_node); ++ goto unlock; ++ } ++ info->user_ptr[1] = rate_node; ++ } else if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_LINECARD) { ++ linecard = devlink_linecard_get_from_info(devlink, info); ++ if (IS_ERR(linecard)) { ++ err = PTR_ERR(linecard); ++ goto unlock; ++ } ++ info->user_ptr[1] = linecard; ++ } ++ return 0; ++ ++unlock: ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ return err; ++} ++ ++static void devlink_nl_post_doit(const struct genl_ops *ops, ++ struct sk_buff *skb, struct genl_info *info) ++{ ++ struct devlink_linecard *linecard; ++ struct devlink *devlink; ++ ++ devlink = info->user_ptr[0]; ++ if (ops->internal_flags & DEVLINK_NL_FLAG_NEED_LINECARD) { ++ linecard = info->user_ptr[1]; ++ devlink_linecard_put(linecard); ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++} ++ ++static struct genl_family devlink_nl_family; ++ ++enum devlink_multicast_groups { ++ DEVLINK_MCGRP_CONFIG, ++}; ++ ++static const struct genl_multicast_group devlink_nl_mcgrps[] = { ++ [DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME }, ++}; ++ ++static int devlink_nl_put_handle(struct sk_buff *msg, struct devlink *devlink) ++{ ++ if (nla_put_string(msg, DEVLINK_ATTR_BUS_NAME, devlink->dev->bus->name)) ++ return -EMSGSIZE; ++ if (nla_put_string(msg, DEVLINK_ATTR_DEV_NAME, dev_name(devlink->dev))) ++ return -EMSGSIZE; ++ return 0; ++} ++ ++static int devlink_nl_put_nested_handle(struct sk_buff *msg, struct devlink *devlink) ++{ ++ struct nlattr *nested_attr; ++ ++ nested_attr = nla_nest_start(msg, DEVLINK_ATTR_NESTED_DEVLINK); ++ if (!nested_attr) ++ return -EMSGSIZE; ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, nested_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, nested_attr); ++ return -EMSGSIZE; ++} ++ ++struct devlink_reload_combination { ++ enum devlink_reload_action action; ++ enum devlink_reload_limit limit; ++}; ++ ++static const struct devlink_reload_combination devlink_reload_invalid_combinations[] = { ++ { ++ /* can't reinitialize driver with no down time */ ++ .action = DEVLINK_RELOAD_ACTION_DRIVER_REINIT, ++ .limit = DEVLINK_RELOAD_LIMIT_NO_RESET, ++ }, ++}; ++ ++static bool ++devlink_reload_combination_is_invalid(enum devlink_reload_action action, ++ enum devlink_reload_limit limit) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(devlink_reload_invalid_combinations); i++) ++ if (devlink_reload_invalid_combinations[i].action == action && ++ devlink_reload_invalid_combinations[i].limit == limit) ++ return true; ++ return false; ++} ++ ++static bool ++devlink_reload_action_is_supported(struct devlink *devlink, enum devlink_reload_action action) ++{ ++ return test_bit(action, &devlink->ops->reload_actions); ++} ++ ++static bool ++devlink_reload_limit_is_supported(struct devlink *devlink, enum devlink_reload_limit limit) ++{ ++ return test_bit(limit, &devlink->ops->reload_limits); ++} ++ ++static int devlink_reload_stat_put(struct sk_buff *msg, ++ enum devlink_reload_limit limit, u32 value) ++{ ++ struct nlattr *reload_stats_entry; ++ ++ reload_stats_entry = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_STATS_ENTRY); ++ if (!reload_stats_entry) ++ return -EMSGSIZE; ++ ++ if (nla_put_u8(msg, DEVLINK_ATTR_RELOAD_STATS_LIMIT, limit) || ++ nla_put_u32(msg, DEVLINK_ATTR_RELOAD_STATS_VALUE, value)) ++ goto nla_put_failure; ++ nla_nest_end(msg, reload_stats_entry); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, reload_stats_entry); ++ return -EMSGSIZE; ++} ++ ++static int devlink_reload_stats_put(struct sk_buff *msg, struct devlink *devlink, bool is_remote) ++{ ++ struct nlattr *reload_stats_attr, *act_info, *act_stats; ++ int i, j, stat_idx; ++ u32 value; ++ ++ if (!is_remote) ++ reload_stats_attr = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_STATS); ++ else ++ reload_stats_attr = nla_nest_start(msg, DEVLINK_ATTR_REMOTE_RELOAD_STATS); ++ ++ if (!reload_stats_attr) ++ return -EMSGSIZE; ++ ++ for (i = 0; i <= DEVLINK_RELOAD_ACTION_MAX; i++) { ++ if ((!is_remote && ++ !devlink_reload_action_is_supported(devlink, i)) || ++ i == DEVLINK_RELOAD_ACTION_UNSPEC) ++ continue; ++ act_info = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_ACTION_INFO); ++ if (!act_info) ++ goto nla_put_failure; ++ ++ if (nla_put_u8(msg, DEVLINK_ATTR_RELOAD_ACTION, i)) ++ goto action_info_nest_cancel; ++ act_stats = nla_nest_start(msg, DEVLINK_ATTR_RELOAD_ACTION_STATS); ++ if (!act_stats) ++ goto action_info_nest_cancel; ++ ++ for (j = 0; j <= DEVLINK_RELOAD_LIMIT_MAX; j++) { ++ /* Remote stats are shown even if not locally supported. ++ * Stats of actions with unspecified limit are shown ++ * though drivers don't need to register unspecified ++ * limit. ++ */ ++ if ((!is_remote && j != DEVLINK_RELOAD_LIMIT_UNSPEC && ++ !devlink_reload_limit_is_supported(devlink, j)) || ++ devlink_reload_combination_is_invalid(i, j)) ++ continue; ++ ++ stat_idx = j * __DEVLINK_RELOAD_ACTION_MAX + i; ++ if (!is_remote) ++ value = devlink->stats.reload_stats[stat_idx]; ++ else ++ value = devlink->stats.remote_reload_stats[stat_idx]; ++ if (devlink_reload_stat_put(msg, j, value)) ++ goto action_stats_nest_cancel; ++ } ++ nla_nest_end(msg, act_stats); ++ nla_nest_end(msg, act_info); ++ } ++ nla_nest_end(msg, reload_stats_attr); ++ return 0; ++ ++action_stats_nest_cancel: ++ nla_nest_cancel(msg, act_stats); ++action_info_nest_cancel: ++ nla_nest_cancel(msg, act_info); ++nla_put_failure: ++ nla_nest_cancel(msg, reload_stats_attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_fill(struct sk_buff *msg, struct devlink *devlink, ++ enum devlink_command cmd, u32 portid, ++ u32 seq, int flags) ++{ ++ struct nlattr *dev_stats; ++ void *hdr; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ if (nla_put_u8(msg, DEVLINK_ATTR_RELOAD_FAILED, devlink->reload_failed)) ++ goto nla_put_failure; ++ ++ dev_stats = nla_nest_start(msg, DEVLINK_ATTR_DEV_STATS); ++ if (!dev_stats) ++ goto nla_put_failure; ++ ++ if (devlink_reload_stats_put(msg, devlink, false)) ++ goto dev_stats_nest_cancel; ++ if (devlink_reload_stats_put(msg, devlink, true)) ++ goto dev_stats_nest_cancel; ++ ++ nla_nest_end(msg, dev_stats); ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++dev_stats_nest_cancel: ++ nla_nest_cancel(msg, dev_stats); ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static void devlink_notify(struct devlink *devlink, enum devlink_command cmd) ++{ ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON(cmd != DEVLINK_CMD_NEW && cmd != DEVLINK_CMD_DEL); ++ WARN_ON(!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)); ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_fill(msg, devlink, cmd, 0, 0, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), ++ msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static int devlink_nl_port_attrs_put(struct sk_buff *msg, ++ struct devlink_port *devlink_port) ++{ ++ struct devlink_port_attrs *attrs = &devlink_port->attrs; ++ ++ if (!devlink_port->attrs_set) ++ return 0; ++ if (attrs->lanes) { ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_LANES, attrs->lanes)) ++ return -EMSGSIZE; ++ } ++ if (nla_put_u8(msg, DEVLINK_ATTR_PORT_SPLITTABLE, attrs->splittable)) ++ return -EMSGSIZE; ++ if (nla_put_u16(msg, DEVLINK_ATTR_PORT_FLAVOUR, attrs->flavour)) ++ return -EMSGSIZE; ++ switch (devlink_port->attrs.flavour) { ++ case DEVLINK_PORT_FLAVOUR_PCI_PF: ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, ++ attrs->pci_pf.controller) || ++ nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_PF_NUMBER, attrs->pci_pf.pf)) ++ return -EMSGSIZE; ++ if (nla_put_u8(msg, DEVLINK_ATTR_PORT_EXTERNAL, attrs->pci_pf.external)) ++ return -EMSGSIZE; ++ break; ++ case DEVLINK_PORT_FLAVOUR_PCI_VF: ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, ++ attrs->pci_vf.controller) || ++ nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_PF_NUMBER, attrs->pci_vf.pf) || ++ nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_VF_NUMBER, attrs->pci_vf.vf)) ++ return -EMSGSIZE; ++ if (nla_put_u8(msg, DEVLINK_ATTR_PORT_EXTERNAL, attrs->pci_vf.external)) ++ return -EMSGSIZE; ++ break; ++ case DEVLINK_PORT_FLAVOUR_PCI_SF: ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_CONTROLLER_NUMBER, ++ attrs->pci_sf.controller) || ++ nla_put_u16(msg, DEVLINK_ATTR_PORT_PCI_PF_NUMBER, ++ attrs->pci_sf.pf) || ++ nla_put_u32(msg, DEVLINK_ATTR_PORT_PCI_SF_NUMBER, ++ attrs->pci_sf.sf)) ++ return -EMSGSIZE; ++ break; ++ case DEVLINK_PORT_FLAVOUR_PHYSICAL: ++ case DEVLINK_PORT_FLAVOUR_CPU: ++ case DEVLINK_PORT_FLAVOUR_DSA: ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_NUMBER, ++ attrs->phys.port_number)) ++ return -EMSGSIZE; ++ if (!attrs->split) ++ return 0; ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_SPLIT_GROUP, ++ attrs->phys.port_number)) ++ return -EMSGSIZE; ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_SPLIT_SUBPORT_NUMBER, ++ attrs->phys.split_subport_number)) ++ return -EMSGSIZE; ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static int devlink_port_fn_hw_addr_fill(const struct devlink_ops *ops, ++ struct devlink_port *port, ++ struct sk_buff *msg, ++ struct netlink_ext_ack *extack, ++ bool *msg_updated) ++{ ++ u8 hw_addr[MAX_ADDR_LEN]; ++ int hw_addr_len; ++ int err; ++ ++ if (!ops->port_function_hw_addr_get) ++ return 0; ++ ++ err = ops->port_function_hw_addr_get(port, hw_addr, &hw_addr_len, ++ extack); ++ if (err) { ++ if (err == -EOPNOTSUPP) ++ return 0; ++ return err; ++ } ++ err = nla_put(msg, DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR, hw_addr_len, hw_addr); ++ if (err) ++ return err; ++ *msg_updated = true; ++ 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, ++ int flags, struct netlink_ext_ack *extack) ++{ ++ struct devlink *devlink = devlink_rate->devlink; ++ void *hdr; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ ++ if (nla_put_u16(msg, DEVLINK_ATTR_RATE_TYPE, devlink_rate->type)) ++ goto nla_put_failure; ++ ++ if (devlink_rate_is_leaf(devlink_rate)) { ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, ++ devlink_rate->devlink_port->index)) ++ goto nla_put_failure; ++ } else if (devlink_rate_is_node(devlink_rate)) { ++ if (nla_put_string(msg, DEVLINK_ATTR_RATE_NODE_NAME, ++ devlink_rate->name)) ++ goto nla_put_failure; ++ } ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_RATE_TX_SHARE, ++ devlink_rate->tx_share, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_RATE_TX_MAX, ++ devlink_rate->tx_max, DEVLINK_ATTR_PAD)) ++ 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; ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static bool ++devlink_port_fn_state_valid(enum devlink_port_fn_state state) ++{ ++ return state == DEVLINK_PORT_FN_STATE_INACTIVE || ++ state == DEVLINK_PORT_FN_STATE_ACTIVE; ++} ++ ++static bool ++devlink_port_fn_opstate_valid(enum devlink_port_fn_opstate opstate) ++{ ++ return opstate == DEVLINK_PORT_FN_OPSTATE_DETACHED || ++ opstate == DEVLINK_PORT_FN_OPSTATE_ATTACHED; ++} ++ ++static int devlink_port_fn_state_fill(const struct devlink_ops *ops, ++ struct devlink_port *port, ++ struct sk_buff *msg, ++ struct netlink_ext_ack *extack, ++ bool *msg_updated) ++{ ++ enum devlink_port_fn_opstate opstate; ++ enum devlink_port_fn_state state; ++ int err; ++ ++ if (!ops->port_fn_state_get) ++ return 0; ++ ++ err = ops->port_fn_state_get(port, &state, &opstate, extack); ++ if (err) { ++ if (err == -EOPNOTSUPP) ++ return 0; ++ return err; ++ } ++ if (!devlink_port_fn_state_valid(state)) { ++ WARN_ON_ONCE(1); ++ NL_SET_ERR_MSG_MOD(extack, "Invalid state read from driver"); ++ return -EINVAL; ++ } ++ if (!devlink_port_fn_opstate_valid(opstate)) { ++ WARN_ON_ONCE(1); ++ NL_SET_ERR_MSG_MOD(extack, ++ "Invalid operational state read from driver"); ++ return -EINVAL; ++ } ++ if (nla_put_u8(msg, DEVLINK_PORT_FN_ATTR_STATE, state) || ++ nla_put_u8(msg, DEVLINK_PORT_FN_ATTR_OPSTATE, opstate)) ++ return -EMSGSIZE; ++ *msg_updated = true; ++ return 0; ++} ++ ++static int ++devlink_nl_port_function_attrs_put(struct sk_buff *msg, struct devlink_port *port, ++ struct netlink_ext_ack *extack) ++{ ++ const struct devlink_ops *ops; ++ struct nlattr *function_attr; ++ bool msg_updated = false; ++ int err; ++ ++ function_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_PORT_FUNCTION); ++ if (!function_attr) ++ return -EMSGSIZE; ++ ++ ops = port->devlink->ops; ++ err = devlink_port_fn_hw_addr_fill(ops, port, msg, extack, ++ &msg_updated); ++ if (err) ++ goto out; ++ err = devlink_port_fn_state_fill(ops, port, msg, extack, &msg_updated); ++out: ++ if (err || !msg_updated) ++ nla_nest_cancel(msg, function_attr); ++ else ++ nla_nest_end(msg, function_attr); ++ return err; ++} ++ ++static int devlink_nl_port_fill(struct sk_buff *msg, ++ struct devlink_port *devlink_port, ++ enum devlink_command cmd, u32 portid, u32 seq, ++ int flags, struct netlink_ext_ack *extack) ++{ ++ struct devlink *devlink = devlink_port->devlink; ++ void *hdr; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) ++ goto nla_put_failure; ++ ++ /* Hold rtnl lock while accessing port's netdev attributes. */ ++ rtnl_lock(); ++ spin_lock_bh(&devlink_port->type_lock); ++ if (nla_put_u16(msg, DEVLINK_ATTR_PORT_TYPE, devlink_port->type)) ++ goto nla_put_failure_type_locked; ++ if (devlink_port->desired_type != DEVLINK_PORT_TYPE_NOTSET && ++ nla_put_u16(msg, DEVLINK_ATTR_PORT_DESIRED_TYPE, ++ devlink_port->desired_type)) ++ goto nla_put_failure_type_locked; ++ if (devlink_port->type == DEVLINK_PORT_TYPE_ETH) { ++ struct net *net = devlink_net(devlink_port->devlink); ++ struct net_device *netdev = devlink_port->type_dev; ++ ++ if (netdev && net_eq(net, dev_net(netdev)) && ++ (nla_put_u32(msg, DEVLINK_ATTR_PORT_NETDEV_IFINDEX, ++ netdev->ifindex) || ++ nla_put_string(msg, DEVLINK_ATTR_PORT_NETDEV_NAME, ++ netdev->name))) ++ goto nla_put_failure_type_locked; ++ } ++ if (devlink_port->type == DEVLINK_PORT_TYPE_IB) { ++ struct ib_device *ibdev = devlink_port->type_dev; ++ ++ if (ibdev && ++ nla_put_string(msg, DEVLINK_ATTR_PORT_IBDEV_NAME, ++ ibdev->name)) ++ goto nla_put_failure_type_locked; ++ } ++ spin_unlock_bh(&devlink_port->type_lock); ++ rtnl_unlock(); ++ if (devlink_nl_port_attrs_put(msg, devlink_port)) ++ goto nla_put_failure; ++ if (devlink_nl_port_function_attrs_put(msg, devlink_port, extack)) ++ goto nla_put_failure; ++ if (devlink_port->linecard && ++ nla_put_u32(msg, DEVLINK_ATTR_LINECARD_INDEX, ++ devlink_port->linecard->index)) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure_type_locked: ++ spin_unlock_bh(&devlink_port->type_lock); ++ rtnl_unlock(); ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static void devlink_port_notify(struct devlink_port *devlink_port, ++ enum devlink_command cmd) ++{ ++ struct devlink *devlink = devlink_port->devlink; ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON(cmd != DEVLINK_CMD_PORT_NEW && cmd != DEVLINK_CMD_PORT_DEL); ++ ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_port_fill(msg, devlink_port, cmd, 0, 0, 0, NULL); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, ++ 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static void devlink_rate_notify(struct devlink_rate *devlink_rate, ++ enum devlink_command cmd) ++{ ++ struct devlink *devlink = devlink_rate->devlink; ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON(cmd != DEVLINK_CMD_RATE_NEW && cmd != DEVLINK_CMD_RATE_DEL); ++ ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_rate_fill(msg, devlink_rate, cmd, 0, 0, 0, NULL); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, ++ 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static int devlink_nl_cmd_rate_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink_rate *devlink_rate; ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(devlink_rate, &devlink->rate_list, list) { ++ enum devlink_command cmd = DEVLINK_CMD_RATE_NEW; ++ u32 id = NETLINK_CB(cb->skb).portid; ++ ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_rate_fill(msg, devlink_rate, cmd, id, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI, NULL); ++ if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ if (err != -EMSGSIZE) ++ return err; ++ ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_nl_cmd_rate_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_rate *devlink_rate = info->user_ptr[1]; ++ struct sk_buff *msg; ++ int err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ 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; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static bool ++devlink_rate_is_parent_node(struct devlink_rate *devlink_rate, ++ struct devlink_rate *parent) ++{ ++ while (parent) { ++ if (parent == devlink_rate) ++ return true; ++ parent = parent->parent; ++ } ++ return false; ++} ++ ++static int devlink_nl_cmd_get_doit(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct sk_buff *msg; ++ int err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_fill(msg, devlink, DEVLINK_CMD_NEW, ++ info->snd_portid, info->snd_seq, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_nl_cmd_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ if (idx < start) { ++ idx++; ++ devlink_put(devlink); ++ continue; ++ } ++ ++ devl_lock(devlink); ++ err = devlink_nl_fill(msg, devlink, DEVLINK_CMD_NEW, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, NLM_F_MULTI); ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ ++ if (err) ++ goto out; ++ idx++; ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_nl_cmd_port_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_port *devlink_port = info->user_ptr[1]; ++ struct sk_buff *msg; ++ int err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_port_fill(msg, devlink_port, DEVLINK_CMD_PORT_NEW, ++ info->snd_portid, info->snd_seq, 0, ++ info->extack); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_nl_cmd_port_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ struct devlink_port *devlink_port; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(devlink_port, &devlink->port_list, list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_port_fill(msg, devlink_port, ++ DEVLINK_CMD_NEW, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI, cb->extack); ++ if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_port_type_set(struct devlink_port *devlink_port, ++ enum devlink_port_type port_type) ++ ++{ ++ int err; ++ ++ if (!devlink_port->devlink->ops->port_type_set) ++ return -EOPNOTSUPP; ++ ++ if (port_type == devlink_port->type) ++ return 0; ++ ++ err = devlink_port->devlink->ops->port_type_set(devlink_port, ++ port_type); ++ if (err) ++ return err; ++ ++ devlink_port->desired_type = port_type; ++ devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); ++ return 0; ++} ++ ++static int devlink_port_function_hw_addr_set(struct devlink_port *port, ++ const struct nlattr *attr, ++ struct netlink_ext_ack *extack) ++{ ++ const struct devlink_ops *ops = port->devlink->ops; ++ const u8 *hw_addr; ++ int hw_addr_len; ++ ++ hw_addr = nla_data(attr); ++ hw_addr_len = nla_len(attr); ++ if (hw_addr_len > MAX_ADDR_LEN) { ++ NL_SET_ERR_MSG_MOD(extack, "Port function hardware address too long"); ++ return -EINVAL; ++ } ++ if (port->type == DEVLINK_PORT_TYPE_ETH) { ++ if (hw_addr_len != ETH_ALEN) { ++ NL_SET_ERR_MSG_MOD(extack, "Address must be 6 bytes for Ethernet device"); ++ return -EINVAL; ++ } ++ if (!is_unicast_ether_addr(hw_addr)) { ++ NL_SET_ERR_MSG_MOD(extack, "Non-unicast hardware address unsupported"); ++ return -EINVAL; ++ } ++ } ++ ++ if (!ops->port_function_hw_addr_set) { ++ NL_SET_ERR_MSG_MOD(extack, "Port doesn't support function attributes"); ++ return -EOPNOTSUPP; ++ } ++ ++ return ops->port_function_hw_addr_set(port, hw_addr, hw_addr_len, ++ extack); ++} ++ ++static int devlink_port_fn_state_set(struct devlink_port *port, ++ const struct nlattr *attr, ++ struct netlink_ext_ack *extack) ++{ ++ enum devlink_port_fn_state state; ++ const struct devlink_ops *ops; ++ ++ state = nla_get_u8(attr); ++ ops = port->devlink->ops; ++ if (!ops->port_fn_state_set) { ++ NL_SET_ERR_MSG_MOD(extack, ++ "Function does not support state setting"); ++ return -EOPNOTSUPP; ++ } ++ return ops->port_fn_state_set(port, state, extack); ++} ++ ++static int devlink_port_function_set(struct devlink_port *port, ++ const struct nlattr *attr, ++ struct netlink_ext_ack *extack) ++{ ++ struct nlattr *tb[DEVLINK_PORT_FUNCTION_ATTR_MAX + 1]; ++ int err; ++ ++ err = nla_parse_nested(tb, DEVLINK_PORT_FUNCTION_ATTR_MAX, attr, ++ devlink_function_nl_policy, extack); ++ if (err < 0) { ++ NL_SET_ERR_MSG_MOD(extack, "Fail to parse port function attributes"); ++ return err; ++ } ++ ++ attr = tb[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]; ++ if (attr) { ++ err = devlink_port_function_hw_addr_set(port, attr, extack); ++ if (err) ++ return err; ++ } ++ /* Keep this as the last function attribute set, so that when ++ * multiple port function attributes are set along with state, ++ * Those can be applied first before activating the state. ++ */ ++ attr = tb[DEVLINK_PORT_FN_ATTR_STATE]; ++ if (attr) ++ err = devlink_port_fn_state_set(port, attr, extack); ++ ++ if (!err) ++ devlink_port_notify(port, DEVLINK_CMD_PORT_NEW); ++ return err; ++} ++ ++static int devlink_nl_cmd_port_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_port *devlink_port = info->user_ptr[1]; ++ int err; ++ ++ if (info->attrs[DEVLINK_ATTR_PORT_TYPE]) { ++ enum devlink_port_type port_type; ++ ++ port_type = nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_TYPE]); ++ err = devlink_port_type_set(devlink_port, port_type); ++ if (err) ++ return err; ++ } ++ ++ if (info->attrs[DEVLINK_ATTR_PORT_FUNCTION]) { ++ struct nlattr *attr = info->attrs[DEVLINK_ATTR_PORT_FUNCTION]; ++ struct netlink_ext_ack *extack = info->extack; ++ ++ err = devlink_port_function_set(devlink_port, attr, extack); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int devlink_nl_cmd_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]; ++ u32 count; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_SPLIT_COUNT)) ++ return -EINVAL; ++ if (!devlink->ops->port_split) ++ return -EOPNOTSUPP; ++ ++ count = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_SPLIT_COUNT]); ++ ++ if (!devlink_port->attrs.splittable) { ++ /* Split ports cannot be split. */ ++ if (devlink_port->attrs.split) ++ NL_SET_ERR_MSG_MOD(info->extack, "Port cannot be split further"); ++ else ++ NL_SET_ERR_MSG_MOD(info->extack, "Port cannot be split"); ++ return -EINVAL; ++ } ++ ++ if (count < 2 || !is_power_of_2(count) || count > devlink_port->attrs.lanes) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Invalid split count"); ++ return -EINVAL; ++ } ++ ++ return devlink->ops->port_split(devlink, devlink_port, count, ++ info->extack); ++} ++ ++static int devlink_nl_cmd_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]; ++ ++ if (!devlink->ops->port_unsplit) ++ return -EOPNOTSUPP; ++ return devlink->ops->port_unsplit(devlink, devlink_port, info->extack); ++} ++ ++static int devlink_port_new_notify(struct devlink *devlink, ++ unsigned int port_index, ++ struct genl_info *info) ++{ ++ struct devlink_port *devlink_port; ++ struct sk_buff *msg; ++ int err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ lockdep_assert_held(&devlink->lock); ++ devlink_port = devlink_port_get_by_index(devlink, port_index); ++ if (!devlink_port) { ++ err = -ENODEV; ++ goto out; ++ } ++ ++ err = devlink_nl_port_fill(msg, devlink_port, DEVLINK_CMD_NEW, ++ info->snd_portid, info->snd_seq, 0, NULL); ++ if (err) ++ goto out; ++ ++ return genlmsg_reply(msg, info); ++ ++out: ++ nlmsg_free(msg); ++ return err; ++} ++ ++static int devlink_nl_cmd_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]; ++ unsigned int new_port_index; ++ int err; ++ ++ if (!devlink->ops->port_new || !devlink->ops->port_del) ++ return -EOPNOTSUPP; ++ ++ if (!info->attrs[DEVLINK_ATTR_PORT_FLAVOUR] || ++ !info->attrs[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]) { ++ NL_SET_ERR_MSG_MOD(extack, "Port flavour or PCI PF are not specified"); ++ return -EINVAL; ++ } ++ new_attrs.flavour = nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_FLAVOUR]); ++ new_attrs.pfnum = ++ nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]); ++ ++ if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { ++ /* Port index of the new port being created by driver. */ ++ new_attrs.port_index = ++ nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); ++ new_attrs.port_index_valid = true; ++ } ++ if (info->attrs[DEVLINK_ATTR_PORT_CONTROLLER_NUMBER]) { ++ new_attrs.controller = ++ nla_get_u16(info->attrs[DEVLINK_ATTR_PORT_CONTROLLER_NUMBER]); ++ new_attrs.controller_valid = true; ++ } ++ if (new_attrs.flavour == DEVLINK_PORT_FLAVOUR_PCI_SF && ++ info->attrs[DEVLINK_ATTR_PORT_PCI_SF_NUMBER]) { ++ new_attrs.sfnum = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_PCI_SF_NUMBER]); ++ new_attrs.sfnum_valid = true; ++ } ++ ++ err = devlink->ops->port_new(devlink, &new_attrs, extack, ++ &new_port_index); ++ if (err) ++ return err; ++ ++ err = devlink_port_new_notify(devlink, new_port_index, info); ++ if (err && err != -ENODEV) { ++ /* Fail to send the response; destroy newly created port. */ ++ devlink->ops->port_del(devlink, new_port_index, extack); ++ } ++ return err; ++} ++ ++static int devlink_nl_cmd_port_del_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct netlink_ext_ack *extack = info->extack; ++ struct devlink *devlink = info->user_ptr[0]; ++ unsigned int port_index; ++ ++ if (!devlink->ops->port_del) ++ return -EOPNOTSUPP; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_INDEX)) { ++ NL_SET_ERR_MSG_MOD(extack, "Port index is not specified"); ++ return -EINVAL; ++ } ++ port_index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); ++ ++ return devlink->ops->port_del(devlink, port_index, extack); ++} ++ ++static int ++devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, ++ struct genl_info *info, ++ struct nlattr *nla_parent) ++{ ++ struct devlink *devlink = devlink_rate->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_rate->parent; ++ if (parent && len) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Rate object already has parent."); ++ return -EBUSY; ++ } else if (parent && !len) { ++ if (devlink_rate_is_leaf(devlink_rate)) ++ err = ops->rate_leaf_parent_set(devlink_rate, NULL, ++ devlink_rate->priv, NULL, ++ info->extack); ++ else if (devlink_rate_is_node(devlink_rate)) ++ err = ops->rate_node_parent_set(devlink_rate, NULL, ++ devlink_rate->priv, NULL, ++ info->extack); ++ if (err) ++ return err; ++ ++ refcount_dec(&parent->refcnt); ++ devlink_rate->parent = NULL; ++ } else if (!parent && len) { ++ parent = devlink_rate_node_get_by_name(devlink, parent_name); ++ if (IS_ERR(parent)) ++ return -ENODEV; ++ ++ if (parent == devlink_rate) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Parent to self is not allowed"); ++ return -EINVAL; ++ } ++ ++ if (devlink_rate_is_node(devlink_rate) && ++ devlink_rate_is_parent_node(devlink_rate, parent->parent)) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Node is already a parent of parent node."); ++ return -EEXIST; ++ } ++ ++ if (devlink_rate_is_leaf(devlink_rate)) ++ err = ops->rate_leaf_parent_set(devlink_rate, parent, ++ devlink_rate->priv, parent->priv, ++ info->extack); ++ else if (devlink_rate_is_node(devlink_rate)) ++ err = ops->rate_node_parent_set(devlink_rate, parent, ++ devlink_rate->priv, parent->priv, ++ info->extack); ++ if (err) ++ return err; ++ ++ refcount_inc(&parent->refcnt); ++ devlink_rate->parent = parent; ++ } ++ ++ return 0; ++} ++ ++static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, ++ const struct devlink_ops *ops, ++ struct genl_info *info) ++{ ++ struct nlattr *nla_parent, **attrs = info->attrs; ++ int err = -EOPNOTSUPP; ++ u64 rate; ++ ++ if (attrs[DEVLINK_ATTR_RATE_TX_SHARE]) { ++ rate = nla_get_u64(attrs[DEVLINK_ATTR_RATE_TX_SHARE]); ++ if (devlink_rate_is_leaf(devlink_rate)) ++ err = ops->rate_leaf_tx_share_set(devlink_rate, devlink_rate->priv, ++ rate, info->extack); ++ else if (devlink_rate_is_node(devlink_rate)) ++ err = ops->rate_node_tx_share_set(devlink_rate, devlink_rate->priv, ++ rate, info->extack); ++ if (err) ++ return err; ++ devlink_rate->tx_share = rate; ++ } ++ ++ if (attrs[DEVLINK_ATTR_RATE_TX_MAX]) { ++ rate = nla_get_u64(attrs[DEVLINK_ATTR_RATE_TX_MAX]); ++ if (devlink_rate_is_leaf(devlink_rate)) ++ err = ops->rate_leaf_tx_max_set(devlink_rate, devlink_rate->priv, ++ rate, info->extack); ++ else if (devlink_rate_is_node(devlink_rate)) ++ err = ops->rate_node_tx_max_set(devlink_rate, devlink_rate->priv, ++ rate, info->extack); ++ if (err) ++ return err; ++ devlink_rate->tx_max = rate; ++ } ++ ++ nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; ++ if (nla_parent) { ++ err = devlink_nl_rate_parent_node_set(devlink_rate, info, ++ nla_parent); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, ++ struct genl_info *info, ++ enum devlink_rate_type type) ++{ ++ struct nlattr **attrs = info->attrs; ++ ++ if (type == DEVLINK_RATE_TYPE_LEAF) { ++ if (attrs[DEVLINK_ATTR_RATE_TX_SHARE] && !ops->rate_leaf_tx_share_set) { ++ NL_SET_ERR_MSG_MOD(info->extack, "TX share set isn't supported for the leafs"); ++ return false; ++ } ++ if (attrs[DEVLINK_ATTR_RATE_TX_MAX] && !ops->rate_leaf_tx_max_set) { ++ NL_SET_ERR_MSG_MOD(info->extack, "TX max set isn't supported for the leafs"); ++ return false; ++ } ++ if (attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME] && ++ !ops->rate_leaf_parent_set) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Parent set isn't supported for the leafs"); ++ return false; ++ } ++ } else if (type == DEVLINK_RATE_TYPE_NODE) { ++ if (attrs[DEVLINK_ATTR_RATE_TX_SHARE] && !ops->rate_node_tx_share_set) { ++ NL_SET_ERR_MSG_MOD(info->extack, "TX share set isn't supported for the nodes"); ++ return false; ++ } ++ if (attrs[DEVLINK_ATTR_RATE_TX_MAX] && !ops->rate_node_tx_max_set) { ++ NL_SET_ERR_MSG_MOD(info->extack, "TX max set isn't supported for the nodes"); ++ return false; ++ } ++ if (attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME] && ++ !ops->rate_node_parent_set) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Parent set isn't supported for the nodes"); ++ return false; ++ } ++ } else { ++ WARN(1, "Unknown type of rate object"); ++ return false; ++ } ++ ++ return true; ++} ++ ++static int devlink_nl_cmd_rate_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_rate *devlink_rate = info->user_ptr[1]; ++ struct devlink *devlink = devlink_rate->devlink; ++ const struct devlink_ops *ops = devlink->ops; ++ int err; ++ ++ if (!ops || !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) ++ return -EOPNOTSUPP; ++ ++ err = devlink_nl_rate_set(devlink_rate, ops, info); ++ ++ if (!err) ++ devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); ++ return err; ++} ++ ++static int devlink_nl_cmd_rate_new_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_rate *rate_node; ++ const struct devlink_ops *ops; ++ int err; ++ ++ ops = devlink->ops; ++ if (!ops || !ops->rate_node_new || !ops->rate_node_del) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Rate nodes aren't supported"); ++ return -EOPNOTSUPP; ++ } ++ ++ 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; ++ ++ rate_node = kzalloc(sizeof(*rate_node), GFP_KERNEL); ++ if (!rate_node) ++ return -ENOMEM; ++ ++ rate_node->devlink = devlink; ++ rate_node->type = DEVLINK_RATE_TYPE_NODE; ++ rate_node->name = nla_strdup(info->attrs[DEVLINK_ATTR_RATE_NODE_NAME], GFP_KERNEL); ++ if (!rate_node->name) { ++ err = -ENOMEM; ++ goto err_strdup; ++ } ++ ++ err = ops->rate_node_new(rate_node, &rate_node->priv, info->extack); ++ if (err) ++ goto err_node_new; ++ ++ err = devlink_nl_rate_set(rate_node, ops, info); ++ if (err) ++ goto err_rate_set; ++ ++ refcount_set(&rate_node->refcnt, 1); ++ list_add(&rate_node->list, &devlink->rate_list); ++ devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); ++ return 0; ++ ++err_rate_set: ++ ops->rate_node_del(rate_node, rate_node->priv, info->extack); ++err_node_new: ++ kfree(rate_node->name); ++err_strdup: ++ kfree(rate_node); ++ return err; ++} ++ ++static int devlink_nl_cmd_rate_del_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_rate *rate_node = info->user_ptr[1]; ++ struct devlink *devlink = rate_node->devlink; ++ const struct devlink_ops *ops = devlink->ops; ++ int err; ++ ++ if (refcount_read(&rate_node->refcnt) > 1) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Node has children. Cannot delete node."); ++ return -EBUSY; ++ } ++ ++ devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); ++ err = ops->rate_node_del(rate_node, rate_node->priv, info->extack); ++ if (rate_node->parent) ++ refcount_dec(&rate_node->parent->refcnt); ++ list_del(&rate_node->list); ++ kfree(rate_node->name); ++ kfree(rate_node); ++ return err; ++} ++ ++struct devlink_linecard_type { ++ const char *type; ++ const void *priv; ++}; ++ ++static int devlink_nl_linecard_fill(struct sk_buff *msg, ++ struct devlink *devlink, ++ struct devlink_linecard *linecard, ++ enum devlink_command cmd, u32 portid, ++ u32 seq, int flags, ++ struct netlink_ext_ack *extack) ++{ ++ struct devlink_linecard_type *linecard_type; ++ struct nlattr *attr; ++ void *hdr; ++ int i; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_LINECARD_INDEX, linecard->index)) ++ goto nla_put_failure; ++ if (nla_put_u8(msg, DEVLINK_ATTR_LINECARD_STATE, linecard->state)) ++ goto nla_put_failure; ++ if (linecard->type && ++ nla_put_string(msg, DEVLINK_ATTR_LINECARD_TYPE, linecard->type)) ++ goto nla_put_failure; ++ ++ if (linecard->types_count) { ++ attr = nla_nest_start(msg, ++ DEVLINK_ATTR_LINECARD_SUPPORTED_TYPES); ++ if (!attr) ++ goto nla_put_failure; ++ for (i = 0; i < linecard->types_count; i++) { ++ linecard_type = &linecard->types[i]; ++ if (nla_put_string(msg, DEVLINK_ATTR_LINECARD_TYPE, ++ linecard_type->type)) { ++ nla_nest_cancel(msg, attr); ++ goto nla_put_failure; ++ } ++ } ++ nla_nest_end(msg, attr); ++ } ++ ++ if (linecard->nested_devlink && ++ devlink_nl_put_nested_handle(msg, linecard->nested_devlink)) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static void devlink_linecard_notify(struct devlink_linecard *linecard, ++ enum devlink_command cmd) ++{ ++ struct devlink *devlink = linecard->devlink; ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON(cmd != DEVLINK_CMD_LINECARD_NEW && ++ cmd != DEVLINK_CMD_LINECARD_DEL); ++ ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_linecard_fill(msg, devlink, linecard, cmd, 0, 0, 0, ++ NULL); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), ++ msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static int devlink_nl_cmd_linecard_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_linecard *linecard = info->user_ptr[1]; ++ struct devlink *devlink = linecard->devlink; ++ struct sk_buff *msg; ++ int err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ mutex_lock(&linecard->state_lock); ++ err = devlink_nl_linecard_fill(msg, devlink, linecard, ++ DEVLINK_CMD_LINECARD_NEW, ++ info->snd_portid, info->snd_seq, 0, ++ info->extack); ++ mutex_unlock(&linecard->state_lock); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_nl_cmd_linecard_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink_linecard *linecard; ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ mutex_lock(&devlink->linecards_lock); ++ list_for_each_entry(linecard, &devlink->linecard_list, list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ mutex_lock(&linecard->state_lock); ++ err = devlink_nl_linecard_fill(msg, devlink, linecard, ++ DEVLINK_CMD_LINECARD_NEW, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI, ++ cb->extack); ++ mutex_unlock(&linecard->state_lock); ++ if (err) { ++ mutex_unlock(&devlink->linecards_lock); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ mutex_unlock(&devlink->linecards_lock); ++ devlink_put(devlink); ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static struct devlink_linecard_type * ++devlink_linecard_type_lookup(struct devlink_linecard *linecard, ++ const char *type) ++{ ++ struct devlink_linecard_type *linecard_type; ++ int i; ++ ++ for (i = 0; i < linecard->types_count; i++) { ++ linecard_type = &linecard->types[i]; ++ if (!strcmp(type, linecard_type->type)) ++ return linecard_type; ++ } ++ return NULL; ++} ++ ++static int devlink_linecard_type_set(struct devlink_linecard *linecard, ++ const char *type, ++ struct netlink_ext_ack *extack) ++{ ++ const struct devlink_linecard_ops *ops = linecard->ops; ++ struct devlink_linecard_type *linecard_type; ++ int err; ++ ++ mutex_lock(&linecard->state_lock); ++ if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING) { ++ NL_SET_ERR_MSG_MOD(extack, "Line card is currently being provisioned"); ++ err = -EBUSY; ++ goto out; ++ } ++ if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONING) { ++ NL_SET_ERR_MSG_MOD(extack, "Line card is currently being unprovisioned"); ++ err = -EBUSY; ++ goto out; ++ } ++ ++ linecard_type = devlink_linecard_type_lookup(linecard, type); ++ if (!linecard_type) { ++ NL_SET_ERR_MSG_MOD(extack, "Unsupported line card type provided"); ++ err = -EINVAL; ++ goto out; ++ } ++ ++ if (linecard->state != DEVLINK_LINECARD_STATE_UNPROVISIONED && ++ linecard->state != DEVLINK_LINECARD_STATE_PROVISIONING_FAILED) { ++ NL_SET_ERR_MSG_MOD(extack, "Line card already provisioned"); ++ err = -EBUSY; ++ /* Check if the line card is provisioned in the same ++ * way the user asks. In case it is, make the operation ++ * to return success. ++ */ ++ if (ops->same_provision && ++ ops->same_provision(linecard, linecard->priv, ++ linecard_type->type, ++ linecard_type->priv)) ++ err = 0; ++ goto out; ++ } ++ ++ linecard->state = DEVLINK_LINECARD_STATE_PROVISIONING; ++ linecard->type = linecard_type->type; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++ err = ops->provision(linecard, linecard->priv, linecard_type->type, ++ linecard_type->priv, extack); ++ if (err) { ++ /* Provisioning failed. Assume the linecard is unprovisioned ++ * for future operations. ++ */ ++ mutex_lock(&linecard->state_lock); ++ linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; ++ linecard->type = NULL; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++ } ++ return err; ++ ++out: ++ mutex_unlock(&linecard->state_lock); ++ return err; ++} ++ ++static int devlink_linecard_type_unset(struct devlink_linecard *linecard, ++ struct netlink_ext_ack *extack) ++{ ++ int err; ++ ++ mutex_lock(&linecard->state_lock); ++ if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING) { ++ NL_SET_ERR_MSG_MOD(extack, "Line card is currently being provisioned"); ++ err = -EBUSY; ++ goto out; ++ } ++ if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONING) { ++ NL_SET_ERR_MSG_MOD(extack, "Line card is currently being unprovisioned"); ++ err = -EBUSY; ++ goto out; ++ } ++ if (linecard->state == DEVLINK_LINECARD_STATE_PROVISIONING_FAILED) { ++ linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; ++ linecard->type = NULL; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ err = 0; ++ goto out; ++ } ++ ++ if (linecard->state == DEVLINK_LINECARD_STATE_UNPROVISIONED) { ++ NL_SET_ERR_MSG_MOD(extack, "Line card is not provisioned"); ++ err = 0; ++ goto out; ++ } ++ linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONING; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++ err = linecard->ops->unprovision(linecard, linecard->priv, ++ extack); ++ if (err) { ++ /* Unprovisioning failed. Assume the linecard is unprovisioned ++ * for future operations. ++ */ ++ mutex_lock(&linecard->state_lock); ++ linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; ++ linecard->type = NULL; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++ } ++ return err; ++ ++out: ++ mutex_unlock(&linecard->state_lock); ++ return err; ++} ++ ++static int devlink_nl_cmd_linecard_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_linecard *linecard = info->user_ptr[1]; ++ struct netlink_ext_ack *extack = info->extack; ++ int err; ++ ++ if (info->attrs[DEVLINK_ATTR_LINECARD_TYPE]) { ++ const char *type; ++ ++ type = nla_data(info->attrs[DEVLINK_ATTR_LINECARD_TYPE]); ++ if (strcmp(type, "")) { ++ err = devlink_linecard_type_set(linecard, type, extack); ++ if (err) ++ return err; ++ } else { ++ err = devlink_linecard_type_unset(linecard, extack); ++ if (err) ++ return err; ++ } ++ } ++ ++ return 0; ++} ++ ++static int devlink_nl_sb_fill(struct sk_buff *msg, struct devlink *devlink, ++ struct devlink_sb *devlink_sb, ++ enum devlink_command cmd, u32 portid, ++ u32 seq, int flags) ++{ ++ void *hdr; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_SIZE, devlink_sb->size)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_INGRESS_POOL_COUNT, ++ devlink_sb->ingress_pools_count)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_EGRESS_POOL_COUNT, ++ devlink_sb->egress_pools_count)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_INGRESS_TC_COUNT, ++ devlink_sb->ingress_tc_count)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_EGRESS_TC_COUNT, ++ devlink_sb->egress_tc_count)) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_sb_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_sb *devlink_sb; ++ struct sk_buff *msg; ++ int err; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_sb_fill(msg, devlink, devlink_sb, ++ DEVLINK_CMD_SB_NEW, ++ info->snd_portid, info->snd_seq, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_nl_cmd_sb_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ struct devlink_sb *devlink_sb; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(devlink_sb, &devlink->sb_list, list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_sb_fill(msg, devlink, devlink_sb, ++ DEVLINK_CMD_SB_NEW, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI); ++ if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_nl_sb_pool_fill(struct sk_buff *msg, struct devlink *devlink, ++ struct devlink_sb *devlink_sb, ++ u16 pool_index, enum devlink_command cmd, ++ u32 portid, u32 seq, int flags) ++{ ++ struct devlink_sb_pool_info pool_info; ++ void *hdr; ++ int err; ++ ++ err = devlink->ops->sb_pool_get(devlink, devlink_sb->index, ++ pool_index, &pool_info); ++ if (err) ++ return err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_POOL_INDEX, pool_index)) ++ goto nla_put_failure; ++ if (nla_put_u8(msg, DEVLINK_ATTR_SB_POOL_TYPE, pool_info.pool_type)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_POOL_SIZE, pool_info.size)) ++ goto nla_put_failure; ++ if (nla_put_u8(msg, DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE, ++ pool_info.threshold_type)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_POOL_CELL_SIZE, ++ pool_info.cell_size)) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_sb_pool_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_sb *devlink_sb; ++ struct sk_buff *msg; ++ u16 pool_index; ++ int err; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ err = devlink_sb_pool_index_get_from_info(devlink_sb, info, ++ &pool_index); ++ if (err) ++ return err; ++ ++ if (!devlink->ops->sb_pool_get) ++ return -EOPNOTSUPP; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_sb_pool_fill(msg, devlink, devlink_sb, pool_index, ++ DEVLINK_CMD_SB_POOL_NEW, ++ info->snd_portid, info->snd_seq, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int __sb_pool_get_dumpit(struct sk_buff *msg, int start, int *p_idx, ++ struct devlink *devlink, ++ struct devlink_sb *devlink_sb, ++ u32 portid, u32 seq) ++{ ++ u16 pool_count = devlink_sb_pool_count(devlink_sb); ++ u16 pool_index; ++ int err; ++ ++ for (pool_index = 0; pool_index < pool_count; pool_index++) { ++ if (*p_idx < start) { ++ (*p_idx)++; ++ continue; ++ } ++ err = devlink_nl_sb_pool_fill(msg, devlink, ++ devlink_sb, ++ pool_index, ++ DEVLINK_CMD_SB_POOL_NEW, ++ portid, seq, NLM_F_MULTI); ++ if (err) ++ return err; ++ (*p_idx)++; ++ } ++ return 0; ++} ++ ++static int devlink_nl_cmd_sb_pool_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ struct devlink_sb *devlink_sb; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ if (!devlink->ops->sb_pool_get) ++ goto retry; ++ ++ devl_lock(devlink); ++ list_for_each_entry(devlink_sb, &devlink->sb_list, list) { ++ err = __sb_pool_get_dumpit(msg, start, &idx, devlink, ++ devlink_sb, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq); ++ if (err == -EOPNOTSUPP) { ++ err = 0; ++ } else if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ } ++ devl_unlock(devlink); ++retry: ++ devlink_put(devlink); ++ } ++out: ++ if (err != -EMSGSIZE) ++ return err; ++ ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index, ++ u16 pool_index, u32 size, ++ enum devlink_sb_threshold_type threshold_type, ++ struct netlink_ext_ack *extack) ++ ++{ ++ const struct devlink_ops *ops = devlink->ops; ++ ++ if (ops->sb_pool_set) ++ return ops->sb_pool_set(devlink, sb_index, pool_index, ++ size, threshold_type, extack); ++ return -EOPNOTSUPP; ++} ++ ++static int devlink_nl_cmd_sb_pool_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ enum devlink_sb_threshold_type threshold_type; ++ struct devlink_sb *devlink_sb; ++ u16 pool_index; ++ u32 size; ++ int err; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ err = devlink_sb_pool_index_get_from_info(devlink_sb, info, ++ &pool_index); ++ if (err) ++ return err; ++ ++ err = devlink_sb_th_type_get_from_info(info, &threshold_type); ++ if (err) ++ return err; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_POOL_SIZE)) ++ return -EINVAL; ++ ++ size = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_POOL_SIZE]); ++ return devlink_sb_pool_set(devlink, devlink_sb->index, ++ pool_index, size, threshold_type, ++ info->extack); ++} ++ ++static int devlink_nl_sb_port_pool_fill(struct sk_buff *msg, ++ struct devlink *devlink, ++ struct devlink_port *devlink_port, ++ struct devlink_sb *devlink_sb, ++ u16 pool_index, ++ enum devlink_command cmd, ++ u32 portid, u32 seq, int flags) ++{ ++ const struct devlink_ops *ops = devlink->ops; ++ u32 threshold; ++ void *hdr; ++ int err; ++ ++ err = ops->sb_port_pool_get(devlink_port, devlink_sb->index, ++ pool_index, &threshold); ++ if (err) ++ return err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_POOL_INDEX, pool_index)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_THRESHOLD, threshold)) ++ goto nla_put_failure; ++ ++ if (ops->sb_occ_port_pool_get) { ++ u32 cur; ++ u32 max; ++ ++ err = ops->sb_occ_port_pool_get(devlink_port, devlink_sb->index, ++ pool_index, &cur, &max); ++ if (err && err != -EOPNOTSUPP) ++ goto sb_occ_get_failure; ++ if (!err) { ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_CUR, cur)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_MAX, max)) ++ goto nla_put_failure; ++ } ++ } ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ err = -EMSGSIZE; ++sb_occ_get_failure: ++ genlmsg_cancel(msg, hdr); ++ return err; ++} ++ ++static int devlink_nl_cmd_sb_port_pool_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_port *devlink_port = info->user_ptr[1]; ++ struct devlink *devlink = devlink_port->devlink; ++ struct devlink_sb *devlink_sb; ++ struct sk_buff *msg; ++ u16 pool_index; ++ int err; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ err = devlink_sb_pool_index_get_from_info(devlink_sb, info, ++ &pool_index); ++ if (err) ++ return err; ++ ++ if (!devlink->ops->sb_port_pool_get) ++ return -EOPNOTSUPP; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_sb_port_pool_fill(msg, devlink, devlink_port, ++ devlink_sb, pool_index, ++ DEVLINK_CMD_SB_PORT_POOL_NEW, ++ info->snd_portid, info->snd_seq, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int __sb_port_pool_get_dumpit(struct sk_buff *msg, int start, int *p_idx, ++ struct devlink *devlink, ++ struct devlink_sb *devlink_sb, ++ u32 portid, u32 seq) ++{ ++ struct devlink_port *devlink_port; ++ u16 pool_count = devlink_sb_pool_count(devlink_sb); ++ u16 pool_index; ++ int err; ++ ++ list_for_each_entry(devlink_port, &devlink->port_list, list) { ++ for (pool_index = 0; pool_index < pool_count; pool_index++) { ++ if (*p_idx < start) { ++ (*p_idx)++; ++ continue; ++ } ++ err = devlink_nl_sb_port_pool_fill(msg, devlink, ++ devlink_port, ++ devlink_sb, ++ pool_index, ++ DEVLINK_CMD_SB_PORT_POOL_NEW, ++ portid, seq, ++ NLM_F_MULTI); ++ if (err) ++ return err; ++ (*p_idx)++; ++ } ++ } ++ return 0; ++} ++ ++static int devlink_nl_cmd_sb_port_pool_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ struct devlink_sb *devlink_sb; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ if (!devlink->ops->sb_port_pool_get) ++ goto retry; ++ ++ devl_lock(devlink); ++ list_for_each_entry(devlink_sb, &devlink->sb_list, list) { ++ err = __sb_port_pool_get_dumpit(msg, start, &idx, ++ devlink, devlink_sb, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq); ++ if (err == -EOPNOTSUPP) { ++ err = 0; ++ } else if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ } ++ devl_unlock(devlink); ++retry: ++ devlink_put(devlink); ++ } ++out: ++ if (err != -EMSGSIZE) ++ return err; ++ ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_sb_port_pool_set(struct devlink_port *devlink_port, ++ unsigned int sb_index, u16 pool_index, ++ u32 threshold, ++ struct netlink_ext_ack *extack) ++ ++{ ++ const struct devlink_ops *ops = devlink_port->devlink->ops; ++ ++ if (ops->sb_port_pool_set) ++ return ops->sb_port_pool_set(devlink_port, sb_index, ++ pool_index, threshold, extack); ++ return -EOPNOTSUPP; ++} ++ ++static int devlink_nl_cmd_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_sb *devlink_sb; ++ u16 pool_index; ++ u32 threshold; ++ int err; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ err = devlink_sb_pool_index_get_from_info(devlink_sb, info, ++ &pool_index); ++ if (err) ++ return err; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_THRESHOLD)) ++ return -EINVAL; ++ ++ threshold = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_THRESHOLD]); ++ return devlink_sb_port_pool_set(devlink_port, devlink_sb->index, ++ pool_index, threshold, info->extack); ++} ++ ++static int ++devlink_nl_sb_tc_pool_bind_fill(struct sk_buff *msg, struct devlink *devlink, ++ struct devlink_port *devlink_port, ++ struct devlink_sb *devlink_sb, u16 tc_index, ++ enum devlink_sb_pool_type pool_type, ++ enum devlink_command cmd, ++ u32 portid, u32 seq, int flags) ++{ ++ const struct devlink_ops *ops = devlink->ops; ++ u16 pool_index; ++ u32 threshold; ++ void *hdr; ++ int err; ++ ++ err = ops->sb_tc_pool_bind_get(devlink_port, devlink_sb->index, ++ tc_index, pool_type, ++ &pool_index, &threshold); ++ if (err) ++ return err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_INDEX, devlink_sb->index)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_TC_INDEX, tc_index)) ++ goto nla_put_failure; ++ if (nla_put_u8(msg, DEVLINK_ATTR_SB_POOL_TYPE, pool_type)) ++ goto nla_put_failure; ++ if (nla_put_u16(msg, DEVLINK_ATTR_SB_POOL_INDEX, pool_index)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_THRESHOLD, threshold)) ++ goto nla_put_failure; ++ ++ if (ops->sb_occ_tc_port_bind_get) { ++ u32 cur; ++ u32 max; ++ ++ err = ops->sb_occ_tc_port_bind_get(devlink_port, ++ devlink_sb->index, ++ tc_index, pool_type, ++ &cur, &max); ++ if (err && err != -EOPNOTSUPP) ++ return err; ++ if (!err) { ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_CUR, cur)) ++ goto nla_put_failure; ++ if (nla_put_u32(msg, DEVLINK_ATTR_SB_OCC_MAX, max)) ++ goto nla_put_failure; ++ } ++ } ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_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 *devlink = devlink_port->devlink; ++ struct devlink_sb *devlink_sb; ++ struct sk_buff *msg; ++ enum devlink_sb_pool_type pool_type; ++ u16 tc_index; ++ int err; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ err = devlink_sb_pool_type_get_from_info(info, &pool_type); ++ if (err) ++ return err; ++ ++ err = devlink_sb_tc_index_get_from_info(devlink_sb, info, ++ pool_type, &tc_index); ++ if (err) ++ return err; ++ ++ if (!devlink->ops->sb_tc_pool_bind_get) ++ return -EOPNOTSUPP; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_sb_tc_pool_bind_fill(msg, devlink, devlink_port, ++ devlink_sb, tc_index, pool_type, ++ DEVLINK_CMD_SB_TC_POOL_BIND_NEW, ++ info->snd_portid, ++ info->snd_seq, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int __sb_tc_pool_bind_get_dumpit(struct sk_buff *msg, ++ int start, int *p_idx, ++ struct devlink *devlink, ++ struct devlink_sb *devlink_sb, ++ u32 portid, u32 seq) ++{ ++ struct devlink_port *devlink_port; ++ u16 tc_index; ++ int err; ++ ++ list_for_each_entry(devlink_port, &devlink->port_list, list) { ++ for (tc_index = 0; ++ tc_index < devlink_sb->ingress_tc_count; tc_index++) { ++ if (*p_idx < start) { ++ (*p_idx)++; ++ continue; ++ } ++ err = devlink_nl_sb_tc_pool_bind_fill(msg, devlink, ++ devlink_port, ++ devlink_sb, ++ tc_index, ++ DEVLINK_SB_POOL_TYPE_INGRESS, ++ DEVLINK_CMD_SB_TC_POOL_BIND_NEW, ++ portid, seq, ++ NLM_F_MULTI); ++ if (err) ++ return err; ++ (*p_idx)++; ++ } ++ for (tc_index = 0; ++ tc_index < devlink_sb->egress_tc_count; tc_index++) { ++ if (*p_idx < start) { ++ (*p_idx)++; ++ continue; ++ } ++ err = devlink_nl_sb_tc_pool_bind_fill(msg, devlink, ++ devlink_port, ++ devlink_sb, ++ tc_index, ++ DEVLINK_SB_POOL_TYPE_EGRESS, ++ DEVLINK_CMD_SB_TC_POOL_BIND_NEW, ++ portid, seq, ++ NLM_F_MULTI); ++ if (err) ++ return err; ++ (*p_idx)++; ++ } ++ } ++ return 0; ++} ++ ++static int ++devlink_nl_cmd_sb_tc_pool_bind_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ struct devlink_sb *devlink_sb; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ if (!devlink->ops->sb_tc_pool_bind_get) ++ goto retry; ++ ++ devl_lock(devlink); ++ list_for_each_entry(devlink_sb, &devlink->sb_list, list) { ++ err = __sb_tc_pool_bind_get_dumpit(msg, start, &idx, ++ devlink, ++ devlink_sb, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq); ++ if (err == -EOPNOTSUPP) { ++ err = 0; ++ } else if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ } ++ devl_unlock(devlink); ++retry: ++ devlink_put(devlink); ++ } ++out: ++ if (err != -EMSGSIZE) ++ return err; ++ ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port, ++ unsigned int sb_index, u16 tc_index, ++ enum devlink_sb_pool_type pool_type, ++ u16 pool_index, u32 threshold, ++ struct netlink_ext_ack *extack) ++ ++{ ++ const struct devlink_ops *ops = devlink_port->devlink->ops; ++ ++ if (ops->sb_tc_pool_bind_set) ++ return ops->sb_tc_pool_bind_set(devlink_port, sb_index, ++ tc_index, pool_type, ++ pool_index, threshold, extack); ++ return -EOPNOTSUPP; ++} ++ ++static int devlink_nl_cmd_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]; ++ enum devlink_sb_pool_type pool_type; ++ struct devlink_sb *devlink_sb; ++ u16 tc_index; ++ u16 pool_index; ++ u32 threshold; ++ int err; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ err = devlink_sb_pool_type_get_from_info(info, &pool_type); ++ if (err) ++ return err; ++ ++ err = devlink_sb_tc_index_get_from_info(devlink_sb, info, ++ pool_type, &tc_index); ++ if (err) ++ return err; ++ ++ err = devlink_sb_pool_index_get_from_info(devlink_sb, info, ++ &pool_index); ++ if (err) ++ return err; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SB_THRESHOLD)) ++ return -EINVAL; ++ ++ threshold = nla_get_u32(info->attrs[DEVLINK_ATTR_SB_THRESHOLD]); ++ return devlink_sb_tc_pool_bind_set(devlink_port, devlink_sb->index, ++ tc_index, pool_type, ++ pool_index, threshold, info->extack); ++} ++ ++static int devlink_nl_cmd_sb_occ_snapshot_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ const struct devlink_ops *ops = devlink->ops; ++ struct devlink_sb *devlink_sb; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ if (ops->sb_occ_snapshot) ++ return ops->sb_occ_snapshot(devlink, devlink_sb->index); ++ return -EOPNOTSUPP; ++} ++ ++static int devlink_nl_cmd_sb_occ_max_clear_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ const struct devlink_ops *ops = devlink->ops; ++ struct devlink_sb *devlink_sb; ++ ++ devlink_sb = devlink_sb_get_from_info(devlink, info); ++ if (IS_ERR(devlink_sb)) ++ return PTR_ERR(devlink_sb); ++ ++ if (ops->sb_occ_max_clear) ++ return ops->sb_occ_max_clear(devlink, devlink_sb->index); ++ return -EOPNOTSUPP; ++} ++ ++static int devlink_nl_eswitch_fill(struct sk_buff *msg, struct devlink *devlink, ++ enum devlink_command cmd, u32 portid, ++ u32 seq, int flags) ++{ ++ const struct devlink_ops *ops = devlink->ops; ++ enum devlink_eswitch_encap_mode encap_mode; ++ u8 inline_mode; ++ void *hdr; ++ int err = 0; ++ u16 mode; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ err = devlink_nl_put_handle(msg, devlink); ++ if (err) ++ goto nla_put_failure; ++ ++ if (ops->eswitch_mode_get) { ++ err = ops->eswitch_mode_get(devlink, &mode); ++ if (err) ++ goto nla_put_failure; ++ err = nla_put_u16(msg, DEVLINK_ATTR_ESWITCH_MODE, mode); ++ if (err) ++ goto nla_put_failure; ++ } ++ ++ if (ops->eswitch_inline_mode_get) { ++ err = ops->eswitch_inline_mode_get(devlink, &inline_mode); ++ if (err) ++ goto nla_put_failure; ++ err = nla_put_u8(msg, DEVLINK_ATTR_ESWITCH_INLINE_MODE, ++ inline_mode); ++ if (err) ++ goto nla_put_failure; ++ } ++ ++ if (ops->eswitch_encap_mode_get) { ++ err = ops->eswitch_encap_mode_get(devlink, &encap_mode); ++ if (err) ++ goto nla_put_failure; ++ err = nla_put_u8(msg, DEVLINK_ATTR_ESWITCH_ENCAP_MODE, encap_mode); ++ if (err) ++ goto nla_put_failure; ++ } ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return err; ++} ++ ++static int devlink_nl_cmd_eswitch_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct sk_buff *msg; ++ int err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_eswitch_fill(msg, devlink, DEVLINK_CMD_ESWITCH_GET, ++ info->snd_portid, info->snd_seq, 0); ++ ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_rate_nodes_check(struct devlink *devlink, u16 mode, ++ struct netlink_ext_ack *extack) ++{ ++ struct devlink_rate *devlink_rate; ++ ++ list_for_each_entry(devlink_rate, &devlink->rate_list, list) ++ if (devlink_rate_is_node(devlink_rate)) { ++ NL_SET_ERR_MSG_MOD(extack, "Rate node(s) exists."); ++ return -EBUSY; ++ } ++ return 0; ++} ++ ++static int devlink_nl_cmd_eswitch_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ const struct devlink_ops *ops = devlink->ops; ++ enum devlink_eswitch_encap_mode encap_mode; ++ u8 inline_mode; ++ int err = 0; ++ u16 mode; ++ ++ if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) { ++ if (!ops->eswitch_mode_set) ++ return -EOPNOTSUPP; ++ mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]); ++ err = devlink_rate_nodes_check(devlink, mode, info->extack); ++ if (err) ++ return err; ++ err = ops->eswitch_mode_set(devlink, mode, info->extack); ++ if (err) ++ return err; ++ } ++ ++ if (info->attrs[DEVLINK_ATTR_ESWITCH_INLINE_MODE]) { ++ if (!ops->eswitch_inline_mode_set) ++ return -EOPNOTSUPP; ++ inline_mode = nla_get_u8( ++ info->attrs[DEVLINK_ATTR_ESWITCH_INLINE_MODE]); ++ err = ops->eswitch_inline_mode_set(devlink, inline_mode, ++ info->extack); ++ if (err) ++ return err; ++ } ++ ++ if (info->attrs[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]) { ++ if (!ops->eswitch_encap_mode_set) ++ return -EOPNOTSUPP; ++ encap_mode = nla_get_u8(info->attrs[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]); ++ err = ops->eswitch_encap_mode_set(devlink, encap_mode, ++ info->extack); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++int devlink_dpipe_match_put(struct sk_buff *skb, ++ struct devlink_dpipe_match *match) ++{ ++ struct devlink_dpipe_header *header = match->header; ++ struct devlink_dpipe_field *field = &header->fields[match->field_id]; ++ struct nlattr *match_attr; ++ ++ match_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_MATCH); ++ if (!match_attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_u32(skb, DEVLINK_ATTR_DPIPE_MATCH_TYPE, match->type) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_INDEX, match->header_index) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_ID, header->id) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_ID, field->id) || ++ nla_put_u8(skb, DEVLINK_ATTR_DPIPE_HEADER_GLOBAL, header->global)) ++ goto nla_put_failure; ++ ++ nla_nest_end(skb, match_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(skb, match_attr); ++ return -EMSGSIZE; ++} ++EXPORT_SYMBOL_GPL(devlink_dpipe_match_put); ++ ++static int devlink_dpipe_matches_put(struct devlink_dpipe_table *table, ++ struct sk_buff *skb) ++{ ++ struct nlattr *matches_attr; ++ ++ matches_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_TABLE_MATCHES); ++ if (!matches_attr) ++ return -EMSGSIZE; ++ ++ if (table->table_ops->matches_dump(table->priv, skb)) ++ goto nla_put_failure; ++ ++ nla_nest_end(skb, matches_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(skb, matches_attr); ++ return -EMSGSIZE; ++} ++ ++int devlink_dpipe_action_put(struct sk_buff *skb, ++ struct devlink_dpipe_action *action) ++{ ++ struct devlink_dpipe_header *header = action->header; ++ struct devlink_dpipe_field *field = &header->fields[action->field_id]; ++ struct nlattr *action_attr; ++ ++ action_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_ACTION); ++ if (!action_attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_u32(skb, DEVLINK_ATTR_DPIPE_ACTION_TYPE, action->type) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_INDEX, action->header_index) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_ID, header->id) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_ID, field->id) || ++ nla_put_u8(skb, DEVLINK_ATTR_DPIPE_HEADER_GLOBAL, header->global)) ++ goto nla_put_failure; ++ ++ nla_nest_end(skb, action_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(skb, action_attr); ++ return -EMSGSIZE; ++} ++EXPORT_SYMBOL_GPL(devlink_dpipe_action_put); ++ ++static int devlink_dpipe_actions_put(struct devlink_dpipe_table *table, ++ struct sk_buff *skb) ++{ ++ struct nlattr *actions_attr; ++ ++ actions_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_TABLE_ACTIONS); ++ if (!actions_attr) ++ return -EMSGSIZE; ++ ++ if (table->table_ops->actions_dump(table->priv, skb)) ++ goto nla_put_failure; ++ ++ nla_nest_end(skb, actions_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(skb, actions_attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_dpipe_table_put(struct sk_buff *skb, ++ struct devlink_dpipe_table *table) ++{ ++ struct nlattr *table_attr; ++ u64 table_size; ++ ++ table_size = table->table_ops->size_get(table->priv); ++ table_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_TABLE); ++ if (!table_attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_string(skb, DEVLINK_ATTR_DPIPE_TABLE_NAME, table->name) || ++ nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_TABLE_SIZE, table_size, ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ if (nla_put_u8(skb, DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED, ++ table->counters_enabled)) ++ goto nla_put_failure; ++ ++ if (table->resource_valid) { ++ if (nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID, ++ table->resource_id, DEVLINK_ATTR_PAD) || ++ nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS, ++ table->resource_units, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ } ++ if (devlink_dpipe_matches_put(table, skb)) ++ goto nla_put_failure; ++ ++ if (devlink_dpipe_actions_put(table, skb)) ++ goto nla_put_failure; ++ ++ nla_nest_end(skb, table_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(skb, table_attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_dpipe_send_and_alloc_skb(struct sk_buff **pskb, ++ struct genl_info *info) ++{ ++ int err; ++ ++ if (*pskb) { ++ err = genlmsg_reply(*pskb, info); ++ if (err) ++ return err; ++ } ++ *pskb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!*pskb) ++ return -ENOMEM; ++ return 0; ++} ++ ++static int devlink_dpipe_tables_fill(struct genl_info *info, ++ enum devlink_command cmd, int flags, ++ struct list_head *dpipe_tables, ++ const char *table_name) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_dpipe_table *table; ++ struct nlattr *tables_attr; ++ struct sk_buff *skb = NULL; ++ struct nlmsghdr *nlh; ++ bool incomplete; ++ void *hdr; ++ int i; ++ int err; ++ ++ table = list_first_entry(dpipe_tables, ++ struct devlink_dpipe_table, list); ++start_again: ++ err = devlink_dpipe_send_and_alloc_skb(&skb, info); ++ if (err) ++ return err; ++ ++ hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, ++ &devlink_nl_family, NLM_F_MULTI, cmd); ++ if (!hdr) { ++ nlmsg_free(skb); ++ return -EMSGSIZE; ++ } ++ ++ if (devlink_nl_put_handle(skb, devlink)) ++ goto nla_put_failure; ++ tables_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_TABLES); ++ if (!tables_attr) ++ goto nla_put_failure; ++ ++ i = 0; ++ incomplete = false; ++ list_for_each_entry_from(table, dpipe_tables, list) { ++ if (!table_name) { ++ err = devlink_dpipe_table_put(skb, table); ++ if (err) { ++ if (!i) ++ goto err_table_put; ++ incomplete = true; ++ break; ++ } ++ } else { ++ if (!strcmp(table->name, table_name)) { ++ err = devlink_dpipe_table_put(skb, table); ++ if (err) ++ break; ++ } ++ } ++ i++; ++ } ++ ++ nla_nest_end(skb, tables_attr); ++ genlmsg_end(skb, hdr); ++ if (incomplete) ++ goto start_again; ++ ++send_done: ++ nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, ++ NLMSG_DONE, 0, flags | NLM_F_MULTI); ++ if (!nlh) { ++ err = devlink_dpipe_send_and_alloc_skb(&skb, info); ++ if (err) ++ return err; ++ goto send_done; ++ } ++ ++ return genlmsg_reply(skb, info); ++ ++nla_put_failure: ++ err = -EMSGSIZE; ++err_table_put: ++ nlmsg_free(skb); ++ return err; ++} ++ ++static int devlink_nl_cmd_dpipe_table_get(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ const char *table_name = NULL; ++ ++ if (info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]) ++ table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]); ++ ++ return devlink_dpipe_tables_fill(info, DEVLINK_CMD_DPIPE_TABLE_GET, 0, ++ &devlink->dpipe_table_list, ++ table_name); ++} ++ ++static int devlink_dpipe_value_put(struct sk_buff *skb, ++ struct devlink_dpipe_value *value) ++{ ++ if (nla_put(skb, DEVLINK_ATTR_DPIPE_VALUE, ++ value->value_size, value->value)) ++ return -EMSGSIZE; ++ if (value->mask) ++ if (nla_put(skb, DEVLINK_ATTR_DPIPE_VALUE_MASK, ++ value->value_size, value->mask)) ++ return -EMSGSIZE; ++ if (value->mapping_valid) ++ if (nla_put_u32(skb, DEVLINK_ATTR_DPIPE_VALUE_MAPPING, ++ value->mapping_value)) ++ return -EMSGSIZE; ++ return 0; ++} ++ ++static int devlink_dpipe_action_value_put(struct sk_buff *skb, ++ struct devlink_dpipe_value *value) ++{ ++ if (!value->action) ++ return -EINVAL; ++ if (devlink_dpipe_action_put(skb, value->action)) ++ return -EMSGSIZE; ++ if (devlink_dpipe_value_put(skb, value)) ++ return -EMSGSIZE; ++ return 0; ++} ++ ++static int devlink_dpipe_action_values_put(struct sk_buff *skb, ++ struct devlink_dpipe_value *values, ++ unsigned int values_count) ++{ ++ struct nlattr *action_attr; ++ int i; ++ int err; ++ ++ for (i = 0; i < values_count; i++) { ++ action_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_ACTION_VALUE); ++ if (!action_attr) ++ return -EMSGSIZE; ++ err = devlink_dpipe_action_value_put(skb, &values[i]); ++ if (err) ++ goto err_action_value_put; ++ nla_nest_end(skb, action_attr); ++ } ++ return 0; ++ ++err_action_value_put: ++ nla_nest_cancel(skb, action_attr); ++ return err; ++} ++ ++static int devlink_dpipe_match_value_put(struct sk_buff *skb, ++ struct devlink_dpipe_value *value) ++{ ++ if (!value->match) ++ return -EINVAL; ++ if (devlink_dpipe_match_put(skb, value->match)) ++ return -EMSGSIZE; ++ if (devlink_dpipe_value_put(skb, value)) ++ return -EMSGSIZE; ++ return 0; ++} ++ ++static int devlink_dpipe_match_values_put(struct sk_buff *skb, ++ struct devlink_dpipe_value *values, ++ unsigned int values_count) ++{ ++ struct nlattr *match_attr; ++ int i; ++ int err; ++ ++ for (i = 0; i < values_count; i++) { ++ match_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_MATCH_VALUE); ++ if (!match_attr) ++ return -EMSGSIZE; ++ err = devlink_dpipe_match_value_put(skb, &values[i]); ++ if (err) ++ goto err_match_value_put; ++ nla_nest_end(skb, match_attr); ++ } ++ return 0; ++ ++err_match_value_put: ++ nla_nest_cancel(skb, match_attr); ++ return err; ++} ++ ++static int devlink_dpipe_entry_put(struct sk_buff *skb, ++ struct devlink_dpipe_entry *entry) ++{ ++ struct nlattr *entry_attr, *matches_attr, *actions_attr; ++ int err; ++ ++ entry_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_ENTRY); ++ if (!entry_attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_ENTRY_INDEX, entry->index, ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ if (entry->counter_valid) ++ if (nla_put_u64_64bit(skb, DEVLINK_ATTR_DPIPE_ENTRY_COUNTER, ++ entry->counter, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ matches_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES); ++ if (!matches_attr) ++ goto nla_put_failure; ++ ++ err = devlink_dpipe_match_values_put(skb, entry->match_values, ++ entry->match_values_count); ++ if (err) { ++ nla_nest_cancel(skb, matches_attr); ++ goto err_match_values_put; ++ } ++ nla_nest_end(skb, matches_attr); ++ ++ actions_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES); ++ if (!actions_attr) ++ goto nla_put_failure; ++ ++ err = devlink_dpipe_action_values_put(skb, entry->action_values, ++ entry->action_values_count); ++ if (err) { ++ nla_nest_cancel(skb, actions_attr); ++ goto err_action_values_put; ++ } ++ nla_nest_end(skb, actions_attr); ++ ++ nla_nest_end(skb, entry_attr); ++ return 0; ++ ++nla_put_failure: ++ err = -EMSGSIZE; ++err_match_values_put: ++err_action_values_put: ++ nla_nest_cancel(skb, entry_attr); ++ return err; ++} ++ ++static struct devlink_dpipe_table * ++devlink_dpipe_table_find(struct list_head *dpipe_tables, ++ const char *table_name, struct devlink *devlink) ++{ ++ struct devlink_dpipe_table *table; ++ list_for_each_entry_rcu(table, dpipe_tables, list, ++ lockdep_is_held(&devlink->lock)) { ++ if (!strcmp(table->name, table_name)) ++ return table; ++ } ++ return NULL; ++} ++ ++int devlink_dpipe_entry_ctx_prepare(struct devlink_dpipe_dump_ctx *dump_ctx) ++{ ++ struct devlink *devlink; ++ int err; ++ ++ err = devlink_dpipe_send_and_alloc_skb(&dump_ctx->skb, ++ dump_ctx->info); ++ if (err) ++ return err; ++ ++ dump_ctx->hdr = genlmsg_put(dump_ctx->skb, ++ dump_ctx->info->snd_portid, ++ dump_ctx->info->snd_seq, ++ &devlink_nl_family, NLM_F_MULTI, ++ dump_ctx->cmd); ++ if (!dump_ctx->hdr) ++ goto nla_put_failure; ++ ++ devlink = dump_ctx->info->user_ptr[0]; ++ if (devlink_nl_put_handle(dump_ctx->skb, devlink)) ++ goto nla_put_failure; ++ dump_ctx->nest = nla_nest_start_noflag(dump_ctx->skb, ++ DEVLINK_ATTR_DPIPE_ENTRIES); ++ if (!dump_ctx->nest) ++ goto nla_put_failure; ++ return 0; ++ ++nla_put_failure: ++ nlmsg_free(dump_ctx->skb); ++ return -EMSGSIZE; ++} ++EXPORT_SYMBOL_GPL(devlink_dpipe_entry_ctx_prepare); ++ ++int devlink_dpipe_entry_ctx_append(struct devlink_dpipe_dump_ctx *dump_ctx, ++ struct devlink_dpipe_entry *entry) ++{ ++ return devlink_dpipe_entry_put(dump_ctx->skb, entry); ++} ++EXPORT_SYMBOL_GPL(devlink_dpipe_entry_ctx_append); ++ ++int devlink_dpipe_entry_ctx_close(struct devlink_dpipe_dump_ctx *dump_ctx) ++{ ++ nla_nest_end(dump_ctx->skb, dump_ctx->nest); ++ genlmsg_end(dump_ctx->skb, dump_ctx->hdr); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_dpipe_entry_ctx_close); ++ ++void devlink_dpipe_entry_clear(struct devlink_dpipe_entry *entry) ++ ++{ ++ unsigned int value_count, value_index; ++ struct devlink_dpipe_value *value; ++ ++ value = entry->action_values; ++ value_count = entry->action_values_count; ++ for (value_index = 0; value_index < value_count; value_index++) { ++ kfree(value[value_index].value); ++ kfree(value[value_index].mask); ++ } ++ ++ value = entry->match_values; ++ value_count = entry->match_values_count; ++ for (value_index = 0; value_index < value_count; value_index++) { ++ kfree(value[value_index].value); ++ kfree(value[value_index].mask); ++ } ++} ++EXPORT_SYMBOL_GPL(devlink_dpipe_entry_clear); ++ ++static int devlink_dpipe_entries_fill(struct genl_info *info, ++ enum devlink_command cmd, int flags, ++ struct devlink_dpipe_table *table) ++{ ++ struct devlink_dpipe_dump_ctx dump_ctx; ++ struct nlmsghdr *nlh; ++ int err; ++ ++ dump_ctx.skb = NULL; ++ dump_ctx.cmd = cmd; ++ dump_ctx.info = info; ++ ++ err = table->table_ops->entries_dump(table->priv, ++ table->counters_enabled, ++ &dump_ctx); ++ if (err) ++ return err; ++ ++send_done: ++ nlh = nlmsg_put(dump_ctx.skb, info->snd_portid, info->snd_seq, ++ NLMSG_DONE, 0, flags | NLM_F_MULTI); ++ if (!nlh) { ++ err = devlink_dpipe_send_and_alloc_skb(&dump_ctx.skb, info); ++ if (err) ++ return err; ++ goto send_done; ++ } ++ return genlmsg_reply(dump_ctx.skb, info); ++} ++ ++static int devlink_nl_cmd_dpipe_entries_get(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_dpipe_table *table; ++ const char *table_name; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_DPIPE_TABLE_NAME)) ++ return -EINVAL; ++ ++ table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]); ++ table = devlink_dpipe_table_find(&devlink->dpipe_table_list, ++ table_name, devlink); ++ if (!table) ++ return -EINVAL; ++ ++ if (!table->table_ops->entries_dump) ++ return -EINVAL; ++ ++ return devlink_dpipe_entries_fill(info, DEVLINK_CMD_DPIPE_ENTRIES_GET, ++ 0, table); ++} ++ ++static int devlink_dpipe_fields_put(struct sk_buff *skb, ++ const struct devlink_dpipe_header *header) ++{ ++ struct devlink_dpipe_field *field; ++ struct nlattr *field_attr; ++ int i; ++ ++ for (i = 0; i < header->fields_count; i++) { ++ field = &header->fields[i]; ++ field_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_FIELD); ++ if (!field_attr) ++ return -EMSGSIZE; ++ if (nla_put_string(skb, DEVLINK_ATTR_DPIPE_FIELD_NAME, field->name) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_ID, field->id) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH, field->bitwidth) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE, field->mapping_type)) ++ goto nla_put_failure; ++ nla_nest_end(skb, field_attr); ++ } ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(skb, field_attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_dpipe_header_put(struct sk_buff *skb, ++ struct devlink_dpipe_header *header) ++{ ++ struct nlattr *fields_attr, *header_attr; ++ int err; ++ ++ header_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_HEADER); ++ if (!header_attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_string(skb, DEVLINK_ATTR_DPIPE_HEADER_NAME, header->name) || ++ nla_put_u32(skb, DEVLINK_ATTR_DPIPE_HEADER_ID, header->id) || ++ nla_put_u8(skb, DEVLINK_ATTR_DPIPE_HEADER_GLOBAL, header->global)) ++ goto nla_put_failure; ++ ++ fields_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_DPIPE_HEADER_FIELDS); ++ if (!fields_attr) ++ goto nla_put_failure; ++ ++ err = devlink_dpipe_fields_put(skb, header); ++ if (err) { ++ nla_nest_cancel(skb, fields_attr); ++ goto nla_put_failure; ++ } ++ nla_nest_end(skb, fields_attr); ++ nla_nest_end(skb, header_attr); ++ return 0; ++ ++nla_put_failure: ++ err = -EMSGSIZE; ++ nla_nest_cancel(skb, header_attr); ++ return err; ++} ++ ++static int devlink_dpipe_headers_fill(struct genl_info *info, ++ enum devlink_command cmd, int flags, ++ struct devlink_dpipe_headers * ++ dpipe_headers) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct nlattr *headers_attr; ++ struct sk_buff *skb = NULL; ++ struct nlmsghdr *nlh; ++ void *hdr; ++ int i, j; ++ int err; ++ ++ i = 0; ++start_again: ++ err = devlink_dpipe_send_and_alloc_skb(&skb, info); ++ if (err) ++ return err; ++ ++ hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, ++ &devlink_nl_family, NLM_F_MULTI, cmd); ++ if (!hdr) { ++ nlmsg_free(skb); ++ return -EMSGSIZE; ++ } ++ ++ if (devlink_nl_put_handle(skb, devlink)) ++ goto nla_put_failure; ++ headers_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_DPIPE_HEADERS); ++ if (!headers_attr) ++ goto nla_put_failure; ++ ++ j = 0; ++ for (; i < dpipe_headers->headers_count; i++) { ++ err = devlink_dpipe_header_put(skb, dpipe_headers->headers[i]); ++ if (err) { ++ if (!j) ++ goto err_table_put; ++ break; ++ } ++ j++; ++ } ++ nla_nest_end(skb, headers_attr); ++ genlmsg_end(skb, hdr); ++ if (i != dpipe_headers->headers_count) ++ goto start_again; ++ ++send_done: ++ nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, ++ NLMSG_DONE, 0, flags | NLM_F_MULTI); ++ if (!nlh) { ++ err = devlink_dpipe_send_and_alloc_skb(&skb, info); ++ if (err) ++ return err; ++ goto send_done; ++ } ++ return genlmsg_reply(skb, info); ++ ++nla_put_failure: ++ err = -EMSGSIZE; ++err_table_put: ++ nlmsg_free(skb); ++ return err; ++} ++ ++static int devlink_nl_cmd_dpipe_headers_get(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ ++ if (!devlink->dpipe_headers) ++ return -EOPNOTSUPP; ++ return devlink_dpipe_headers_fill(info, DEVLINK_CMD_DPIPE_HEADERS_GET, ++ 0, devlink->dpipe_headers); ++} ++ ++static int devlink_dpipe_table_counters_set(struct devlink *devlink, ++ const char *table_name, ++ bool enable) ++{ ++ struct devlink_dpipe_table *table; ++ ++ table = devlink_dpipe_table_find(&devlink->dpipe_table_list, ++ table_name, devlink); ++ if (!table) ++ return -EINVAL; ++ ++ if (table->counter_control_extern) ++ return -EOPNOTSUPP; ++ ++ if (!(table->counters_enabled ^ enable)) ++ return 0; ++ ++ table->counters_enabled = enable; ++ if (table->table_ops->counters_set_update) ++ table->table_ops->counters_set_update(table->priv, enable); ++ return 0; ++} ++ ++static int devlink_nl_cmd_dpipe_table_counters_set(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ const char *table_name; ++ bool counters_enable; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_DPIPE_TABLE_NAME) || ++ GENL_REQ_ATTR_CHECK(info, ++ DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED)) ++ return -EINVAL; ++ ++ table_name = nla_data(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]); ++ counters_enable = !!nla_get_u8(info->attrs[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED]); ++ ++ return devlink_dpipe_table_counters_set(devlink, table_name, ++ counters_enable); ++} ++ ++static struct devlink_resource * ++devlink_resource_find(struct devlink *devlink, ++ struct devlink_resource *resource, u64 resource_id) ++{ ++ struct list_head *resource_list; ++ ++ if (resource) ++ resource_list = &resource->resource_list; ++ else ++ resource_list = &devlink->resource_list; ++ ++ list_for_each_entry(resource, resource_list, list) { ++ struct devlink_resource *child_resource; ++ ++ if (resource->id == resource_id) ++ return resource; ++ ++ child_resource = devlink_resource_find(devlink, resource, ++ resource_id); ++ if (child_resource) ++ return child_resource; ++ } ++ return NULL; ++} ++ ++static void ++devlink_resource_validate_children(struct devlink_resource *resource) ++{ ++ struct devlink_resource *child_resource; ++ bool size_valid = true; ++ u64 parts_size = 0; ++ ++ if (list_empty(&resource->resource_list)) ++ goto out; ++ ++ list_for_each_entry(child_resource, &resource->resource_list, list) ++ parts_size += child_resource->size_new; ++ ++ if (parts_size > resource->size_new) ++ size_valid = false; ++out: ++ resource->size_valid = size_valid; ++} ++ ++static int ++devlink_resource_validate_size(struct devlink_resource *resource, u64 size, ++ struct netlink_ext_ack *extack) ++{ ++ u64 reminder; ++ int err = 0; ++ ++ if (size > resource->size_params.size_max) { ++ NL_SET_ERR_MSG_MOD(extack, "Size larger than maximum"); ++ err = -EINVAL; ++ } ++ ++ if (size < resource->size_params.size_min) { ++ NL_SET_ERR_MSG_MOD(extack, "Size smaller than minimum"); ++ err = -EINVAL; ++ } ++ ++ div64_u64_rem(size, resource->size_params.size_granularity, &reminder); ++ if (reminder) { ++ NL_SET_ERR_MSG_MOD(extack, "Wrong granularity"); ++ err = -EINVAL; ++ } ++ ++ return err; ++} ++ ++static int devlink_nl_cmd_resource_set(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_resource *resource; ++ u64 resource_id; ++ u64 size; ++ int err; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_ID) || ++ GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_RESOURCE_SIZE)) ++ return -EINVAL; ++ resource_id = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_ID]); ++ ++ resource = devlink_resource_find(devlink, NULL, resource_id); ++ if (!resource) ++ return -EINVAL; ++ ++ size = nla_get_u64(info->attrs[DEVLINK_ATTR_RESOURCE_SIZE]); ++ err = devlink_resource_validate_size(resource, size, info->extack); ++ if (err) ++ return err; ++ ++ resource->size_new = size; ++ devlink_resource_validate_children(resource); ++ if (resource->parent) ++ devlink_resource_validate_children(resource->parent); ++ return 0; ++} ++ ++static int ++devlink_resource_size_params_put(struct devlink_resource *resource, ++ struct sk_buff *skb) ++{ ++ struct devlink_resource_size_params *size_params; ++ ++ size_params = &resource->size_params; ++ if (nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_GRAN, ++ size_params->size_granularity, DEVLINK_ATTR_PAD) || ++ nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_MAX, ++ size_params->size_max, DEVLINK_ATTR_PAD) || ++ nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_MIN, ++ size_params->size_min, DEVLINK_ATTR_PAD) || ++ nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_UNIT, size_params->unit)) ++ return -EMSGSIZE; ++ return 0; ++} ++ ++static int devlink_resource_occ_put(struct devlink_resource *resource, ++ struct sk_buff *skb) ++{ ++ if (!resource->occ_get) ++ return 0; ++ return nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_OCC, ++ resource->occ_get(resource->occ_get_priv), ++ DEVLINK_ATTR_PAD); ++} ++ ++static int devlink_resource_put(struct devlink *devlink, struct sk_buff *skb, ++ struct devlink_resource *resource) ++{ ++ struct devlink_resource *child_resource; ++ struct nlattr *child_resource_attr; ++ struct nlattr *resource_attr; ++ ++ resource_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_RESOURCE); ++ if (!resource_attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_string(skb, DEVLINK_ATTR_RESOURCE_NAME, resource->name) || ++ nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE, resource->size, ++ DEVLINK_ATTR_PAD) || ++ nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_ID, resource->id, ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ if (resource->size != resource->size_new) ++ nla_put_u64_64bit(skb, DEVLINK_ATTR_RESOURCE_SIZE_NEW, ++ resource->size_new, DEVLINK_ATTR_PAD); ++ if (devlink_resource_occ_put(resource, skb)) ++ goto nla_put_failure; ++ if (devlink_resource_size_params_put(resource, skb)) ++ goto nla_put_failure; ++ if (list_empty(&resource->resource_list)) ++ goto out; ++ ++ if (nla_put_u8(skb, DEVLINK_ATTR_RESOURCE_SIZE_VALID, ++ resource->size_valid)) ++ goto nla_put_failure; ++ ++ child_resource_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_RESOURCE_LIST); ++ if (!child_resource_attr) ++ goto nla_put_failure; ++ ++ list_for_each_entry(child_resource, &resource->resource_list, list) { ++ if (devlink_resource_put(devlink, skb, child_resource)) ++ goto resource_put_failure; ++ } ++ ++ nla_nest_end(skb, child_resource_attr); ++out: ++ nla_nest_end(skb, resource_attr); ++ return 0; ++ ++resource_put_failure: ++ nla_nest_cancel(skb, child_resource_attr); ++nla_put_failure: ++ nla_nest_cancel(skb, resource_attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_resource_fill(struct genl_info *info, ++ enum devlink_command cmd, int flags) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_resource *resource; ++ struct nlattr *resources_attr; ++ struct sk_buff *skb = NULL; ++ struct nlmsghdr *nlh; ++ bool incomplete; ++ void *hdr; ++ int i; ++ int err; ++ ++ resource = list_first_entry(&devlink->resource_list, ++ struct devlink_resource, list); ++start_again: ++ err = devlink_dpipe_send_and_alloc_skb(&skb, info); ++ if (err) ++ return err; ++ ++ hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, ++ &devlink_nl_family, NLM_F_MULTI, cmd); ++ if (!hdr) { ++ nlmsg_free(skb); ++ return -EMSGSIZE; ++ } ++ ++ if (devlink_nl_put_handle(skb, devlink)) ++ goto nla_put_failure; ++ ++ resources_attr = nla_nest_start_noflag(skb, ++ DEVLINK_ATTR_RESOURCE_LIST); ++ if (!resources_attr) ++ goto nla_put_failure; ++ ++ incomplete = false; ++ i = 0; ++ list_for_each_entry_from(resource, &devlink->resource_list, list) { ++ err = devlink_resource_put(devlink, skb, resource); ++ if (err) { ++ if (!i) ++ goto err_resource_put; ++ incomplete = true; ++ break; ++ } ++ i++; ++ } ++ nla_nest_end(skb, resources_attr); ++ genlmsg_end(skb, hdr); ++ if (incomplete) ++ goto start_again; ++send_done: ++ nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, ++ NLMSG_DONE, 0, flags | NLM_F_MULTI); ++ if (!nlh) { ++ err = devlink_dpipe_send_and_alloc_skb(&skb, info); ++ if (err) ++ return err; ++ goto send_done; ++ } ++ return genlmsg_reply(skb, info); ++ ++nla_put_failure: ++ err = -EMSGSIZE; ++err_resource_put: ++ nlmsg_free(skb); ++ return err; ++} ++ ++static int devlink_nl_cmd_resource_dump(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ ++ if (list_empty(&devlink->resource_list)) ++ return -EOPNOTSUPP; ++ ++ return devlink_resource_fill(info, DEVLINK_CMD_RESOURCE_DUMP, 0); ++} ++ ++static int ++devlink_resources_validate(struct devlink *devlink, ++ struct devlink_resource *resource, ++ struct genl_info *info) ++{ ++ struct list_head *resource_list; ++ int err = 0; ++ ++ if (resource) ++ resource_list = &resource->resource_list; ++ else ++ resource_list = &devlink->resource_list; ++ ++ list_for_each_entry(resource, resource_list, list) { ++ if (!resource->size_valid) ++ return -EINVAL; ++ err = devlink_resources_validate(devlink, resource, info); ++ if (err) ++ return err; ++ } ++ return err; ++} ++ ++static struct net *devlink_netns_get(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct nlattr *netns_pid_attr = info->attrs[DEVLINK_ATTR_NETNS_PID]; ++ struct nlattr *netns_fd_attr = info->attrs[DEVLINK_ATTR_NETNS_FD]; ++ struct nlattr *netns_id_attr = info->attrs[DEVLINK_ATTR_NETNS_ID]; ++ struct net *net; ++ ++ if (!!netns_pid_attr + !!netns_fd_attr + !!netns_id_attr > 1) { ++ NL_SET_ERR_MSG_MOD(info->extack, "multiple netns identifying attributes specified"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ if (netns_pid_attr) { ++ net = get_net_ns_by_pid(nla_get_u32(netns_pid_attr)); ++ } else if (netns_fd_attr) { ++ net = get_net_ns_by_fd(nla_get_u32(netns_fd_attr)); ++ } else if (netns_id_attr) { ++ net = get_net_ns_by_id(sock_net(skb->sk), ++ nla_get_u32(netns_id_attr)); ++ if (!net) ++ net = ERR_PTR(-EINVAL); ++ } else { ++ WARN_ON(1); ++ net = ERR_PTR(-EINVAL); ++ } ++ if (IS_ERR(net)) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Unknown network namespace"); ++ return ERR_PTR(-EINVAL); ++ } ++ if (!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN)) { ++ put_net(net); ++ return ERR_PTR(-EPERM); ++ } ++ return net; ++} ++ ++static void devlink_param_notify(struct devlink *devlink, ++ unsigned int port_index, ++ struct devlink_param_item *param_item, ++ enum devlink_command cmd); ++ ++static void devlink_ns_change_notify(struct devlink *devlink, ++ struct net *dest_net, struct net *curr_net, ++ bool new) ++{ ++ struct devlink_param_item *param_item; ++ enum devlink_command cmd; ++ ++ /* Userspace needs to be notified about devlink objects ++ * removed from original and entering new network namespace. ++ * The rest of the devlink objects are re-created during ++ * reload process so the notifications are generated separatelly. ++ */ ++ ++ if (!dest_net || net_eq(dest_net, curr_net)) ++ return; ++ ++ if (new) ++ devlink_notify(devlink, DEVLINK_CMD_NEW); ++ ++ cmd = new ? DEVLINK_CMD_PARAM_NEW : DEVLINK_CMD_PARAM_DEL; ++ list_for_each_entry(param_item, &devlink->param_list, list) ++ devlink_param_notify(devlink, 0, param_item, cmd); ++ ++ if (!new) ++ devlink_notify(devlink, DEVLINK_CMD_DEL); ++} ++ ++static bool devlink_reload_supported(const struct devlink_ops *ops) ++{ ++ return ops->reload_down && ops->reload_up; ++} ++ ++static void devlink_reload_failed_set(struct devlink *devlink, ++ bool reload_failed) ++{ ++ if (devlink->reload_failed == reload_failed) ++ return; ++ devlink->reload_failed = reload_failed; ++ devlink_notify(devlink, DEVLINK_CMD_NEW); ++} ++ ++bool devlink_is_reload_failed(const struct devlink *devlink) ++{ ++ return devlink->reload_failed; ++} ++EXPORT_SYMBOL_GPL(devlink_is_reload_failed); ++ ++static void ++__devlink_reload_stats_update(struct devlink *devlink, u32 *reload_stats, ++ enum devlink_reload_limit limit, u32 actions_performed) ++{ ++ unsigned long actions = actions_performed; ++ int stat_idx; ++ int action; ++ ++ for_each_set_bit(action, &actions, __DEVLINK_RELOAD_ACTION_MAX) { ++ stat_idx = limit * __DEVLINK_RELOAD_ACTION_MAX + action; ++ reload_stats[stat_idx]++; ++ } ++ devlink_notify(devlink, DEVLINK_CMD_NEW); ++} ++ ++static void ++devlink_reload_stats_update(struct devlink *devlink, enum devlink_reload_limit limit, ++ u32 actions_performed) ++{ ++ __devlink_reload_stats_update(devlink, devlink->stats.reload_stats, limit, ++ actions_performed); ++} ++ ++/** ++ * devlink_remote_reload_actions_performed - Update devlink on reload actions ++ * performed which are not a direct result of devlink reload call. ++ * ++ * This should be called by a driver after performing reload actions in case it was not ++ * a result of devlink reload call. For example fw_activate was performed as a result ++ * of devlink reload triggered fw_activate on another host. ++ * The motivation for this function is to keep data on reload actions performed on this ++ * function whether it was done due to direct devlink reload call or not. ++ * ++ * @devlink: devlink ++ * @limit: reload limit ++ * @actions_performed: bitmask of actions performed ++ */ ++void devlink_remote_reload_actions_performed(struct devlink *devlink, ++ enum devlink_reload_limit limit, ++ u32 actions_performed) ++{ ++ if (WARN_ON(!actions_performed || ++ actions_performed & BIT(DEVLINK_RELOAD_ACTION_UNSPEC) || ++ actions_performed >= BIT(__DEVLINK_RELOAD_ACTION_MAX) || ++ limit > DEVLINK_RELOAD_LIMIT_MAX)) ++ return; ++ ++ __devlink_reload_stats_update(devlink, devlink->stats.remote_reload_stats, limit, ++ actions_performed); ++} ++EXPORT_SYMBOL_GPL(devlink_remote_reload_actions_performed); ++ ++static int devlink_reload(struct devlink *devlink, struct net *dest_net, ++ enum devlink_reload_action action, enum devlink_reload_limit limit, ++ u32 *actions_performed, struct netlink_ext_ack *extack) ++{ ++ u32 remote_reload_stats[DEVLINK_RELOAD_STATS_ARRAY_SIZE]; ++ struct net *curr_net; ++ int err; ++ ++ memcpy(remote_reload_stats, devlink->stats.remote_reload_stats, ++ sizeof(remote_reload_stats)); ++ ++ curr_net = devlink_net(devlink); ++ devlink_ns_change_notify(devlink, dest_net, curr_net, false); ++ err = devlink->ops->reload_down(devlink, !!dest_net, action, limit, extack); ++ if (err) ++ return err; ++ ++ if (dest_net && !net_eq(dest_net, curr_net)) ++ write_pnet(&devlink->_net, dest_net); ++ ++ err = devlink->ops->reload_up(devlink, action, limit, actions_performed, extack); ++ devlink_reload_failed_set(devlink, !!err); ++ if (err) ++ return err; ++ ++ devlink_ns_change_notify(devlink, dest_net, curr_net, true); ++ WARN_ON(!(*actions_performed & BIT(action))); ++ /* Catch driver on updating the remote action within devlink reload */ ++ WARN_ON(memcmp(remote_reload_stats, devlink->stats.remote_reload_stats, ++ sizeof(remote_reload_stats))); ++ devlink_reload_stats_update(devlink, limit, *actions_performed); ++ return 0; ++} ++ ++static int ++devlink_nl_reload_actions_performed_snd(struct devlink *devlink, u32 actions_performed, ++ enum devlink_command cmd, struct genl_info *info) ++{ ++ struct sk_buff *msg; ++ void *hdr; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &devlink_nl_family, 0, cmd); ++ if (!hdr) ++ goto free_msg; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ ++ if (nla_put_bitfield32(msg, DEVLINK_ATTR_RELOAD_ACTIONS_PERFORMED, actions_performed, ++ actions_performed)) ++ goto nla_put_failure; ++ genlmsg_end(msg, hdr); ++ ++ return genlmsg_reply(msg, info); ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++free_msg: ++ nlmsg_free(msg); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_reload(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ enum devlink_reload_action action; ++ enum devlink_reload_limit limit; ++ struct net *dest_net = NULL; ++ u32 actions_performed; ++ int err; ++ ++ if (!(devlink->features & DEVLINK_F_RELOAD)) ++ return -EOPNOTSUPP; ++ ++ err = devlink_resources_validate(devlink, NULL, info); ++ if (err) { ++ NL_SET_ERR_MSG_MOD(info->extack, "resources size validation failed"); ++ return err; ++ } ++ ++ if (info->attrs[DEVLINK_ATTR_RELOAD_ACTION]) ++ action = nla_get_u8(info->attrs[DEVLINK_ATTR_RELOAD_ACTION]); ++ else ++ action = DEVLINK_RELOAD_ACTION_DRIVER_REINIT; ++ ++ if (!devlink_reload_action_is_supported(devlink, action)) { ++ NL_SET_ERR_MSG_MOD(info->extack, ++ "Requested reload action is not supported by the driver"); ++ return -EOPNOTSUPP; ++ } ++ ++ limit = DEVLINK_RELOAD_LIMIT_UNSPEC; ++ if (info->attrs[DEVLINK_ATTR_RELOAD_LIMITS]) { ++ struct nla_bitfield32 limits; ++ u32 limits_selected; ++ ++ limits = nla_get_bitfield32(info->attrs[DEVLINK_ATTR_RELOAD_LIMITS]); ++ limits_selected = limits.value & limits.selector; ++ if (!limits_selected) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Invalid limit selected"); ++ return -EINVAL; ++ } ++ for (limit = 0 ; limit <= DEVLINK_RELOAD_LIMIT_MAX ; limit++) ++ if (limits_selected & BIT(limit)) ++ break; ++ /* UAPI enables multiselection, but currently it is not used */ ++ if (limits_selected != BIT(limit)) { ++ NL_SET_ERR_MSG_MOD(info->extack, ++ "Multiselection of limit is not supported"); ++ return -EOPNOTSUPP; ++ } ++ if (!devlink_reload_limit_is_supported(devlink, limit)) { ++ NL_SET_ERR_MSG_MOD(info->extack, ++ "Requested limit is not supported by the driver"); ++ return -EOPNOTSUPP; ++ } ++ if (devlink_reload_combination_is_invalid(action, limit)) { ++ NL_SET_ERR_MSG_MOD(info->extack, ++ "Requested limit is invalid for this action"); ++ return -EINVAL; ++ } ++ } ++ if (info->attrs[DEVLINK_ATTR_NETNS_PID] || ++ info->attrs[DEVLINK_ATTR_NETNS_FD] || ++ info->attrs[DEVLINK_ATTR_NETNS_ID]) { ++ dest_net = devlink_netns_get(skb, info); ++ if (IS_ERR(dest_net)) ++ return PTR_ERR(dest_net); ++ } ++ ++ err = devlink_reload(devlink, dest_net, action, limit, &actions_performed, info->extack); ++ ++ if (dest_net) ++ put_net(dest_net); ++ ++ if (err) ++ return err; ++ /* For backward compatibility generate reply only if attributes used by user */ ++ if (!info->attrs[DEVLINK_ATTR_RELOAD_ACTION] && !info->attrs[DEVLINK_ATTR_RELOAD_LIMITS]) ++ return 0; ++ ++ return devlink_nl_reload_actions_performed_snd(devlink, actions_performed, ++ DEVLINK_CMD_RELOAD, info); ++} ++ ++static int devlink_nl_flash_update_fill(struct sk_buff *msg, ++ struct devlink *devlink, ++ enum devlink_command cmd, ++ struct devlink_flash_notify *params) ++{ ++ void *hdr; ++ ++ hdr = genlmsg_put(msg, 0, 0, &devlink_nl_family, 0, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ ++ if (cmd != DEVLINK_CMD_FLASH_UPDATE_STATUS) ++ goto out; ++ ++ if (params->status_msg && ++ nla_put_string(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG, ++ params->status_msg)) ++ goto nla_put_failure; ++ if (params->component && ++ nla_put_string(msg, DEVLINK_ATTR_FLASH_UPDATE_COMPONENT, ++ params->component)) ++ goto nla_put_failure; ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE, ++ params->done, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL, ++ params->total, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_FLASH_UPDATE_STATUS_TIMEOUT, ++ params->timeout, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++out: ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static void __devlink_flash_update_notify(struct devlink *devlink, ++ enum devlink_command cmd, ++ struct devlink_flash_notify *params) ++{ ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON(cmd != DEVLINK_CMD_FLASH_UPDATE && ++ cmd != DEVLINK_CMD_FLASH_UPDATE_END && ++ cmd != DEVLINK_CMD_FLASH_UPDATE_STATUS); ++ ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_flash_update_fill(msg, devlink, cmd, params); ++ if (err) ++ goto out_free_msg; ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), ++ msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++ return; ++ ++out_free_msg: ++ nlmsg_free(msg); ++} ++ ++static void devlink_flash_update_begin_notify(struct devlink *devlink) ++{ ++ struct devlink_flash_notify params = {}; ++ ++ __devlink_flash_update_notify(devlink, ++ DEVLINK_CMD_FLASH_UPDATE, ++ ¶ms); ++} ++ ++static void devlink_flash_update_end_notify(struct devlink *devlink) ++{ ++ struct devlink_flash_notify params = {}; ++ ++ __devlink_flash_update_notify(devlink, ++ DEVLINK_CMD_FLASH_UPDATE_END, ++ ¶ms); ++} ++ ++void devlink_flash_update_status_notify(struct devlink *devlink, ++ const char *status_msg, ++ const char *component, ++ unsigned long done, ++ unsigned long total) ++{ ++ struct devlink_flash_notify params = { ++ .status_msg = status_msg, ++ .component = component, ++ .done = done, ++ .total = total, ++ }; ++ ++ __devlink_flash_update_notify(devlink, ++ DEVLINK_CMD_FLASH_UPDATE_STATUS, ++ ¶ms); ++} ++EXPORT_SYMBOL_GPL(devlink_flash_update_status_notify); ++ ++void devlink_flash_update_timeout_notify(struct devlink *devlink, ++ const char *status_msg, ++ const char *component, ++ unsigned long timeout) ++{ ++ struct devlink_flash_notify params = { ++ .status_msg = status_msg, ++ .component = component, ++ .timeout = timeout, ++ }; ++ ++ __devlink_flash_update_notify(devlink, ++ DEVLINK_CMD_FLASH_UPDATE_STATUS, ++ ¶ms); ++} ++EXPORT_SYMBOL_GPL(devlink_flash_update_timeout_notify); ++ ++struct devlink_info_req { ++ struct sk_buff *msg; ++ void (*version_cb)(const char *version_name, ++ enum devlink_info_version_type version_type, ++ void *version_cb_priv); ++ void *version_cb_priv; ++}; ++ ++struct devlink_flash_component_lookup_ctx { ++ const char *lookup_name; ++ bool lookup_name_found; ++}; ++ ++static void ++devlink_flash_component_lookup_cb(const char *version_name, ++ enum devlink_info_version_type version_type, ++ void *version_cb_priv) ++{ ++ struct devlink_flash_component_lookup_ctx *lookup_ctx = version_cb_priv; ++ ++ if (version_type != DEVLINK_INFO_VERSION_TYPE_COMPONENT || ++ lookup_ctx->lookup_name_found) ++ return; ++ ++ lookup_ctx->lookup_name_found = ++ !strcmp(lookup_ctx->lookup_name, version_name); ++} ++ ++static int devlink_flash_component_get(struct devlink *devlink, ++ struct nlattr *nla_component, ++ const char **p_component, ++ struct netlink_ext_ack *extack) ++{ ++ struct devlink_flash_component_lookup_ctx lookup_ctx = {}; ++ struct devlink_info_req req = {}; ++ const char *component; ++ int ret; ++ ++ if (!nla_component) ++ return 0; ++ ++ component = nla_data(nla_component); ++ ++ if (!devlink->ops->info_get) { ++ NL_SET_ERR_MSG_ATTR(extack, nla_component, ++ "component update is not supported by this device"); ++ return -EOPNOTSUPP; ++ } ++ ++ lookup_ctx.lookup_name = component; ++ req.version_cb = devlink_flash_component_lookup_cb; ++ req.version_cb_priv = &lookup_ctx; ++ ++ ret = devlink->ops->info_get(devlink, &req, NULL); ++ if (ret) ++ return ret; ++ ++ if (!lookup_ctx.lookup_name_found) { ++ NL_SET_ERR_MSG_ATTR(extack, nla_component, ++ "selected component is not supported by this device"); ++ return -EINVAL; ++ } ++ *p_component = component; ++ return 0; ++} ++ ++static int devlink_nl_cmd_flash_update(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]; ++ const char *file_name; ++ u32 supported_params; ++ int ret; ++ ++ if (!devlink->ops->flash_update) ++ return -EOPNOTSUPP; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME)) ++ return -EINVAL; ++ ++ ret = devlink_flash_component_get(devlink, ++ info->attrs[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT], ++ ¶ms.component, info->extack); ++ if (ret) ++ return ret; ++ ++ supported_params = devlink->ops->supported_flash_update_params; ++ ++ nla_overwrite_mask = info->attrs[DEVLINK_ATTR_FLASH_UPDATE_OVERWRITE_MASK]; ++ if (nla_overwrite_mask) { ++ struct nla_bitfield32 sections; ++ ++ if (!(supported_params & DEVLINK_SUPPORT_FLASH_UPDATE_OVERWRITE_MASK)) { ++ NL_SET_ERR_MSG_ATTR(info->extack, nla_overwrite_mask, ++ "overwrite settings are not supported by this device"); ++ return -EOPNOTSUPP; ++ } ++ sections = nla_get_bitfield32(nla_overwrite_mask); ++ params.overwrite_mask = sections.value & sections.selector; ++ } ++ ++ nla_file_name = info->attrs[DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME]; ++ file_name = nla_data(nla_file_name); ++ ret = request_firmware(¶ms.fw, file_name, devlink->dev); ++ if (ret) { ++ NL_SET_ERR_MSG_ATTR(info->extack, nla_file_name, "failed to locate the requested firmware file"); ++ return ret; ++ } ++ ++ devlink_flash_update_begin_notify(devlink); ++ ret = devlink->ops->flash_update(devlink, ¶ms, info->extack); ++ devlink_flash_update_end_notify(devlink); ++ ++ release_firmware(params.fw); ++ ++ return ret; ++} ++ ++static int ++devlink_nl_selftests_fill(struct sk_buff *msg, struct devlink *devlink, ++ u32 portid, u32 seq, int flags, ++ struct netlink_ext_ack *extack) ++{ ++ struct nlattr *selftests; ++ void *hdr; ++ int err; ++ int i; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, ++ DEVLINK_CMD_SELFTESTS_GET); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ err = -EMSGSIZE; ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto err_cancel_msg; ++ ++ selftests = nla_nest_start(msg, DEVLINK_ATTR_SELFTESTS); ++ if (!selftests) ++ goto err_cancel_msg; ++ ++ for (i = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1; ++ i <= DEVLINK_ATTR_SELFTEST_ID_MAX; i++) { ++ if (devlink->ops->selftest_check(devlink, i, extack)) { ++ err = nla_put_flag(msg, i); ++ if (err) ++ goto err_cancel_msg; ++ } ++ } ++ ++ nla_nest_end(msg, selftests); ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++err_cancel_msg: ++ genlmsg_cancel(msg, hdr); ++ return err; ++} ++ ++static int devlink_nl_cmd_selftests_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct sk_buff *msg; ++ int err; ++ ++ if (!devlink->ops->selftest_check) ++ return -EOPNOTSUPP; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_selftests_fill(msg, devlink, info->snd_portid, ++ info->snd_seq, 0, info->extack); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_nl_cmd_selftests_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ if (idx < start || !devlink->ops->selftest_check) ++ goto inc; ++ ++ devl_lock(devlink); ++ err = devlink_nl_selftests_fill(msg, devlink, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, NLM_F_MULTI, ++ cb->extack); ++ devl_unlock(devlink); ++ if (err) { ++ devlink_put(devlink); ++ break; ++ } ++inc: ++ idx++; ++ devlink_put(devlink); ++ } ++ ++ if (err != -EMSGSIZE) ++ return err; ++ ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_selftest_result_put(struct sk_buff *skb, unsigned int id, ++ enum devlink_selftest_status test_status) ++{ ++ struct nlattr *result_attr; ++ ++ result_attr = nla_nest_start(skb, DEVLINK_ATTR_SELFTEST_RESULT); ++ if (!result_attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_u32(skb, DEVLINK_ATTR_SELFTEST_RESULT_ID, id) || ++ nla_put_u8(skb, DEVLINK_ATTR_SELFTEST_RESULT_STATUS, ++ test_status)) ++ goto nla_put_failure; ++ ++ nla_nest_end(skb, result_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(skb, result_attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_selftests_run(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 nlattr *attrs, *selftests; ++ struct sk_buff *msg; ++ void *hdr; ++ int err; ++ int i; ++ ++ if (!devlink->ops->selftest_run || !devlink->ops->selftest_check) ++ return -EOPNOTSUPP; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_SELFTESTS)) ++ return -EINVAL; ++ ++ attrs = info->attrs[DEVLINK_ATTR_SELFTESTS]; ++ ++ err = nla_parse_nested(tb, DEVLINK_ATTR_SELFTEST_ID_MAX, attrs, ++ devlink_selftest_nl_policy, info->extack); ++ if (err < 0) ++ return err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = -EMSGSIZE; ++ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, ++ &devlink_nl_family, 0, DEVLINK_CMD_SELFTESTS_RUN); ++ if (!hdr) ++ goto free_msg; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto genlmsg_cancel; ++ ++ selftests = nla_nest_start(msg, DEVLINK_ATTR_SELFTESTS); ++ if (!selftests) ++ goto genlmsg_cancel; ++ ++ for (i = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1; ++ i <= DEVLINK_ATTR_SELFTEST_ID_MAX; i++) { ++ enum devlink_selftest_status test_status; ++ ++ if (nla_get_flag(tb[i])) { ++ if (!devlink->ops->selftest_check(devlink, i, ++ info->extack)) { ++ if (devlink_selftest_result_put(msg, i, ++ DEVLINK_SELFTEST_STATUS_SKIP)) ++ goto selftests_nest_cancel; ++ continue; ++ } ++ ++ test_status = devlink->ops->selftest_run(devlink, i, ++ info->extack); ++ if (devlink_selftest_result_put(msg, i, test_status)) ++ goto selftests_nest_cancel; ++ } ++ } ++ ++ nla_nest_end(msg, selftests); ++ genlmsg_end(msg, hdr); ++ return genlmsg_reply(msg, info); ++ ++selftests_nest_cancel: ++ nla_nest_cancel(msg, selftests); ++genlmsg_cancel: ++ genlmsg_cancel(msg, hdr); ++free_msg: ++ nlmsg_free(msg); ++ return err; ++} ++ ++static const struct devlink_param devlink_param_generic[] = { ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_INT_ERR_RESET, ++ .name = DEVLINK_PARAM_GENERIC_INT_ERR_RESET_NAME, ++ .type = DEVLINK_PARAM_GENERIC_INT_ERR_RESET_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_MAX_MACS, ++ .name = DEVLINK_PARAM_GENERIC_MAX_MACS_NAME, ++ .type = DEVLINK_PARAM_GENERIC_MAX_MACS_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_SRIOV, ++ .name = DEVLINK_PARAM_GENERIC_ENABLE_SRIOV_NAME, ++ .type = DEVLINK_PARAM_GENERIC_ENABLE_SRIOV_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_REGION_SNAPSHOT, ++ .name = DEVLINK_PARAM_GENERIC_REGION_SNAPSHOT_NAME, ++ .type = DEVLINK_PARAM_GENERIC_REGION_SNAPSHOT_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_IGNORE_ARI, ++ .name = DEVLINK_PARAM_GENERIC_IGNORE_ARI_NAME, ++ .type = DEVLINK_PARAM_GENERIC_IGNORE_ARI_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_MSIX_VEC_PER_PF_MAX, ++ .name = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MAX_NAME, ++ .type = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MAX_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_MSIX_VEC_PER_PF_MIN, ++ .name = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MIN_NAME, ++ .type = DEVLINK_PARAM_GENERIC_MSIX_VEC_PER_PF_MIN_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_FW_LOAD_POLICY, ++ .name = DEVLINK_PARAM_GENERIC_FW_LOAD_POLICY_NAME, ++ .type = DEVLINK_PARAM_GENERIC_FW_LOAD_POLICY_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_RESET_DEV_ON_DRV_PROBE, ++ .name = DEVLINK_PARAM_GENERIC_RESET_DEV_ON_DRV_PROBE_NAME, ++ .type = DEVLINK_PARAM_GENERIC_RESET_DEV_ON_DRV_PROBE_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_ROCE, ++ .name = DEVLINK_PARAM_GENERIC_ENABLE_ROCE_NAME, ++ .type = DEVLINK_PARAM_GENERIC_ENABLE_ROCE_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_REMOTE_DEV_RESET, ++ .name = DEVLINK_PARAM_GENERIC_ENABLE_REMOTE_DEV_RESET_NAME, ++ .type = DEVLINK_PARAM_GENERIC_ENABLE_REMOTE_DEV_RESET_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_ETH, ++ .name = DEVLINK_PARAM_GENERIC_ENABLE_ETH_NAME, ++ .type = DEVLINK_PARAM_GENERIC_ENABLE_ETH_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_RDMA, ++ .name = DEVLINK_PARAM_GENERIC_ENABLE_RDMA_NAME, ++ .type = DEVLINK_PARAM_GENERIC_ENABLE_RDMA_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_VNET, ++ .name = DEVLINK_PARAM_GENERIC_ENABLE_VNET_NAME, ++ .type = DEVLINK_PARAM_GENERIC_ENABLE_VNET_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_ENABLE_IWARP, ++ .name = DEVLINK_PARAM_GENERIC_ENABLE_IWARP_NAME, ++ .type = DEVLINK_PARAM_GENERIC_ENABLE_IWARP_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_IO_EQ_SIZE, ++ .name = DEVLINK_PARAM_GENERIC_IO_EQ_SIZE_NAME, ++ .type = DEVLINK_PARAM_GENERIC_IO_EQ_SIZE_TYPE, ++ }, ++ { ++ .id = DEVLINK_PARAM_GENERIC_ID_EVENT_EQ_SIZE, ++ .name = DEVLINK_PARAM_GENERIC_EVENT_EQ_SIZE_NAME, ++ .type = DEVLINK_PARAM_GENERIC_EVENT_EQ_SIZE_TYPE, ++ }, ++}; ++ ++static int devlink_param_generic_verify(const struct devlink_param *param) ++{ ++ /* verify it match generic parameter by id and name */ ++ if (param->id > DEVLINK_PARAM_GENERIC_ID_MAX) ++ return -EINVAL; ++ if (strcmp(param->name, devlink_param_generic[param->id].name)) ++ return -ENOENT; ++ ++ WARN_ON(param->type != devlink_param_generic[param->id].type); ++ ++ return 0; ++} ++ ++static int devlink_param_driver_verify(const struct devlink_param *param) ++{ ++ int i; ++ ++ if (param->id <= DEVLINK_PARAM_GENERIC_ID_MAX) ++ return -EINVAL; ++ /* verify no such name in generic params */ ++ for (i = 0; i <= DEVLINK_PARAM_GENERIC_ID_MAX; i++) ++ if (!strcmp(param->name, devlink_param_generic[i].name)) ++ return -EEXIST; ++ ++ return 0; ++} ++ ++static struct devlink_param_item * ++devlink_param_find_by_name(struct list_head *param_list, ++ const char *param_name) ++{ ++ struct devlink_param_item *param_item; ++ ++ list_for_each_entry(param_item, param_list, list) ++ if (!strcmp(param_item->param->name, param_name)) ++ return param_item; ++ return NULL; ++} ++ ++static struct devlink_param_item * ++devlink_param_find_by_id(struct list_head *param_list, u32 param_id) ++{ ++ struct devlink_param_item *param_item; ++ ++ list_for_each_entry(param_item, param_list, list) ++ if (param_item->param->id == param_id) ++ return param_item; ++ return NULL; ++} ++ ++static bool ++devlink_param_cmode_is_supported(const struct devlink_param *param, ++ enum devlink_param_cmode cmode) ++{ ++ return test_bit(cmode, ¶m->supported_cmodes); ++} ++ ++static int devlink_param_get(struct devlink *devlink, ++ const struct devlink_param *param, ++ struct devlink_param_gset_ctx *ctx) ++{ ++ if (!param->get || devlink->reload_failed) ++ return -EOPNOTSUPP; ++ return param->get(devlink, param->id, ctx); ++} ++ ++static int devlink_param_set(struct devlink *devlink, ++ const struct devlink_param *param, ++ struct devlink_param_gset_ctx *ctx) ++{ ++ if (!param->set || devlink->reload_failed) ++ return -EOPNOTSUPP; ++ return param->set(devlink, param->id, ctx); ++} ++ ++static int ++devlink_param_type_to_nla_type(enum devlink_param_type param_type) ++{ ++ switch (param_type) { ++ case DEVLINK_PARAM_TYPE_U8: ++ return NLA_U8; ++ case DEVLINK_PARAM_TYPE_U16: ++ return NLA_U16; ++ case DEVLINK_PARAM_TYPE_U32: ++ return NLA_U32; ++ case DEVLINK_PARAM_TYPE_STRING: ++ return NLA_STRING; ++ case DEVLINK_PARAM_TYPE_BOOL: ++ return NLA_FLAG; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int ++devlink_nl_param_value_fill_one(struct sk_buff *msg, ++ enum devlink_param_type type, ++ enum devlink_param_cmode cmode, ++ union devlink_param_value val) ++{ ++ struct nlattr *param_value_attr; ++ ++ param_value_attr = nla_nest_start_noflag(msg, ++ DEVLINK_ATTR_PARAM_VALUE); ++ if (!param_value_attr) ++ goto nla_put_failure; ++ ++ if (nla_put_u8(msg, DEVLINK_ATTR_PARAM_VALUE_CMODE, cmode)) ++ goto value_nest_cancel; ++ ++ switch (type) { ++ case DEVLINK_PARAM_TYPE_U8: ++ if (nla_put_u8(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, val.vu8)) ++ goto value_nest_cancel; ++ break; ++ case DEVLINK_PARAM_TYPE_U16: ++ if (nla_put_u16(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, val.vu16)) ++ goto value_nest_cancel; ++ break; ++ case DEVLINK_PARAM_TYPE_U32: ++ if (nla_put_u32(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, val.vu32)) ++ goto value_nest_cancel; ++ break; ++ case DEVLINK_PARAM_TYPE_STRING: ++ if (nla_put_string(msg, DEVLINK_ATTR_PARAM_VALUE_DATA, ++ val.vstr)) ++ goto value_nest_cancel; ++ break; ++ case DEVLINK_PARAM_TYPE_BOOL: ++ if (val.vbool && ++ nla_put_flag(msg, DEVLINK_ATTR_PARAM_VALUE_DATA)) ++ goto value_nest_cancel; ++ break; ++ } ++ ++ nla_nest_end(msg, param_value_attr); ++ return 0; ++ ++value_nest_cancel: ++ nla_nest_cancel(msg, param_value_attr); ++nla_put_failure: ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink, ++ unsigned int port_index, ++ struct devlink_param_item *param_item, ++ enum devlink_command cmd, ++ u32 portid, u32 seq, int flags) ++{ ++ union devlink_param_value param_value[DEVLINK_PARAM_CMODE_MAX + 1]; ++ bool param_value_set[DEVLINK_PARAM_CMODE_MAX + 1] = {}; ++ const struct devlink_param *param = param_item->param; ++ struct devlink_param_gset_ctx ctx; ++ struct nlattr *param_values_list; ++ struct nlattr *param_attr; ++ int nla_type; ++ void *hdr; ++ int err; ++ int i; ++ ++ /* Get value from driver part to driverinit configuration mode */ ++ for (i = 0; i <= DEVLINK_PARAM_CMODE_MAX; i++) { ++ if (!devlink_param_cmode_is_supported(param, i)) ++ continue; ++ if (i == DEVLINK_PARAM_CMODE_DRIVERINIT) { ++ if (!param_item->driverinit_value_valid) ++ return -EOPNOTSUPP; ++ param_value[i] = param_item->driverinit_value; ++ } else { ++ ctx.cmode = i; ++ err = devlink_param_get(devlink, param, &ctx); ++ if (err) ++ return err; ++ param_value[i] = ctx.val; ++ } ++ param_value_set[i] = true; ++ } ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto genlmsg_cancel; ++ ++ if (cmd == DEVLINK_CMD_PORT_PARAM_GET || ++ cmd == DEVLINK_CMD_PORT_PARAM_NEW || ++ cmd == DEVLINK_CMD_PORT_PARAM_DEL) ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, port_index)) ++ goto genlmsg_cancel; ++ ++ param_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_PARAM); ++ if (!param_attr) ++ goto genlmsg_cancel; ++ if (nla_put_string(msg, DEVLINK_ATTR_PARAM_NAME, param->name)) ++ goto param_nest_cancel; ++ if (param->generic && nla_put_flag(msg, DEVLINK_ATTR_PARAM_GENERIC)) ++ goto param_nest_cancel; ++ ++ nla_type = devlink_param_type_to_nla_type(param->type); ++ if (nla_type < 0) ++ goto param_nest_cancel; ++ if (nla_put_u8(msg, DEVLINK_ATTR_PARAM_TYPE, nla_type)) ++ goto param_nest_cancel; ++ ++ param_values_list = nla_nest_start_noflag(msg, ++ DEVLINK_ATTR_PARAM_VALUES_LIST); ++ if (!param_values_list) ++ goto param_nest_cancel; ++ ++ for (i = 0; i <= DEVLINK_PARAM_CMODE_MAX; i++) { ++ if (!param_value_set[i]) ++ continue; ++ err = devlink_nl_param_value_fill_one(msg, param->type, ++ i, param_value[i]); ++ if (err) ++ goto values_list_nest_cancel; ++ } ++ ++ nla_nest_end(msg, param_values_list); ++ nla_nest_end(msg, param_attr); ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++values_list_nest_cancel: ++ nla_nest_end(msg, param_values_list); ++param_nest_cancel: ++ nla_nest_cancel(msg, param_attr); ++genlmsg_cancel: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static void devlink_param_notify(struct devlink *devlink, ++ unsigned int port_index, ++ struct devlink_param_item *param_item, ++ enum devlink_command cmd) ++{ ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON(cmd != DEVLINK_CMD_PARAM_NEW && cmd != DEVLINK_CMD_PARAM_DEL && ++ cmd != DEVLINK_CMD_PORT_PARAM_NEW && ++ cmd != DEVLINK_CMD_PORT_PARAM_DEL); ++ ASSERT_DEVLINK_REGISTERED(devlink); ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ err = devlink_nl_param_fill(msg, devlink, port_index, param_item, cmd, ++ 0, 0, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), ++ msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static int devlink_nl_cmd_param_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink_param_item *param_item; ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(param_item, &devlink->param_list, list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_param_fill(msg, devlink, 0, param_item, ++ DEVLINK_CMD_PARAM_GET, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI); ++ if (err == -EOPNOTSUPP) { ++ err = 0; ++ } else if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ if (err != -EMSGSIZE) ++ return err; ++ ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int ++devlink_param_type_get_from_info(struct genl_info *info, ++ enum devlink_param_type *param_type) ++{ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_TYPE)) ++ return -EINVAL; ++ ++ switch (nla_get_u8(info->attrs[DEVLINK_ATTR_PARAM_TYPE])) { ++ case NLA_U8: ++ *param_type = DEVLINK_PARAM_TYPE_U8; ++ break; ++ case NLA_U16: ++ *param_type = DEVLINK_PARAM_TYPE_U16; ++ break; ++ case NLA_U32: ++ *param_type = DEVLINK_PARAM_TYPE_U32; ++ break; ++ case NLA_STRING: ++ *param_type = DEVLINK_PARAM_TYPE_STRING; ++ break; ++ case NLA_FLAG: ++ *param_type = DEVLINK_PARAM_TYPE_BOOL; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int ++devlink_param_value_get_from_info(const struct devlink_param *param, ++ struct genl_info *info, ++ union devlink_param_value *value) ++{ ++ struct nlattr *param_data; ++ int len; ++ ++ param_data = info->attrs[DEVLINK_ATTR_PARAM_VALUE_DATA]; ++ ++ if (param->type != DEVLINK_PARAM_TYPE_BOOL && !param_data) ++ return -EINVAL; ++ ++ switch (param->type) { ++ case DEVLINK_PARAM_TYPE_U8: ++ if (nla_len(param_data) != sizeof(u8)) ++ return -EINVAL; ++ value->vu8 = nla_get_u8(param_data); ++ break; ++ case DEVLINK_PARAM_TYPE_U16: ++ if (nla_len(param_data) != sizeof(u16)) ++ return -EINVAL; ++ value->vu16 = nla_get_u16(param_data); ++ break; ++ case DEVLINK_PARAM_TYPE_U32: ++ if (nla_len(param_data) != sizeof(u32)) ++ return -EINVAL; ++ value->vu32 = nla_get_u32(param_data); ++ break; ++ case DEVLINK_PARAM_TYPE_STRING: ++ len = strnlen(nla_data(param_data), nla_len(param_data)); ++ if (len == nla_len(param_data) || ++ len >= __DEVLINK_PARAM_MAX_STRING_VALUE) ++ return -EINVAL; ++ strcpy(value->vstr, nla_data(param_data)); ++ break; ++ case DEVLINK_PARAM_TYPE_BOOL: ++ if (param_data && nla_len(param_data)) ++ return -EINVAL; ++ value->vbool = nla_get_flag(param_data); ++ break; ++ } ++ return 0; ++} ++ ++static struct devlink_param_item * ++devlink_param_get_from_info(struct list_head *param_list, ++ struct genl_info *info) ++{ ++ char *param_name; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_NAME)) ++ return NULL; ++ ++ param_name = nla_data(info->attrs[DEVLINK_ATTR_PARAM_NAME]); ++ return devlink_param_find_by_name(param_list, param_name); ++} ++ ++static int devlink_nl_cmd_param_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_param_item *param_item; ++ struct sk_buff *msg; ++ int err; ++ ++ param_item = devlink_param_get_from_info(&devlink->param_list, info); ++ if (!param_item) ++ return -EINVAL; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_param_fill(msg, devlink, 0, param_item, ++ DEVLINK_CMD_PARAM_GET, ++ info->snd_portid, info->snd_seq, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, ++ unsigned int port_index, ++ struct list_head *param_list, ++ struct genl_info *info, ++ enum devlink_command cmd) ++{ ++ enum devlink_param_type param_type; ++ struct devlink_param_gset_ctx ctx; ++ enum devlink_param_cmode cmode; ++ struct devlink_param_item *param_item; ++ const struct devlink_param *param; ++ union devlink_param_value value; ++ int err = 0; ++ ++ param_item = devlink_param_get_from_info(param_list, info); ++ if (!param_item) ++ return -EINVAL; ++ param = param_item->param; ++ err = devlink_param_type_get_from_info(info, ¶m_type); ++ if (err) ++ return err; ++ if (param_type != param->type) ++ return -EINVAL; ++ err = devlink_param_value_get_from_info(param, info, &value); ++ if (err) ++ return err; ++ if (param->validate) { ++ err = param->validate(devlink, param->id, value, info->extack); ++ if (err) ++ return err; ++ } ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_VALUE_CMODE)) ++ return -EINVAL; ++ cmode = nla_get_u8(info->attrs[DEVLINK_ATTR_PARAM_VALUE_CMODE]); ++ if (!devlink_param_cmode_is_supported(param, cmode)) ++ return -EOPNOTSUPP; ++ ++ if (cmode == DEVLINK_PARAM_CMODE_DRIVERINIT) { ++ if (param->type == DEVLINK_PARAM_TYPE_STRING) ++ strcpy(param_item->driverinit_value.vstr, value.vstr); ++ else ++ param_item->driverinit_value = value; ++ param_item->driverinit_value_valid = true; ++ } else { ++ if (!param->set) ++ return -EOPNOTSUPP; ++ ctx.val = value; ++ ctx.cmode = cmode; ++ err = devlink_param_set(devlink, param, &ctx); ++ if (err) ++ return err; ++ } ++ ++ devlink_param_notify(devlink, port_index, param_item, cmd); ++ return 0; ++} ++ ++static int devlink_nl_cmd_param_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ ++ return __devlink_nl_cmd_param_set_doit(devlink, 0, &devlink->param_list, ++ info, DEVLINK_CMD_PARAM_NEW); ++} ++ ++static int devlink_nl_cmd_port_param_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ NL_SET_ERR_MSG_MOD(cb->extack, "Port params are not supported"); ++ return msg->len; ++} ++ ++static int devlink_nl_cmd_port_param_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ NL_SET_ERR_MSG_MOD(info->extack, "Port params are not supported"); ++ return -EINVAL; ++} ++ ++static int devlink_nl_cmd_port_param_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ NL_SET_ERR_MSG_MOD(info->extack, "Port params are not supported"); ++ return -EINVAL; ++} ++ ++static int devlink_nl_region_snapshot_id_put(struct sk_buff *msg, ++ struct devlink *devlink, ++ struct devlink_snapshot *snapshot) ++{ ++ struct nlattr *snap_attr; ++ int err; ++ ++ snap_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_REGION_SNAPSHOT); ++ if (!snap_attr) ++ return -EINVAL; ++ ++ err = nla_put_u32(msg, DEVLINK_ATTR_REGION_SNAPSHOT_ID, snapshot->id); ++ if (err) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, snap_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, snap_attr); ++ return err; ++} ++ ++static int devlink_nl_region_snapshots_id_put(struct sk_buff *msg, ++ struct devlink *devlink, ++ struct devlink_region *region) ++{ ++ struct devlink_snapshot *snapshot; ++ struct nlattr *snapshots_attr; ++ int err; ++ ++ snapshots_attr = nla_nest_start_noflag(msg, ++ DEVLINK_ATTR_REGION_SNAPSHOTS); ++ if (!snapshots_attr) ++ return -EINVAL; ++ ++ list_for_each_entry(snapshot, ®ion->snapshot_list, list) { ++ err = devlink_nl_region_snapshot_id_put(msg, devlink, snapshot); ++ if (err) ++ goto nla_put_failure; ++ } ++ ++ nla_nest_end(msg, snapshots_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, snapshots_attr); ++ return err; ++} ++ ++static int devlink_nl_region_fill(struct sk_buff *msg, struct devlink *devlink, ++ enum devlink_command cmd, u32 portid, ++ u32 seq, int flags, ++ struct devlink_region *region) ++{ ++ void *hdr; ++ int err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ err = devlink_nl_put_handle(msg, devlink); ++ if (err) ++ goto nla_put_failure; ++ ++ if (region->port) { ++ err = nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, ++ region->port->index); ++ if (err) ++ goto nla_put_failure; ++ } ++ ++ err = nla_put_string(msg, DEVLINK_ATTR_REGION_NAME, region->ops->name); ++ if (err) ++ goto nla_put_failure; ++ ++ err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_SIZE, ++ region->size, ++ DEVLINK_ATTR_PAD); ++ if (err) ++ goto nla_put_failure; ++ ++ err = nla_put_u32(msg, DEVLINK_ATTR_REGION_MAX_SNAPSHOTS, ++ region->max_snapshots); ++ if (err) ++ goto nla_put_failure; ++ ++ err = devlink_nl_region_snapshots_id_put(msg, devlink, region); ++ if (err) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return err; ++} ++ ++static struct sk_buff * ++devlink_nl_region_notify_build(struct devlink_region *region, ++ struct devlink_snapshot *snapshot, ++ enum devlink_command cmd, u32 portid, u32 seq) ++{ ++ struct devlink *devlink = region->devlink; ++ struct sk_buff *msg; ++ void *hdr; ++ int err; ++ ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return ERR_PTR(-ENOMEM); ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, 0, cmd); ++ if (!hdr) { ++ err = -EMSGSIZE; ++ goto out_free_msg; ++ } ++ ++ err = devlink_nl_put_handle(msg, devlink); ++ if (err) ++ goto out_cancel_msg; ++ ++ if (region->port) { ++ err = nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, ++ region->port->index); ++ if (err) ++ goto out_cancel_msg; ++ } ++ ++ err = nla_put_string(msg, DEVLINK_ATTR_REGION_NAME, ++ region->ops->name); ++ if (err) ++ goto out_cancel_msg; ++ ++ if (snapshot) { ++ err = nla_put_u32(msg, DEVLINK_ATTR_REGION_SNAPSHOT_ID, ++ snapshot->id); ++ if (err) ++ goto out_cancel_msg; ++ } else { ++ err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_SIZE, ++ region->size, DEVLINK_ATTR_PAD); ++ if (err) ++ goto out_cancel_msg; ++ } ++ genlmsg_end(msg, hdr); ++ ++ return msg; ++ ++out_cancel_msg: ++ genlmsg_cancel(msg, hdr); ++out_free_msg: ++ nlmsg_free(msg); ++ return ERR_PTR(err); ++} ++ ++static void devlink_nl_region_notify(struct devlink_region *region, ++ struct devlink_snapshot *snapshot, ++ enum devlink_command cmd) ++{ ++ struct devlink *devlink = region->devlink; ++ struct sk_buff *msg; ++ ++ WARN_ON(cmd != DEVLINK_CMD_REGION_NEW && cmd != DEVLINK_CMD_REGION_DEL); ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = devlink_nl_region_notify_build(region, snapshot, cmd, 0, 0); ++ if (IS_ERR(msg)) ++ return; ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, ++ 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++/** ++ * __devlink_snapshot_id_increment - Increment number of snapshots using an id ++ * @devlink: devlink instance ++ * @id: the snapshot id ++ * ++ * Track when a new snapshot begins using an id. Load the count for the ++ * given id from the snapshot xarray, increment it, and store it back. ++ * ++ * Called when a new snapshot is created with the given id. ++ * ++ * The id *must* have been previously allocated by ++ * devlink_region_snapshot_id_get(). ++ * ++ * Returns 0 on success, or an error on failure. ++ */ ++static int __devlink_snapshot_id_increment(struct devlink *devlink, u32 id) ++{ ++ unsigned long count; ++ void *p; ++ int err; ++ ++ xa_lock(&devlink->snapshot_ids); ++ p = xa_load(&devlink->snapshot_ids, id); ++ if (WARN_ON(!p)) { ++ err = -EINVAL; ++ goto unlock; ++ } ++ ++ if (WARN_ON(!xa_is_value(p))) { ++ err = -EINVAL; ++ goto unlock; ++ } ++ ++ count = xa_to_value(p); ++ count++; ++ ++ err = xa_err(__xa_store(&devlink->snapshot_ids, id, xa_mk_value(count), ++ GFP_ATOMIC)); ++unlock: ++ xa_unlock(&devlink->snapshot_ids); ++ return err; ++} ++ ++/** ++ * __devlink_snapshot_id_decrement - Decrease number of snapshots using an id ++ * @devlink: devlink instance ++ * @id: the snapshot id ++ * ++ * Track when a snapshot is deleted and stops using an id. Load the count ++ * for the given id from the snapshot xarray, decrement it, and store it ++ * back. ++ * ++ * If the count reaches zero, erase this id from the xarray, freeing it ++ * up for future re-use by devlink_region_snapshot_id_get(). ++ * ++ * Called when a snapshot using the given id is deleted, and when the ++ * initial allocator of the id is finished using it. ++ */ ++static void __devlink_snapshot_id_decrement(struct devlink *devlink, u32 id) ++{ ++ unsigned long count; ++ void *p; ++ ++ xa_lock(&devlink->snapshot_ids); ++ p = xa_load(&devlink->snapshot_ids, id); ++ if (WARN_ON(!p)) ++ goto unlock; ++ ++ if (WARN_ON(!xa_is_value(p))) ++ goto unlock; ++ ++ count = xa_to_value(p); ++ ++ if (count > 1) { ++ count--; ++ __xa_store(&devlink->snapshot_ids, id, xa_mk_value(count), ++ GFP_ATOMIC); ++ } else { ++ /* If this was the last user, we can erase this id */ ++ __xa_erase(&devlink->snapshot_ids, id); ++ } ++unlock: ++ xa_unlock(&devlink->snapshot_ids); ++} ++ ++/** ++ * __devlink_snapshot_id_insert - Insert a specific snapshot ID ++ * @devlink: devlink instance ++ * @id: the snapshot id ++ * ++ * Mark the given snapshot id as used by inserting a zero value into the ++ * snapshot xarray. ++ * ++ * This must be called while holding the devlink instance lock. Unlike ++ * devlink_snapshot_id_get, the initial reference count is zero, not one. ++ * It is expected that the id will immediately be used before ++ * releasing the devlink instance lock. ++ * ++ * Returns zero on success, or an error code if the snapshot id could not ++ * be inserted. ++ */ ++static int __devlink_snapshot_id_insert(struct devlink *devlink, u32 id) ++{ ++ int err; ++ ++ xa_lock(&devlink->snapshot_ids); ++ if (xa_load(&devlink->snapshot_ids, id)) { ++ xa_unlock(&devlink->snapshot_ids); ++ return -EEXIST; ++ } ++ err = xa_err(__xa_store(&devlink->snapshot_ids, id, xa_mk_value(0), ++ GFP_ATOMIC)); ++ xa_unlock(&devlink->snapshot_ids); ++ return err; ++} ++ ++/** ++ * __devlink_region_snapshot_id_get - get snapshot ID ++ * @devlink: devlink instance ++ * @id: storage to return snapshot id ++ * ++ * Allocates a new snapshot id. Returns zero on success, or a negative ++ * error on failure. Must be called while holding the devlink instance ++ * lock. ++ * ++ * Snapshot IDs are tracked using an xarray which stores the number of ++ * users of the snapshot id. ++ * ++ * Note that the caller of this function counts as a 'user', in order to ++ * avoid race conditions. The caller must release its hold on the ++ * snapshot by using devlink_region_snapshot_id_put. ++ */ ++static int __devlink_region_snapshot_id_get(struct devlink *devlink, u32 *id) ++{ ++ return xa_alloc(&devlink->snapshot_ids, id, xa_mk_value(1), ++ xa_limit_32b, GFP_KERNEL); ++} ++ ++/** ++ * __devlink_region_snapshot_create - create a new snapshot ++ * This will add a new snapshot of a region. The snapshot ++ * will be stored on the region struct and can be accessed ++ * from devlink. This is useful for future analyses of snapshots. ++ * Multiple snapshots can be created on a region. ++ * The @snapshot_id should be obtained using the getter function. ++ * ++ * Must be called only while holding the region snapshot lock. ++ * ++ * @region: devlink region of the snapshot ++ * @data: snapshot data ++ * @snapshot_id: snapshot id to be created ++ */ ++static int ++__devlink_region_snapshot_create(struct devlink_region *region, ++ u8 *data, u32 snapshot_id) ++{ ++ struct devlink *devlink = region->devlink; ++ struct devlink_snapshot *snapshot; ++ int err; ++ ++ lockdep_assert_held(®ion->snapshot_lock); ++ ++ /* check if region can hold one more snapshot */ ++ if (region->cur_snapshots == region->max_snapshots) ++ return -ENOSPC; ++ ++ if (devlink_region_snapshot_get_by_id(region, snapshot_id)) ++ return -EEXIST; ++ ++ snapshot = kzalloc(sizeof(*snapshot), GFP_KERNEL); ++ if (!snapshot) ++ return -ENOMEM; ++ ++ err = __devlink_snapshot_id_increment(devlink, snapshot_id); ++ if (err) ++ goto err_snapshot_id_increment; ++ ++ snapshot->id = snapshot_id; ++ snapshot->region = region; ++ snapshot->data = data; ++ ++ list_add_tail(&snapshot->list, ®ion->snapshot_list); ++ ++ region->cur_snapshots++; ++ ++ devlink_nl_region_notify(region, snapshot, DEVLINK_CMD_REGION_NEW); ++ return 0; ++ ++err_snapshot_id_increment: ++ kfree(snapshot); ++ return err; ++} ++ ++static void devlink_region_snapshot_del(struct devlink_region *region, ++ struct devlink_snapshot *snapshot) ++{ ++ struct devlink *devlink = region->devlink; ++ ++ lockdep_assert_held(®ion->snapshot_lock); ++ ++ devlink_nl_region_notify(region, snapshot, DEVLINK_CMD_REGION_DEL); ++ region->cur_snapshots--; ++ list_del(&snapshot->list); ++ region->ops->destructor(snapshot->data); ++ __devlink_snapshot_id_decrement(devlink, snapshot->id); ++ kfree(snapshot); ++} ++ ++static int devlink_nl_cmd_region_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_port *port = NULL; ++ struct devlink_region *region; ++ const char *region_name; ++ struct sk_buff *msg; ++ unsigned int index; ++ int err; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME)) ++ return -EINVAL; ++ ++ if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { ++ index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); ++ ++ port = devlink_port_get_by_index(devlink, index); ++ if (!port) ++ return -ENODEV; ++ } ++ ++ region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]); ++ if (port) ++ region = devlink_port_region_get_by_name(port, region_name); ++ else ++ region = devlink_region_get_by_name(devlink, region_name); ++ ++ if (!region) ++ return -EINVAL; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_region_fill(msg, devlink, DEVLINK_CMD_REGION_GET, ++ info->snd_portid, info->snd_seq, 0, ++ region); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_nl_cmd_region_get_port_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb, ++ struct devlink_port *port, ++ int *idx, ++ int start) ++{ ++ struct devlink_region *region; ++ int err = 0; ++ ++ list_for_each_entry(region, &port->region_list, list) { ++ if (*idx < start) { ++ (*idx)++; ++ continue; ++ } ++ err = devlink_nl_region_fill(msg, port->devlink, ++ DEVLINK_CMD_REGION_GET, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI, region); ++ if (err) ++ goto out; ++ (*idx)++; ++ } ++ ++out: ++ return err; ++} ++ ++static int devlink_nl_cmd_region_get_devlink_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb, ++ struct devlink *devlink, ++ int *idx, ++ int start) ++{ ++ struct devlink_region *region; ++ struct devlink_port *port; ++ int err = 0; ++ ++ devl_lock(devlink); ++ list_for_each_entry(region, &devlink->region_list, list) { ++ if (*idx < start) { ++ (*idx)++; ++ continue; ++ } ++ err = devlink_nl_region_fill(msg, devlink, ++ DEVLINK_CMD_REGION_GET, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI, region); ++ if (err) ++ goto out; ++ (*idx)++; ++ } ++ ++ list_for_each_entry(port, &devlink->port_list, list) { ++ err = devlink_nl_cmd_region_get_port_dumpit(msg, cb, port, idx, ++ start); ++ if (err) ++ goto out; ++ } ++ ++out: ++ devl_unlock(devlink); ++ return err; ++} ++ ++static int devlink_nl_cmd_region_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ err = devlink_nl_cmd_region_get_devlink_dumpit(msg, cb, devlink, ++ &idx, start); ++ devlink_put(devlink); ++ if (err) ++ goto out; ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int devlink_nl_cmd_region_del(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_snapshot *snapshot; ++ struct devlink_port *port = NULL; ++ struct devlink_region *region; ++ const char *region_name; ++ unsigned int index; ++ u32 snapshot_id; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME) || ++ GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_SNAPSHOT_ID)) ++ return -EINVAL; ++ ++ region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]); ++ snapshot_id = nla_get_u32(info->attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]); ++ ++ if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { ++ index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); ++ ++ port = devlink_port_get_by_index(devlink, index); ++ if (!port) ++ return -ENODEV; ++ } ++ ++ if (port) ++ region = devlink_port_region_get_by_name(port, region_name); ++ else ++ region = devlink_region_get_by_name(devlink, region_name); ++ ++ if (!region) ++ return -EINVAL; ++ ++ mutex_lock(®ion->snapshot_lock); ++ snapshot = devlink_region_snapshot_get_by_id(region, snapshot_id); ++ if (!snapshot) { ++ mutex_unlock(®ion->snapshot_lock); ++ return -EINVAL; ++ } ++ ++ devlink_region_snapshot_del(region, snapshot); ++ mutex_unlock(®ion->snapshot_lock); ++ return 0; ++} ++ ++static int ++devlink_nl_cmd_region_new(struct sk_buff *skb, struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_snapshot *snapshot; ++ struct devlink_port *port = NULL; ++ struct nlattr *snapshot_id_attr; ++ struct devlink_region *region; ++ const char *region_name; ++ unsigned int index; ++ u32 snapshot_id; ++ u8 *data; ++ int err; ++ ++ if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME)) { ++ NL_SET_ERR_MSG_MOD(info->extack, "No region name provided"); ++ return -EINVAL; ++ } ++ ++ region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]); ++ ++ if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { ++ index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); ++ ++ port = devlink_port_get_by_index(devlink, index); ++ if (!port) ++ return -ENODEV; ++ } ++ ++ if (port) ++ region = devlink_port_region_get_by_name(port, region_name); ++ else ++ region = devlink_region_get_by_name(devlink, region_name); ++ ++ if (!region) { ++ NL_SET_ERR_MSG_MOD(info->extack, "The requested region does not exist"); ++ return -EINVAL; ++ } ++ ++ if (!region->ops->snapshot) { ++ NL_SET_ERR_MSG_MOD(info->extack, "The requested region does not support taking an immediate snapshot"); ++ return -EOPNOTSUPP; ++ } ++ ++ mutex_lock(®ion->snapshot_lock); ++ ++ if (region->cur_snapshots == region->max_snapshots) { ++ NL_SET_ERR_MSG_MOD(info->extack, "The region has reached the maximum number of stored snapshots"); ++ err = -ENOSPC; ++ goto unlock; ++ } ++ ++ snapshot_id_attr = info->attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]; ++ if (snapshot_id_attr) { ++ snapshot_id = nla_get_u32(snapshot_id_attr); ++ ++ if (devlink_region_snapshot_get_by_id(region, snapshot_id)) { ++ NL_SET_ERR_MSG_MOD(info->extack, "The requested snapshot id is already in use"); ++ err = -EEXIST; ++ goto unlock; ++ } ++ ++ err = __devlink_snapshot_id_insert(devlink, snapshot_id); ++ if (err) ++ goto unlock; ++ } else { ++ err = __devlink_region_snapshot_id_get(devlink, &snapshot_id); ++ if (err) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Failed to allocate a new snapshot id"); ++ goto unlock; ++ } ++ } ++ ++ if (port) ++ err = region->port_ops->snapshot(port, region->port_ops, ++ info->extack, &data); ++ else ++ err = region->ops->snapshot(devlink, region->ops, ++ info->extack, &data); ++ if (err) ++ goto err_snapshot_capture; ++ ++ err = __devlink_region_snapshot_create(region, data, snapshot_id); ++ if (err) ++ goto err_snapshot_create; ++ ++ if (!snapshot_id_attr) { ++ struct sk_buff *msg; ++ ++ snapshot = devlink_region_snapshot_get_by_id(region, ++ snapshot_id); ++ if (WARN_ON(!snapshot)) { ++ err = -EINVAL; ++ goto unlock; ++ } ++ ++ msg = devlink_nl_region_notify_build(region, snapshot, ++ DEVLINK_CMD_REGION_NEW, ++ info->snd_portid, ++ info->snd_seq); ++ err = PTR_ERR_OR_ZERO(msg); ++ if (err) ++ goto err_notify; ++ ++ err = genlmsg_reply(msg, info); ++ if (err) ++ goto err_notify; ++ } ++ ++ mutex_unlock(®ion->snapshot_lock); ++ return 0; ++ ++err_snapshot_create: ++ region->ops->destructor(data); ++err_snapshot_capture: ++ __devlink_snapshot_id_decrement(devlink, snapshot_id); ++ mutex_unlock(®ion->snapshot_lock); ++ return err; ++ ++err_notify: ++ devlink_region_snapshot_del(region, snapshot); ++unlock: ++ mutex_unlock(®ion->snapshot_lock); ++ return err; ++} ++ ++static int devlink_nl_cmd_region_read_chunk_fill(struct sk_buff *msg, ++ struct devlink *devlink, ++ u8 *chunk, u32 chunk_size, ++ u64 addr) ++{ ++ struct nlattr *chunk_attr; ++ int err; ++ ++ chunk_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_REGION_CHUNK); ++ if (!chunk_attr) ++ return -EINVAL; ++ ++ err = nla_put(msg, DEVLINK_ATTR_REGION_CHUNK_DATA, chunk_size, chunk); ++ if (err) ++ goto nla_put_failure; ++ ++ err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_CHUNK_ADDR, addr, ++ DEVLINK_ATTR_PAD); ++ if (err) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, chunk_attr); ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, chunk_attr); ++ return err; ++} ++ ++#define DEVLINK_REGION_READ_CHUNK_SIZE 256 ++ ++static int devlink_nl_region_read_snapshot_fill(struct sk_buff *skb, ++ struct devlink *devlink, ++ struct devlink_region *region, ++ struct nlattr **attrs, ++ u64 start_offset, ++ u64 end_offset, ++ u64 *new_offset) ++{ ++ struct devlink_snapshot *snapshot; ++ u64 curr_offset = start_offset; ++ u32 snapshot_id; ++ int err = 0; ++ ++ *new_offset = start_offset; ++ ++ snapshot_id = nla_get_u32(attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]); ++ snapshot = devlink_region_snapshot_get_by_id(region, snapshot_id); ++ if (!snapshot) ++ return -EINVAL; ++ ++ while (curr_offset < end_offset) { ++ u32 data_size; ++ u8 *data; ++ ++ if (end_offset - curr_offset < DEVLINK_REGION_READ_CHUNK_SIZE) ++ data_size = end_offset - curr_offset; ++ else ++ data_size = DEVLINK_REGION_READ_CHUNK_SIZE; ++ ++ data = &snapshot->data[curr_offset]; ++ err = devlink_nl_cmd_region_read_chunk_fill(skb, devlink, ++ data, data_size, ++ curr_offset); ++ if (err) ++ break; ++ ++ curr_offset += data_size; ++ } ++ *new_offset = curr_offset; ++ ++ return err; ++} ++ ++static int devlink_nl_cmd_region_read_dumpit(struct sk_buff *skb, ++ struct netlink_callback *cb) ++{ ++ const struct genl_dumpit_info *info = genl_dumpit_info(cb); ++ u64 ret_offset, start_offset, end_offset = U64_MAX; ++ struct nlattr **attrs = info->attrs; ++ struct devlink_port *port = NULL; ++ struct devlink_region *region; ++ struct nlattr *chunks_attr; ++ const char *region_name; ++ struct devlink *devlink; ++ unsigned int index; ++ void *hdr; ++ int err; ++ ++ start_offset = *((u64 *)&cb->args[0]); ++ ++ devlink = devlink_get_from_attrs(sock_net(cb->skb->sk), attrs); ++ if (IS_ERR(devlink)) ++ return PTR_ERR(devlink); ++ ++ devl_lock(devlink); ++ ++ if (!attrs[DEVLINK_ATTR_REGION_NAME] || ++ !attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]) { ++ err = -EINVAL; ++ goto out_unlock; ++ } ++ ++ if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) { ++ index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]); ++ ++ port = devlink_port_get_by_index(devlink, index); ++ if (!port) { ++ err = -ENODEV; ++ goto out_unlock; ++ } ++ } ++ ++ region_name = nla_data(attrs[DEVLINK_ATTR_REGION_NAME]); ++ ++ if (port) ++ region = devlink_port_region_get_by_name(port, region_name); ++ else ++ region = devlink_region_get_by_name(devlink, region_name); ++ ++ if (!region) { ++ err = -EINVAL; ++ goto out_unlock; ++ } ++ ++ if (attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR] && ++ attrs[DEVLINK_ATTR_REGION_CHUNK_LEN]) { ++ if (!start_offset) ++ start_offset = ++ nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR]); ++ ++ end_offset = nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR]); ++ end_offset += nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_LEN]); ++ } ++ ++ if (end_offset > region->size) ++ end_offset = region->size; ++ ++ /* return 0 if there is no further data to read */ ++ if (start_offset == end_offset) { ++ err = 0; ++ goto out_unlock; ++ } ++ ++ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, ++ &devlink_nl_family, NLM_F_ACK | NLM_F_MULTI, ++ DEVLINK_CMD_REGION_READ); ++ if (!hdr) { ++ err = -EMSGSIZE; ++ goto out_unlock; ++ } ++ ++ err = devlink_nl_put_handle(skb, devlink); ++ if (err) ++ goto nla_put_failure; ++ ++ if (region->port) { ++ err = nla_put_u32(skb, DEVLINK_ATTR_PORT_INDEX, ++ region->port->index); ++ if (err) ++ goto nla_put_failure; ++ } ++ ++ err = nla_put_string(skb, DEVLINK_ATTR_REGION_NAME, region_name); ++ if (err) ++ goto nla_put_failure; ++ ++ chunks_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_REGION_CHUNKS); ++ if (!chunks_attr) { ++ err = -EMSGSIZE; ++ goto nla_put_failure; ++ } ++ ++ err = devlink_nl_region_read_snapshot_fill(skb, devlink, ++ region, attrs, ++ start_offset, ++ end_offset, &ret_offset); ++ ++ if (err && err != -EMSGSIZE) ++ goto nla_put_failure; ++ ++ /* Check if there was any progress done to prevent infinite loop */ ++ if (ret_offset == start_offset) { ++ err = -EINVAL; ++ goto nla_put_failure; ++ } ++ ++ *((u64 *)&cb->args[0]) = ret_offset; ++ ++ nla_nest_end(skb, chunks_attr); ++ genlmsg_end(skb, hdr); ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ return skb->len; ++ ++nla_put_failure: ++ genlmsg_cancel(skb, hdr); ++out_unlock: ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ return err; ++} ++ ++int devlink_info_driver_name_put(struct devlink_info_req *req, const char *name) ++{ ++ if (!req->msg) ++ return 0; ++ return nla_put_string(req->msg, DEVLINK_ATTR_INFO_DRIVER_NAME, name); ++} ++EXPORT_SYMBOL_GPL(devlink_info_driver_name_put); ++ ++int devlink_info_serial_number_put(struct devlink_info_req *req, const char *sn) ++{ ++ if (!req->msg) ++ return 0; ++ return nla_put_string(req->msg, DEVLINK_ATTR_INFO_SERIAL_NUMBER, sn); ++} ++EXPORT_SYMBOL_GPL(devlink_info_serial_number_put); ++ ++int devlink_info_board_serial_number_put(struct devlink_info_req *req, ++ const char *bsn) ++{ ++ if (!req->msg) ++ return 0; ++ return nla_put_string(req->msg, DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER, ++ bsn); ++} ++EXPORT_SYMBOL_GPL(devlink_info_board_serial_number_put); ++ ++static int devlink_info_version_put(struct devlink_info_req *req, int attr, ++ const char *version_name, ++ const char *version_value, ++ enum devlink_info_version_type version_type) ++{ ++ struct nlattr *nest; ++ int err; ++ ++ if (req->version_cb) ++ req->version_cb(version_name, version_type, ++ req->version_cb_priv); ++ ++ if (!req->msg) ++ return 0; ++ ++ nest = nla_nest_start_noflag(req->msg, attr); ++ if (!nest) ++ return -EMSGSIZE; ++ ++ err = nla_put_string(req->msg, DEVLINK_ATTR_INFO_VERSION_NAME, ++ version_name); ++ if (err) ++ goto nla_put_failure; ++ ++ err = nla_put_string(req->msg, DEVLINK_ATTR_INFO_VERSION_VALUE, ++ version_value); ++ if (err) ++ goto nla_put_failure; ++ ++ nla_nest_end(req->msg, nest); ++ ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(req->msg, nest); ++ return err; ++} ++ ++int devlink_info_version_fixed_put(struct devlink_info_req *req, ++ const char *version_name, ++ const char *version_value) ++{ ++ return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_FIXED, ++ version_name, version_value, ++ DEVLINK_INFO_VERSION_TYPE_NONE); ++} ++EXPORT_SYMBOL_GPL(devlink_info_version_fixed_put); ++ ++int devlink_info_version_stored_put(struct devlink_info_req *req, ++ const char *version_name, ++ const char *version_value) ++{ ++ return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_STORED, ++ version_name, version_value, ++ DEVLINK_INFO_VERSION_TYPE_NONE); ++} ++EXPORT_SYMBOL_GPL(devlink_info_version_stored_put); ++ ++int devlink_info_version_stored_put_ext(struct devlink_info_req *req, ++ const char *version_name, ++ const char *version_value, ++ enum devlink_info_version_type version_type) ++{ ++ return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_STORED, ++ version_name, version_value, ++ version_type); ++} ++EXPORT_SYMBOL_GPL(devlink_info_version_stored_put_ext); ++ ++int devlink_info_version_running_put(struct devlink_info_req *req, ++ const char *version_name, ++ const char *version_value) ++{ ++ return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_RUNNING, ++ version_name, version_value, ++ DEVLINK_INFO_VERSION_TYPE_NONE); ++} ++EXPORT_SYMBOL_GPL(devlink_info_version_running_put); ++ ++int devlink_info_version_running_put_ext(struct devlink_info_req *req, ++ const char *version_name, ++ const char *version_value, ++ enum devlink_info_version_type version_type) ++{ ++ return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_RUNNING, ++ version_name, version_value, ++ version_type); ++} ++EXPORT_SYMBOL_GPL(devlink_info_version_running_put_ext); ++ ++static int ++devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink, ++ enum devlink_command cmd, u32 portid, ++ u32 seq, int flags, struct netlink_ext_ack *extack) ++{ ++ struct devlink_info_req req = {}; ++ void *hdr; ++ int err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ err = -EMSGSIZE; ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto err_cancel_msg; ++ ++ req.msg = msg; ++ err = devlink->ops->info_get(devlink, &req, extack); ++ if (err) ++ goto err_cancel_msg; ++ ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++err_cancel_msg: ++ genlmsg_cancel(msg, hdr); ++ return err; ++} ++ ++static int devlink_nl_cmd_info_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct sk_buff *msg; ++ int err; ++ ++ if (!devlink->ops->info_get) ++ return -EOPNOTSUPP; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_info_fill(msg, devlink, DEVLINK_CMD_INFO_GET, ++ info->snd_portid, info->snd_seq, 0, ++ info->extack); ++ if (err) { ++ nlmsg_free(msg); ++ return err; ++ } ++ ++ return genlmsg_reply(msg, info); ++} ++ ++static int devlink_nl_cmd_info_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err = 0; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ if (idx < start || !devlink->ops->info_get) ++ goto inc; ++ ++ devl_lock(devlink); ++ err = devlink_nl_info_fill(msg, devlink, DEVLINK_CMD_INFO_GET, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, NLM_F_MULTI, ++ cb->extack); ++ devl_unlock(devlink); ++ if (err == -EOPNOTSUPP) ++ err = 0; ++ else if (err) { ++ devlink_put(devlink); ++ break; ++ } ++inc: ++ idx++; ++ devlink_put(devlink); ++ } ++ ++ if (err != -EMSGSIZE) ++ return err; ++ ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++struct devlink_fmsg_item { ++ struct list_head list; ++ int attrtype; ++ u8 nla_type; ++ u16 len; ++ int value[]; ++}; ++ ++struct devlink_fmsg { ++ struct list_head item_list; ++ bool putting_binary; /* This flag forces enclosing of binary data ++ * in an array brackets. It forces using ++ * of designated API: ++ * devlink_fmsg_binary_pair_nest_start() ++ * devlink_fmsg_binary_pair_nest_end() ++ */ ++}; ++ ++static struct devlink_fmsg *devlink_fmsg_alloc(void) ++{ ++ struct devlink_fmsg *fmsg; ++ ++ fmsg = kzalloc(sizeof(*fmsg), GFP_KERNEL); ++ if (!fmsg) ++ return NULL; ++ ++ INIT_LIST_HEAD(&fmsg->item_list); ++ ++ return fmsg; ++} ++ ++static void devlink_fmsg_free(struct devlink_fmsg *fmsg) ++{ ++ struct devlink_fmsg_item *item, *tmp; ++ ++ list_for_each_entry_safe(item, tmp, &fmsg->item_list, list) { ++ list_del(&item->list); ++ kfree(item); ++ } ++ kfree(fmsg); ++} ++ ++static int devlink_fmsg_nest_common(struct devlink_fmsg *fmsg, ++ int attrtype) ++{ ++ struct devlink_fmsg_item *item; ++ ++ item = kzalloc(sizeof(*item), GFP_KERNEL); ++ if (!item) ++ return -ENOMEM; ++ ++ item->attrtype = attrtype; ++ list_add_tail(&item->list, &fmsg->item_list); ++ ++ return 0; ++} ++ ++int devlink_fmsg_obj_nest_start(struct devlink_fmsg *fmsg) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_OBJ_NEST_START); ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_start); ++ ++static int devlink_fmsg_nest_end(struct devlink_fmsg *fmsg) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_NEST_END); ++} ++ ++int devlink_fmsg_obj_nest_end(struct devlink_fmsg *fmsg) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_nest_end(fmsg); ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_end); ++ ++#define DEVLINK_FMSG_MAX_SIZE (GENLMSG_DEFAULT_SIZE - GENL_HDRLEN - NLA_HDRLEN) ++ ++static int devlink_fmsg_put_name(struct devlink_fmsg *fmsg, const char *name) ++{ ++ struct devlink_fmsg_item *item; ++ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ if (strlen(name) + 1 > DEVLINK_FMSG_MAX_SIZE) ++ return -EMSGSIZE; ++ ++ item = kzalloc(sizeof(*item) + strlen(name) + 1, GFP_KERNEL); ++ if (!item) ++ return -ENOMEM; ++ ++ item->nla_type = NLA_NUL_STRING; ++ item->len = strlen(name) + 1; ++ item->attrtype = DEVLINK_ATTR_FMSG_OBJ_NAME; ++ memcpy(&item->value, name, item->len); ++ list_add_tail(&item->list, &fmsg->item_list); ++ ++ return 0; ++} ++ ++int devlink_fmsg_pair_nest_start(struct devlink_fmsg *fmsg, const char *name) ++{ ++ int err; ++ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_PAIR_NEST_START); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_put_name(fmsg, name); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_start); ++ ++int devlink_fmsg_pair_nest_end(struct devlink_fmsg *fmsg) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_nest_end(fmsg); ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_end); ++ ++int devlink_fmsg_arr_pair_nest_start(struct devlink_fmsg *fmsg, ++ const char *name) ++{ ++ int err; ++ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ err = devlink_fmsg_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_ARR_NEST_START); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_start); ++ ++int devlink_fmsg_arr_pair_nest_end(struct devlink_fmsg *fmsg) ++{ ++ int err; ++ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ err = devlink_fmsg_nest_end(fmsg); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_nest_end(fmsg); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_end); ++ ++int devlink_fmsg_binary_pair_nest_start(struct devlink_fmsg *fmsg, ++ const char *name) ++{ ++ int err; ++ ++ err = devlink_fmsg_arr_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ fmsg->putting_binary = true; ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_nest_start); ++ ++int devlink_fmsg_binary_pair_nest_end(struct devlink_fmsg *fmsg) ++{ ++ if (!fmsg->putting_binary) ++ return -EINVAL; ++ ++ fmsg->putting_binary = false; ++ return devlink_fmsg_arr_pair_nest_end(fmsg); ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_nest_end); ++ ++static int devlink_fmsg_put_value(struct devlink_fmsg *fmsg, ++ const void *value, u16 value_len, ++ u8 value_nla_type) ++{ ++ struct devlink_fmsg_item *item; ++ ++ if (value_len > DEVLINK_FMSG_MAX_SIZE) ++ return -EMSGSIZE; ++ ++ item = kzalloc(sizeof(*item) + value_len, GFP_KERNEL); ++ if (!item) ++ return -ENOMEM; ++ ++ item->nla_type = value_nla_type; ++ item->len = value_len; ++ item->attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA; ++ memcpy(&item->value, value, item->len); ++ list_add_tail(&item->list, &fmsg->item_list); ++ ++ return 0; ++} ++ ++static int devlink_fmsg_bool_put(struct devlink_fmsg *fmsg, bool value) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_FLAG); ++} ++ ++static int devlink_fmsg_u8_put(struct devlink_fmsg *fmsg, u8 value) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U8); ++} ++ ++int devlink_fmsg_u32_put(struct devlink_fmsg *fmsg, u32 value) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U32); ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_u32_put); ++ ++static int devlink_fmsg_u64_put(struct devlink_fmsg *fmsg, u64 value) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U64); ++} ++ ++int devlink_fmsg_string_put(struct devlink_fmsg *fmsg, const char *value) ++{ ++ if (fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_put_value(fmsg, value, strlen(value) + 1, ++ NLA_NUL_STRING); ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_string_put); ++ ++int devlink_fmsg_binary_put(struct devlink_fmsg *fmsg, const void *value, ++ u16 value_len) ++{ ++ if (!fmsg->putting_binary) ++ return -EINVAL; ++ ++ return devlink_fmsg_put_value(fmsg, value, value_len, NLA_BINARY); ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_binary_put); ++ ++int devlink_fmsg_bool_pair_put(struct devlink_fmsg *fmsg, const char *name, ++ bool value) ++{ ++ int err; ++ ++ err = devlink_fmsg_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_bool_put(fmsg, value); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_pair_nest_end(fmsg); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_bool_pair_put); ++ ++int devlink_fmsg_u8_pair_put(struct devlink_fmsg *fmsg, const char *name, ++ u8 value) ++{ ++ int err; ++ ++ err = devlink_fmsg_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_u8_put(fmsg, value); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_pair_nest_end(fmsg); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_u8_pair_put); ++ ++int devlink_fmsg_u32_pair_put(struct devlink_fmsg *fmsg, const char *name, ++ u32 value) ++{ ++ int err; ++ ++ err = devlink_fmsg_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_u32_put(fmsg, value); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_pair_nest_end(fmsg); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_u32_pair_put); ++ ++int devlink_fmsg_u64_pair_put(struct devlink_fmsg *fmsg, const char *name, ++ u64 value) ++{ ++ int err; ++ ++ err = devlink_fmsg_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_u64_put(fmsg, value); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_pair_nest_end(fmsg); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_u64_pair_put); ++ ++int devlink_fmsg_string_pair_put(struct devlink_fmsg *fmsg, const char *name, ++ const char *value) ++{ ++ int err; ++ ++ err = devlink_fmsg_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_string_put(fmsg, value); ++ if (err) ++ return err; ++ ++ err = devlink_fmsg_pair_nest_end(fmsg); ++ if (err) ++ return err; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_string_pair_put); ++ ++int devlink_fmsg_binary_pair_put(struct devlink_fmsg *fmsg, const char *name, ++ const void *value, u32 value_len) ++{ ++ u32 data_size; ++ int end_err; ++ u32 offset; ++ int err; ++ ++ err = devlink_fmsg_binary_pair_nest_start(fmsg, name); ++ if (err) ++ return err; ++ ++ for (offset = 0; offset < value_len; offset += data_size) { ++ data_size = value_len - offset; ++ if (data_size > DEVLINK_FMSG_MAX_SIZE) ++ data_size = DEVLINK_FMSG_MAX_SIZE; ++ err = devlink_fmsg_binary_put(fmsg, value + offset, data_size); ++ if (err) ++ break; ++ /* Exit from loop with a break (instead of ++ * return) to make sure putting_binary is turned off in ++ * devlink_fmsg_binary_pair_nest_end ++ */ ++ } ++ ++ end_err = devlink_fmsg_binary_pair_nest_end(fmsg); ++ if (end_err) ++ err = end_err; ++ ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_put); ++ ++static int ++devlink_fmsg_item_fill_type(struct devlink_fmsg_item *msg, struct sk_buff *skb) ++{ ++ switch (msg->nla_type) { ++ case NLA_FLAG: ++ case NLA_U8: ++ case NLA_U32: ++ case NLA_U64: ++ case NLA_NUL_STRING: ++ case NLA_BINARY: ++ return nla_put_u8(skb, DEVLINK_ATTR_FMSG_OBJ_VALUE_TYPE, ++ msg->nla_type); ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int ++devlink_fmsg_item_fill_data(struct devlink_fmsg_item *msg, struct sk_buff *skb) ++{ ++ int attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA; ++ u8 tmp; ++ ++ switch (msg->nla_type) { ++ case NLA_FLAG: ++ /* Always provide flag data, regardless of its value */ ++ tmp = *(bool *) msg->value; ++ ++ return nla_put_u8(skb, attrtype, tmp); ++ case NLA_U8: ++ return nla_put_u8(skb, attrtype, *(u8 *) msg->value); ++ case NLA_U32: ++ return nla_put_u32(skb, attrtype, *(u32 *) msg->value); ++ case NLA_U64: ++ return nla_put_u64_64bit(skb, attrtype, *(u64 *) msg->value, ++ DEVLINK_ATTR_PAD); ++ case NLA_NUL_STRING: ++ return nla_put_string(skb, attrtype, (char *) &msg->value); ++ case NLA_BINARY: ++ return nla_put(skb, attrtype, msg->len, (void *) &msg->value); ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int ++devlink_fmsg_prepare_skb(struct devlink_fmsg *fmsg, struct sk_buff *skb, ++ int *start) ++{ ++ struct devlink_fmsg_item *item; ++ struct nlattr *fmsg_nlattr; ++ int i = 0; ++ int err; ++ ++ fmsg_nlattr = nla_nest_start_noflag(skb, DEVLINK_ATTR_FMSG); ++ if (!fmsg_nlattr) ++ return -EMSGSIZE; ++ ++ list_for_each_entry(item, &fmsg->item_list, list) { ++ if (i < *start) { ++ i++; ++ continue; ++ } ++ ++ switch (item->attrtype) { ++ case DEVLINK_ATTR_FMSG_OBJ_NEST_START: ++ case DEVLINK_ATTR_FMSG_PAIR_NEST_START: ++ case DEVLINK_ATTR_FMSG_ARR_NEST_START: ++ case DEVLINK_ATTR_FMSG_NEST_END: ++ err = nla_put_flag(skb, item->attrtype); ++ break; ++ case DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA: ++ err = devlink_fmsg_item_fill_type(item, skb); ++ if (err) ++ break; ++ err = devlink_fmsg_item_fill_data(item, skb); ++ break; ++ case DEVLINK_ATTR_FMSG_OBJ_NAME: ++ err = nla_put_string(skb, item->attrtype, ++ (char *) &item->value); ++ break; ++ default: ++ err = -EINVAL; ++ break; ++ } ++ if (!err) ++ *start = ++i; ++ else ++ break; ++ } ++ ++ nla_nest_end(skb, fmsg_nlattr); ++ return err; ++} ++ ++static int devlink_fmsg_snd(struct devlink_fmsg *fmsg, ++ struct genl_info *info, ++ enum devlink_command cmd, int flags) ++{ ++ struct nlmsghdr *nlh; ++ struct sk_buff *skb; ++ bool last = false; ++ int index = 0; ++ void *hdr; ++ int err; ++ ++ while (!last) { ++ int tmp_index = index; ++ ++ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!skb) ++ return -ENOMEM; ++ ++ hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, ++ &devlink_nl_family, flags | NLM_F_MULTI, cmd); ++ if (!hdr) { ++ err = -EMSGSIZE; ++ goto nla_put_failure; ++ } ++ ++ err = devlink_fmsg_prepare_skb(fmsg, skb, &index); ++ if (!err) ++ last = true; ++ else if (err != -EMSGSIZE || tmp_index == index) ++ goto nla_put_failure; ++ ++ genlmsg_end(skb, hdr); ++ err = genlmsg_reply(skb, info); ++ if (err) ++ return err; ++ } ++ ++ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!skb) ++ return -ENOMEM; ++ nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq, ++ NLMSG_DONE, 0, flags | NLM_F_MULTI); ++ if (!nlh) { ++ err = -EMSGSIZE; ++ goto nla_put_failure; ++ } ++ ++ return genlmsg_reply(skb, info); ++ ++nla_put_failure: ++ nlmsg_free(skb); ++ return err; ++} ++ ++static int devlink_fmsg_dumpit(struct devlink_fmsg *fmsg, struct sk_buff *skb, ++ struct netlink_callback *cb, ++ enum devlink_command cmd) ++{ ++ int index = cb->args[0]; ++ int tmp_index = index; ++ void *hdr; ++ int err; ++ ++ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, ++ &devlink_nl_family, NLM_F_ACK | NLM_F_MULTI, cmd); ++ if (!hdr) { ++ err = -EMSGSIZE; ++ goto nla_put_failure; ++ } ++ ++ err = devlink_fmsg_prepare_skb(fmsg, skb, &index); ++ if ((err && err != -EMSGSIZE) || tmp_index == index) ++ goto nla_put_failure; ++ ++ cb->args[0] = index; ++ genlmsg_end(skb, hdr); ++ return skb->len; ++ ++nla_put_failure: ++ genlmsg_cancel(skb, hdr); ++ return err; ++} ++ ++struct devlink_health_reporter { ++ struct list_head list; ++ void *priv; ++ const struct devlink_health_reporter_ops *ops; ++ struct devlink *devlink; ++ struct devlink_port *devlink_port; ++ struct devlink_fmsg *dump_fmsg; ++ struct mutex dump_lock; /* lock parallel read/write from dump buffers */ ++ u64 graceful_period; ++ bool auto_recover; ++ bool auto_dump; ++ u8 health_state; ++ u64 dump_ts; ++ u64 dump_real_ts; ++ u64 error_count; ++ u64 recovery_count; ++ u64 last_recovery_ts; ++ refcount_t refcount; ++}; ++ ++void * ++devlink_health_reporter_priv(struct devlink_health_reporter *reporter) ++{ ++ return reporter->priv; ++} ++EXPORT_SYMBOL_GPL(devlink_health_reporter_priv); ++ ++static struct devlink_health_reporter * ++__devlink_health_reporter_find_by_name(struct list_head *reporter_list, ++ struct mutex *list_lock, ++ const char *reporter_name) ++{ ++ struct devlink_health_reporter *reporter; ++ ++ lockdep_assert_held(list_lock); ++ list_for_each_entry(reporter, reporter_list, list) ++ if (!strcmp(reporter->ops->name, reporter_name)) ++ return reporter; ++ return NULL; ++} ++ ++static struct devlink_health_reporter * ++devlink_health_reporter_find_by_name(struct devlink *devlink, ++ const char *reporter_name) ++{ ++ return __devlink_health_reporter_find_by_name(&devlink->reporter_list, ++ &devlink->reporters_lock, ++ reporter_name); ++} ++ ++static struct devlink_health_reporter * ++devlink_port_health_reporter_find_by_name(struct devlink_port *devlink_port, ++ const char *reporter_name) ++{ ++ return __devlink_health_reporter_find_by_name(&devlink_port->reporter_list, ++ &devlink_port->reporters_lock, ++ reporter_name); ++} ++ ++static struct devlink_health_reporter * ++__devlink_health_reporter_create(struct devlink *devlink, ++ const struct devlink_health_reporter_ops *ops, ++ u64 graceful_period, void *priv) ++{ ++ struct devlink_health_reporter *reporter; ++ ++ if (WARN_ON(graceful_period && !ops->recover)) ++ return ERR_PTR(-EINVAL); ++ ++ reporter = kzalloc(sizeof(*reporter), GFP_KERNEL); ++ if (!reporter) ++ return ERR_PTR(-ENOMEM); ++ ++ reporter->priv = priv; ++ reporter->ops = ops; ++ reporter->devlink = devlink; ++ reporter->graceful_period = graceful_period; ++ reporter->auto_recover = !!ops->recover; ++ reporter->auto_dump = !!ops->dump; ++ mutex_init(&reporter->dump_lock); ++ refcount_set(&reporter->refcount, 1); ++ return reporter; ++} ++ ++/** ++ * devlink_port_health_reporter_create - create devlink health reporter for ++ * specified port instance ++ * ++ * @port: devlink_port which should contain the new reporter ++ * @ops: ops ++ * @graceful_period: to avoid recovery loops, in msecs ++ * @priv: priv ++ */ ++struct devlink_health_reporter * ++devlink_port_health_reporter_create(struct devlink_port *port, ++ const struct devlink_health_reporter_ops *ops, ++ u64 graceful_period, void *priv) ++{ ++ struct devlink_health_reporter *reporter; ++ ++ mutex_lock(&port->reporters_lock); ++ if (__devlink_health_reporter_find_by_name(&port->reporter_list, ++ &port->reporters_lock, ops->name)) { ++ reporter = ERR_PTR(-EEXIST); ++ goto unlock; ++ } ++ ++ reporter = __devlink_health_reporter_create(port->devlink, ops, ++ graceful_period, priv); ++ if (IS_ERR(reporter)) ++ goto unlock; ++ ++ reporter->devlink_port = port; ++ list_add_tail(&reporter->list, &port->reporter_list); ++unlock: ++ mutex_unlock(&port->reporters_lock); ++ return reporter; ++} ++EXPORT_SYMBOL_GPL(devlink_port_health_reporter_create); ++ ++/** ++ * devlink_health_reporter_create - create devlink health reporter ++ * ++ * @devlink: devlink ++ * @ops: ops ++ * @graceful_period: to avoid recovery loops, in msecs ++ * @priv: priv ++ */ ++struct devlink_health_reporter * ++devlink_health_reporter_create(struct devlink *devlink, ++ const struct devlink_health_reporter_ops *ops, ++ u64 graceful_period, void *priv) ++{ ++ struct devlink_health_reporter *reporter; ++ ++ mutex_lock(&devlink->reporters_lock); ++ if (devlink_health_reporter_find_by_name(devlink, ops->name)) { ++ reporter = ERR_PTR(-EEXIST); ++ goto unlock; ++ } ++ ++ reporter = __devlink_health_reporter_create(devlink, ops, ++ graceful_period, priv); ++ if (IS_ERR(reporter)) ++ goto unlock; ++ ++ list_add_tail(&reporter->list, &devlink->reporter_list); ++unlock: ++ mutex_unlock(&devlink->reporters_lock); ++ return reporter; ++} ++EXPORT_SYMBOL_GPL(devlink_health_reporter_create); ++ ++static void ++devlink_health_reporter_free(struct devlink_health_reporter *reporter) ++{ ++ mutex_destroy(&reporter->dump_lock); ++ if (reporter->dump_fmsg) ++ devlink_fmsg_free(reporter->dump_fmsg); ++ kfree(reporter); ++} ++ ++static void ++devlink_health_reporter_put(struct devlink_health_reporter *reporter) ++{ ++ if (refcount_dec_and_test(&reporter->refcount)) ++ devlink_health_reporter_free(reporter); ++} ++ ++static void ++__devlink_health_reporter_destroy(struct devlink_health_reporter *reporter) ++{ ++ list_del(&reporter->list); ++ devlink_health_reporter_put(reporter); ++} ++ ++/** ++ * devlink_health_reporter_destroy - destroy devlink health reporter ++ * ++ * @reporter: devlink health reporter to destroy ++ */ ++void ++devlink_health_reporter_destroy(struct devlink_health_reporter *reporter) ++{ ++ struct mutex *lock = &reporter->devlink->reporters_lock; ++ ++ mutex_lock(lock); ++ __devlink_health_reporter_destroy(reporter); ++ mutex_unlock(lock); ++} ++EXPORT_SYMBOL_GPL(devlink_health_reporter_destroy); ++ ++/** ++ * devlink_port_health_reporter_destroy - destroy devlink port health reporter ++ * ++ * @reporter: devlink health reporter to destroy ++ */ ++void ++devlink_port_health_reporter_destroy(struct devlink_health_reporter *reporter) ++{ ++ struct mutex *lock = &reporter->devlink_port->reporters_lock; ++ ++ mutex_lock(lock); ++ __devlink_health_reporter_destroy(reporter); ++ mutex_unlock(lock); ++} ++EXPORT_SYMBOL_GPL(devlink_port_health_reporter_destroy); ++ ++static int ++devlink_nl_health_reporter_fill(struct sk_buff *msg, ++ struct devlink_health_reporter *reporter, ++ enum devlink_command cmd, u32 portid, ++ u32 seq, int flags) ++{ ++ struct devlink *devlink = reporter->devlink; ++ struct nlattr *reporter_attr; ++ void *hdr; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto genlmsg_cancel; ++ ++ if (reporter->devlink_port) { ++ if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, reporter->devlink_port->index)) ++ goto genlmsg_cancel; ++ } ++ reporter_attr = nla_nest_start_noflag(msg, ++ DEVLINK_ATTR_HEALTH_REPORTER); ++ if (!reporter_attr) ++ goto genlmsg_cancel; ++ if (nla_put_string(msg, DEVLINK_ATTR_HEALTH_REPORTER_NAME, ++ reporter->ops->name)) ++ goto reporter_nest_cancel; ++ if (nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_STATE, ++ reporter->health_state)) ++ goto reporter_nest_cancel; ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT, ++ reporter->error_count, DEVLINK_ATTR_PAD)) ++ goto reporter_nest_cancel; ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT, ++ reporter->recovery_count, DEVLINK_ATTR_PAD)) ++ goto reporter_nest_cancel; ++ if (reporter->ops->recover && ++ nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD, ++ reporter->graceful_period, ++ DEVLINK_ATTR_PAD)) ++ goto reporter_nest_cancel; ++ if (reporter->ops->recover && ++ nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER, ++ reporter->auto_recover)) ++ goto reporter_nest_cancel; ++ if (reporter->dump_fmsg && ++ nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS, ++ jiffies_to_msecs(reporter->dump_ts), ++ DEVLINK_ATTR_PAD)) ++ goto reporter_nest_cancel; ++ if (reporter->dump_fmsg && ++ nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS_NS, ++ reporter->dump_real_ts, DEVLINK_ATTR_PAD)) ++ goto reporter_nest_cancel; ++ if (reporter->ops->dump && ++ nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP, ++ reporter->auto_dump)) ++ goto reporter_nest_cancel; ++ ++ nla_nest_end(msg, reporter_attr); ++ genlmsg_end(msg, hdr); ++ return 0; ++ ++reporter_nest_cancel: ++ nla_nest_end(msg, reporter_attr); ++genlmsg_cancel: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static void devlink_recover_notify(struct devlink_health_reporter *reporter, ++ enum devlink_command cmd) ++{ ++ struct devlink *devlink = reporter->devlink; ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON(cmd != DEVLINK_CMD_HEALTH_REPORTER_RECOVER); ++ WARN_ON(!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)); ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_health_reporter_fill(msg, reporter, cmd, 0, 0, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg, ++ 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++void ++devlink_health_reporter_recovery_done(struct devlink_health_reporter *reporter) ++{ ++ reporter->recovery_count++; ++ reporter->last_recovery_ts = jiffies; ++} ++EXPORT_SYMBOL_GPL(devlink_health_reporter_recovery_done); ++ ++static int ++devlink_health_reporter_recover(struct devlink_health_reporter *reporter, ++ void *priv_ctx, struct netlink_ext_ack *extack) ++{ ++ int err; ++ ++ if (reporter->health_state == DEVLINK_HEALTH_REPORTER_STATE_HEALTHY) ++ return 0; ++ ++ if (!reporter->ops->recover) ++ return -EOPNOTSUPP; ++ ++ err = reporter->ops->recover(reporter, priv_ctx, extack); ++ if (err) ++ return err; ++ ++ devlink_health_reporter_recovery_done(reporter); ++ reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_HEALTHY; ++ devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER); ++ ++ return 0; ++} ++ ++static void ++devlink_health_dump_clear(struct devlink_health_reporter *reporter) ++{ ++ if (!reporter->dump_fmsg) ++ return; ++ devlink_fmsg_free(reporter->dump_fmsg); ++ reporter->dump_fmsg = NULL; ++} ++ ++static int devlink_health_do_dump(struct devlink_health_reporter *reporter, ++ void *priv_ctx, ++ struct netlink_ext_ack *extack) ++{ ++ int err; ++ ++ if (!reporter->ops->dump) ++ return 0; ++ ++ if (reporter->dump_fmsg) ++ return 0; ++ ++ reporter->dump_fmsg = devlink_fmsg_alloc(); ++ if (!reporter->dump_fmsg) { ++ err = -ENOMEM; ++ return err; ++ } ++ ++ err = devlink_fmsg_obj_nest_start(reporter->dump_fmsg); ++ if (err) ++ goto dump_err; ++ ++ err = reporter->ops->dump(reporter, reporter->dump_fmsg, ++ priv_ctx, extack); ++ if (err) ++ goto dump_err; ++ ++ err = devlink_fmsg_obj_nest_end(reporter->dump_fmsg); ++ if (err) ++ goto dump_err; ++ ++ reporter->dump_ts = jiffies; ++ reporter->dump_real_ts = ktime_get_real_ns(); ++ ++ return 0; ++ ++dump_err: ++ devlink_health_dump_clear(reporter); ++ return err; ++} ++ ++int devlink_health_report(struct devlink_health_reporter *reporter, ++ const char *msg, void *priv_ctx) ++{ ++ enum devlink_health_reporter_state prev_health_state; ++ struct devlink *devlink = reporter->devlink; ++ unsigned long recover_ts_threshold; ++ int ret; ++ ++ /* write a log message of the current error */ ++ WARN_ON(!msg); ++ trace_devlink_health_report(devlink, reporter->ops->name, msg); ++ reporter->error_count++; ++ prev_health_state = reporter->health_state; ++ reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_ERROR; ++ devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER); ++ ++ /* abort if the previous error wasn't recovered */ ++ recover_ts_threshold = reporter->last_recovery_ts + ++ msecs_to_jiffies(reporter->graceful_period); ++ if (reporter->auto_recover && ++ (prev_health_state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY || ++ (reporter->last_recovery_ts && reporter->recovery_count && ++ time_is_after_jiffies(recover_ts_threshold)))) { ++ trace_devlink_health_recover_aborted(devlink, ++ reporter->ops->name, ++ reporter->health_state, ++ jiffies - ++ reporter->last_recovery_ts); ++ return -ECANCELED; ++ } ++ ++ reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_ERROR; ++ ++ if (reporter->auto_dump) { ++ mutex_lock(&reporter->dump_lock); ++ /* store current dump of current error, for later analysis */ ++ devlink_health_do_dump(reporter, priv_ctx, NULL); ++ mutex_unlock(&reporter->dump_lock); ++ } ++ ++ if (!reporter->auto_recover) ++ return 0; ++ ++ devl_lock(devlink); ++ ret = devlink_health_reporter_recover(reporter, priv_ctx, NULL); ++ devl_unlock(devlink); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(devlink_health_report); ++ ++static struct devlink_health_reporter * ++devlink_health_reporter_get_from_attrs(struct devlink *devlink, ++ struct nlattr **attrs) ++{ ++ struct devlink_health_reporter *reporter; ++ struct devlink_port *devlink_port; ++ char *reporter_name; ++ ++ if (!attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME]) ++ return NULL; ++ ++ reporter_name = nla_data(attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME]); ++ devlink_port = devlink_port_get_from_attrs(devlink, attrs); ++ if (IS_ERR(devlink_port)) { ++ mutex_lock(&devlink->reporters_lock); ++ reporter = devlink_health_reporter_find_by_name(devlink, reporter_name); ++ if (reporter) ++ refcount_inc(&reporter->refcount); ++ mutex_unlock(&devlink->reporters_lock); ++ } else { ++ mutex_lock(&devlink_port->reporters_lock); ++ reporter = devlink_port_health_reporter_find_by_name(devlink_port, reporter_name); ++ if (reporter) ++ refcount_inc(&reporter->refcount); ++ mutex_unlock(&devlink_port->reporters_lock); ++ } ++ ++ return reporter; ++} ++ ++static struct devlink_health_reporter * ++devlink_health_reporter_get_from_info(struct devlink *devlink, ++ struct genl_info *info) ++{ ++ return devlink_health_reporter_get_from_attrs(devlink, info->attrs); ++} ++ ++static struct devlink_health_reporter * ++devlink_health_reporter_get_from_cb(struct netlink_callback *cb) ++{ ++ const struct genl_dumpit_info *info = genl_dumpit_info(cb); ++ struct devlink_health_reporter *reporter; ++ struct nlattr **attrs = info->attrs; ++ struct devlink *devlink; ++ ++ devlink = devlink_get_from_attrs(sock_net(cb->skb->sk), attrs); ++ if (IS_ERR(devlink)) ++ return NULL; ++ ++ reporter = devlink_health_reporter_get_from_attrs(devlink, attrs); ++ devlink_put(devlink); ++ return reporter; ++} ++ ++void ++devlink_health_reporter_state_update(struct devlink_health_reporter *reporter, ++ enum devlink_health_reporter_state state) ++{ ++ if (WARN_ON(state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY && ++ state != DEVLINK_HEALTH_REPORTER_STATE_ERROR)) ++ return; ++ ++ if (reporter->health_state == state) ++ return; ++ ++ reporter->health_state = state; ++ trace_devlink_health_reporter_state_update(reporter->devlink, ++ reporter->ops->name, state); ++ devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER); ++} ++EXPORT_SYMBOL_GPL(devlink_health_reporter_state_update); ++ ++static int devlink_nl_cmd_health_reporter_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_health_reporter *reporter; ++ struct sk_buff *msg; ++ int err; ++ ++ reporter = devlink_health_reporter_get_from_info(devlink, info); ++ if (!reporter) ++ return -EINVAL; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) { ++ err = -ENOMEM; ++ goto out; ++ } ++ ++ err = devlink_nl_health_reporter_fill(msg, reporter, ++ DEVLINK_CMD_HEALTH_REPORTER_GET, ++ info->snd_portid, info->snd_seq, ++ 0); ++ if (err) { ++ nlmsg_free(msg); ++ goto out; ++ } ++ ++ err = genlmsg_reply(msg, info); ++out: ++ devlink_health_reporter_put(reporter); ++ return err; ++} ++ ++static int ++devlink_nl_cmd_health_reporter_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink_health_reporter *reporter; ++ struct devlink_port *port; ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ mutex_lock(&devlink->reporters_lock); ++ list_for_each_entry(reporter, &devlink->reporter_list, ++ list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_health_reporter_fill( ++ msg, reporter, DEVLINK_CMD_HEALTH_REPORTER_GET, ++ NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, ++ NLM_F_MULTI); ++ if (err) { ++ mutex_unlock(&devlink->reporters_lock); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ mutex_unlock(&devlink->reporters_lock); ++ devlink_put(devlink); ++ } ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(port, &devlink->port_list, list) { ++ mutex_lock(&port->reporters_lock); ++ list_for_each_entry(reporter, &port->reporter_list, list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_health_reporter_fill( ++ msg, reporter, ++ DEVLINK_CMD_HEALTH_REPORTER_GET, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, NLM_F_MULTI); ++ if (err) { ++ mutex_unlock(&port->reporters_lock); ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ mutex_unlock(&port->reporters_lock); ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int ++devlink_nl_cmd_health_reporter_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_health_reporter *reporter; ++ int err; ++ ++ reporter = devlink_health_reporter_get_from_info(devlink, info); ++ if (!reporter) ++ return -EINVAL; ++ ++ if (!reporter->ops->recover && ++ (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] || ++ info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER])) { ++ err = -EOPNOTSUPP; ++ goto out; ++ } ++ if (!reporter->ops->dump && ++ info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP]) { ++ err = -EOPNOTSUPP; ++ goto out; ++ } ++ ++ if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD]) ++ reporter->graceful_period = ++ nla_get_u64(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD]); ++ ++ if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]) ++ reporter->auto_recover = ++ nla_get_u8(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]); ++ ++ if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP]) ++ reporter->auto_dump = ++ nla_get_u8(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP]); ++ ++ devlink_health_reporter_put(reporter); ++ return 0; ++out: ++ devlink_health_reporter_put(reporter); ++ return err; ++} ++ ++static int devlink_nl_cmd_health_reporter_recover_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_health_reporter *reporter; ++ int err; ++ ++ reporter = devlink_health_reporter_get_from_info(devlink, info); ++ if (!reporter) ++ return -EINVAL; ++ ++ err = devlink_health_reporter_recover(reporter, NULL, info->extack); ++ ++ devlink_health_reporter_put(reporter); ++ return err; ++} ++ ++static int devlink_nl_cmd_health_reporter_diagnose_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_health_reporter *reporter; ++ struct devlink_fmsg *fmsg; ++ int err; ++ ++ reporter = devlink_health_reporter_get_from_info(devlink, info); ++ if (!reporter) ++ return -EINVAL; ++ ++ if (!reporter->ops->diagnose) { ++ devlink_health_reporter_put(reporter); ++ return -EOPNOTSUPP; ++ } ++ ++ fmsg = devlink_fmsg_alloc(); ++ if (!fmsg) { ++ devlink_health_reporter_put(reporter); ++ return -ENOMEM; ++ } ++ ++ err = devlink_fmsg_obj_nest_start(fmsg); ++ if (err) ++ goto out; ++ ++ err = reporter->ops->diagnose(reporter, fmsg, info->extack); ++ if (err) ++ goto out; ++ ++ err = devlink_fmsg_obj_nest_end(fmsg); ++ if (err) ++ goto out; ++ ++ err = devlink_fmsg_snd(fmsg, info, ++ DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE, 0); ++ ++out: ++ devlink_fmsg_free(fmsg); ++ devlink_health_reporter_put(reporter); ++ return err; ++} ++ ++static int ++devlink_nl_cmd_health_reporter_dump_get_dumpit(struct sk_buff *skb, ++ struct netlink_callback *cb) ++{ ++ struct devlink_health_reporter *reporter; ++ u64 start = cb->args[0]; ++ int err; ++ ++ reporter = devlink_health_reporter_get_from_cb(cb); ++ if (!reporter) ++ return -EINVAL; ++ ++ if (!reporter->ops->dump) { ++ err = -EOPNOTSUPP; ++ goto out; ++ } ++ mutex_lock(&reporter->dump_lock); ++ if (!start) { ++ err = devlink_health_do_dump(reporter, NULL, cb->extack); ++ if (err) ++ goto unlock; ++ cb->args[1] = reporter->dump_ts; ++ } ++ if (!reporter->dump_fmsg || cb->args[1] != reporter->dump_ts) { ++ NL_SET_ERR_MSG_MOD(cb->extack, "Dump trampled, please retry"); ++ err = -EAGAIN; ++ goto unlock; ++ } ++ ++ err = devlink_fmsg_dumpit(reporter->dump_fmsg, skb, cb, ++ DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET); ++unlock: ++ mutex_unlock(&reporter->dump_lock); ++out: ++ devlink_health_reporter_put(reporter); ++ return err; ++} ++ ++static int ++devlink_nl_cmd_health_reporter_dump_clear_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_health_reporter *reporter; ++ ++ reporter = devlink_health_reporter_get_from_info(devlink, info); ++ if (!reporter) ++ return -EINVAL; ++ ++ if (!reporter->ops->dump) { ++ devlink_health_reporter_put(reporter); ++ return -EOPNOTSUPP; ++ } ++ ++ mutex_lock(&reporter->dump_lock); ++ devlink_health_dump_clear(reporter); ++ mutex_unlock(&reporter->dump_lock); ++ devlink_health_reporter_put(reporter); ++ return 0; ++} ++ ++static int devlink_nl_cmd_health_reporter_test_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink *devlink = info->user_ptr[0]; ++ struct devlink_health_reporter *reporter; ++ int err; ++ ++ reporter = devlink_health_reporter_get_from_info(devlink, info); ++ if (!reporter) ++ return -EINVAL; ++ ++ if (!reporter->ops->test) { ++ devlink_health_reporter_put(reporter); ++ return -EOPNOTSUPP; ++ } ++ ++ err = reporter->ops->test(reporter, info->extack); ++ ++ devlink_health_reporter_put(reporter); ++ return err; ++} ++ ++struct devlink_stats { ++ u64_stats_t rx_bytes; ++ u64_stats_t rx_packets; ++ struct u64_stats_sync syncp; ++}; ++ ++/** ++ * struct devlink_trap_policer_item - Packet trap policer attributes. ++ * @policer: Immutable packet trap policer attributes. ++ * @rate: Rate in packets / sec. ++ * @burst: Burst size in packets. ++ * @list: trap_policer_list member. ++ * ++ * Describes packet trap policer attributes. Created by devlink during trap ++ * policer registration. ++ */ ++struct devlink_trap_policer_item { ++ const struct devlink_trap_policer *policer; ++ u64 rate; ++ u64 burst; ++ struct list_head list; ++}; ++ ++/** ++ * struct devlink_trap_group_item - Packet trap group attributes. ++ * @group: Immutable packet trap group attributes. ++ * @policer_item: Associated policer item. Can be NULL. ++ * @list: trap_group_list member. ++ * @stats: Trap group statistics. ++ * ++ * Describes packet trap group attributes. Created by devlink during trap ++ * group registration. ++ */ ++struct devlink_trap_group_item { ++ const struct devlink_trap_group *group; ++ struct devlink_trap_policer_item *policer_item; ++ struct list_head list; ++ struct devlink_stats __percpu *stats; ++}; ++ ++/** ++ * struct devlink_trap_item - Packet trap attributes. ++ * @trap: Immutable packet trap attributes. ++ * @group_item: Associated group item. ++ * @list: trap_list member. ++ * @action: Trap action. ++ * @stats: Trap statistics. ++ * @priv: Driver private information. ++ * ++ * Describes both mutable and immutable packet trap attributes. Created by ++ * devlink during trap registration and used for all trap related operations. ++ */ ++struct devlink_trap_item { ++ const struct devlink_trap *trap; ++ struct devlink_trap_group_item *group_item; ++ struct list_head list; ++ enum devlink_trap_action action; ++ struct devlink_stats __percpu *stats; ++ void *priv; ++}; ++ ++static struct devlink_trap_policer_item * ++devlink_trap_policer_item_lookup(struct devlink *devlink, u32 id) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ ++ list_for_each_entry(policer_item, &devlink->trap_policer_list, list) { ++ if (policer_item->policer->id == id) ++ return policer_item; ++ } ++ ++ return NULL; ++} ++ ++static struct devlink_trap_item * ++devlink_trap_item_lookup(struct devlink *devlink, const char *name) ++{ ++ struct devlink_trap_item *trap_item; ++ ++ list_for_each_entry(trap_item, &devlink->trap_list, list) { ++ if (!strcmp(trap_item->trap->name, name)) ++ return trap_item; ++ } ++ ++ return NULL; ++} ++ ++static struct devlink_trap_item * ++devlink_trap_item_get_from_info(struct devlink *devlink, ++ struct genl_info *info) ++{ ++ struct nlattr *attr; ++ ++ if (!info->attrs[DEVLINK_ATTR_TRAP_NAME]) ++ return NULL; ++ attr = info->attrs[DEVLINK_ATTR_TRAP_NAME]; ++ ++ return devlink_trap_item_lookup(devlink, nla_data(attr)); ++} ++ ++static int ++devlink_trap_action_get_from_info(struct genl_info *info, ++ enum devlink_trap_action *p_trap_action) ++{ ++ u8 val; ++ ++ val = nla_get_u8(info->attrs[DEVLINK_ATTR_TRAP_ACTION]); ++ switch (val) { ++ case DEVLINK_TRAP_ACTION_DROP: ++ case DEVLINK_TRAP_ACTION_TRAP: ++ case DEVLINK_TRAP_ACTION_MIRROR: ++ *p_trap_action = val; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int devlink_trap_metadata_put(struct sk_buff *msg, ++ const struct devlink_trap *trap) ++{ ++ struct nlattr *attr; ++ ++ attr = nla_nest_start(msg, DEVLINK_ATTR_TRAP_METADATA); ++ if (!attr) ++ return -EMSGSIZE; ++ ++ if ((trap->metadata_cap & DEVLINK_TRAP_METADATA_TYPE_F_IN_PORT) && ++ nla_put_flag(msg, DEVLINK_ATTR_TRAP_METADATA_TYPE_IN_PORT)) ++ goto nla_put_failure; ++ if ((trap->metadata_cap & DEVLINK_TRAP_METADATA_TYPE_F_FA_COOKIE) && ++ nla_put_flag(msg, DEVLINK_ATTR_TRAP_METADATA_TYPE_FA_COOKIE)) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, attr); ++ ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, attr); ++ return -EMSGSIZE; ++} ++ ++static void devlink_trap_stats_read(struct devlink_stats __percpu *trap_stats, ++ struct devlink_stats *stats) ++{ ++ int i; ++ ++ memset(stats, 0, sizeof(*stats)); ++ for_each_possible_cpu(i) { ++ struct devlink_stats *cpu_stats; ++ u64 rx_packets, rx_bytes; ++ unsigned int start; ++ ++ cpu_stats = per_cpu_ptr(trap_stats, i); ++ do { ++ start = u64_stats_fetch_begin_irq(&cpu_stats->syncp); ++ rx_packets = u64_stats_read(&cpu_stats->rx_packets); ++ rx_bytes = u64_stats_read(&cpu_stats->rx_bytes); ++ } while (u64_stats_fetch_retry_irq(&cpu_stats->syncp, start)); ++ ++ u64_stats_add(&stats->rx_packets, rx_packets); ++ u64_stats_add(&stats->rx_bytes, rx_bytes); ++ } ++} ++ ++static int ++devlink_trap_group_stats_put(struct sk_buff *msg, ++ struct devlink_stats __percpu *trap_stats) ++{ ++ struct devlink_stats stats; ++ struct nlattr *attr; ++ ++ devlink_trap_stats_read(trap_stats, &stats); ++ ++ attr = nla_nest_start(msg, DEVLINK_ATTR_STATS); ++ if (!attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_PACKETS, ++ u64_stats_read(&stats.rx_packets), ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_BYTES, ++ u64_stats_read(&stats.rx_bytes), ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, attr); ++ ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_trap_stats_put(struct sk_buff *msg, struct devlink *devlink, ++ const struct devlink_trap_item *trap_item) ++{ ++ struct devlink_stats stats; ++ struct nlattr *attr; ++ u64 drops = 0; ++ int err; ++ ++ if (devlink->ops->trap_drop_counter_get) { ++ err = devlink->ops->trap_drop_counter_get(devlink, ++ trap_item->trap, ++ &drops); ++ if (err) ++ return err; ++ } ++ ++ devlink_trap_stats_read(trap_item->stats, &stats); ++ ++ attr = nla_nest_start(msg, DEVLINK_ATTR_STATS); ++ if (!attr) ++ return -EMSGSIZE; ++ ++ if (devlink->ops->trap_drop_counter_get && ++ nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_DROPPED, drops, ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_PACKETS, ++ u64_stats_read(&stats.rx_packets), ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_BYTES, ++ u64_stats_read(&stats.rx_bytes), ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, attr); ++ ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, attr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_trap_fill(struct sk_buff *msg, struct devlink *devlink, ++ const struct devlink_trap_item *trap_item, ++ enum devlink_command cmd, u32 portid, u32 seq, ++ int flags) ++{ ++ struct devlink_trap_group_item *group_item = trap_item->group_item; ++ void *hdr; ++ int err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ ++ if (nla_put_string(msg, DEVLINK_ATTR_TRAP_GROUP_NAME, ++ group_item->group->name)) ++ goto nla_put_failure; ++ ++ if (nla_put_string(msg, DEVLINK_ATTR_TRAP_NAME, trap_item->trap->name)) ++ goto nla_put_failure; ++ ++ if (nla_put_u8(msg, DEVLINK_ATTR_TRAP_TYPE, trap_item->trap->type)) ++ goto nla_put_failure; ++ ++ if (trap_item->trap->generic && ++ nla_put_flag(msg, DEVLINK_ATTR_TRAP_GENERIC)) ++ goto nla_put_failure; ++ ++ if (nla_put_u8(msg, DEVLINK_ATTR_TRAP_ACTION, trap_item->action)) ++ goto nla_put_failure; ++ ++ err = devlink_trap_metadata_put(msg, trap_item->trap); ++ if (err) ++ goto nla_put_failure; ++ ++ err = devlink_trap_stats_put(msg, devlink, trap_item); ++ if (err) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_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_trap_item *trap_item; ++ struct sk_buff *msg; ++ int err; ++ ++ if (list_empty(&devlink->trap_list)) ++ return -EOPNOTSUPP; ++ ++ trap_item = devlink_trap_item_get_from_info(devlink, info); ++ if (!trap_item) { ++ NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap"); ++ return -ENOENT; ++ } ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_trap_fill(msg, devlink, trap_item, ++ DEVLINK_CMD_TRAP_NEW, info->snd_portid, ++ info->snd_seq, 0); ++ if (err) ++ goto err_trap_fill; ++ ++ return genlmsg_reply(msg, info); ++ ++err_trap_fill: ++ nlmsg_free(msg); ++ return err; ++} ++ ++static int devlink_nl_cmd_trap_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ struct devlink_trap_item *trap_item; ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(trap_item, &devlink->trap_list, list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_trap_fill(msg, devlink, trap_item, ++ DEVLINK_CMD_TRAP_NEW, ++ NETLINK_CB(cb->skb).portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI); ++ if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int __devlink_trap_action_set(struct devlink *devlink, ++ struct devlink_trap_item *trap_item, ++ enum devlink_trap_action trap_action, ++ struct netlink_ext_ack *extack) ++{ ++ int err; ++ ++ if (trap_item->action != trap_action && ++ trap_item->trap->type != DEVLINK_TRAP_TYPE_DROP) { ++ NL_SET_ERR_MSG_MOD(extack, "Cannot change action of non-drop traps. Skipping"); ++ return 0; ++ } ++ ++ err = devlink->ops->trap_action_set(devlink, trap_item->trap, ++ trap_action, extack); ++ if (err) ++ return err; ++ ++ trap_item->action = trap_action; ++ ++ return 0; ++} ++ ++static int devlink_trap_action_set(struct devlink *devlink, ++ struct devlink_trap_item *trap_item, ++ struct genl_info *info) ++{ ++ enum devlink_trap_action trap_action; ++ int err; ++ ++ if (!info->attrs[DEVLINK_ATTR_TRAP_ACTION]) ++ return 0; ++ ++ err = devlink_trap_action_get_from_info(info, &trap_action); ++ if (err) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Invalid trap action"); ++ return -EINVAL; ++ } ++ ++ return __devlink_trap_action_set(devlink, trap_item, trap_action, ++ info->extack); ++} ++ ++static int devlink_nl_cmd_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_trap_item *trap_item; ++ ++ if (list_empty(&devlink->trap_list)) ++ return -EOPNOTSUPP; ++ ++ trap_item = devlink_trap_item_get_from_info(devlink, info); ++ if (!trap_item) { ++ NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap"); ++ return -ENOENT; ++ } ++ ++ return devlink_trap_action_set(devlink, trap_item, info); ++} ++ ++static struct devlink_trap_group_item * ++devlink_trap_group_item_lookup(struct devlink *devlink, const char *name) ++{ ++ struct devlink_trap_group_item *group_item; ++ ++ list_for_each_entry(group_item, &devlink->trap_group_list, list) { ++ if (!strcmp(group_item->group->name, name)) ++ return group_item; ++ } ++ ++ return NULL; ++} ++ ++static struct devlink_trap_group_item * ++devlink_trap_group_item_lookup_by_id(struct devlink *devlink, u16 id) ++{ ++ struct devlink_trap_group_item *group_item; ++ ++ list_for_each_entry(group_item, &devlink->trap_group_list, list) { ++ if (group_item->group->id == id) ++ return group_item; ++ } ++ ++ return NULL; ++} ++ ++static struct devlink_trap_group_item * ++devlink_trap_group_item_get_from_info(struct devlink *devlink, ++ struct genl_info *info) ++{ ++ char *name; ++ ++ if (!info->attrs[DEVLINK_ATTR_TRAP_GROUP_NAME]) ++ return NULL; ++ name = nla_data(info->attrs[DEVLINK_ATTR_TRAP_GROUP_NAME]); ++ ++ return devlink_trap_group_item_lookup(devlink, name); ++} ++ ++static int ++devlink_nl_trap_group_fill(struct sk_buff *msg, struct devlink *devlink, ++ const struct devlink_trap_group_item *group_item, ++ enum devlink_command cmd, u32 portid, u32 seq, ++ int flags) ++{ ++ void *hdr; ++ int err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ ++ if (nla_put_string(msg, DEVLINK_ATTR_TRAP_GROUP_NAME, ++ group_item->group->name)) ++ goto nla_put_failure; ++ ++ if (group_item->group->generic && ++ nla_put_flag(msg, DEVLINK_ATTR_TRAP_GENERIC)) ++ goto nla_put_failure; ++ ++ if (group_item->policer_item && ++ nla_put_u32(msg, DEVLINK_ATTR_TRAP_POLICER_ID, ++ group_item->policer_item->policer->id)) ++ goto nla_put_failure; ++ ++ err = devlink_trap_group_stats_put(msg, group_item->stats); ++ if (err) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_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_trap_group_item *group_item; ++ struct sk_buff *msg; ++ int err; ++ ++ if (list_empty(&devlink->trap_group_list)) ++ return -EOPNOTSUPP; ++ ++ group_item = devlink_trap_group_item_get_from_info(devlink, info); ++ if (!group_item) { ++ NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap group"); ++ return -ENOENT; ++ } ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_trap_group_fill(msg, devlink, group_item, ++ DEVLINK_CMD_TRAP_GROUP_NEW, ++ info->snd_portid, info->snd_seq, 0); ++ if (err) ++ goto err_trap_group_fill; ++ ++ return genlmsg_reply(msg, info); ++ ++err_trap_group_fill: ++ nlmsg_free(msg); ++ return err; ++} ++ ++static int devlink_nl_cmd_trap_group_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ enum devlink_command cmd = DEVLINK_CMD_TRAP_GROUP_NEW; ++ struct devlink_trap_group_item *group_item; ++ u32 portid = NETLINK_CB(cb->skb).portid; ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(group_item, &devlink->trap_group_list, ++ list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_trap_group_fill(msg, devlink, ++ group_item, cmd, ++ portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI); ++ if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int ++__devlink_trap_group_action_set(struct devlink *devlink, ++ struct devlink_trap_group_item *group_item, ++ enum devlink_trap_action trap_action, ++ struct netlink_ext_ack *extack) ++{ ++ const char *group_name = group_item->group->name; ++ struct devlink_trap_item *trap_item; ++ int err; ++ ++ if (devlink->ops->trap_group_action_set) { ++ err = devlink->ops->trap_group_action_set(devlink, group_item->group, ++ trap_action, extack); ++ if (err) ++ return err; ++ ++ list_for_each_entry(trap_item, &devlink->trap_list, list) { ++ if (strcmp(trap_item->group_item->group->name, group_name)) ++ continue; ++ if (trap_item->action != trap_action && ++ trap_item->trap->type != DEVLINK_TRAP_TYPE_DROP) ++ continue; ++ trap_item->action = trap_action; ++ } ++ ++ return 0; ++ } ++ ++ list_for_each_entry(trap_item, &devlink->trap_list, list) { ++ if (strcmp(trap_item->group_item->group->name, group_name)) ++ continue; ++ err = __devlink_trap_action_set(devlink, trap_item, ++ trap_action, extack); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int ++devlink_trap_group_action_set(struct devlink *devlink, ++ struct devlink_trap_group_item *group_item, ++ struct genl_info *info, bool *p_modified) ++{ ++ enum devlink_trap_action trap_action; ++ int err; ++ ++ if (!info->attrs[DEVLINK_ATTR_TRAP_ACTION]) ++ return 0; ++ ++ err = devlink_trap_action_get_from_info(info, &trap_action); ++ if (err) { ++ NL_SET_ERR_MSG_MOD(info->extack, "Invalid trap action"); ++ return -EINVAL; ++ } ++ ++ err = __devlink_trap_group_action_set(devlink, group_item, trap_action, ++ info->extack); ++ if (err) ++ return err; ++ ++ *p_modified = true; ++ ++ return 0; ++} ++ ++static int devlink_trap_group_set(struct devlink *devlink, ++ struct devlink_trap_group_item *group_item, ++ struct genl_info *info) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ struct netlink_ext_ack *extack = info->extack; ++ const struct devlink_trap_policer *policer; ++ struct nlattr **attrs = info->attrs; ++ int err; ++ ++ if (!attrs[DEVLINK_ATTR_TRAP_POLICER_ID]) ++ return 0; ++ ++ if (!devlink->ops->trap_group_set) ++ return -EOPNOTSUPP; ++ ++ policer_item = group_item->policer_item; ++ if (attrs[DEVLINK_ATTR_TRAP_POLICER_ID]) { ++ u32 policer_id; ++ ++ policer_id = nla_get_u32(attrs[DEVLINK_ATTR_TRAP_POLICER_ID]); ++ policer_item = devlink_trap_policer_item_lookup(devlink, ++ policer_id); ++ if (policer_id && !policer_item) { ++ NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap policer"); ++ return -ENOENT; ++ } ++ } ++ policer = policer_item ? policer_item->policer : NULL; ++ ++ err = devlink->ops->trap_group_set(devlink, group_item->group, policer, ++ extack); ++ if (err) ++ return err; ++ ++ group_item->policer_item = policer_item; ++ ++ return 0; ++} ++ ++static int devlink_nl_cmd_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_trap_group_item *group_item; ++ bool modified = false; ++ int err; ++ ++ if (list_empty(&devlink->trap_group_list)) ++ return -EOPNOTSUPP; ++ ++ group_item = devlink_trap_group_item_get_from_info(devlink, info); ++ if (!group_item) { ++ NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap group"); ++ return -ENOENT; ++ } ++ ++ err = devlink_trap_group_action_set(devlink, group_item, info, ++ &modified); ++ if (err) ++ return err; ++ ++ err = devlink_trap_group_set(devlink, group_item, info); ++ if (err) ++ goto err_trap_group_set; ++ ++ return 0; ++ ++err_trap_group_set: ++ if (modified) ++ NL_SET_ERR_MSG_MOD(extack, "Trap group set failed, but some changes were committed already"); ++ return err; ++} ++ ++static struct devlink_trap_policer_item * ++devlink_trap_policer_item_get_from_info(struct devlink *devlink, ++ struct genl_info *info) ++{ ++ u32 id; ++ ++ if (!info->attrs[DEVLINK_ATTR_TRAP_POLICER_ID]) ++ return NULL; ++ id = nla_get_u32(info->attrs[DEVLINK_ATTR_TRAP_POLICER_ID]); ++ ++ return devlink_trap_policer_item_lookup(devlink, id); ++} ++ ++static int ++devlink_trap_policer_stats_put(struct sk_buff *msg, struct devlink *devlink, ++ const struct devlink_trap_policer *policer) ++{ ++ struct nlattr *attr; ++ u64 drops; ++ int err; ++ ++ if (!devlink->ops->trap_policer_counter_get) ++ return 0; ++ ++ err = devlink->ops->trap_policer_counter_get(devlink, policer, &drops); ++ if (err) ++ return err; ++ ++ attr = nla_nest_start(msg, DEVLINK_ATTR_STATS); ++ if (!attr) ++ return -EMSGSIZE; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_DROPPED, drops, ++ DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ nla_nest_end(msg, attr); ++ ++ return 0; ++ ++nla_put_failure: ++ nla_nest_cancel(msg, attr); ++ return -EMSGSIZE; ++} ++ ++static int ++devlink_nl_trap_policer_fill(struct sk_buff *msg, struct devlink *devlink, ++ const struct devlink_trap_policer_item *policer_item, ++ enum devlink_command cmd, u32 portid, u32 seq, ++ int flags) ++{ ++ void *hdr; ++ int err; ++ ++ hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); ++ if (!hdr) ++ return -EMSGSIZE; ++ ++ if (devlink_nl_put_handle(msg, devlink)) ++ goto nla_put_failure; ++ ++ if (nla_put_u32(msg, DEVLINK_ATTR_TRAP_POLICER_ID, ++ policer_item->policer->id)) ++ goto nla_put_failure; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_TRAP_POLICER_RATE, ++ policer_item->rate, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ if (nla_put_u64_64bit(msg, DEVLINK_ATTR_TRAP_POLICER_BURST, ++ policer_item->burst, DEVLINK_ATTR_PAD)) ++ goto nla_put_failure; ++ ++ err = devlink_trap_policer_stats_put(msg, devlink, ++ policer_item->policer); ++ if (err) ++ goto nla_put_failure; ++ ++ genlmsg_end(msg, hdr); ++ ++ return 0; ++ ++nla_put_failure: ++ genlmsg_cancel(msg, hdr); ++ return -EMSGSIZE; ++} ++ ++static int devlink_nl_cmd_trap_policer_get_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ struct netlink_ext_ack *extack = info->extack; ++ struct devlink *devlink = info->user_ptr[0]; ++ struct sk_buff *msg; ++ int err; ++ ++ if (list_empty(&devlink->trap_policer_list)) ++ return -EOPNOTSUPP; ++ ++ policer_item = devlink_trap_policer_item_get_from_info(devlink, info); ++ if (!policer_item) { ++ NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap policer"); ++ return -ENOENT; ++ } ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return -ENOMEM; ++ ++ err = devlink_nl_trap_policer_fill(msg, devlink, policer_item, ++ DEVLINK_CMD_TRAP_POLICER_NEW, ++ info->snd_portid, info->snd_seq, 0); ++ if (err) ++ goto err_trap_policer_fill; ++ ++ return genlmsg_reply(msg, info); ++ ++err_trap_policer_fill: ++ nlmsg_free(msg); ++ return err; ++} ++ ++static int devlink_nl_cmd_trap_policer_get_dumpit(struct sk_buff *msg, ++ struct netlink_callback *cb) ++{ ++ enum devlink_command cmd = DEVLINK_CMD_TRAP_POLICER_NEW; ++ struct devlink_trap_policer_item *policer_item; ++ u32 portid = NETLINK_CB(cb->skb).portid; ++ struct devlink *devlink; ++ int start = cb->args[0]; ++ unsigned long index; ++ int idx = 0; ++ int err; ++ ++ devlinks_xa_for_each_registered_get(sock_net(msg->sk), index, devlink) { ++ devl_lock(devlink); ++ list_for_each_entry(policer_item, &devlink->trap_policer_list, ++ list) { ++ if (idx < start) { ++ idx++; ++ continue; ++ } ++ err = devlink_nl_trap_policer_fill(msg, devlink, ++ policer_item, cmd, ++ portid, ++ cb->nlh->nlmsg_seq, ++ NLM_F_MULTI); ++ if (err) { ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ goto out; ++ } ++ idx++; ++ } ++ devl_unlock(devlink); ++ devlink_put(devlink); ++ } ++out: ++ cb->args[0] = idx; ++ return msg->len; ++} ++ ++static int ++devlink_trap_policer_set(struct devlink *devlink, ++ struct devlink_trap_policer_item *policer_item, ++ struct genl_info *info) ++{ ++ struct netlink_ext_ack *extack = info->extack; ++ struct nlattr **attrs = info->attrs; ++ u64 rate, burst; ++ int err; ++ ++ rate = policer_item->rate; ++ burst = policer_item->burst; ++ ++ if (attrs[DEVLINK_ATTR_TRAP_POLICER_RATE]) ++ rate = nla_get_u64(attrs[DEVLINK_ATTR_TRAP_POLICER_RATE]); ++ ++ if (attrs[DEVLINK_ATTR_TRAP_POLICER_BURST]) ++ burst = nla_get_u64(attrs[DEVLINK_ATTR_TRAP_POLICER_BURST]); ++ ++ if (rate < policer_item->policer->min_rate) { ++ NL_SET_ERR_MSG_MOD(extack, "Policer rate lower than limit"); ++ return -EINVAL; ++ } ++ ++ if (rate > policer_item->policer->max_rate) { ++ NL_SET_ERR_MSG_MOD(extack, "Policer rate higher than limit"); ++ return -EINVAL; ++ } ++ ++ if (burst < policer_item->policer->min_burst) { ++ NL_SET_ERR_MSG_MOD(extack, "Policer burst size lower than limit"); ++ return -EINVAL; ++ } ++ ++ if (burst > policer_item->policer->max_burst) { ++ NL_SET_ERR_MSG_MOD(extack, "Policer burst size higher than limit"); ++ return -EINVAL; ++ } ++ ++ err = devlink->ops->trap_policer_set(devlink, policer_item->policer, ++ rate, burst, info->extack); ++ if (err) ++ return err; ++ ++ policer_item->rate = rate; ++ policer_item->burst = burst; ++ ++ return 0; ++} ++ ++static int devlink_nl_cmd_trap_policer_set_doit(struct sk_buff *skb, ++ struct genl_info *info) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ struct netlink_ext_ack *extack = info->extack; ++ struct devlink *devlink = info->user_ptr[0]; ++ ++ if (list_empty(&devlink->trap_policer_list)) ++ return -EOPNOTSUPP; ++ ++ if (!devlink->ops->trap_policer_set) ++ return -EOPNOTSUPP; ++ ++ policer_item = devlink_trap_policer_item_get_from_info(devlink, info); ++ if (!policer_item) { ++ NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap policer"); ++ return -ENOENT; ++ } ++ ++ return devlink_trap_policer_set(devlink, policer_item, info); ++} ++ ++static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = { ++ [DEVLINK_ATTR_UNSPEC] = { .strict_start_type = ++ DEVLINK_ATTR_TRAP_POLICER_ID }, ++ [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_PORT_INDEX] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_PORT_TYPE] = NLA_POLICY_RANGE(NLA_U16, DEVLINK_PORT_TYPE_AUTO, ++ DEVLINK_PORT_TYPE_IB), ++ [DEVLINK_ATTR_PORT_SPLIT_COUNT] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_SB_INDEX] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_SB_POOL_INDEX] = { .type = NLA_U16 }, ++ [DEVLINK_ATTR_SB_POOL_TYPE] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_SB_POOL_SIZE] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_SB_THRESHOLD] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_SB_TC_INDEX] = { .type = NLA_U16 }, ++ [DEVLINK_ATTR_ESWITCH_MODE] = NLA_POLICY_RANGE(NLA_U16, DEVLINK_ESWITCH_MODE_LEGACY, ++ DEVLINK_ESWITCH_MODE_SWITCHDEV), ++ [DEVLINK_ATTR_ESWITCH_INLINE_MODE] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_ESWITCH_ENCAP_MODE] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_DPIPE_TABLE_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_RESOURCE_ID] = { .type = NLA_U64}, ++ [DEVLINK_ATTR_RESOURCE_SIZE] = { .type = NLA_U64}, ++ [DEVLINK_ATTR_PARAM_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_PARAM_TYPE] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_PARAM_VALUE_CMODE] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_REGION_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_REGION_SNAPSHOT_ID] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_REGION_CHUNK_ADDR] = { .type = NLA_U64 }, ++ [DEVLINK_ATTR_REGION_CHUNK_LEN] = { .type = NLA_U64 }, ++ [DEVLINK_ATTR_HEALTH_REPORTER_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] = { .type = NLA_U64 }, ++ [DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_FLASH_UPDATE_COMPONENT] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_FLASH_UPDATE_OVERWRITE_MASK] = ++ NLA_POLICY_BITFIELD32(DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS), ++ [DEVLINK_ATTR_TRAP_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_TRAP_ACTION] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_TRAP_GROUP_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_NETNS_PID] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_NETNS_FD] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_NETNS_ID] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP] = { .type = NLA_U8 }, ++ [DEVLINK_ATTR_TRAP_POLICER_ID] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_TRAP_POLICER_RATE] = { .type = NLA_U64 }, ++ [DEVLINK_ATTR_TRAP_POLICER_BURST] = { .type = NLA_U64 }, ++ [DEVLINK_ATTR_PORT_FUNCTION] = { .type = NLA_NESTED }, ++ [DEVLINK_ATTR_RELOAD_ACTION] = NLA_POLICY_RANGE(NLA_U8, DEVLINK_RELOAD_ACTION_DRIVER_REINIT, ++ DEVLINK_RELOAD_ACTION_MAX), ++ [DEVLINK_ATTR_RELOAD_LIMITS] = NLA_POLICY_BITFIELD32(DEVLINK_RELOAD_LIMITS_VALID_MASK), ++ [DEVLINK_ATTR_PORT_FLAVOUR] = { .type = NLA_U16 }, ++ [DEVLINK_ATTR_PORT_PCI_PF_NUMBER] = { .type = NLA_U16 }, ++ [DEVLINK_ATTR_PORT_PCI_SF_NUMBER] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_PORT_CONTROLLER_NUMBER] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_RATE_TYPE] = { .type = NLA_U16 }, ++ [DEVLINK_ATTR_RATE_TX_SHARE] = { .type = NLA_U64 }, ++ [DEVLINK_ATTR_RATE_TX_MAX] = { .type = NLA_U64 }, ++ [DEVLINK_ATTR_RATE_NODE_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_LINECARD_INDEX] = { .type = NLA_U32 }, ++ [DEVLINK_ATTR_LINECARD_TYPE] = { .type = NLA_NUL_STRING }, ++ [DEVLINK_ATTR_SELFTESTS] = { .type = NLA_NESTED }, ++}; ++ ++static const struct genl_small_ops devlink_nl_ops[] = { ++ { ++ .cmd = DEVLINK_CMD_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_get_doit, ++ .dumpit = devlink_nl_cmd_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_port_get_doit, ++ .dumpit = devlink_nl_cmd_port_get_dumpit, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_port_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_RATE_GET, ++ .doit = devlink_nl_cmd_rate_get_doit, ++ .dumpit = devlink_nl_cmd_rate_get_dumpit, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_RATE, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_RATE_SET, ++ .doit = devlink_nl_cmd_rate_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_RATE, ++ }, ++ { ++ .cmd = DEVLINK_CMD_RATE_NEW, ++ .doit = devlink_nl_cmd_rate_new_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_RATE_DEL, ++ .doit = devlink_nl_cmd_rate_del_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_RATE_NODE, ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_SPLIT, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_port_split_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_UNSPLIT, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_port_unsplit_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_NEW, ++ .doit = devlink_nl_cmd_port_new_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_DEL, ++ .doit = devlink_nl_cmd_port_del_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_LINECARD_GET, ++ .doit = devlink_nl_cmd_linecard_get_doit, ++ .dumpit = devlink_nl_cmd_linecard_get_dumpit, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_LINECARD, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_LINECARD_SET, ++ .doit = devlink_nl_cmd_linecard_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_LINECARD, ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_get_doit, ++ .dumpit = devlink_nl_cmd_sb_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_POOL_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_pool_get_doit, ++ .dumpit = devlink_nl_cmd_sb_pool_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_POOL_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_pool_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_PORT_POOL_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_port_pool_get_doit, ++ .dumpit = devlink_nl_cmd_sb_port_pool_get_dumpit, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_PORT_POOL_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_port_pool_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_TC_POOL_BIND_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_tc_pool_bind_get_doit, ++ .dumpit = devlink_nl_cmd_sb_tc_pool_bind_get_dumpit, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_TC_POOL_BIND_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_tc_pool_bind_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_OCC_SNAPSHOT, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_occ_snapshot_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_SB_OCC_MAX_CLEAR, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_sb_occ_max_clear_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_ESWITCH_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_eswitch_get_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_ESWITCH_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_eswitch_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_DPIPE_TABLE_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_dpipe_table_get, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_DPIPE_ENTRIES_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_dpipe_entries_get, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_DPIPE_HEADERS_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_dpipe_headers_get, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_DPIPE_TABLE_COUNTERS_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_dpipe_table_counters_set, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_RESOURCE_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_resource_set, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_RESOURCE_DUMP, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_resource_dump, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_RELOAD, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_reload, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_PARAM_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_param_get_doit, ++ .dumpit = devlink_nl_cmd_param_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_PARAM_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_param_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_PARAM_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_port_param_get_doit, ++ .dumpit = devlink_nl_cmd_port_param_get_dumpit, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_PORT_PARAM_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_port_param_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_REGION_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_region_get_doit, ++ .dumpit = devlink_nl_cmd_region_get_dumpit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_REGION_NEW, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_region_new, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_REGION_DEL, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_region_del, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_REGION_READ, ++ .validate = GENL_DONT_VALIDATE_STRICT | ++ GENL_DONT_VALIDATE_DUMP_STRICT, ++ .dumpit = devlink_nl_cmd_region_read_dumpit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_INFO_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_info_get_doit, ++ .dumpit = devlink_nl_cmd_info_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_HEALTH_REPORTER_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_health_reporter_get_doit, ++ .dumpit = devlink_nl_cmd_health_reporter_get_dumpit, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_HEALTH_REPORTER_SET, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_health_reporter_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_HEALTH_REPORTER_RECOVER, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_health_reporter_recover_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_health_reporter_diagnose_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET, ++ .validate = GENL_DONT_VALIDATE_STRICT | ++ GENL_DONT_VALIDATE_DUMP_STRICT, ++ .dumpit = devlink_nl_cmd_health_reporter_dump_get_dumpit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_HEALTH_REPORTER_DUMP_CLEAR, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_health_reporter_dump_clear_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_HEALTH_REPORTER_TEST, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_health_reporter_test_doit, ++ .flags = GENL_ADMIN_PERM, ++ .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT, ++ }, ++ { ++ .cmd = DEVLINK_CMD_FLASH_UPDATE, ++ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, ++ .doit = devlink_nl_cmd_flash_update, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_TRAP_GET, ++ .doit = devlink_nl_cmd_trap_get_doit, ++ .dumpit = devlink_nl_cmd_trap_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_TRAP_SET, ++ .doit = devlink_nl_cmd_trap_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_TRAP_GROUP_GET, ++ .doit = devlink_nl_cmd_trap_group_get_doit, ++ .dumpit = devlink_nl_cmd_trap_group_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_TRAP_GROUP_SET, ++ .doit = devlink_nl_cmd_trap_group_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_TRAP_POLICER_GET, ++ .doit = devlink_nl_cmd_trap_policer_get_doit, ++ .dumpit = devlink_nl_cmd_trap_policer_get_dumpit, ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_TRAP_POLICER_SET, ++ .doit = devlink_nl_cmd_trap_policer_set_doit, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = DEVLINK_CMD_SELFTESTS_GET, ++ .doit = devlink_nl_cmd_selftests_get_doit, ++ .dumpit = devlink_nl_cmd_selftests_get_dumpit ++ /* can be retrieved by unprivileged users */ ++ }, ++ { ++ .cmd = DEVLINK_CMD_SELFTESTS_RUN, ++ .doit = devlink_nl_cmd_selftests_run, ++ .flags = GENL_ADMIN_PERM, ++ }, ++}; ++ ++static struct genl_family devlink_nl_family __ro_after_init = { ++ .name = DEVLINK_GENL_NAME, ++ .version = DEVLINK_GENL_VERSION, ++ .maxattr = DEVLINK_ATTR_MAX, ++ .policy = devlink_nl_policy, ++ .netnsok = true, ++ .parallel_ops = true, ++ .pre_doit = devlink_nl_pre_doit, ++ .post_doit = devlink_nl_post_doit, ++ .module = THIS_MODULE, ++ .small_ops = devlink_nl_ops, ++ .n_small_ops = ARRAY_SIZE(devlink_nl_ops), ++ .resv_start_op = DEVLINK_CMD_SELFTESTS_RUN + 1, ++ .mcgrps = devlink_nl_mcgrps, ++ .n_mcgrps = ARRAY_SIZE(devlink_nl_mcgrps), ++}; ++ ++static bool devlink_reload_actions_valid(const struct devlink_ops *ops) ++{ ++ const struct devlink_reload_combination *comb; ++ int i; ++ ++ if (!devlink_reload_supported(ops)) { ++ if (WARN_ON(ops->reload_actions)) ++ return false; ++ return true; ++ } ++ ++ if (WARN_ON(!ops->reload_actions || ++ ops->reload_actions & BIT(DEVLINK_RELOAD_ACTION_UNSPEC) || ++ ops->reload_actions >= BIT(__DEVLINK_RELOAD_ACTION_MAX))) ++ return false; ++ ++ if (WARN_ON(ops->reload_limits & BIT(DEVLINK_RELOAD_LIMIT_UNSPEC) || ++ ops->reload_limits >= BIT(__DEVLINK_RELOAD_LIMIT_MAX))) ++ return false; ++ ++ for (i = 0; i < ARRAY_SIZE(devlink_reload_invalid_combinations); i++) { ++ comb = &devlink_reload_invalid_combinations[i]; ++ if (ops->reload_actions == BIT(comb->action) && ++ ops->reload_limits == BIT(comb->limit)) ++ return false; ++ } ++ return true; ++} ++ ++/** ++ * devlink_set_features - Set devlink supported features ++ * ++ * @devlink: devlink ++ * @features: devlink support features ++ * ++ * This interface allows us to set reload ops separatelly from ++ * the devlink_alloc. ++ */ ++void devlink_set_features(struct devlink *devlink, u64 features) ++{ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ ++ WARN_ON(features & DEVLINK_F_RELOAD && ++ !devlink_reload_supported(devlink->ops)); ++ devlink->features = features; ++} ++EXPORT_SYMBOL_GPL(devlink_set_features); ++ ++/** ++ * devlink_alloc_ns - Allocate new devlink instance resources ++ * in specific namespace ++ * ++ * @ops: ops ++ * @priv_size: size of user private data ++ * @net: net namespace ++ * @dev: parent device ++ * ++ * Allocate new devlink instance resources, including devlink index ++ * and name. ++ */ ++struct devlink *devlink_alloc_ns(const struct devlink_ops *ops, ++ size_t priv_size, struct net *net, ++ struct device *dev) ++{ ++ struct devlink *devlink; ++ static u32 last_id; ++ int ret; ++ ++ WARN_ON(!ops || !dev); ++ if (!devlink_reload_actions_valid(ops)) ++ return NULL; ++ ++ devlink = kzalloc(sizeof(*devlink) + priv_size, GFP_KERNEL); ++ if (!devlink) ++ return NULL; ++ ++ ret = xa_alloc_cyclic(&devlinks, &devlink->index, devlink, xa_limit_31b, ++ &last_id, GFP_KERNEL); ++ if (ret < 0) { ++ kfree(devlink); ++ return NULL; ++ } ++ ++ devlink->dev = dev; ++ devlink->ops = ops; ++ xa_init_flags(&devlink->snapshot_ids, XA_FLAGS_ALLOC); ++ write_pnet(&devlink->_net, net); ++ INIT_LIST_HEAD(&devlink->port_list); ++ INIT_LIST_HEAD(&devlink->rate_list); ++ INIT_LIST_HEAD(&devlink->linecard_list); ++ INIT_LIST_HEAD(&devlink->sb_list); ++ INIT_LIST_HEAD_RCU(&devlink->dpipe_table_list); ++ INIT_LIST_HEAD(&devlink->resource_list); ++ INIT_LIST_HEAD(&devlink->param_list); ++ INIT_LIST_HEAD(&devlink->region_list); ++ INIT_LIST_HEAD(&devlink->reporter_list); ++ INIT_LIST_HEAD(&devlink->trap_list); ++ INIT_LIST_HEAD(&devlink->trap_group_list); ++ INIT_LIST_HEAD(&devlink->trap_policer_list); ++ lockdep_register_key(&devlink->lock_key); ++ mutex_init(&devlink->lock); ++ lockdep_set_class(&devlink->lock, &devlink->lock_key); ++ mutex_init(&devlink->reporters_lock); ++ mutex_init(&devlink->linecards_lock); ++ refcount_set(&devlink->refcount, 1); ++ init_completion(&devlink->comp); ++ ++ return devlink; ++} ++EXPORT_SYMBOL_GPL(devlink_alloc_ns); ++ ++static void ++devlink_trap_policer_notify(struct devlink *devlink, ++ const struct devlink_trap_policer_item *policer_item, ++ enum devlink_command cmd); ++static void ++devlink_trap_group_notify(struct devlink *devlink, ++ const struct devlink_trap_group_item *group_item, ++ enum devlink_command cmd); ++static void devlink_trap_notify(struct devlink *devlink, ++ const struct devlink_trap_item *trap_item, ++ enum devlink_command cmd); ++ ++static void devlink_notify_register(struct devlink *devlink) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ struct devlink_trap_group_item *group_item; ++ struct devlink_param_item *param_item; ++ struct devlink_trap_item *trap_item; ++ struct devlink_port *devlink_port; ++ struct devlink_linecard *linecard; ++ struct devlink_rate *rate_node; ++ struct devlink_region *region; ++ ++ devlink_notify(devlink, DEVLINK_CMD_NEW); ++ list_for_each_entry(linecard, &devlink->linecard_list, list) ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ ++ list_for_each_entry(devlink_port, &devlink->port_list, list) ++ devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); ++ ++ list_for_each_entry(policer_item, &devlink->trap_policer_list, list) ++ devlink_trap_policer_notify(devlink, policer_item, ++ DEVLINK_CMD_TRAP_POLICER_NEW); ++ ++ list_for_each_entry(group_item, &devlink->trap_group_list, list) ++ devlink_trap_group_notify(devlink, group_item, ++ DEVLINK_CMD_TRAP_GROUP_NEW); ++ ++ list_for_each_entry(trap_item, &devlink->trap_list, list) ++ devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_NEW); ++ ++ list_for_each_entry(rate_node, &devlink->rate_list, list) ++ devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); ++ ++ list_for_each_entry(region, &devlink->region_list, list) ++ devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW); ++ ++ list_for_each_entry(param_item, &devlink->param_list, list) ++ devlink_param_notify(devlink, 0, param_item, ++ DEVLINK_CMD_PARAM_NEW); ++} ++ ++static void devlink_notify_unregister(struct devlink *devlink) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ struct devlink_trap_group_item *group_item; ++ struct devlink_param_item *param_item; ++ struct devlink_trap_item *trap_item; ++ struct devlink_port *devlink_port; ++ struct devlink_linecard *linecard; ++ struct devlink_rate *rate_node; ++ struct devlink_region *region; ++ ++ list_for_each_entry_reverse(param_item, &devlink->param_list, list) ++ devlink_param_notify(devlink, 0, param_item, ++ DEVLINK_CMD_PARAM_DEL); ++ ++ list_for_each_entry_reverse(region, &devlink->region_list, list) ++ devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_DEL); ++ ++ list_for_each_entry_reverse(rate_node, &devlink->rate_list, list) ++ devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); ++ ++ list_for_each_entry_reverse(trap_item, &devlink->trap_list, list) ++ devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_DEL); ++ ++ list_for_each_entry_reverse(group_item, &devlink->trap_group_list, list) ++ devlink_trap_group_notify(devlink, group_item, ++ DEVLINK_CMD_TRAP_GROUP_DEL); ++ list_for_each_entry_reverse(policer_item, &devlink->trap_policer_list, ++ list) ++ devlink_trap_policer_notify(devlink, policer_item, ++ DEVLINK_CMD_TRAP_POLICER_DEL); ++ ++ list_for_each_entry_reverse(devlink_port, &devlink->port_list, list) ++ devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_DEL); ++ list_for_each_entry_reverse(linecard, &devlink->linecard_list, list) ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_DEL); ++ devlink_notify(devlink, DEVLINK_CMD_DEL); ++} ++ ++/** ++ * devlink_register - Register devlink instance ++ * ++ * @devlink: devlink ++ */ ++void devlink_register(struct devlink *devlink) ++{ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ /* Make sure that we are in .probe() routine */ ++ ++ xa_set_mark(&devlinks, devlink->index, DEVLINK_REGISTERED); ++ devlink_notify_register(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_register); ++ ++/** ++ * devlink_unregister - Unregister devlink instance ++ * ++ * @devlink: devlink ++ */ ++void devlink_unregister(struct devlink *devlink) ++{ ++ ASSERT_DEVLINK_REGISTERED(devlink); ++ /* Make sure that we are in .remove() routine */ ++ ++ xa_set_mark(&devlinks, devlink->index, DEVLINK_UNREGISTERING); ++ devlink_put(devlink); ++ wait_for_completion(&devlink->comp); ++ ++ devlink_notify_unregister(devlink); ++ xa_clear_mark(&devlinks, devlink->index, DEVLINK_REGISTERED); ++ xa_clear_mark(&devlinks, devlink->index, DEVLINK_UNREGISTERING); ++} ++EXPORT_SYMBOL_GPL(devlink_unregister); ++ ++/** ++ * devlink_free - Free devlink instance resources ++ * ++ * @devlink: devlink ++ */ ++void devlink_free(struct devlink *devlink) ++{ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ ++ mutex_destroy(&devlink->linecards_lock); ++ mutex_destroy(&devlink->reporters_lock); ++ mutex_destroy(&devlink->lock); ++ lockdep_unregister_key(&devlink->lock_key); ++ WARN_ON(!list_empty(&devlink->trap_policer_list)); ++ WARN_ON(!list_empty(&devlink->trap_group_list)); ++ WARN_ON(!list_empty(&devlink->trap_list)); ++ WARN_ON(!list_empty(&devlink->reporter_list)); ++ WARN_ON(!list_empty(&devlink->region_list)); ++ WARN_ON(!list_empty(&devlink->param_list)); ++ WARN_ON(!list_empty(&devlink->resource_list)); ++ WARN_ON(!list_empty(&devlink->dpipe_table_list)); ++ WARN_ON(!list_empty(&devlink->sb_list)); ++ WARN_ON(!list_empty(&devlink->rate_list)); ++ WARN_ON(!list_empty(&devlink->linecard_list)); ++ WARN_ON(!list_empty(&devlink->port_list)); ++ ++ xa_destroy(&devlink->snapshot_ids); ++ xa_erase(&devlinks, devlink->index); ++ ++ kfree(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_free); ++ ++static void devlink_port_type_warn(struct work_struct *work) ++{ ++ struct devlink_port *port = container_of(to_delayed_work(work), ++ struct devlink_port, ++ type_warn_dw); ++ dev_warn(port->devlink->dev, "Type was not set for devlink port."); ++} ++ ++static bool devlink_port_type_should_warn(struct devlink_port *devlink_port) ++{ ++ /* Ignore CPU and DSA flavours. */ ++ return devlink_port->attrs.flavour != DEVLINK_PORT_FLAVOUR_CPU && ++ devlink_port->attrs.flavour != DEVLINK_PORT_FLAVOUR_DSA && ++ devlink_port->attrs.flavour != DEVLINK_PORT_FLAVOUR_UNUSED; ++} ++ ++#define DEVLINK_PORT_TYPE_WARN_TIMEOUT (HZ * 3600) ++ ++static void devlink_port_type_warn_schedule(struct devlink_port *devlink_port) ++{ ++ if (!devlink_port_type_should_warn(devlink_port)) ++ return; ++ /* Schedule a work to WARN in case driver does not set port ++ * type within timeout. ++ */ ++ schedule_delayed_work(&devlink_port->type_warn_dw, ++ DEVLINK_PORT_TYPE_WARN_TIMEOUT); ++} ++ ++static void devlink_port_type_warn_cancel(struct devlink_port *devlink_port) ++{ ++ if (!devlink_port_type_should_warn(devlink_port)) ++ return; ++ cancel_delayed_work_sync(&devlink_port->type_warn_dw); ++} ++ ++/** ++ * devlink_port_init() - Init devlink port ++ * ++ * @devlink: devlink ++ * @devlink_port: devlink port ++ * ++ * Initialize essencial stuff that is needed for functions ++ * that may be called before devlink port registration. ++ * Call to this function is optional and not needed ++ * in case the driver does not use such functions. ++ */ ++void devlink_port_init(struct devlink *devlink, ++ struct devlink_port *devlink_port) ++{ ++ if (devlink_port->initialized) ++ return; ++ devlink_port->devlink = devlink; ++ INIT_LIST_HEAD(&devlink_port->region_list); ++ devlink_port->initialized = true; ++} ++EXPORT_SYMBOL_GPL(devlink_port_init); ++ ++/** ++ * devlink_port_fini() - Deinitialize devlink port ++ * ++ * @devlink_port: devlink port ++ * ++ * Deinitialize essencial stuff that is in use for functions ++ * that may be called after devlink port unregistration. ++ * Call to this function is optional and not needed ++ * in case the driver does not use such functions. ++ */ ++void devlink_port_fini(struct devlink_port *devlink_port) ++{ ++ WARN_ON(!list_empty(&devlink_port->region_list)); ++} ++EXPORT_SYMBOL_GPL(devlink_port_fini); ++ ++/** ++ * devl_port_register() - Register devlink port ++ * ++ * @devlink: devlink ++ * @devlink_port: devlink port ++ * @port_index: driver-specific numerical identifier of the port ++ * ++ * Register devlink port with provided port index. User can use ++ * any indexing, even hw-related one. devlink_port structure ++ * is convenient to be embedded inside user driver private structure. ++ * Note that the caller should take care of zeroing the devlink_port ++ * structure. ++ */ ++int devl_port_register(struct devlink *devlink, ++ struct devlink_port *devlink_port, ++ unsigned int port_index) ++{ ++ devl_assert_locked(devlink); ++ ++ if (devlink_port_index_exists(devlink, port_index)) ++ return -EEXIST; ++ ++ ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); ++ ++ devlink_port_init(devlink, devlink_port); ++ devlink_port->registered = true; ++ devlink_port->index = port_index; ++ spin_lock_init(&devlink_port->type_lock); ++ INIT_LIST_HEAD(&devlink_port->reporter_list); ++ mutex_init(&devlink_port->reporters_lock); ++ list_add_tail(&devlink_port->list, &devlink->port_list); ++ ++ INIT_DELAYED_WORK(&devlink_port->type_warn_dw, &devlink_port_type_warn); ++ devlink_port_type_warn_schedule(devlink_port); ++ devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devl_port_register); ++ ++/** ++ * devlink_port_register - Register devlink port ++ * ++ * @devlink: devlink ++ * @devlink_port: devlink port ++ * @port_index: driver-specific numerical identifier of the port ++ * ++ * Register devlink port with provided port index. User can use ++ * any indexing, even hw-related one. devlink_port structure ++ * is convenient to be embedded inside user driver private structure. ++ * Note that the caller should take care of zeroing the devlink_port ++ * structure. ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++int devlink_port_register(struct devlink *devlink, ++ struct devlink_port *devlink_port, ++ unsigned int port_index) ++{ ++ int err; ++ ++ devl_lock(devlink); ++ err = devl_port_register(devlink, devlink_port, port_index); ++ devl_unlock(devlink); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_port_register); ++ ++/** ++ * devl_port_unregister() - Unregister devlink port ++ * ++ * @devlink_port: devlink port ++ */ ++void devl_port_unregister(struct devlink_port *devlink_port) ++{ ++ lockdep_assert_held(&devlink_port->devlink->lock); ++ ++ devlink_port_type_warn_cancel(devlink_port); ++ devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_DEL); ++ list_del(&devlink_port->list); ++ WARN_ON(!list_empty(&devlink_port->reporter_list)); ++ mutex_destroy(&devlink_port->reporters_lock); ++ devlink_port->registered = false; ++} ++EXPORT_SYMBOL_GPL(devl_port_unregister); ++ ++/** ++ * devlink_port_unregister - Unregister devlink port ++ * ++ * @devlink_port: devlink port ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++void devlink_port_unregister(struct devlink_port *devlink_port) ++{ ++ struct devlink *devlink = devlink_port->devlink; ++ ++ devl_lock(devlink); ++ devl_port_unregister(devlink_port); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_port_unregister); ++ ++static void __devlink_port_type_set(struct devlink_port *devlink_port, ++ enum devlink_port_type type, ++ void *type_dev) ++{ ++ ASSERT_DEVLINK_PORT_REGISTERED(devlink_port); ++ ++ devlink_port_type_warn_cancel(devlink_port); ++ spin_lock_bh(&devlink_port->type_lock); ++ devlink_port->type = type; ++ devlink_port->type_dev = type_dev; ++ spin_unlock_bh(&devlink_port->type_lock); ++ devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); ++} ++ ++static void devlink_port_type_netdev_checks(struct devlink_port *devlink_port, ++ struct net_device *netdev) ++{ ++ const struct net_device_ops *ops = netdev->netdev_ops; ++ ++ /* If driver registers devlink port, it should set devlink port ++ * attributes accordingly so the compat functions are called ++ * and the original ops are not used. ++ */ ++ if (ops->ndo_get_phys_port_name) { ++ /* Some drivers use the same set of ndos for netdevs ++ * that have devlink_port registered and also for ++ * those who don't. Make sure that ndo_get_phys_port_name ++ * returns -EOPNOTSUPP here in case it is defined. ++ * Warn if not. ++ */ ++ char name[IFNAMSIZ]; ++ int err; ++ ++ err = ops->ndo_get_phys_port_name(netdev, name, sizeof(name)); ++ WARN_ON(err != -EOPNOTSUPP); ++ } ++ if (ops->ndo_get_port_parent_id) { ++ /* Some drivers use the same set of ndos for netdevs ++ * that have devlink_port registered and also for ++ * those who don't. Make sure that ndo_get_port_parent_id ++ * returns -EOPNOTSUPP here in case it is defined. ++ * Warn if not. ++ */ ++ struct netdev_phys_item_id ppid; ++ int err; ++ ++ err = ops->ndo_get_port_parent_id(netdev, &ppid); ++ WARN_ON(err != -EOPNOTSUPP); ++ } ++} ++ ++/** ++ * devlink_port_type_eth_set - Set port type to Ethernet ++ * ++ * @devlink_port: devlink port ++ * @netdev: related netdevice ++ */ ++void devlink_port_type_eth_set(struct devlink_port *devlink_port, ++ struct net_device *netdev) ++{ ++ if (netdev) ++ devlink_port_type_netdev_checks(devlink_port, netdev); ++ else ++ dev_warn(devlink_port->devlink->dev, ++ "devlink port type for port %d set to Ethernet without a software interface reference, device type not supported by the kernel?\n", ++ devlink_port->index); ++ ++ __devlink_port_type_set(devlink_port, DEVLINK_PORT_TYPE_ETH, netdev); ++} ++EXPORT_SYMBOL_GPL(devlink_port_type_eth_set); ++ ++/** ++ * devlink_port_type_ib_set - Set port type to InfiniBand ++ * ++ * @devlink_port: devlink port ++ * @ibdev: related IB device ++ */ ++void devlink_port_type_ib_set(struct devlink_port *devlink_port, ++ struct ib_device *ibdev) ++{ ++ __devlink_port_type_set(devlink_port, DEVLINK_PORT_TYPE_IB, ibdev); ++} ++EXPORT_SYMBOL_GPL(devlink_port_type_ib_set); ++ ++/** ++ * devlink_port_type_clear - Clear port type ++ * ++ * @devlink_port: devlink port ++ */ ++void devlink_port_type_clear(struct devlink_port *devlink_port) ++{ ++ __devlink_port_type_set(devlink_port, DEVLINK_PORT_TYPE_NOTSET, NULL); ++ devlink_port_type_warn_schedule(devlink_port); ++} ++EXPORT_SYMBOL_GPL(devlink_port_type_clear); ++ ++static int __devlink_port_attrs_set(struct devlink_port *devlink_port, ++ enum devlink_port_flavour flavour) ++{ ++ struct devlink_port_attrs *attrs = &devlink_port->attrs; ++ ++ devlink_port->attrs_set = true; ++ attrs->flavour = flavour; ++ if (attrs->switch_id.id_len) { ++ devlink_port->switch_port = true; ++ if (WARN_ON(attrs->switch_id.id_len > MAX_PHYS_ITEM_ID_LEN)) ++ attrs->switch_id.id_len = MAX_PHYS_ITEM_ID_LEN; ++ } else { ++ devlink_port->switch_port = false; ++ } ++ return 0; ++} ++ ++/** ++ * devlink_port_attrs_set - Set port attributes ++ * ++ * @devlink_port: devlink port ++ * @attrs: devlink port attrs ++ */ ++void devlink_port_attrs_set(struct devlink_port *devlink_port, ++ struct devlink_port_attrs *attrs) ++{ ++ int ret; ++ ++ ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); ++ ++ devlink_port->attrs = *attrs; ++ ret = __devlink_port_attrs_set(devlink_port, attrs->flavour); ++ if (ret) ++ return; ++ WARN_ON(attrs->splittable && attrs->split); ++} ++EXPORT_SYMBOL_GPL(devlink_port_attrs_set); ++ ++/** ++ * devlink_port_attrs_pci_pf_set - Set PCI PF port attributes ++ * ++ * @devlink_port: devlink port ++ * @controller: associated controller number for the devlink port instance ++ * @pf: associated PF for the devlink port instance ++ * @external: indicates if the port is for an external controller ++ */ ++void devlink_port_attrs_pci_pf_set(struct devlink_port *devlink_port, u32 controller, ++ u16 pf, bool external) ++{ ++ struct devlink_port_attrs *attrs = &devlink_port->attrs; ++ int ret; ++ ++ ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); ++ ++ ret = __devlink_port_attrs_set(devlink_port, ++ DEVLINK_PORT_FLAVOUR_PCI_PF); ++ if (ret) ++ return; ++ attrs->pci_pf.controller = controller; ++ attrs->pci_pf.pf = pf; ++ attrs->pci_pf.external = external; ++} ++EXPORT_SYMBOL_GPL(devlink_port_attrs_pci_pf_set); ++ ++/** ++ * devlink_port_attrs_pci_vf_set - Set PCI VF port attributes ++ * ++ * @devlink_port: devlink port ++ * @controller: associated controller number for the devlink port instance ++ * @pf: associated PF for the devlink port instance ++ * @vf: associated VF of a PF for the devlink port instance ++ * @external: indicates if the port is for an external controller ++ */ ++void devlink_port_attrs_pci_vf_set(struct devlink_port *devlink_port, u32 controller, ++ u16 pf, u16 vf, bool external) ++{ ++ struct devlink_port_attrs *attrs = &devlink_port->attrs; ++ int ret; ++ ++ ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); ++ ++ ret = __devlink_port_attrs_set(devlink_port, ++ DEVLINK_PORT_FLAVOUR_PCI_VF); ++ if (ret) ++ return; ++ attrs->pci_vf.controller = controller; ++ attrs->pci_vf.pf = pf; ++ attrs->pci_vf.vf = vf; ++ attrs->pci_vf.external = external; ++} ++EXPORT_SYMBOL_GPL(devlink_port_attrs_pci_vf_set); ++ ++/** ++ * devlink_port_attrs_pci_sf_set - Set PCI SF port attributes ++ * ++ * @devlink_port: devlink port ++ * @controller: associated controller number for the devlink port instance ++ * @pf: associated PF for the devlink port instance ++ * @sf: associated SF of a PF for the devlink port instance ++ * @external: indicates if the port is for an external controller ++ */ ++void devlink_port_attrs_pci_sf_set(struct devlink_port *devlink_port, u32 controller, ++ u16 pf, u32 sf, bool external) ++{ ++ struct devlink_port_attrs *attrs = &devlink_port->attrs; ++ int ret; ++ ++ ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); ++ ++ ret = __devlink_port_attrs_set(devlink_port, ++ DEVLINK_PORT_FLAVOUR_PCI_SF); ++ if (ret) ++ return; ++ attrs->pci_sf.controller = controller; ++ attrs->pci_sf.pf = pf; ++ attrs->pci_sf.sf = sf; ++ attrs->pci_sf.external = external; ++} ++EXPORT_SYMBOL_GPL(devlink_port_attrs_pci_sf_set); ++ ++/** ++ * devl_rate_leaf_create - create devlink rate leaf ++ * @devlink_port: devlink port object to create rate object on ++ * @priv: driver private data ++ * ++ * Create devlink rate object of type leaf on provided @devlink_port. ++ */ ++int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv) ++{ ++ struct devlink *devlink = devlink_port->devlink; ++ struct devlink_rate *devlink_rate; ++ ++ devl_assert_locked(devlink_port->devlink); ++ ++ if (WARN_ON(devlink_port->devlink_rate)) ++ return -EBUSY; ++ ++ devlink_rate = kzalloc(sizeof(*devlink_rate), GFP_KERNEL); ++ if (!devlink_rate) ++ return -ENOMEM; ++ ++ devlink_rate->type = DEVLINK_RATE_TYPE_LEAF; ++ devlink_rate->devlink = devlink; ++ devlink_rate->devlink_port = devlink_port; ++ devlink_rate->priv = priv; ++ list_add_tail(&devlink_rate->list, &devlink->rate_list); ++ devlink_port->devlink_rate = devlink_rate; ++ devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devl_rate_leaf_create); ++ ++/** ++ * devl_rate_leaf_destroy - destroy devlink rate leaf ++ * ++ * @devlink_port: devlink port linked to the rate object ++ * ++ * Destroy the devlink rate object of type leaf on provided @devlink_port. ++ */ ++void devl_rate_leaf_destroy(struct devlink_port *devlink_port) ++{ ++ struct devlink_rate *devlink_rate = devlink_port->devlink_rate; ++ ++ devl_assert_locked(devlink_port->devlink); ++ if (!devlink_rate) ++ return; ++ ++ 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; ++ kfree(devlink_rate); ++} ++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. ++ */ ++void devl_rate_nodes_destroy(struct devlink *devlink) ++{ ++ static struct devlink_rate *devlink_rate, *tmp; ++ const struct devlink_ops *ops = devlink->ops; ++ ++ devl_assert_locked(devlink); ++ ++ list_for_each_entry(devlink_rate, &devlink->rate_list, list) { ++ if (!devlink_rate->parent) ++ continue; ++ ++ refcount_dec(&devlink_rate->parent->refcnt); ++ if (devlink_rate_is_leaf(devlink_rate)) ++ ops->rate_leaf_parent_set(devlink_rate, NULL, devlink_rate->priv, ++ NULL, NULL); ++ else if (devlink_rate_is_node(devlink_rate)) ++ ops->rate_node_parent_set(devlink_rate, NULL, devlink_rate->priv, ++ NULL, NULL); ++ } ++ list_for_each_entry_safe(devlink_rate, tmp, &devlink->rate_list, list) { ++ if (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); ++ } ++ } ++} ++EXPORT_SYMBOL_GPL(devl_rate_nodes_destroy); ++ ++/** ++ * devlink_port_linecard_set - Link port with a linecard ++ * ++ * @devlink_port: devlink port ++ * @linecard: devlink linecard ++ */ ++void devlink_port_linecard_set(struct devlink_port *devlink_port, ++ struct devlink_linecard *linecard) ++{ ++ ASSERT_DEVLINK_PORT_NOT_REGISTERED(devlink_port); ++ ++ devlink_port->linecard = linecard; ++} ++EXPORT_SYMBOL_GPL(devlink_port_linecard_set); ++ ++static int __devlink_port_phys_port_name_get(struct devlink_port *devlink_port, ++ char *name, size_t len) ++{ ++ struct devlink_port_attrs *attrs = &devlink_port->attrs; ++ int n = 0; ++ ++ if (!devlink_port->attrs_set) ++ return -EOPNOTSUPP; ++ ++ switch (attrs->flavour) { ++ case DEVLINK_PORT_FLAVOUR_PHYSICAL: ++ if (devlink_port->linecard) ++ n = snprintf(name, len, "l%u", ++ devlink_port->linecard->index); ++ if (n < len) ++ n += snprintf(name + n, len - n, "p%u", ++ attrs->phys.port_number); ++ if (n < len && attrs->split) ++ n += snprintf(name + n, len - n, "s%u", ++ attrs->phys.split_subport_number); ++ break; ++ case DEVLINK_PORT_FLAVOUR_CPU: ++ case DEVLINK_PORT_FLAVOUR_DSA: ++ case DEVLINK_PORT_FLAVOUR_UNUSED: ++ /* As CPU and DSA ports do not have a netdevice associated ++ * case should not ever happen. ++ */ ++ WARN_ON(1); ++ return -EINVAL; ++ case DEVLINK_PORT_FLAVOUR_PCI_PF: ++ if (attrs->pci_pf.external) { ++ n = snprintf(name, len, "c%u", attrs->pci_pf.controller); ++ if (n >= len) ++ return -EINVAL; ++ len -= n; ++ name += n; ++ } ++ n = snprintf(name, len, "pf%u", attrs->pci_pf.pf); ++ break; ++ case DEVLINK_PORT_FLAVOUR_PCI_VF: ++ if (attrs->pci_vf.external) { ++ n = snprintf(name, len, "c%u", attrs->pci_vf.controller); ++ if (n >= len) ++ return -EINVAL; ++ len -= n; ++ name += n; ++ } ++ n = snprintf(name, len, "pf%uvf%u", ++ attrs->pci_vf.pf, attrs->pci_vf.vf); ++ break; ++ case DEVLINK_PORT_FLAVOUR_PCI_SF: ++ if (attrs->pci_sf.external) { ++ n = snprintf(name, len, "c%u", attrs->pci_sf.controller); ++ if (n >= len) ++ return -EINVAL; ++ len -= n; ++ name += n; ++ } ++ n = snprintf(name, len, "pf%usf%u", attrs->pci_sf.pf, ++ attrs->pci_sf.sf); ++ break; ++ case DEVLINK_PORT_FLAVOUR_VIRTUAL: ++ return -EOPNOTSUPP; ++ } ++ ++ if (n >= len) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static int devlink_linecard_types_init(struct devlink_linecard *linecard) ++{ ++ struct devlink_linecard_type *linecard_type; ++ unsigned int count; ++ int i; ++ ++ count = linecard->ops->types_count(linecard, linecard->priv); ++ linecard->types = kmalloc_array(count, sizeof(*linecard_type), ++ GFP_KERNEL); ++ if (!linecard->types) ++ return -ENOMEM; ++ linecard->types_count = count; ++ ++ for (i = 0; i < count; i++) { ++ linecard_type = &linecard->types[i]; ++ linecard->ops->types_get(linecard, linecard->priv, i, ++ &linecard_type->type, ++ &linecard_type->priv); ++ } ++ return 0; ++} ++ ++static void devlink_linecard_types_fini(struct devlink_linecard *linecard) ++{ ++ kfree(linecard->types); ++} ++ ++/** ++ * devlink_linecard_create - Create devlink linecard ++ * ++ * @devlink: devlink ++ * @linecard_index: driver-specific numerical identifier of the linecard ++ * @ops: linecards ops ++ * @priv: user priv pointer ++ * ++ * Create devlink linecard instance with provided linecard index. ++ * Caller can use any indexing, even hw-related one. ++ * ++ * Return: Line card structure or an ERR_PTR() encoded error code. ++ */ ++struct devlink_linecard * ++devlink_linecard_create(struct devlink *devlink, unsigned int linecard_index, ++ const struct devlink_linecard_ops *ops, void *priv) ++{ ++ struct devlink_linecard *linecard; ++ int err; ++ ++ if (WARN_ON(!ops || !ops->provision || !ops->unprovision || ++ !ops->types_count || !ops->types_get)) ++ return ERR_PTR(-EINVAL); ++ ++ mutex_lock(&devlink->linecards_lock); ++ if (devlink_linecard_index_exists(devlink, linecard_index)) { ++ mutex_unlock(&devlink->linecards_lock); ++ return ERR_PTR(-EEXIST); ++ } ++ ++ linecard = kzalloc(sizeof(*linecard), GFP_KERNEL); ++ if (!linecard) { ++ mutex_unlock(&devlink->linecards_lock); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ linecard->devlink = devlink; ++ linecard->index = linecard_index; ++ linecard->ops = ops; ++ linecard->priv = priv; ++ linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; ++ mutex_init(&linecard->state_lock); ++ ++ err = devlink_linecard_types_init(linecard); ++ if (err) { ++ mutex_destroy(&linecard->state_lock); ++ kfree(linecard); ++ mutex_unlock(&devlink->linecards_lock); ++ return ERR_PTR(err); ++ } ++ ++ list_add_tail(&linecard->list, &devlink->linecard_list); ++ refcount_set(&linecard->refcount, 1); ++ mutex_unlock(&devlink->linecards_lock); ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ return linecard; ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_create); ++ ++/** ++ * devlink_linecard_destroy - Destroy devlink linecard ++ * ++ * @linecard: devlink linecard ++ */ ++void devlink_linecard_destroy(struct devlink_linecard *linecard) ++{ ++ struct devlink *devlink = linecard->devlink; ++ ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_DEL); ++ mutex_lock(&devlink->linecards_lock); ++ list_del(&linecard->list); ++ devlink_linecard_types_fini(linecard); ++ mutex_unlock(&devlink->linecards_lock); ++ devlink_linecard_put(linecard); ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_destroy); ++ ++/** ++ * devlink_linecard_provision_set - Set provisioning on linecard ++ * ++ * @linecard: devlink linecard ++ * @type: linecard type ++ * ++ * This is either called directly from the provision() op call or ++ * as a result of the provision() op call asynchronously. ++ */ ++void devlink_linecard_provision_set(struct devlink_linecard *linecard, ++ const char *type) ++{ ++ mutex_lock(&linecard->state_lock); ++ WARN_ON(linecard->type && strcmp(linecard->type, type)); ++ linecard->state = DEVLINK_LINECARD_STATE_PROVISIONED; ++ linecard->type = type; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_provision_set); ++ ++/** ++ * devlink_linecard_provision_clear - Clear provisioning on linecard ++ * ++ * @linecard: devlink linecard ++ * ++ * This is either called directly from the unprovision() op call or ++ * as a result of the unprovision() op call asynchronously. ++ */ ++void devlink_linecard_provision_clear(struct devlink_linecard *linecard) ++{ ++ mutex_lock(&linecard->state_lock); ++ WARN_ON(linecard->nested_devlink); ++ linecard->state = DEVLINK_LINECARD_STATE_UNPROVISIONED; ++ linecard->type = NULL; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_provision_clear); ++ ++/** ++ * devlink_linecard_provision_fail - Fail provisioning on linecard ++ * ++ * @linecard: devlink linecard ++ * ++ * This is either called directly from the provision() op call or ++ * as a result of the provision() op call asynchronously. ++ */ ++void devlink_linecard_provision_fail(struct devlink_linecard *linecard) ++{ ++ mutex_lock(&linecard->state_lock); ++ WARN_ON(linecard->nested_devlink); ++ linecard->state = DEVLINK_LINECARD_STATE_PROVISIONING_FAILED; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_provision_fail); ++ ++/** ++ * devlink_linecard_activate - Set linecard active ++ * ++ * @linecard: devlink linecard ++ */ ++void devlink_linecard_activate(struct devlink_linecard *linecard) ++{ ++ mutex_lock(&linecard->state_lock); ++ WARN_ON(linecard->state != DEVLINK_LINECARD_STATE_PROVISIONED); ++ linecard->state = DEVLINK_LINECARD_STATE_ACTIVE; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_activate); ++ ++/** ++ * devlink_linecard_deactivate - Set linecard inactive ++ * ++ * @linecard: devlink linecard ++ */ ++void devlink_linecard_deactivate(struct devlink_linecard *linecard) ++{ ++ mutex_lock(&linecard->state_lock); ++ switch (linecard->state) { ++ case DEVLINK_LINECARD_STATE_ACTIVE: ++ linecard->state = DEVLINK_LINECARD_STATE_PROVISIONED; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ break; ++ case DEVLINK_LINECARD_STATE_UNPROVISIONING: ++ /* Line card is being deactivated as part ++ * of unprovisioning flow. ++ */ ++ break; ++ default: ++ WARN_ON(1); ++ break; ++ } ++ mutex_unlock(&linecard->state_lock); ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_deactivate); ++ ++/** ++ * devlink_linecard_nested_dl_set - Attach/detach nested devlink ++ * instance to linecard. ++ * ++ * @linecard: devlink linecard ++ * @nested_devlink: devlink instance to attach or NULL to detach ++ */ ++void devlink_linecard_nested_dl_set(struct devlink_linecard *linecard, ++ struct devlink *nested_devlink) ++{ ++ mutex_lock(&linecard->state_lock); ++ linecard->nested_devlink = nested_devlink; ++ devlink_linecard_notify(linecard, DEVLINK_CMD_LINECARD_NEW); ++ mutex_unlock(&linecard->state_lock); ++} ++EXPORT_SYMBOL_GPL(devlink_linecard_nested_dl_set); ++ ++int devl_sb_register(struct devlink *devlink, unsigned int sb_index, ++ u32 size, u16 ingress_pools_count, ++ u16 egress_pools_count, u16 ingress_tc_count, ++ u16 egress_tc_count) ++{ ++ struct devlink_sb *devlink_sb; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ if (devlink_sb_index_exists(devlink, sb_index)) ++ return -EEXIST; ++ ++ devlink_sb = kzalloc(sizeof(*devlink_sb), GFP_KERNEL); ++ if (!devlink_sb) ++ return -ENOMEM; ++ devlink_sb->index = sb_index; ++ devlink_sb->size = size; ++ devlink_sb->ingress_pools_count = ingress_pools_count; ++ devlink_sb->egress_pools_count = egress_pools_count; ++ devlink_sb->ingress_tc_count = ingress_tc_count; ++ devlink_sb->egress_tc_count = egress_tc_count; ++ list_add_tail(&devlink_sb->list, &devlink->sb_list); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devl_sb_register); ++ ++int devlink_sb_register(struct devlink *devlink, unsigned int sb_index, ++ u32 size, u16 ingress_pools_count, ++ u16 egress_pools_count, u16 ingress_tc_count, ++ u16 egress_tc_count) ++{ ++ int err; ++ ++ devl_lock(devlink); ++ err = devl_sb_register(devlink, sb_index, size, ingress_pools_count, ++ egress_pools_count, ingress_tc_count, ++ egress_tc_count); ++ devl_unlock(devlink); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_sb_register); ++ ++void devl_sb_unregister(struct devlink *devlink, unsigned int sb_index) ++{ ++ struct devlink_sb *devlink_sb; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ devlink_sb = devlink_sb_get_by_index(devlink, sb_index); ++ WARN_ON(!devlink_sb); ++ list_del(&devlink_sb->list); ++ kfree(devlink_sb); ++} ++EXPORT_SYMBOL_GPL(devl_sb_unregister); ++ ++void devlink_sb_unregister(struct devlink *devlink, unsigned int sb_index) ++{ ++ devl_lock(devlink); ++ devl_sb_unregister(devlink, sb_index); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_sb_unregister); ++ ++/** ++ * devl_dpipe_headers_register - register dpipe headers ++ * ++ * @devlink: devlink ++ * @dpipe_headers: dpipe header array ++ * ++ * Register the headers supported by hardware. ++ */ ++void devl_dpipe_headers_register(struct devlink *devlink, ++ struct devlink_dpipe_headers *dpipe_headers) ++{ ++ lockdep_assert_held(&devlink->lock); ++ ++ devlink->dpipe_headers = dpipe_headers; ++} ++EXPORT_SYMBOL_GPL(devl_dpipe_headers_register); ++ ++/** ++ * devl_dpipe_headers_unregister - unregister dpipe headers ++ * ++ * @devlink: devlink ++ * ++ * Unregister the headers supported by hardware. ++ */ ++void devl_dpipe_headers_unregister(struct devlink *devlink) ++{ ++ lockdep_assert_held(&devlink->lock); ++ ++ devlink->dpipe_headers = NULL; ++} ++EXPORT_SYMBOL_GPL(devl_dpipe_headers_unregister); ++ ++/** ++ * devlink_dpipe_table_counter_enabled - check if counter allocation ++ * required ++ * @devlink: devlink ++ * @table_name: tables name ++ * ++ * Used by driver to check if counter allocation is required. ++ * After counter allocation is turned on the table entries ++ * are updated to include counter statistics. ++ * ++ * After that point on the driver must respect the counter ++ * state so that each entry added to the table is added ++ * with a counter. ++ */ ++bool devlink_dpipe_table_counter_enabled(struct devlink *devlink, ++ const char *table_name) ++{ ++ struct devlink_dpipe_table *table; ++ bool enabled; ++ ++ rcu_read_lock(); ++ table = devlink_dpipe_table_find(&devlink->dpipe_table_list, ++ table_name, devlink); ++ enabled = false; ++ if (table) ++ enabled = table->counters_enabled; ++ rcu_read_unlock(); ++ return enabled; ++} ++EXPORT_SYMBOL_GPL(devlink_dpipe_table_counter_enabled); ++ ++/** ++ * devl_dpipe_table_register - register dpipe table ++ * ++ * @devlink: devlink ++ * @table_name: table name ++ * @table_ops: table ops ++ * @priv: priv ++ * @counter_control_extern: external control for counters ++ */ ++int devl_dpipe_table_register(struct devlink *devlink, ++ const char *table_name, ++ struct devlink_dpipe_table_ops *table_ops, ++ void *priv, bool counter_control_extern) ++{ ++ struct devlink_dpipe_table *table; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ if (WARN_ON(!table_ops->size_get)) ++ return -EINVAL; ++ ++ if (devlink_dpipe_table_find(&devlink->dpipe_table_list, table_name, ++ devlink)) ++ return -EEXIST; ++ ++ table = kzalloc(sizeof(*table), GFP_KERNEL); ++ if (!table) ++ return -ENOMEM; ++ ++ table->name = table_name; ++ table->table_ops = table_ops; ++ table->priv = priv; ++ table->counter_control_extern = counter_control_extern; ++ ++ list_add_tail_rcu(&table->list, &devlink->dpipe_table_list); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devl_dpipe_table_register); ++ ++/** ++ * devl_dpipe_table_unregister - unregister dpipe table ++ * ++ * @devlink: devlink ++ * @table_name: table name ++ */ ++void devl_dpipe_table_unregister(struct devlink *devlink, ++ const char *table_name) ++{ ++ struct devlink_dpipe_table *table; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ table = devlink_dpipe_table_find(&devlink->dpipe_table_list, ++ table_name, devlink); ++ if (!table) ++ return; ++ list_del_rcu(&table->list); ++ kfree_rcu(table, rcu); ++} ++EXPORT_SYMBOL_GPL(devl_dpipe_table_unregister); ++ ++/** ++ * devl_resource_register - devlink resource register ++ * ++ * @devlink: devlink ++ * @resource_name: resource's name ++ * @resource_size: resource's size ++ * @resource_id: resource's id ++ * @parent_resource_id: resource's parent id ++ * @size_params: size parameters ++ * ++ * Generic resources should reuse the same names across drivers. ++ * Please see the generic resources list at: ++ * Documentation/networking/devlink/devlink-resource.rst ++ */ ++int devl_resource_register(struct devlink *devlink, ++ const char *resource_name, ++ u64 resource_size, ++ u64 resource_id, ++ u64 parent_resource_id, ++ const struct devlink_resource_size_params *size_params) ++{ ++ struct devlink_resource *resource; ++ struct list_head *resource_list; ++ bool top_hierarchy; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ top_hierarchy = parent_resource_id == DEVLINK_RESOURCE_ID_PARENT_TOP; ++ ++ resource = devlink_resource_find(devlink, NULL, resource_id); ++ if (resource) ++ return -EINVAL; ++ ++ resource = kzalloc(sizeof(*resource), GFP_KERNEL); ++ if (!resource) ++ return -ENOMEM; ++ ++ if (top_hierarchy) { ++ resource_list = &devlink->resource_list; ++ } else { ++ struct devlink_resource *parent_resource; ++ ++ parent_resource = devlink_resource_find(devlink, NULL, ++ parent_resource_id); ++ if (parent_resource) { ++ resource_list = &parent_resource->resource_list; ++ resource->parent = parent_resource; ++ } else { ++ kfree(resource); ++ return -EINVAL; ++ } ++ } ++ ++ resource->name = resource_name; ++ resource->size = resource_size; ++ resource->size_new = resource_size; ++ resource->id = resource_id; ++ resource->size_valid = true; ++ memcpy(&resource->size_params, size_params, ++ sizeof(resource->size_params)); ++ INIT_LIST_HEAD(&resource->resource_list); ++ list_add_tail(&resource->list, resource_list); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devl_resource_register); ++ ++/** ++ * devlink_resource_register - devlink resource register ++ * ++ * @devlink: devlink ++ * @resource_name: resource's name ++ * @resource_size: resource's size ++ * @resource_id: resource's id ++ * @parent_resource_id: resource's parent id ++ * @size_params: size parameters ++ * ++ * Generic resources should reuse the same names across drivers. ++ * Please see the generic resources list at: ++ * Documentation/networking/devlink/devlink-resource.rst ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++int devlink_resource_register(struct devlink *devlink, ++ const char *resource_name, ++ u64 resource_size, ++ u64 resource_id, ++ u64 parent_resource_id, ++ const struct devlink_resource_size_params *size_params) ++{ ++ int err; ++ ++ devl_lock(devlink); ++ err = devl_resource_register(devlink, resource_name, resource_size, ++ resource_id, parent_resource_id, size_params); ++ devl_unlock(devlink); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_resource_register); ++ ++static void devlink_resource_unregister(struct devlink *devlink, ++ struct devlink_resource *resource) ++{ ++ struct devlink_resource *tmp, *child_resource; ++ ++ list_for_each_entry_safe(child_resource, tmp, &resource->resource_list, ++ list) { ++ devlink_resource_unregister(devlink, child_resource); ++ list_del(&child_resource->list); ++ kfree(child_resource); ++ } ++} ++ ++/** ++ * devl_resources_unregister - free all resources ++ * ++ * @devlink: devlink ++ */ ++void devl_resources_unregister(struct devlink *devlink) ++{ ++ struct devlink_resource *tmp, *child_resource; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ list_for_each_entry_safe(child_resource, tmp, &devlink->resource_list, ++ list) { ++ devlink_resource_unregister(devlink, child_resource); ++ list_del(&child_resource->list); ++ kfree(child_resource); ++ } ++} ++EXPORT_SYMBOL_GPL(devl_resources_unregister); ++ ++/** ++ * devlink_resources_unregister - free all resources ++ * ++ * @devlink: devlink ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++void devlink_resources_unregister(struct devlink *devlink) ++{ ++ devl_lock(devlink); ++ devl_resources_unregister(devlink); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_resources_unregister); ++ ++/** ++ * devl_resource_size_get - get and update size ++ * ++ * @devlink: devlink ++ * @resource_id: the requested resource id ++ * @p_resource_size: ptr to update ++ */ ++int devl_resource_size_get(struct devlink *devlink, ++ u64 resource_id, ++ u64 *p_resource_size) ++{ ++ struct devlink_resource *resource; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ resource = devlink_resource_find(devlink, NULL, resource_id); ++ if (!resource) ++ return -EINVAL; ++ *p_resource_size = resource->size_new; ++ resource->size = resource->size_new; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devl_resource_size_get); ++ ++/** ++ * devl_dpipe_table_resource_set - set the resource id ++ * ++ * @devlink: devlink ++ * @table_name: table name ++ * @resource_id: resource id ++ * @resource_units: number of resource's units consumed per table's entry ++ */ ++int devl_dpipe_table_resource_set(struct devlink *devlink, ++ const char *table_name, u64 resource_id, ++ u64 resource_units) ++{ ++ struct devlink_dpipe_table *table; ++ ++ table = devlink_dpipe_table_find(&devlink->dpipe_table_list, ++ table_name, devlink); ++ if (!table) ++ return -EINVAL; ++ ++ table->resource_id = resource_id; ++ table->resource_units = resource_units; ++ table->resource_valid = true; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devl_dpipe_table_resource_set); ++ ++/** ++ * devl_resource_occ_get_register - register occupancy getter ++ * ++ * @devlink: devlink ++ * @resource_id: resource id ++ * @occ_get: occupancy getter callback ++ * @occ_get_priv: occupancy getter callback priv ++ */ ++void devl_resource_occ_get_register(struct devlink *devlink, ++ u64 resource_id, ++ devlink_resource_occ_get_t *occ_get, ++ void *occ_get_priv) ++{ ++ struct devlink_resource *resource; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ resource = devlink_resource_find(devlink, NULL, resource_id); ++ if (WARN_ON(!resource)) ++ return; ++ WARN_ON(resource->occ_get); ++ ++ resource->occ_get = occ_get; ++ resource->occ_get_priv = occ_get_priv; ++} ++EXPORT_SYMBOL_GPL(devl_resource_occ_get_register); ++ ++/** ++ * devlink_resource_occ_get_register - register occupancy getter ++ * ++ * @devlink: devlink ++ * @resource_id: resource id ++ * @occ_get: occupancy getter callback ++ * @occ_get_priv: occupancy getter callback priv ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++void devlink_resource_occ_get_register(struct devlink *devlink, ++ u64 resource_id, ++ devlink_resource_occ_get_t *occ_get, ++ void *occ_get_priv) ++{ ++ devl_lock(devlink); ++ devl_resource_occ_get_register(devlink, resource_id, ++ occ_get, occ_get_priv); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_resource_occ_get_register); ++ ++/** ++ * devl_resource_occ_get_unregister - unregister occupancy getter ++ * ++ * @devlink: devlink ++ * @resource_id: resource id ++ */ ++void devl_resource_occ_get_unregister(struct devlink *devlink, ++ u64 resource_id) ++{ ++ struct devlink_resource *resource; ++ ++ lockdep_assert_held(&devlink->lock); ++ ++ resource = devlink_resource_find(devlink, NULL, resource_id); ++ if (WARN_ON(!resource)) ++ return; ++ WARN_ON(!resource->occ_get); ++ ++ resource->occ_get = NULL; ++ resource->occ_get_priv = NULL; ++} ++EXPORT_SYMBOL_GPL(devl_resource_occ_get_unregister); ++ ++/** ++ * devlink_resource_occ_get_unregister - unregister occupancy getter ++ * ++ * @devlink: devlink ++ * @resource_id: resource id ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++void devlink_resource_occ_get_unregister(struct devlink *devlink, ++ u64 resource_id) ++{ ++ devl_lock(devlink); ++ devl_resource_occ_get_unregister(devlink, resource_id); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_resource_occ_get_unregister); ++ ++static int devlink_param_verify(const struct devlink_param *param) ++{ ++ if (!param || !param->name || !param->supported_cmodes) ++ return -EINVAL; ++ if (param->generic) ++ return devlink_param_generic_verify(param); ++ else ++ return devlink_param_driver_verify(param); ++} ++ ++/** ++ * devlink_params_register - register configuration parameters ++ * ++ * @devlink: devlink ++ * @params: configuration parameters array ++ * @params_count: number of parameters provided ++ * ++ * Register the configuration parameters supported by the driver. ++ */ ++int devlink_params_register(struct devlink *devlink, ++ const struct devlink_param *params, ++ size_t params_count) ++{ ++ const struct devlink_param *param = params; ++ int i, err; ++ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ ++ for (i = 0; i < params_count; i++, param++) { ++ err = devlink_param_register(devlink, param); ++ if (err) ++ goto rollback; ++ } ++ return 0; ++ ++rollback: ++ if (!i) ++ return err; ++ ++ for (param--; i > 0; i--, param--) ++ devlink_param_unregister(devlink, param); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_params_register); ++ ++/** ++ * devlink_params_unregister - unregister configuration parameters ++ * @devlink: devlink ++ * @params: configuration parameters to unregister ++ * @params_count: number of parameters provided ++ */ ++void devlink_params_unregister(struct devlink *devlink, ++ const struct devlink_param *params, ++ size_t params_count) ++{ ++ const struct devlink_param *param = params; ++ int i; ++ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ ++ for (i = 0; i < params_count; i++, param++) ++ devlink_param_unregister(devlink, param); ++} ++EXPORT_SYMBOL_GPL(devlink_params_unregister); ++ ++/** ++ * devlink_param_register - register one configuration parameter ++ * ++ * @devlink: devlink ++ * @param: one configuration parameter ++ * ++ * Register the configuration parameter supported by the driver. ++ * Return: returns 0 on successful registration or error code otherwise. ++ */ ++int devlink_param_register(struct devlink *devlink, ++ const struct devlink_param *param) ++{ ++ struct devlink_param_item *param_item; ++ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ ++ WARN_ON(devlink_param_verify(param)); ++ WARN_ON(devlink_param_find_by_name(&devlink->param_list, param->name)); ++ ++ if (param->supported_cmodes == BIT(DEVLINK_PARAM_CMODE_DRIVERINIT)) ++ WARN_ON(param->get || param->set); ++ else ++ WARN_ON(!param->get || !param->set); ++ ++ param_item = kzalloc(sizeof(*param_item), GFP_KERNEL); ++ if (!param_item) ++ return -ENOMEM; ++ ++ param_item->param = param; ++ ++ list_add_tail(¶m_item->list, &devlink->param_list); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_param_register); ++ ++/** ++ * devlink_param_unregister - unregister one configuration parameter ++ * @devlink: devlink ++ * @param: configuration parameter to unregister ++ */ ++void devlink_param_unregister(struct devlink *devlink, ++ const struct devlink_param *param) ++{ ++ struct devlink_param_item *param_item; ++ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ ++ param_item = ++ devlink_param_find_by_name(&devlink->param_list, param->name); ++ WARN_ON(!param_item); ++ list_del(¶m_item->list); ++ kfree(param_item); ++} ++EXPORT_SYMBOL_GPL(devlink_param_unregister); ++ ++/** ++ * devlink_param_driverinit_value_get - get configuration parameter ++ * value for driver initializing ++ * ++ * @devlink: devlink ++ * @param_id: parameter ID ++ * @init_val: value of parameter in driverinit configuration mode ++ * ++ * This function should be used by the driver to get driverinit ++ * configuration for initialization after reload command. ++ */ ++int devlink_param_driverinit_value_get(struct devlink *devlink, u32 param_id, ++ union devlink_param_value *init_val) ++{ ++ struct devlink_param_item *param_item; ++ ++ if (!devlink_reload_supported(devlink->ops)) ++ return -EOPNOTSUPP; ++ ++ param_item = devlink_param_find_by_id(&devlink->param_list, param_id); ++ if (!param_item) ++ return -EINVAL; ++ ++ if (!param_item->driverinit_value_valid || ++ !devlink_param_cmode_is_supported(param_item->param, ++ DEVLINK_PARAM_CMODE_DRIVERINIT)) ++ return -EOPNOTSUPP; ++ ++ if (param_item->param->type == DEVLINK_PARAM_TYPE_STRING) ++ strcpy(init_val->vstr, param_item->driverinit_value.vstr); ++ else ++ *init_val = param_item->driverinit_value; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_get); ++ ++/** ++ * devlink_param_driverinit_value_set - set value of configuration ++ * parameter for driverinit ++ * configuration mode ++ * ++ * @devlink: devlink ++ * @param_id: parameter ID ++ * @init_val: value of parameter to set for driverinit configuration mode ++ * ++ * This function should be used by the driver to set driverinit ++ * configuration mode default value. ++ */ ++int devlink_param_driverinit_value_set(struct devlink *devlink, u32 param_id, ++ union devlink_param_value init_val) ++{ ++ struct devlink_param_item *param_item; ++ ++ ASSERT_DEVLINK_NOT_REGISTERED(devlink); ++ ++ param_item = devlink_param_find_by_id(&devlink->param_list, param_id); ++ if (!param_item) ++ return -EINVAL; ++ ++ if (!devlink_param_cmode_is_supported(param_item->param, ++ DEVLINK_PARAM_CMODE_DRIVERINIT)) ++ return -EOPNOTSUPP; ++ ++ if (param_item->param->type == DEVLINK_PARAM_TYPE_STRING) ++ strcpy(param_item->driverinit_value.vstr, init_val.vstr); ++ else ++ param_item->driverinit_value = init_val; ++ param_item->driverinit_value_valid = true; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_set); ++ ++/** ++ * devlink_param_value_changed - notify devlink on a parameter's value ++ * change. Should be called by the driver ++ * right after the change. ++ * ++ * @devlink: devlink ++ * @param_id: parameter ID ++ * ++ * This function should be used by the driver to notify devlink on value ++ * change, excluding driverinit configuration mode. ++ * For driverinit configuration mode driver should use the function ++ */ ++void devlink_param_value_changed(struct devlink *devlink, u32 param_id) ++{ ++ struct devlink_param_item *param_item; ++ ++ param_item = devlink_param_find_by_id(&devlink->param_list, param_id); ++ WARN_ON(!param_item); ++ ++ devlink_param_notify(devlink, 0, param_item, DEVLINK_CMD_PARAM_NEW); ++} ++EXPORT_SYMBOL_GPL(devlink_param_value_changed); ++ ++/** ++ * devl_region_create - create a new address region ++ * ++ * @devlink: devlink ++ * @ops: region operations and name ++ * @region_max_snapshots: Maximum supported number of snapshots for region ++ * @region_size: size of region ++ */ ++struct devlink_region *devl_region_create(struct devlink *devlink, ++ const struct devlink_region_ops *ops, ++ u32 region_max_snapshots, ++ u64 region_size) ++{ ++ struct devlink_region *region; ++ ++ devl_assert_locked(devlink); ++ ++ if (WARN_ON(!ops) || WARN_ON(!ops->destructor)) ++ return ERR_PTR(-EINVAL); ++ ++ if (devlink_region_get_by_name(devlink, ops->name)) ++ return ERR_PTR(-EEXIST); ++ ++ region = kzalloc(sizeof(*region), GFP_KERNEL); ++ if (!region) ++ return ERR_PTR(-ENOMEM); ++ ++ region->devlink = devlink; ++ region->max_snapshots = region_max_snapshots; ++ region->ops = ops; ++ region->size = region_size; ++ INIT_LIST_HEAD(®ion->snapshot_list); ++ mutex_init(®ion->snapshot_lock); ++ list_add_tail(®ion->list, &devlink->region_list); ++ devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW); ++ ++ return region; ++} ++EXPORT_SYMBOL_GPL(devl_region_create); ++ ++/** ++ * devlink_region_create - create a new address region ++ * ++ * @devlink: devlink ++ * @ops: region operations and name ++ * @region_max_snapshots: Maximum supported number of snapshots for region ++ * @region_size: size of region ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++struct devlink_region * ++devlink_region_create(struct devlink *devlink, ++ const struct devlink_region_ops *ops, ++ u32 region_max_snapshots, u64 region_size) ++{ ++ struct devlink_region *region; ++ ++ devl_lock(devlink); ++ region = devl_region_create(devlink, ops, region_max_snapshots, ++ region_size); ++ devl_unlock(devlink); ++ return region; ++} ++EXPORT_SYMBOL_GPL(devlink_region_create); ++ ++/** ++ * devlink_port_region_create - create a new address region for a port ++ * ++ * @port: devlink port ++ * @ops: region operations and name ++ * @region_max_snapshots: Maximum supported number of snapshots for region ++ * @region_size: size of region ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++struct devlink_region * ++devlink_port_region_create(struct devlink_port *port, ++ const struct devlink_port_region_ops *ops, ++ u32 region_max_snapshots, u64 region_size) ++{ ++ struct devlink *devlink = port->devlink; ++ struct devlink_region *region; ++ int err = 0; ++ ++ ASSERT_DEVLINK_PORT_INITIALIZED(port); ++ ++ if (WARN_ON(!ops) || WARN_ON(!ops->destructor)) ++ return ERR_PTR(-EINVAL); ++ ++ devl_lock(devlink); ++ ++ if (devlink_port_region_get_by_name(port, ops->name)) { ++ err = -EEXIST; ++ goto unlock; ++ } ++ ++ region = kzalloc(sizeof(*region), GFP_KERNEL); ++ if (!region) { ++ err = -ENOMEM; ++ goto unlock; ++ } ++ ++ region->devlink = devlink; ++ region->port = port; ++ region->max_snapshots = region_max_snapshots; ++ region->port_ops = ops; ++ region->size = region_size; ++ INIT_LIST_HEAD(®ion->snapshot_list); ++ mutex_init(®ion->snapshot_lock); ++ list_add_tail(®ion->list, &port->region_list); ++ devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW); ++ ++ devl_unlock(devlink); ++ return region; ++ ++unlock: ++ devl_unlock(devlink); ++ return ERR_PTR(err); ++} ++EXPORT_SYMBOL_GPL(devlink_port_region_create); ++ ++/** ++ * devl_region_destroy - destroy address region ++ * ++ * @region: devlink region to destroy ++ */ ++void devl_region_destroy(struct devlink_region *region) ++{ ++ struct devlink *devlink = region->devlink; ++ struct devlink_snapshot *snapshot, *ts; ++ ++ devl_assert_locked(devlink); ++ ++ /* Free all snapshots of region */ ++ mutex_lock(®ion->snapshot_lock); ++ list_for_each_entry_safe(snapshot, ts, ®ion->snapshot_list, list) ++ devlink_region_snapshot_del(region, snapshot); ++ mutex_unlock(®ion->snapshot_lock); ++ ++ list_del(®ion->list); ++ mutex_destroy(®ion->snapshot_lock); ++ ++ devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_DEL); ++ kfree(region); ++} ++EXPORT_SYMBOL_GPL(devl_region_destroy); ++ ++/** ++ * devlink_region_destroy - destroy address region ++ * ++ * @region: devlink region to destroy ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++void devlink_region_destroy(struct devlink_region *region) ++{ ++ struct devlink *devlink = region->devlink; ++ ++ devl_lock(devlink); ++ devl_region_destroy(region); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_region_destroy); ++ ++/** ++ * devlink_region_snapshot_id_get - get snapshot ID ++ * ++ * This callback should be called when adding a new snapshot, ++ * Driver should use the same id for multiple snapshots taken ++ * on multiple regions at the same time/by the same trigger. ++ * ++ * The caller of this function must use devlink_region_snapshot_id_put ++ * when finished creating regions using this id. ++ * ++ * Returns zero on success, or a negative error code on failure. ++ * ++ * @devlink: devlink ++ * @id: storage to return id ++ */ ++int devlink_region_snapshot_id_get(struct devlink *devlink, u32 *id) ++{ ++ return __devlink_region_snapshot_id_get(devlink, id); ++} ++EXPORT_SYMBOL_GPL(devlink_region_snapshot_id_get); ++ ++/** ++ * devlink_region_snapshot_id_put - put snapshot ID reference ++ * ++ * This should be called by a driver after finishing creating snapshots ++ * with an id. Doing so ensures that the ID can later be released in the ++ * event that all snapshots using it have been destroyed. ++ * ++ * @devlink: devlink ++ * @id: id to release reference on ++ */ ++void devlink_region_snapshot_id_put(struct devlink *devlink, u32 id) ++{ ++ __devlink_snapshot_id_decrement(devlink, id); ++} ++EXPORT_SYMBOL_GPL(devlink_region_snapshot_id_put); ++ ++/** ++ * devlink_region_snapshot_create - create a new snapshot ++ * This will add a new snapshot of a region. The snapshot ++ * will be stored on the region struct and can be accessed ++ * from devlink. This is useful for future analyses of snapshots. ++ * Multiple snapshots can be created on a region. ++ * The @snapshot_id should be obtained using the getter function. ++ * ++ * @region: devlink region of the snapshot ++ * @data: snapshot data ++ * @snapshot_id: snapshot id to be created ++ */ ++int devlink_region_snapshot_create(struct devlink_region *region, ++ u8 *data, u32 snapshot_id) ++{ ++ int err; ++ ++ mutex_lock(®ion->snapshot_lock); ++ err = __devlink_region_snapshot_create(region, data, snapshot_id); ++ mutex_unlock(®ion->snapshot_lock); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_region_snapshot_create); ++ ++#define DEVLINK_TRAP(_id, _type) \ ++ { \ ++ .type = DEVLINK_TRAP_TYPE_##_type, \ ++ .id = DEVLINK_TRAP_GENERIC_ID_##_id, \ ++ .name = DEVLINK_TRAP_GENERIC_NAME_##_id, \ ++ } ++ ++static const struct devlink_trap devlink_trap_generic[] = { ++ DEVLINK_TRAP(SMAC_MC, DROP), ++ DEVLINK_TRAP(VLAN_TAG_MISMATCH, DROP), ++ DEVLINK_TRAP(INGRESS_VLAN_FILTER, DROP), ++ DEVLINK_TRAP(INGRESS_STP_FILTER, DROP), ++ DEVLINK_TRAP(EMPTY_TX_LIST, DROP), ++ DEVLINK_TRAP(PORT_LOOPBACK_FILTER, DROP), ++ DEVLINK_TRAP(BLACKHOLE_ROUTE, DROP), ++ DEVLINK_TRAP(TTL_ERROR, EXCEPTION), ++ DEVLINK_TRAP(TAIL_DROP, DROP), ++ DEVLINK_TRAP(NON_IP_PACKET, DROP), ++ DEVLINK_TRAP(UC_DIP_MC_DMAC, DROP), ++ DEVLINK_TRAP(DIP_LB, DROP), ++ DEVLINK_TRAP(SIP_MC, DROP), ++ DEVLINK_TRAP(SIP_LB, DROP), ++ DEVLINK_TRAP(CORRUPTED_IP_HDR, DROP), ++ DEVLINK_TRAP(IPV4_SIP_BC, DROP), ++ DEVLINK_TRAP(IPV6_MC_DIP_RESERVED_SCOPE, DROP), ++ DEVLINK_TRAP(IPV6_MC_DIP_INTERFACE_LOCAL_SCOPE, DROP), ++ DEVLINK_TRAP(MTU_ERROR, EXCEPTION), ++ DEVLINK_TRAP(UNRESOLVED_NEIGH, EXCEPTION), ++ DEVLINK_TRAP(RPF, EXCEPTION), ++ DEVLINK_TRAP(REJECT_ROUTE, EXCEPTION), ++ DEVLINK_TRAP(IPV4_LPM_UNICAST_MISS, EXCEPTION), ++ DEVLINK_TRAP(IPV6_LPM_UNICAST_MISS, EXCEPTION), ++ DEVLINK_TRAP(NON_ROUTABLE, DROP), ++ DEVLINK_TRAP(DECAP_ERROR, EXCEPTION), ++ DEVLINK_TRAP(OVERLAY_SMAC_MC, DROP), ++ DEVLINK_TRAP(INGRESS_FLOW_ACTION_DROP, DROP), ++ DEVLINK_TRAP(EGRESS_FLOW_ACTION_DROP, DROP), ++ DEVLINK_TRAP(STP, CONTROL), ++ DEVLINK_TRAP(LACP, CONTROL), ++ DEVLINK_TRAP(LLDP, CONTROL), ++ DEVLINK_TRAP(IGMP_QUERY, CONTROL), ++ DEVLINK_TRAP(IGMP_V1_REPORT, CONTROL), ++ DEVLINK_TRAP(IGMP_V2_REPORT, CONTROL), ++ DEVLINK_TRAP(IGMP_V3_REPORT, CONTROL), ++ DEVLINK_TRAP(IGMP_V2_LEAVE, CONTROL), ++ DEVLINK_TRAP(MLD_QUERY, CONTROL), ++ DEVLINK_TRAP(MLD_V1_REPORT, CONTROL), ++ DEVLINK_TRAP(MLD_V2_REPORT, CONTROL), ++ DEVLINK_TRAP(MLD_V1_DONE, CONTROL), ++ DEVLINK_TRAP(IPV4_DHCP, CONTROL), ++ DEVLINK_TRAP(IPV6_DHCP, CONTROL), ++ DEVLINK_TRAP(ARP_REQUEST, CONTROL), ++ DEVLINK_TRAP(ARP_RESPONSE, CONTROL), ++ DEVLINK_TRAP(ARP_OVERLAY, CONTROL), ++ DEVLINK_TRAP(IPV6_NEIGH_SOLICIT, CONTROL), ++ DEVLINK_TRAP(IPV6_NEIGH_ADVERT, CONTROL), ++ DEVLINK_TRAP(IPV4_BFD, CONTROL), ++ DEVLINK_TRAP(IPV6_BFD, CONTROL), ++ DEVLINK_TRAP(IPV4_OSPF, CONTROL), ++ DEVLINK_TRAP(IPV6_OSPF, CONTROL), ++ DEVLINK_TRAP(IPV4_BGP, CONTROL), ++ DEVLINK_TRAP(IPV6_BGP, CONTROL), ++ DEVLINK_TRAP(IPV4_VRRP, CONTROL), ++ DEVLINK_TRAP(IPV6_VRRP, CONTROL), ++ DEVLINK_TRAP(IPV4_PIM, CONTROL), ++ DEVLINK_TRAP(IPV6_PIM, CONTROL), ++ DEVLINK_TRAP(UC_LB, CONTROL), ++ DEVLINK_TRAP(LOCAL_ROUTE, CONTROL), ++ DEVLINK_TRAP(EXTERNAL_ROUTE, CONTROL), ++ DEVLINK_TRAP(IPV6_UC_DIP_LINK_LOCAL_SCOPE, CONTROL), ++ DEVLINK_TRAP(IPV6_DIP_ALL_NODES, CONTROL), ++ DEVLINK_TRAP(IPV6_DIP_ALL_ROUTERS, CONTROL), ++ DEVLINK_TRAP(IPV6_ROUTER_SOLICIT, CONTROL), ++ DEVLINK_TRAP(IPV6_ROUTER_ADVERT, CONTROL), ++ DEVLINK_TRAP(IPV6_REDIRECT, CONTROL), ++ DEVLINK_TRAP(IPV4_ROUTER_ALERT, CONTROL), ++ DEVLINK_TRAP(IPV6_ROUTER_ALERT, CONTROL), ++ DEVLINK_TRAP(PTP_EVENT, CONTROL), ++ DEVLINK_TRAP(PTP_GENERAL, CONTROL), ++ DEVLINK_TRAP(FLOW_ACTION_SAMPLE, CONTROL), ++ DEVLINK_TRAP(FLOW_ACTION_TRAP, CONTROL), ++ DEVLINK_TRAP(EARLY_DROP, DROP), ++ DEVLINK_TRAP(VXLAN_PARSING, DROP), ++ DEVLINK_TRAP(LLC_SNAP_PARSING, DROP), ++ DEVLINK_TRAP(VLAN_PARSING, DROP), ++ DEVLINK_TRAP(PPPOE_PPP_PARSING, DROP), ++ DEVLINK_TRAP(MPLS_PARSING, DROP), ++ DEVLINK_TRAP(ARP_PARSING, DROP), ++ DEVLINK_TRAP(IP_1_PARSING, DROP), ++ DEVLINK_TRAP(IP_N_PARSING, DROP), ++ DEVLINK_TRAP(GRE_PARSING, DROP), ++ DEVLINK_TRAP(UDP_PARSING, DROP), ++ DEVLINK_TRAP(TCP_PARSING, DROP), ++ DEVLINK_TRAP(IPSEC_PARSING, DROP), ++ DEVLINK_TRAP(SCTP_PARSING, DROP), ++ DEVLINK_TRAP(DCCP_PARSING, DROP), ++ DEVLINK_TRAP(GTP_PARSING, DROP), ++ DEVLINK_TRAP(ESP_PARSING, DROP), ++ DEVLINK_TRAP(BLACKHOLE_NEXTHOP, DROP), ++ DEVLINK_TRAP(DMAC_FILTER, DROP), ++}; ++ ++#define DEVLINK_TRAP_GROUP(_id) \ ++ { \ ++ .id = DEVLINK_TRAP_GROUP_GENERIC_ID_##_id, \ ++ .name = DEVLINK_TRAP_GROUP_GENERIC_NAME_##_id, \ ++ } ++ ++static const struct devlink_trap_group devlink_trap_group_generic[] = { ++ DEVLINK_TRAP_GROUP(L2_DROPS), ++ DEVLINK_TRAP_GROUP(L3_DROPS), ++ DEVLINK_TRAP_GROUP(L3_EXCEPTIONS), ++ DEVLINK_TRAP_GROUP(BUFFER_DROPS), ++ DEVLINK_TRAP_GROUP(TUNNEL_DROPS), ++ DEVLINK_TRAP_GROUP(ACL_DROPS), ++ DEVLINK_TRAP_GROUP(STP), ++ DEVLINK_TRAP_GROUP(LACP), ++ DEVLINK_TRAP_GROUP(LLDP), ++ DEVLINK_TRAP_GROUP(MC_SNOOPING), ++ DEVLINK_TRAP_GROUP(DHCP), ++ DEVLINK_TRAP_GROUP(NEIGH_DISCOVERY), ++ DEVLINK_TRAP_GROUP(BFD), ++ DEVLINK_TRAP_GROUP(OSPF), ++ DEVLINK_TRAP_GROUP(BGP), ++ DEVLINK_TRAP_GROUP(VRRP), ++ DEVLINK_TRAP_GROUP(PIM), ++ DEVLINK_TRAP_GROUP(UC_LB), ++ DEVLINK_TRAP_GROUP(LOCAL_DELIVERY), ++ DEVLINK_TRAP_GROUP(EXTERNAL_DELIVERY), ++ DEVLINK_TRAP_GROUP(IPV6), ++ DEVLINK_TRAP_GROUP(PTP_EVENT), ++ DEVLINK_TRAP_GROUP(PTP_GENERAL), ++ DEVLINK_TRAP_GROUP(ACL_SAMPLE), ++ DEVLINK_TRAP_GROUP(ACL_TRAP), ++ DEVLINK_TRAP_GROUP(PARSER_ERROR_DROPS), ++}; ++ ++static int devlink_trap_generic_verify(const struct devlink_trap *trap) ++{ ++ if (trap->id > DEVLINK_TRAP_GENERIC_ID_MAX) ++ return -EINVAL; ++ ++ if (strcmp(trap->name, devlink_trap_generic[trap->id].name)) ++ return -EINVAL; ++ ++ if (trap->type != devlink_trap_generic[trap->id].type) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static int devlink_trap_driver_verify(const struct devlink_trap *trap) ++{ ++ int i; ++ ++ if (trap->id <= DEVLINK_TRAP_GENERIC_ID_MAX) ++ return -EINVAL; ++ ++ for (i = 0; i < ARRAY_SIZE(devlink_trap_generic); i++) { ++ if (!strcmp(trap->name, devlink_trap_generic[i].name)) ++ return -EEXIST; ++ } ++ ++ return 0; ++} ++ ++static int devlink_trap_verify(const struct devlink_trap *trap) ++{ ++ if (!trap || !trap->name) ++ return -EINVAL; ++ ++ if (trap->generic) ++ return devlink_trap_generic_verify(trap); ++ else ++ return devlink_trap_driver_verify(trap); ++} ++ ++static int ++devlink_trap_group_generic_verify(const struct devlink_trap_group *group) ++{ ++ if (group->id > DEVLINK_TRAP_GROUP_GENERIC_ID_MAX) ++ return -EINVAL; ++ ++ if (strcmp(group->name, devlink_trap_group_generic[group->id].name)) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static int ++devlink_trap_group_driver_verify(const struct devlink_trap_group *group) ++{ ++ int i; ++ ++ if (group->id <= DEVLINK_TRAP_GROUP_GENERIC_ID_MAX) ++ return -EINVAL; ++ ++ for (i = 0; i < ARRAY_SIZE(devlink_trap_group_generic); i++) { ++ if (!strcmp(group->name, devlink_trap_group_generic[i].name)) ++ return -EEXIST; ++ } ++ ++ return 0; ++} ++ ++static int devlink_trap_group_verify(const struct devlink_trap_group *group) ++{ ++ if (group->generic) ++ return devlink_trap_group_generic_verify(group); ++ else ++ return devlink_trap_group_driver_verify(group); ++} ++ ++static void ++devlink_trap_group_notify(struct devlink *devlink, ++ const struct devlink_trap_group_item *group_item, ++ enum devlink_command cmd) ++{ ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_GROUP_NEW && ++ cmd != DEVLINK_CMD_TRAP_GROUP_DEL); ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_trap_group_fill(msg, devlink, group_item, cmd, 0, 0, ++ 0); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), ++ msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static int ++devlink_trap_item_group_link(struct devlink *devlink, ++ struct devlink_trap_item *trap_item) ++{ ++ u16 group_id = trap_item->trap->init_group_id; ++ struct devlink_trap_group_item *group_item; ++ ++ group_item = devlink_trap_group_item_lookup_by_id(devlink, group_id); ++ if (WARN_ON_ONCE(!group_item)) ++ return -EINVAL; ++ ++ trap_item->group_item = group_item; ++ ++ return 0; ++} ++ ++static void devlink_trap_notify(struct devlink *devlink, ++ const struct devlink_trap_item *trap_item, ++ enum devlink_command cmd) ++{ ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_NEW && ++ cmd != DEVLINK_CMD_TRAP_DEL); ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_trap_fill(msg, devlink, trap_item, cmd, 0, 0, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), ++ msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static int ++devlink_trap_register(struct devlink *devlink, ++ const struct devlink_trap *trap, void *priv) ++{ ++ struct devlink_trap_item *trap_item; ++ int err; ++ ++ if (devlink_trap_item_lookup(devlink, trap->name)) ++ return -EEXIST; ++ ++ trap_item = kzalloc(sizeof(*trap_item), GFP_KERNEL); ++ if (!trap_item) ++ return -ENOMEM; ++ ++ trap_item->stats = netdev_alloc_pcpu_stats(struct devlink_stats); ++ if (!trap_item->stats) { ++ err = -ENOMEM; ++ goto err_stats_alloc; ++ } ++ ++ trap_item->trap = trap; ++ trap_item->action = trap->init_action; ++ trap_item->priv = priv; ++ ++ err = devlink_trap_item_group_link(devlink, trap_item); ++ if (err) ++ goto err_group_link; ++ ++ err = devlink->ops->trap_init(devlink, trap, trap_item); ++ if (err) ++ goto err_trap_init; ++ ++ list_add_tail(&trap_item->list, &devlink->trap_list); ++ devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_NEW); ++ ++ return 0; ++ ++err_trap_init: ++err_group_link: ++ free_percpu(trap_item->stats); ++err_stats_alloc: ++ kfree(trap_item); ++ return err; ++} ++ ++static void devlink_trap_unregister(struct devlink *devlink, ++ const struct devlink_trap *trap) ++{ ++ struct devlink_trap_item *trap_item; ++ ++ trap_item = devlink_trap_item_lookup(devlink, trap->name); ++ if (WARN_ON_ONCE(!trap_item)) ++ return; ++ ++ devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_DEL); ++ list_del(&trap_item->list); ++ if (devlink->ops->trap_fini) ++ devlink->ops->trap_fini(devlink, trap, trap_item); ++ free_percpu(trap_item->stats); ++ kfree(trap_item); ++} ++ ++static void devlink_trap_disable(struct devlink *devlink, ++ const struct devlink_trap *trap) ++{ ++ struct devlink_trap_item *trap_item; ++ ++ trap_item = devlink_trap_item_lookup(devlink, trap->name); ++ if (WARN_ON_ONCE(!trap_item)) ++ return; ++ ++ devlink->ops->trap_action_set(devlink, trap, DEVLINK_TRAP_ACTION_DROP, ++ NULL); ++ trap_item->action = DEVLINK_TRAP_ACTION_DROP; ++} ++ ++/** ++ * devl_traps_register - Register packet traps with devlink. ++ * @devlink: devlink. ++ * @traps: Packet traps. ++ * @traps_count: Count of provided packet traps. ++ * @priv: Driver private information. ++ * ++ * Return: Non-zero value on failure. ++ */ ++int devl_traps_register(struct devlink *devlink, ++ const struct devlink_trap *traps, ++ size_t traps_count, void *priv) ++{ ++ int i, err; ++ ++ if (!devlink->ops->trap_init || !devlink->ops->trap_action_set) ++ return -EINVAL; ++ ++ devl_assert_locked(devlink); ++ for (i = 0; i < traps_count; i++) { ++ const struct devlink_trap *trap = &traps[i]; ++ ++ err = devlink_trap_verify(trap); ++ if (err) ++ goto err_trap_verify; ++ ++ err = devlink_trap_register(devlink, trap, priv); ++ if (err) ++ goto err_trap_register; ++ } ++ ++ return 0; ++ ++err_trap_register: ++err_trap_verify: ++ for (i--; i >= 0; i--) ++ devlink_trap_unregister(devlink, &traps[i]); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devl_traps_register); ++ ++/** ++ * devlink_traps_register - Register packet traps with devlink. ++ * @devlink: devlink. ++ * @traps: Packet traps. ++ * @traps_count: Count of provided packet traps. ++ * @priv: Driver private information. ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ * ++ * Return: Non-zero value on failure. ++ */ ++int devlink_traps_register(struct devlink *devlink, ++ const struct devlink_trap *traps, ++ size_t traps_count, void *priv) ++{ ++ int err; ++ ++ devl_lock(devlink); ++ err = devl_traps_register(devlink, traps, traps_count, priv); ++ devl_unlock(devlink); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_traps_register); ++ ++/** ++ * devl_traps_unregister - Unregister packet traps from devlink. ++ * @devlink: devlink. ++ * @traps: Packet traps. ++ * @traps_count: Count of provided packet traps. ++ */ ++void devl_traps_unregister(struct devlink *devlink, ++ const struct devlink_trap *traps, ++ size_t traps_count) ++{ ++ int i; ++ ++ devl_assert_locked(devlink); ++ /* Make sure we do not have any packets in-flight while unregistering ++ * traps by disabling all of them and waiting for a grace period. ++ */ ++ for (i = traps_count - 1; i >= 0; i--) ++ devlink_trap_disable(devlink, &traps[i]); ++ synchronize_rcu(); ++ for (i = traps_count - 1; i >= 0; i--) ++ devlink_trap_unregister(devlink, &traps[i]); ++} ++EXPORT_SYMBOL_GPL(devl_traps_unregister); ++ ++/** ++ * devlink_traps_unregister - Unregister packet traps from devlink. ++ * @devlink: devlink. ++ * @traps: Packet traps. ++ * @traps_count: Count of provided packet traps. ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++void devlink_traps_unregister(struct devlink *devlink, ++ const struct devlink_trap *traps, ++ size_t traps_count) ++{ ++ devl_lock(devlink); ++ devl_traps_unregister(devlink, traps, traps_count); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_traps_unregister); ++ ++static void ++devlink_trap_stats_update(struct devlink_stats __percpu *trap_stats, ++ size_t skb_len) ++{ ++ struct devlink_stats *stats; ++ ++ stats = this_cpu_ptr(trap_stats); ++ u64_stats_update_begin(&stats->syncp); ++ u64_stats_add(&stats->rx_bytes, skb_len); ++ u64_stats_inc(&stats->rx_packets); ++ u64_stats_update_end(&stats->syncp); ++} ++ ++static void ++devlink_trap_report_metadata_set(struct devlink_trap_metadata *metadata, ++ const struct devlink_trap_item *trap_item, ++ struct devlink_port *in_devlink_port, ++ const struct flow_action_cookie *fa_cookie) ++{ ++ metadata->trap_name = trap_item->trap->name; ++ metadata->trap_group_name = trap_item->group_item->group->name; ++ metadata->fa_cookie = fa_cookie; ++ metadata->trap_type = trap_item->trap->type; ++ ++ spin_lock(&in_devlink_port->type_lock); ++ if (in_devlink_port->type == DEVLINK_PORT_TYPE_ETH) ++ metadata->input_dev = in_devlink_port->type_dev; ++ spin_unlock(&in_devlink_port->type_lock); ++} ++ ++/** ++ * devlink_trap_report - Report trapped packet to drop monitor. ++ * @devlink: devlink. ++ * @skb: Trapped packet. ++ * @trap_ctx: Trap context. ++ * @in_devlink_port: Input devlink port. ++ * @fa_cookie: Flow action cookie. Could be NULL. ++ */ ++void devlink_trap_report(struct devlink *devlink, struct sk_buff *skb, ++ void *trap_ctx, struct devlink_port *in_devlink_port, ++ const struct flow_action_cookie *fa_cookie) ++ ++{ ++ struct devlink_trap_item *trap_item = trap_ctx; ++ ++ devlink_trap_stats_update(trap_item->stats, skb->len); ++ devlink_trap_stats_update(trap_item->group_item->stats, skb->len); ++ ++ if (trace_devlink_trap_report_enabled()) { ++ struct devlink_trap_metadata metadata = {}; ++ ++ devlink_trap_report_metadata_set(&metadata, trap_item, ++ in_devlink_port, fa_cookie); ++ trace_devlink_trap_report(devlink, skb, &metadata); ++ } ++} ++EXPORT_SYMBOL_GPL(devlink_trap_report); ++ ++/** ++ * devlink_trap_ctx_priv - Trap context to driver private information. ++ * @trap_ctx: Trap context. ++ * ++ * Return: Driver private information passed during registration. ++ */ ++void *devlink_trap_ctx_priv(void *trap_ctx) ++{ ++ struct devlink_trap_item *trap_item = trap_ctx; ++ ++ return trap_item->priv; ++} ++EXPORT_SYMBOL_GPL(devlink_trap_ctx_priv); ++ ++static int ++devlink_trap_group_item_policer_link(struct devlink *devlink, ++ struct devlink_trap_group_item *group_item) ++{ ++ u32 policer_id = group_item->group->init_policer_id; ++ struct devlink_trap_policer_item *policer_item; ++ ++ if (policer_id == 0) ++ return 0; ++ ++ policer_item = devlink_trap_policer_item_lookup(devlink, policer_id); ++ if (WARN_ON_ONCE(!policer_item)) ++ return -EINVAL; ++ ++ group_item->policer_item = policer_item; ++ ++ return 0; ++} ++ ++static int ++devlink_trap_group_register(struct devlink *devlink, ++ const struct devlink_trap_group *group) ++{ ++ struct devlink_trap_group_item *group_item; ++ int err; ++ ++ if (devlink_trap_group_item_lookup(devlink, group->name)) ++ return -EEXIST; ++ ++ group_item = kzalloc(sizeof(*group_item), GFP_KERNEL); ++ if (!group_item) ++ return -ENOMEM; ++ ++ group_item->stats = netdev_alloc_pcpu_stats(struct devlink_stats); ++ if (!group_item->stats) { ++ err = -ENOMEM; ++ goto err_stats_alloc; ++ } ++ ++ group_item->group = group; ++ ++ err = devlink_trap_group_item_policer_link(devlink, group_item); ++ if (err) ++ goto err_policer_link; ++ ++ if (devlink->ops->trap_group_init) { ++ err = devlink->ops->trap_group_init(devlink, group); ++ if (err) ++ goto err_group_init; ++ } ++ ++ list_add_tail(&group_item->list, &devlink->trap_group_list); ++ devlink_trap_group_notify(devlink, group_item, ++ DEVLINK_CMD_TRAP_GROUP_NEW); ++ ++ return 0; ++ ++err_group_init: ++err_policer_link: ++ free_percpu(group_item->stats); ++err_stats_alloc: ++ kfree(group_item); ++ return err; ++} ++ ++static void ++devlink_trap_group_unregister(struct devlink *devlink, ++ const struct devlink_trap_group *group) ++{ ++ struct devlink_trap_group_item *group_item; ++ ++ group_item = devlink_trap_group_item_lookup(devlink, group->name); ++ if (WARN_ON_ONCE(!group_item)) ++ return; ++ ++ devlink_trap_group_notify(devlink, group_item, ++ DEVLINK_CMD_TRAP_GROUP_DEL); ++ list_del(&group_item->list); ++ free_percpu(group_item->stats); ++ kfree(group_item); ++} ++ ++/** ++ * devl_trap_groups_register - Register packet trap groups with devlink. ++ * @devlink: devlink. ++ * @groups: Packet trap groups. ++ * @groups_count: Count of provided packet trap groups. ++ * ++ * Return: Non-zero value on failure. ++ */ ++int devl_trap_groups_register(struct devlink *devlink, ++ const struct devlink_trap_group *groups, ++ size_t groups_count) ++{ ++ int i, err; ++ ++ devl_assert_locked(devlink); ++ for (i = 0; i < groups_count; i++) { ++ const struct devlink_trap_group *group = &groups[i]; ++ ++ err = devlink_trap_group_verify(group); ++ if (err) ++ goto err_trap_group_verify; ++ ++ err = devlink_trap_group_register(devlink, group); ++ if (err) ++ goto err_trap_group_register; ++ } ++ ++ return 0; ++ ++err_trap_group_register: ++err_trap_group_verify: ++ for (i--; i >= 0; i--) ++ devlink_trap_group_unregister(devlink, &groups[i]); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devl_trap_groups_register); ++ ++/** ++ * devlink_trap_groups_register - Register packet trap groups with devlink. ++ * @devlink: devlink. ++ * @groups: Packet trap groups. ++ * @groups_count: Count of provided packet trap groups. ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ * ++ * Return: Non-zero value on failure. ++ */ ++int devlink_trap_groups_register(struct devlink *devlink, ++ const struct devlink_trap_group *groups, ++ size_t groups_count) ++{ ++ int err; ++ ++ devl_lock(devlink); ++ err = devl_trap_groups_register(devlink, groups, groups_count); ++ devl_unlock(devlink); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devlink_trap_groups_register); ++ ++/** ++ * devl_trap_groups_unregister - Unregister packet trap groups from devlink. ++ * @devlink: devlink. ++ * @groups: Packet trap groups. ++ * @groups_count: Count of provided packet trap groups. ++ */ ++void devl_trap_groups_unregister(struct devlink *devlink, ++ const struct devlink_trap_group *groups, ++ size_t groups_count) ++{ ++ int i; ++ ++ devl_assert_locked(devlink); ++ for (i = groups_count - 1; i >= 0; i--) ++ devlink_trap_group_unregister(devlink, &groups[i]); ++} ++EXPORT_SYMBOL_GPL(devl_trap_groups_unregister); ++ ++/** ++ * devlink_trap_groups_unregister - Unregister packet trap groups from devlink. ++ * @devlink: devlink. ++ * @groups: Packet trap groups. ++ * @groups_count: Count of provided packet trap groups. ++ * ++ * Context: Takes and release devlink->lock <mutex>. ++ */ ++void devlink_trap_groups_unregister(struct devlink *devlink, ++ const struct devlink_trap_group *groups, ++ size_t groups_count) ++{ ++ devl_lock(devlink); ++ devl_trap_groups_unregister(devlink, groups, groups_count); ++ devl_unlock(devlink); ++} ++EXPORT_SYMBOL_GPL(devlink_trap_groups_unregister); ++ ++static void ++devlink_trap_policer_notify(struct devlink *devlink, ++ const struct devlink_trap_policer_item *policer_item, ++ enum devlink_command cmd) ++{ ++ struct sk_buff *msg; ++ int err; ++ ++ WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_POLICER_NEW && ++ cmd != DEVLINK_CMD_TRAP_POLICER_DEL); ++ if (!xa_get_mark(&devlinks, devlink->index, DEVLINK_REGISTERED)) ++ return; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ err = devlink_nl_trap_policer_fill(msg, devlink, policer_item, cmd, 0, ++ 0, 0); ++ if (err) { ++ nlmsg_free(msg); ++ return; ++ } ++ ++ genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), ++ msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); ++} ++ ++static int ++devlink_trap_policer_register(struct devlink *devlink, ++ const struct devlink_trap_policer *policer) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ int err; ++ ++ if (devlink_trap_policer_item_lookup(devlink, policer->id)) ++ return -EEXIST; ++ ++ policer_item = kzalloc(sizeof(*policer_item), GFP_KERNEL); ++ if (!policer_item) ++ return -ENOMEM; ++ ++ policer_item->policer = policer; ++ policer_item->rate = policer->init_rate; ++ policer_item->burst = policer->init_burst; ++ ++ if (devlink->ops->trap_policer_init) { ++ err = devlink->ops->trap_policer_init(devlink, policer); ++ if (err) ++ goto err_policer_init; ++ } ++ ++ list_add_tail(&policer_item->list, &devlink->trap_policer_list); ++ devlink_trap_policer_notify(devlink, policer_item, ++ DEVLINK_CMD_TRAP_POLICER_NEW); ++ ++ return 0; ++ ++err_policer_init: ++ kfree(policer_item); ++ return err; ++} ++ ++static void ++devlink_trap_policer_unregister(struct devlink *devlink, ++ const struct devlink_trap_policer *policer) ++{ ++ struct devlink_trap_policer_item *policer_item; ++ ++ policer_item = devlink_trap_policer_item_lookup(devlink, policer->id); ++ if (WARN_ON_ONCE(!policer_item)) ++ return; ++ ++ devlink_trap_policer_notify(devlink, policer_item, ++ DEVLINK_CMD_TRAP_POLICER_DEL); ++ list_del(&policer_item->list); ++ if (devlink->ops->trap_policer_fini) ++ devlink->ops->trap_policer_fini(devlink, policer); ++ kfree(policer_item); ++} ++ ++/** ++ * devl_trap_policers_register - Register packet trap policers with devlink. ++ * @devlink: devlink. ++ * @policers: Packet trap policers. ++ * @policers_count: Count of provided packet trap policers. ++ * ++ * Return: Non-zero value on failure. ++ */ ++int ++devl_trap_policers_register(struct devlink *devlink, ++ const struct devlink_trap_policer *policers, ++ size_t policers_count) ++{ ++ int i, err; ++ ++ devl_assert_locked(devlink); ++ for (i = 0; i < policers_count; i++) { ++ const struct devlink_trap_policer *policer = &policers[i]; ++ ++ if (WARN_ON(policer->id == 0 || ++ policer->max_rate < policer->min_rate || ++ policer->max_burst < policer->min_burst)) { ++ err = -EINVAL; ++ goto err_trap_policer_verify; ++ } ++ ++ err = devlink_trap_policer_register(devlink, policer); ++ if (err) ++ goto err_trap_policer_register; ++ } ++ return 0; ++ ++err_trap_policer_register: ++err_trap_policer_verify: ++ for (i--; i >= 0; i--) ++ devlink_trap_policer_unregister(devlink, &policers[i]); ++ return err; ++} ++EXPORT_SYMBOL_GPL(devl_trap_policers_register); ++ ++/** ++ * devl_trap_policers_unregister - Unregister packet trap policers from devlink. ++ * @devlink: devlink. ++ * @policers: Packet trap policers. ++ * @policers_count: Count of provided packet trap policers. ++ */ ++void ++devl_trap_policers_unregister(struct devlink *devlink, ++ const struct devlink_trap_policer *policers, ++ size_t policers_count) ++{ ++ int i; ++ ++ devl_assert_locked(devlink); ++ for (i = policers_count - 1; i >= 0; i--) ++ devlink_trap_policer_unregister(devlink, &policers[i]); ++} ++EXPORT_SYMBOL_GPL(devl_trap_policers_unregister); ++ ++static void __devlink_compat_running_version(struct devlink *devlink, ++ char *buf, size_t len) ++{ ++ struct devlink_info_req req = {}; ++ const struct nlattr *nlattr; ++ struct sk_buff *msg; ++ int rem, err; ++ ++ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); ++ if (!msg) ++ return; ++ ++ req.msg = msg; ++ err = devlink->ops->info_get(devlink, &req, NULL); ++ if (err) ++ goto free_msg; ++ ++ nla_for_each_attr(nlattr, (void *)msg->data, msg->len, rem) { ++ const struct nlattr *kv; ++ int rem_kv; ++ ++ if (nla_type(nlattr) != DEVLINK_ATTR_INFO_VERSION_RUNNING) ++ continue; ++ ++ nla_for_each_nested(kv, nlattr, rem_kv) { ++ if (nla_type(kv) != DEVLINK_ATTR_INFO_VERSION_VALUE) ++ continue; ++ ++ strlcat(buf, nla_data(kv), len); ++ strlcat(buf, " ", len); ++ } ++ } ++free_msg: ++ nlmsg_free(msg); ++} ++ ++static struct devlink_port *netdev_to_devlink_port(struct net_device *dev) ++{ ++ if (!dev->netdev_ops->ndo_get_devlink_port) ++ return NULL; ++ ++ return dev->netdev_ops->ndo_get_devlink_port(dev); ++} ++ ++void devlink_compat_running_version(struct devlink *devlink, ++ char *buf, size_t len) ++{ ++ if (!devlink->ops->info_get) ++ return; ++ ++ devl_lock(devlink); ++ __devlink_compat_running_version(devlink, buf, len); ++ devl_unlock(devlink); ++} ++ ++int devlink_compat_flash_update(struct devlink *devlink, const char *file_name) ++{ ++ struct devlink_flash_update_params params = {}; ++ int ret; ++ ++ if (!devlink->ops->flash_update) ++ return -EOPNOTSUPP; ++ ++ ret = request_firmware(¶ms.fw, file_name, devlink->dev); ++ if (ret) ++ return ret; ++ ++ devl_lock(devlink); ++ devlink_flash_update_begin_notify(devlink); ++ ret = devlink->ops->flash_update(devlink, ¶ms, NULL); ++ devlink_flash_update_end_notify(devlink); ++ devl_unlock(devlink); ++ ++ release_firmware(params.fw); ++ ++ return ret; ++} ++ ++int devlink_compat_phys_port_name_get(struct net_device *dev, ++ char *name, size_t len) ++{ ++ struct devlink_port *devlink_port; ++ ++ /* RTNL mutex is held here which ensures that devlink_port ++ * instance cannot disappear in the middle. No need to take ++ * any devlink lock as only permanent values are accessed. ++ */ ++ ASSERT_RTNL(); ++ ++ devlink_port = netdev_to_devlink_port(dev); ++ if (!devlink_port) ++ return -EOPNOTSUPP; ++ ++ return __devlink_port_phys_port_name_get(devlink_port, name, len); ++} ++ ++int devlink_compat_switch_id_get(struct net_device *dev, ++ struct netdev_phys_item_id *ppid) ++{ ++ struct devlink_port *devlink_port; ++ ++ /* Caller must hold RTNL mutex or reference to dev, which ensures that ++ * devlink_port instance cannot disappear in the middle. No need to take ++ * any devlink lock as only permanent values are accessed. ++ */ ++ devlink_port = netdev_to_devlink_port(dev); ++ if (!devlink_port || !devlink_port->switch_port) ++ return -EOPNOTSUPP; ++ ++ memcpy(ppid, &devlink_port->attrs.switch_id, sizeof(*ppid)); ++ ++ return 0; ++} ++ ++static void __net_exit devlink_pernet_pre_exit(struct net *net) ++{ ++ struct devlink *devlink; ++ u32 actions_performed; ++ unsigned long index; ++ int err; ++ ++ /* In case network namespace is getting destroyed, reload ++ * all devlink instances from this namespace into init_net. ++ */ ++ devlinks_xa_for_each_registered_get(net, index, devlink) { ++ WARN_ON(!(devlink->features & DEVLINK_F_RELOAD)); ++ mutex_lock(&devlink->lock); ++ err = devlink_reload(devlink, &init_net, ++ DEVLINK_RELOAD_ACTION_DRIVER_REINIT, ++ DEVLINK_RELOAD_LIMIT_UNSPEC, ++ &actions_performed, NULL); ++ mutex_unlock(&devlink->lock); ++ if (err && err != -EOPNOTSUPP) ++ pr_warn("Failed to reload devlink instance into init_net\n"); ++ devlink_put(devlink); ++ } ++} ++ ++static struct pernet_operations devlink_pernet_ops __net_initdata = { ++ .pre_exit = devlink_pernet_pre_exit, ++}; ++ ++static int __init devlink_init(void) ++{ ++ int err; ++ ++ err = genl_register_family(&devlink_nl_family); ++ if (err) ++ goto out; ++ err = register_pernet_subsys(&devlink_pernet_ops); ++ ++out: ++ WARN_ON(err); ++ return err; ++} ++ ++subsys_initcall(devlink_init); +diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c +index ebb737ac9e894..04853c83c85c4 100644 +--- a/net/ipv4/af_inet.c ++++ b/net/ipv4/af_inet.c +@@ -340,7 +340,7 @@ lookup_protocol: + else + inet->pmtudisc = IP_PMTUDISC_WANT; + +- inet->inet_id = 0; ++ atomic_set(&inet->inet_id, 0); + + sock_init_data(sock, sk); + +diff --git a/net/ipv4/datagram.c b/net/ipv4/datagram.c +index 4d1af0cd7d99e..cb5dbee9e018f 100644 +--- a/net/ipv4/datagram.c ++++ b/net/ipv4/datagram.c +@@ -73,7 +73,7 @@ int __ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len + reuseport_has_conns_set(sk); + sk->sk_state = TCP_ESTABLISHED; + sk_set_txhash(sk); +- inet->inet_id = get_random_u16(); ++ atomic_set(&inet->inet_id, get_random_u16()); + + sk_dst_set(sk, &rt->dst); + err = 0; +diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c +index 08921b96f9728..f9b8a4a1d2edc 100644 +--- a/net/ipv4/tcp_ipv4.c ++++ b/net/ipv4/tcp_ipv4.c +@@ -312,7 +312,7 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) + inet->inet_daddr)); + } + +- inet->inet_id = get_random_u16(); ++ atomic_set(&inet->inet_id, get_random_u16()); + + if (tcp_fastopen_defer_connect(sk, &err)) + return err; +@@ -1539,7 +1539,7 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, + inet_csk(newsk)->icsk_ext_hdr_len = 0; + if (inet_opt) + inet_csk(newsk)->icsk_ext_hdr_len = inet_opt->opt.optlen; +- newinet->inet_id = get_random_u16(); ++ atomic_set(&newinet->inet_id, get_random_u16()); + + /* Set ToS of the new socket based upon the value of incoming SYN. + * ECT bits are set later in tcp_init_transfer(). +diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c +index 0f81492da0b46..55dc0610e8633 100644 +--- a/net/mac80211/rx.c ++++ b/net/mac80211/rx.c +@@ -1102,7 +1102,8 @@ static inline bool ieee80211_rx_reorder_ready(struct tid_ampdu_rx *tid_agg_rx, + struct sk_buff *tail = skb_peek_tail(frames); + struct ieee80211_rx_status *status; + +- if (tid_agg_rx->reorder_buf_filtered & BIT_ULL(index)) ++ if (tid_agg_rx->reorder_buf_filtered && ++ tid_agg_rx->reorder_buf_filtered & BIT_ULL(index)) + return true; + + if (!tail) +@@ -1143,7 +1144,8 @@ static void ieee80211_release_reorder_frame(struct ieee80211_sub_if_data *sdata, + } + + no_frame: +- tid_agg_rx->reorder_buf_filtered &= ~BIT_ULL(index); ++ if (tid_agg_rx->reorder_buf_filtered) ++ tid_agg_rx->reorder_buf_filtered &= ~BIT_ULL(index); + tid_agg_rx->head_seq_num = ieee80211_sn_inc(tid_agg_rx->head_seq_num); + } + +@@ -4162,6 +4164,7 @@ void ieee80211_mark_rx_ba_filtered_frames(struct ieee80211_sta *pubsta, u8 tid, + u16 ssn, u64 filtered, + u16 received_mpdus) + { ++ struct ieee80211_local *local; + struct sta_info *sta; + struct tid_ampdu_rx *tid_agg_rx; + struct sk_buff_head frames; +@@ -4179,6 +4182,11 @@ void ieee80211_mark_rx_ba_filtered_frames(struct ieee80211_sta *pubsta, u8 tid, + + sta = container_of(pubsta, struct sta_info, sta); + ++ local = sta->sdata->local; ++ WARN_ONCE(local->hw.max_rx_aggregation_subframes > 64, ++ "RX BA marker can't support max_rx_aggregation_subframes %u > 64\n", ++ local->hw.max_rx_aggregation_subframes); ++ + if (!ieee80211_rx_data_set_sta(&rx, sta, -1)) + return; + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 4c2df7af73f76..3c5cac9bd9b70 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -10509,7 +10509,7 @@ static int nft_rcv_nl_event(struct notifier_block *this, unsigned long event, + deleted = 0; + mutex_lock(&nft_net->commit_mutex); + if (!list_empty(&nf_tables_destroy_list)) +- rcu_barrier(); ++ nf_tables_trans_destroy_flush_work(); + again: + list_for_each_entry(table, &nft_net->tables, list) { + if (nft_table_has_owner(table) && +diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c +index 32cfd0a84b0e2..8c16681884b7e 100644 +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -901,12 +901,14 @@ static void pipapo_lt_bits_adjust(struct nft_pipapo_field *f) + static int pipapo_insert(struct nft_pipapo_field *f, const uint8_t *k, + int mask_bits) + { +- int rule = f->rules++, group, ret, bit_offset = 0; ++ int rule = f->rules, group, ret, bit_offset = 0; + +- ret = pipapo_resize(f, f->rules - 1, f->rules); ++ ret = pipapo_resize(f, f->rules, f->rules + 1); + if (ret) + return ret; + ++ f->rules++; ++ + for (group = 0; group < f->groups; group++) { + int i, v; + u8 mask; +@@ -1051,7 +1053,9 @@ static int pipapo_expand(struct nft_pipapo_field *f, + step++; + if (step >= len) { + if (!masks) { +- pipapo_insert(f, base, 0); ++ err = pipapo_insert(f, base, 0); ++ if (err < 0) ++ return err; + masks = 1; + } + goto out; +@@ -1234,6 +1238,9 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set, + else + ret = pipapo_expand(f, start, end, f->groups * f->bb); + ++ if (ret < 0) ++ return ret; ++ + if (f->bsize > bsize_max) + bsize_max = f->bsize; + +diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c +index 01d07e6a68119..e8f988e1c7e64 100644 +--- a/net/sched/sch_api.c ++++ b/net/sched/sch_api.c +@@ -1550,10 +1550,28 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, + return 0; + } + ++static bool req_create_or_replace(struct nlmsghdr *n) ++{ ++ return (n->nlmsg_flags & NLM_F_CREATE && ++ n->nlmsg_flags & NLM_F_REPLACE); ++} ++ ++static bool req_create_exclusive(struct nlmsghdr *n) ++{ ++ return (n->nlmsg_flags & NLM_F_CREATE && ++ n->nlmsg_flags & NLM_F_EXCL); ++} ++ ++static bool req_change(struct nlmsghdr *n) ++{ ++ return (!(n->nlmsg_flags & NLM_F_CREATE) && ++ !(n->nlmsg_flags & NLM_F_REPLACE) && ++ !(n->nlmsg_flags & NLM_F_EXCL)); ++} ++ + /* + * Create/change qdisc. + */ +- + static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, + struct netlink_ext_ack *extack) + { +@@ -1647,27 +1665,35 @@ replay: + * + * We know, that some child q is already + * attached to this parent and have choice: +- * either to change it or to create/graft new one. ++ * 1) change it or 2) create/graft new one. ++ * If the requested qdisc kind is different ++ * than the existing one, then we choose graft. ++ * If they are the same then this is "change" ++ * operation - just let it fallthrough.. + * + * 1. We are allowed to create/graft only +- * if CREATE and REPLACE flags are set. ++ * if the request is explicitly stating ++ * "please create if it doesn't exist". + * +- * 2. If EXCL is set, requestor wanted to say, +- * that qdisc tcm_handle is not expected ++ * 2. If the request is to exclusive create ++ * then the qdisc tcm_handle is not expected + * to exist, so that we choose create/graft too. + * + * 3. The last case is when no flags are set. ++ * This will happen when for example tc ++ * utility issues a "change" command. + * Alas, it is sort of hole in API, we + * cannot decide what to do unambiguously. +- * For now we select create/graft, if +- * user gave KIND, which does not match existing. ++ * For now we select create/graft. + */ +- if ((n->nlmsg_flags & NLM_F_CREATE) && +- (n->nlmsg_flags & NLM_F_REPLACE) && +- ((n->nlmsg_flags & NLM_F_EXCL) || +- (tca[TCA_KIND] && +- nla_strcmp(tca[TCA_KIND], q->ops->id)))) +- goto create_n_graft; ++ if (tca[TCA_KIND] && ++ nla_strcmp(tca[TCA_KIND], q->ops->id)) { ++ if (req_create_or_replace(n) || ++ req_create_exclusive(n)) ++ goto create_n_graft; ++ else if (req_change(n)) ++ goto create_n_graft2; ++ } + } + } + } else { +@@ -1701,6 +1727,7 @@ create_n_graft: + NL_SET_ERR_MSG(extack, "Qdisc not found. To create specify NLM_F_CREATE flag"); + return -ENOENT; + } ++create_n_graft2: + if (clid == TC_H_INGRESS) { + if (dev_ingress_queue(dev)) { + q = qdisc_create(dev, dev_ingress_queue(dev), +diff --git a/net/sctp/socket.c b/net/sctp/socket.c +index c806d272107ac..a11b0d903514c 100644 +--- a/net/sctp/socket.c ++++ b/net/sctp/socket.c +@@ -98,7 +98,7 @@ struct percpu_counter sctp_sockets_allocated; + + static void sctp_enter_memory_pressure(struct sock *sk) + { +- sctp_memory_pressure = 1; ++ WRITE_ONCE(sctp_memory_pressure, 1); + } + + +@@ -9472,7 +9472,7 @@ void sctp_copy_sock(struct sock *newsk, struct sock *sk, + newinet->inet_rcv_saddr = inet->inet_rcv_saddr; + newinet->inet_dport = htons(asoc->peer.port); + newinet->pmtudisc = inet->pmtudisc; +- newinet->inet_id = get_random_u16(); ++ atomic_set(&newinet->inet_id, get_random_u16()); + + newinet->uc_ttl = inet->uc_ttl; + newinet->mc_loop = 1; +diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c +index b098fde373abf..28c0771c4e8c3 100644 +--- a/net/sunrpc/xprtrdma/verbs.c ++++ b/net/sunrpc/xprtrdma/verbs.c +@@ -935,9 +935,6 @@ struct rpcrdma_rep *rpcrdma_rep_create(struct rpcrdma_xprt *r_xprt, + if (!rep->rr_rdmabuf) + goto out_free; + +- if (!rpcrdma_regbuf_dma_map(r_xprt, rep->rr_rdmabuf)) +- goto out_free_regbuf; +- + rep->rr_cid.ci_completion_id = + atomic_inc_return(&r_xprt->rx_ep->re_completion_ids); + +@@ -956,8 +953,6 @@ struct rpcrdma_rep *rpcrdma_rep_create(struct rpcrdma_xprt *r_xprt, + spin_unlock(&buf->rb_lock); + return rep; + +-out_free_regbuf: +- rpcrdma_regbuf_free(rep->rr_rdmabuf); + out_free: + kfree(rep); + out: +@@ -1363,6 +1358,10 @@ void rpcrdma_post_recvs(struct rpcrdma_xprt *r_xprt, int needed, bool temp) + rep = rpcrdma_rep_create(r_xprt, temp); + if (!rep) + break; ++ if (!rpcrdma_regbuf_dma_map(r_xprt, rep->rr_rdmabuf)) { ++ rpcrdma_rep_put(buf, rep); ++ break; ++ } + + rep->rr_cid.ci_queue_id = ep->re_attr.recv_cq->res.id; + trace_xprtrdma_post_recv(rep); +diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c +index adcfb63b3550d..6f9ff4643dcbc 100644 +--- a/security/selinux/ss/policydb.c ++++ b/security/selinux/ss/policydb.c +@@ -2005,6 +2005,7 @@ static int filename_trans_read_helper(struct policydb *p, void *fp) + if (!datum) + goto out; + ++ datum->next = NULL; + *dst = datum; + + /* ebitmap_read() will at least init the bitmap */ +@@ -2017,7 +2018,6 @@ static int filename_trans_read_helper(struct policydb *p, void *fp) + goto out; + + datum->otype = le32_to_cpu(buf[0]); +- datum->next = NULL; + + dst = &datum->next; + } +diff --git a/sound/pci/ymfpci/ymfpci.c b/sound/pci/ymfpci/ymfpci.c +index 82d4e0fda91be..d62a0e2ddf609 100644 +--- a/sound/pci/ymfpci/ymfpci.c ++++ b/sound/pci/ymfpci/ymfpci.c +@@ -150,8 +150,8 @@ static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, i + void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) { } + #endif /* SUPPORT_JOYSTICK */ + +-static int snd_card_ymfpci_probe(struct pci_dev *pci, +- const struct pci_device_id *pci_id) ++static int __snd_card_ymfpci_probe(struct pci_dev *pci, ++ const struct pci_device_id *pci_id) + { + static int dev; + struct snd_card *card; +@@ -333,6 +333,12 @@ static int snd_card_ymfpci_probe(struct pci_dev *pci, + return 0; + } + ++static int snd_card_ymfpci_probe(struct pci_dev *pci, ++ const struct pci_device_id *pci_id) ++{ ++ return snd_card_free_on_error(&pci->dev, __snd_card_ymfpci_probe(pci, pci_id)); ++} ++ + static struct pci_driver ymfpci_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_ymfpci_ids, +diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig +index 3968c478c9381..44d4e6e51a358 100644 +--- a/sound/soc/amd/Kconfig ++++ b/sound/soc/amd/Kconfig +@@ -71,6 +71,7 @@ config SND_SOC_AMD_RENOIR_MACH + config SND_SOC_AMD_ACP5x + tristate "AMD Audio Coprocessor-v5.x I2S support" + depends on X86 && PCI ++ select SND_AMD_ACP_CONFIG + help + This option enables ACP v5.x support on AMD platform + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +index c1ca3ceac5f2f..9a9571c3f08c0 100644 +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -217,7 +217,7 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), +- DMI_MATCH(DMI_PRODUCT_NAME, "82"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "82V2"), + } + }, + { +@@ -248,6 +248,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Redmi Book Pro 14 2022"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "M6500RC"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c +index f2b5032daa6ae..2f4b0ee93aced 100644 +--- a/sound/soc/codecs/cs35l41.c ++++ b/sound/soc/codecs/cs35l41.c +@@ -167,7 +167,7 @@ static int cs35l41_get_fs_mon_config_index(int freq) + static const DECLARE_TLV_DB_RANGE(dig_vol_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + 1, 913, TLV_DB_MINMAX_ITEM(-10200, 1200)); +-static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1); ++static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 50, 100, 0); + + static const struct snd_kcontrol_new dre_ctrl = + SOC_DAPM_SINGLE("Switch", CS35L41_PWR_CTRL3, 20, 1, 0); +diff --git a/tools/testing/selftests/drivers/net/bonding/bond-break-lacpdu-tx.sh b/tools/testing/selftests/drivers/net/bonding/bond-break-lacpdu-tx.sh +index 47ab90596acb2..6358df5752f90 100755 +--- a/tools/testing/selftests/drivers/net/bonding/bond-break-lacpdu-tx.sh ++++ b/tools/testing/selftests/drivers/net/bonding/bond-break-lacpdu-tx.sh +@@ -57,8 +57,8 @@ ip link add name veth2-bond type veth peer name veth2-end + + # add ports + ip link set fbond master fab-br0 +-ip link set veth1-bond down master fbond +-ip link set veth2-bond down master fbond ++ip link set veth1-bond master fbond ++ip link set veth2-bond master fbond + + # bring up + ip link set veth1-end up +diff --git a/tools/testing/selftests/drivers/net/mlxsw/sharedbuffer.sh b/tools/testing/selftests/drivers/net/mlxsw/sharedbuffer.sh +index 7d9e73a43a49b..0c47faff9274b 100755 +--- a/tools/testing/selftests/drivers/net/mlxsw/sharedbuffer.sh ++++ b/tools/testing/selftests/drivers/net/mlxsw/sharedbuffer.sh +@@ -98,12 +98,12 @@ sb_occ_etc_check() + + port_pool_test() + { +- local exp_max_occ=288 ++ local exp_max_occ=$(devlink_cell_size_get) + local max_occ + + devlink sb occupancy clearmax $DEVLINK_DEV + +- $MZ $h1 -c 1 -p 160 -a $h1mac -b $h2mac -A 192.0.1.1 -B 192.0.1.2 \ ++ $MZ $h1 -c 1 -p 10 -a $h1mac -b $h2mac -A 192.0.1.1 -B 192.0.1.2 \ + -t ip -q + + devlink sb occupancy snapshot $DEVLINK_DEV +@@ -126,12 +126,12 @@ port_pool_test() + + port_tc_ip_test() + { +- local exp_max_occ=288 ++ local exp_max_occ=$(devlink_cell_size_get) + local max_occ + + devlink sb occupancy clearmax $DEVLINK_DEV + +- $MZ $h1 -c 1 -p 160 -a $h1mac -b $h2mac -A 192.0.1.1 -B 192.0.1.2 \ ++ $MZ $h1 -c 1 -p 10 -a $h1mac -b $h2mac -A 192.0.1.1 -B 192.0.1.2 \ + -t ip -q + + devlink sb occupancy snapshot $DEVLINK_DEV +@@ -154,16 +154,12 @@ port_tc_ip_test() + + port_tc_arp_test() + { +- local exp_max_occ=96 ++ local exp_max_occ=$(devlink_cell_size_get) + local max_occ + +- if [[ $MLXSW_CHIP != "mlxsw_spectrum" ]]; then +- exp_max_occ=144 +- fi +- + devlink sb occupancy clearmax $DEVLINK_DEV + +- $MZ $h1 -c 1 -p 160 -a $h1mac -A 192.0.1.1 -t arp -q ++ $MZ $h1 -c 1 -p 10 -a $h1mac -A 192.0.1.1 -t arp -q + + devlink sb occupancy snapshot $DEVLINK_DEV + +diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile +index 69c58362c0edf..48d1a68be1d52 100644 +--- a/tools/testing/selftests/net/Makefile ++++ b/tools/testing/selftests/net/Makefile +@@ -71,14 +71,60 @@ TEST_GEN_FILES += bind_bhash + TEST_GEN_PROGS += sk_bind_sendto_listen + TEST_GEN_PROGS += sk_connect_zero_addr + TEST_PROGS += test_ingress_egress_chaining.sh ++TEST_GEN_FILES += nat6to4.o + + TEST_FILES := settings + + include ../lib.mk + +-include bpf/Makefile +- + $(OUTPUT)/reuseport_bpf_numa: LDLIBS += -lnuma + $(OUTPUT)/tcp_mmap: LDLIBS += -lpthread + $(OUTPUT)/tcp_inq: LDLIBS += -lpthread + $(OUTPUT)/bind_bhash: LDLIBS += -lpthread ++ ++# Rules to generate bpf obj nat6to4.o ++CLANG ?= clang ++SCRATCH_DIR := $(OUTPUT)/tools ++BUILD_DIR := $(SCRATCH_DIR)/build ++BPFDIR := $(abspath ../../../lib/bpf) ++APIDIR := $(abspath ../../../include/uapi) ++ ++CCINCLUDE += -I../bpf ++CCINCLUDE += -I../../../../usr/include/ ++CCINCLUDE += -I$(SCRATCH_DIR)/include ++ ++BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a ++ ++MAKE_DIRS := $(BUILD_DIR)/libbpf ++$(MAKE_DIRS): ++ mkdir -p $@ ++ ++# Get Clang's default includes on this system, as opposed to those seen by ++# '-target bpf'. This fixes "missing" files on some architectures/distros, ++# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc. ++# ++# Use '-idirafter': Don't interfere with include mechanics except where the ++# build would have failed anyways. ++define get_sys_includes ++$(shell $(1) $(2) -v -E - </dev/null 2>&1 \ ++ | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \ ++$(shell $(1) $(2) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}') ++endef ++ ++ifneq ($(CROSS_COMPILE),) ++CLANG_TARGET_ARCH = --target=$(notdir $(CROSS_COMPILE:%-=%)) ++endif ++ ++CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG),$(CLANG_TARGET_ARCH)) ++ ++$(OUTPUT)/nat6to4.o: nat6to4.c $(BPFOBJ) | $(MAKE_DIRS) ++ $(CLANG) -O2 -target bpf -c $< $(CCINCLUDE) $(CLANG_SYS_INCLUDES) -o $@ ++ ++$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \ ++ $(APIDIR)/linux/bpf.h \ ++ | $(BUILD_DIR)/libbpf ++ $(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \ ++ EXTRA_CFLAGS='-g -O0' \ ++ DESTDIR=$(SCRATCH_DIR) prefix= all install_headers ++ ++EXTRA_CLEAN := $(SCRATCH_DIR) +diff --git a/tools/testing/selftests/net/bpf/Makefile b/tools/testing/selftests/net/bpf/Makefile +deleted file mode 100644 +index 8ccaf8732eb22..0000000000000 +--- a/tools/testing/selftests/net/bpf/Makefile ++++ /dev/null +@@ -1,14 +0,0 @@ +-# SPDX-License-Identifier: GPL-2.0 +- +-CLANG ?= clang +-CCINCLUDE += -I../../bpf +-CCINCLUDE += -I../../../../lib +-CCINCLUDE += -I../../../../../usr/include/ +- +-TEST_CUSTOM_PROGS = $(OUTPUT)/bpf/nat6to4.o +-all: $(TEST_CUSTOM_PROGS) +- +-$(OUTPUT)/%.o: %.c +- $(CLANG) -O2 -target bpf -c $< $(CCINCLUDE) -o $@ +- +-EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) +diff --git a/tools/testing/selftests/net/bpf/nat6to4.c b/tools/testing/selftests/net/bpf/nat6to4.c +deleted file mode 100644 +index ac54c36b25fc8..0000000000000 +--- a/tools/testing/selftests/net/bpf/nat6to4.c ++++ /dev/null +@@ -1,285 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +- * This code is taken from the Android Open Source Project and the author +- * (Maciej Żenczykowski) has gave permission to relicense it under the +- * GPLv2. Therefore 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 +- +- * The original headers, including the original license headers, are +- * included below for completeness. +- * +- * Copyright (C) 2019 The Android Open Source Project +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +-#include <linux/bpf.h> +-#include <linux/if.h> +-#include <linux/if_ether.h> +-#include <linux/if_packet.h> +-#include <linux/in.h> +-#include <linux/in6.h> +-#include <linux/ip.h> +-#include <linux/ipv6.h> +-#include <linux/pkt_cls.h> +-#include <linux/swab.h> +-#include <stdbool.h> +-#include <stdint.h> +- +- +-#include <linux/udp.h> +- +-#include <bpf/bpf_helpers.h> +-#include <bpf/bpf_endian.h> +- +-#define IP_DF 0x4000 // Flag: "Don't Fragment" +- +-SEC("schedcls/ingress6/nat_6") +-int sched_cls_ingress6_nat_6_prog(struct __sk_buff *skb) +-{ +- const int l2_header_size = sizeof(struct ethhdr); +- void *data = (void *)(long)skb->data; +- const void *data_end = (void *)(long)skb->data_end; +- const struct ethhdr * const eth = data; // used iff is_ethernet +- const struct ipv6hdr * const ip6 = (void *)(eth + 1); +- +- // Require ethernet dst mac address to be our unicast address. +- if (skb->pkt_type != PACKET_HOST) +- return TC_ACT_OK; +- +- // Must be meta-ethernet IPv6 frame +- if (skb->protocol != bpf_htons(ETH_P_IPV6)) +- return TC_ACT_OK; +- +- // Must have (ethernet and) ipv6 header +- if (data + l2_header_size + sizeof(*ip6) > data_end) +- return TC_ACT_OK; +- +- // Ethertype - if present - must be IPv6 +- if (eth->h_proto != bpf_htons(ETH_P_IPV6)) +- return TC_ACT_OK; +- +- // IP version must be 6 +- if (ip6->version != 6) +- return TC_ACT_OK; +- // Maximum IPv6 payload length that can be translated to IPv4 +- if (bpf_ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) +- return TC_ACT_OK; +- switch (ip6->nexthdr) { +- case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6 +- case IPPROTO_UDP: // address means there is no need to update their checksums. +- case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers, +- case IPPROTO_ESP: // since there is never a checksum to update. +- break; +- default: // do not know how to handle anything else +- return TC_ACT_OK; +- } +- +- struct ethhdr eth2; // used iff is_ethernet +- +- eth2 = *eth; // Copy over the ethernet header (src/dst mac) +- eth2.h_proto = bpf_htons(ETH_P_IP); // But replace the ethertype +- +- struct iphdr ip = { +- .version = 4, // u4 +- .ihl = sizeof(struct iphdr) / sizeof(__u32), // u4 +- .tos = (ip6->priority << 4) + (ip6->flow_lbl[0] >> 4), // u8 +- .tot_len = bpf_htons(bpf_ntohs(ip6->payload_len) + sizeof(struct iphdr)), // u16 +- .id = 0, // u16 +- .frag_off = bpf_htons(IP_DF), // u16 +- .ttl = ip6->hop_limit, // u8 +- .protocol = ip6->nexthdr, // u8 +- .check = 0, // u16 +- .saddr = 0x0201a8c0, // u32 +- .daddr = 0x0101a8c0, // u32 +- }; +- +- // Calculate the IPv4 one's complement checksum of the IPv4 header. +- __wsum sum4 = 0; +- +- for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) +- sum4 += ((__u16 *)&ip)[i]; +- +- // Note that sum4 is guaranteed to be non-zero by virtue of ip.version == 4 +- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE +- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16 +- ip.check = (__u16)~sum4; // sum4 cannot be zero, so this is never 0xFFFF +- +- // Calculate the *negative* IPv6 16-bit one's complement checksum of the IPv6 header. +- __wsum sum6 = 0; +- // We'll end up with a non-zero sum due to ip6->version == 6 (which has '0' bits) +- for (int i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) +- sum6 += ~((__u16 *)ip6)[i]; // note the bitwise negation +- +- // Note that there is no L4 checksum update: we are relying on the checksum neutrality +- // of the ipv6 address chosen by netd's ClatdController. +- +- // Packet mutations begin - point of no return, but if this first modification fails +- // the packet is probably still pristine, so let clatd handle it. +- if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IP), 0)) +- return TC_ACT_OK; +- bpf_csum_update(skb, sum6); +- +- data = (void *)(long)skb->data; +- data_end = (void *)(long)skb->data_end; +- if (data + l2_header_size + sizeof(struct iphdr) > data_end) +- return TC_ACT_SHOT; +- +- struct ethhdr *new_eth = data; +- +- // Copy over the updated ethernet header +- *new_eth = eth2; +- +- // Copy over the new ipv4 header. +- *(struct iphdr *)(new_eth + 1) = ip; +- return bpf_redirect(skb->ifindex, BPF_F_INGRESS); +-} +- +-SEC("schedcls/egress4/snat4") +-int sched_cls_egress4_snat4_prog(struct __sk_buff *skb) +-{ +- const int l2_header_size = sizeof(struct ethhdr); +- void *data = (void *)(long)skb->data; +- const void *data_end = (void *)(long)skb->data_end; +- const struct ethhdr *const eth = data; // used iff is_ethernet +- const struct iphdr *const ip4 = (void *)(eth + 1); +- +- // Must be meta-ethernet IPv4 frame +- if (skb->protocol != bpf_htons(ETH_P_IP)) +- return TC_ACT_OK; +- +- // Must have ipv4 header +- if (data + l2_header_size + sizeof(struct ipv6hdr) > data_end) +- return TC_ACT_OK; +- +- // Ethertype - if present - must be IPv4 +- if (eth->h_proto != bpf_htons(ETH_P_IP)) +- return TC_ACT_OK; +- +- // IP version must be 4 +- if (ip4->version != 4) +- return TC_ACT_OK; +- +- // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header +- if (ip4->ihl != 5) +- return TC_ACT_OK; +- +- // Maximum IPv6 payload length that can be translated to IPv4 +- if (bpf_htons(ip4->tot_len) > 0xFFFF - sizeof(struct ipv6hdr)) +- return TC_ACT_OK; +- +- // Calculate the IPv4 one's complement checksum of the IPv4 header. +- __wsum sum4 = 0; +- +- for (int i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) +- sum4 += ((__u16 *)ip4)[i]; +- +- // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4 +- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE +- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16 +- // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF +- if (sum4 != 0xFFFF) +- return TC_ACT_OK; +- +- // Minimum IPv4 total length is the size of the header +- if (bpf_ntohs(ip4->tot_len) < sizeof(*ip4)) +- return TC_ACT_OK; +- +- // We are incapable of dealing with IPv4 fragments +- if (ip4->frag_off & ~bpf_htons(IP_DF)) +- return TC_ACT_OK; +- +- switch (ip4->protocol) { +- case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6 +- case IPPROTO_GRE: // address means there is no need to update their checksums. +- case IPPROTO_ESP: // We do not need to bother looking at GRE/ESP headers, +- break; // since there is never a checksum to update. +- +- case IPPROTO_UDP: // See above comment, but must also have UDP header... +- if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) +- return TC_ACT_OK; +- const struct udphdr *uh = (const struct udphdr *)(ip4 + 1); +- // If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the +- // checksum. Otherwise the network or more likely the NAT64 gateway might +- // drop the packet because in most cases IPv6/UDP packets with a zero checksum +- // are invalid. See RFC 6935. TODO: calculate checksum via bpf_csum_diff() +- if (!uh->check) +- return TC_ACT_OK; +- break; +- +- default: // do not know how to handle anything else +- return TC_ACT_OK; +- } +- struct ethhdr eth2; // used iff is_ethernet +- +- eth2 = *eth; // Copy over the ethernet header (src/dst mac) +- eth2.h_proto = bpf_htons(ETH_P_IPV6); // But replace the ethertype +- +- struct ipv6hdr ip6 = { +- .version = 6, // __u8:4 +- .priority = ip4->tos >> 4, // __u8:4 +- .flow_lbl = {(ip4->tos & 0xF) << 4, 0, 0}, // __u8[3] +- .payload_len = bpf_htons(bpf_ntohs(ip4->tot_len) - 20), // __be16 +- .nexthdr = ip4->protocol, // __u8 +- .hop_limit = ip4->ttl, // __u8 +- }; +- ip6.saddr.in6_u.u6_addr32[0] = bpf_htonl(0x20010db8); +- ip6.saddr.in6_u.u6_addr32[1] = 0; +- ip6.saddr.in6_u.u6_addr32[2] = 0; +- ip6.saddr.in6_u.u6_addr32[3] = bpf_htonl(1); +- ip6.daddr.in6_u.u6_addr32[0] = bpf_htonl(0x20010db8); +- ip6.daddr.in6_u.u6_addr32[1] = 0; +- ip6.daddr.in6_u.u6_addr32[2] = 0; +- ip6.daddr.in6_u.u6_addr32[3] = bpf_htonl(2); +- +- // Calculate the IPv6 16-bit one's complement checksum of the IPv6 header. +- __wsum sum6 = 0; +- // We'll end up with a non-zero sum due to ip6.version == 6 +- for (int i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) +- sum6 += ((__u16 *)&ip6)[i]; +- +- // Packet mutations begin - point of no return, but if this first modification fails +- // the packet is probably still pristine, so let clatd handle it. +- if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IPV6), 0)) +- return TC_ACT_OK; +- +- // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet. +- // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload, +- // thus we need to subtract out the ipv4 header's sum, and add in the ipv6 header's sum. +- // However, we've already verified the ipv4 checksum is correct and thus 0. +- // Thus we only need to add the ipv6 header's sum. +- // +- // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error +- // (-ENOTSUPP) if it isn't. So we just ignore the return code (see above for more details). +- bpf_csum_update(skb, sum6); +- +- // bpf_skb_change_proto() invalidates all pointers - reload them. +- data = (void *)(long)skb->data; +- data_end = (void *)(long)skb->data_end; +- +- // I cannot think of any valid way for this error condition to trigger, however I do +- // believe the explicit check is required to keep the in kernel ebpf verifier happy. +- if (data + l2_header_size + sizeof(ip6) > data_end) +- return TC_ACT_SHOT; +- +- struct ethhdr *new_eth = data; +- +- // Copy over the updated ethernet header +- *new_eth = eth2; +- // Copy over the new ipv4 header. +- *(struct ipv6hdr *)(new_eth + 1) = ip6; +- return TC_ACT_OK; +-} +- +-char _license[] SEC("license") = ("GPL"); +diff --git a/tools/testing/selftests/net/nat6to4.c b/tools/testing/selftests/net/nat6to4.c +new file mode 100644 +index 0000000000000..ac54c36b25fc8 +--- /dev/null ++++ b/tools/testing/selftests/net/nat6to4.c +@@ -0,0 +1,285 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * This code is taken from the Android Open Source Project and the author ++ * (Maciej Żenczykowski) has gave permission to relicense it under the ++ * GPLv2. Therefore 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 ++ ++ * The original headers, including the original license headers, are ++ * included below for completeness. ++ * ++ * Copyright (C) 2019 The Android Open Source Project ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++#include <linux/bpf.h> ++#include <linux/if.h> ++#include <linux/if_ether.h> ++#include <linux/if_packet.h> ++#include <linux/in.h> ++#include <linux/in6.h> ++#include <linux/ip.h> ++#include <linux/ipv6.h> ++#include <linux/pkt_cls.h> ++#include <linux/swab.h> ++#include <stdbool.h> ++#include <stdint.h> ++ ++ ++#include <linux/udp.h> ++ ++#include <bpf/bpf_helpers.h> ++#include <bpf/bpf_endian.h> ++ ++#define IP_DF 0x4000 // Flag: "Don't Fragment" ++ ++SEC("schedcls/ingress6/nat_6") ++int sched_cls_ingress6_nat_6_prog(struct __sk_buff *skb) ++{ ++ const int l2_header_size = sizeof(struct ethhdr); ++ void *data = (void *)(long)skb->data; ++ const void *data_end = (void *)(long)skb->data_end; ++ const struct ethhdr * const eth = data; // used iff is_ethernet ++ const struct ipv6hdr * const ip6 = (void *)(eth + 1); ++ ++ // Require ethernet dst mac address to be our unicast address. ++ if (skb->pkt_type != PACKET_HOST) ++ return TC_ACT_OK; ++ ++ // Must be meta-ethernet IPv6 frame ++ if (skb->protocol != bpf_htons(ETH_P_IPV6)) ++ return TC_ACT_OK; ++ ++ // Must have (ethernet and) ipv6 header ++ if (data + l2_header_size + sizeof(*ip6) > data_end) ++ return TC_ACT_OK; ++ ++ // Ethertype - if present - must be IPv6 ++ if (eth->h_proto != bpf_htons(ETH_P_IPV6)) ++ return TC_ACT_OK; ++ ++ // IP version must be 6 ++ if (ip6->version != 6) ++ return TC_ACT_OK; ++ // Maximum IPv6 payload length that can be translated to IPv4 ++ if (bpf_ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) ++ return TC_ACT_OK; ++ switch (ip6->nexthdr) { ++ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6 ++ case IPPROTO_UDP: // address means there is no need to update their checksums. ++ case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers, ++ case IPPROTO_ESP: // since there is never a checksum to update. ++ break; ++ default: // do not know how to handle anything else ++ return TC_ACT_OK; ++ } ++ ++ struct ethhdr eth2; // used iff is_ethernet ++ ++ eth2 = *eth; // Copy over the ethernet header (src/dst mac) ++ eth2.h_proto = bpf_htons(ETH_P_IP); // But replace the ethertype ++ ++ struct iphdr ip = { ++ .version = 4, // u4 ++ .ihl = sizeof(struct iphdr) / sizeof(__u32), // u4 ++ .tos = (ip6->priority << 4) + (ip6->flow_lbl[0] >> 4), // u8 ++ .tot_len = bpf_htons(bpf_ntohs(ip6->payload_len) + sizeof(struct iphdr)), // u16 ++ .id = 0, // u16 ++ .frag_off = bpf_htons(IP_DF), // u16 ++ .ttl = ip6->hop_limit, // u8 ++ .protocol = ip6->nexthdr, // u8 ++ .check = 0, // u16 ++ .saddr = 0x0201a8c0, // u32 ++ .daddr = 0x0101a8c0, // u32 ++ }; ++ ++ // Calculate the IPv4 one's complement checksum of the IPv4 header. ++ __wsum sum4 = 0; ++ ++ for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) ++ sum4 += ((__u16 *)&ip)[i]; ++ ++ // Note that sum4 is guaranteed to be non-zero by virtue of ip.version == 4 ++ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE ++ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16 ++ ip.check = (__u16)~sum4; // sum4 cannot be zero, so this is never 0xFFFF ++ ++ // Calculate the *negative* IPv6 16-bit one's complement checksum of the IPv6 header. ++ __wsum sum6 = 0; ++ // We'll end up with a non-zero sum due to ip6->version == 6 (which has '0' bits) ++ for (int i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) ++ sum6 += ~((__u16 *)ip6)[i]; // note the bitwise negation ++ ++ // Note that there is no L4 checksum update: we are relying on the checksum neutrality ++ // of the ipv6 address chosen by netd's ClatdController. ++ ++ // Packet mutations begin - point of no return, but if this first modification fails ++ // the packet is probably still pristine, so let clatd handle it. ++ if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IP), 0)) ++ return TC_ACT_OK; ++ bpf_csum_update(skb, sum6); ++ ++ data = (void *)(long)skb->data; ++ data_end = (void *)(long)skb->data_end; ++ if (data + l2_header_size + sizeof(struct iphdr) > data_end) ++ return TC_ACT_SHOT; ++ ++ struct ethhdr *new_eth = data; ++ ++ // Copy over the updated ethernet header ++ *new_eth = eth2; ++ ++ // Copy over the new ipv4 header. ++ *(struct iphdr *)(new_eth + 1) = ip; ++ return bpf_redirect(skb->ifindex, BPF_F_INGRESS); ++} ++ ++SEC("schedcls/egress4/snat4") ++int sched_cls_egress4_snat4_prog(struct __sk_buff *skb) ++{ ++ const int l2_header_size = sizeof(struct ethhdr); ++ void *data = (void *)(long)skb->data; ++ const void *data_end = (void *)(long)skb->data_end; ++ const struct ethhdr *const eth = data; // used iff is_ethernet ++ const struct iphdr *const ip4 = (void *)(eth + 1); ++ ++ // Must be meta-ethernet IPv4 frame ++ if (skb->protocol != bpf_htons(ETH_P_IP)) ++ return TC_ACT_OK; ++ ++ // Must have ipv4 header ++ if (data + l2_header_size + sizeof(struct ipv6hdr) > data_end) ++ return TC_ACT_OK; ++ ++ // Ethertype - if present - must be IPv4 ++ if (eth->h_proto != bpf_htons(ETH_P_IP)) ++ return TC_ACT_OK; ++ ++ // IP version must be 4 ++ if (ip4->version != 4) ++ return TC_ACT_OK; ++ ++ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header ++ if (ip4->ihl != 5) ++ return TC_ACT_OK; ++ ++ // Maximum IPv6 payload length that can be translated to IPv4 ++ if (bpf_htons(ip4->tot_len) > 0xFFFF - sizeof(struct ipv6hdr)) ++ return TC_ACT_OK; ++ ++ // Calculate the IPv4 one's complement checksum of the IPv4 header. ++ __wsum sum4 = 0; ++ ++ for (int i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) ++ sum4 += ((__u16 *)ip4)[i]; ++ ++ // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4 ++ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE ++ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16 ++ // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF ++ if (sum4 != 0xFFFF) ++ return TC_ACT_OK; ++ ++ // Minimum IPv4 total length is the size of the header ++ if (bpf_ntohs(ip4->tot_len) < sizeof(*ip4)) ++ return TC_ACT_OK; ++ ++ // We are incapable of dealing with IPv4 fragments ++ if (ip4->frag_off & ~bpf_htons(IP_DF)) ++ return TC_ACT_OK; ++ ++ switch (ip4->protocol) { ++ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6 ++ case IPPROTO_GRE: // address means there is no need to update their checksums. ++ case IPPROTO_ESP: // We do not need to bother looking at GRE/ESP headers, ++ break; // since there is never a checksum to update. ++ ++ case IPPROTO_UDP: // See above comment, but must also have UDP header... ++ if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) ++ return TC_ACT_OK; ++ const struct udphdr *uh = (const struct udphdr *)(ip4 + 1); ++ // If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the ++ // checksum. Otherwise the network or more likely the NAT64 gateway might ++ // drop the packet because in most cases IPv6/UDP packets with a zero checksum ++ // are invalid. See RFC 6935. TODO: calculate checksum via bpf_csum_diff() ++ if (!uh->check) ++ return TC_ACT_OK; ++ break; ++ ++ default: // do not know how to handle anything else ++ return TC_ACT_OK; ++ } ++ struct ethhdr eth2; // used iff is_ethernet ++ ++ eth2 = *eth; // Copy over the ethernet header (src/dst mac) ++ eth2.h_proto = bpf_htons(ETH_P_IPV6); // But replace the ethertype ++ ++ struct ipv6hdr ip6 = { ++ .version = 6, // __u8:4 ++ .priority = ip4->tos >> 4, // __u8:4 ++ .flow_lbl = {(ip4->tos & 0xF) << 4, 0, 0}, // __u8[3] ++ .payload_len = bpf_htons(bpf_ntohs(ip4->tot_len) - 20), // __be16 ++ .nexthdr = ip4->protocol, // __u8 ++ .hop_limit = ip4->ttl, // __u8 ++ }; ++ ip6.saddr.in6_u.u6_addr32[0] = bpf_htonl(0x20010db8); ++ ip6.saddr.in6_u.u6_addr32[1] = 0; ++ ip6.saddr.in6_u.u6_addr32[2] = 0; ++ ip6.saddr.in6_u.u6_addr32[3] = bpf_htonl(1); ++ ip6.daddr.in6_u.u6_addr32[0] = bpf_htonl(0x20010db8); ++ ip6.daddr.in6_u.u6_addr32[1] = 0; ++ ip6.daddr.in6_u.u6_addr32[2] = 0; ++ ip6.daddr.in6_u.u6_addr32[3] = bpf_htonl(2); ++ ++ // Calculate the IPv6 16-bit one's complement checksum of the IPv6 header. ++ __wsum sum6 = 0; ++ // We'll end up with a non-zero sum due to ip6.version == 6 ++ for (int i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) ++ sum6 += ((__u16 *)&ip6)[i]; ++ ++ // Packet mutations begin - point of no return, but if this first modification fails ++ // the packet is probably still pristine, so let clatd handle it. ++ if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IPV6), 0)) ++ return TC_ACT_OK; ++ ++ // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet. ++ // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload, ++ // thus we need to subtract out the ipv4 header's sum, and add in the ipv6 header's sum. ++ // However, we've already verified the ipv4 checksum is correct and thus 0. ++ // Thus we only need to add the ipv6 header's sum. ++ // ++ // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error ++ // (-ENOTSUPP) if it isn't. So we just ignore the return code (see above for more details). ++ bpf_csum_update(skb, sum6); ++ ++ // bpf_skb_change_proto() invalidates all pointers - reload them. ++ data = (void *)(long)skb->data; ++ data_end = (void *)(long)skb->data_end; ++ ++ // I cannot think of any valid way for this error condition to trigger, however I do ++ // believe the explicit check is required to keep the in kernel ebpf verifier happy. ++ if (data + l2_header_size + sizeof(ip6) > data_end) ++ return TC_ACT_SHOT; ++ ++ struct ethhdr *new_eth = data; ++ ++ // Copy over the updated ethernet header ++ *new_eth = eth2; ++ // Copy over the new ipv4 header. ++ *(struct ipv6hdr *)(new_eth + 1) = ip6; ++ return TC_ACT_OK; ++} ++ ++char _license[] SEC("license") = ("GPL"); +diff --git a/tools/testing/selftests/net/udpgro_frglist.sh b/tools/testing/selftests/net/udpgro_frglist.sh +index c9c4b9d658390..0a6359bed0b92 100755 +--- a/tools/testing/selftests/net/udpgro_frglist.sh ++++ b/tools/testing/selftests/net/udpgro_frglist.sh +@@ -40,8 +40,8 @@ run_one() { + + ip -n "${PEER_NS}" link set veth1 xdp object ${BPF_FILE} section xdp + tc -n "${PEER_NS}" qdisc add dev veth1 clsact +- tc -n "${PEER_NS}" filter add dev veth1 ingress prio 4 protocol ipv6 bpf object-file ../bpf/nat6to4.o section schedcls/ingress6/nat_6 direct-action +- tc -n "${PEER_NS}" filter add dev veth1 egress prio 4 protocol ip bpf object-file ../bpf/nat6to4.o section schedcls/egress4/snat4 direct-action ++ tc -n "${PEER_NS}" filter add dev veth1 ingress prio 4 protocol ipv6 bpf object-file nat6to4.o section schedcls/ingress6/nat_6 direct-action ++ tc -n "${PEER_NS}" filter add dev veth1 egress prio 4 protocol ip bpf object-file nat6to4.o section schedcls/egress4/snat4 direct-action + echo ${rx_args} + ip netns exec "${PEER_NS}" ./udpgso_bench_rx ${rx_args} -r & + +@@ -88,8 +88,8 @@ if [ ! -f ${BPF_FILE} ]; then + exit -1 + fi + +-if [ ! -f bpf/nat6to4.o ]; then +- echo "Missing nat6to4 helper. Build bpfnat6to4.o selftest first" ++if [ ! -f nat6to4.o ]; then ++ echo "Missing nat6to4 helper. Build bpf nat6to4.o selftest first" + exit -1 + fi + |