From a54f499838292c1768f6575ed1ec7cf35f1b6489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=B6hmwalder?= Date: Wed, 6 May 2026 14:45:40 +0200 Subject: [PATCH 001/117] drbd: move UAPI headers to include/uapi/linux/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drbd.h and drbd_limits.h contain only type definitions, enums, and constants shared between kernel and userspace. These should be part of UAPI. Split the genl_api header into two: the genlmsghdr and the enums are UAPI, the rest stays there for now (it will be removed by one of the next commits in this series). drbd_config.h is clearly DRBD-internal, so move it there. Signed-off-by: Christoph Böhmwalder Acked-by: Jakub Kicinski Link: https://patch.msgid.link/20260506124541.1951772-2-christoph.boehmwalder@linbit.com Signed-off-by: Jens Axboe --- drivers/block/drbd/drbd_buildtag.c | 2 +- .../block/drbd}/drbd_config.h | 0 drivers/block/drbd/drbd_int.h | 2 +- include/linux/drbd_genl_api.h | 40 ------------------ include/{ => uapi}/linux/drbd.h | 42 ++++++++++++++++++- include/{ => uapi}/linux/drbd_limits.h | 2 +- 6 files changed, 44 insertions(+), 44 deletions(-) rename {include/linux => drivers/block/drbd}/drbd_config.h (100%) rename include/{ => uapi}/linux/drbd.h (87%) rename include/{ => uapi}/linux/drbd_limits.h (99%) diff --git a/drivers/block/drbd/drbd_buildtag.c b/drivers/block/drbd/drbd_buildtag.c index cb1aa66d7d5d..cd0389488f63 100644 --- a/drivers/block/drbd/drbd_buildtag.c +++ b/drivers/block/drbd/drbd_buildtag.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -#include +#include "drbd_config.h" #include const char *drbd_buildtag(void) diff --git a/include/linux/drbd_config.h b/drivers/block/drbd/drbd_config.h similarity index 100% rename from include/linux/drbd_config.h rename to drivers/block/drbd/drbd_config.h diff --git a/drivers/block/drbd/drbd_int.h b/drivers/block/drbd/drbd_int.h index f6d6276974ee..f3d746a6d6fd 100644 --- a/drivers/block/drbd/drbd_int.h +++ b/drivers/block/drbd/drbd_int.h @@ -34,7 +34,7 @@ #include #include #include -#include +#include "drbd_config.h" #include "drbd_strings.h" #include "drbd_state.h" #include "drbd_protocol.h" diff --git a/include/linux/drbd_genl_api.h b/include/linux/drbd_genl_api.h index 70682c058027..19d263924852 100644 --- a/include/linux/drbd_genl_api.h +++ b/include/linux/drbd_genl_api.h @@ -2,46 +2,6 @@ #ifndef DRBD_GENL_STRUCT_H #define DRBD_GENL_STRUCT_H -/** - * struct drbd_genlmsghdr - DRBD specific header used in NETLINK_GENERIC requests - * @minor: - * For admin requests (user -> kernel): which minor device to operate on. - * For (unicast) replies or informational (broadcast) messages - * (kernel -> user): which minor device the information is about. - * If we do not operate on minors, but on connections or resources, - * the minor value shall be (~0), and the attribute DRBD_NLA_CFG_CONTEXT - * is used instead. - * @flags: possible operation modifiers (relevant only for user->kernel): - * DRBD_GENL_F_SET_DEFAULTS - * @volume: - * When creating a new minor (adding it to a resource), the resource needs - * to know which volume number within the resource this is supposed to be. - * The volume number corresponds to the same volume number on the remote side, - * whereas the minor number on the remote side may be different - * (union with flags). - * @ret_code: kernel->userland unicast cfg reply return code (union with flags); - */ -struct drbd_genlmsghdr { - __u32 minor; - union { - __u32 flags; - __s32 ret_code; - }; -}; - -/* To be used in drbd_genlmsghdr.flags */ -enum { - DRBD_GENL_F_SET_DEFAULTS = 1, -}; - -enum drbd_state_info_bcast_reason { - SIB_GET_STATUS_REPLY = 1, - SIB_STATE_CHANGE = 2, - SIB_HELPER_PRE = 3, - SIB_HELPER_POST = 4, - SIB_SYNC_PROGRESS = 5, -}; - /* hack around predefined gcc/cpp "linux=1", * we cannot possibly include <1/drbd_genl.h> */ #undef linux diff --git a/include/linux/drbd.h b/include/uapi/linux/drbd.h similarity index 87% rename from include/linux/drbd.h rename to include/uapi/linux/drbd.h index 5468a2399d48..7930a972d8a4 100644 --- a/include/linux/drbd.h +++ b/include/uapi/linux/drbd.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ /* drbd.h Kernel module for 2.6.x Kernels @@ -389,4 +389,44 @@ enum write_ordering_e { #define DRBD_CPU_MASK_SIZE 32 +/** + * struct drbd_genlmsghdr - DRBD specific header used in NETLINK_GENERIC requests + * @minor: + * For admin requests (user -> kernel): which minor device to operate on. + * For (unicast) replies or informational (broadcast) messages + * (kernel -> user): which minor device the information is about. + * If we do not operate on minors, but on connections or resources, + * the minor value shall be (~0), and the attribute DRBD_NLA_CFG_CONTEXT + * is used instead. + * @flags: possible operation modifiers (relevant only for user->kernel): + * DRBD_GENL_F_SET_DEFAULTS + * @volume: + * When creating a new minor (adding it to a resource), the resource needs + * to know which volume number within the resource this is supposed to be. + * The volume number corresponds to the same volume number on the remote side, + * whereas the minor number on the remote side may be different + * (union with flags). + * @ret_code: kernel->userland unicast cfg reply return code (union with flags); + */ +struct drbd_genlmsghdr { + __u32 minor; + union { + __u32 flags; + __s32 ret_code; + }; +}; + +/* To be used in drbd_genlmsghdr.flags */ +enum { + DRBD_GENL_F_SET_DEFAULTS = 1, +}; + +enum drbd_state_info_bcast_reason { + SIB_GET_STATUS_REPLY = 1, + SIB_STATE_CHANGE = 2, + SIB_HELPER_PRE = 3, + SIB_HELPER_POST = 4, + SIB_SYNC_PROGRESS = 5, +}; + #endif diff --git a/include/linux/drbd_limits.h b/include/uapi/linux/drbd_limits.h similarity index 99% rename from include/linux/drbd_limits.h rename to include/uapi/linux/drbd_limits.h index 5b042fb427e9..a72a102d1ca7 100644 --- a/include/linux/drbd_limits.h +++ b/include/uapi/linux/drbd_limits.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* drbd_limits.h This file is part of DRBD by Philipp Reisner and Lars Ellenberg. From 8098eeb693c4cc4e774c62fbd4875197cb5578ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=B6hmwalder?= Date: Wed, 6 May 2026 14:45:41 +0200 Subject: [PATCH 002/117] drbd: replace genl_magic with explicit netlink serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the genl_magic multi-include macro system with explicit serialization and parsing. The *_gen files were initially produced from a YNL spec via a customized ynl-gen-c, but the DRBD netlink family is effectively frozen, so the generator is kept unmodified. All new functionality will land in a separate, properly-designed family. Carry the resulting code as ordinary in-tree source rather than landing the spec and generator changes that produced it. The bulk of the changes are mechanical renames to fit the YNL naming conventions: - Handler functions: drbd_adm_* -> drbd_nl_*_doit/dumpit - GENL_MAGIC_VERSION -> DRBD_FAMILY_VERSION - GENL_MAGIC_FAMILY_HDRSZ -> sizeof(struct drbd_genlmsghdr) - drbd_genl_family -> drbd_nl_family - Attribute IDs: T_* -> DRBD_A_* Remove the nested_attr_tb static global buffer and move to a per-call allocation approach: each deserialization manages its own nested attribute table. This will be needed anyway when we eventually move to parallel_ops, and it's actually simpler this way, so make the move now. Replace the functionality of the "sensitive" flag: this was only used by a single field (shared_secret); open-code redaction logic for that locally. Also replace the "invariant" flag: this only had a couple of users, and those basically never change. Hard code the check directly inline. The genl_family struct itself is defined manually in drbd_nl.c. Also replace a couple of drbd-specific wrappers (nla_put_u64_0pad, drbd_nla_find_nested) with standard kernel functions while we're at it. Finally, completely remove the genl_magic system; DRBD was its only user. Signed-off-by: Christoph Böhmwalder Acked-by: Jakub Kicinski Link: https://patch.msgid.link/20260506124541.1951772-3-christoph.boehmwalder@linbit.com Signed-off-by: Jens Axboe --- drivers/block/drbd/Makefile | 1 + drivers/block/drbd/drbd_debugfs.c | 2 +- drivers/block/drbd/drbd_int.h | 4 +- drivers/block/drbd/drbd_main.c | 6 +- drivers/block/drbd/drbd_nl.c | 414 +++-- drivers/block/drbd/drbd_nl_gen.c | 2606 +++++++++++++++++++++++++++++ drivers/block/drbd/drbd_nl_gen.h | 395 +++++ drivers/block/drbd/drbd_proc.c | 2 +- include/linux/drbd_genl.h | 536 ------ include/linux/drbd_genl_api.h | 16 - include/linux/genl_magic_func.h | 413 ----- include/linux/genl_magic_struct.h | 272 --- include/uapi/linux/drbd.h | 3 + include/uapi/linux/drbd_genl.h | 359 ++++ 14 files changed, 3608 insertions(+), 1421 deletions(-) create mode 100644 drivers/block/drbd/drbd_nl_gen.c create mode 100644 drivers/block/drbd/drbd_nl_gen.h delete mode 100644 include/linux/drbd_genl.h delete mode 100644 include/linux/drbd_genl_api.h delete mode 100644 include/linux/genl_magic_func.h delete mode 100644 include/linux/genl_magic_struct.h create mode 100644 include/uapi/linux/drbd_genl.h diff --git a/drivers/block/drbd/Makefile b/drivers/block/drbd/Makefile index 187eaf81f0f8..5faaa8a8e7f0 100644 --- a/drivers/block/drbd/Makefile +++ b/drivers/block/drbd/Makefile @@ -3,6 +3,7 @@ drbd-y := drbd_buildtag.o drbd_bitmap.o drbd_proc.o drbd-y += drbd_worker.o drbd_receiver.o drbd_req.o drbd_actlog.o drbd-y += drbd_main.o drbd_strings.o drbd_nl.o drbd-y += drbd_interval.o drbd_state.o +drbd-y += drbd_nl_gen.o drbd-$(CONFIG_DEBUG_FS) += drbd_debugfs.o obj-$(CONFIG_BLK_DEV_DRBD) += drbd.o diff --git a/drivers/block/drbd/drbd_debugfs.c b/drivers/block/drbd/drbd_debugfs.c index 12460b584bcb..371abcd7e880 100644 --- a/drivers/block/drbd/drbd_debugfs.c +++ b/drivers/block/drbd/drbd_debugfs.c @@ -844,7 +844,7 @@ static int drbd_version_show(struct seq_file *m, void *ignored) { seq_printf(m, "# %s\n", drbd_buildtag()); seq_printf(m, "VERSION=%s\n", REL_VERSION); - seq_printf(m, "API_VERSION=%u\n", GENL_MAGIC_VERSION); + seq_printf(m, "API_VERSION=%u\n", DRBD_FAMILY_VERSION); seq_printf(m, "PRO_VERSION_MIN=%u\n", PRO_VERSION_MIN); seq_printf(m, "PRO_VERSION_MAX=%u\n", PRO_VERSION_MAX); return 0; diff --git a/drivers/block/drbd/drbd_int.h b/drivers/block/drbd/drbd_int.h index f3d746a6d6fd..48b45c3142f7 100644 --- a/drivers/block/drbd/drbd_int.h +++ b/drivers/block/drbd/drbd_int.h @@ -32,14 +32,16 @@ #include #include #include -#include #include #include "drbd_config.h" +#include "drbd_nl_gen.h" #include "drbd_strings.h" #include "drbd_state.h" #include "drbd_protocol.h" #include "drbd_polymorph_printk.h" +extern struct genl_family drbd_nl_family; + /* shared module parameters, defined in drbd_main.c */ #ifdef CONFIG_DRBD_FAULT_INJECTION extern int drbd_enable_faults; diff --git a/drivers/block/drbd/drbd_main.c b/drivers/block/drbd/drbd_main.c index b1a721dd0496..a2a841c89201 100644 --- a/drivers/block/drbd/drbd_main.c +++ b/drivers/block/drbd/drbd_main.c @@ -2324,7 +2324,7 @@ static void drbd_cleanup(void) if (retry.wq) destroy_workqueue(retry.wq); - drbd_genl_unregister(); + genl_unregister_family(&drbd_nl_family); idr_for_each_entry(&drbd_devices, device, i) drbd_delete_device(device); @@ -2846,7 +2846,7 @@ static int __init drbd_init(void) mutex_init(&resources_mutex); INIT_LIST_HEAD(&drbd_resources); - err = drbd_genl_register(); + err = genl_register_family(&drbd_nl_family); if (err) { pr_err("unable to register generic netlink family\n"); goto fail; @@ -2876,7 +2876,7 @@ static int __init drbd_init(void) pr_info("initialized. " "Version: " REL_VERSION " (api:%d/proto:%d-%d)\n", - GENL_MAGIC_VERSION, PRO_VERSION_MIN, PRO_VERSION_MAX); + DRBD_FAMILY_VERSION, PRO_VERSION_MIN, PRO_VERSION_MAX); pr_info("%s\n", drbd_buildtag()); pr_info("registered as block device major %d\n", DRBD_MAJOR); return 0; /* Success! */ diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c index c2ac555473e7..f9ffcd67607b 100644 --- a/drivers/block/drbd/drbd_nl.c +++ b/drivers/block/drbd/drbd_nl.c @@ -31,59 +31,13 @@ #include -/* .doit */ -// int drbd_adm_create_resource(struct sk_buff *skb, struct genl_info *info); -// int drbd_adm_delete_resource(struct sk_buff *skb, struct genl_info *info); +#include "drbd_nl_gen.h" -int drbd_adm_new_minor(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_del_minor(struct sk_buff *skb, struct genl_info *info); - -int drbd_adm_new_resource(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_del_resource(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_down(struct sk_buff *skb, struct genl_info *info); - -int drbd_adm_set_role(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_disk_opts(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_start_ov(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_new_c_uuid(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_invalidate(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_invalidate_peer(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_pause_sync(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_resume_sync(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_suspend_io(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_resume_io(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_outdate(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_resource_opts(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_get_status(struct sk_buff *skb, struct genl_info *info); -int drbd_adm_get_timeout_type(struct sk_buff *skb, struct genl_info *info); -/* .dumpit */ -int drbd_adm_get_status_all(struct sk_buff *skb, struct netlink_callback *cb); -int drbd_adm_dump_resources(struct sk_buff *skb, struct netlink_callback *cb); -int drbd_adm_dump_devices(struct sk_buff *skb, struct netlink_callback *cb); -int drbd_adm_dump_devices_done(struct netlink_callback *cb); -int drbd_adm_dump_connections(struct sk_buff *skb, struct netlink_callback *cb); -int drbd_adm_dump_connections_done(struct netlink_callback *cb); -int drbd_adm_dump_peer_devices(struct sk_buff *skb, struct netlink_callback *cb); -int drbd_adm_dump_peer_devices_done(struct netlink_callback *cb); -int drbd_adm_get_initial_state(struct sk_buff *skb, struct netlink_callback *cb); - -#include - -static int drbd_pre_doit(const struct genl_split_ops *ops, - struct sk_buff *skb, struct genl_info *info); -static void drbd_post_doit(const struct genl_split_ops *ops, - struct sk_buff *skb, struct genl_info *info); - -#define GENL_MAGIC_FAMILY_PRE_DOIT drbd_pre_doit -#define GENL_MAGIC_FAMILY_POST_DOIT drbd_post_doit - -#include +static int drbd_genl_multicast_events(struct sk_buff *skb, gfp_t flags) +{ + return genlmsg_multicast(&drbd_nl_family, skb, 0, + DRBD_NLGRP_EVENTS, flags); +} static atomic_t drbd_genl_seq = ATOMIC_INIT(2); /* two. */ static atomic_t notify_genl_seq = ATOMIC_INIT(2); /* two. */ @@ -114,7 +68,7 @@ static int drbd_msg_put_info(struct sk_buff *skb, const char *info) if (!nla) return err; - err = nla_put_string(skb, T_info_text, info); + err = nla_put_string(skb, DRBD_A_DRBD_CFG_REPLY_INFO_TEXT, info); if (err) { nla_nest_cancel(skb, nla); return err; @@ -135,7 +89,7 @@ static int drbd_msg_sprintf_info(struct sk_buff *skb, const char *fmt, ...) if (!nla) return err; - txt = nla_reserve(skb, T_info_text, 256); + txt = nla_reserve(skb, DRBD_A_DRBD_CFG_REPLY_INFO_TEXT, 256); if (!txt) { nla_nest_cancel(skb, nla); return err; @@ -187,6 +141,15 @@ static const unsigned int drbd_genl_cmd_flags[] = { [DRBD_ADM_DOWN] = DRBD_ADM_NEED_RESOURCE, }; +/* Detect attempts to change invariant attributes in a _change_ handler. */ +#define has_invariant(ntb, attr) \ +({ \ + bool __found = !!(ntb)[attr]; \ + if (__found) \ + pr_info("must not change invariant attr: %s\n", #attr); \ + __found; \ +}) + /* * At this point, we still rely on the global genl_lock(). * If we want to avoid that, and allow "genl_family.parallel_ops", we may need @@ -210,7 +173,7 @@ static int drbd_adm_prepare(struct drbd_config_context *adm_ctx, } adm_ctx->reply_dh = genlmsg_put_reply(adm_ctx->reply_skb, - info, &drbd_genl_family, 0, cmd); + info, &drbd_nl_family, 0, cmd); /* put of a few bytes into a fresh skb of >= 4k will always succeed. * but anyways */ if (!adm_ctx->reply_dh) { @@ -223,9 +186,11 @@ static int drbd_adm_prepare(struct drbd_config_context *adm_ctx, adm_ctx->volume = VOLUME_UNSPECIFIED; if (info->attrs[DRBD_NLA_CFG_CONTEXT]) { + struct nlattr **ntb; struct nlattr *nla; - /* parse and validate only */ - err = drbd_cfg_context_from_attrs(NULL, info); + + /* parse and validate, get nested attribute table */ + err = drbd_cfg_context_ntb_from_attrs(&ntb, info); if (err) goto fail; @@ -234,18 +199,21 @@ static int drbd_adm_prepare(struct drbd_config_context *adm_ctx, err = nla_put_nohdr(adm_ctx->reply_skb, info->attrs[DRBD_NLA_CFG_CONTEXT]->nla_len, info->attrs[DRBD_NLA_CFG_CONTEXT]); - if (err) + if (err) { + kfree(ntb); goto fail; + } /* and assign stuff to the adm_ctx */ - nla = nested_attr_tb[T_ctx_volume]; + nla = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_VOLUME]; if (nla) adm_ctx->volume = nla_get_u32(nla); - nla = nested_attr_tb[T_ctx_resource_name]; + nla = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME]; if (nla) adm_ctx->resource_name = nla_data(nla); - adm_ctx->my_addr = nested_attr_tb[T_ctx_my_addr]; - adm_ctx->peer_addr = nested_attr_tb[T_ctx_peer_addr]; + adm_ctx->my_addr = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_MY_ADDR]; + adm_ctx->peer_addr = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR]; + kfree(ntb); if ((adm_ctx->my_addr && nla_len(adm_ctx->my_addr) > sizeof(adm_ctx->connection->my_addr)) || (adm_ctx->peer_addr && @@ -259,7 +227,7 @@ static int drbd_adm_prepare(struct drbd_config_context *adm_ctx, adm_ctx->device = minor_to_device(d_in->minor); /* We are protected by the global genl_lock(). - * But we may explicitly drop it/retake it in drbd_adm_set_role(), + * But we may explicitly drop it/retake it in drbd_nl_set_role(), * so make sure this object stays around. */ if (adm_ctx->device) kref_get(&adm_ctx->device->kref); @@ -334,8 +302,8 @@ static int drbd_adm_prepare(struct drbd_config_context *adm_ctx, return err; } -static int drbd_pre_doit(const struct genl_split_ops *ops, - struct sk_buff *skb, struct genl_info *info) +int drbd_pre_doit(const struct genl_split_ops *ops, + struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx; u8 cmd = info->genlhdr->cmd; @@ -362,8 +330,8 @@ static int drbd_pre_doit(const struct genl_split_ops *ops, return 0; } -static void drbd_post_doit(const struct genl_split_ops *ops, - struct sk_buff *skb, struct genl_info *info) +void drbd_post_doit(const struct genl_split_ops *ops, + struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; @@ -828,7 +796,7 @@ static const char *from_attrs_err_to_txt(int err) "invalid attribute value"; } -int drbd_adm_set_role(struct sk_buff *skb, struct genl_info *info) +static int drbd_nl_set_role(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct set_role_parms parms; @@ -868,6 +836,16 @@ int drbd_adm_set_role(struct sk_buff *skb, struct genl_info *info) return 0; } +int drbd_nl_primary_doit(struct sk_buff *skb, struct genl_info *info) +{ + return drbd_nl_set_role(skb, info); +} + +int drbd_nl_secondary_doit(struct sk_buff *skb, struct genl_info *info) +{ + return drbd_nl_set_role(skb, info); +} + /* Initializes the md.*_offset members, so we are able to find * the on disk meta data. * @@ -962,7 +940,7 @@ char *ppsize(char *buf, unsigned long long size) * peer may not initiate a resize. */ /* Note these are not to be confused with - * drbd_adm_suspend_io/drbd_adm_resume_io, + * drbd_nl_suspend_io_doit/drbd_nl_resume_io_doit, * which are (sub) state changes triggered by admin (drbdsetup), * and can be long lived. * This changes an device->flag, is triggered by drbd internals, @@ -1574,13 +1552,14 @@ static int disk_opts_check_al_size(struct drbd_device *device, struct disk_conf return err; } -int drbd_adm_disk_opts(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_chg_disk_opts_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; struct drbd_device *device; struct disk_conf *new_disk_conf, *old_disk_conf; struct fifo_buffer *old_plan = NULL, *new_plan = NULL; + struct nlattr **ntb; int err; unsigned int fifo_size; @@ -1612,13 +1591,29 @@ int drbd_adm_disk_opts(struct sk_buff *skb, struct genl_info *info) if (should_set_defaults(info)) set_disk_conf_defaults(new_disk_conf); - err = disk_conf_from_attrs_for_change(new_disk_conf, info); + err = disk_conf_from_attrs(new_disk_conf, info); if (err && err != -ENOMSG) { retcode = ERR_MANDATORY_TAG; drbd_msg_put_info(adm_ctx->reply_skb, from_attrs_err_to_txt(err)); goto fail_unlock; } + err = disk_conf_ntb_from_attrs(&ntb, info); + if (!err) { + if (has_invariant(ntb, DRBD_A_DISK_CONF_BACKING_DEV) || + has_invariant(ntb, DRBD_A_DISK_CONF_META_DEV) || + has_invariant(ntb, DRBD_A_DISK_CONF_META_DEV_IDX) || + has_invariant(ntb, DRBD_A_DISK_CONF_DISK_SIZE) || + has_invariant(ntb, DRBD_A_DISK_CONF_MAX_BIO_BVECS)) { + retcode = ERR_MANDATORY_TAG; + drbd_msg_put_info(adm_ctx->reply_skb, + "cannot change invariant setting"); + kfree(ntb); + goto fail_unlock; + } + kfree(ntb); + } + if (!expect(device, new_disk_conf->resync_rate >= 1)) new_disk_conf->resync_rate = 1; @@ -1796,7 +1791,7 @@ void drbd_backing_dev_free(struct drbd_device *device, struct drbd_backing_dev * kfree(ldev); } -int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_attach_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_device *device; @@ -2236,7 +2231,7 @@ static int adm_detach(struct drbd_device *device, int force) * Then we transition to D_DISKLESS, and wait for put_ldev() to return all * internal references as well. * Only then we have finally detached. */ -int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_detach_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; @@ -2434,12 +2429,13 @@ static void free_crypto(struct crypto *crypto) crypto_free_shash(crypto->verify_tfm); } -int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_chg_net_opts_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; struct drbd_connection *connection; struct net_conf *old_net_conf, *new_net_conf = NULL; + struct nlattr **ntb; int err; int ovr; /* online verify running */ int rsr; /* re-sync running */ @@ -2476,13 +2472,26 @@ int drbd_adm_net_opts(struct sk_buff *skb, struct genl_info *info) if (should_set_defaults(info)) set_net_conf_defaults(new_net_conf); - err = net_conf_from_attrs_for_change(new_net_conf, info); + err = net_conf_from_attrs(new_net_conf, info); if (err && err != -ENOMSG) { retcode = ERR_MANDATORY_TAG; drbd_msg_put_info(adm_ctx->reply_skb, from_attrs_err_to_txt(err)); goto fail; } + err = net_conf_ntb_from_attrs(&ntb, info); + if (!err) { + if (has_invariant(ntb, DRBD_A_NET_CONF_DISCARD_MY_DATA) || + has_invariant(ntb, DRBD_A_NET_CONF_TENTATIVE)) { + retcode = ERR_MANDATORY_TAG; + drbd_msg_put_info(adm_ctx->reply_skb, + "cannot change invariant setting"); + kfree(ntb); + goto fail; + } + kfree(ntb); + } + retcode = check_net_options(connection, new_net_conf); if (retcode != NO_ERROR) goto fail; @@ -2575,7 +2584,7 @@ static void peer_device_to_info(struct peer_device_info *info, info->peer_resync_susp_dependency = device->state.aftr_isp; } -int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_connect_doit(struct sk_buff *skb, struct genl_info *info) { struct connection_info connection_info; enum drbd_notification_type flags; @@ -2790,7 +2799,7 @@ static enum drbd_state_rv conn_try_disconnect(struct drbd_connection *connection return rv; } -int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_disconnect_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct disconnect_parms parms; @@ -2845,7 +2854,7 @@ void resync_after_online_grow(struct drbd_device *device) _drbd_request_state(device, NS(conn, C_WF_SYNC_UUID), CS_VERBOSE + CS_SERIALIZE); } -int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_resize_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct disk_conf *old_disk_conf, *new_disk_conf = NULL; @@ -2981,7 +2990,7 @@ int drbd_adm_resize(struct sk_buff *skb, struct genl_info *info) goto fail; } -int drbd_adm_resource_opts(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_resource_opts_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; @@ -3019,7 +3028,7 @@ int drbd_adm_resource_opts(struct sk_buff *skb, struct genl_info *info) return 0; } -int drbd_adm_invalidate(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_invalidate_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_device *device; @@ -3097,7 +3106,7 @@ static int drbd_bmio_set_susp_al(struct drbd_device *device, return rv; } -int drbd_adm_invalidate_peer(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_inval_peer_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; int retcode; /* drbd_ret_code, drbd_state_rv */ @@ -3148,7 +3157,7 @@ int drbd_adm_invalidate_peer(struct sk_buff *skb, struct genl_info *info) return 0; } -int drbd_adm_pause_sync(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_pause_sync_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; @@ -3168,7 +3177,7 @@ int drbd_adm_pause_sync(struct sk_buff *skb, struct genl_info *info) return 0; } -int drbd_adm_resume_sync(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_resume_sync_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; union drbd_dev_state s; @@ -3196,12 +3205,12 @@ int drbd_adm_resume_sync(struct sk_buff *skb, struct genl_info *info) return 0; } -int drbd_adm_suspend_io(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_suspend_io_doit(struct sk_buff *skb, struct genl_info *info) { return drbd_adm_simple_request_state(skb, info, NS(susp, 1)); } -int drbd_adm_resume_io(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_resume_io_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_device *device; @@ -3257,7 +3266,7 @@ int drbd_adm_resume_io(struct sk_buff *skb, struct genl_info *info) return 0; } -int drbd_adm_outdate(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_outdate_doit(struct sk_buff *skb, struct genl_info *info) { return drbd_adm_simple_request_state(skb, info, NS(disk, D_OUTDATED)); } @@ -3272,16 +3281,20 @@ static int nla_put_drbd_cfg_context(struct sk_buff *skb, if (!nla) goto nla_put_failure; if (device && - nla_put_u32(skb, T_ctx_volume, device->vnr)) + nla_put_u32(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_VOLUME, device->vnr)) goto nla_put_failure; - if (nla_put_string(skb, T_ctx_resource_name, resource->name)) + if (nla_put_string(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME, resource->name)) goto nla_put_failure; if (connection) { if (connection->my_addr_len && - nla_put(skb, T_ctx_my_addr, connection->my_addr_len, &connection->my_addr)) + nla_put(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_MY_ADDR, + connection->my_addr_len, + &connection->my_addr)) goto nla_put_failure; if (connection->peer_addr_len && - nla_put(skb, T_ctx_peer_addr, connection->peer_addr_len, &connection->peer_addr)) + nla_put(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR, + connection->peer_addr_len, + &connection->peer_addr)) goto nla_put_failure; } nla_nest_end(skb, nla); @@ -3300,7 +3313,7 @@ static int nla_put_drbd_cfg_context(struct sk_buff *skb, */ static struct nlattr *find_cfg_context_attr(const struct nlmsghdr *nlh, int attr) { - const unsigned hdrlen = GENL_HDRLEN + GENL_MAGIC_FAMILY_HDRSZ; + const unsigned int hdrlen = GENL_HDRLEN + sizeof(struct drbd_genlmsghdr); struct nlattr *nla; nla = nla_find(nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), @@ -3312,7 +3325,7 @@ static struct nlattr *find_cfg_context_attr(const struct nlmsghdr *nlh, int attr static void resource_to_info(struct resource_info *, struct drbd_resource *); -int drbd_adm_dump_resources(struct sk_buff *skb, struct netlink_callback *cb) +int drbd_nl_get_resources_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct drbd_genlmsghdr *dh; struct drbd_resource *resource; @@ -3340,7 +3353,7 @@ int drbd_adm_dump_resources(struct sk_buff *skb, struct netlink_callback *cb) put_result: dh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, &drbd_genl_family, + cb->nlh->nlmsg_seq, &drbd_nl_family, NLM_F_MULTI, DRBD_ADM_GET_RESOURCES); err = -ENOMEM; if (!dh) @@ -3350,15 +3363,15 @@ int drbd_adm_dump_resources(struct sk_buff *skb, struct netlink_callback *cb) err = nla_put_drbd_cfg_context(skb, resource, NULL, NULL); if (err) goto out; - err = res_opts_to_skb(skb, &resource->res_opts, !capable(CAP_SYS_ADMIN)); + err = res_opts_to_skb(skb, &resource->res_opts); if (err) goto out; resource_to_info(&resource_info, resource); - err = resource_info_to_skb(skb, &resource_info, !capable(CAP_SYS_ADMIN)); + err = resource_info_to_skb(skb, &resource_info); if (err) goto out; resource_statistics.res_stat_write_ordering = resource->write_ordering; - err = resource_statistics_to_skb(skb, &resource_statistics, !capable(CAP_SYS_ADMIN)); + err = resource_statistics_to_skb(skb, &resource_statistics); if (err) goto out; cb->args[0] = (long)resource; @@ -3423,7 +3436,7 @@ int drbd_adm_dump_devices_done(struct netlink_callback *cb) { static void device_to_info(struct device_info *, struct drbd_device *); -int drbd_adm_dump_devices(struct sk_buff *skb, struct netlink_callback *cb) +int drbd_nl_get_devices_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct nlattr *resource_filter; struct drbd_resource *resource; @@ -3436,7 +3449,8 @@ int drbd_adm_dump_devices(struct sk_buff *skb, struct netlink_callback *cb) resource = (struct drbd_resource *)cb->args[0]; if (!cb->args[0] && !cb->args[1]) { - resource_filter = find_cfg_context_attr(cb->nlh, T_ctx_resource_name); + resource_filter = find_cfg_context_attr(cb->nlh, + DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME); if (resource_filter) { retcode = ERR_RES_NOT_KNOWN; resource = drbd_find_resource(nla_data(resource_filter)); @@ -3465,7 +3479,7 @@ int drbd_adm_dump_devices(struct sk_buff *skb, struct netlink_callback *cb) put_result: dh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, &drbd_genl_family, + cb->nlh->nlmsg_seq, &drbd_nl_family, NLM_F_MULTI, DRBD_ADM_GET_DEVICES); err = -ENOMEM; if (!dh) @@ -3481,18 +3495,18 @@ int drbd_adm_dump_devices(struct sk_buff *skb, struct netlink_callback *cb) struct disk_conf *disk_conf = rcu_dereference(device->ldev->disk_conf); - err = disk_conf_to_skb(skb, disk_conf, !capable(CAP_SYS_ADMIN)); + err = disk_conf_to_skb(skb, disk_conf); put_ldev(device); if (err) goto out; } device_to_info(&device_info, device); - err = device_info_to_skb(skb, &device_info, !capable(CAP_SYS_ADMIN)); + err = device_info_to_skb(skb, &device_info); if (err) goto out; device_to_statistics(&device_statistics, device); - err = device_statistics_to_skb(skb, &device_statistics, !capable(CAP_SYS_ADMIN)); + err = device_statistics_to_skb(skb, &device_statistics); if (err) goto out; cb->args[1] = minor + 1; @@ -3514,7 +3528,7 @@ int drbd_adm_dump_connections_done(struct netlink_callback *cb) enum { SINGLE_RESOURCE, ITERATE_RESOURCES }; -int drbd_adm_dump_connections(struct sk_buff *skb, struct netlink_callback *cb) +int drbd_nl_get_connections_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct nlattr *resource_filter; struct drbd_resource *resource = NULL, *next_resource; @@ -3527,7 +3541,8 @@ int drbd_adm_dump_connections(struct sk_buff *skb, struct netlink_callback *cb) rcu_read_lock(); resource = (struct drbd_resource *)cb->args[0]; if (!cb->args[0]) { - resource_filter = find_cfg_context_attr(cb->nlh, T_ctx_resource_name); + resource_filter = find_cfg_context_attr(cb->nlh, + DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME); if (resource_filter) { retcode = ERR_RES_NOT_KNOWN; resource = drbd_find_resource(nla_data(resource_filter)); @@ -3591,7 +3606,7 @@ int drbd_adm_dump_connections(struct sk_buff *skb, struct netlink_callback *cb) put_result: dh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, &drbd_genl_family, + cb->nlh->nlmsg_seq, &drbd_nl_family, NLM_F_MULTI, DRBD_ADM_GET_CONNECTIONS); err = -ENOMEM; if (!dh) @@ -3606,16 +3621,16 @@ int drbd_adm_dump_connections(struct sk_buff *skb, struct netlink_callback *cb) goto out; net_conf = rcu_dereference(connection->net_conf); if (net_conf) { - err = net_conf_to_skb(skb, net_conf, !capable(CAP_SYS_ADMIN)); + err = net_conf_to_skb(skb, net_conf); if (err) goto out; } connection_to_info(&connection_info, connection); - err = connection_info_to_skb(skb, &connection_info, !capable(CAP_SYS_ADMIN)); + err = connection_info_to_skb(skb, &connection_info); if (err) goto out; connection_statistics.conn_congested = test_bit(NET_CONGESTED, &connection->flags); - err = connection_statistics_to_skb(skb, &connection_statistics, !capable(CAP_SYS_ADMIN)); + err = connection_statistics_to_skb(skb, &connection_statistics); if (err) goto out; cb->args[2] = (long)connection; @@ -3676,7 +3691,7 @@ int drbd_adm_dump_peer_devices_done(struct netlink_callback *cb) return put_resource_in_arg0(cb, 9); } -int drbd_adm_dump_peer_devices(struct sk_buff *skb, struct netlink_callback *cb) +int drbd_nl_get_peer_devices_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct nlattr *resource_filter; struct drbd_resource *resource; @@ -3688,7 +3703,8 @@ int drbd_adm_dump_peer_devices(struct sk_buff *skb, struct netlink_callback *cb) resource = (struct drbd_resource *)cb->args[0]; if (!cb->args[0] && !cb->args[1]) { - resource_filter = find_cfg_context_attr(cb->nlh, T_ctx_resource_name); + resource_filter = find_cfg_context_attr(cb->nlh, + DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME); if (resource_filter) { retcode = ERR_RES_NOT_KNOWN; resource = drbd_find_resource(nla_data(resource_filter)); @@ -3735,7 +3751,7 @@ int drbd_adm_dump_peer_devices(struct sk_buff *skb, struct netlink_callback *cb) put_result: dh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, &drbd_genl_family, + cb->nlh->nlmsg_seq, &drbd_nl_family, NLM_F_MULTI, DRBD_ADM_GET_PEER_DEVICES); err = -ENOMEM; if (!dh) @@ -3751,11 +3767,11 @@ int drbd_adm_dump_peer_devices(struct sk_buff *skb, struct netlink_callback *cb) if (err) goto out; peer_device_to_info(&peer_device_info, peer_device); - err = peer_device_info_to_skb(skb, &peer_device_info, !capable(CAP_SYS_ADMIN)); + err = peer_device_info_to_skb(skb, &peer_device_info); if (err) goto out; peer_device_to_statistics(&peer_device_statistics, peer_device); - err = peer_device_statistics_to_skb(skb, &peer_device_statistics, !capable(CAP_SYS_ADMIN)); + err = peer_device_statistics_to_skb(skb, &peer_device_statistics); if (err) goto out; cb->args[1] = minor; @@ -3795,11 +3811,11 @@ static int nla_put_status_info(struct sk_buff *skb, struct drbd_device *device, /* If sib != NULL, this is drbd_bcast_event, which anyone can listen * to. So we better exclude_sensitive information. * - * If sib == NULL, this is drbd_adm_get_status, executed synchronously + * If sib == NULL, this is drbd_nl_get_status_doit, executed synchronously * in the context of the requesting user process. Exclude sensitive * information, unless current has superuser. * - * NOTE: for drbd_adm_get_status_all(), this is a netlink dump, and + * NOTE: for drbd_nl_get_status_dumpit(), this is a netlink dump, and * relies on the current implementation of netlink_dump(), which * executes the dump callback successively from netlink_recvmsg(), * always in the context of the receiving process */ @@ -3812,7 +3828,7 @@ static int nla_put_status_info(struct sk_buff *skb, struct drbd_device *device, if (nla_put_drbd_cfg_context(skb, resource, the_only_connection(resource), device)) goto nla_put_failure; - if (res_opts_to_skb(skb, &device->resource->res_opts, exclude_sensitive)) + if (res_opts_to_skb(skb, &device->resource->res_opts)) goto nla_put_failure; rcu_read_lock(); @@ -3820,14 +3836,24 @@ static int nla_put_status_info(struct sk_buff *skb, struct drbd_device *device, struct disk_conf *disk_conf; disk_conf = rcu_dereference(device->ldev->disk_conf); - err = disk_conf_to_skb(skb, disk_conf, exclude_sensitive); + err = disk_conf_to_skb(skb, disk_conf); } if (!err) { struct net_conf *nc; nc = rcu_dereference(first_peer_device(device)->connection->net_conf); - if (nc) - err = net_conf_to_skb(skb, nc, exclude_sensitive); + if (nc) { + if (exclude_sensitive) { + struct net_conf nc_clean = *nc; + + memset(nc_clean.shared_secret, 0, + sizeof(nc_clean.shared_secret)); + nc_clean.shared_secret_len = 0; + err = net_conf_to_skb(skb, &nc_clean); + } else { + err = net_conf_to_skb(skb, nc); + } + } } rcu_read_unlock(); if (err) @@ -3836,42 +3862,57 @@ static int nla_put_status_info(struct sk_buff *skb, struct drbd_device *device, nla = nla_nest_start_noflag(skb, DRBD_NLA_STATE_INFO); if (!nla) goto nla_put_failure; - if (nla_put_u32(skb, T_sib_reason, sib ? sib->sib_reason : SIB_GET_STATUS_REPLY) || - nla_put_u32(skb, T_current_state, device->state.i) || - nla_put_u64_0pad(skb, T_ed_uuid, device->ed_uuid) || - nla_put_u64_0pad(skb, T_capacity, get_capacity(device->vdisk)) || - nla_put_u64_0pad(skb, T_send_cnt, device->send_cnt) || - nla_put_u64_0pad(skb, T_recv_cnt, device->recv_cnt) || - nla_put_u64_0pad(skb, T_read_cnt, device->read_cnt) || - nla_put_u64_0pad(skb, T_writ_cnt, device->writ_cnt) || - nla_put_u64_0pad(skb, T_al_writ_cnt, device->al_writ_cnt) || - nla_put_u64_0pad(skb, T_bm_writ_cnt, device->bm_writ_cnt) || - nla_put_u32(skb, T_ap_bio_cnt, atomic_read(&device->ap_bio_cnt)) || - nla_put_u32(skb, T_ap_pending_cnt, atomic_read(&device->ap_pending_cnt)) || - nla_put_u32(skb, T_rs_pending_cnt, atomic_read(&device->rs_pending_cnt))) + if (nla_put_u32(skb, DRBD_A_STATE_INFO_SIB_REASON, + sib ? sib->sib_reason : SIB_GET_STATUS_REPLY) || + nla_put_u32(skb, DRBD_A_STATE_INFO_CURRENT_STATE, + device->state.i) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_ED_UUID, + device->ed_uuid, 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_CAPACITY, + get_capacity(device->vdisk), 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_SEND_CNT, + device->send_cnt, 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_RECV_CNT, + device->recv_cnt, 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_READ_CNT, + device->read_cnt, 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_WRIT_CNT, + device->writ_cnt, 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_AL_WRIT_CNT, + device->al_writ_cnt, 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BM_WRIT_CNT, + device->bm_writ_cnt, 0) || + nla_put_u32(skb, DRBD_A_STATE_INFO_AP_BIO_CNT, + atomic_read(&device->ap_bio_cnt)) || + nla_put_u32(skb, DRBD_A_STATE_INFO_AP_PENDING_CNT, + atomic_read(&device->ap_pending_cnt)) || + nla_put_u32(skb, DRBD_A_STATE_INFO_RS_PENDING_CNT, + atomic_read(&device->rs_pending_cnt))) goto nla_put_failure; if (got_ldev) { int err; spin_lock_irq(&device->ldev->md.uuid_lock); - err = nla_put(skb, T_uuids, sizeof(si->uuids), device->ldev->md.uuid); + err = nla_put(skb, DRBD_A_STATE_INFO_UUIDS, + sizeof(si->uuids), + device->ldev->md.uuid); spin_unlock_irq(&device->ldev->md.uuid_lock); if (err) goto nla_put_failure; - if (nla_put_u32(skb, T_disk_flags, device->ldev->md.flags) || - nla_put_u64_0pad(skb, T_bits_total, drbd_bm_bits(device)) || - nla_put_u64_0pad(skb, T_bits_oos, - drbd_bm_total_weight(device))) + if (nla_put_u32(skb, DRBD_A_STATE_INFO_DISK_FLAGS, device->ldev->md.flags) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_TOTAL, drbd_bm_bits(device), 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_OOS, + drbd_bm_total_weight(device), 0)) goto nla_put_failure; if (C_SYNC_SOURCE <= device->state.conn && C_PAUSED_SYNC_T >= device->state.conn) { - if (nla_put_u64_0pad(skb, T_bits_rs_total, - device->rs_total) || - nla_put_u64_0pad(skb, T_bits_rs_failed, - device->rs_failed)) + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_TOTAL, + device->rs_total, 0) || + nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_FAILED, + device->rs_failed, 0)) goto nla_put_failure; } } @@ -3882,17 +3923,17 @@ static int nla_put_status_info(struct sk_buff *skb, struct drbd_device *device, case SIB_GET_STATUS_REPLY: break; case SIB_STATE_CHANGE: - if (nla_put_u32(skb, T_prev_state, sib->os.i) || - nla_put_u32(skb, T_new_state, sib->ns.i)) + if (nla_put_u32(skb, DRBD_A_STATE_INFO_PREV_STATE, sib->os.i) || + nla_put_u32(skb, DRBD_A_STATE_INFO_NEW_STATE, sib->ns.i)) goto nla_put_failure; break; case SIB_HELPER_POST: - if (nla_put_u32(skb, T_helper_exit_code, + if (nla_put_u32(skb, DRBD_A_STATE_INFO_HELPER_EXIT_CODE, sib->helper_exit_code)) goto nla_put_failure; fallthrough; case SIB_HELPER_PRE: - if (nla_put_string(skb, T_helper, sib->helper_name)) + if (nla_put_string(skb, DRBD_A_STATE_INFO_HELPER, sib->helper_name)) goto nla_put_failure; break; } @@ -3907,7 +3948,7 @@ static int nla_put_status_info(struct sk_buff *skb, struct drbd_device *device, return err; } -int drbd_adm_get_status(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_get_status_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; @@ -3997,7 +4038,7 @@ static int get_one_status(struct sk_buff *skb, struct netlink_callback *cb) } dh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, &drbd_genl_family, + cb->nlh->nlmsg_seq, &drbd_nl_family, NLM_F_MULTI, DRBD_ADM_GET_STATUS); if (!dh) goto out; @@ -4017,7 +4058,7 @@ static int get_one_status(struct sk_buff *skb, struct netlink_callback *cb) struct net_conf *nc; nc = rcu_dereference(connection->net_conf); - if (nc && net_conf_to_skb(skb, nc, 1) != 0) + if (nc && net_conf_to_skb(skb, nc) != 0) goto cancel; } goto done; @@ -4059,9 +4100,9 @@ static int get_one_status(struct sk_buff *skb, struct netlink_callback *cb) * * Once things are setup properly, we call into get_one_status(). */ -int drbd_adm_get_status_all(struct sk_buff *skb, struct netlink_callback *cb) +int drbd_nl_get_status_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { - const unsigned hdrlen = GENL_HDRLEN + GENL_MAGIC_FAMILY_HDRSZ; + const unsigned int hdrlen = GENL_HDRLEN + sizeof(struct drbd_genlmsghdr); struct nlattr *nla; const char *resource_name; struct drbd_resource *resource; @@ -4084,7 +4125,7 @@ int drbd_adm_get_status_all(struct sk_buff *skb, struct netlink_callback *cb) /* No explicit context given. Dump all. */ if (!nla) goto dump; - nla = nla_find_nested(nla, T_ctx_resource_name); + nla = nla_find_nested(nla, DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME); /* context given, but no name present? */ if (!nla) return -EINVAL; @@ -4107,7 +4148,7 @@ int drbd_adm_get_status_all(struct sk_buff *skb, struct netlink_callback *cb) return get_one_status(skb, cb); } -int drbd_adm_get_timeout_type(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_get_timeout_type_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; @@ -4125,7 +4166,7 @@ int drbd_adm_get_timeout_type(struct sk_buff *skb, struct genl_info *info) test_bit(USE_DEGR_WFC_T, &adm_ctx->device->flags) ? UT_DEGRADED : UT_DEFAULT; - err = timeout_parms_to_priv_skb(adm_ctx->reply_skb, &tp); + err = timeout_parms_to_skb(adm_ctx->reply_skb, &tp); if (err) { nlmsg_free(adm_ctx->reply_skb); adm_ctx->reply_skb = NULL; @@ -4136,7 +4177,7 @@ int drbd_adm_get_timeout_type(struct sk_buff *skb, struct genl_info *info) return 0; } -int drbd_adm_start_ov(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_start_ov_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_device *device; @@ -4182,7 +4223,7 @@ int drbd_adm_start_ov(struct sk_buff *skb, struct genl_info *info) } -int drbd_adm_new_c_uuid(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_new_c_uuid_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_device *device; @@ -4285,7 +4326,7 @@ static void resource_to_info(struct resource_info *info, info->res_susp_fen = resource->susp_fen; } -int drbd_adm_new_resource(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_new_resource_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_connection *connection; struct drbd_config_context *adm_ctx = info->user_ptr[0]; @@ -4348,7 +4389,7 @@ static void device_to_info(struct device_info *info, } -int drbd_adm_new_minor(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_new_minor_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_genlmsghdr *dh = genl_info_userhdr(info); @@ -4455,7 +4496,7 @@ static enum drbd_ret_code adm_del_minor(struct drbd_device *device) return ERR_MINOR_CONFIGURED; } -int drbd_adm_del_minor(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_del_minor_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; enum drbd_ret_code retcode; @@ -4504,7 +4545,7 @@ static int adm_del_resource(struct drbd_resource *resource) return NO_ERROR; } -int drbd_adm_down(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_down_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_resource *resource; @@ -4567,7 +4608,7 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info) return 0; } -int drbd_adm_del_resource(struct sk_buff *skb, struct genl_info *info) +int drbd_nl_del_resource_doit(struct sk_buff *skb, struct genl_info *info) { struct drbd_config_context *adm_ctx = info->user_ptr[0]; struct drbd_resource *resource; @@ -4601,7 +4642,7 @@ void drbd_bcast_event(struct drbd_device *device, const struct sib_info *sib) goto failed; err = -EMSGSIZE; - d_out = genlmsg_put(msg, 0, seq, &drbd_genl_family, 0, DRBD_EVENT); + d_out = genlmsg_put(msg, 0, seq, &drbd_nl_family, 0, DRBD_ADM_EVENT); if (!d_out) /* cannot happen, but anyways. */ goto nla_put_failure; d_out->minor = device_to_minor(device); @@ -4632,7 +4673,7 @@ static int nla_put_notification_header(struct sk_buff *msg, .nh_type = type, }; - return drbd_notification_header_to_skb(msg, &nh, true); + return drbd_notification_header_to_skb(msg, &nh); } int notify_resource_state(struct sk_buff *skb, @@ -4656,7 +4697,7 @@ int notify_resource_state(struct sk_buff *skb, } err = -EMSGSIZE; - dh = genlmsg_put(skb, 0, seq, &drbd_genl_family, 0, DRBD_RESOURCE_STATE); + dh = genlmsg_put(skb, 0, seq, &drbd_nl_family, 0, DRBD_ADM_RESOURCE_STATE); if (!dh) goto nla_put_failure; dh->minor = -1U; @@ -4664,10 +4705,10 @@ int notify_resource_state(struct sk_buff *skb, if (nla_put_drbd_cfg_context(skb, resource, NULL, NULL) || nla_put_notification_header(skb, type) || ((type & ~NOTIFY_FLAGS) != NOTIFY_DESTROY && - resource_info_to_skb(skb, resource_info, true))) + resource_info_to_skb(skb, resource_info))) goto nla_put_failure; resource_statistics.res_stat_write_ordering = resource->write_ordering; - err = resource_statistics_to_skb(skb, &resource_statistics, !capable(CAP_SYS_ADMIN)); + err = resource_statistics_to_skb(skb, &resource_statistics); if (err) goto nla_put_failure; genlmsg_end(skb, dh); @@ -4708,7 +4749,7 @@ int notify_device_state(struct sk_buff *skb, } err = -EMSGSIZE; - dh = genlmsg_put(skb, 0, seq, &drbd_genl_family, 0, DRBD_DEVICE_STATE); + dh = genlmsg_put(skb, 0, seq, &drbd_nl_family, 0, DRBD_ADM_DEVICE_STATE); if (!dh) goto nla_put_failure; dh->minor = device->minor; @@ -4716,10 +4757,10 @@ int notify_device_state(struct sk_buff *skb, if (nla_put_drbd_cfg_context(skb, device->resource, NULL, device) || nla_put_notification_header(skb, type) || ((type & ~NOTIFY_FLAGS) != NOTIFY_DESTROY && - device_info_to_skb(skb, device_info, true))) + device_info_to_skb(skb, device_info))) goto nla_put_failure; device_to_statistics(&device_statistics, device); - device_statistics_to_skb(skb, &device_statistics, !capable(CAP_SYS_ADMIN)); + device_statistics_to_skb(skb, &device_statistics); genlmsg_end(skb, dh); if (multicast) { err = drbd_genl_multicast_events(skb, GFP_NOWAIT); @@ -4758,7 +4799,7 @@ int notify_connection_state(struct sk_buff *skb, } err = -EMSGSIZE; - dh = genlmsg_put(skb, 0, seq, &drbd_genl_family, 0, DRBD_CONNECTION_STATE); + dh = genlmsg_put(skb, 0, seq, &drbd_nl_family, 0, DRBD_ADM_CONNECTION_STATE); if (!dh) goto nla_put_failure; dh->minor = -1U; @@ -4766,10 +4807,10 @@ int notify_connection_state(struct sk_buff *skb, if (nla_put_drbd_cfg_context(skb, connection->resource, connection, NULL) || nla_put_notification_header(skb, type) || ((type & ~NOTIFY_FLAGS) != NOTIFY_DESTROY && - connection_info_to_skb(skb, connection_info, true))) + connection_info_to_skb(skb, connection_info))) goto nla_put_failure; connection_statistics.conn_congested = test_bit(NET_CONGESTED, &connection->flags); - connection_statistics_to_skb(skb, &connection_statistics, !capable(CAP_SYS_ADMIN)); + connection_statistics_to_skb(skb, &connection_statistics); genlmsg_end(skb, dh); if (multicast) { err = drbd_genl_multicast_events(skb, GFP_NOWAIT); @@ -4809,7 +4850,7 @@ int notify_peer_device_state(struct sk_buff *skb, } err = -EMSGSIZE; - dh = genlmsg_put(skb, 0, seq, &drbd_genl_family, 0, DRBD_PEER_DEVICE_STATE); + dh = genlmsg_put(skb, 0, seq, &drbd_nl_family, 0, DRBD_ADM_PEER_DEVICE_STATE); if (!dh) goto nla_put_failure; dh->minor = -1U; @@ -4817,10 +4858,10 @@ int notify_peer_device_state(struct sk_buff *skb, if (nla_put_drbd_cfg_context(skb, resource, peer_device->connection, peer_device->device) || nla_put_notification_header(skb, type) || ((type & ~NOTIFY_FLAGS) != NOTIFY_DESTROY && - peer_device_info_to_skb(skb, peer_device_info, true))) + peer_device_info_to_skb(skb, peer_device_info))) goto nla_put_failure; peer_device_to_statistics(&peer_device_statistics, peer_device); - peer_device_statistics_to_skb(skb, &peer_device_statistics, !capable(CAP_SYS_ADMIN)); + peer_device_statistics_to_skb(skb, &peer_device_statistics); genlmsg_end(skb, dh); if (multicast) { err = drbd_genl_multicast_events(skb, GFP_NOWAIT); @@ -4859,7 +4900,7 @@ void notify_helper(enum drbd_notification_type type, goto fail; err = -EMSGSIZE; - dh = genlmsg_put(skb, 0, seq, &drbd_genl_family, 0, DRBD_HELPER); + dh = genlmsg_put(skb, 0, seq, &drbd_nl_family, 0, DRBD_ADM_HELPER); if (!dh) goto fail; dh->minor = device ? device->minor : -1; @@ -4867,7 +4908,7 @@ void notify_helper(enum drbd_notification_type type, mutex_lock(¬ification_mutex); if (nla_put_drbd_cfg_context(skb, resource, connection, device) || nla_put_notification_header(skb, type) || - drbd_helper_info_to_skb(skb, &helper_info, true)) + drbd_helper_info_to_skb(skb, &helper_info)) goto unlock_fail; genlmsg_end(skb, dh); err = drbd_genl_multicast_events(skb, GFP_NOWAIT); @@ -4892,7 +4933,7 @@ static int notify_initial_state_done(struct sk_buff *skb, unsigned int seq) int err; err = -EMSGSIZE; - dh = genlmsg_put(skb, 0, seq, &drbd_genl_family, 0, DRBD_INITIAL_STATE_DONE); + dh = genlmsg_put(skb, 0, seq, &drbd_nl_family, 0, DRBD_ADM_INITIAL_STATE_DONE); if (!dh) goto nla_put_failure; dh->minor = -1U; @@ -4987,7 +5028,7 @@ static int get_initial_state(struct sk_buff *skb, struct netlink_callback *cb) return skb->len; } -int drbd_adm_get_initial_state(struct sk_buff *skb, struct netlink_callback *cb) +int drbd_nl_get_initial_state_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct drbd_resource *resource; LIST_HEAD(head); @@ -5035,3 +5076,20 @@ int drbd_adm_get_initial_state(struct sk_buff *skb, struct netlink_callback *cb) cb->args[2] = cb->nlh->nlmsg_seq; return get_initial_state(skb, cb); } + +static const struct genl_multicast_group drbd_nl_mcgrps[] = { + [DRBD_NLGRP_EVENTS] = { .name = "events", }, +}; + +struct genl_family drbd_nl_family __ro_after_init = { + .name = "drbd", + .version = DRBD_FAMILY_VERSION, + .hdrsize = NLA_ALIGN(sizeof(struct drbd_genlmsghdr)), + .split_ops = drbd_nl_ops, + .n_split_ops = ARRAY_SIZE(drbd_nl_ops), + .mcgrps = drbd_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(drbd_nl_mcgrps), + .resv_start_op = 42, + .module = THIS_MODULE, + .netnsok = true, +}; diff --git a/drivers/block/drbd/drbd_nl_gen.c b/drivers/block/drbd/drbd_nl_gen.c new file mode 100644 index 000000000000..fb44b948cec8 --- /dev/null +++ b/drivers/block/drbd/drbd_nl_gen.c @@ -0,0 +1,2606 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) + +#include +#include + +#include +#include +#include "drbd_nl_gen.h" + +#include +#include +#include + +/* Common nested types */ +const struct nla_policy drbd_connection_info_nl_policy[DRBD_A_CONNECTION_INFO_CONN_ROLE + 1] = { + [DRBD_A_CONNECTION_INFO_CONN_CONNECTION_STATE] = { .type = NLA_U32, }, + [DRBD_A_CONNECTION_INFO_CONN_ROLE] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_connection_statistics_nl_policy[DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED + 1] = { + [DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED] = { .type = NLA_U8, }, +}; + +const struct nla_policy drbd_detach_parms_nl_policy[DRBD_A_DETACH_PARMS_FORCE_DETACH + 1] = { + [DRBD_A_DETACH_PARMS_FORCE_DETACH] = { .type = NLA_U8, }, +}; + +const struct nla_policy drbd_device_info_nl_policy[DRBD_A_DEVICE_INFO_DEV_DISK_STATE + 1] = { + [DRBD_A_DEVICE_INFO_DEV_DISK_STATE] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_device_statistics_nl_policy[DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS + 1] = { + [DRBD_A_DEVICE_STATISTICS_DEV_SIZE] = { .type = NLA_U64, }, + [DRBD_A_DEVICE_STATISTICS_DEV_READ] = { .type = NLA_U64, }, + [DRBD_A_DEVICE_STATISTICS_DEV_WRITE] = { .type = NLA_U64, }, + [DRBD_A_DEVICE_STATISTICS_DEV_AL_WRITES] = { .type = NLA_U64, }, + [DRBD_A_DEVICE_STATISTICS_DEV_BM_WRITES] = { .type = NLA_U64, }, + [DRBD_A_DEVICE_STATISTICS_DEV_UPPER_PENDING] = { .type = NLA_U32, }, + [DRBD_A_DEVICE_STATISTICS_DEV_LOWER_PENDING] = { .type = NLA_U32, }, + [DRBD_A_DEVICE_STATISTICS_DEV_UPPER_BLOCKED] = { .type = NLA_U8, }, + [DRBD_A_DEVICE_STATISTICS_DEV_LOWER_BLOCKED] = { .type = NLA_U8, }, + [DRBD_A_DEVICE_STATISTICS_DEV_AL_SUSPENDED] = { .type = NLA_U8, }, + [DRBD_A_DEVICE_STATISTICS_DEV_EXPOSED_DATA_UUID] = { .type = NLA_U64, }, + [DRBD_A_DEVICE_STATISTICS_DEV_CURRENT_UUID] = { .type = NLA_U64, }, + [DRBD_A_DEVICE_STATISTICS_DEV_DISK_FLAGS] = { .type = NLA_U32, }, + [DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS] = NLA_POLICY_MAX_LEN(DRBD_NL_HISTORY_UUIDS_SIZE), +}; + +const struct nla_policy drbd_disconnect_parms_nl_policy[DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT + 1] = { + [DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT] = { .type = NLA_U8, }, +}; + +const struct nla_policy drbd_disk_conf_nl_policy[DRBD_A_DISK_CONF_DISABLE_WRITE_SAME + 1] = { + [DRBD_A_DISK_CONF_BACKING_DEV] = { .type = NLA_NUL_STRING, .len = 128, }, + [DRBD_A_DISK_CONF_META_DEV] = { .type = NLA_NUL_STRING, .len = 128, }, + [DRBD_A_DISK_CONF_META_DEV_IDX] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_DISK_SIZE] = { .type = NLA_U64, }, + [DRBD_A_DISK_CONF_MAX_BIO_BVECS] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_ON_IO_ERROR] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_FENCING] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_RESYNC_RATE] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_RESYNC_AFTER] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_AL_EXTENTS] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_C_PLAN_AHEAD] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_C_DELAY_TARGET] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_C_FILL_TARGET] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_C_MAX_RATE] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_C_MIN_RATE] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_DISK_BARRIER] = { .type = NLA_U8, }, + [DRBD_A_DISK_CONF_DISK_FLUSHES] = { .type = NLA_U8, }, + [DRBD_A_DISK_CONF_DISK_DRAIN] = { .type = NLA_U8, }, + [DRBD_A_DISK_CONF_MD_FLUSHES] = { .type = NLA_U8, }, + [DRBD_A_DISK_CONF_DISK_TIMEOUT] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_READ_BALANCING] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_AL_UPDATES] = { .type = NLA_U8, }, + [DRBD_A_DISK_CONF_DISCARD_ZEROES_IF_ALIGNED] = { .type = NLA_U8, }, + [DRBD_A_DISK_CONF_RS_DISCARD_GRANULARITY] = { .type = NLA_U32, }, + [DRBD_A_DISK_CONF_DISABLE_WRITE_SAME] = { .type = NLA_U8, }, +}; + +const struct nla_policy drbd_drbd_cfg_context_nl_policy[DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR + 1] = { + [DRBD_A_DRBD_CFG_CONTEXT_CTX_VOLUME] = { .type = NLA_U32, }, + [DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME] = { .type = NLA_NUL_STRING, .len = 128, }, + [DRBD_A_DRBD_CFG_CONTEXT_CTX_MY_ADDR] = NLA_POLICY_MAX_LEN(128), + [DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR] = NLA_POLICY_MAX_LEN(128), +}; + +const struct nla_policy drbd_net_conf_nl_policy[DRBD_A_NET_CONF_SOCK_CHECK_TIMEO + 1] = { + [DRBD_A_NET_CONF_SHARED_SECRET] = { .type = NLA_NUL_STRING, .len = SHARED_SECRET_MAX, }, + [DRBD_A_NET_CONF_CRAM_HMAC_ALG] = { .type = NLA_NUL_STRING, .len = SHARED_SECRET_MAX, }, + [DRBD_A_NET_CONF_INTEGRITY_ALG] = { .type = NLA_NUL_STRING, .len = SHARED_SECRET_MAX, }, + [DRBD_A_NET_CONF_VERIFY_ALG] = { .type = NLA_NUL_STRING, .len = SHARED_SECRET_MAX, }, + [DRBD_A_NET_CONF_CSUMS_ALG] = { .type = NLA_NUL_STRING, .len = SHARED_SECRET_MAX, }, + [DRBD_A_NET_CONF_WIRE_PROTOCOL] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_CONNECT_INT] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_TIMEOUT] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_PING_INT] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_PING_TIMEO] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_SNDBUF_SIZE] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_RCVBUF_SIZE] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_KO_COUNT] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_MAX_BUFFERS] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_MAX_EPOCH_SIZE] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_UNPLUG_WATERMARK] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_AFTER_SB_0P] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_AFTER_SB_1P] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_AFTER_SB_2P] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_RR_CONFLICT] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_ON_CONGESTION] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_CONG_FILL] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_CONG_EXTENTS] = { .type = NLA_U32, }, + [DRBD_A_NET_CONF_TWO_PRIMARIES] = { .type = NLA_U8, }, + [DRBD_A_NET_CONF_DISCARD_MY_DATA] = { .type = NLA_U8, }, + [DRBD_A_NET_CONF_TCP_CORK] = { .type = NLA_U8, }, + [DRBD_A_NET_CONF_ALWAYS_ASBP] = { .type = NLA_U8, }, + [DRBD_A_NET_CONF_TENTATIVE] = { .type = NLA_U8, }, + [DRBD_A_NET_CONF_USE_RLE] = { .type = NLA_U8, }, + [DRBD_A_NET_CONF_CSUMS_AFTER_CRASH_ONLY] = { .type = NLA_U8, }, + [DRBD_A_NET_CONF_SOCK_CHECK_TIMEO] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_new_c_uuid_parms_nl_policy[DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM + 1] = { + [DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM] = { .type = NLA_U8, }, +}; + +const struct nla_policy drbd_peer_device_info_nl_policy[DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY + 1] = { + [DRBD_A_PEER_DEVICE_INFO_PEER_REPL_STATE] = { .type = NLA_U32, }, + [DRBD_A_PEER_DEVICE_INFO_PEER_DISK_STATE] = { .type = NLA_U32, }, + [DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_USER] = { .type = NLA_U32, }, + [DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_PEER] = { .type = NLA_U32, }, + [DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_peer_device_statistics_nl_policy[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS + 1] = { + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RECEIVED] = { .type = NLA_U64, }, + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_SENT] = { .type = NLA_U64, }, + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_PENDING] = { .type = NLA_U32, }, + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_UNACKED] = { .type = NLA_U32, }, + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_OUT_OF_SYNC] = { .type = NLA_U64, }, + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RESYNC_FAILED] = { .type = NLA_U64, }, + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_BITMAP_UUID] = { .type = NLA_U64, }, + [DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_res_opts_nl_policy[DRBD_A_RES_OPTS_ON_NO_DATA + 1] = { + [DRBD_A_RES_OPTS_CPU_MASK] = { .type = NLA_NUL_STRING, .len = DRBD_CPU_MASK_SIZE, }, + [DRBD_A_RES_OPTS_ON_NO_DATA] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_resize_parms_nl_policy[DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE + 1] = { + [DRBD_A_RESIZE_PARMS_RESIZE_SIZE] = { .type = NLA_U64, }, + [DRBD_A_RESIZE_PARMS_RESIZE_FORCE] = { .type = NLA_U8, }, + [DRBD_A_RESIZE_PARMS_NO_RESYNC] = { .type = NLA_U8, }, + [DRBD_A_RESIZE_PARMS_AL_STRIPES] = { .type = NLA_U32, }, + [DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_resource_info_nl_policy[DRBD_A_RESOURCE_INFO_RES_SUSP_FEN + 1] = { + [DRBD_A_RESOURCE_INFO_RES_ROLE] = { .type = NLA_U32, }, + [DRBD_A_RESOURCE_INFO_RES_SUSP] = { .type = NLA_U8, }, + [DRBD_A_RESOURCE_INFO_RES_SUSP_NOD] = { .type = NLA_U8, }, + [DRBD_A_RESOURCE_INFO_RES_SUSP_FEN] = { .type = NLA_U8, }, +}; + +const struct nla_policy drbd_resource_statistics_nl_policy[DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING + 1] = { + [DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING] = { .type = NLA_U32, }, +}; + +const struct nla_policy drbd_set_role_parms_nl_policy[DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE + 1] = { + [DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE] = { .type = NLA_U8, }, +}; + +const struct nla_policy drbd_start_ov_parms_nl_policy[DRBD_A_START_OV_PARMS_OV_STOP_SECTOR + 1] = { + [DRBD_A_START_OV_PARMS_OV_START_SECTOR] = { .type = NLA_U64, }, + [DRBD_A_START_OV_PARMS_OV_STOP_SECTOR] = { .type = NLA_U64, }, +}; + +/* DRBD_ADM_GET_STATUS - do */ +static const struct nla_policy drbd_get_status_do_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_GET_STATUS - dump */ +static const struct nla_policy drbd_get_status_dump_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_NEW_MINOR - do */ +static const struct nla_policy drbd_new_minor_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_DEL_MINOR - do */ +static const struct nla_policy drbd_del_minor_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_NEW_RESOURCE - do */ +static const struct nla_policy drbd_new_resource_nl_policy[DRBD_NLA_RESOURCE_OPTS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_RESOURCE_OPTS] = NLA_POLICY_NESTED(drbd_res_opts_nl_policy), +}; + +/* DRBD_ADM_DEL_RESOURCE - do */ +static const struct nla_policy drbd_del_resource_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_RESOURCE_OPTS - do */ +static const struct nla_policy drbd_resource_opts_nl_policy[DRBD_NLA_RESOURCE_OPTS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_RESOURCE_OPTS] = NLA_POLICY_NESTED(drbd_res_opts_nl_policy), +}; + +/* DRBD_ADM_CONNECT - do */ +static const struct nla_policy drbd_connect_nl_policy[DRBD_NLA_NET_CONF + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_NET_CONF] = NLA_POLICY_NESTED(drbd_net_conf_nl_policy), +}; + +/* DRBD_ADM_DISCONNECT - do */ +static const struct nla_policy drbd_disconnect_nl_policy[DRBD_NLA_DISCONNECT_PARMS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_DISCONNECT_PARMS] = NLA_POLICY_NESTED(drbd_disconnect_parms_nl_policy), +}; + +/* DRBD_ADM_ATTACH - do */ +static const struct nla_policy drbd_attach_nl_policy[DRBD_NLA_DISK_CONF + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_DISK_CONF] = NLA_POLICY_NESTED(drbd_disk_conf_nl_policy), +}; + +/* DRBD_ADM_RESIZE - do */ +static const struct nla_policy drbd_resize_nl_policy[DRBD_NLA_RESIZE_PARMS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_RESIZE_PARMS] = NLA_POLICY_NESTED(drbd_resize_parms_nl_policy), +}; + +/* DRBD_ADM_PRIMARY - do */ +static const struct nla_policy drbd_primary_nl_policy[DRBD_NLA_SET_ROLE_PARMS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_SET_ROLE_PARMS] = NLA_POLICY_NESTED(drbd_set_role_parms_nl_policy), +}; + +/* DRBD_ADM_SECONDARY - do */ +static const struct nla_policy drbd_secondary_nl_policy[DRBD_NLA_SET_ROLE_PARMS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_SET_ROLE_PARMS] = NLA_POLICY_NESTED(drbd_set_role_parms_nl_policy), +}; + +/* DRBD_ADM_NEW_C_UUID - do */ +static const struct nla_policy drbd_new_c_uuid_nl_policy[DRBD_NLA_NEW_C_UUID_PARMS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_NEW_C_UUID_PARMS] = NLA_POLICY_NESTED(drbd_new_c_uuid_parms_nl_policy), +}; + +/* DRBD_ADM_START_OV - do */ +static const struct nla_policy drbd_start_ov_nl_policy[DRBD_NLA_START_OV_PARMS + 1] = { + [DRBD_NLA_START_OV_PARMS] = NLA_POLICY_NESTED(drbd_start_ov_parms_nl_policy), +}; + +/* DRBD_ADM_DETACH - do */ +static const struct nla_policy drbd_detach_nl_policy[DRBD_NLA_DETACH_PARMS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_DETACH_PARMS] = NLA_POLICY_NESTED(drbd_detach_parms_nl_policy), +}; + +/* DRBD_ADM_INVALIDATE - do */ +static const struct nla_policy drbd_invalidate_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_INVAL_PEER - do */ +static const struct nla_policy drbd_inval_peer_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_PAUSE_SYNC - do */ +static const struct nla_policy drbd_pause_sync_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_RESUME_SYNC - do */ +static const struct nla_policy drbd_resume_sync_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_SUSPEND_IO - do */ +static const struct nla_policy drbd_suspend_io_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_RESUME_IO - do */ +static const struct nla_policy drbd_resume_io_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_OUTDATE - do */ +static const struct nla_policy drbd_outdate_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_GET_TIMEOUT_TYPE - do */ +static const struct nla_policy drbd_get_timeout_type_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_DOWN - do */ +static const struct nla_policy drbd_down_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* DRBD_ADM_CHG_DISK_OPTS - do */ +static const struct nla_policy drbd_chg_disk_opts_nl_policy[DRBD_NLA_DISK_CONF + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_DISK_CONF] = NLA_POLICY_NESTED(drbd_disk_conf_nl_policy), +}; + +/* DRBD_ADM_CHG_NET_OPTS - do */ +static const struct nla_policy drbd_chg_net_opts_nl_policy[DRBD_NLA_NET_CONF + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_NET_CONF] = NLA_POLICY_NESTED(drbd_net_conf_nl_policy), +}; + +/* DRBD_ADM_GET_RESOURCES - dump */ +static const struct nla_policy drbd_get_resources_nl_policy[DRBD_NLA_RESOURCE_STATISTICS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_RESOURCE_INFO] = NLA_POLICY_NESTED(drbd_resource_info_nl_policy), + [DRBD_NLA_RESOURCE_STATISTICS] = NLA_POLICY_NESTED(drbd_resource_statistics_nl_policy), +}; + +/* DRBD_ADM_GET_DEVICES - dump */ +static const struct nla_policy drbd_get_devices_nl_policy[DRBD_NLA_DEVICE_STATISTICS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_DEVICE_INFO] = NLA_POLICY_NESTED(drbd_device_info_nl_policy), + [DRBD_NLA_DEVICE_STATISTICS] = NLA_POLICY_NESTED(drbd_device_statistics_nl_policy), +}; + +/* DRBD_ADM_GET_CONNECTIONS - dump */ +static const struct nla_policy drbd_get_connections_nl_policy[DRBD_NLA_CONNECTION_STATISTICS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_CONNECTION_INFO] = NLA_POLICY_NESTED(drbd_connection_info_nl_policy), + [DRBD_NLA_CONNECTION_STATISTICS] = NLA_POLICY_NESTED(drbd_connection_statistics_nl_policy), +}; + +/* DRBD_ADM_GET_PEER_DEVICES - dump */ +static const struct nla_policy drbd_get_peer_devices_nl_policy[DRBD_NLA_PEER_DEVICE_STATISTICS + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), + [DRBD_NLA_PEER_DEVICE_INFO] = NLA_POLICY_NESTED(drbd_peer_device_info_nl_policy), + [DRBD_NLA_PEER_DEVICE_STATISTICS] = NLA_POLICY_NESTED(drbd_peer_device_statistics_nl_policy), +}; + +/* DRBD_ADM_GET_INITIAL_STATE - dump */ +static const struct nla_policy drbd_get_initial_state_nl_policy[DRBD_NLA_CFG_CONTEXT + 1] = { + [DRBD_NLA_CFG_CONTEXT] = NLA_POLICY_NESTED(drbd_drbd_cfg_context_nl_policy), +}; + +/* Ops table for drbd */ +const struct genl_split_ops drbd_nl_ops[32] = { + { + .cmd = DRBD_ADM_GET_STATUS, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_get_status_doit, + .post_doit = drbd_post_doit, + .policy = drbd_get_status_do_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_GET_STATUS, + .dumpit = drbd_nl_get_status_dumpit, + .policy = drbd_get_status_dump_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_CMD_CAP_DUMP, + }, + { + .cmd = DRBD_ADM_NEW_MINOR, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_new_minor_doit, + .post_doit = drbd_post_doit, + .policy = drbd_new_minor_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_DEL_MINOR, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_del_minor_doit, + .post_doit = drbd_post_doit, + .policy = drbd_del_minor_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_NEW_RESOURCE, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_new_resource_doit, + .post_doit = drbd_post_doit, + .policy = drbd_new_resource_nl_policy, + .maxattr = DRBD_NLA_RESOURCE_OPTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_DEL_RESOURCE, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_del_resource_doit, + .post_doit = drbd_post_doit, + .policy = drbd_del_resource_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_RESOURCE_OPTS, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_resource_opts_doit, + .post_doit = drbd_post_doit, + .policy = drbd_resource_opts_nl_policy, + .maxattr = DRBD_NLA_RESOURCE_OPTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_CONNECT, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_connect_doit, + .post_doit = drbd_post_doit, + .policy = drbd_connect_nl_policy, + .maxattr = DRBD_NLA_NET_CONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_DISCONNECT, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_disconnect_doit, + .post_doit = drbd_post_doit, + .policy = drbd_disconnect_nl_policy, + .maxattr = DRBD_NLA_DISCONNECT_PARMS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_ATTACH, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_attach_doit, + .post_doit = drbd_post_doit, + .policy = drbd_attach_nl_policy, + .maxattr = DRBD_NLA_DISK_CONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_RESIZE, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_resize_doit, + .post_doit = drbd_post_doit, + .policy = drbd_resize_nl_policy, + .maxattr = DRBD_NLA_RESIZE_PARMS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_PRIMARY, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_primary_doit, + .post_doit = drbd_post_doit, + .policy = drbd_primary_nl_policy, + .maxattr = DRBD_NLA_SET_ROLE_PARMS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_SECONDARY, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_secondary_doit, + .post_doit = drbd_post_doit, + .policy = drbd_secondary_nl_policy, + .maxattr = DRBD_NLA_SET_ROLE_PARMS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_NEW_C_UUID, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_new_c_uuid_doit, + .post_doit = drbd_post_doit, + .policy = drbd_new_c_uuid_nl_policy, + .maxattr = DRBD_NLA_NEW_C_UUID_PARMS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_START_OV, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_start_ov_doit, + .post_doit = drbd_post_doit, + .policy = drbd_start_ov_nl_policy, + .maxattr = DRBD_NLA_START_OV_PARMS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_DETACH, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_detach_doit, + .post_doit = drbd_post_doit, + .policy = drbd_detach_nl_policy, + .maxattr = DRBD_NLA_DETACH_PARMS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_INVALIDATE, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_invalidate_doit, + .post_doit = drbd_post_doit, + .policy = drbd_invalidate_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_INVAL_PEER, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_inval_peer_doit, + .post_doit = drbd_post_doit, + .policy = drbd_inval_peer_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_PAUSE_SYNC, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_pause_sync_doit, + .post_doit = drbd_post_doit, + .policy = drbd_pause_sync_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_RESUME_SYNC, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_resume_sync_doit, + .post_doit = drbd_post_doit, + .policy = drbd_resume_sync_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_SUSPEND_IO, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_suspend_io_doit, + .post_doit = drbd_post_doit, + .policy = drbd_suspend_io_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_RESUME_IO, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_resume_io_doit, + .post_doit = drbd_post_doit, + .policy = drbd_resume_io_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_OUTDATE, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_outdate_doit, + .post_doit = drbd_post_doit, + .policy = drbd_outdate_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_GET_TIMEOUT_TYPE, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_get_timeout_type_doit, + .post_doit = drbd_post_doit, + .policy = drbd_get_timeout_type_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_DOWN, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_down_doit, + .post_doit = drbd_post_doit, + .policy = drbd_down_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_CHG_DISK_OPTS, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_chg_disk_opts_doit, + .post_doit = drbd_post_doit, + .policy = drbd_chg_disk_opts_nl_policy, + .maxattr = DRBD_NLA_DISK_CONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_CHG_NET_OPTS, + .pre_doit = drbd_pre_doit, + .doit = drbd_nl_chg_net_opts_doit, + .post_doit = drbd_post_doit, + .policy = drbd_chg_net_opts_nl_policy, + .maxattr = DRBD_NLA_NET_CONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DRBD_ADM_GET_RESOURCES, + .dumpit = drbd_nl_get_resources_dumpit, + .policy = drbd_get_resources_nl_policy, + .maxattr = DRBD_NLA_RESOURCE_STATISTICS, + .flags = GENL_CMD_CAP_DUMP, + }, + { + .cmd = DRBD_ADM_GET_DEVICES, + .dumpit = drbd_nl_get_devices_dumpit, + .done = drbd_adm_dump_devices_done, + .policy = drbd_get_devices_nl_policy, + .maxattr = DRBD_NLA_DEVICE_STATISTICS, + .flags = GENL_CMD_CAP_DUMP, + }, + { + .cmd = DRBD_ADM_GET_CONNECTIONS, + .dumpit = drbd_nl_get_connections_dumpit, + .done = drbd_adm_dump_connections_done, + .policy = drbd_get_connections_nl_policy, + .maxattr = DRBD_NLA_CONNECTION_STATISTICS, + .flags = GENL_CMD_CAP_DUMP, + }, + { + .cmd = DRBD_ADM_GET_PEER_DEVICES, + .dumpit = drbd_nl_get_peer_devices_dumpit, + .done = drbd_adm_dump_peer_devices_done, + .policy = drbd_get_peer_devices_nl_policy, + .maxattr = DRBD_NLA_PEER_DEVICE_STATISTICS, + .flags = GENL_CMD_CAP_DUMP, + }, + { + .cmd = DRBD_ADM_GET_INITIAL_STATE, + .dumpit = drbd_nl_get_initial_state_dumpit, + .policy = drbd_get_initial_state_nl_policy, + .maxattr = DRBD_NLA_CFG_CONTEXT, + .flags = GENL_CMD_CAP_DUMP, + }, +}; + +static const struct genl_multicast_group drbd_nl_mcgrps[] = { + [DRBD_NLGRP_EVENTS] = { "events", }, +}; + +static int __drbd_cfg_context_from_attrs(struct drbd_cfg_context *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR; + struct nlattr *tla = info->attrs[DRBD_NLA_CFG_CONTEXT]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_drbd_cfg_context_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_VOLUME]; + if (nla && s) + s->ctx_volume = nla_get_u32(nla); + + nla = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME]; + if (nla && s) + s->ctx_resource_name_len = nla_strscpy(s->ctx_resource_name, nla, 128); + + nla = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_MY_ADDR]; + if (nla && s) + s->ctx_my_addr_len = nla_memcpy(s->ctx_my_addr, nla, 128); + + nla = ntb[DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR]; + if (nla && s) + s->ctx_peer_addr_len = nla_memcpy(s->ctx_peer_addr, nla, 128); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int drbd_cfg_context_from_attrs(struct drbd_cfg_context *s, + struct genl_info *info) +{ + return __drbd_cfg_context_from_attrs(s, NULL, info); +} + +int drbd_cfg_context_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __drbd_cfg_context_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __disk_conf_from_attrs(struct disk_conf *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_DISK_CONF_DISABLE_WRITE_SAME; + struct nlattr *tla = info->attrs[DRBD_NLA_DISK_CONF]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_DISK_CONF_DISABLE_WRITE_SAME + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_disk_conf_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_DISK_CONF_BACKING_DEV]; + if (nla) { + if (s) + s->backing_dev_len = nla_strscpy(s->backing_dev, nla, 128); + } else { + pr_info("<< missing required attr: backing_dev\n"); + err = -ENOMSG; + } + + nla = ntb[DRBD_A_DISK_CONF_META_DEV]; + if (nla) { + if (s) + s->meta_dev_len = nla_strscpy(s->meta_dev, nla, 128); + } else { + pr_info("<< missing required attr: meta_dev\n"); + err = -ENOMSG; + } + + nla = ntb[DRBD_A_DISK_CONF_META_DEV_IDX]; + if (nla) { + if (s) + s->meta_dev_idx = nla_get_s32(nla); + } else { + pr_info("<< missing required attr: meta_dev_idx\n"); + err = -ENOMSG; + } + + nla = ntb[DRBD_A_DISK_CONF_DISK_SIZE]; + if (nla && s) + s->disk_size = nla_get_u64(nla); + + nla = ntb[DRBD_A_DISK_CONF_MAX_BIO_BVECS]; + if (nla && s) + s->max_bio_bvecs = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_ON_IO_ERROR]; + if (nla && s) + s->on_io_error = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_FENCING]; + if (nla && s) + s->fencing = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_RESYNC_RATE]; + if (nla && s) + s->resync_rate = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_RESYNC_AFTER]; + if (nla && s) + s->resync_after = nla_get_s32(nla); + + nla = ntb[DRBD_A_DISK_CONF_AL_EXTENTS]; + if (nla && s) + s->al_extents = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_C_PLAN_AHEAD]; + if (nla && s) + s->c_plan_ahead = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_C_DELAY_TARGET]; + if (nla && s) + s->c_delay_target = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_C_FILL_TARGET]; + if (nla && s) + s->c_fill_target = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_C_MAX_RATE]; + if (nla && s) + s->c_max_rate = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_C_MIN_RATE]; + if (nla && s) + s->c_min_rate = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_DISK_BARRIER]; + if (nla && s) + s->disk_barrier = nla_get_u8(nla); + + nla = ntb[DRBD_A_DISK_CONF_DISK_FLUSHES]; + if (nla && s) + s->disk_flushes = nla_get_u8(nla); + + nla = ntb[DRBD_A_DISK_CONF_DISK_DRAIN]; + if (nla && s) + s->disk_drain = nla_get_u8(nla); + + nla = ntb[DRBD_A_DISK_CONF_MD_FLUSHES]; + if (nla && s) + s->md_flushes = nla_get_u8(nla); + + nla = ntb[DRBD_A_DISK_CONF_DISK_TIMEOUT]; + if (nla && s) + s->disk_timeout = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_READ_BALANCING]; + if (nla && s) + s->read_balancing = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_AL_UPDATES]; + if (nla && s) + s->al_updates = nla_get_u8(nla); + + nla = ntb[DRBD_A_DISK_CONF_DISCARD_ZEROES_IF_ALIGNED]; + if (nla && s) + s->discard_zeroes_if_aligned = nla_get_u8(nla); + + nla = ntb[DRBD_A_DISK_CONF_RS_DISCARD_GRANULARITY]; + if (nla && s) + s->rs_discard_granularity = nla_get_u32(nla); + + nla = ntb[DRBD_A_DISK_CONF_DISABLE_WRITE_SAME]; + if (nla && s) + s->disable_write_same = nla_get_u8(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int disk_conf_from_attrs(struct disk_conf *s, + struct genl_info *info) +{ + return __disk_conf_from_attrs(s, NULL, info); +} + +int disk_conf_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __disk_conf_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __res_opts_from_attrs(struct res_opts *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_RES_OPTS_ON_NO_DATA; + struct nlattr *tla = info->attrs[DRBD_NLA_RESOURCE_OPTS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_RES_OPTS_ON_NO_DATA + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_res_opts_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_RES_OPTS_CPU_MASK]; + if (nla && s) + s->cpu_mask_len = nla_strscpy(s->cpu_mask, nla, DRBD_CPU_MASK_SIZE); + + nla = ntb[DRBD_A_RES_OPTS_ON_NO_DATA]; + if (nla && s) + s->on_no_data = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int res_opts_from_attrs(struct res_opts *s, + struct genl_info *info) +{ + return __res_opts_from_attrs(s, NULL, info); +} + +int res_opts_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __res_opts_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __net_conf_from_attrs(struct net_conf *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_NET_CONF_SOCK_CHECK_TIMEO; + struct nlattr *tla = info->attrs[DRBD_NLA_NET_CONF]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_NET_CONF_SOCK_CHECK_TIMEO + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_net_conf_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_NET_CONF_SHARED_SECRET]; + if (nla && s) + s->shared_secret_len = nla_strscpy(s->shared_secret, nla, SHARED_SECRET_MAX); + + nla = ntb[DRBD_A_NET_CONF_CRAM_HMAC_ALG]; + if (nla && s) + s->cram_hmac_alg_len = nla_strscpy(s->cram_hmac_alg, nla, SHARED_SECRET_MAX); + + nla = ntb[DRBD_A_NET_CONF_INTEGRITY_ALG]; + if (nla && s) + s->integrity_alg_len = nla_strscpy(s->integrity_alg, nla, SHARED_SECRET_MAX); + + nla = ntb[DRBD_A_NET_CONF_VERIFY_ALG]; + if (nla && s) + s->verify_alg_len = nla_strscpy(s->verify_alg, nla, SHARED_SECRET_MAX); + + nla = ntb[DRBD_A_NET_CONF_CSUMS_ALG]; + if (nla && s) + s->csums_alg_len = nla_strscpy(s->csums_alg, nla, SHARED_SECRET_MAX); + + nla = ntb[DRBD_A_NET_CONF_WIRE_PROTOCOL]; + if (nla && s) + s->wire_protocol = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_CONNECT_INT]; + if (nla && s) + s->connect_int = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_TIMEOUT]; + if (nla && s) + s->timeout = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_PING_INT]; + if (nla && s) + s->ping_int = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_PING_TIMEO]; + if (nla && s) + s->ping_timeo = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_SNDBUF_SIZE]; + if (nla && s) + s->sndbuf_size = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_RCVBUF_SIZE]; + if (nla && s) + s->rcvbuf_size = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_KO_COUNT]; + if (nla && s) + s->ko_count = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_MAX_BUFFERS]; + if (nla && s) + s->max_buffers = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_MAX_EPOCH_SIZE]; + if (nla && s) + s->max_epoch_size = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_UNPLUG_WATERMARK]; + if (nla && s) + s->unplug_watermark = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_AFTER_SB_0P]; + if (nla && s) + s->after_sb_0p = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_AFTER_SB_1P]; + if (nla && s) + s->after_sb_1p = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_AFTER_SB_2P]; + if (nla && s) + s->after_sb_2p = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_RR_CONFLICT]; + if (nla && s) + s->rr_conflict = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_ON_CONGESTION]; + if (nla && s) + s->on_congestion = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_CONG_FILL]; + if (nla && s) + s->cong_fill = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_CONG_EXTENTS]; + if (nla && s) + s->cong_extents = nla_get_u32(nla); + + nla = ntb[DRBD_A_NET_CONF_TWO_PRIMARIES]; + if (nla && s) + s->two_primaries = nla_get_u8(nla); + + nla = ntb[DRBD_A_NET_CONF_DISCARD_MY_DATA]; + if (nla && s) + s->discard_my_data = nla_get_u8(nla); + + nla = ntb[DRBD_A_NET_CONF_TCP_CORK]; + if (nla && s) + s->tcp_cork = nla_get_u8(nla); + + nla = ntb[DRBD_A_NET_CONF_ALWAYS_ASBP]; + if (nla && s) + s->always_asbp = nla_get_u8(nla); + + nla = ntb[DRBD_A_NET_CONF_TENTATIVE]; + if (nla && s) + s->tentative = nla_get_u8(nla); + + nla = ntb[DRBD_A_NET_CONF_USE_RLE]; + if (nla && s) + s->use_rle = nla_get_u8(nla); + + nla = ntb[DRBD_A_NET_CONF_CSUMS_AFTER_CRASH_ONLY]; + if (nla && s) + s->csums_after_crash_only = nla_get_u8(nla); + + nla = ntb[DRBD_A_NET_CONF_SOCK_CHECK_TIMEO]; + if (nla && s) + s->sock_check_timeo = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int net_conf_from_attrs(struct net_conf *s, + struct genl_info *info) +{ + return __net_conf_from_attrs(s, NULL, info); +} + +int net_conf_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __net_conf_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __set_role_parms_from_attrs(struct set_role_parms *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE; + struct nlattr *tla = info->attrs[DRBD_NLA_SET_ROLE_PARMS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_set_role_parms_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE]; + if (nla && s) + s->assume_uptodate = nla_get_u8(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int set_role_parms_from_attrs(struct set_role_parms *s, + struct genl_info *info) +{ + return __set_role_parms_from_attrs(s, NULL, info); +} + +int set_role_parms_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __set_role_parms_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __resize_parms_from_attrs(struct resize_parms *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE; + struct nlattr *tla = info->attrs[DRBD_NLA_RESIZE_PARMS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_resize_parms_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_RESIZE_PARMS_RESIZE_SIZE]; + if (nla && s) + s->resize_size = nla_get_u64(nla); + + nla = ntb[DRBD_A_RESIZE_PARMS_RESIZE_FORCE]; + if (nla && s) + s->resize_force = nla_get_u8(nla); + + nla = ntb[DRBD_A_RESIZE_PARMS_NO_RESYNC]; + if (nla && s) + s->no_resync = nla_get_u8(nla); + + nla = ntb[DRBD_A_RESIZE_PARMS_AL_STRIPES]; + if (nla && s) + s->al_stripes = nla_get_u32(nla); + + nla = ntb[DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE]; + if (nla && s) + s->al_stripe_size = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int resize_parms_from_attrs(struct resize_parms *s, + struct genl_info *info) +{ + return __resize_parms_from_attrs(s, NULL, info); +} + +int resize_parms_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __resize_parms_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __start_ov_parms_from_attrs(struct start_ov_parms *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_START_OV_PARMS_OV_STOP_SECTOR; + struct nlattr *tla = info->attrs[DRBD_NLA_START_OV_PARMS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_START_OV_PARMS_OV_STOP_SECTOR + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_start_ov_parms_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_START_OV_PARMS_OV_START_SECTOR]; + if (nla && s) + s->ov_start_sector = nla_get_u64(nla); + + nla = ntb[DRBD_A_START_OV_PARMS_OV_STOP_SECTOR]; + if (nla && s) + s->ov_stop_sector = nla_get_u64(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int start_ov_parms_from_attrs(struct start_ov_parms *s, + struct genl_info *info) +{ + return __start_ov_parms_from_attrs(s, NULL, info); +} + +int start_ov_parms_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __start_ov_parms_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __new_c_uuid_parms_from_attrs(struct new_c_uuid_parms *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM; + struct nlattr *tla = info->attrs[DRBD_NLA_NEW_C_UUID_PARMS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_new_c_uuid_parms_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM]; + if (nla && s) + s->clear_bm = nla_get_u8(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int new_c_uuid_parms_from_attrs(struct new_c_uuid_parms *s, + struct genl_info *info) +{ + return __new_c_uuid_parms_from_attrs(s, NULL, info); +} + +int new_c_uuid_parms_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __new_c_uuid_parms_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __disconnect_parms_from_attrs(struct disconnect_parms *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT; + struct nlattr *tla = info->attrs[DRBD_NLA_DISCONNECT_PARMS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_disconnect_parms_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT]; + if (nla && s) + s->force_disconnect = nla_get_u8(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int disconnect_parms_from_attrs(struct disconnect_parms *s, + struct genl_info *info) +{ + return __disconnect_parms_from_attrs(s, NULL, info); +} + +int disconnect_parms_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __disconnect_parms_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __detach_parms_from_attrs(struct detach_parms *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_DETACH_PARMS_FORCE_DETACH; + struct nlattr *tla = info->attrs[DRBD_NLA_DETACH_PARMS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_DETACH_PARMS_FORCE_DETACH + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_detach_parms_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_DETACH_PARMS_FORCE_DETACH]; + if (nla && s) + s->force_detach = nla_get_u8(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int detach_parms_from_attrs(struct detach_parms *s, + struct genl_info *info) +{ + return __detach_parms_from_attrs(s, NULL, info); +} + +int detach_parms_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __detach_parms_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __resource_info_from_attrs(struct resource_info *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_RESOURCE_INFO_RES_SUSP_FEN; + struct nlattr *tla = info->attrs[DRBD_NLA_RESOURCE_INFO]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_RESOURCE_INFO_RES_SUSP_FEN + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_resource_info_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_RESOURCE_INFO_RES_ROLE]; + if (nla && s) + s->res_role = nla_get_u32(nla); + + nla = ntb[DRBD_A_RESOURCE_INFO_RES_SUSP]; + if (nla && s) + s->res_susp = nla_get_u8(nla); + + nla = ntb[DRBD_A_RESOURCE_INFO_RES_SUSP_NOD]; + if (nla && s) + s->res_susp_nod = nla_get_u8(nla); + + nla = ntb[DRBD_A_RESOURCE_INFO_RES_SUSP_FEN]; + if (nla && s) + s->res_susp_fen = nla_get_u8(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int resource_info_from_attrs(struct resource_info *s, + struct genl_info *info) +{ + return __resource_info_from_attrs(s, NULL, info); +} + +int resource_info_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __resource_info_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __device_info_from_attrs(struct device_info *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_DEVICE_INFO_DEV_DISK_STATE; + struct nlattr *tla = info->attrs[DRBD_NLA_DEVICE_INFO]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_DEVICE_INFO_DEV_DISK_STATE + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_device_info_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_DEVICE_INFO_DEV_DISK_STATE]; + if (nla && s) + s->dev_disk_state = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int device_info_from_attrs(struct device_info *s, + struct genl_info *info) +{ + return __device_info_from_attrs(s, NULL, info); +} + +int device_info_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __device_info_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __connection_info_from_attrs(struct connection_info *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_CONNECTION_INFO_CONN_ROLE; + struct nlattr *tla = info->attrs[DRBD_NLA_CONNECTION_INFO]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_CONNECTION_INFO_CONN_ROLE + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_connection_info_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_CONNECTION_INFO_CONN_CONNECTION_STATE]; + if (nla && s) + s->conn_connection_state = nla_get_u32(nla); + + nla = ntb[DRBD_A_CONNECTION_INFO_CONN_ROLE]; + if (nla && s) + s->conn_role = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int connection_info_from_attrs(struct connection_info *s, + struct genl_info *info) +{ + return __connection_info_from_attrs(s, NULL, info); +} + +int connection_info_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __connection_info_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __peer_device_info_from_attrs(struct peer_device_info *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY; + struct nlattr *tla = info->attrs[DRBD_NLA_PEER_DEVICE_INFO]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_peer_device_info_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_PEER_DEVICE_INFO_PEER_REPL_STATE]; + if (nla && s) + s->peer_repl_state = nla_get_u32(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_INFO_PEER_DISK_STATE]; + if (nla && s) + s->peer_disk_state = nla_get_u32(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_USER]; + if (nla && s) + s->peer_resync_susp_user = nla_get_u32(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_PEER]; + if (nla && s) + s->peer_resync_susp_peer = nla_get_u32(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY]; + if (nla && s) + s->peer_resync_susp_dependency = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int peer_device_info_from_attrs(struct peer_device_info *s, + struct genl_info *info) +{ + return __peer_device_info_from_attrs(s, NULL, info); +} + +int peer_device_info_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __peer_device_info_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __resource_statistics_from_attrs(struct resource_statistics *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING; + struct nlattr *tla = info->attrs[DRBD_NLA_RESOURCE_STATISTICS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_resource_statistics_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING]; + if (nla && s) + s->res_stat_write_ordering = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int resource_statistics_from_attrs(struct resource_statistics *s, + struct genl_info *info) +{ + return __resource_statistics_from_attrs(s, NULL, info); +} + +int resource_statistics_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __resource_statistics_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __device_statistics_from_attrs(struct device_statistics *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS; + struct nlattr *tla = info->attrs[DRBD_NLA_DEVICE_STATISTICS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_device_statistics_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_SIZE]; + if (nla && s) + s->dev_size = nla_get_u64(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_READ]; + if (nla && s) + s->dev_read = nla_get_u64(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_WRITE]; + if (nla && s) + s->dev_write = nla_get_u64(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_AL_WRITES]; + if (nla && s) + s->dev_al_writes = nla_get_u64(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_BM_WRITES]; + if (nla && s) + s->dev_bm_writes = nla_get_u64(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_UPPER_PENDING]; + if (nla && s) + s->dev_upper_pending = nla_get_u32(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_LOWER_PENDING]; + if (nla && s) + s->dev_lower_pending = nla_get_u32(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_UPPER_BLOCKED]; + if (nla && s) + s->dev_upper_blocked = nla_get_u8(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_LOWER_BLOCKED]; + if (nla && s) + s->dev_lower_blocked = nla_get_u8(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_AL_SUSPENDED]; + if (nla && s) + s->dev_al_suspended = nla_get_u8(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_EXPOSED_DATA_UUID]; + if (nla && s) + s->dev_exposed_data_uuid = nla_get_u64(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_CURRENT_UUID]; + if (nla && s) + s->dev_current_uuid = nla_get_u64(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_DEV_DISK_FLAGS]; + if (nla && s) + s->dev_disk_flags = nla_get_u32(nla); + + nla = ntb[DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS]; + if (nla && s) + s->history_uuids_len = nla_memcpy(s->history_uuids, nla, DRBD_NL_HISTORY_UUIDS_SIZE); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int device_statistics_from_attrs(struct device_statistics *s, + struct genl_info *info) +{ + return __device_statistics_from_attrs(s, NULL, info); +} + +int device_statistics_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __device_statistics_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __connection_statistics_from_attrs(struct connection_statistics *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED; + struct nlattr *tla = info->attrs[DRBD_NLA_CONNECTION_STATISTICS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_connection_statistics_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED]; + if (nla && s) + s->conn_congested = nla_get_u8(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int connection_statistics_from_attrs(struct connection_statistics *s, + struct genl_info *info) +{ + return __connection_statistics_from_attrs(s, NULL, info); +} + +int connection_statistics_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __connection_statistics_from_attrs(NULL, ret_nested_attribute_table, info); +} + +static int __peer_device_statistics_from_attrs(struct peer_device_statistics *s, + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + const int maxtype = DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS; + struct nlattr *tla = info->attrs[DRBD_NLA_PEER_DEVICE_STATISTICS]; + struct nlattr **ntb; + struct nlattr *nla; + int err = 0; + + if (ret_nested_attribute_table) + *ret_nested_attribute_table = NULL; + if (!tla) + return -ENOMSG; + ntb = kcalloc(DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS + 1, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_peer_device_statistics_nl_policy, NULL); + if (err) + goto out; + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RECEIVED]; + if (nla && s) + s->peer_dev_received = nla_get_u64(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_SENT]; + if (nla && s) + s->peer_dev_sent = nla_get_u64(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_PENDING]; + if (nla && s) + s->peer_dev_pending = nla_get_u32(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_UNACKED]; + if (nla && s) + s->peer_dev_unacked = nla_get_u32(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_OUT_OF_SYNC]; + if (nla && s) + s->peer_dev_out_of_sync = nla_get_u64(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RESYNC_FAILED]; + if (nla && s) + s->peer_dev_resync_failed = nla_get_u64(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_BITMAP_UUID]; + if (nla && s) + s->peer_dev_bitmap_uuid = nla_get_u64(nla); + + nla = ntb[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS]; + if (nla && s) + s->peer_dev_flags = nla_get_u32(nla); + +out: + if (ret_nested_attribute_table && (!err || err == -ENOMSG)) + *ret_nested_attribute_table = ntb; + else + kfree(ntb); + return err; +} + +int peer_device_statistics_from_attrs(struct peer_device_statistics *s, + struct genl_info *info) +{ + return __peer_device_statistics_from_attrs(s, NULL, info); +} + +int peer_device_statistics_ntb_from_attrs( + struct nlattr ***ret_nested_attribute_table, + struct genl_info *info) +{ + return __peer_device_statistics_from_attrs(NULL, ret_nested_attribute_table, info); +} + +int drbd_cfg_reply_to_skb(struct sk_buff *skb, struct drbd_cfg_reply *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_CFG_REPLY); + + if (!tla) + goto nla_put_failure; + + if (nla_put(skb, DRBD_A_DRBD_CFG_REPLY_INFO_TEXT, min_t(int, 0, + s->info_text_len + (s->info_text_len < 0)), s->info_text)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int drbd_cfg_context_to_skb(struct sk_buff *skb, struct drbd_cfg_context *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_CFG_CONTEXT); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_VOLUME, s->ctx_volume)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME, min_t(int, 128, + s->ctx_resource_name_len + (s->ctx_resource_name_len < 128)), s->ctx_resource_name)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_MY_ADDR, min_t(int, 128, + s->ctx_my_addr_len), s->ctx_my_addr)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR, min_t(int, 128, + s->ctx_peer_addr_len), s->ctx_peer_addr)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int disk_conf_to_skb(struct sk_buff *skb, struct disk_conf *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_DISK_CONF); + + if (!tla) + goto nla_put_failure; + + if (nla_put(skb, DRBD_A_DISK_CONF_BACKING_DEV, min_t(int, 128, + s->backing_dev_len + (s->backing_dev_len < 128)), s->backing_dev)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_DISK_CONF_META_DEV, min_t(int, 128, + s->meta_dev_len + (s->meta_dev_len < 128)), s->meta_dev)) + goto nla_put_failure; + if (nla_put_s32(skb, DRBD_A_DISK_CONF_META_DEV_IDX, s->meta_dev_idx)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_DISK_CONF_DISK_SIZE, s->disk_size, 0)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_MAX_BIO_BVECS, s->max_bio_bvecs)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_ON_IO_ERROR, s->on_io_error)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_FENCING, s->fencing)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_RESYNC_RATE, s->resync_rate)) + goto nla_put_failure; + if (nla_put_s32(skb, DRBD_A_DISK_CONF_RESYNC_AFTER, s->resync_after)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_AL_EXTENTS, s->al_extents)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_C_PLAN_AHEAD, s->c_plan_ahead)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_C_DELAY_TARGET, s->c_delay_target)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_C_FILL_TARGET, s->c_fill_target)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_C_MAX_RATE, s->c_max_rate)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_C_MIN_RATE, s->c_min_rate)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DISK_CONF_DISK_BARRIER, s->disk_barrier)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DISK_CONF_DISK_FLUSHES, s->disk_flushes)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DISK_CONF_DISK_DRAIN, s->disk_drain)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DISK_CONF_MD_FLUSHES, s->md_flushes)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_DISK_TIMEOUT, s->disk_timeout)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_READ_BALANCING, s->read_balancing)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DISK_CONF_AL_UPDATES, s->al_updates)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DISK_CONF_DISCARD_ZEROES_IF_ALIGNED, s->discard_zeroes_if_aligned)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DISK_CONF_RS_DISCARD_GRANULARITY, s->rs_discard_granularity)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DISK_CONF_DISABLE_WRITE_SAME, s->disable_write_same)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int res_opts_to_skb(struct sk_buff *skb, struct res_opts *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_RESOURCE_OPTS); + + if (!tla) + goto nla_put_failure; + + if (nla_put(skb, DRBD_A_RES_OPTS_CPU_MASK, min_t(int, DRBD_CPU_MASK_SIZE, + s->cpu_mask_len + (s->cpu_mask_len < DRBD_CPU_MASK_SIZE)), s->cpu_mask)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_RES_OPTS_ON_NO_DATA, s->on_no_data)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int net_conf_to_skb(struct sk_buff *skb, struct net_conf *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_NET_CONF); + + if (!tla) + goto nla_put_failure; + + if (nla_put(skb, DRBD_A_NET_CONF_SHARED_SECRET, min_t(int, SHARED_SECRET_MAX, + s->shared_secret_len + (s->shared_secret_len < SHARED_SECRET_MAX)), s->shared_secret)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_NET_CONF_CRAM_HMAC_ALG, min_t(int, SHARED_SECRET_MAX, + s->cram_hmac_alg_len + (s->cram_hmac_alg_len < SHARED_SECRET_MAX)), s->cram_hmac_alg)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_NET_CONF_INTEGRITY_ALG, min_t(int, SHARED_SECRET_MAX, + s->integrity_alg_len + (s->integrity_alg_len < SHARED_SECRET_MAX)), s->integrity_alg)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_NET_CONF_VERIFY_ALG, min_t(int, SHARED_SECRET_MAX, + s->verify_alg_len + (s->verify_alg_len < SHARED_SECRET_MAX)), s->verify_alg)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_NET_CONF_CSUMS_ALG, min_t(int, SHARED_SECRET_MAX, + s->csums_alg_len + (s->csums_alg_len < SHARED_SECRET_MAX)), s->csums_alg)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_WIRE_PROTOCOL, s->wire_protocol)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_CONNECT_INT, s->connect_int)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_TIMEOUT, s->timeout)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_PING_INT, s->ping_int)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_PING_TIMEO, s->ping_timeo)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_SNDBUF_SIZE, s->sndbuf_size)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_RCVBUF_SIZE, s->rcvbuf_size)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_KO_COUNT, s->ko_count)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_MAX_BUFFERS, s->max_buffers)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_MAX_EPOCH_SIZE, s->max_epoch_size)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_UNPLUG_WATERMARK, s->unplug_watermark)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_AFTER_SB_0P, s->after_sb_0p)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_AFTER_SB_1P, s->after_sb_1p)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_AFTER_SB_2P, s->after_sb_2p)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_RR_CONFLICT, s->rr_conflict)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_ON_CONGESTION, s->on_congestion)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_CONG_FILL, s->cong_fill)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_CONG_EXTENTS, s->cong_extents)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_NET_CONF_TWO_PRIMARIES, s->two_primaries)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_NET_CONF_DISCARD_MY_DATA, s->discard_my_data)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_NET_CONF_TCP_CORK, s->tcp_cork)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_NET_CONF_ALWAYS_ASBP, s->always_asbp)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_NET_CONF_TENTATIVE, s->tentative)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_NET_CONF_USE_RLE, s->use_rle)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_NET_CONF_CSUMS_AFTER_CRASH_ONLY, s->csums_after_crash_only)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_NET_CONF_SOCK_CHECK_TIMEO, s->sock_check_timeo)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int set_role_parms_to_skb(struct sk_buff *skb, struct set_role_parms *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_SET_ROLE_PARMS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u8(skb, DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE, s->assume_uptodate)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int resize_parms_to_skb(struct sk_buff *skb, struct resize_parms *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_RESIZE_PARMS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u64_64bit(skb, DRBD_A_RESIZE_PARMS_RESIZE_SIZE, s->resize_size, 0)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_RESIZE_PARMS_RESIZE_FORCE, s->resize_force)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_RESIZE_PARMS_NO_RESYNC, s->no_resync)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_RESIZE_PARMS_AL_STRIPES, s->al_stripes)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE, s->al_stripe_size)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int state_info_to_skb(struct sk_buff *skb, struct state_info *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_STATE_INFO); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_STATE_INFO_SIB_REASON, s->sib_reason)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_CURRENT_STATE, s->current_state)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_CAPACITY, s->capacity, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_ED_UUID, s->ed_uuid, 0)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_PREV_STATE, s->prev_state)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_NEW_STATE, s->new_state)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_STATE_INFO_UUIDS, min_t(int, DRBD_NL_UUIDS_SIZE, + s->uuids_len), s->uuids)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_DISK_FLAGS, s->disk_flags)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_TOTAL, s->bits_total, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_OOS, s->bits_oos, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_TOTAL, s->bits_rs_total, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BITS_RS_FAILED, s->bits_rs_failed, 0)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_STATE_INFO_HELPER, min_t(int, 32, + s->helper_len + (s->helper_len < 32)), s->helper)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_HELPER_EXIT_CODE, s->helper_exit_code)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_SEND_CNT, s->send_cnt, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_RECV_CNT, s->recv_cnt, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_READ_CNT, s->read_cnt, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_WRIT_CNT, s->writ_cnt, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_AL_WRIT_CNT, s->al_writ_cnt, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_STATE_INFO_BM_WRIT_CNT, s->bm_writ_cnt, 0)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_AP_BIO_CNT, s->ap_bio_cnt)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_AP_PENDING_CNT, s->ap_pending_cnt)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_STATE_INFO_RS_PENDING_CNT, s->rs_pending_cnt)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int start_ov_parms_to_skb(struct sk_buff *skb, struct start_ov_parms *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_START_OV_PARMS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u64_64bit(skb, DRBD_A_START_OV_PARMS_OV_START_SECTOR, s->ov_start_sector, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_START_OV_PARMS_OV_STOP_SECTOR, s->ov_stop_sector, 0)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int new_c_uuid_parms_to_skb(struct sk_buff *skb, struct new_c_uuid_parms *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_NEW_C_UUID_PARMS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u8(skb, DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM, s->clear_bm)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int timeout_parms_to_skb(struct sk_buff *skb, struct timeout_parms *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_TIMEOUT_PARMS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_TIMEOUT_PARMS_TIMEOUT_TYPE, s->timeout_type)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int disconnect_parms_to_skb(struct sk_buff *skb, struct disconnect_parms *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_DISCONNECT_PARMS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u8(skb, DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT, s->force_disconnect)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int detach_parms_to_skb(struct sk_buff *skb, struct detach_parms *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_DETACH_PARMS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u8(skb, DRBD_A_DETACH_PARMS_FORCE_DETACH, s->force_detach)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int resource_info_to_skb(struct sk_buff *skb, struct resource_info *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_RESOURCE_INFO); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_RESOURCE_INFO_RES_ROLE, s->res_role)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_RESOURCE_INFO_RES_SUSP, s->res_susp)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_RESOURCE_INFO_RES_SUSP_NOD, s->res_susp_nod)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_RESOURCE_INFO_RES_SUSP_FEN, s->res_susp_fen)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int device_info_to_skb(struct sk_buff *skb, struct device_info *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_DEVICE_INFO); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_DEVICE_INFO_DEV_DISK_STATE, s->dev_disk_state)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int connection_info_to_skb(struct sk_buff *skb, struct connection_info *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_CONNECTION_INFO); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_CONNECTION_INFO_CONN_CONNECTION_STATE, s->conn_connection_state)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_CONNECTION_INFO_CONN_ROLE, s->conn_role)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int peer_device_info_to_skb(struct sk_buff *skb, struct peer_device_info *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_PEER_DEVICE_INFO); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_INFO_PEER_REPL_STATE, s->peer_repl_state)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_INFO_PEER_DISK_STATE, s->peer_disk_state)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_USER, s->peer_resync_susp_user)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_PEER, s->peer_resync_susp_peer)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY, s->peer_resync_susp_dependency)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int resource_statistics_to_skb(struct sk_buff *skb, struct resource_statistics *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_RESOURCE_STATISTICS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING, s->res_stat_write_ordering)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int device_statistics_to_skb(struct sk_buff *skb, struct device_statistics *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_DEVICE_STATISTICS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_SIZE, s->dev_size, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_READ, s->dev_read, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_WRITE, s->dev_write, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_AL_WRITES, s->dev_al_writes, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_BM_WRITES, s->dev_bm_writes, 0)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DEVICE_STATISTICS_DEV_UPPER_PENDING, s->dev_upper_pending)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DEVICE_STATISTICS_DEV_LOWER_PENDING, s->dev_lower_pending)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DEVICE_STATISTICS_DEV_UPPER_BLOCKED, s->dev_upper_blocked)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DEVICE_STATISTICS_DEV_LOWER_BLOCKED, s->dev_lower_blocked)) + goto nla_put_failure; + if (nla_put_u8(skb, DRBD_A_DEVICE_STATISTICS_DEV_AL_SUSPENDED, s->dev_al_suspended)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_EXPOSED_DATA_UUID, s->dev_exposed_data_uuid, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_DEVICE_STATISTICS_DEV_CURRENT_UUID, s->dev_current_uuid, 0)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DEVICE_STATISTICS_DEV_DISK_FLAGS, s->dev_disk_flags)) + goto nla_put_failure; + if (nla_put(skb, DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS, min_t(int, DRBD_NL_HISTORY_UUIDS_SIZE, + s->history_uuids_len), s->history_uuids)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int connection_statistics_to_skb(struct sk_buff *skb, struct connection_statistics *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_CONNECTION_STATISTICS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u8(skb, DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED, s->conn_congested)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int peer_device_statistics_to_skb(struct sk_buff *skb, struct peer_device_statistics *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_PEER_DEVICE_STATISTICS); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RECEIVED, s->peer_dev_received, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_SENT, s->peer_dev_sent, 0)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_PENDING, s->peer_dev_pending)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_UNACKED, s->peer_dev_unacked)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_OUT_OF_SYNC, s->peer_dev_out_of_sync, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RESYNC_FAILED, s->peer_dev_resync_failed, 0)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_BITMAP_UUID, s->peer_dev_bitmap_uuid, 0)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS, s->peer_dev_flags)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int drbd_notification_header_to_skb(struct sk_buff *skb, struct drbd_notification_header *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_NOTIFICATION_HEADER); + + if (!tla) + goto nla_put_failure; + + if (nla_put_u32(skb, DRBD_A_DRBD_NOTIFICATION_HEADER_NH_TYPE, s->nh_type)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +int drbd_helper_info_to_skb(struct sk_buff *skb, struct drbd_helper_info *s) +{ + struct nlattr *tla = nla_nest_start(skb, DRBD_NLA_HELPER); + + if (!tla) + goto nla_put_failure; + + if (nla_put(skb, DRBD_A_DRBD_HELPER_INFO_HELPER_NAME, min_t(int, 32, + s->helper_name_len + (s->helper_name_len < 32)), s->helper_name)) + goto nla_put_failure; + if (nla_put_u32(skb, DRBD_A_DRBD_HELPER_INFO_HELPER_STATUS, s->helper_status)) + goto nla_put_failure; + + nla_nest_end(skb, tla); + return 0; + +nla_put_failure: + if (tla) + nla_nest_cancel(skb, tla); + return -EMSGSIZE; +} + +void set_disk_conf_defaults(struct disk_conf *x) +{ + x->on_io_error = DRBD_ON_IO_ERROR_DEF; + x->fencing = DRBD_FENCING_DEF; + x->resync_rate = DRBD_RESYNC_RATE_DEF; + x->resync_after = DRBD_MINOR_NUMBER_DEF; + x->al_extents = DRBD_AL_EXTENTS_DEF; + x->c_plan_ahead = DRBD_C_PLAN_AHEAD_DEF; + x->c_delay_target = DRBD_C_DELAY_TARGET_DEF; + x->c_fill_target = DRBD_C_FILL_TARGET_DEF; + x->c_max_rate = DRBD_C_MAX_RATE_DEF; + x->c_min_rate = DRBD_C_MIN_RATE_DEF; + x->disk_barrier = DRBD_DISK_BARRIER_DEF; + x->disk_flushes = DRBD_DISK_FLUSHES_DEF; + x->disk_drain = DRBD_DISK_DRAIN_DEF; + x->md_flushes = DRBD_MD_FLUSHES_DEF; + x->disk_timeout = DRBD_DISK_TIMEOUT_DEF; + x->read_balancing = DRBD_READ_BALANCING_DEF; + x->al_updates = DRBD_AL_UPDATES_DEF; + x->discard_zeroes_if_aligned = DRBD_DISCARD_ZEROES_IF_ALIGNED_DEF; + x->rs_discard_granularity = DRBD_RS_DISCARD_GRANULARITY_DEF; + x->disable_write_same = DRBD_DISABLE_WRITE_SAME_DEF; +} + +void set_res_opts_defaults(struct res_opts *x) +{ + memset(x->cpu_mask, 0, sizeof(x->cpu_mask)); + x->cpu_mask_len = 0; + x->on_no_data = DRBD_ON_NO_DATA_DEF; +} + +void set_net_conf_defaults(struct net_conf *x) +{ + memset(x->shared_secret, 0, sizeof(x->shared_secret)); + x->shared_secret_len = 0; + memset(x->cram_hmac_alg, 0, sizeof(x->cram_hmac_alg)); + x->cram_hmac_alg_len = 0; + memset(x->integrity_alg, 0, sizeof(x->integrity_alg)); + x->integrity_alg_len = 0; + memset(x->verify_alg, 0, sizeof(x->verify_alg)); + x->verify_alg_len = 0; + memset(x->csums_alg, 0, sizeof(x->csums_alg)); + x->csums_alg_len = 0; + x->wire_protocol = DRBD_PROTOCOL_DEF; + x->connect_int = DRBD_CONNECT_INT_DEF; + x->timeout = DRBD_TIMEOUT_DEF; + x->ping_int = DRBD_PING_INT_DEF; + x->ping_timeo = DRBD_PING_TIMEO_DEF; + x->sndbuf_size = DRBD_SNDBUF_SIZE_DEF; + x->rcvbuf_size = DRBD_RCVBUF_SIZE_DEF; + x->ko_count = DRBD_KO_COUNT_DEF; + x->max_buffers = DRBD_MAX_BUFFERS_DEF; + x->max_epoch_size = DRBD_MAX_EPOCH_SIZE_DEF; + x->unplug_watermark = DRBD_UNPLUG_WATERMARK_DEF; + x->after_sb_0p = DRBD_AFTER_SB_0P_DEF; + x->after_sb_1p = DRBD_AFTER_SB_1P_DEF; + x->after_sb_2p = DRBD_AFTER_SB_2P_DEF; + x->rr_conflict = DRBD_RR_CONFLICT_DEF; + x->on_congestion = DRBD_ON_CONGESTION_DEF; + x->cong_fill = DRBD_CONG_FILL_DEF; + x->cong_extents = DRBD_CONG_EXTENTS_DEF; + x->two_primaries = DRBD_ALLOW_TWO_PRIMARIES_DEF; + x->tcp_cork = DRBD_TCP_CORK_DEF; + x->always_asbp = DRBD_ALWAYS_ASBP_DEF; + x->use_rle = DRBD_USE_RLE_DEF; + x->csums_after_crash_only = DRBD_CSUMS_AFTER_CRASH_ONLY_DEF; + x->sock_check_timeo = DRBD_SOCKET_CHECK_TIMEO_DEF; +} + +void set_resize_parms_defaults(struct resize_parms *x) +{ + x->al_stripes = DRBD_AL_STRIPES_DEF; + x->al_stripe_size = DRBD_AL_STRIPE_SIZE_DEF; +} diff --git a/drivers/block/drbd/drbd_nl_gen.h b/drivers/block/drbd/drbd_nl_gen.h new file mode 100644 index 000000000000..f2140dd1ac4e --- /dev/null +++ b/drivers/block/drbd/drbd_nl_gen.h @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ + +#ifndef _LINUX_DRBD_GEN_H +#define _LINUX_DRBD_GEN_H + +#include +#include + +#include +#include +#include + +/* Common nested types */ +extern const struct nla_policy drbd_connection_info_nl_policy[DRBD_A_CONNECTION_INFO_CONN_ROLE + 1]; +extern const struct nla_policy drbd_connection_statistics_nl_policy[DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED + 1]; +extern const struct nla_policy drbd_detach_parms_nl_policy[DRBD_A_DETACH_PARMS_FORCE_DETACH + 1]; +extern const struct nla_policy drbd_device_info_nl_policy[DRBD_A_DEVICE_INFO_DEV_DISK_STATE + 1]; +extern const struct nla_policy drbd_device_statistics_nl_policy[DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS + 1]; +extern const struct nla_policy drbd_disconnect_parms_nl_policy[DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT + 1]; +extern const struct nla_policy drbd_disk_conf_nl_policy[DRBD_A_DISK_CONF_DISABLE_WRITE_SAME + 1]; +extern const struct nla_policy drbd_drbd_cfg_context_nl_policy[DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR + 1]; +extern const struct nla_policy drbd_net_conf_nl_policy[DRBD_A_NET_CONF_SOCK_CHECK_TIMEO + 1]; +extern const struct nla_policy drbd_new_c_uuid_parms_nl_policy[DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM + 1]; +extern const struct nla_policy drbd_peer_device_info_nl_policy[DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY + 1]; +extern const struct nla_policy drbd_peer_device_statistics_nl_policy[DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS + 1]; +extern const struct nla_policy drbd_res_opts_nl_policy[DRBD_A_RES_OPTS_ON_NO_DATA + 1]; +extern const struct nla_policy drbd_resize_parms_nl_policy[DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE + 1]; +extern const struct nla_policy drbd_resource_info_nl_policy[DRBD_A_RESOURCE_INFO_RES_SUSP_FEN + 1]; +extern const struct nla_policy drbd_resource_statistics_nl_policy[DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING + 1]; +extern const struct nla_policy drbd_set_role_parms_nl_policy[DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE + 1]; +extern const struct nla_policy drbd_start_ov_parms_nl_policy[DRBD_A_START_OV_PARMS_OV_STOP_SECTOR + 1]; + +/* Ops table for drbd */ +extern const struct genl_split_ops drbd_nl_ops[32]; + +int drbd_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +void +drbd_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +int drbd_adm_dump_devices_done(struct netlink_callback *cb); +int drbd_adm_dump_connections_done(struct netlink_callback *cb); +int drbd_adm_dump_peer_devices_done(struct netlink_callback *cb); + +int drbd_nl_get_status_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_get_status_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int drbd_nl_new_minor_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_del_minor_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_new_resource_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_del_resource_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_resource_opts_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_connect_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_disconnect_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_attach_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_resize_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_primary_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_secondary_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_new_c_uuid_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_start_ov_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_detach_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_invalidate_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_inval_peer_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_pause_sync_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_resume_sync_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_suspend_io_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_resume_io_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_outdate_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_get_timeout_type_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_down_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_chg_disk_opts_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_chg_net_opts_doit(struct sk_buff *skb, struct genl_info *info); +int drbd_nl_get_resources_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int drbd_nl_get_devices_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int drbd_nl_get_connections_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int drbd_nl_get_peer_devices_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int drbd_nl_get_initial_state_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); + +enum { + DRBD_NLGRP_EVENTS, +}; + +struct drbd_cfg_reply { + char info_text[0]; + __u32 info_text_len; +}; + +struct drbd_cfg_context { + __u32 ctx_volume; + char ctx_resource_name[128]; + __u32 ctx_resource_name_len; + char ctx_my_addr[128]; + __u32 ctx_my_addr_len; + char ctx_peer_addr[128]; + __u32 ctx_peer_addr_len; +}; + +struct disk_conf { + char backing_dev[128]; + __u32 backing_dev_len; + char meta_dev[128]; + __u32 meta_dev_len; + __s32 meta_dev_idx; + __u64 disk_size; + __u32 max_bio_bvecs; + __u32 on_io_error; + __u32 fencing; + __u32 resync_rate; + __s32 resync_after; + __u32 al_extents; + __u32 c_plan_ahead; + __u32 c_delay_target; + __u32 c_fill_target; + __u32 c_max_rate; + __u32 c_min_rate; + unsigned char disk_barrier; + unsigned char disk_flushes; + unsigned char disk_drain; + unsigned char md_flushes; + __u32 disk_timeout; + __u32 read_balancing; + unsigned char al_updates; + unsigned char discard_zeroes_if_aligned; + __u32 rs_discard_granularity; + unsigned char disable_write_same; +}; + +struct res_opts { + char cpu_mask[DRBD_CPU_MASK_SIZE]; + __u32 cpu_mask_len; + __u32 on_no_data; +}; + +struct net_conf { + char shared_secret[SHARED_SECRET_MAX]; + __u32 shared_secret_len; + char cram_hmac_alg[SHARED_SECRET_MAX]; + __u32 cram_hmac_alg_len; + char integrity_alg[SHARED_SECRET_MAX]; + __u32 integrity_alg_len; + char verify_alg[SHARED_SECRET_MAX]; + __u32 verify_alg_len; + char csums_alg[SHARED_SECRET_MAX]; + __u32 csums_alg_len; + __u32 wire_protocol; + __u32 connect_int; + __u32 timeout; + __u32 ping_int; + __u32 ping_timeo; + __u32 sndbuf_size; + __u32 rcvbuf_size; + __u32 ko_count; + __u32 max_buffers; + __u32 max_epoch_size; + __u32 unplug_watermark; + __u32 after_sb_0p; + __u32 after_sb_1p; + __u32 after_sb_2p; + __u32 rr_conflict; + __u32 on_congestion; + __u32 cong_fill; + __u32 cong_extents; + unsigned char two_primaries; + unsigned char discard_my_data; + unsigned char tcp_cork; + unsigned char always_asbp; + unsigned char tentative; + unsigned char use_rle; + unsigned char csums_after_crash_only; + __u32 sock_check_timeo; +}; + +struct set_role_parms { + unsigned char assume_uptodate; +}; + +struct resize_parms { + __u64 resize_size; + unsigned char resize_force; + unsigned char no_resync; + __u32 al_stripes; + __u32 al_stripe_size; +}; + +struct state_info { + __u32 sib_reason; + __u32 current_state; + __u64 capacity; + __u64 ed_uuid; + __u32 prev_state; + __u32 new_state; + char uuids[DRBD_NL_UUIDS_SIZE]; + __u32 uuids_len; + __u32 disk_flags; + __u64 bits_total; + __u64 bits_oos; + __u64 bits_rs_total; + __u64 bits_rs_failed; + char helper[32]; + __u32 helper_len; + __u32 helper_exit_code; + __u64 send_cnt; + __u64 recv_cnt; + __u64 read_cnt; + __u64 writ_cnt; + __u64 al_writ_cnt; + __u64 bm_writ_cnt; + __u32 ap_bio_cnt; + __u32 ap_pending_cnt; + __u32 rs_pending_cnt; +}; + +struct start_ov_parms { + __u64 ov_start_sector; + __u64 ov_stop_sector; +}; + +struct new_c_uuid_parms { + unsigned char clear_bm; +}; + +struct timeout_parms { + __u32 timeout_type; +}; + +struct disconnect_parms { + unsigned char force_disconnect; +}; + +struct detach_parms { + unsigned char force_detach; +}; + +struct resource_info { + __u32 res_role; + unsigned char res_susp; + unsigned char res_susp_nod; + unsigned char res_susp_fen; +}; + +struct device_info { + __u32 dev_disk_state; +}; + +struct connection_info { + __u32 conn_connection_state; + __u32 conn_role; +}; + +struct peer_device_info { + __u32 peer_repl_state; + __u32 peer_disk_state; + __u32 peer_resync_susp_user; + __u32 peer_resync_susp_peer; + __u32 peer_resync_susp_dependency; +}; + +struct resource_statistics { + __u32 res_stat_write_ordering; +}; + +struct device_statistics { + __u64 dev_size; + __u64 dev_read; + __u64 dev_write; + __u64 dev_al_writes; + __u64 dev_bm_writes; + __u32 dev_upper_pending; + __u32 dev_lower_pending; + unsigned char dev_upper_blocked; + unsigned char dev_lower_blocked; + unsigned char dev_al_suspended; + __u64 dev_exposed_data_uuid; + __u64 dev_current_uuid; + __u32 dev_disk_flags; + char history_uuids[DRBD_NL_HISTORY_UUIDS_SIZE]; + __u32 history_uuids_len; +}; + +struct connection_statistics { + unsigned char conn_congested; +}; + +struct peer_device_statistics { + __u64 peer_dev_received; + __u64 peer_dev_sent; + __u32 peer_dev_pending; + __u32 peer_dev_unacked; + __u64 peer_dev_out_of_sync; + __u64 peer_dev_resync_failed; + __u64 peer_dev_bitmap_uuid; + __u32 peer_dev_flags; +}; + +struct drbd_notification_header { + __u32 nh_type; +}; + +struct drbd_helper_info { + char helper_name[32]; + __u32 helper_name_len; + __u32 helper_status; +}; + +int drbd_cfg_reply_to_skb(struct sk_buff *skb, struct drbd_cfg_reply *s); + +int drbd_cfg_context_from_attrs(struct drbd_cfg_context *s, struct genl_info *info); +int drbd_cfg_context_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int drbd_cfg_context_to_skb(struct sk_buff *skb, struct drbd_cfg_context *s); + +int disk_conf_from_attrs(struct disk_conf *s, struct genl_info *info); +int disk_conf_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int disk_conf_to_skb(struct sk_buff *skb, struct disk_conf *s); +void set_disk_conf_defaults(struct disk_conf *x); + +int res_opts_from_attrs(struct res_opts *s, struct genl_info *info); +int res_opts_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int res_opts_to_skb(struct sk_buff *skb, struct res_opts *s); +void set_res_opts_defaults(struct res_opts *x); + +int net_conf_from_attrs(struct net_conf *s, struct genl_info *info); +int net_conf_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int net_conf_to_skb(struct sk_buff *skb, struct net_conf *s); +void set_net_conf_defaults(struct net_conf *x); + +int set_role_parms_from_attrs(struct set_role_parms *s, struct genl_info *info); +int set_role_parms_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int set_role_parms_to_skb(struct sk_buff *skb, struct set_role_parms *s); + +int resize_parms_from_attrs(struct resize_parms *s, struct genl_info *info); +int resize_parms_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int resize_parms_to_skb(struct sk_buff *skb, struct resize_parms *s); +void set_resize_parms_defaults(struct resize_parms *x); + +int state_info_to_skb(struct sk_buff *skb, struct state_info *s); + +int start_ov_parms_from_attrs(struct start_ov_parms *s, struct genl_info *info); +int start_ov_parms_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int start_ov_parms_to_skb(struct sk_buff *skb, struct start_ov_parms *s); + +int new_c_uuid_parms_from_attrs(struct new_c_uuid_parms *s, struct genl_info *info); +int new_c_uuid_parms_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int new_c_uuid_parms_to_skb(struct sk_buff *skb, struct new_c_uuid_parms *s); + +int timeout_parms_to_skb(struct sk_buff *skb, struct timeout_parms *s); + +int disconnect_parms_from_attrs(struct disconnect_parms *s, struct genl_info *info); +int disconnect_parms_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int disconnect_parms_to_skb(struct sk_buff *skb, struct disconnect_parms *s); + +int detach_parms_from_attrs(struct detach_parms *s, struct genl_info *info); +int detach_parms_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int detach_parms_to_skb(struct sk_buff *skb, struct detach_parms *s); + +int resource_info_from_attrs(struct resource_info *s, struct genl_info *info); +int resource_info_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int resource_info_to_skb(struct sk_buff *skb, struct resource_info *s); + +int device_info_from_attrs(struct device_info *s, struct genl_info *info); +int device_info_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int device_info_to_skb(struct sk_buff *skb, struct device_info *s); + +int connection_info_from_attrs(struct connection_info *s, struct genl_info *info); +int connection_info_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int connection_info_to_skb(struct sk_buff *skb, struct connection_info *s); + +int peer_device_info_from_attrs(struct peer_device_info *s, struct genl_info *info); +int peer_device_info_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int peer_device_info_to_skb(struct sk_buff *skb, struct peer_device_info *s); + +int resource_statistics_from_attrs(struct resource_statistics *s, struct genl_info *info); +int resource_statistics_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int resource_statistics_to_skb(struct sk_buff *skb, struct resource_statistics *s); + +int device_statistics_from_attrs(struct device_statistics *s, struct genl_info *info); +int device_statistics_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int device_statistics_to_skb(struct sk_buff *skb, struct device_statistics *s); + +int connection_statistics_from_attrs(struct connection_statistics *s, struct genl_info *info); +int connection_statistics_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int connection_statistics_to_skb(struct sk_buff *skb, struct connection_statistics *s); + +int peer_device_statistics_from_attrs(struct peer_device_statistics *s, struct genl_info *info); +int peer_device_statistics_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info); +int peer_device_statistics_to_skb(struct sk_buff *skb, struct peer_device_statistics *s); + +int drbd_notification_header_to_skb(struct sk_buff *skb, struct drbd_notification_header *s); + +int drbd_helper_info_to_skb(struct sk_buff *skb, struct drbd_helper_info *s); + +#endif /* _LINUX_DRBD_GEN_H */ diff --git a/drivers/block/drbd/drbd_proc.c b/drivers/block/drbd/drbd_proc.c index 1d0feafceadc..6d0c12c10260 100644 --- a/drivers/block/drbd/drbd_proc.c +++ b/drivers/block/drbd/drbd_proc.c @@ -228,7 +228,7 @@ int drbd_seq_show(struct seq_file *seq, void *v) }; seq_printf(seq, "version: " REL_VERSION " (api:%d/proto:%d-%d)\n%s\n", - GENL_MAGIC_VERSION, PRO_VERSION_MIN, PRO_VERSION_MAX, drbd_buildtag()); + DRBD_FAMILY_VERSION, PRO_VERSION_MIN, PRO_VERSION_MAX, drbd_buildtag()); /* cs .. connection state diff --git a/include/linux/drbd_genl.h b/include/linux/drbd_genl.h deleted file mode 100644 index f53c534aba0c..000000000000 --- a/include/linux/drbd_genl.h +++ /dev/null @@ -1,536 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * General overview: - * full generic netlink message: - * |nlmsghdr|genlmsghdr| - * - * payload: - * |optional fixed size family header| - * - * sequence of netlink attributes: - * I chose to have all "top level" attributes NLA_NESTED, - * corresponding to some real struct. - * So we have a sequence of |tla, len| - * - * nested nla sequence: - * may be empty, or contain a sequence of netlink attributes - * representing the struct fields. - * - * The tag number of any field (regardless of containing struct) - * will be available as T_ ## field_name, - * so you cannot have the same field name in two differnt structs. - * - * The tag numbers themselves are per struct, though, - * so should always begin at 1 (not 0, that is the special "NLA_UNSPEC" type, - * which we won't use here). - * The tag numbers are used as index in the respective nla_policy array. - * - * GENL_struct(tag_name, tag_number, struct name, struct fields) - struct and policy - * genl_magic_struct.h - * generates the struct declaration, - * generates an entry in the tla enum, - * genl_magic_func.h - * generates an entry in the static tla policy - * with .type = NLA_NESTED - * generates the static _nl_policy definition, - * and static conversion functions - * - * genl_magic_func.h - * - * GENL_mc_group(group) - * genl_magic_struct.h - * does nothing - * genl_magic_func.h - * defines and registers the mcast group, - * and provides a send helper - * - * GENL_notification(op_name, op_num, mcast_group, tla list) - * These are notifications to userspace. - * - * genl_magic_struct.h - * generates an entry in the genl_ops enum, - * genl_magic_func.h - * does nothing - * - * mcast group: the name of the mcast group this notification should be - * expected on - * tla list: the list of expected top level attributes, - * for documentation and sanity checking. - * - * GENL_op(op_name, op_num, flags and handler, tla list) - "genl operations" - * These are requests from userspace. - * - * _op and _notification share the same "number space", - * op_nr will be assigned to "genlmsghdr->cmd" - * - * genl_magic_struct.h - * generates an entry in the genl_ops enum, - * genl_magic_func.h - * generates an entry in the static genl_ops array, - * and static register/unregister functions to - * genl_register_family(). - * - * flags and handler: - * GENL_op_init( .doit = x, .dumpit = y, .flags = something) - * GENL_doit(x) => .dumpit = NULL, .flags = GENL_ADMIN_PERM - * tla list: the list of expected top level attributes, - * for documentation and sanity checking. - */ - -/* - * STRUCTS - */ - -/* this is sent kernel -> userland on various error conditions, and contains - * informational textual info, which is supposedly human readable. - * The computer relevant return code is in the drbd_genlmsghdr. - */ -GENL_struct(DRBD_NLA_CFG_REPLY, 1, drbd_cfg_reply, - /* "arbitrary" size strings, nla_policy.len = 0 */ - __str_field(1, 0, info_text, 0) -) - -/* Configuration requests typically need a context to operate on. - * Possible keys are device minor (fits in the drbd_genlmsghdr), - * the replication link (aka connection) name, - * and/or the replication group (aka resource) name, - * and the volume id within the resource. */ -GENL_struct(DRBD_NLA_CFG_CONTEXT, 2, drbd_cfg_context, - __u32_field(1, 0, ctx_volume) - __str_field(2, 0, ctx_resource_name, 128) - __bin_field(3, 0, ctx_my_addr, 128) - __bin_field(4, 0, ctx_peer_addr, 128) -) - -GENL_struct(DRBD_NLA_DISK_CONF, 3, disk_conf, - __str_field(1, DRBD_F_REQUIRED | DRBD_F_INVARIANT, backing_dev, 128) - __str_field(2, DRBD_F_REQUIRED | DRBD_F_INVARIANT, meta_dev, 128) - __s32_field(3, DRBD_F_REQUIRED | DRBD_F_INVARIANT, meta_dev_idx) - - /* use the resize command to try and change the disk_size */ - __u64_field(4, DRBD_F_INVARIANT, disk_size) - /* we could change the max_bio_bvecs, - * but it won't propagate through the stack */ - __u32_field(5, DRBD_F_INVARIANT, max_bio_bvecs) - - __u32_field_def(6, 0, on_io_error, DRBD_ON_IO_ERROR_DEF) - __u32_field_def(7, 0, fencing, DRBD_FENCING_DEF) - - __u32_field_def(8, 0, resync_rate, DRBD_RESYNC_RATE_DEF) - __s32_field_def(9, 0, resync_after, DRBD_MINOR_NUMBER_DEF) - __u32_field_def(10, 0, al_extents, DRBD_AL_EXTENTS_DEF) - __u32_field_def(11, 0, c_plan_ahead, DRBD_C_PLAN_AHEAD_DEF) - __u32_field_def(12, 0, c_delay_target, DRBD_C_DELAY_TARGET_DEF) - __u32_field_def(13, 0, c_fill_target, DRBD_C_FILL_TARGET_DEF) - __u32_field_def(14, 0, c_max_rate, DRBD_C_MAX_RATE_DEF) - __u32_field_def(15, 0, c_min_rate, DRBD_C_MIN_RATE_DEF) - __u32_field_def(20, 0, disk_timeout, DRBD_DISK_TIMEOUT_DEF) - __u32_field_def(21, 0 /* OPTIONAL */, read_balancing, DRBD_READ_BALANCING_DEF) - __u32_field_def(25, 0 /* OPTIONAL */, rs_discard_granularity, DRBD_RS_DISCARD_GRANULARITY_DEF) - - __flg_field_def(16, 0, disk_barrier, DRBD_DISK_BARRIER_DEF) - __flg_field_def(17, 0, disk_flushes, DRBD_DISK_FLUSHES_DEF) - __flg_field_def(18, 0, disk_drain, DRBD_DISK_DRAIN_DEF) - __flg_field_def(19, 0, md_flushes, DRBD_MD_FLUSHES_DEF) - __flg_field_def(23, 0 /* OPTIONAL */, al_updates, DRBD_AL_UPDATES_DEF) - __flg_field_def(24, 0 /* OPTIONAL */, discard_zeroes_if_aligned, DRBD_DISCARD_ZEROES_IF_ALIGNED_DEF) - __flg_field_def(26, 0 /* OPTIONAL */, disable_write_same, DRBD_DISABLE_WRITE_SAME_DEF) -) - -GENL_struct(DRBD_NLA_RESOURCE_OPTS, 4, res_opts, - __str_field_def(1, 0, cpu_mask, DRBD_CPU_MASK_SIZE) - __u32_field_def(2, 0, on_no_data, DRBD_ON_NO_DATA_DEF) -) - -GENL_struct(DRBD_NLA_NET_CONF, 5, net_conf, - __str_field_def(1, DRBD_F_SENSITIVE, - shared_secret, SHARED_SECRET_MAX) - __str_field_def(2, 0, cram_hmac_alg, SHARED_SECRET_MAX) - __str_field_def(3, 0, integrity_alg, SHARED_SECRET_MAX) - __str_field_def(4, 0, verify_alg, SHARED_SECRET_MAX) - __str_field_def(5, 0, csums_alg, SHARED_SECRET_MAX) - __u32_field_def(6, 0, wire_protocol, DRBD_PROTOCOL_DEF) - __u32_field_def(7, 0, connect_int, DRBD_CONNECT_INT_DEF) - __u32_field_def(8, 0, timeout, DRBD_TIMEOUT_DEF) - __u32_field_def(9, 0, ping_int, DRBD_PING_INT_DEF) - __u32_field_def(10, 0, ping_timeo, DRBD_PING_TIMEO_DEF) - __u32_field_def(11, 0, sndbuf_size, DRBD_SNDBUF_SIZE_DEF) - __u32_field_def(12, 0, rcvbuf_size, DRBD_RCVBUF_SIZE_DEF) - __u32_field_def(13, 0, ko_count, DRBD_KO_COUNT_DEF) - __u32_field_def(14, 0, max_buffers, DRBD_MAX_BUFFERS_DEF) - __u32_field_def(15, 0, max_epoch_size, DRBD_MAX_EPOCH_SIZE_DEF) - __u32_field_def(16, 0, unplug_watermark, DRBD_UNPLUG_WATERMARK_DEF) - __u32_field_def(17, 0, after_sb_0p, DRBD_AFTER_SB_0P_DEF) - __u32_field_def(18, 0, after_sb_1p, DRBD_AFTER_SB_1P_DEF) - __u32_field_def(19, 0, after_sb_2p, DRBD_AFTER_SB_2P_DEF) - __u32_field_def(20, 0, rr_conflict, DRBD_RR_CONFLICT_DEF) - __u32_field_def(21, 0, on_congestion, DRBD_ON_CONGESTION_DEF) - __u32_field_def(22, 0, cong_fill, DRBD_CONG_FILL_DEF) - __u32_field_def(23, 0, cong_extents, DRBD_CONG_EXTENTS_DEF) - __flg_field_def(24, 0, two_primaries, DRBD_ALLOW_TWO_PRIMARIES_DEF) - __flg_field(25, DRBD_F_INVARIANT, discard_my_data) - __flg_field_def(26, 0, tcp_cork, DRBD_TCP_CORK_DEF) - __flg_field_def(27, 0, always_asbp, DRBD_ALWAYS_ASBP_DEF) - __flg_field(28, DRBD_F_INVARIANT, tentative) - __flg_field_def(29, 0, use_rle, DRBD_USE_RLE_DEF) - /* 9: __u32_field_def(30, 0, fencing_policy, DRBD_FENCING_DEF) */ - /* 9: __str_field_def(31, 0, name, SHARED_SECRET_MAX) */ - /* 9: __u32_field(32, DRBD_F_REQUIRED | DRBD_F_INVARIANT, peer_node_id) */ - __flg_field_def(33, 0 /* OPTIONAL */, csums_after_crash_only, DRBD_CSUMS_AFTER_CRASH_ONLY_DEF) - __u32_field_def(34, 0 /* OPTIONAL */, sock_check_timeo, DRBD_SOCKET_CHECK_TIMEO_DEF) -) - -GENL_struct(DRBD_NLA_SET_ROLE_PARMS, 6, set_role_parms, - __flg_field(1, 0, assume_uptodate) -) - -GENL_struct(DRBD_NLA_RESIZE_PARMS, 7, resize_parms, - __u64_field(1, 0, resize_size) - __flg_field(2, 0, resize_force) - __flg_field(3, 0, no_resync) - __u32_field_def(4, 0 /* OPTIONAL */, al_stripes, DRBD_AL_STRIPES_DEF) - __u32_field_def(5, 0 /* OPTIONAL */, al_stripe_size, DRBD_AL_STRIPE_SIZE_DEF) -) - -GENL_struct(DRBD_NLA_STATE_INFO, 8, state_info, - /* the reason of the broadcast, - * if this is an event triggered broadcast. */ - __u32_field(1, 0, sib_reason) - __u32_field(2, DRBD_F_REQUIRED, current_state) - __u64_field(3, 0, capacity) - __u64_field(4, 0, ed_uuid) - - /* These are for broadcast from after state change work. - * prev_state and new_state are from the moment the state change took - * place, new_state is not neccessarily the same as current_state, - * there may have been more state changes since. Which will be - * broadcasted soon, in their respective after state change work. */ - __u32_field(5, 0, prev_state) - __u32_field(6, 0, new_state) - - /* if we have a local disk: */ - __bin_field(7, 0, uuids, (UI_SIZE*sizeof(__u64))) - __u32_field(8, 0, disk_flags) - __u64_field(9, 0, bits_total) - __u64_field(10, 0, bits_oos) - /* and in case resync or online verify is active */ - __u64_field(11, 0, bits_rs_total) - __u64_field(12, 0, bits_rs_failed) - - /* for pre and post notifications of helper execution */ - __str_field(13, 0, helper, 32) - __u32_field(14, 0, helper_exit_code) - - __u64_field(15, 0, send_cnt) - __u64_field(16, 0, recv_cnt) - __u64_field(17, 0, read_cnt) - __u64_field(18, 0, writ_cnt) - __u64_field(19, 0, al_writ_cnt) - __u64_field(20, 0, bm_writ_cnt) - __u32_field(21, 0, ap_bio_cnt) - __u32_field(22, 0, ap_pending_cnt) - __u32_field(23, 0, rs_pending_cnt) -) - -GENL_struct(DRBD_NLA_START_OV_PARMS, 9, start_ov_parms, - __u64_field(1, 0, ov_start_sector) - __u64_field(2, 0, ov_stop_sector) -) - -GENL_struct(DRBD_NLA_NEW_C_UUID_PARMS, 10, new_c_uuid_parms, - __flg_field(1, 0, clear_bm) -) - -GENL_struct(DRBD_NLA_TIMEOUT_PARMS, 11, timeout_parms, - __u32_field(1, DRBD_F_REQUIRED, timeout_type) -) - -GENL_struct(DRBD_NLA_DISCONNECT_PARMS, 12, disconnect_parms, - __flg_field(1, 0, force_disconnect) -) - -GENL_struct(DRBD_NLA_DETACH_PARMS, 13, detach_parms, - __flg_field(1, 0, force_detach) -) - -GENL_struct(DRBD_NLA_RESOURCE_INFO, 15, resource_info, - __u32_field(1, 0, res_role) - __flg_field(2, 0, res_susp) - __flg_field(3, 0, res_susp_nod) - __flg_field(4, 0, res_susp_fen) - /* __flg_field(5, 0, res_weak) */ -) - -GENL_struct(DRBD_NLA_DEVICE_INFO, 16, device_info, - __u32_field(1, 0, dev_disk_state) -) - -GENL_struct(DRBD_NLA_CONNECTION_INFO, 17, connection_info, - __u32_field(1, 0, conn_connection_state) - __u32_field(2, 0, conn_role) -) - -GENL_struct(DRBD_NLA_PEER_DEVICE_INFO, 18, peer_device_info, - __u32_field(1, 0, peer_repl_state) - __u32_field(2, 0, peer_disk_state) - __u32_field(3, 0, peer_resync_susp_user) - __u32_field(4, 0, peer_resync_susp_peer) - __u32_field(5, 0, peer_resync_susp_dependency) -) - -GENL_struct(DRBD_NLA_RESOURCE_STATISTICS, 19, resource_statistics, - __u32_field(1, 0, res_stat_write_ordering) -) - -GENL_struct(DRBD_NLA_DEVICE_STATISTICS, 20, device_statistics, - __u64_field(1, 0, dev_size) /* (sectors) */ - __u64_field(2, 0, dev_read) /* (sectors) */ - __u64_field(3, 0, dev_write) /* (sectors) */ - __u64_field(4, 0, dev_al_writes) /* activity log writes (count) */ - __u64_field(5, 0, dev_bm_writes) /* bitmap writes (count) */ - __u32_field(6, 0, dev_upper_pending) /* application requests in progress */ - __u32_field(7, 0, dev_lower_pending) /* backing device requests in progress */ - __flg_field(8, 0, dev_upper_blocked) - __flg_field(9, 0, dev_lower_blocked) - __flg_field(10, 0, dev_al_suspended) /* activity log suspended */ - __u64_field(11, 0, dev_exposed_data_uuid) - __u64_field(12, 0, dev_current_uuid) - __u32_field(13, 0, dev_disk_flags) - __bin_field(14, 0, history_uuids, HISTORY_UUIDS * sizeof(__u64)) -) - -GENL_struct(DRBD_NLA_CONNECTION_STATISTICS, 21, connection_statistics, - __flg_field(1, 0, conn_congested) -) - -GENL_struct(DRBD_NLA_PEER_DEVICE_STATISTICS, 22, peer_device_statistics, - __u64_field(1, 0, peer_dev_received) /* sectors */ - __u64_field(2, 0, peer_dev_sent) /* sectors */ - __u32_field(3, 0, peer_dev_pending) /* number of requests */ - __u32_field(4, 0, peer_dev_unacked) /* number of requests */ - __u64_field(5, 0, peer_dev_out_of_sync) /* sectors */ - __u64_field(6, 0, peer_dev_resync_failed) /* sectors */ - __u64_field(7, 0, peer_dev_bitmap_uuid) - __u32_field(9, 0, peer_dev_flags) -) - -GENL_struct(DRBD_NLA_NOTIFICATION_HEADER, 23, drbd_notification_header, - __u32_field(1, 0, nh_type) -) - -GENL_struct(DRBD_NLA_HELPER, 24, drbd_helper_info, - __str_field(1, 0, helper_name, 32) - __u32_field(2, 0, helper_status) -) - -/* - * Notifications and commands (genlmsghdr->cmd) - */ -GENL_mc_group(events) - - /* kernel -> userspace announcement of changes */ -GENL_notification( - DRBD_EVENT, 1, events, - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_STATE_INFO, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NET_CONF, 0) - GENL_tla_expected(DRBD_NLA_DISK_CONF, 0) - GENL_tla_expected(DRBD_NLA_SYNCER_CONF, 0) -) - - /* query kernel for specific or all info */ -GENL_op( - DRBD_ADM_GET_STATUS, 2, - GENL_op_init( - .doit = drbd_adm_get_status, - .dumpit = drbd_adm_get_status_all, - /* anyone may ask for the status, - * it is broadcasted anyways */ - ), - /* To select the object .doit. - * Or a subset of objects in .dumpit. */ - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, 0) -) - - /* add DRBD minor devices as volumes to resources */ -GENL_op(DRBD_ADM_NEW_MINOR, 5, GENL_doit(drbd_adm_new_minor), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_DEL_MINOR, 6, GENL_doit(drbd_adm_del_minor), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) - - /* add or delete resources */ -GENL_op(DRBD_ADM_NEW_RESOURCE, 7, GENL_doit(drbd_adm_new_resource), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_DEL_RESOURCE, 8, GENL_doit(drbd_adm_del_resource), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) - -GENL_op(DRBD_ADM_RESOURCE_OPTS, 9, - GENL_doit(drbd_adm_resource_opts), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_RESOURCE_OPTS, 0) -) - -GENL_op( - DRBD_ADM_CONNECT, 10, - GENL_doit(drbd_adm_connect), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NET_CONF, DRBD_F_REQUIRED) -) - -GENL_op( - DRBD_ADM_CHG_NET_OPTS, 29, - GENL_doit(drbd_adm_net_opts), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NET_CONF, DRBD_F_REQUIRED) -) - -GENL_op(DRBD_ADM_DISCONNECT, 11, GENL_doit(drbd_adm_disconnect), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) - -GENL_op(DRBD_ADM_ATTACH, 12, - GENL_doit(drbd_adm_attach), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_DISK_CONF, DRBD_F_REQUIRED) -) - -GENL_op(DRBD_ADM_CHG_DISK_OPTS, 28, - GENL_doit(drbd_adm_disk_opts), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_DISK_OPTS, DRBD_F_REQUIRED) -) - -GENL_op( - DRBD_ADM_RESIZE, 13, - GENL_doit(drbd_adm_resize), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_RESIZE_PARMS, 0) -) - -GENL_op( - DRBD_ADM_PRIMARY, 14, - GENL_doit(drbd_adm_set_role), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_SET_ROLE_PARMS, DRBD_F_REQUIRED) -) - -GENL_op( - DRBD_ADM_SECONDARY, 15, - GENL_doit(drbd_adm_set_role), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_SET_ROLE_PARMS, DRBD_F_REQUIRED) -) - -GENL_op( - DRBD_ADM_NEW_C_UUID, 16, - GENL_doit(drbd_adm_new_c_uuid), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NEW_C_UUID_PARMS, 0) -) - -GENL_op( - DRBD_ADM_START_OV, 17, - GENL_doit(drbd_adm_start_ov), - GENL_tla_expected(DRBD_NLA_START_OV_PARMS, 0) -) - -GENL_op(DRBD_ADM_DETACH, 18, GENL_doit(drbd_adm_detach), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_DETACH_PARMS, 0)) - -GENL_op(DRBD_ADM_INVALIDATE, 19, GENL_doit(drbd_adm_invalidate), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_INVAL_PEER, 20, GENL_doit(drbd_adm_invalidate_peer), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_PAUSE_SYNC, 21, GENL_doit(drbd_adm_pause_sync), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_RESUME_SYNC, 22, GENL_doit(drbd_adm_resume_sync), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_SUSPEND_IO, 23, GENL_doit(drbd_adm_suspend_io), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_RESUME_IO, 24, GENL_doit(drbd_adm_resume_io), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_OUTDATE, 25, GENL_doit(drbd_adm_outdate), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_GET_TIMEOUT_TYPE, 26, GENL_doit(drbd_adm_get_timeout_type), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) -GENL_op(DRBD_ADM_DOWN, 27, GENL_doit(drbd_adm_down), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED)) - -GENL_op(DRBD_ADM_GET_RESOURCES, 30, - GENL_op_init( - .dumpit = drbd_adm_dump_resources, - ), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, 0) - GENL_tla_expected(DRBD_NLA_RESOURCE_INFO, 0) - GENL_tla_expected(DRBD_NLA_RESOURCE_STATISTICS, 0)) - -GENL_op(DRBD_ADM_GET_DEVICES, 31, - GENL_op_init( - .dumpit = drbd_adm_dump_devices, - .done = drbd_adm_dump_devices_done, - ), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, 0) - GENL_tla_expected(DRBD_NLA_DEVICE_INFO, 0) - GENL_tla_expected(DRBD_NLA_DEVICE_STATISTICS, 0)) - -GENL_op(DRBD_ADM_GET_CONNECTIONS, 32, - GENL_op_init( - .dumpit = drbd_adm_dump_connections, - .done = drbd_adm_dump_connections_done, - ), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, 0) - GENL_tla_expected(DRBD_NLA_CONNECTION_INFO, 0) - GENL_tla_expected(DRBD_NLA_CONNECTION_STATISTICS, 0)) - -GENL_op(DRBD_ADM_GET_PEER_DEVICES, 33, - GENL_op_init( - .dumpit = drbd_adm_dump_peer_devices, - .done = drbd_adm_dump_peer_devices_done, - ), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, 0) - GENL_tla_expected(DRBD_NLA_PEER_DEVICE_INFO, 0) - GENL_tla_expected(DRBD_NLA_PEER_DEVICE_STATISTICS, 0)) - -GENL_notification( - DRBD_RESOURCE_STATE, 34, events, - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NOTIFICATION_HEADER, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_RESOURCE_INFO, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_RESOURCE_STATISTICS, DRBD_F_REQUIRED)) - -GENL_notification( - DRBD_DEVICE_STATE, 35, events, - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NOTIFICATION_HEADER, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_DEVICE_INFO, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_DEVICE_STATISTICS, DRBD_F_REQUIRED)) - -GENL_notification( - DRBD_CONNECTION_STATE, 36, events, - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NOTIFICATION_HEADER, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_CONNECTION_INFO, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_CONNECTION_STATISTICS, DRBD_F_REQUIRED)) - -GENL_notification( - DRBD_PEER_DEVICE_STATE, 37, events, - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_NOTIFICATION_HEADER, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_PEER_DEVICE_INFO, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_PEER_DEVICE_STATISTICS, DRBD_F_REQUIRED)) - -GENL_op( - DRBD_ADM_GET_INITIAL_STATE, 38, - GENL_op_init( - .dumpit = drbd_adm_get_initial_state, - ), - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, 0)) - -GENL_notification( - DRBD_HELPER, 40, events, - GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, DRBD_F_REQUIRED) - GENL_tla_expected(DRBD_NLA_HELPER, DRBD_F_REQUIRED)) - -GENL_notification( - DRBD_INITIAL_STATE_DONE, 41, events, - GENL_tla_expected(DRBD_NLA_NOTIFICATION_HEADER, DRBD_F_REQUIRED)) diff --git a/include/linux/drbd_genl_api.h b/include/linux/drbd_genl_api.h deleted file mode 100644 index 19d263924852..000000000000 --- a/include/linux/drbd_genl_api.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef DRBD_GENL_STRUCT_H -#define DRBD_GENL_STRUCT_H - -/* hack around predefined gcc/cpp "linux=1", - * we cannot possibly include <1/drbd_genl.h> */ -#undef linux - -#include -#define GENL_MAGIC_VERSION 1 -#define GENL_MAGIC_FAMILY drbd -#define GENL_MAGIC_FAMILY_HDRSZ sizeof(struct drbd_genlmsghdr) -#define GENL_MAGIC_INCLUDE_FILE -#include - -#endif diff --git a/include/linux/genl_magic_func.h b/include/linux/genl_magic_func.h deleted file mode 100644 index a7d36c9ea924..000000000000 --- a/include/linux/genl_magic_func.h +++ /dev/null @@ -1,413 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef GENL_MAGIC_FUNC_H -#define GENL_MAGIC_FUNC_H - -#include -#include -#include - -/* - * Magic: declare tla policy {{{1 - * Magic: declare nested policies - * {{{2 - */ -#undef GENL_mc_group -#define GENL_mc_group(group) - -#undef GENL_notification -#define GENL_notification(op_name, op_num, mcast_group, tla_list) - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, tla_list) - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ - [tag_name] = { .type = NLA_NESTED }, - -static struct nla_policy CONCATENATE(GENL_MAGIC_FAMILY, _tla_nl_policy)[] = { -#include GENL_MAGIC_INCLUDE_FILE -}; - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -static struct nla_policy s_name ## _nl_policy[] __read_mostly = \ -{ s_fields }; - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, _type, __get, \ - __put, __is_signed) \ - [attr_nr] = { .type = nla_type }, - -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, _type, maxlen, \ - __get, __put, __is_signed) \ - [attr_nr] = { .type = nla_type, \ - .len = maxlen - (nla_type == NLA_NUL_STRING) }, - -#include GENL_MAGIC_INCLUDE_FILE - -#ifndef __KERNEL__ -#ifndef pr_info -#define pr_info(args...) fprintf(stderr, args); -#endif -#endif - -#ifdef GENL_MAGIC_DEBUG -static void dprint_field(const char *dir, int nla_type, - const char *name, void *valp) -{ - __u64 val = valp ? *(__u32 *)valp : 1; - switch (nla_type) { - case NLA_U8: val = (__u8)val; - case NLA_U16: val = (__u16)val; - case NLA_U32: val = (__u32)val; - pr_info("%s attr %s: %d 0x%08x\n", dir, - name, (int)val, (unsigned)val); - break; - case NLA_U64: - val = *(__u64*)valp; - pr_info("%s attr %s: %lld 0x%08llx\n", dir, - name, (long long)val, (unsigned long long)val); - break; - case NLA_FLAG: - if (val) - pr_info("%s attr %s: set\n", dir, name); - break; - } -} - -static void dprint_array(const char *dir, int nla_type, - const char *name, const char *val, unsigned len) -{ - switch (nla_type) { - case NLA_NUL_STRING: - if (len && val[len-1] == '\0') - len--; - pr_info("%s attr %s: [len:%u] '%s'\n", dir, name, len, val); - break; - default: - /* we can always show 4 byte, - * thats what nlattr are aligned to. */ - pr_info("%s attr %s: [len:%u] %02x%02x%02x%02x ...\n", - dir, name, len, val[0], val[1], val[2], val[3]); - } -} - -#define DPRINT_TLA(a, op, b) pr_info("%s %s %s\n", a, op, b); - -/* Name is a member field name of the struct s. - * If s is NULL (only parsing, no copy requested in *_from_attrs()), - * nla is supposed to point to the attribute containing the information - * corresponding to that struct member. */ -#define DPRINT_FIELD(dir, nla_type, name, s, nla) \ - do { \ - if (s) \ - dprint_field(dir, nla_type, #name, &s->name); \ - else if (nla) \ - dprint_field(dir, nla_type, #name, \ - (nla_type == NLA_FLAG) ? NULL \ - : nla_data(nla)); \ - } while (0) - -#define DPRINT_ARRAY(dir, nla_type, name, s, nla) \ - do { \ - if (s) \ - dprint_array(dir, nla_type, #name, \ - s->name, s->name ## _len); \ - else if (nla) \ - dprint_array(dir, nla_type, #name, \ - nla_data(nla), nla_len(nla)); \ - } while (0) -#else -#define DPRINT_TLA(a, op, b) do {} while (0) -#define DPRINT_FIELD(dir, nla_type, name, s, nla) do {} while (0) -#define DPRINT_ARRAY(dir, nla_type, name, s, nla) do {} while (0) -#endif - -/* - * Magic: provide conversion functions {{{1 - * populate struct from attribute table: - * {{{2 - */ - -/* processing of generic netlink messages is serialized. - * use one static buffer for parsing of nested attributes */ -static struct nlattr *nested_attr_tb[128]; - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -/* *_from_attrs functions are static, but potentially unused */ \ -static int __ ## s_name ## _from_attrs(struct s_name *s, \ - struct genl_info *info, bool exclude_invariants) \ -{ \ - const int maxtype = ARRAY_SIZE(s_name ## _nl_policy)-1; \ - struct nlattr *tla = info->attrs[tag_number]; \ - struct nlattr **ntb = nested_attr_tb; \ - struct nlattr *nla; \ - int err; \ - BUILD_BUG_ON(ARRAY_SIZE(s_name ## _nl_policy) > ARRAY_SIZE(nested_attr_tb)); \ - if (!tla) \ - return -ENOMSG; \ - DPRINT_TLA(#s_name, "<=-", #tag_name); \ - err = nla_parse_nested_deprecated(ntb, maxtype, tla, \ - s_name ## _nl_policy, NULL); \ - if (err) \ - return err; \ - \ - s_fields \ - return 0; \ -} __attribute__((unused)) \ -static int s_name ## _from_attrs(struct s_name *s, \ - struct genl_info *info) \ -{ \ - return __ ## s_name ## _from_attrs(s, info, false); \ -} __attribute__((unused)) \ -static int s_name ## _from_attrs_for_change(struct s_name *s, \ - struct genl_info *info) \ -{ \ - return __ ## s_name ## _from_attrs(s, info, true); \ -} __attribute__((unused)) \ - -#define __assign(attr_nr, attr_flag, name, nla_type, type, assignment...) \ - nla = ntb[attr_nr]; \ - if (nla) { \ - if (exclude_invariants && !!((attr_flag) & DRBD_F_INVARIANT)) { \ - pr_info("<< must not change invariant attr: %s\n", #name); \ - return -EEXIST; \ - } \ - assignment; \ - } else if (exclude_invariants && !!((attr_flag) & DRBD_F_INVARIANT)) { \ - /* attribute missing from payload, */ \ - /* which was expected */ \ - } else if ((attr_flag) & DRBD_F_REQUIRED) { \ - pr_info("<< missing attr: %s\n", #name); \ - return -ENOMSG; \ - } - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, type, __get, __put, \ - __is_signed) \ - __assign(attr_nr, attr_flag, name, nla_type, type, \ - if (s) \ - s->name = __get(nla); \ - DPRINT_FIELD("<<", nla_type, name, s, nla)) - -/* validate_nla() already checked nla_len <= maxlen appropriately. */ -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, type, maxlen, \ - __get, __put, __is_signed) \ - __assign(attr_nr, attr_flag, name, nla_type, type, \ - if (s) \ - s->name ## _len = \ - __get(s->name, nla, maxlen); \ - DPRINT_ARRAY("<<", nla_type, name, s, nla)) - -#include GENL_MAGIC_INCLUDE_FILE - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) - -/* - * Magic: define op number to op name mapping {{{1 - * {{{2 - */ -static const char *CONCATENATE(GENL_MAGIC_FAMILY, _genl_cmd_to_str)(__u8 cmd) -{ - switch (cmd) { -#undef GENL_op -#define GENL_op(op_name, op_num, handler, tla_list) \ - case op_num: return #op_name; -#include GENL_MAGIC_INCLUDE_FILE - default: - return "unknown"; - } -} - -#ifdef __KERNEL__ -#include -/* - * Magic: define genl_ops {{{1 - * {{{2 - */ - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, tla_list) \ -{ \ - handler \ - .cmd = op_name, \ -}, - -#define ZZZ_genl_ops CONCATENATE(GENL_MAGIC_FAMILY, _genl_ops) -static struct genl_ops ZZZ_genl_ops[] __read_mostly = { -#include GENL_MAGIC_INCLUDE_FILE -}; - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, tla_list) - -/* - * Define the genl_family, multicast groups, {{{1 - * and provide register/unregister functions. - * {{{2 - */ -#define ZZZ_genl_family CONCATENATE(GENL_MAGIC_FAMILY, _genl_family) -static struct genl_family ZZZ_genl_family; -/* - * Magic: define multicast groups - * Magic: define multicast group registration helper - */ -#define ZZZ_genl_mcgrps CONCATENATE(GENL_MAGIC_FAMILY, _genl_mcgrps) -static const struct genl_multicast_group ZZZ_genl_mcgrps[] = { -#undef GENL_mc_group -#define GENL_mc_group(group) { .name = #group, }, -#include GENL_MAGIC_INCLUDE_FILE -}; - -enum CONCATENATE(GENL_MAGIC_FAMILY, group_ids) { -#undef GENL_mc_group -#define GENL_mc_group(group) CONCATENATE(GENL_MAGIC_FAMILY, _group_ ## group), -#include GENL_MAGIC_INCLUDE_FILE -}; - -#undef GENL_mc_group -#define GENL_mc_group(group) \ -static int CONCATENATE(GENL_MAGIC_FAMILY, _genl_multicast_ ## group)( \ - struct sk_buff *skb, gfp_t flags) \ -{ \ - unsigned int group_id = \ - CONCATENATE(GENL_MAGIC_FAMILY, _group_ ## group); \ - return genlmsg_multicast(&ZZZ_genl_family, skb, 0, \ - group_id, flags); \ -} - -#include GENL_MAGIC_INCLUDE_FILE - -#undef GENL_mc_group -#define GENL_mc_group(group) - -static struct genl_family ZZZ_genl_family __ro_after_init = { - .name = __stringify(GENL_MAGIC_FAMILY), - .version = GENL_MAGIC_VERSION, -#ifdef GENL_MAGIC_FAMILY_HDRSZ - .hdrsize = NLA_ALIGN(GENL_MAGIC_FAMILY_HDRSZ), -#endif - .maxattr = ARRAY_SIZE(CONCATENATE(GENL_MAGIC_FAMILY, _tla_nl_policy))-1, - .policy = CONCATENATE(GENL_MAGIC_FAMILY, _tla_nl_policy), -#ifdef GENL_MAGIC_FAMILY_PRE_DOIT - .pre_doit = GENL_MAGIC_FAMILY_PRE_DOIT, - .post_doit = GENL_MAGIC_FAMILY_POST_DOIT, -#endif - .ops = ZZZ_genl_ops, - .n_ops = ARRAY_SIZE(ZZZ_genl_ops), - .mcgrps = ZZZ_genl_mcgrps, - .resv_start_op = 42, /* drbd is currently the only user */ - .n_mcgrps = ARRAY_SIZE(ZZZ_genl_mcgrps), - .module = THIS_MODULE, -}; - -int CONCATENATE(GENL_MAGIC_FAMILY, _genl_register)(void) -{ - return genl_register_family(&ZZZ_genl_family); -} - -void CONCATENATE(GENL_MAGIC_FAMILY, _genl_unregister)(void) -{ - genl_unregister_family(&ZZZ_genl_family); -} - -/* - * Magic: provide conversion functions {{{1 - * populate skb from struct. - * {{{2 - */ - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, tla_list) - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -static int s_name ## _to_skb(struct sk_buff *skb, struct s_name *s, \ - const bool exclude_sensitive) \ -{ \ - struct nlattr *tla = nla_nest_start(skb, tag_number); \ - if (!tla) \ - goto nla_put_failure; \ - DPRINT_TLA(#s_name, "-=>", #tag_name); \ - s_fields \ - nla_nest_end(skb, tla); \ - return 0; \ - \ -nla_put_failure: \ - if (tla) \ - nla_nest_cancel(skb, tla); \ - return -EMSGSIZE; \ -} \ -static inline int s_name ## _to_priv_skb(struct sk_buff *skb, \ - struct s_name *s) \ -{ \ - return s_name ## _to_skb(skb, s, 0); \ -} \ -static inline int s_name ## _to_unpriv_skb(struct sk_buff *skb, \ - struct s_name *s) \ -{ \ - return s_name ## _to_skb(skb, s, 1); \ -} - - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, type, __get, __put, \ - __is_signed) \ - if (!exclude_sensitive || !((attr_flag) & DRBD_F_SENSITIVE)) { \ - DPRINT_FIELD(">>", nla_type, name, s, NULL); \ - if (__put(skb, attr_nr, s->name)) \ - goto nla_put_failure; \ - } - -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, type, maxlen, \ - __get, __put, __is_signed) \ - if (!exclude_sensitive || !((attr_flag) & DRBD_F_SENSITIVE)) { \ - DPRINT_ARRAY(">>",nla_type, name, s, NULL); \ - if (__put(skb, attr_nr, min_t(int, maxlen, \ - s->name ## _len + (nla_type == NLA_NUL_STRING)),\ - s->name)) \ - goto nla_put_failure; \ - } - -#include GENL_MAGIC_INCLUDE_FILE - - -/* Functions for initializing structs to default values. */ - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, type, __get, __put, \ - __is_signed) -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, type, maxlen, \ - __get, __put, __is_signed) -#undef __u32_field_def -#define __u32_field_def(attr_nr, attr_flag, name, default) \ - x->name = default; -#undef __s32_field_def -#define __s32_field_def(attr_nr, attr_flag, name, default) \ - x->name = default; -#undef __flg_field_def -#define __flg_field_def(attr_nr, attr_flag, name, default) \ - x->name = default; -#undef __str_field_def -#define __str_field_def(attr_nr, attr_flag, name, maxlen) \ - memset(x->name, 0, sizeof(x->name)); \ - x->name ## _len = 0; -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -static void set_ ## s_name ## _defaults(struct s_name *x) __attribute__((unused)); \ -static void set_ ## s_name ## _defaults(struct s_name *x) { \ -s_fields \ -} - -#include GENL_MAGIC_INCLUDE_FILE - -#endif /* __KERNEL__ */ - -/* }}}1 */ -#endif /* GENL_MAGIC_FUNC_H */ diff --git a/include/linux/genl_magic_struct.h b/include/linux/genl_magic_struct.h deleted file mode 100644 index 2200cedd160a..000000000000 --- a/include/linux/genl_magic_struct.h +++ /dev/null @@ -1,272 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef GENL_MAGIC_STRUCT_H -#define GENL_MAGIC_STRUCT_H - -#ifndef GENL_MAGIC_FAMILY -# error "you need to define GENL_MAGIC_FAMILY before inclusion" -#endif - -#ifndef GENL_MAGIC_VERSION -# error "you need to define GENL_MAGIC_VERSION before inclusion" -#endif - -#ifndef GENL_MAGIC_INCLUDE_FILE -# error "you need to define GENL_MAGIC_INCLUDE_FILE before inclusion" -#endif - -#include -#include -#include - -extern int CONCATENATE(GENL_MAGIC_FAMILY, _genl_register)(void); -extern void CONCATENATE(GENL_MAGIC_FAMILY, _genl_unregister)(void); - -/* - * Extension of genl attribute validation policies {{{2 - */ - -/* - * Flags specific to drbd and not visible at the netlink layer, used in - * _from_attrs and _to_skb: - * - * @DRBD_F_REQUIRED: Attribute is required; a request without this attribute is - * invalid. - * - * @DRBD_F_SENSITIVE: Attribute includes sensitive information and must not be - * included in unpriviledged get requests or broadcasts. - * - * @DRBD_F_INVARIANT: Attribute is set when an object is initially created, but - * cannot subsequently be changed. - */ -#define DRBD_F_REQUIRED (1 << 0) -#define DRBD_F_SENSITIVE (1 << 1) -#define DRBD_F_INVARIANT (1 << 2) - - -/* }}}1 - * MAGIC - * multi-include macro expansion magic starts here - */ - -/* MAGIC helpers {{{2 */ - -static inline int nla_put_u64_0pad(struct sk_buff *skb, int attrtype, u64 value) -{ - return nla_put_64bit(skb, attrtype, sizeof(u64), &value, 0); -} - -/* possible field types */ -#define __flg_field(attr_nr, attr_flag, name) \ - __field(attr_nr, attr_flag, name, NLA_U8, char, \ - nla_get_u8, nla_put_u8, false) -#define __u8_field(attr_nr, attr_flag, name) \ - __field(attr_nr, attr_flag, name, NLA_U8, unsigned char, \ - nla_get_u8, nla_put_u8, false) -#define __u16_field(attr_nr, attr_flag, name) \ - __field(attr_nr, attr_flag, name, NLA_U16, __u16, \ - nla_get_u16, nla_put_u16, false) -#define __u32_field(attr_nr, attr_flag, name) \ - __field(attr_nr, attr_flag, name, NLA_U32, __u32, \ - nla_get_u32, nla_put_u32, false) -#define __s32_field(attr_nr, attr_flag, name) \ - __field(attr_nr, attr_flag, name, NLA_U32, __s32, \ - nla_get_u32, nla_put_u32, true) -#define __u64_field(attr_nr, attr_flag, name) \ - __field(attr_nr, attr_flag, name, NLA_U64, __u64, \ - nla_get_u64, nla_put_u64_0pad, false) -#define __str_field(attr_nr, attr_flag, name, maxlen) \ - __array(attr_nr, attr_flag, name, NLA_NUL_STRING, char, maxlen, \ - nla_strscpy, nla_put, false) -#define __bin_field(attr_nr, attr_flag, name, maxlen) \ - __array(attr_nr, attr_flag, name, NLA_BINARY, char, maxlen, \ - nla_memcpy, nla_put, false) - -/* fields with default values */ -#define __flg_field_def(attr_nr, attr_flag, name, default) \ - __flg_field(attr_nr, attr_flag, name) -#define __u32_field_def(attr_nr, attr_flag, name, default) \ - __u32_field(attr_nr, attr_flag, name) -#define __s32_field_def(attr_nr, attr_flag, name, default) \ - __s32_field(attr_nr, attr_flag, name) -#define __str_field_def(attr_nr, attr_flag, name, maxlen) \ - __str_field(attr_nr, attr_flag, name, maxlen) - -#define GENL_op_init(args...) args -#define GENL_doit(handler) \ - .doit = handler, \ - .flags = GENL_ADMIN_PERM, -#define GENL_dumpit(handler) \ - .dumpit = handler, \ - .flags = GENL_ADMIN_PERM, - -/* }}}1 - * Magic: define the enum symbols for genl_ops - * Magic: define the enum symbols for top level attributes - * Magic: define the enum symbols for nested attributes - * {{{2 - */ - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) - -#undef GENL_mc_group -#define GENL_mc_group(group) - -#undef GENL_notification -#define GENL_notification(op_name, op_num, mcast_group, tla_list) \ - op_name = op_num, - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, tla_list) \ - op_name = op_num, - -enum { -#include GENL_MAGIC_INCLUDE_FILE -}; - -#undef GENL_notification -#define GENL_notification(op_name, op_num, mcast_group, tla_list) - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, attr_list) - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ - tag_name = tag_number, - -enum { -#include GENL_MAGIC_INCLUDE_FILE -}; - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -enum { \ - s_fields \ -}; - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, type, \ - __get, __put, __is_signed) \ - T_ ## name = (__u16)(attr_nr), - -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, type, \ - maxlen, __get, __put, __is_signed) \ - T_ ## name = (__u16)(attr_nr), - -#include GENL_MAGIC_INCLUDE_FILE - -/* }}}1 - * Magic: compile time assert unique numbers for operations - * Magic: -"- unique numbers for top level attributes - * Magic: -"- unique numbers for nested attributes - * {{{2 - */ - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, attr_list) \ - case op_name: - -#undef GENL_notification -#define GENL_notification(op_name, op_num, mcast_group, tla_list) \ - case op_name: - -static inline void ct_assert_unique_operations(void) -{ - switch (0) { -#include GENL_MAGIC_INCLUDE_FILE - case 0: - ; - } -} - -#undef GENL_op -#define GENL_op(op_name, op_num, handler, attr_list) - -#undef GENL_notification -#define GENL_notification(op_name, op_num, mcast_group, tla_list) - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ - case tag_number: - -static inline void ct_assert_unique_top_level_attributes(void) -{ - switch (0) { -#include GENL_MAGIC_INCLUDE_FILE - case 0: - ; - } -} - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -static inline void ct_assert_unique_ ## s_name ## _attributes(void) \ -{ \ - switch (0) { \ - s_fields \ - case 0: \ - ; \ - } \ -} - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, type, __get, __put, \ - __is_signed) \ - case attr_nr: - -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, type, maxlen, \ - __get, __put, __is_signed) \ - case attr_nr: - -#include GENL_MAGIC_INCLUDE_FILE - -/* }}}1 - * Magic: declare structs - * struct { - * fields - * }; - * {{{2 - */ - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -struct s_name { s_fields }; - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, type, __get, __put, \ - __is_signed) \ - type name; - -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, type, maxlen, \ - __get, __put, __is_signed) \ - type name[maxlen]; \ - __u32 name ## _len; - -#include GENL_MAGIC_INCLUDE_FILE - -#undef GENL_struct -#define GENL_struct(tag_name, tag_number, s_name, s_fields) \ -enum { \ - s_fields \ -}; - -#undef __field -#define __field(attr_nr, attr_flag, name, nla_type, type, __get, __put, \ - is_signed) \ - F_ ## name ## _IS_SIGNED = is_signed, - -#undef __array -#define __array(attr_nr, attr_flag, name, nla_type, type, maxlen, \ - __get, __put, is_signed) \ - F_ ## name ## _IS_SIGNED = is_signed, - -#include GENL_MAGIC_INCLUDE_FILE - -/* }}}1 */ -#endif /* GENL_MAGIC_STRUCT_H */ diff --git a/include/uapi/linux/drbd.h b/include/uapi/linux/drbd.h index 7930a972d8a4..5d4d677cf1ad 100644 --- a/include/uapi/linux/drbd.h +++ b/include/uapi/linux/drbd.h @@ -333,6 +333,9 @@ enum drbd_uuid_index { #define HISTORY_UUIDS MAX_PEERS +#define DRBD_NL_UUIDS_SIZE (UI_SIZE * sizeof(__u64)) +#define DRBD_NL_HISTORY_UUIDS_SIZE (HISTORY_UUIDS * sizeof(__u64)) + enum drbd_timeout_flag { UT_DEFAULT = 0, UT_DEGRADED = 1, diff --git a/include/uapi/linux/drbd_genl.h b/include/uapi/linux/drbd_genl.h new file mode 100644 index 000000000000..8b25f08d8a90 --- /dev/null +++ b/include/uapi/linux/drbd_genl.h @@ -0,0 +1,359 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ + +#ifndef _UAPI_LINUX_DRBD_GENL_H +#define _UAPI_LINUX_DRBD_GENL_H + +#define DRBD_FAMILY_NAME "drbd" +#define DRBD_FAMILY_VERSION 1 + +enum { + DRBD_NLA_CFG_REPLY = 1, + DRBD_NLA_CFG_CONTEXT, + DRBD_NLA_DISK_CONF, + DRBD_NLA_RESOURCE_OPTS, + DRBD_NLA_NET_CONF, + DRBD_NLA_SET_ROLE_PARMS, + DRBD_NLA_RESIZE_PARMS, + DRBD_NLA_STATE_INFO, + DRBD_NLA_START_OV_PARMS, + DRBD_NLA_NEW_C_UUID_PARMS, + DRBD_NLA_TIMEOUT_PARMS, + DRBD_NLA_DISCONNECT_PARMS, + DRBD_NLA_DETACH_PARMS, + DRBD_NLA_RESOURCE_INFO = 15, + DRBD_NLA_DEVICE_INFO, + DRBD_NLA_CONNECTION_INFO, + DRBD_NLA_PEER_DEVICE_INFO, + DRBD_NLA_RESOURCE_STATISTICS, + DRBD_NLA_DEVICE_STATISTICS, + DRBD_NLA_CONNECTION_STATISTICS, + DRBD_NLA_PEER_DEVICE_STATISTICS, + DRBD_NLA_NOTIFICATION_HEADER, + DRBD_NLA_HELPER, + + __DRBD_NLA_MAX, + DRBD_NLA_MAX = (__DRBD_NLA_MAX - 1) +}; + +enum { + DRBD_A_DRBD_CFG_REPLY_INFO_TEXT = 1, + + __DRBD_A_DRBD_CFG_REPLY_MAX, + DRBD_A_DRBD_CFG_REPLY_MAX = (__DRBD_A_DRBD_CFG_REPLY_MAX - 1) +}; + +enum { + DRBD_A_DRBD_CFG_CONTEXT_CTX_VOLUME = 1, + DRBD_A_DRBD_CFG_CONTEXT_CTX_RESOURCE_NAME, + DRBD_A_DRBD_CFG_CONTEXT_CTX_MY_ADDR, + DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR, + + __DRBD_A_DRBD_CFG_CONTEXT_MAX, + DRBD_A_DRBD_CFG_CONTEXT_MAX = (__DRBD_A_DRBD_CFG_CONTEXT_MAX - 1) +}; + +enum { + DRBD_A_DISK_CONF_BACKING_DEV = 1, + DRBD_A_DISK_CONF_META_DEV, + DRBD_A_DISK_CONF_META_DEV_IDX, + DRBD_A_DISK_CONF_DISK_SIZE, + DRBD_A_DISK_CONF_MAX_BIO_BVECS, + DRBD_A_DISK_CONF_ON_IO_ERROR, + DRBD_A_DISK_CONF_FENCING, + DRBD_A_DISK_CONF_RESYNC_RATE, + DRBD_A_DISK_CONF_RESYNC_AFTER, + DRBD_A_DISK_CONF_AL_EXTENTS, + DRBD_A_DISK_CONF_C_PLAN_AHEAD, + DRBD_A_DISK_CONF_C_DELAY_TARGET, + DRBD_A_DISK_CONF_C_FILL_TARGET, + DRBD_A_DISK_CONF_C_MAX_RATE, + DRBD_A_DISK_CONF_C_MIN_RATE, + DRBD_A_DISK_CONF_DISK_BARRIER, + DRBD_A_DISK_CONF_DISK_FLUSHES, + DRBD_A_DISK_CONF_DISK_DRAIN, + DRBD_A_DISK_CONF_MD_FLUSHES, + DRBD_A_DISK_CONF_DISK_TIMEOUT, + DRBD_A_DISK_CONF_READ_BALANCING, + DRBD_A_DISK_CONF_AL_UPDATES = 23, + DRBD_A_DISK_CONF_DISCARD_ZEROES_IF_ALIGNED, + DRBD_A_DISK_CONF_RS_DISCARD_GRANULARITY, + DRBD_A_DISK_CONF_DISABLE_WRITE_SAME, + + __DRBD_A_DISK_CONF_MAX, + DRBD_A_DISK_CONF_MAX = (__DRBD_A_DISK_CONF_MAX - 1) +}; + +enum { + DRBD_A_RES_OPTS_CPU_MASK = 1, + DRBD_A_RES_OPTS_ON_NO_DATA, + + __DRBD_A_RES_OPTS_MAX, + DRBD_A_RES_OPTS_MAX = (__DRBD_A_RES_OPTS_MAX - 1) +}; + +enum { + DRBD_A_NET_CONF_SHARED_SECRET = 1, + DRBD_A_NET_CONF_CRAM_HMAC_ALG, + DRBD_A_NET_CONF_INTEGRITY_ALG, + DRBD_A_NET_CONF_VERIFY_ALG, + DRBD_A_NET_CONF_CSUMS_ALG, + DRBD_A_NET_CONF_WIRE_PROTOCOL, + DRBD_A_NET_CONF_CONNECT_INT, + DRBD_A_NET_CONF_TIMEOUT, + DRBD_A_NET_CONF_PING_INT, + DRBD_A_NET_CONF_PING_TIMEO, + DRBD_A_NET_CONF_SNDBUF_SIZE, + DRBD_A_NET_CONF_RCVBUF_SIZE, + DRBD_A_NET_CONF_KO_COUNT, + DRBD_A_NET_CONF_MAX_BUFFERS, + DRBD_A_NET_CONF_MAX_EPOCH_SIZE, + DRBD_A_NET_CONF_UNPLUG_WATERMARK, + DRBD_A_NET_CONF_AFTER_SB_0P, + DRBD_A_NET_CONF_AFTER_SB_1P, + DRBD_A_NET_CONF_AFTER_SB_2P, + DRBD_A_NET_CONF_RR_CONFLICT, + DRBD_A_NET_CONF_ON_CONGESTION, + DRBD_A_NET_CONF_CONG_FILL, + DRBD_A_NET_CONF_CONG_EXTENTS, + DRBD_A_NET_CONF_TWO_PRIMARIES, + DRBD_A_NET_CONF_DISCARD_MY_DATA, + DRBD_A_NET_CONF_TCP_CORK, + DRBD_A_NET_CONF_ALWAYS_ASBP, + DRBD_A_NET_CONF_TENTATIVE, + DRBD_A_NET_CONF_USE_RLE, + DRBD_A_NET_CONF_CSUMS_AFTER_CRASH_ONLY = 33, + DRBD_A_NET_CONF_SOCK_CHECK_TIMEO, + + __DRBD_A_NET_CONF_MAX, + DRBD_A_NET_CONF_MAX = (__DRBD_A_NET_CONF_MAX - 1) +}; + +enum { + DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE = 1, + + __DRBD_A_SET_ROLE_PARMS_MAX, + DRBD_A_SET_ROLE_PARMS_MAX = (__DRBD_A_SET_ROLE_PARMS_MAX - 1) +}; + +enum { + DRBD_A_RESIZE_PARMS_RESIZE_SIZE = 1, + DRBD_A_RESIZE_PARMS_RESIZE_FORCE, + DRBD_A_RESIZE_PARMS_NO_RESYNC, + DRBD_A_RESIZE_PARMS_AL_STRIPES, + DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE, + + __DRBD_A_RESIZE_PARMS_MAX, + DRBD_A_RESIZE_PARMS_MAX = (__DRBD_A_RESIZE_PARMS_MAX - 1) +}; + +enum { + DRBD_A_STATE_INFO_SIB_REASON = 1, + DRBD_A_STATE_INFO_CURRENT_STATE, + DRBD_A_STATE_INFO_CAPACITY, + DRBD_A_STATE_INFO_ED_UUID, + DRBD_A_STATE_INFO_PREV_STATE, + DRBD_A_STATE_INFO_NEW_STATE, + DRBD_A_STATE_INFO_UUIDS, + DRBD_A_STATE_INFO_DISK_FLAGS, + DRBD_A_STATE_INFO_BITS_TOTAL, + DRBD_A_STATE_INFO_BITS_OOS, + DRBD_A_STATE_INFO_BITS_RS_TOTAL, + DRBD_A_STATE_INFO_BITS_RS_FAILED, + DRBD_A_STATE_INFO_HELPER, + DRBD_A_STATE_INFO_HELPER_EXIT_CODE, + DRBD_A_STATE_INFO_SEND_CNT, + DRBD_A_STATE_INFO_RECV_CNT, + DRBD_A_STATE_INFO_READ_CNT, + DRBD_A_STATE_INFO_WRIT_CNT, + DRBD_A_STATE_INFO_AL_WRIT_CNT, + DRBD_A_STATE_INFO_BM_WRIT_CNT, + DRBD_A_STATE_INFO_AP_BIO_CNT, + DRBD_A_STATE_INFO_AP_PENDING_CNT, + DRBD_A_STATE_INFO_RS_PENDING_CNT, + + __DRBD_A_STATE_INFO_MAX, + DRBD_A_STATE_INFO_MAX = (__DRBD_A_STATE_INFO_MAX - 1) +}; + +enum { + DRBD_A_START_OV_PARMS_OV_START_SECTOR = 1, + DRBD_A_START_OV_PARMS_OV_STOP_SECTOR, + + __DRBD_A_START_OV_PARMS_MAX, + DRBD_A_START_OV_PARMS_MAX = (__DRBD_A_START_OV_PARMS_MAX - 1) +}; + +enum { + DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM = 1, + + __DRBD_A_NEW_C_UUID_PARMS_MAX, + DRBD_A_NEW_C_UUID_PARMS_MAX = (__DRBD_A_NEW_C_UUID_PARMS_MAX - 1) +}; + +enum { + DRBD_A_TIMEOUT_PARMS_TIMEOUT_TYPE = 1, + + __DRBD_A_TIMEOUT_PARMS_MAX, + DRBD_A_TIMEOUT_PARMS_MAX = (__DRBD_A_TIMEOUT_PARMS_MAX - 1) +}; + +enum { + DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT = 1, + + __DRBD_A_DISCONNECT_PARMS_MAX, + DRBD_A_DISCONNECT_PARMS_MAX = (__DRBD_A_DISCONNECT_PARMS_MAX - 1) +}; + +enum { + DRBD_A_DETACH_PARMS_FORCE_DETACH = 1, + + __DRBD_A_DETACH_PARMS_MAX, + DRBD_A_DETACH_PARMS_MAX = (__DRBD_A_DETACH_PARMS_MAX - 1) +}; + +enum { + DRBD_A_RESOURCE_INFO_RES_ROLE = 1, + DRBD_A_RESOURCE_INFO_RES_SUSP, + DRBD_A_RESOURCE_INFO_RES_SUSP_NOD, + DRBD_A_RESOURCE_INFO_RES_SUSP_FEN, + + __DRBD_A_RESOURCE_INFO_MAX, + DRBD_A_RESOURCE_INFO_MAX = (__DRBD_A_RESOURCE_INFO_MAX - 1) +}; + +enum { + DRBD_A_DEVICE_INFO_DEV_DISK_STATE = 1, + + __DRBD_A_DEVICE_INFO_MAX, + DRBD_A_DEVICE_INFO_MAX = (__DRBD_A_DEVICE_INFO_MAX - 1) +}; + +enum { + DRBD_A_CONNECTION_INFO_CONN_CONNECTION_STATE = 1, + DRBD_A_CONNECTION_INFO_CONN_ROLE, + + __DRBD_A_CONNECTION_INFO_MAX, + DRBD_A_CONNECTION_INFO_MAX = (__DRBD_A_CONNECTION_INFO_MAX - 1) +}; + +enum { + DRBD_A_PEER_DEVICE_INFO_PEER_REPL_STATE = 1, + DRBD_A_PEER_DEVICE_INFO_PEER_DISK_STATE, + DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_USER, + DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_PEER, + DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY, + + __DRBD_A_PEER_DEVICE_INFO_MAX, + DRBD_A_PEER_DEVICE_INFO_MAX = (__DRBD_A_PEER_DEVICE_INFO_MAX - 1) +}; + +enum { + DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING = 1, + + __DRBD_A_RESOURCE_STATISTICS_MAX, + DRBD_A_RESOURCE_STATISTICS_MAX = (__DRBD_A_RESOURCE_STATISTICS_MAX - 1) +}; + +enum { + DRBD_A_DEVICE_STATISTICS_DEV_SIZE = 1, + DRBD_A_DEVICE_STATISTICS_DEV_READ, + DRBD_A_DEVICE_STATISTICS_DEV_WRITE, + DRBD_A_DEVICE_STATISTICS_DEV_AL_WRITES, + DRBD_A_DEVICE_STATISTICS_DEV_BM_WRITES, + DRBD_A_DEVICE_STATISTICS_DEV_UPPER_PENDING, + DRBD_A_DEVICE_STATISTICS_DEV_LOWER_PENDING, + DRBD_A_DEVICE_STATISTICS_DEV_UPPER_BLOCKED, + DRBD_A_DEVICE_STATISTICS_DEV_LOWER_BLOCKED, + DRBD_A_DEVICE_STATISTICS_DEV_AL_SUSPENDED, + DRBD_A_DEVICE_STATISTICS_DEV_EXPOSED_DATA_UUID, + DRBD_A_DEVICE_STATISTICS_DEV_CURRENT_UUID, + DRBD_A_DEVICE_STATISTICS_DEV_DISK_FLAGS, + DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS, + + __DRBD_A_DEVICE_STATISTICS_MAX, + DRBD_A_DEVICE_STATISTICS_MAX = (__DRBD_A_DEVICE_STATISTICS_MAX - 1) +}; + +enum { + DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED = 1, + + __DRBD_A_CONNECTION_STATISTICS_MAX, + DRBD_A_CONNECTION_STATISTICS_MAX = (__DRBD_A_CONNECTION_STATISTICS_MAX - 1) +}; + +enum { + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RECEIVED = 1, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_SENT, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_PENDING, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_UNACKED, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_OUT_OF_SYNC, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_RESYNC_FAILED, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_BITMAP_UUID, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS = 9, + + __DRBD_A_PEER_DEVICE_STATISTICS_MAX, + DRBD_A_PEER_DEVICE_STATISTICS_MAX = (__DRBD_A_PEER_DEVICE_STATISTICS_MAX - 1) +}; + +enum { + DRBD_A_DRBD_NOTIFICATION_HEADER_NH_TYPE = 1, + + __DRBD_A_DRBD_NOTIFICATION_HEADER_MAX, + DRBD_A_DRBD_NOTIFICATION_HEADER_MAX = (__DRBD_A_DRBD_NOTIFICATION_HEADER_MAX - 1) +}; + +enum { + DRBD_A_DRBD_HELPER_INFO_HELPER_NAME = 1, + DRBD_A_DRBD_HELPER_INFO_HELPER_STATUS, + + __DRBD_A_DRBD_HELPER_INFO_MAX, + DRBD_A_DRBD_HELPER_INFO_MAX = (__DRBD_A_DRBD_HELPER_INFO_MAX - 1) +}; + +enum { + DRBD_ADM_EVENT = 1, + DRBD_ADM_GET_STATUS, + DRBD_ADM_NEW_MINOR = 5, + DRBD_ADM_DEL_MINOR, + DRBD_ADM_NEW_RESOURCE, + DRBD_ADM_DEL_RESOURCE, + DRBD_ADM_RESOURCE_OPTS, + DRBD_ADM_CONNECT, + DRBD_ADM_DISCONNECT, + DRBD_ADM_ATTACH, + DRBD_ADM_RESIZE, + DRBD_ADM_PRIMARY, + DRBD_ADM_SECONDARY, + DRBD_ADM_NEW_C_UUID, + DRBD_ADM_START_OV, + DRBD_ADM_DETACH, + DRBD_ADM_INVALIDATE, + DRBD_ADM_INVAL_PEER, + DRBD_ADM_PAUSE_SYNC, + DRBD_ADM_RESUME_SYNC, + DRBD_ADM_SUSPEND_IO, + DRBD_ADM_RESUME_IO, + DRBD_ADM_OUTDATE, + DRBD_ADM_GET_TIMEOUT_TYPE, + DRBD_ADM_DOWN, + DRBD_ADM_CHG_DISK_OPTS, + DRBD_ADM_CHG_NET_OPTS, + DRBD_ADM_GET_RESOURCES, + DRBD_ADM_GET_DEVICES, + DRBD_ADM_GET_CONNECTIONS, + DRBD_ADM_GET_PEER_DEVICES, + DRBD_ADM_RESOURCE_STATE, + DRBD_ADM_DEVICE_STATE, + DRBD_ADM_CONNECTION_STATE, + DRBD_ADM_PEER_DEVICE_STATE, + DRBD_ADM_GET_INITIAL_STATE, + DRBD_ADM_HELPER = 40, + DRBD_ADM_INITIAL_STATE_DONE, + + __DRBD_ADM_MAX, + DRBD_ADM_MAX = (__DRBD_ADM_MAX - 1) +}; + +#define DRBD_MCGRP_EVENTS "events" + +#endif /* _UAPI_LINUX_DRBD_GENL_H */ From d5607f1fafd7eb72ed693b6a033d96221e870edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=B6hmwalder?= Date: Wed, 13 May 2026 13:03:42 +0200 Subject: [PATCH 003/117] drbd: clean up UAPI headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit b1798910fc7f ("drbd: move UAPI headers to include/uapi/linux/") broke compilation on targets without a hosted libc: ./usr/include/linux/drbd.h:18:10: fatal error: sys/types.h: No such file or directory The underlying issue is that there were some constructs left over in those headers that don't belong in uapi. Drop the __KERNEL__-gated split in drbd.h. The !__KERNEL__ branch pulls in , and for symbols that the header does not actually reference; they were carried over from when this lived in include/linux/. Replace and the entire #ifdef block with the standard UAPI combo + , which provides __u32/__u64/__s32 and __{LITTLE,BIG}_ENDIAN_BITFIELD in both kernel and userspace contexts. drbd_limits.h references some enum values and the DRBD_PROT_C define from drbd.h, but does not include it. Add the missing include while we're here. Drop the unprefixed DEBUG_RANGE_CHECK from drbd_limits.h. It has no in-kernel users and pollutes the userspace namespace. Switch the drbd.h and drbd_limits.h include guards to the _UAPI_LINUX_* convention already used by drbd_genl.h. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605101346.V2wwJqv1-lkp@intel.com/ Fixes: b1798910fc7f ("drbd: move UAPI headers to include/uapi/linux/") Signed-off-by: Christoph Böhmwalder Link: https://patch.msgid.link/20260513110343.3170338-1-christoph.boehmwalder@linbit.com Signed-off-by: Jens Axboe --- include/uapi/linux/drbd.h | 28 +++------------------------- include/uapi/linux/drbd_limits.h | 8 ++++---- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/include/uapi/linux/drbd.h b/include/uapi/linux/drbd.h index 5d4d677cf1ad..cf1ec3eb872f 100644 --- a/include/uapi/linux/drbd.h +++ b/include/uapi/linux/drbd.h @@ -11,32 +11,10 @@ */ -#ifndef DRBD_H -#define DRBD_H -#include - -#ifdef __KERNEL__ +#ifndef _UAPI_LINUX_DRBD_H +#define _UAPI_LINUX_DRBD_H #include #include -#else -#include -#include -#include - -/* Although the Linux source code makes a difference between - generic endianness and the bitfields' endianness, there is no - architecture as of Linux-2.6.24-rc4 where the bitfields' endianness - does not match the generic endianness. */ - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define __LITTLE_ENDIAN_BITFIELD -#elif __BYTE_ORDER == __BIG_ENDIAN -#define __BIG_ENDIAN_BITFIELD -#else -# error "sorry, weird endianness on this box" -#endif - -#endif enum drbd_io_error_p { EP_PASS_ON, /* FIXME should the better be named "Ignore"? */ @@ -432,4 +410,4 @@ enum drbd_state_info_bcast_reason { SIB_SYNC_PROGRESS = 5, }; -#endif +#endif /* _UAPI_LINUX_DRBD_H */ diff --git a/include/uapi/linux/drbd_limits.h b/include/uapi/linux/drbd_limits.h index a72a102d1ca7..acefe84bc602 100644 --- a/include/uapi/linux/drbd_limits.h +++ b/include/uapi/linux/drbd_limits.h @@ -11,10 +11,10 @@ * feedback about nonsense settings for certain configurable values. */ -#ifndef DRBD_LIMITS_H -#define DRBD_LIMITS_H 1 +#ifndef _UAPI_LINUX_DRBD_LIMITS_H +#define _UAPI_LINUX_DRBD_LIMITS_H -#define DEBUG_RANGE_CHECK 0 +#include #define DRBD_MINOR_COUNT_MIN 1U #define DRBD_MINOR_COUNT_MAX 255U @@ -248,4 +248,4 @@ #define DRBD_RS_DISCARD_GRANULARITY_DEF 0U /* disabled by default */ #define DRBD_RS_DISCARD_GRANULARITY_SCALE '1' /* bytes */ -#endif +#endif /* _UAPI_LINUX_DRBD_LIMITS_H */ From 64cf531f9900c011afe97a28c8ef464d551cec1b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 15 May 2026 06:55:30 +0200 Subject: [PATCH 004/117] block: remove zero_fill_bio_iter Only used to implement zero_fill_bio, so directly implement that. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260515045547.3790129-2-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 6 +++--- include/linux/bio.h | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/block/bio.c b/block/bio.c index b8972dba68a0..b990c453d72f 100644 --- a/block/bio.c +++ b/block/bio.c @@ -635,15 +635,15 @@ struct bio *bio_kmalloc(unsigned short nr_vecs, gfp_t gfp_mask) } EXPORT_SYMBOL(bio_kmalloc); -void zero_fill_bio_iter(struct bio *bio, struct bvec_iter start) +void zero_fill_bio(struct bio *bio) { struct bio_vec bv; struct bvec_iter iter; - __bio_for_each_segment(bv, bio, iter, start) + bio_for_each_segment(bv, bio, iter) memzero_bvec(&bv); } -EXPORT_SYMBOL(zero_fill_bio_iter); +EXPORT_SYMBOL(zero_fill_bio); /** * bio_truncate - truncate the bio to small size of @new_size diff --git a/include/linux/bio.h b/include/linux/bio.h index 97d747320b35..84643fc0fb08 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -482,13 +482,8 @@ extern void bio_copy_data_iter(struct bio *dst, struct bvec_iter *dst_iter, struct bio *src, struct bvec_iter *src_iter); extern void bio_copy_data(struct bio *dst, struct bio *src); extern void bio_free_pages(struct bio *bio); +void zero_fill_bio(struct bio *bio); void guard_bio_eod(struct bio *bio); -void zero_fill_bio_iter(struct bio *bio, struct bvec_iter iter); - -static inline void zero_fill_bio(struct bio *bio) -{ - zero_fill_bio_iter(bio, bio->bi_iter); -} static inline void bio_release_pages(struct bio *bio, bool mark_dirty) { From 8d57b5876d39679c81d479485422e88319f17418 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 15 May 2026 06:55:31 +0200 Subject: [PATCH 005/117] block: remove bio_copy_data_iter Only used by bio_copy_data, so implement that directly. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260515045547.3790129-3-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 36 +++++++++++++++--------------------- include/linux/bio.h | 2 -- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/block/bio.c b/block/bio.c index b990c453d72f..57d5a87b3e2f 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1575,26 +1575,6 @@ void __bio_advance(struct bio *bio, unsigned bytes) } EXPORT_SYMBOL(__bio_advance); -void bio_copy_data_iter(struct bio *dst, struct bvec_iter *dst_iter, - struct bio *src, struct bvec_iter *src_iter) -{ - while (src_iter->bi_size && dst_iter->bi_size) { - struct bio_vec src_bv = bio_iter_iovec(src, *src_iter); - struct bio_vec dst_bv = bio_iter_iovec(dst, *dst_iter); - unsigned int bytes = min(src_bv.bv_len, dst_bv.bv_len); - void *src_buf = bvec_kmap_local(&src_bv); - void *dst_buf = bvec_kmap_local(&dst_bv); - - memcpy(dst_buf, src_buf, bytes); - - kunmap_local(dst_buf); - kunmap_local(src_buf); - - bio_advance_iter_single(src, src_iter, bytes); - bio_advance_iter_single(dst, dst_iter, bytes); - } -} -EXPORT_SYMBOL(bio_copy_data_iter); /** * bio_copy_data - copy contents of data buffers from one bio to another @@ -1609,7 +1589,21 @@ void bio_copy_data(struct bio *dst, struct bio *src) struct bvec_iter src_iter = src->bi_iter; struct bvec_iter dst_iter = dst->bi_iter; - bio_copy_data_iter(dst, &dst_iter, src, &src_iter); + while (src_iter.bi_size && dst_iter.bi_size) { + struct bio_vec src_bv = bio_iter_iovec(src, src_iter); + struct bio_vec dst_bv = bio_iter_iovec(dst, dst_iter); + unsigned int bytes = min(src_bv.bv_len, dst_bv.bv_len); + void *src_buf = bvec_kmap_local(&src_bv); + void *dst_buf = bvec_kmap_local(&dst_bv); + + memcpy(dst_buf, src_buf, bytes); + + kunmap_local(dst_buf); + kunmap_local(src_buf); + + bio_advance_iter_single(src, &src_iter, bytes); + bio_advance_iter_single(dst, &dst_iter, bytes); + } } EXPORT_SYMBOL(bio_copy_data); diff --git a/include/linux/bio.h b/include/linux/bio.h index 84643fc0fb08..85463981d0f5 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -478,8 +478,6 @@ extern void bio_check_pages_dirty(struct bio *bio); int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen); void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty); -extern void bio_copy_data_iter(struct bio *dst, struct bvec_iter *dst_iter, - struct bio *src, struct bvec_iter *src_iter); extern void bio_copy_data(struct bio *dst, struct bio *src); extern void bio_free_pages(struct bio *bio); void zero_fill_bio(struct bio *bio); From 9957d5bbb916d37b277bf298e635d29a71205f99 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 15 May 2026 06:55:32 +0200 Subject: [PATCH 006/117] block: unexport blk_io_schedule Only used in built-in code. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260515045547.3790129-4-hch@lst.de Signed-off-by: Jens Axboe --- block/blk-core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/block/blk-core.c b/block/blk-core.c index 17450058ea6d..d7de87e86994 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -1270,7 +1270,6 @@ void blk_io_schedule(void) else io_schedule(); } -EXPORT_SYMBOL_GPL(blk_io_schedule); int __init blk_dev_init(void) { From 82aaa55a3162acaad10baced6f87126a4454a0d7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 15 May 2026 06:55:33 +0200 Subject: [PATCH 007/117] block: unexport blk_status_to_str Only used in core block code, so unexport and move the prototype to blk.h. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260515045547.3790129-5-hch@lst.de Signed-off-by: Jens Axboe --- block/blk-core.c | 1 - block/blk.h | 2 ++ include/linux/blkdev.h | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/block/blk-core.c b/block/blk-core.c index d7de87e86994..22af5dec112b 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -197,7 +197,6 @@ const char *blk_status_to_str(blk_status_t status) return ""; return blk_errors[idx].name; } -EXPORT_SYMBOL_GPL(blk_status_to_str); /** * blk_sync_queue - cancel any pending callbacks on a queue diff --git a/block/blk.h b/block/blk.h index b998a7761faf..bf1a80493ff1 100644 --- a/block/blk.h +++ b/block/blk.h @@ -49,6 +49,8 @@ struct blk_flush_queue *blk_alloc_flush_queue(int node, int cmd_size, gfp_t flags); void blk_free_flush_queue(struct blk_flush_queue *q); +const char *blk_status_to_str(blk_status_t status); + bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic); bool blk_queue_start_drain(struct request_queue *q); bool __blk_freeze_queue_start(struct request_queue *q, diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 890128cdea1c..17270a28c66d 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -1040,7 +1040,6 @@ extern const char *blk_op_str(enum req_op op); int blk_status_to_errno(blk_status_t status); blk_status_t errno_to_blk_status(int errno); -const char *blk_status_to_str(blk_status_t status); /* only poll the hardware once, don't continue until a completion was found */ #define BLK_POLL_ONESHOT (1 << 0) From be34ec59ef061a1cc435a3e066ad71ca675c7fcd Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 15 May 2026 06:55:34 +0200 Subject: [PATCH 008/117] block: unexport bio_{set,check}_pages_dirty Only used in built-in code. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260515045547.3790129-6-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/block/bio.c b/block/bio.c index 57d5a87b3e2f..2d880d1255fe 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1650,7 +1650,6 @@ void bio_set_pages_dirty(struct bio *bio) folio_unlock(fi.folio); } } -EXPORT_SYMBOL_GPL(bio_set_pages_dirty); /* * bio_check_pages_dirty() will check that all the BIO's pages are still dirty. @@ -1709,7 +1708,6 @@ void bio_check_pages_dirty(struct bio *bio) spin_unlock_irqrestore(&bio_dirty_lock, flags); schedule_work(&bio_dirty_work); } -EXPORT_SYMBOL_GPL(bio_check_pages_dirty); static inline bool bio_remaining_done(struct bio *bio) { From 165a5d4fbe5c9e09d7cf82ff431dd74a8d6c0b75 Mon Sep 17 00:00:00 2001 From: Maximilian Heyne Date: Thu, 14 May 2026 10:32:49 +0200 Subject: [PATCH 009/117] nvme: Let the blocklayer set timeouts for requests When initializing an nvme request which is about to be send to the block layer, we do not need to initialize its timeout. If it's left uninitialized at 0 the block layer will use the request queue's timeout in blk_add_timer (via nvme_start_request which is called from nvme_*_queue_rq). These timeouts are setup to either NVME_IO_TIMEOUT or NVME_ADMIN_TIMEOUT when the request queues were created. Because the io_timeout of the IO queues can be modified via sysfs, the following situation can occur: 1) NVME_IO_TIMEOUT = 30 (default module parameter) 2) nvme1n1 is probed. IO queues default timeout is 30 s 3) manually change the IO timeout to 90 s echo 90000 > /sys/class/nvme/nvme1/nvme1n1/queue/io_timeout 4) Any call of __submit_sync_cmd on nvme1n1 to an IO queue will issue commands with the 30 s timeout instead of the wanted 90 s which might be more suitable for this device. Commit 470e900c8036 ("nvme: refactor nvme_alloc_request") silently changed the behavior for ioctl's already because it unconditionally overrides the request's timeout that was set in nvme_init_request. If it was unset by the user of the ioctl if will be overridden with 0 meaning the block layer will pick the request queue's IO timeout. Following up on that, this patch further improves the consistency of IO timeout usage. However, there are still uses of NVME_IO_TIMEOUT which could be inconsistent with what is set in the device's request_queue by the user. Reviewed-by: Mohamed Khalfella Reviewed-by: Christoph Hellwig Reviewed-by: Daniel Wagner Reviewed-by: Hannes Reinecke Signed-off-by: Maximilian Heyne Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index dc388e24caad..89948d0acf18 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -729,10 +729,8 @@ void nvme_init_request(struct request *req, struct nvme_command *cmd) struct nvme_ns *ns = req->q->disk->private_data; logging_enabled = ns->head->passthru_err_log_enabled; - req->timeout = NVME_IO_TIMEOUT; } else { /* no queuedata implies admin queue */ logging_enabled = nr->ctrl->passthru_err_log_enabled; - req->timeout = NVME_ADMIN_TIMEOUT; } if (!logging_enabled) From 23b6d2cbf75ff15647efbb7c0e5c03bd7ed1fe1a Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 14 May 2026 10:32:50 +0200 Subject: [PATCH 010/117] nvme: remove redundant timeout argument from nvme_wait_freeze_timeout All callers of nvme_wait_freeze_timeout() currently pass the exact same NVME_IO_TIMEOUT default as their timeout argument. Remove it and use a local variable. Reviewed-by: Daniel Wagner Reviewed-by: Mohamed Khalfella Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/apple.c | 2 +- drivers/nvme/host/core.c | 3 ++- drivers/nvme/host/nvme.h | 2 +- drivers/nvme/host/pci.c | 2 +- drivers/nvme/host/rdma.c | 2 +- drivers/nvme/host/tcp.c | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 423c9c628e7b..e77c47408102 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -858,7 +858,7 @@ static void apple_nvme_disable(struct apple_nvme *anv, bool shutdown) * doing a safe shutdown. */ if (!dead && shutdown && freeze) - nvme_wait_freeze_timeout(&anv->ctrl, NVME_IO_TIMEOUT); + nvme_wait_freeze_timeout(&anv->ctrl); nvme_quiesce_io_queues(&anv->ctrl); diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 89948d0acf18..f9fe7bb65ec6 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -5246,8 +5246,9 @@ void nvme_unfreeze(struct nvme_ctrl *ctrl) } EXPORT_SYMBOL_GPL(nvme_unfreeze); -int nvme_wait_freeze_timeout(struct nvme_ctrl *ctrl, long timeout) +int nvme_wait_freeze_timeout(struct nvme_ctrl *ctrl) { + unsigned long timeout = NVME_IO_TIMEOUT; struct nvme_ns *ns; int srcu_idx; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index ccd5e05dac98..6f9ecb4948f4 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -900,7 +900,7 @@ void nvme_sync_queues(struct nvme_ctrl *ctrl); void nvme_sync_io_queues(struct nvme_ctrl *ctrl); void nvme_unfreeze(struct nvme_ctrl *ctrl); void nvme_wait_freeze(struct nvme_ctrl *ctrl); -int nvme_wait_freeze_timeout(struct nvme_ctrl *ctrl, long timeout); +int nvme_wait_freeze_timeout(struct nvme_ctrl *ctrl); void nvme_start_freeze(struct nvme_ctrl *ctrl); static inline enum req_op nvme_req_op(struct nvme_command *cmd) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 9fd04cd7c5cb..2dc1074f9984 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -3276,7 +3276,7 @@ static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown) * if doing a safe shutdown. */ if (!dead && shutdown) - nvme_wait_freeze_timeout(&dev->ctrl, NVME_IO_TIMEOUT); + nvme_wait_freeze_timeout(&dev->ctrl); } nvme_quiesce_io_queues(&dev->ctrl); diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index f77c960f7632..bf73135c1439 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -888,7 +888,7 @@ static int nvme_rdma_configure_io_queues(struct nvme_rdma_ctrl *ctrl, bool new) if (!new) { nvme_start_freeze(&ctrl->ctrl); nvme_unquiesce_io_queues(&ctrl->ctrl); - if (!nvme_wait_freeze_timeout(&ctrl->ctrl, NVME_IO_TIMEOUT)) { + if (!nvme_wait_freeze_timeout(&ctrl->ctrl)) { /* * If we timed out waiting for freeze we are likely to * be stuck. Fail the controller initialization just diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 15d36d6a728e..0552aa8a1150 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -2208,7 +2208,7 @@ static int nvme_tcp_configure_io_queues(struct nvme_ctrl *ctrl, bool new) if (!new) { nvme_start_freeze(ctrl); nvme_unquiesce_io_queues(ctrl); - if (!nvme_wait_freeze_timeout(ctrl, NVME_IO_TIMEOUT)) { + if (!nvme_wait_freeze_timeout(ctrl)) { /* * If we timed out waiting for freeze we are likely to * be stuck. Fail the controller initialization just From 61b99f24f0d56867d83b49f890790dd01ddd7675 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 14 May 2026 10:32:51 +0200 Subject: [PATCH 011/117] nvme: add sysfs attribute to change admin timeout per nvme controller Currently, there is no method to adjust the timeout values on a per-controller basis with nvme admin queues. Add an admin_timeout attribute to nvme so that different nvme controllers which may have different timeout requirements can have custom admin timeouts set. The admin timeout is also applied to the fabrics queue (fabrics_q). The fabrics queue is utilized for fabric-specific administrative and control operations, such as Connect and Property Get/Set commands. Reviewed-by: Daniel Wagner Reviewed-by: Sagi Grimberg Reviewed-by: Hannes Reinecke Reviewed-by: Mohamed Khalfella Reviewed-by: Christoph Hellwig Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 1 + drivers/nvme/host/nvme.h | 1 + drivers/nvme/host/pci.c | 2 +- drivers/nvme/host/sysfs.c | 41 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index f9fe7bb65ec6..20df7c12c718 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -5140,6 +5140,7 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev, memset(&ctrl->ka_cmd, 0, sizeof(ctrl->ka_cmd)); ctrl->ka_cmd.common.opcode = nvme_admin_keep_alive; ctrl->ka_last_check_time = jiffies; + ctrl->admin_timeout = NVME_ADMIN_TIMEOUT; BUILD_BUG_ON(NVME_DSM_MAX_RANGES * sizeof(struct nvme_dsm_range) > PAGE_SIZE); diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 6f9ecb4948f4..7923533cce00 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -370,6 +370,7 @@ struct nvme_ctrl { u16 mtfa; u32 ctrl_config; u32 queue_count; + u32 admin_timeout; u64 cap; u32 max_hw_sectors; diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 2dc1074f9984..35affda088f4 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -3094,7 +3094,7 @@ static bool __nvme_delete_io_queues(struct nvme_dev *dev, u8 opcode) unsigned long timeout; retry: - timeout = NVME_ADMIN_TIMEOUT; + timeout = dev->ctrl.admin_timeout; while (nr_queues > 0) { if (nvme_delete_queue(&dev->queues[nr_queues], opcode)) break; diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index e59758616f27..3b39b64cd9da 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -623,6 +623,46 @@ static ssize_t quirks_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(quirks); +static ssize_t nvme_admin_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", + jiffies_to_msecs(ctrl->admin_timeout)); +} + +static ssize_t nvme_admin_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + u32 timeout; + int err; + + /* + * Wait until the controller reaches the LIVE state to be sure that + * admin_q and fabrics_q are properly initialized. + */ + if (!test_bit(NVME_CTRL_STARTED_ONCE, &ctrl->flags)) + return -EBUSY; + + err = kstrtou32(buf, 10, &timeout); + if (err || !timeout) + return -EINVAL; + + ctrl->admin_timeout = msecs_to_jiffies(timeout); + + blk_queue_rq_timeout(ctrl->admin_q, ctrl->admin_timeout); + if (ctrl->fabrics_q) + blk_queue_rq_timeout(ctrl->fabrics_q, ctrl->admin_timeout); + + return count; +} + +static DEVICE_ATTR(admin_timeout, S_IRUGO | S_IWUSR, + nvme_admin_timeout_show, nvme_admin_timeout_store); + #ifdef CONFIG_NVME_HOST_AUTH static ssize_t nvme_ctrl_dhchap_secret_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -765,6 +805,7 @@ static struct attribute *nvme_dev_attrs[] = { &dev_attr_cntrltype.attr, &dev_attr_dctype.attr, &dev_attr_quirks.attr, + &dev_attr_admin_timeout.attr, #ifdef CONFIG_NVME_HOST_AUTH &dev_attr_dhchap_secret.attr, &dev_attr_dhchap_ctrl_secret.attr, From 97960b93d32a0230362c2f4dce021e98421c5a91 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 14 May 2026 10:32:52 +0200 Subject: [PATCH 012/117] nvme: add sysfs attribute to change IO timeout per controller Currently, there is no method to adjust the timeout values on a per controller basis with nvme I/O queues. Add an io_timeout attribute to nvme so that different nvme controllers which may have different timeout requirements can have custom I/O timeouts set. The I/O timeout is also applied to the connect queue (connect_q). In NVMe over Fabrics, the connect queue is utilized specifically to issue Connect commands that establish the I/O queues. Reviewed-by: Mohamed Khalfella Reviewed-by: Daniel Wagner Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 4 +++- drivers/nvme/host/nvme.h | 1 + drivers/nvme/host/sysfs.c | 47 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 20df7c12c718..b14aae0a4217 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -4203,6 +4203,7 @@ static void nvme_alloc_ns(struct nvme_ctrl *ctrl, struct nvme_ns_info *info) mutex_unlock(&ctrl->namespaces_lock); goto out_unlink_ns; } + blk_queue_rq_timeout(ns->queue, ctrl->io_timeout); nvme_ns_add_to_ctrl_list(ns); mutex_unlock(&ctrl->namespaces_lock); synchronize_srcu(&ctrl->srcu); @@ -5141,6 +5142,7 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev, ctrl->ka_cmd.common.opcode = nvme_admin_keep_alive; ctrl->ka_last_check_time = jiffies; ctrl->admin_timeout = NVME_ADMIN_TIMEOUT; + ctrl->io_timeout = NVME_IO_TIMEOUT; BUILD_BUG_ON(NVME_DSM_MAX_RANGES * sizeof(struct nvme_dsm_range) > PAGE_SIZE); @@ -5249,7 +5251,7 @@ EXPORT_SYMBOL_GPL(nvme_unfreeze); int nvme_wait_freeze_timeout(struct nvme_ctrl *ctrl) { - unsigned long timeout = NVME_IO_TIMEOUT; + unsigned long timeout = ctrl->io_timeout; struct nvme_ns *ns; int srcu_idx; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 7923533cce00..9ccaed0b9dbf 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -371,6 +371,7 @@ struct nvme_ctrl { u32 ctrl_config; u32 queue_count; u32 admin_timeout; + u32 io_timeout; u64 cap; u32 max_hw_sectors; diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 3b39b64cd9da..b682c1a4b23f 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -663,6 +663,52 @@ static ssize_t nvme_admin_timeout_store(struct device *dev, static DEVICE_ATTR(admin_timeout, S_IRUGO | S_IWUSR, nvme_admin_timeout_show, nvme_admin_timeout_store); +static ssize_t nvme_io_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(ctrl->io_timeout)); +} + +static ssize_t nvme_io_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + struct nvme_ns *ns; + u32 timeout; + int err; + + /* + * Wait until the controller reaches the LIVE state to be sure that + * connect_q is properly initialized. + */ + if (!test_bit(NVME_CTRL_STARTED_ONCE, &ctrl->flags)) + return -EBUSY; + + err = kstrtou32(buf, 10, &timeout); + if (err || !timeout) + return -EINVAL; + + /* Take the namespaces_lock to avoid racing against nvme_alloc_ns() */ + mutex_lock(&ctrl->namespaces_lock); + + ctrl->io_timeout = msecs_to_jiffies(timeout); + list_for_each_entry(ns, &ctrl->namespaces, list) + blk_queue_rq_timeout(ns->queue, ctrl->io_timeout); + + mutex_unlock(&ctrl->namespaces_lock); + + if (ctrl->connect_q) + blk_queue_rq_timeout(ctrl->connect_q, ctrl->io_timeout); + + return count; +} + +static DEVICE_ATTR(io_timeout, S_IRUGO | S_IWUSR, + nvme_io_timeout_show, nvme_io_timeout_store); + #ifdef CONFIG_NVME_HOST_AUTH static ssize_t nvme_ctrl_dhchap_secret_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -806,6 +852,7 @@ static struct attribute *nvme_dev_attrs[] = { &dev_attr_dctype.attr, &dev_attr_quirks.attr, &dev_attr_admin_timeout.attr, + &dev_attr_io_timeout.attr, #ifdef CONFIG_NVME_HOST_AUTH &dev_attr_dhchap_secret.attr, &dev_attr_dhchap_ctrl_secret.attr, From f702badaf7d31dc3dea6c66da92b5f35fadd89dc Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 14 May 2026 10:32:53 +0200 Subject: [PATCH 013/117] nvme-core: align fabrics_q teardown with admin_q in nvme_free_ctrl Currently, the final reference for the fabrics admin queue (fabrics_q) is dropped inside nvme_remove_admin_tag_set(). However, the primary admin queue (admin_q) defers dropping its final reference until nvme_free_ctrl(). Move the blk_put_queue() call for fabrics_q from nvme_remove_admin_tag_set() to nvme_free_ctrl(). This aligns the lifecycle management of both admin queues, ensuring they are freed symmetrically when the controller is finally torn down. Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Reviewed-by: Sagi Grimberg Reviewed-by: Daniel Wagner Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index b14aae0a4217..a6fe2cfb1ab1 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -4932,10 +4932,8 @@ void nvme_remove_admin_tag_set(struct nvme_ctrl *ctrl) */ nvme_stop_keep_alive(ctrl); blk_mq_destroy_queue(ctrl->admin_q); - if (ctrl->ops->flags & NVME_F_FABRICS) { + if (ctrl->fabrics_q) blk_mq_destroy_queue(ctrl->fabrics_q); - blk_put_queue(ctrl->fabrics_q); - } blk_mq_free_tag_set(ctrl->admin_tagset); } EXPORT_SYMBOL_GPL(nvme_remove_admin_tag_set); @@ -5077,6 +5075,8 @@ static void nvme_free_ctrl(struct device *dev) if (ctrl->admin_q) blk_put_queue(ctrl->admin_q); + if (ctrl->fabrics_q) + blk_put_queue(ctrl->fabrics_q); if (!subsys || ctrl->instance != subsys->instance) ida_free(&nvme_instance_ida, ctrl->instance); nvme_free_cels(ctrl); From 233bbeb4a47cbead8c0471c0b8daec141033eae4 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 14 May 2026 10:32:54 +0200 Subject: [PATCH 014/117] nvmet-loop: do not alloc admin tag set during reset Currently, resetting a loopback controller unconditionally invokes nvme_alloc_admin_tag_set() inside nvme_loop_configure_admin_queue(). Doing so drops the old queue and allocates a new one. Consequently, this reverts the admin queue's timeout (q->rq_timeout) back to the module default (NVME_ADMIN_TIMEOUT), completely wiping out any custom timeout values the user may have configured via sysfs and potentially racing against the sysfs nvme_admin_timeout_store() function that may dereference the admin_q pointer during the RESETTING state. Decouple the admin tag set lifecycle from the admin queue configuration and destruction paths, which are executed during resets; Specifically: * Move nvme_alloc_admin_tag_set() into nvme_loop_create_ctrl() so it is only allocated once during the initial controller creation. * Defer the destruction of the admin tag set to nvme_loop_delete_ctrl_host() and the terminal error-handling paths of nvme_loop_reset_ctrl_work() and nvme_loop_create_ctrl(). Reviewed-by: Daniel Wagner Reviewed-by: Sagi Grimberg Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/target/loop.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/drivers/nvme/target/loop.c b/drivers/nvme/target/loop.c index d98d0cdc5d6f..070d16068e6b 100644 --- a/drivers/nvme/target/loop.c +++ b/drivers/nvme/target/loop.c @@ -274,7 +274,6 @@ static void nvme_loop_destroy_admin_queue(struct nvme_loop_ctrl *ctrl) nvmet_sq_destroy(&ctrl->queues[0].nvme_sq); nvmet_cq_put(&ctrl->queues[0].nvme_cq); - nvme_remove_admin_tag_set(&ctrl->ctrl); } static void nvme_loop_free_ctrl(struct nvme_ctrl *nctrl) @@ -375,25 +374,18 @@ static int nvme_loop_configure_admin_queue(struct nvme_loop_ctrl *ctrl) } ctrl->ctrl.queue_count = 1; - error = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set, - &nvme_loop_admin_mq_ops, - sizeof(struct nvme_loop_iod) + - NVME_INLINE_SG_CNT * sizeof(struct scatterlist)); - if (error) - goto out_free_sq; - /* reset stopped state for the fresh admin queue */ clear_bit(NVME_CTRL_ADMIN_Q_STOPPED, &ctrl->ctrl.flags); error = nvmf_connect_admin_queue(&ctrl->ctrl); if (error) - goto out_cleanup_tagset; + goto out_free_sq; set_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[0].flags); error = nvme_enable_ctrl(&ctrl->ctrl); if (error) - goto out_cleanup_tagset; + goto out_free_sq; ctrl->ctrl.max_hw_sectors = (NVME_LOOP_MAX_SEGMENTS - 1) << PAGE_SECTORS_SHIFT; @@ -402,14 +394,12 @@ static int nvme_loop_configure_admin_queue(struct nvme_loop_ctrl *ctrl) error = nvme_init_ctrl_finish(&ctrl->ctrl, false); if (error) - goto out_cleanup_tagset; + goto out_free_sq; return 0; -out_cleanup_tagset: - clear_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[0].flags); - nvme_remove_admin_tag_set(&ctrl->ctrl); out_free_sq: + clear_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[0].flags); nvmet_sq_destroy(&ctrl->queues[0].nvme_sq); nvmet_cq_put(&ctrl->queues[0].nvme_cq); return error; @@ -432,6 +422,7 @@ static void nvme_loop_shutdown_ctrl(struct nvme_loop_ctrl *ctrl) static void nvme_loop_delete_ctrl_host(struct nvme_ctrl *ctrl) { nvme_loop_shutdown_ctrl(to_loop_ctrl(ctrl)); + nvme_remove_admin_tag_set(ctrl); } static void nvme_loop_delete_ctrl(struct nvmet_ctrl *nctrl) @@ -494,6 +485,7 @@ static void nvme_loop_reset_ctrl_work(struct work_struct *work) nvme_cancel_admin_tagset(&ctrl->ctrl); nvme_loop_destroy_admin_queue(ctrl); out_disable: + nvme_remove_admin_tag_set(&ctrl->ctrl); dev_warn(ctrl->ctrl.device, "Removing after reset failure\n"); nvme_uninit_ctrl(&ctrl->ctrl); } @@ -594,10 +586,17 @@ static struct nvme_ctrl *nvme_loop_create_ctrl(struct device *dev, if (!ctrl->queues) goto out_uninit_ctrl; - ret = nvme_loop_configure_admin_queue(ctrl); + ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set, + &nvme_loop_admin_mq_ops, + sizeof(struct nvme_loop_iod) + + NVME_INLINE_SG_CNT * sizeof(struct scatterlist)); if (ret) goto out_free_queues; + ret = nvme_loop_configure_admin_queue(ctrl); + if (ret) + goto out_remove_admin_tagset; + if (opts->queue_size > ctrl->ctrl.maxcmd) { /* warn if maxcmd is lower than queue_size */ dev_warn(ctrl->ctrl.device, @@ -633,6 +632,8 @@ static struct nvme_ctrl *nvme_loop_create_ctrl(struct device *dev, nvme_quiesce_admin_queue(&ctrl->ctrl); nvme_cancel_admin_tagset(&ctrl->ctrl); nvme_loop_destroy_admin_queue(ctrl); +out_remove_admin_tagset: + nvme_remove_admin_tag_set(&ctrl->ctrl); out_free_queues: kfree(ctrl->queues); out_uninit_ctrl: From 00d7b33351aac0ea55d17167561e12bbeca73138 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 14 May 2026 10:32:55 +0200 Subject: [PATCH 015/117] nvme-core: warn on allocating admin tag set with existing queue Currently, nvme_alloc_admin_tag_set() silently drops and releases the existing admin_q if it called on a controller that already had one (e.g., during a controller reset). However, transport drivers should not be reallocating the admin tag set and queue during a reset. Dropping the old queue and allocating a new one destroys user-configured timeouts and may race against nvme_admin_timeout_store() Since all transport drivers are now expected to preserve the admin queue across resets, calling nvme_alloc_admin_tag_set() when ctrl->admin_q is already populated is a bug. Remove the silent cleanup and replace it with a WARN_ON_ONCE() to explicitly catch any transport drivers that violate this lifecycle rule Reviewed-by: Sagi Grimberg Reviewed-by: Christoph Hellwig Reviewed-by: Daniel Wagner Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index a6fe2cfb1ab1..72c50d5e938d 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -4889,12 +4889,7 @@ int nvme_alloc_admin_tag_set(struct nvme_ctrl *ctrl, struct blk_mq_tag_set *set, if (ret) return ret; - /* - * If a previous admin queue exists (e.g., from before a reset), - * put it now before allocating a new one to avoid orphaning it. - */ - if (ctrl->admin_q) - blk_put_queue(ctrl->admin_q); + WARN_ON_ONCE(ctrl->admin_q); ctrl->admin_q = blk_mq_alloc_queue(set, NULL, NULL); if (IS_ERR(ctrl->admin_q)) { From c8cdecdb47d3191146ab6a90b422d3271bc1ef89 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Fri, 15 May 2026 14:58:53 -0400 Subject: [PATCH 016/117] nvme: core: reject invalid LBA data size from Identify Namespace nvme_update_ns_info_block() trusts id->lbaf[lbaf].ds from the controller and assigns it directly to ns->head->lba_shift without bounds checking. nvme_lba_to_sect() then does: return lba << (head->lba_shift - SECTOR_SHIFT); When called with lba = le64_to_cpu(id->nsze) to compute the device capacity, an attacker-controlled controller can choose ds < 9 or a combination of (ds, nsze) that makes the left shift overflow sector_t. The former is a C undefined behaviour that UBSAN reports as a BUG; the latter silently yields a bogus capacity that the block layer then trusts for bounds checking. Validate ds against SECTOR_SHIFT and use check_shl_overflow() to compute capacity so that any (ds, nsze) combination that would overflow sector_t is rejected. The namespace is skipped with -ENODEV instead of crashing the kernel. This is reachable by a malicious NVMe device, a buggy firmware, or an attacker-controlled NVMe-oF target. The check is performed before queue_limits_start_update() and blk_mq_freeze_queue(), so the error path is a plain `goto out` with no cleanup needed. Stack trace (UBSAN, ds < 9 variant): RIP: nvme_lba_to_sect drivers/nvme/host/nvme.h:699 [inline] RIP: nvme_update_ns_info_block.cold+0x5/0x7 Call Trace: nvme_update_ns_info+0x175/0xd90 drivers/nvme/host/core.c:2467 nvme_validate_ns drivers/nvme/host/core.c:4299 [inline] nvme_scan_ns drivers/nvme/host/core.c:4350 nvme_scan_ns_async+0xa5/0xe0 drivers/nvme/host/core.c:4383 async_run_entry_fn process_one_work worker_thread kthread Found by Syzkaller. Acked-by: Sungwoo Kim Acked-by: Dave Tian Acked-by: Weidong Zhu Signed-off-by: Chao Shi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 72c50d5e938d..10f154529334 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2407,12 +2407,22 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns, goto out; } + if (id->lbaf[lbaf].ds < SECTOR_SHIFT || + check_shl_overflow(le64_to_cpu(id->nsze), + id->lbaf[lbaf].ds - SECTOR_SHIFT, + &capacity)) { + dev_warn_once(ns->ctrl->device, + "invalid LBA data size %u, skipping namespace\n", + id->lbaf[lbaf].ds); + ret = -ENODEV; + goto out; + } + lim = queue_limits_start_update(ns->disk->queue); memflags = blk_mq_freeze_queue(ns->disk->queue); ns->head->lba_shift = id->lbaf[lbaf].ds; ns->head->nuse = le64_to_cpu(id->nuse); - capacity = nvme_lba_to_sect(ns->head, le64_to_cpu(id->nsze)); nvme_set_ctrl_limits(ns->ctrl, &lim, false); nvme_configure_metadata(ns->ctrl, ns->head, id, nvm, info); nvme_set_chunk_sectors(ns, id, &lim); From 999722b34441b4ab65b7ca7fb16dd4b62fc3c354 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Wed, 13 May 2026 15:18:45 -0600 Subject: [PATCH 017/117] blk-mq: introduce blk_rq_has_data() Add blk_rq_has_data(), an analogue of bio_has_data() for struct request. This skips one dereference relative to bio_has_data(rq->bio). Signed-off-by: Caleb Sander Mateos Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260513211846.1956810-2-csander@purestorage.com Signed-off-by: Jens Axboe --- include/linux/blk-mq.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h index 18a2388ba581..4349aefdbc87 100644 --- a/include/linux/blk-mq.h +++ b/include/linux/blk-mq.h @@ -1104,6 +1104,7 @@ struct req_iterator { /* * blk_rq_pos() : the current sector * blk_rq_bytes() : bytes left in the entire request + * blk_rq_has_data() : whether the request carries data * blk_rq_cur_bytes() : bytes left in the current segment * blk_rq_sectors() : sectors left in the entire request * blk_rq_cur_sectors() : sectors left in the current segment @@ -1119,6 +1120,14 @@ static inline unsigned int blk_rq_bytes(const struct request *rq) return rq->__data_len; } +static inline bool blk_rq_has_data(const struct request *rq) +{ + return blk_rq_bytes(rq) && + req_op(rq) != REQ_OP_DISCARD && + req_op(rq) != REQ_OP_SECURE_ERASE && + req_op(rq) != REQ_OP_WRITE_ZEROES; +} + static inline int blk_rq_cur_bytes(const struct request *rq) { if (!rq->bio) From 5995e751d2612cd8254cdf9c1155a96bbbb2d509 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Wed, 13 May 2026 15:18:46 -0600 Subject: [PATCH 018/117] ublk: optimize ublk_rq_has_data() ublk_rq_has_data() currently uses bio_has_data(), which involves 2 indirections and several branches. Use blk_rq_has_data() instead to save an indirection and NULL check. Signed-off-by: Caleb Sander Mateos Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260513211846.1956810-3-csander@purestorage.com Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 8e5f3738c203..4d7efc12247c 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -1172,11 +1172,6 @@ static inline struct ublk_queue *ublk_get_queue(struct ublk_device *dev, return dev->queues[qid]; } -static inline bool ublk_rq_has_data(const struct request *rq) -{ - return bio_has_data(rq->bio); -} - static inline struct ublksrv_io_desc * ublk_queue_cmd_buf(struct ublk_device *ub, int q_id) { @@ -1389,12 +1384,12 @@ static size_t ublk_copy_user_integrity(const struct request *req, static inline bool ublk_need_map_req(const struct request *req) { - return ublk_rq_has_data(req) && req_op(req) == REQ_OP_WRITE; + return blk_rq_has_data(req) && req_op(req) == REQ_OP_WRITE; } static inline bool ublk_need_unmap_req(const struct request *req) { - return ublk_rq_has_data(req) && + return blk_rq_has_data(req) && (req_op(req) == REQ_OP_READ || req_op(req) == REQ_OP_DRV_IN); } @@ -1508,7 +1503,7 @@ static blk_status_t ublk_setup_iod(struct ublk_queue *ubq, struct request *req) iod->start_sector = blk_rq_pos(req); /* Try shmem zero-copy match before setting addr */ - if (ublk_support_shmem_zc(ubq) && ublk_rq_has_data(req)) { + if (ublk_support_shmem_zc(ubq) && blk_rq_has_data(req)) { u32 buf_idx, buf_off; if (ublk_try_buf_match(ubq->dev, req, @@ -1798,7 +1793,7 @@ static void ublk_dispatch_req(struct ublk_queue *ubq, struct request *req) if (!ublk_start_io(ubq, req, io)) return; - if (ublk_support_auto_buf_reg(ubq) && ublk_rq_has_data(req)) { + if (ublk_support_auto_buf_reg(ubq) && blk_rq_has_data(req)) { ublk_auto_buf_dispatch(ubq, req, io, io->cmd, issue_flags); } else { ublk_init_req_ref(ubq, io); @@ -1819,7 +1814,7 @@ static bool __ublk_batch_prep_dispatch(struct ublk_queue *ubq, if (!ublk_start_io(ubq, req, io)) return false; - if (ublk_support_auto_buf_reg(ubq) && ublk_rq_has_data(req)) { + if (ublk_support_auto_buf_reg(ubq) && blk_rq_has_data(req)) { res = ublk_auto_buf_register(ubq, req, io, cmd, data->issue_flags); @@ -3200,7 +3195,7 @@ ublk_daemon_register_io_buf(struct io_uring_cmd *cmd, return ublk_register_io_buf(cmd, ub, q_id, tag, io, index, issue_flags); - if (!ublk_dev_support_zero_copy(ub) || !ublk_rq_has_data(req)) + if (!ublk_dev_support_zero_copy(ub) || !blk_rq_has_data(req)) return -EINVAL; ret = io_buffer_register_bvec(cmd, req, ublk_io_release, index, @@ -3483,7 +3478,7 @@ static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, if (unlikely(!blk_mq_request_started(req) || req->tag != tag)) goto fail_put; - if (!ublk_rq_has_data(req)) + if (!blk_rq_has_data(req)) goto fail_put; return req; @@ -4056,7 +4051,7 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) return -EINVAL; req = io->req; - if (!ublk_rq_has_data(req)) + if (!blk_rq_has_data(req)) return -EINVAL; } else { req = __ublk_check_and_get_req(ub, q_id, tag, io); From eee9224affae6c1bfd664e5b769e40e3ff099879 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Wed, 20 May 2026 14:36:53 -0600 Subject: [PATCH 019/117] ublk: move ublk_req_build_flags() earlier Move ublk_req_build_flags() above its callers so it doesn't need to be forward-declared. Signed-off-by: Caleb Sander Mateos Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260520203654.1413640-2-csander@purestorage.com Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 63 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 4d7efc12247c..0cb29be561b5 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -359,7 +359,6 @@ static void ublk_buf_cleanup(struct ublk_device *ub); static void ublk_abort_queue(struct ublk_device *ub, struct ublk_queue *ubq); static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, u16 q_id, u16 tag, struct ublk_io *io); -static inline unsigned int ublk_req_build_flags(struct request *req); static void ublk_batch_dispatch(struct ublk_queue *ubq, const struct ublk_batch_io_data *data, struct ublk_batch_fetch_cmd *fcmd); @@ -471,6 +470,37 @@ static inline bool ublk_dev_support_integrity(const struct ublk_device *ub) return ub->dev_info.flags & UBLK_F_INTEGRITY; } +static inline unsigned int ublk_req_build_flags(struct request *req) +{ + unsigned flags = 0; + + if (req->cmd_flags & REQ_FAILFAST_DEV) + flags |= UBLK_IO_F_FAILFAST_DEV; + + if (req->cmd_flags & REQ_FAILFAST_TRANSPORT) + flags |= UBLK_IO_F_FAILFAST_TRANSPORT; + + if (req->cmd_flags & REQ_FAILFAST_DRIVER) + flags |= UBLK_IO_F_FAILFAST_DRIVER; + + if (req->cmd_flags & REQ_META) + flags |= UBLK_IO_F_META; + + if (req->cmd_flags & REQ_FUA) + flags |= UBLK_IO_F_FUA; + + if (req->cmd_flags & REQ_NOUNMAP) + flags |= UBLK_IO_F_NOUNMAP; + + if (req->cmd_flags & REQ_SWAP) + flags |= UBLK_IO_F_SWAP; + + if (blk_integrity_rq(req)) + flags |= UBLK_IO_F_INTEGRITY; + + return flags; +} + #ifdef CONFIG_BLK_DEV_ZONED struct ublk_zoned_report_desc { @@ -1438,37 +1468,6 @@ static unsigned int ublk_unmap_io(bool need_map, return rq_bytes; } -static inline unsigned int ublk_req_build_flags(struct request *req) -{ - unsigned flags = 0; - - if (req->cmd_flags & REQ_FAILFAST_DEV) - flags |= UBLK_IO_F_FAILFAST_DEV; - - if (req->cmd_flags & REQ_FAILFAST_TRANSPORT) - flags |= UBLK_IO_F_FAILFAST_TRANSPORT; - - if (req->cmd_flags & REQ_FAILFAST_DRIVER) - flags |= UBLK_IO_F_FAILFAST_DRIVER; - - if (req->cmd_flags & REQ_META) - flags |= UBLK_IO_F_META; - - if (req->cmd_flags & REQ_FUA) - flags |= UBLK_IO_F_FUA; - - if (req->cmd_flags & REQ_NOUNMAP) - flags |= UBLK_IO_F_NOUNMAP; - - if (req->cmd_flags & REQ_SWAP) - flags |= UBLK_IO_F_SWAP; - - if (blk_integrity_rq(req)) - flags |= UBLK_IO_F_INTEGRITY; - - return flags; -} - static blk_status_t ublk_setup_iod(struct ublk_queue *ubq, struct request *req) { struct ublksrv_io_desc *iod = ublk_get_iod(ubq, req->tag); From 23130b3ffcdb1568a9ef178ab3cba866e5486082 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Wed, 20 May 2026 14:36:54 -0600 Subject: [PATCH 020/117] ublk: factor out ublk_init_iod() helper The code for initializing struct ublksrv_io_desc on I/O dispatch is largely duplicated in 3 places. Commit 4d4a512a1f87 ("ublk: add PFN- based buffer matching in I/O path") added support to ublk_setup_iod() for matching request buffers against registered UBLK_F_SHMEM_ZC buffers, but missed adding it to ublk_setup_iod_zoned() for zoned requests. Move the duplicated logic to a new helper ublk_init_iod(). This way, zone appends can also benefit from avoiding the data copy. Signed-off-by: Caleb Sander Mateos Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260520203654.1413640-3-csander@purestorage.com Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 60 +++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 0cb29be561b5..49624e65fe75 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -501,6 +501,31 @@ static inline unsigned int ublk_req_build_flags(struct request *req) return flags; } +static void ublk_init_iod(struct ublk_queue *ubq, struct request *req, + uint8_t ublk_op, uint32_t nr_sectors, + uint64_t start_sector) +{ + struct ublksrv_io_desc *iod = ublk_get_iod(ubq, req->tag); + struct ublk_io *io = &ubq->ios[req->tag]; + + iod->op_flags = ublk_op | ublk_req_build_flags(req); + iod->nr_sectors = nr_sectors; + iod->start_sector = start_sector; + + /* Try shmem zero-copy match before setting addr */ + if (ublk_support_shmem_zc(ubq) && blk_rq_has_data(req)) { + u32 buf_idx, buf_off; + + if (ublk_try_buf_match(ubq->dev, req, &buf_idx, &buf_off)) { + iod->op_flags |= UBLK_IO_F_SHMEM_ZC; + iod->addr = ublk_shmem_zc_addr(buf_idx, buf_off); + return; + } + } + + iod->addr = io->buf.addr; +} + #ifdef CONFIG_BLK_DEV_ZONED struct ublk_zoned_report_desc { @@ -682,8 +707,6 @@ static int ublk_report_zones(struct gendisk *disk, sector_t sector, static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq, struct request *req) { - struct ublksrv_io_desc *iod = ublk_get_iod(ubq, req->tag); - struct ublk_io *io = &ubq->ios[req->tag]; struct ublk_zoned_report_desc *desc; u32 ublk_op; @@ -713,9 +736,8 @@ static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq, ublk_op = desc->operation; switch (ublk_op) { case UBLK_IO_OP_REPORT_ZONES: - iod->op_flags = ublk_op | ublk_req_build_flags(req); - iod->nr_zones = desc->nr_zones; - iod->start_sector = desc->sector; + ublk_init_iod(ubq, req, ublk_op, desc->nr_zones, + desc->sector); return BLK_STS_OK; default: return BLK_STS_IOERR; @@ -727,11 +749,7 @@ static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq, return BLK_STS_IOERR; } - iod->op_flags = ublk_op | ublk_req_build_flags(req); - iod->nr_sectors = blk_rq_sectors(req); - iod->start_sector = blk_rq_pos(req); - iod->addr = io->buf.addr; - + ublk_init_iod(ubq, req, ublk_op, blk_rq_sectors(req), blk_rq_pos(req)); return BLK_STS_OK; } @@ -1470,8 +1488,6 @@ static unsigned int ublk_unmap_io(bool need_map, static blk_status_t ublk_setup_iod(struct ublk_queue *ubq, struct request *req) { - struct ublksrv_io_desc *iod = ublk_get_iod(ubq, req->tag); - struct ublk_io *io = &ubq->ios[req->tag]; u32 ublk_op; switch (req_op(req)) { @@ -1496,25 +1512,7 @@ static blk_status_t ublk_setup_iod(struct ublk_queue *ubq, struct request *req) return BLK_STS_IOERR; } - /* need to translate since kernel may change */ - iod->op_flags = ublk_op | ublk_req_build_flags(req); - iod->nr_sectors = blk_rq_sectors(req); - iod->start_sector = blk_rq_pos(req); - - /* Try shmem zero-copy match before setting addr */ - if (ublk_support_shmem_zc(ubq) && blk_rq_has_data(req)) { - u32 buf_idx, buf_off; - - if (ublk_try_buf_match(ubq->dev, req, - &buf_idx, &buf_off)) { - iod->op_flags |= UBLK_IO_F_SHMEM_ZC; - iod->addr = ublk_shmem_zc_addr(buf_idx, buf_off); - return BLK_STS_OK; - } - } - - iod->addr = io->buf.addr; - + ublk_init_iod(ubq, req, ublk_op, blk_rq_sectors(req), blk_rq_pos(req)); return BLK_STS_OK; } From 148cd4873115feb266c002d4d4618ea7f14342d9 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 26 May 2026 10:21:24 +0000 Subject: [PATCH 021/117] block: partitions: fix of_node refcount leak in of_partition() of_partition() calls of_node_get() on the parent device node at the beginning of the function, storing the reference in 'partitions_np'. This reference is leaked in two paths: 1. The compatibility check at the top of the function returns 0 without releasing partitions_np when the node exists but is not "fixed-partitions" compatible. 2. The function returns 1 at the end after successfully processing all partitions without releasing partitions_np. Fix both leaks by adding of_node_put(partitions_np) on each path. Fixes: 2e3a191e89f9 ("block: add support for partition table defined in OF") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Md Haris Iqbal Link: https://patch.msgid.link/20260526102124.2283846-1-vulab@iscas.ac.cn Signed-off-by: Jens Axboe --- block/partitions/of.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/block/partitions/of.c b/block/partitions/of.c index c22b60661098..53664ea06b65 100644 --- a/block/partitions/of.c +++ b/block/partitions/of.c @@ -74,8 +74,10 @@ int of_partition(struct parsed_partitions *state) struct device_node *partitions_np = of_node_get(ddev->of_node); if (!partitions_np || - !of_device_is_compatible(partitions_np, "fixed-partitions")) + !of_device_is_compatible(partitions_np, "fixed-partitions")) { + of_node_put(partitions_np); return 0; + } slot = 1; /* Validate parition offset and size */ @@ -104,5 +106,6 @@ int of_partition(struct parsed_partitions *state) seq_buf_puts(&state->pp_buf, "\n"); + of_node_put(partitions_np); return 1; } From 4d94ec1bc12c4d9d6fe428e4cf2652e187058373 Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Sun, 24 May 2026 20:51:23 -0400 Subject: [PATCH 022/117] blk-mq: add tracepoint block_rq_tag_wait In high-performance storage environments, particularly when utilising RAID controllers with shared tag sets (BLK_MQ_F_TAG_HCTX_SHARED), severe latency spikes can occur when fast devices (SSDs) are starved of hardware tags when sharing the same blk_mq_tag_set. Currently, diagnosing this specific hardware queue contention is difficult. When a CPU thread exhausts the tag pool, blk_mq_get_tag() forces the current thread to block uninterruptible via io_schedule(). While this can be inferred via sched:sched_switch or dynamically traced by attaching a kprobe to blk_mq_mark_tag_wait(), there is no dedicated, out-of-the-box observability for this event. This patch introduces the block_rq_tag_wait tracepoint in the tag allocation slow-path. It triggers immediately before the task state is altered to TASK_UNINTERRUPTIBLE (ensuring safety for PREEMPT_RT locks). It exposes the exact hardware context (hctx) that is starved, the specific pool experiencing starvation (driver, software scheduler, or reserved), and the exact pool depth. This provides storage engineers with a zero-configuration, low-overhead mechanism to definitively identify shared-tag bottlenecks. For example, userspace can trivially replicate tag starvation counters using bpftrace: # bpftrace -e 'tracepoint:block:block_rq_tag_wait { @tag_waits[cpu] = count(); }' Attaching 1 probe... ^C @tag_waits[4]: 12 @tag_waits[12]: 87 Signed-off-by: Aaron Tomlin Link: https://patch.msgid.link/20260525005123.722277-1-atomlin@atomlin.com Signed-off-by: Jens Axboe --- block/blk-mq-tag.c | 6 ++++ include/trace/events/block.h | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/block/blk-mq-tag.c b/block/blk-mq-tag.c index 33946cdb5716..35deee5bbc73 100644 --- a/block/blk-mq-tag.c +++ b/block/blk-mq-tag.c @@ -13,6 +13,7 @@ #include #include +#include #include "blk.h" #include "blk-mq.h" #include "blk-mq-sched.h" @@ -181,6 +182,11 @@ unsigned int blk_mq_get_tag(struct blk_mq_alloc_data *data) if (tag != BLK_MQ_NO_TAG) break; + /* Log the starvation event before altering task state */ + trace_block_rq_tag_wait(data->q, data->hctx, + data->rq_flags & RQF_SCHED_TAGS, + data->flags); + sbitmap_prepare_to_wait(bt, ws, &wait, TASK_UNINTERRUPTIBLE); tag = __blk_mq_get_tag(data, bt); diff --git a/include/trace/events/block.h b/include/trace/events/block.h index 6aa79e2d799c..9c97a16850b9 100644 --- a/include/trace/events/block.h +++ b/include/trace/events/block.h @@ -226,6 +226,65 @@ DECLARE_EVENT_CLASS(block_rq, IOPRIO_PRIO_LEVEL(__entry->ioprio), __entry->comm) ); +/** + * block_rq_tag_wait - triggered when a request is starved of a tag + * @q: request queue of the target device + * @hctx: hardware context of the request experiencing starvation + * @is_sched_tag: indicates whether the starved pool is the software scheduler + * @alloc_flags: allocation flags dictating the specific tag pool + * + * Called immediately before the submitting context is forced to block due + * to the exhaustion of available tags (i.e., physical hardware driver + * tags, software scheduler tags, or reserved tags). This trace point + * indicates that the context will be placed into an uninterruptible state + * via sbitmap_prepare_to_wait(). If a tag is not acquired in the final + * lockless retry, the context will yield the CPU via io_schedule() until + * an active request completes and relinquishes its assigned tag. + */ +TRACE_EVENT(block_rq_tag_wait, + + TP_PROTO(struct request_queue *q, struct blk_mq_hw_ctx *hctx, + bool is_sched_tag, unsigned int alloc_flags), + + TP_ARGS(q, hctx, is_sched_tag, alloc_flags), + + TP_STRUCT__entry( + __field( dev_t, dev ) + __field( u32, hctx_id ) + __field( u32, nr_tags ) + __field( bool, is_sched_tag ) + __field( bool, is_reserved ) + ), + + TP_fast_assign( + __entry->dev = q->disk ? disk_devt(q->disk) : 0; + __entry->hctx_id = hctx->queue_num; + __entry->is_sched_tag = is_sched_tag; + __entry->is_reserved = alloc_flags & BLK_MQ_REQ_RESERVED; + + if (__entry->is_reserved) { + __entry->nr_tags = is_sched_tag ? + hctx->sched_tags->nr_reserved_tags : + hctx->tags->nr_reserved_tags; + } else { + if (is_sched_tag) + __entry->nr_tags = hctx->sched_tags->nr_tags - + hctx->sched_tags->nr_reserved_tags; + else + __entry->nr_tags = hctx->tags->nr_tags - + hctx->tags->nr_reserved_tags; + } + + ), + + TP_printk("%d,%d hctx=%u starved on %s%s tags (depth=%u)", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->hctx_id, + __entry->is_sched_tag ? "scheduler" : "hardware", + __entry->is_reserved ? " reserved" : "", + __entry->nr_tags) +); + /** * block_rq_insert - insert block operation request into queue * @rq: block IO operation request From 49f06cff50a4ccf3b7a1a662ceb892b3b21a527a Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Fri, 22 May 2026 18:00:25 -0400 Subject: [PATCH 023/117] block: skip sync_blockdev() on surprise removal in bdev_mark_dead() bdev_mark_dead()'s @surprise == true means the device is already gone. The filesystem callback fs_bdev_mark_dead() honours this and skips sync_filesystem(), but the bare block device path (no ->mark_dead op) lost its !surprise guard when the holder ->mark_dead callback was wired up (see Fixes), and now calls sync_blockdev() unconditionally, which can hang forever waiting on writeback that can no longer complete. syzkaller hit this via nvme_reset_work()'s "I/O queues lost" path: nvme_mark_namespaces_dead() -> blk_mark_disk_dead() -> bdev_mark_dead(bdev, true) -> sync_blockdev() blocks in folio_wait_writeback(), wedging the reset worker and every task waiting on it. Skip the sync on surprise removal, matching fs_bdev_mark_dead(); invalidate_bdev() still runs. Orderly removal (surprise == false) is unchanged. Found by FuzzNvme(Syzkaller with FEMU fuzzing framework). Fixes: d8530de5a6e8 ("block: call into the file system for bdev_mark_dead") Acked-by: Sungwoo Kim Acked-by: Dave Tian Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260522220025.1770388-1-coshi036@gmail.com Signed-off-by: Jens Axboe --- block/bdev.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/block/bdev.c b/block/bdev.c index bb0ffa3bb4df..e44a73903201 100644 --- a/block/bdev.c +++ b/block/bdev.c @@ -1250,7 +1250,13 @@ void bdev_mark_dead(struct block_device *bdev, bool surprise) bdev->bd_holder_ops->mark_dead(bdev, surprise); else { mutex_unlock(&bdev->bd_holder_lock); - sync_blockdev(bdev); + /* + * On surprise removal the device is already gone; syncing is + * futile and can hang forever waiting on I/O that will never + * complete. Match fs_bdev_mark_dead(), which also skips it. + */ + if (!surprise) + sync_blockdev(bdev); } invalidate_bdev(bdev); From b040a1a4523d99a935cb6566b1e2a753c84733cd Mon Sep 17 00:00:00 2001 From: Mateusz Nowicki Date: Sat, 23 May 2026 12:52:35 +0000 Subject: [PATCH 024/117] block: switch numa_node to int in blk_mq_hw_ctx and init_request numa_node in blk_mq_hw_ctx and the matching argument of blk_mq_ops::init_request can be NUMA_NO_NODE (-1). Declared as unsigned int, NUMA_NO_NODE becomes UINT_MAX and walks off nvme_dev::descriptor_pools[] on CONFIG_NUMA=n [1]. Switch the field and the callback prototype to int and update all in-tree init_request implementations. No functional change: cpu_to_node(), kmalloc_node() and blk_alloc_flush_queue() already take int. Link: https://lore.kernel.org/linux-nvme/20260522150628.399288-1-mateusz.nowicki@posteo.net/ [1] Link: https://lore.kernel.org/linux-nvme/20260309062840.2937858-2-iam@sung-woo.kim/ Suggested-by: Caleb Sander Mateos Suggested-by: Sung-woo Kim Signed-off-by: Mateusz Nowicki Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260523125210.272274-1-mateusz.nowicki@posteo.net Signed-off-by: Jens Axboe --- block/bsg-lib.c | 2 +- drivers/block/mtip32xx/mtip32xx.c | 2 +- drivers/block/nbd.c | 2 +- drivers/md/dm-rq.c | 2 +- drivers/mmc/core/queue.c | 2 +- drivers/mtd/ubi/block.c | 2 +- drivers/nvme/host/apple.c | 2 +- drivers/nvme/host/fc.c | 2 +- drivers/nvme/host/pci.c | 2 +- drivers/nvme/host/rdma.c | 2 +- drivers/nvme/host/tcp.c | 2 +- drivers/nvme/target/loop.c | 2 +- drivers/scsi/scsi_lib.c | 2 +- include/linux/blk-mq.h | 4 ++-- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/block/bsg-lib.c b/block/bsg-lib.c index fdb4b290ca68..895db30a7033 100644 --- a/block/bsg-lib.c +++ b/block/bsg-lib.c @@ -299,7 +299,7 @@ static blk_status_t bsg_queue_rq(struct blk_mq_hw_ctx *hctx, /* called right after the request is allocated for the request_queue */ static int bsg_init_rq(struct blk_mq_tag_set *set, struct request *req, - unsigned int hctx_idx, unsigned int numa_node) + unsigned int hctx_idx, int numa_node) { struct bsg_job *job = blk_mq_rq_to_pdu(req); diff --git a/drivers/block/mtip32xx/mtip32xx.c b/drivers/block/mtip32xx/mtip32xx.c index 567192e371a8..8aedba9b5690 100644 --- a/drivers/block/mtip32xx/mtip32xx.c +++ b/drivers/block/mtip32xx/mtip32xx.c @@ -3340,7 +3340,7 @@ static void mtip_free_cmd(struct blk_mq_tag_set *set, struct request *rq, } static int mtip_init_cmd(struct blk_mq_tag_set *set, struct request *rq, - unsigned int hctx_idx, unsigned int numa_node) + unsigned int hctx_idx, int numa_node) { struct driver_data *dd = set->driver_data; struct mtip_cmd *cmd = blk_mq_rq_to_pdu(rq); diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c index fe63f3c55d0d..e2fe9e3308fc 100644 --- a/drivers/block/nbd.c +++ b/drivers/block/nbd.c @@ -1888,7 +1888,7 @@ static void nbd_dbg_close(void) #endif static int nbd_init_request(struct blk_mq_tag_set *set, struct request *rq, - unsigned int hctx_idx, unsigned int numa_node) + unsigned int hctx_idx, int numa_node) { struct nbd_cmd *cmd = blk_mq_rq_to_pdu(rq); cmd->nbd = set->driver_data; diff --git a/drivers/md/dm-rq.c b/drivers/md/dm-rq.c index 9703b3ae364e..9a386254d836 100644 --- a/drivers/md/dm-rq.c +++ b/drivers/md/dm-rq.c @@ -462,7 +462,7 @@ static void dm_start_request(struct mapped_device *md, struct request *orig) } static int dm_mq_init_request(struct blk_mq_tag_set *set, struct request *rq, - unsigned int hctx_idx, unsigned int numa_node) + unsigned int hctx_idx, int numa_node) { struct mapped_device *md = set->driver_data; struct dm_rq_target_io *tio = blk_mq_rq_to_pdu(rq); diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c index 39fcb662c43f..cfa268925c26 100644 --- a/drivers/mmc/core/queue.c +++ b/drivers/mmc/core/queue.c @@ -208,7 +208,7 @@ static unsigned short mmc_get_max_segments(struct mmc_host *host) } static int mmc_mq_init_request(struct blk_mq_tag_set *set, struct request *req, - unsigned int hctx_idx, unsigned int numa_node) + unsigned int hctx_idx, int numa_node) { struct mmc_queue_req *mq_rq = req_to_mmc_queue_req(req); struct mmc_queue *mq = set->driver_data; diff --git a/drivers/mtd/ubi/block.c b/drivers/mtd/ubi/block.c index 8880a783c3bc..29c0d6941a81 100644 --- a/drivers/mtd/ubi/block.c +++ b/drivers/mtd/ubi/block.c @@ -312,7 +312,7 @@ static blk_status_t ubiblock_queue_rq(struct blk_mq_hw_ctx *hctx, static int ubiblock_init_request(struct blk_mq_tag_set *set, struct request *req, unsigned int hctx_idx, - unsigned int numa_node) + int numa_node) { struct ubiblock_pdu *pdu = blk_mq_rq_to_pdu(req); diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 423c9c628e7b..7fc6b9eacf2e 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -819,7 +819,7 @@ static int apple_nvme_init_hctx(struct blk_mq_hw_ctx *hctx, void *data, static int apple_nvme_init_request(struct blk_mq_tag_set *set, struct request *req, unsigned int hctx_idx, - unsigned int numa_node) + int numa_node) { struct apple_nvme_queue *q = set->driver_data; struct apple_nvme *anv = queue_to_apple_nvme(q); diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index e4f4528fe2a2..1907da499ad2 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -2109,7 +2109,7 @@ __nvme_fc_init_request(struct nvme_fc_ctrl *ctrl, static int nvme_fc_init_request(struct blk_mq_tag_set *set, struct request *rq, - unsigned int hctx_idx, unsigned int numa_node) + unsigned int hctx_idx, int numa_node) { struct nvme_fc_ctrl *ctrl = to_fc_ctrl(set->driver_data); struct nvme_fcp_op_w_sgl *op = blk_mq_rq_to_pdu(rq); diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 9fd04cd7c5cb..24911e1252d5 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -660,7 +660,7 @@ static int nvme_init_hctx(struct blk_mq_hw_ctx *hctx, void *data, static int nvme_pci_init_request(struct blk_mq_tag_set *set, struct request *req, unsigned int hctx_idx, - unsigned int numa_node) + int numa_node) { struct nvme_iod *iod = blk_mq_rq_to_pdu(req); diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index f77c960f7632..08459c65c3d5 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -292,7 +292,7 @@ static void nvme_rdma_exit_request(struct blk_mq_tag_set *set, static int nvme_rdma_init_request(struct blk_mq_tag_set *set, struct request *rq, unsigned int hctx_idx, - unsigned int numa_node) + int numa_node) { struct nvme_rdma_ctrl *ctrl = to_rdma_ctrl(set->driver_data); struct nvme_rdma_request *req = blk_mq_rq_to_pdu(rq); diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 15d36d6a728e..36b3ec50a9fd 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -548,7 +548,7 @@ static void nvme_tcp_exit_request(struct blk_mq_tag_set *set, static int nvme_tcp_init_request(struct blk_mq_tag_set *set, struct request *rq, unsigned int hctx_idx, - unsigned int numa_node) + int numa_node) { struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(set->driver_data); struct nvme_tcp_request *req = blk_mq_rq_to_pdu(rq); diff --git a/drivers/nvme/target/loop.c b/drivers/nvme/target/loop.c index d98d0cdc5d6f..ae00bcef2251 100644 --- a/drivers/nvme/target/loop.c +++ b/drivers/nvme/target/loop.c @@ -202,7 +202,7 @@ static int nvme_loop_init_iod(struct nvme_loop_ctrl *ctrl, static int nvme_loop_init_request(struct blk_mq_tag_set *set, struct request *req, unsigned int hctx_idx, - unsigned int numa_node) + int numa_node) { struct nvme_loop_ctrl *ctrl = to_loop_ctrl(set->driver_data); struct nvme_loop_iod *iod = blk_mq_rq_to_pdu(req); diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 6e8c7a42603e..67f789bd02e7 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -1950,7 +1950,7 @@ static blk_status_t scsi_queue_rq(struct blk_mq_hw_ctx *hctx, } static int scsi_mq_init_request(struct blk_mq_tag_set *set, struct request *rq, - unsigned int hctx_idx, unsigned int numa_node) + unsigned int hctx_idx, int numa_node) { struct Scsi_Host *shost = set->driver_data; struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(rq); diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h index 4349aefdbc87..24b4160aeaad 100644 --- a/include/linux/blk-mq.h +++ b/include/linux/blk-mq.h @@ -428,7 +428,7 @@ struct blk_mq_hw_ctx { struct blk_mq_tags *sched_tags; /** @numa_node: NUMA node the storage adapter has been connected to. */ - unsigned int numa_node; + int numa_node; /** @queue_num: Index of this hardware queue. */ unsigned int queue_num; @@ -653,7 +653,7 @@ struct blk_mq_ops { * flush request. */ int (*init_request)(struct blk_mq_tag_set *set, struct request *, - unsigned int, unsigned int); + unsigned int, int); /** * @exit_request: Ditto for exit/teardown. */ From f73aa66dffcb8e61e78f01b56163ec16a15d06d2 Mon Sep 17 00:00:00 2001 From: Denis Arefev Date: Thu, 21 May 2026 10:28:56 +0300 Subject: [PATCH 025/117] block: Avoid mounting the bdev pseudo-filesystem in userspace The bdev pseudo-filesystem is an internal kernel filesystem with which userspace should not interfere. Unregister it so that userspace cannot even attempt to mount it. This fixes a bug [1] that occurs when attempting to access files, because the system call move_mount() uses pointers declared in the inode_operations structure, which for the bdev pseudo-filesystem are always equal to 0. `inode->i_op = &empty_iops;` [1] BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor instruction fetch in kernel mode #PF: error_code(0x0010) - not-present page PGD 23380067 P4D 23380067 PUD 23381067 PMD 0 Oops: 0010 [#1] PREEMPT SMP KASAN NOPTI CPU: 2 PID: 17125 Comm: syz-executor.0 Not tainted 6.1.155-syzkaller-00350-g84221fde2681 #0 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1 04/01/2014 RIP: 0010:0x0 Call Trace: lookup_open.isra.0+0x700/0x1180 fs/namei.c:3460 open_last_lookups fs/namei.c:3550 [inline] path_openat+0x953/0x2700 fs/namei.c:3780 do_filp_open+0x1c5/0x410 fs/namei.c:3810 do_sys_openat2+0x171/0x4d0 fs/open.c:1318 do_sys_open fs/open.c:1334 [inline] __do_sys_openat fs/open.c:1350 [inline] __se_sys_openat fs/open.c:1345 [inline] __x64_sys_openat+0x13c/0x1f0 fs/open.c:1345 do_syscall_x64 arch/x86/entry/common.c:51 [inline] do_syscall_64+0x35/0x80 arch/x86/entry/common.c:81 entry_SYSCALL_64_after_hwframe+0x6e/0xd8 Found by Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://lore.kernel.org/all/20131010004732.GJ13318@ZenIV.linux.org.uk/T/# Cc: stable@vger.kernel.org Signed-off-by: Denis Arefev Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260521072857.5078-1-arefev@swemel.ru Signed-off-by: Jens Axboe --- block/bdev.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/block/bdev.c b/block/bdev.c index e44a73903201..85ce57bd2ae4 100644 --- a/block/bdev.c +++ b/block/bdev.c @@ -446,15 +446,10 @@ EXPORT_SYMBOL_GPL(blockdev_superblock); void __init bdev_cache_init(void) { - int err; - bdev_cachep = kmem_cache_create("bdev_cache", sizeof(struct bdev_inode), 0, (SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT| SLAB_ACCOUNT|SLAB_PANIC), init_once); - err = register_filesystem(&bd_type); - if (err) - panic("Cannot register bdev pseudo-fs"); blockdev_mnt = kern_mount(&bd_type); if (IS_ERR(blockdev_mnt)) panic("Cannot create bdev pseudo-fs"); From 38273e7b153d88562bddba9a24dafa733da4fd22 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 May 2026 08:33:29 +0200 Subject: [PATCH 026/117] direct-io: remove IOCB_NOWAIT support None of the file systems using the legacy direct I/O code actually sets FMODE_NOWAIT, and if they did this would not work, as the write locking could not handle the retry. Remove this dead code. Signed-off-by: Christoph Hellwig Reviewed-by: Christian Brauner Reviewed-by: Jan Kara Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260518063336.507369-2-hch@lst.de Signed-off-by: Jens Axboe --- fs/direct-io.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/fs/direct-io.c b/fs/direct-io.c index 2267f5ae7f77..9e4976716985 100644 --- a/fs/direct-io.c +++ b/fs/direct-io.c @@ -502,12 +502,8 @@ static blk_status_t dio_bio_complete(struct dio *dio, struct bio *bio) const enum req_op dio_op = dio->opf & REQ_OP_MASK; bool should_dirty = dio_op == REQ_OP_READ && dio->should_dirty; - if (err) { - if (err == BLK_STS_AGAIN && (bio->bi_opf & REQ_NOWAIT)) - dio->io_error = -EAGAIN; - else - dio->io_error = -EIO; - } + if (err) + dio->io_error = -EIO; if (dio->is_async && should_dirty) { bio_check_pages_dirty(bio); /* transfers ownership */ @@ -1178,13 +1174,10 @@ ssize_t __blockdev_direct_IO(struct kiocb *iocb, struct inode *inode, dio->is_async = true; dio->inode = inode; - if (iov_iter_rw(iter) == WRITE) { + if (iov_iter_rw(iter) == WRITE) dio->opf = REQ_OP_WRITE | REQ_SYNC | REQ_IDLE; - if (iocb->ki_flags & IOCB_NOWAIT) - dio->opf |= REQ_NOWAIT; - } else { + else dio->opf = REQ_OP_READ; - } /* * For AIO O_(D)SYNC writes we need to defer completions to a workqueue From a148d0a5af1ab60253994047403f9eb41ef709a4 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 May 2026 08:33:30 +0200 Subject: [PATCH 027/117] block: don't set BIO_QUIET for BLK_STS_AGAIN Commit abb30460bda2 ("block: mark bio_wouldblock_error() bio with BIO_QUIET") added this to suppress buffer_head warnings, but neither when this commit was added nor now any buffer_head using code actually ever sets REQ_NOWAIT which can lead to BLK_STS_AGAIN. Remove the special handling for now. If we ever plan to use REQ_NOWAIT for buffer_head based I/O we're better off handling BLK_STS_AGAIN in the completion handler as it actually needs to retry the I/O as well. Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Jan Kara Link: https://patch.msgid.link/20260518063336.507369-3-hch@lst.de Signed-off-by: Jens Axboe --- include/linux/bio.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/bio.h b/include/linux/bio.h index 85463981d0f5..7597ae4dc52b 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -379,7 +379,6 @@ static inline void bio_io_error(struct bio *bio) static inline void bio_wouldblock_error(struct bio *bio) { - bio_set_flag(bio, BIO_QUIET); bio->bi_status = BLK_STS_AGAIN; bio_endio(bio); } From 7817bdf8ee049496fa93f68cc257903f079c0180 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 25 May 2026 12:25:31 -0400 Subject: [PATCH 028/117] mtip32xx: fix use-after-free on service thread failure If service thread creation fails after device_add_disk() succeeds, mtip_block_initialize() calls del_gendisk() and then falls through to put_disk(). Since mtip32xx uses .free_disk to free struct driver_data, put_disk() can release dd on the added-disk path. The same unwind then continues to use dd for blk_mq_free_tag_set() and mtip_hw_exit(), and mtip_pci_probe() can later free dd again. This can cause a use-after-free and double free. Track whether the disk was added in the current initialization call. For the post-add service-thread failure path, remove the disk, release the local hardware resources, and return without dropping the final disk reference. The probe error path can then finish its cleanup and call put_disk() after it is done using dd. Keep the pre-add path using put_disk() before blk_mq_free_tag_set(), and clear dd->disk so the outer probe cleanup frees dd directly. Fixes: e8b58ef09e84 ("mtip32xx: fix device removal") Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260525162531.1406677-1-dbgh9129@gmail.com Signed-off-by: Jens Axboe --- drivers/block/mtip32xx/mtip32xx.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/block/mtip32xx/mtip32xx.c b/drivers/block/mtip32xx/mtip32xx.c index 8aedba9b5690..f214a616386c 100644 --- a/drivers/block/mtip32xx/mtip32xx.c +++ b/drivers/block/mtip32xx/mtip32xx.c @@ -3405,6 +3405,7 @@ static int mtip_block_initialize(struct driver_data *dd) .max_segment_size = 0x400000, }; int rv = 0, wait_for_rebuild = 0; + bool disk_added = false; sector_t capacity; unsigned int index = 0; @@ -3438,6 +3439,7 @@ static int mtip_block_initialize(struct driver_data *dd) dev_err(&dd->pdev->dev, "Unable to allocate request queue\n"); rv = -ENOMEM; + dd->disk = NULL; goto block_queue_alloc_init_error; } dd->queue = dd->disk->queue; @@ -3496,6 +3498,7 @@ static int mtip_block_initialize(struct driver_data *dd) rv = device_add_disk(&dd->pdev->dev, dd->disk, mtip_disk_attr_groups); if (rv) goto read_capacity_error; + disk_added = true; if (dd->mtip_svc_handler) { set_bit(MTIP_DDF_INIT_DONE_BIT, &dd->dd_flag); @@ -3511,7 +3514,9 @@ static int mtip_block_initialize(struct driver_data *dd) dev_err(&dd->pdev->dev, "service thread failed to start\n"); dd->mtip_svc_handler = NULL; rv = -EFAULT; - goto kthread_run_error; + if (disk_added) + goto kthread_run_error; + goto read_capacity_error; } wake_up_process(dd->mtip_svc_handler); if (wait_for_rebuild == MTIP_FTL_REBUILD_MAGIC) @@ -3522,6 +3527,10 @@ static int mtip_block_initialize(struct driver_data *dd) kthread_run_error: /* Delete our gendisk. This also removes the device from /dev */ del_gendisk(dd->disk); + mtip_hw_debugfs_exit(dd); + blk_mq_free_tag_set(&dd->tags); + mtip_hw_exit(dd); + return rv; read_capacity_error: init_hw_cmds_error: mtip_hw_debugfs_exit(dd); @@ -3529,6 +3538,7 @@ static int mtip_block_initialize(struct driver_data *dd) ida_free(&rssd_index_ida, index); ida_get_error: put_disk(dd->disk); + dd->disk = NULL; block_queue_alloc_init_error: blk_mq_free_tag_set(&dd->tags); block_queue_alloc_tag_error: @@ -3839,7 +3849,10 @@ static int mtip_pci_probe(struct pci_dev *pdev, } iomap_err: - kfree(dd); + if (dd->disk) + put_disk(dd->disk); + else + kfree(dd); pci_set_drvdata(pdev, NULL); return rv; done: From ee9895ae5a1868b13effc28174e4bb7f1084ded6 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 25 May 2026 14:25:55 -0400 Subject: [PATCH 029/117] block: remove blkdev_write_begin() and blkdev_write_end() Remove blkdev_write_begin(), blkdev_write_end(), and their entries in def_blk_aops. These have been unreachable since commit 487c607df790 ("block: use iomap for writes to block devices") switched block device buffered writes from generic_perform_write() to iomap_file_buffered_write(), which bypasses aops->write_begin/end. Signed-off-by: Tal Zussman Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260525-blk-write-cleanup-v1-1-391c073e3831@columbia.edu Signed-off-by: Jens Axboe --- block/fops.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/block/fops.c b/block/fops.c index bb6642b45937..ffe7b2042f4e 100644 --- a/block/fops.c +++ b/block/fops.c @@ -499,36 +499,12 @@ static void blkdev_readahead(struct readahead_control *rac) mpage_readahead(rac, blkdev_get_block); } -static int blkdev_write_begin(const struct kiocb *iocb, - struct address_space *mapping, loff_t pos, - unsigned len, struct folio **foliop, - void **fsdata) -{ - return block_write_begin(mapping, pos, len, foliop, blkdev_get_block); -} - -static int blkdev_write_end(const struct kiocb *iocb, - struct address_space *mapping, - loff_t pos, unsigned len, unsigned copied, - struct folio *folio, void *fsdata) -{ - int ret; - ret = block_write_end(pos, len, copied, folio); - - folio_unlock(folio); - folio_put(folio); - - return ret; -} - const struct address_space_operations def_blk_aops = { .dirty_folio = block_dirty_folio, .invalidate_folio = block_invalidate_folio, .read_folio = blkdev_read_folio, .readahead = blkdev_readahead, .writepages = blkdev_writepages, - .write_begin = blkdev_write_begin, - .write_end = blkdev_write_end, .migrate_folio = buffer_migrate_folio_norefs, .is_dirty_writeback = buffer_check_dirty_writeback, }; From 7882834048f110931275357db60dccff906dc96a Mon Sep 17 00:00:00 2001 From: Chaitanya Kulkarni Date: Wed, 13 May 2026 11:51:51 -0700 Subject: [PATCH 030/117] block: clear BLK_FEAT_PCI_P2PDMA in blk_stack_limits() for non-supporting devices BLK_FEAT_NOWAIT and BLK_FEAT_POLL are cleared in blk_stack_limits() when an underlying device does not support them. Apply the same treatment to BLK_FEAT_PCI_P2PDMA: stacking drivers set it unconditionally and rely on the core to clear it whenever a non-supporting member device is stacked. Tested-by: Pranjal Shrivastava Reviewed-by: Christoph Hellwig Reviewed-by: Sagi Grimberg Reviewed-by: Nitesh Shetty Signed-off-by: Chaitanya Kulkarni Tested=by: Pranjal Shrivastava Link: https://patch.msgid.link/20260513185153.95552-2-kch@nvidia.com Signed-off-by: Jens Axboe --- block/blk-settings.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/blk-settings.c b/block/blk-settings.c index 78c83817b9d3..8274631290db 100644 --- a/block/blk-settings.c +++ b/block/blk-settings.c @@ -795,6 +795,8 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b, t->features &= ~BLK_FEAT_NOWAIT; if (!(b->features & BLK_FEAT_POLL)) t->features &= ~BLK_FEAT_POLL; + if (!(b->features & BLK_FEAT_PCI_P2PDMA)) + t->features &= ~BLK_FEAT_PCI_P2PDMA; t->flags |= (b->flags & BLK_FLAG_MISALIGNED); From 02666132403aec8fc5de315002894f713ef17dbc Mon Sep 17 00:00:00 2001 From: Kiran Kumar Modukuri Date: Wed, 13 May 2026 11:51:52 -0700 Subject: [PATCH 031/117] md: propagate BLK_FEAT_PCI_P2PDMA from member devices to RAID device MD RAID does not propagate BLK_FEAT_PCI_P2PDMA from member devices to the RAID device, preventing peer-to-peer DMA through the RAID layer even when all underlying devices support it. Enable BLK_FEAT_PCI_P2PDMA unconditionally in raid0, raid1 and raid10 personalities during queue limits setup. blk_stack_limits() clears it automatically if any member device lacks support, consistent with how BLK_FEAT_NOWAIT and BLK_FEAT_POLL are handled in the block core. Parity RAID personalities (raid4/5/6) are excluded because they require CPU access to data pages for parity computation, which is incompatible with P2P mappings. Tested with RAID0/1/10 arrays containing multiple NVMe devices with P2PDMA support, confirming that peer-to-peer transfers work correctly through the RAID layer. Tested-by: Pranjal Shrivastava Reviewed-by: Christoph Hellwig Reviewed-by: Sagi Grimberg Reviewed-by: Xiao Ni Signed-off-by: Kiran Kumar Modukuri Signed-off-by: Chaitanya Kulkarni Reviewed-by: Yu Kuai Tested=by: Pranjal Shrivastava Link: https://patch.msgid.link/20260513185153.95552-3-kch@nvidia.com Signed-off-by: Jens Axboe --- drivers/md/raid0.c | 1 + drivers/md/raid1.c | 1 + drivers/md/raid10.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index 5e38a51e349a..2cdaf7495d92 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -392,6 +392,7 @@ static int raid0_set_limits(struct mddev *mddev) lim.io_opt = lim.io_min * mddev->raid_disks; lim.chunk_sectors = mddev->chunk_sectors; lim.features |= BLK_FEAT_ATOMIC_WRITES; + lim.features |= BLK_FEAT_PCI_P2PDMA; err = mddev_stack_rdev_limits(mddev, &lim, MDDEV_STACK_INTEGRITY); if (err) return err; diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 64d970e2ef50..cc628a1be52c 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -3208,6 +3208,7 @@ static int raid1_set_limits(struct mddev *mddev) lim.max_hw_wzeroes_unmap_sectors = 0; lim.logical_block_size = mddev->logical_block_size; lim.features |= BLK_FEAT_ATOMIC_WRITES; + lim.features |= BLK_FEAT_PCI_P2PDMA; err = mddev_stack_rdev_limits(mddev, &lim, MDDEV_STACK_INTEGRITY); if (err) return err; diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 39085e7dd6d2..f905dc391b74 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -3941,6 +3941,7 @@ static int raid10_set_queue_limits(struct mddev *mddev) lim.chunk_sectors = mddev->chunk_sectors; lim.io_opt = lim.io_min * raid10_nr_stripes(conf); lim.features |= BLK_FEAT_ATOMIC_WRITES; + lim.features |= BLK_FEAT_PCI_P2PDMA; err = mddev_stack_rdev_limits(mddev, &lim, MDDEV_STACK_INTEGRITY); if (err) return err; From fb0eeeed91f3236133383445fee5cc8f20330e6e Mon Sep 17 00:00:00 2001 From: Kiran Kumar Modukuri Date: Wed, 13 May 2026 11:51:53 -0700 Subject: [PATCH 032/117] nvme-multipath: enable PCI P2PDMA for multipath devices NVMe multipath does not expose BLK_FEAT_PCI_P2PDMA on the head disk even when all underlying controllers support it. Set BLK_FEAT_PCI_P2PDMA unconditionally in nvme_mpath_alloc_disk() alongside the other features. nvme_update_ns_info_block() already calls queue_limits_stack_bdev() to stack each path's limits onto the head disk, which routes through blk_stack_limits(). The core now clears BLK_FEAT_PCI_P2PDMA automatically if any path (e.g., FC) does not support it, consistent with how BLK_FEAT_NOWAIT and BLK_FEAT_POLL are handled. Tested-by: Pranjal Shrivastava Reviewed-by: Christoph Hellwig Reviewed-by: Sagi Grimberg Reviewed-by: Nitesh Shetty Signed-off-by: Kiran Kumar Modukuri Signed-off-by: Chaitanya Kulkarni Tested=by: Pranjal Shrivastava Link: https://patch.msgid.link/20260513185153.95552-4-kch@nvidia.com Signed-off-by: Jens Axboe --- drivers/nvme/host/multipath.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 263161cb8ac0..ff442bbf2937 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -730,7 +730,7 @@ int nvme_mpath_alloc_disk(struct nvme_ctrl *ctrl, struct nvme_ns_head *head) blk_set_stacking_limits(&lim); lim.dma_alignment = 3; lim.features |= BLK_FEAT_IO_STAT | BLK_FEAT_NOWAIT | - BLK_FEAT_POLL | BLK_FEAT_ATOMIC_WRITES; + BLK_FEAT_POLL | BLK_FEAT_ATOMIC_WRITES | BLK_FEAT_PCI_P2PDMA; if (head->ids.csi == NVME_CSI_ZNS) lim.features |= BLK_FEAT_ZONED; From 6022a5330fa2eabce7f20a23200e14a771640f1a Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 21 May 2026 17:37:16 +0200 Subject: [PATCH 033/117] nvme-core: fix unsigned comparison warning in nvme_wait_freeze_timeout The timeout variable in nvme_wait_freeze_timeout() is an unsigned type. Checking if it is <= 0 triggers a compiler warning because an unsigned variable can never be negative. Fix this warning by changing the type to long. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202605211257.STzj2Ujv-lkp@intel.com/ Fixes: 23b6d2cbf75f ("nvme: remove redundant timeout argument from nvme_wait_freeze_timeout") Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 10f154529334..fb14a208febe 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -5256,7 +5256,7 @@ EXPORT_SYMBOL_GPL(nvme_unfreeze); int nvme_wait_freeze_timeout(struct nvme_ctrl *ctrl) { - unsigned long timeout = ctrl->io_timeout; + long timeout = ctrl->io_timeout; struct nvme_ns *ns; int srcu_idx; From 4dae393956093c807212918fd91a8fc70df15338 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Tue, 26 May 2026 17:22:22 +0800 Subject: [PATCH 034/117] nvmet-tcp: fix page fragment cache leak in error path In nvmet_tcp_alloc_queue(), when a connection is closed during the allocation process (e.g., nvmet_tcp_set_queue_sock() returns -ENOTCONN), the error handling jumps to out_destroy_sq and then to out_ida_remove without draining the page fragment cache. Although nvmet_tcp_free_cmd() is called in some error paths to release individual page fragments, the underlying page cache reference held by queue->pf_cache is never released. The first allocation using pf_cache is the call to nvmet_tcp_alloc_cmd() for queue->connect, which happens after ida_alloc() returns successfully. This results in a page leak each time a connection fails during allocation, which could lead to memory exhaustion over time if connections are repeatedly opened and closed. Fix this by calling page_frag_cache_drain() before freeing the queue structure in the out_ida_remove label. Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver") Reviewed-by: Christoph Hellwig Signed-off-by: Geliang Tang Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 164a564ba3b4..93b3c6134240 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -1997,6 +1997,12 @@ static void nvmet_tcp_alloc_queue(struct nvmet_tcp_port *port, nvmet_tcp_free_cmd(&queue->connect); out_ida_remove: ida_free(&nvmet_tcp_queue_ida, queue->idx); + /* + * Drain the page fragment cache if any allocations were done. + * The first allocation using pf_cache is nvmet_tcp_alloc_cmd() + * for queue->connect after ida_alloc(). + */ + page_frag_cache_drain(&queue->pf_cache); out_sock: fput(queue->sock->file); out_free_queue: From 17d7492a50251d913ae7101f898cf30ede856cde Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 27 May 2026 17:33:28 +0300 Subject: [PATCH 035/117] block: partitions: replace __get_free_page() with kmalloc() check_partition() allocates a buffer to use as backing memory for seq_buf. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. For a single allocation on the cold path the performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260527-block-v2-1-8e06f914c484@kernel.org Signed-off-by: Jens Axboe --- block/partitions/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/block/partitions/core.c b/block/partitions/core.c index 5d5332ce586b..b5c59b79ca7c 100644 --- a/block/partitions/core.c +++ b/block/partitions/core.c @@ -124,7 +124,7 @@ static struct parsed_partitions *check_partition(struct gendisk *hd) state = allocate_partitions(hd); if (!state) return NULL; - state->pp_buf.buffer = (char *)__get_free_page(GFP_KERNEL); + state->pp_buf.buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!state->pp_buf.buffer) { free_partitions(state); return NULL; @@ -154,7 +154,7 @@ static struct parsed_partitions *check_partition(struct gendisk *hd) if (res > 0) { printk(KERN_INFO "%s", seq_buf_str(&state->pp_buf)); - free_page((unsigned long)state->pp_buf.buffer); + kfree(state->pp_buf.buffer); return state; } if (state->access_beyond_eod) @@ -170,7 +170,7 @@ static struct parsed_partitions *check_partition(struct gendisk *hd) printk(KERN_INFO "%s", seq_buf_str(&state->pp_buf)); } - free_page((unsigned long)state->pp_buf.buffer); + kfree(state->pp_buf.buffer); free_partitions(state); return ERR_PTR(res); } From 6b2f3e4970e48e70c10111366f59f908f2ea6f96 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 27 May 2026 11:18:09 +0200 Subject: [PATCH 036/117] rust: block: mq: align init_request numa_node arg with C signature Commit b040a1a4523d ("block: switch numa_node to int in blk_mq_hw_ctx and init_request") changed the type of the `numa_node` argument of `blk_mq_ops::init_request` from `unsigned int` to `int`. Update the Rust callback signature to match, so that the function item can be coerced to the C fn pointer type stored in `blk_mq_ops`. Without this change the Rust block layer fails to build: error[E0308]: mismatched types --> rust/kernel/block/mq/operations.rs:274:28 | 274 | init_request: Some(Self::init_request_callback), | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | expected fn pointer, found fn item | = note: expected fn pointer `unsafe extern "C" fn(_, _, _, i32) -> _` found fn item `unsafe extern "C" fn(_, _, _, u32) -> _ {...}` The argument is unused on the Rust side, so this is a pure type-signature change with no functional impact. Fixes: b040a1a4523d ("block: switch numa_node to int in blk_mq_hw_ctx and init_request") Signed-off-by: Andreas Hindborg Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260527-block-for-next-2026-05-26-2200-failure-v1-1-4865889e282c@kernel.org Signed-off-by: Jens Axboe --- rust/kernel/block/mq/operations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/block/mq/operations.rs b/rust/kernel/block/mq/operations.rs index 8ad46129a52c..861903e18fbf 100644 --- a/rust/kernel/block/mq/operations.rs +++ b/rust/kernel/block/mq/operations.rs @@ -218,7 +218,7 @@ impl OperationsVTable { _set: *mut bindings::blk_mq_tag_set, rq: *mut bindings::request, _hctx_idx: crate::ffi::c_uint, - _numa_node: crate::ffi::c_uint, + _numa_node: crate::ffi::c_int, ) -> crate::ffi::c_int { from_result(|| { // SAFETY: By the safety requirements of this function, `rq` points From 6235ea3f8b8ffca0333ade0863992f3cd69592ea Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Fri, 22 May 2026 17:15:30 +0800 Subject: [PATCH 037/117] blk-throttle: schedule parent dispatch in tg_flush_bios() tg_flush_bios() schedules pending_timer on the child tg's own service_queue, which causes throtl_pending_timer_fn() to dispatch from the child's pending_tree. For leaf cgroups this tree is empty, so the timer fires and exits without dispatching the throttled bio. The throttled bio sits in the parent's pending_tree with disptime set to jiffies (THROTL_TG_CANCELING zeroes all dispatch times), but the parent's timer is never explicitly rescheduled. The bio only gets dispatched when the parent timer eventually fires at its previously scheduled expiry. Fix by calling throtl_schedule_next_dispatch(sq->parent_sq, true) instead, matching what tg_set_limit() already does. This forces the parent's dispatch cycle to run immediately and flush all canceling bios without waiting for a stale timer. For the device deletion path (blk_throtl_cancel_bios), directly complete throttled bios with EIO via bio_io_error() instead of dispatching them through the timer -> work -> submission chain. This avoids a race with the SCSI state machine where bios can reach the SCSI layer while the device is in SDEV_CANCEL state, causing ENODEV instead of the expected EIO. Reported-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/all/ag2owaQQoigp_fSV@shinmob/ Signed-off-by: Tao Cui Link: https://patch.msgid.link/20260522091530.1901437-1-cuitao@kylinos.cn Signed-off-by: Jens Axboe --- block/blk-throttle.c | 51 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/block/blk-throttle.c b/block/blk-throttle.c index cabf91f0d0dc..88986dde1e18 100644 --- a/block/blk-throttle.c +++ b/block/blk-throttle.c @@ -1649,7 +1649,7 @@ static void tg_flush_bios(struct throtl_grp *tg) */ tg_update_disptime(tg); - throtl_schedule_pending_timer(sq, jiffies + 1); + throtl_schedule_next_dispatch(sq->parent_sq, true); } static void throtl_pd_offline(struct blkg_policy_data *pd) @@ -1668,11 +1668,52 @@ struct blkcg_policy blkcg_policy_throtl = { .pd_free_fn = throtl_pd_free, }; +static void tg_cancel_writeback_bios(struct throtl_grp *tg, + struct bio_list *cancel_bios) +{ + struct throtl_service_queue *sq = &tg->service_queue; + struct throtl_data *td = sq_to_td(sq); + int rw; + + if (tg->flags & THROTL_TG_CANCELING) + return; + tg->flags |= THROTL_TG_CANCELING; + + for (rw = READ; rw <= WRITE; rw++) { + struct throtl_qnode *qn, *tmp; + unsigned int nr_bios = 0; + + list_for_each_entry_safe(qn, tmp, &sq->queued[rw], node) { + struct bio *bio; + + while ((bio = bio_list_pop(&qn->bios_iops))) { + sq->nr_queued_iops[rw]--; + bio_list_add(&cancel_bios[rw], bio); + nr_bios++; + } + while ((bio = bio_list_pop(&qn->bios_bps))) { + sq->nr_queued_bps[rw]--; + bio_list_add(&cancel_bios[rw], bio); + nr_bios++; + } + + list_del_init(&qn->node); + blkg_put(tg_to_blkg(qn->tg)); + } + + td->nr_queued[rw] -= nr_bios; + } + + throtl_dequeue_tg(tg); +} + void blk_throtl_cancel_bios(struct gendisk *disk) { struct request_queue *q = disk->queue; struct cgroup_subsys_state *pos_css; struct blkcg_gq *blkg; + struct bio_list cancel_bios[2] = { }; + int rw; if (!blk_throtl_activated(q)) return; @@ -1693,10 +1734,16 @@ void blk_throtl_cancel_bios(struct gendisk *disk) * Cancel bios here to ensure no bios are inflight after * del_gendisk. */ - tg_flush_bios(blkg_to_tg(blkg)); + tg_cancel_writeback_bios(blkg_to_tg(blkg), cancel_bios); } rcu_read_unlock(); spin_unlock_irq(&q->queue_lock); + + for (rw = READ; rw <= WRITE; rw++) { + struct bio *bio; + while ((bio = bio_list_pop(&cancel_bios[rw]))) + bio_io_error(bio); + } } static bool tg_within_limit(struct throtl_grp *tg, struct bio *bio, bool rw) From 7ef789703e2b91775dcb36b2efa46325be31a2a0 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Tue, 26 May 2026 17:28:05 +0800 Subject: [PATCH 038/117] nvmet-tcp: check return value of nvmet_tcp_set_queue_sock The return value of nvmet_tcp_set_queue_sock() is currently ignored in nvmet_tcp_tls_handshake_done(). If it fails (e.g., due to the socket not being in TCP_ESTABLISHED state), the socket callbacks will not be properly set, leading to queue and socket leakage. Fix this by capturing the return value and calling nvmet_tcp_schedule_release_queue() on failure to ensure proper cleanup. Fixes: 675b453e0241 ("nvmet-tcp: enable TLS handshake upcall") Reviewed-by: Hannes Reinecke Reviewed-by: Chaitanya Kulkarni Signed-off-by: Geliang Tang Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 93b3c6134240..3568fa9a0905 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -1842,10 +1842,11 @@ static void nvmet_tcp_tls_handshake_done(void *data, int status, if (!status) status = nvmet_tcp_tls_key_lookup(queue, peerid); + if (!status) + status = nvmet_tcp_set_queue_sock(queue); + if (status) nvmet_tcp_schedule_release_queue(queue); - else - nvmet_tcp_set_queue_sock(queue); kref_put(&queue->kref, nvmet_tcp_release_queue); } From 5ab7c84f218b08908bf7768e5669d15e89595a02 Mon Sep 17 00:00:00 2001 From: John Garry Date: Wed, 13 May 2026 09:50:30 +0000 Subject: [PATCH 039/117] nvme: use DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE for multipath_sysfs Use DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE instead of DEFINE_SYSFS_GROUP_VISIBLE, which means that we can drop multipath_sysfs_attr_visible(). Incidentally, multipath_sysfs_attr_visible() should have returned a umode_t. This idea was suggested by Ben Marzinski elsewhere. Reviewed-by: Christoph Hellwig Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/sysfs.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index b682c1a4b23f..1f471f2cfd25 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -335,14 +335,7 @@ static bool multipath_sysfs_group_visible(struct kobject *kobj) return nvme_disk_is_ns_head(dev_to_disk(dev)); } - -static bool multipath_sysfs_attr_visible(struct kobject *kobj, - struct attribute *attr, int n) -{ - return false; -} - -DEFINE_SYSFS_GROUP_VISIBLE(multipath_sysfs) +DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(multipath_sysfs) const struct attribute_group nvme_ns_mpath_attr_group = { .name = "multipath", From 1133b93fc7f63defaa2c07d5f49873c14bb74681 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Wed, 27 May 2026 09:40:42 -0500 Subject: [PATCH 040/117] ublk: set canceling flag even when disk is not allocated ublk_start_cancel() previously bailed out early when ublk_get_disk() returned NULL, treating it as "our disk has been dead". That is correct for the post-teardown case, but it also wrongly covers the pre-start case: ublk_ctrl_start_dev() has not assigned ub->ub_disk yet, while io_uring is already tearing down the daemon's uring_cmds via ublk_uring_cmd_cancel_fn(). In that window, the cancel path skips ublk_set_canceling(), so ubq->canceling stays false, even though ublk_cancel_cmd() goes on to NULL out every io->cmd. ublk_ctrl_start_dev() then proceeds to set ub->ub_disk, call add_disk(), and schedule partition_scan_work. When ublk_partition_scan_work() runs bdev_disk_changed() and the resulting read reaches ublk_queue_rq() -> ublk_queue_cmd(), the ubq->canceling check passes and the code dereferences the NULL io->cmd: BUG: kernel NULL pointer dereference, address: 0000000000000018 RIP: ublk_queue_cmd drivers/block/ublk_drv.c [inline] RIP: ublk_queue_rq+0x73/0x100 Call Trace: blk_mq_dispatch_rq_list+0x1c5/0xca0 ... bdev_disk_changed+0x3d4/0x5e0 ublk_partition_scan_work+0x89/0xe0 process_one_work+0x344/0x8a0 Fix it by always setting ub->canceling / ubq->canceling under cancel_mutex. When the disk is allocated, keep the existing quiesce/unquiesce dance so the flag is observed across the ublk_queue_rq() barrier. When the disk is not yet allocated, there is no request_queue and ublk_queue_rq() cannot be running concurrently, so simply flipping the flag is sufficient: any subsequent I/O - including the partition scan started by ublk_ctrl_start_dev() - will see canceling set and be aborted via __ublk_queue_rq_common(). Fixes: 7fc4da6a304b ("ublk: scan partition in async way") Signed-off-by: Ming Lei Link: https://patch.msgid.link/20260527144042.2095194-1-tom.leiming@gmail.com Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 49624e65fe75..0c6b9b34b255 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2704,23 +2704,27 @@ static void ublk_start_cancel(struct ublk_device *ub) { struct gendisk *disk = ublk_get_disk(ub); - /* Our disk has been dead */ - if (!disk) - return; - mutex_lock(&ub->cancel_mutex); if (ub->canceling) goto out; - /* - * Now we are serialized with ublk_queue_rq() - * - * Make sure that ubq->canceling is set when queue is frozen, - * because ublk_queue_rq() has to rely on this flag for avoiding to - * touch completed uring_cmd - */ - blk_mq_quiesce_queue(disk->queue); - ublk_set_canceling(ub, true); - blk_mq_unquiesce_queue(disk->queue); + + if (disk) { + /* + * Quiesce to serialize with ublk_queue_rq(), ensuring + * ubq->canceling is visible when the queue resumes. + */ + blk_mq_quiesce_queue(disk->queue); + ublk_set_canceling(ub, true); + blk_mq_unquiesce_queue(disk->queue); + } else { + /* + * Disk not yet allocated by ublk_ctrl_start_dev(), so + * there is no request queue and ublk_queue_rq() cannot + * be running. Just set the flag; if start_dev proceeds + * later, new I/O will see canceling and be aborted. + */ + ublk_set_canceling(ub, true); + } out: mutex_unlock(&ub->cancel_mutex); ublk_put_disk(disk); From 001e57554de81aa79c25c18fd53911d8a415c304 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Wed, 27 May 2026 11:50:00 +0530 Subject: [PATCH 041/117] nvme-multipath: fix flex array size in struct nvme_ns_head struct nvme_ns_head contains a flexible array member, current_path[], which is indexed using the NUMA node ID: head->current_path[numa_node_id()] The structure is currently allocated as: size = sizeof(struct nvme_ns_head) + (num_possible_nodes() * sizeof(struct nvme_ns *)); head = kzalloc(size, GFP_KERNEL); This allocation assumes that NUMA node IDs are sequential and densely packed from 0 .. num_possible_nodes() - 1. While this assumption holds on many systems, it is not always true on some architectures such as powerpc. On some powerpc systems, NUMA node IDs can be sparse. For example: NUMA: NUMA node(s): 6 NUMA node0 CPU(s): 80-159 NUMA node8 CPU(s): 0-79 NUMA node252 CPU(s): NUMA node253 CPU(s): NUMA node254 CPU(s): NUMA node255 CPU(s): That is, the possible/online NUMA node IDs are: 0, 8, 252, 253, 254, 255 In this case: num_possible_nodes() = 6 So memory is allocated for only 6 entries in current_path[]. However, the array is later indexed using the actual NUMA node ID. As a result, accesses such as: head->current_path[8] or head->current_path[252] goes out of bounds, leading to the following KASAN splat: ================================================================== BUG: KASAN: slab-out-of-bounds in nvme_mpath_revalidate_paths+0x22c/0x290 [nvme_core] Write of size 8 at addr c00020003bda35b8 by task kworker/u641:2/1997 CPU: 1 UID: 0 PID: 1997 Comm: kworker/u641:2 Not tainted 7.1.0-rc5-dirty #14 PREEMPT(lazy) Hardware name: 8335-GTH POWER9 0x4e1202 opal:skiboot-v6.5.3-35-g1851b2a06 PowerNV Workqueue: async async_run_entry_fn Call Trace: [c000200037fa7510] [c0000000021c23d4] dump_stack_lvl+0x88/0xdc (unreliable) [c000200037fa7540] [c0000000009fda90] print_report+0x22c/0x67c [c000200037fa7630] [c0000000009fd508] kasan_report+0x108/0x220 [c000200037fa7740] [c0000000009fff48] __asan_store8+0xe8/0x120 [c000200037fa7760] [c008000018e76474] nvme_mpath_revalidate_paths+0x22c/0x290 [nvme_core] [c000200037fa7800] [c008000018e6556c] nvme_update_ns_info+0x4a4/0x5e0 [nvme_core] [c000200037fa7a50] [c008000018e66270] nvme_alloc_ns+0x6d8/0x1a70 [nvme_core] [c000200037fa7c20] [c008000018e679fc] nvme_scan_ns+0x3f4/0x630 [nvme_core] [c000200037fa7d10] [c00000000031f22c] async_run_entry_fn+0x9c/0x3a0 [c000200037fa7db0] [c0000000002fa544] process_one_work+0x414/0xa10 [c000200037fa7ec0] [c0000000002fbf00] worker_thread+0x320/0x640 [c000200037fa7f80] [c00000000030d0f8] kthread+0x278/0x290 [c000200037fa7fe0] [c00000000000ded8] start_kernel_thread+0x14/0x18 Allocated by task 1997 on cpu 1 at 35.928317s: The buggy address belongs to the object at c00020003bda3000 which belongs to the cache kmalloc-rnd-15-2k of size 2048 The buggy address is located 16 bytes to the right of allocated 1448-byte region [c00020003bda3000, c00020003bda35a8) The buggy address belongs to the physical page: Memory state around the buggy address: c00020003bda3480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c00020003bda3500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >c00020003bda3580: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc ^ c00020003bda3600: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc c00020003bda3680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ================================================================== Fix this by allocating the flexible array using nr_node_ids instead of num_possible_nodes(). Since nr_node_ids represents the maximum possible NUMA node IDs, indexing current_path[] using numa_node_id() becomes safe even on systems with sparse node IDs. Fixes: f333444708f8 ("nvme: take node locality into account when selecting a path") Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Hannes Reinecke Reviewed-by: John Garry Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index fb14a208febe..5d8af8aa472e 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3930,7 +3930,7 @@ static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, int ret = -ENOMEM; #ifdef CONFIG_NVME_MULTIPATH - size += num_possible_nodes() * sizeof(struct nvme_ns *); + size += nr_node_ids * sizeof(struct nvme_ns *); #endif head = kzalloc(size, GFP_KERNEL); From badc53620fe813b3a9f727ef9526f98567c2c898 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 27 May 2026 08:45:44 +0000 Subject: [PATCH 042/117] nvme: target: rdma: fix ndev refcount leak on queue connect nvmet_rdma_queue_connect() calls nvmet_rdma_find_get_device() which acquires a reference on the returned ndev via kref_get(). On the path where the host queue backlog is exceeded and the function returns NVME_SC_CONNECT_CTRL_BUSY, reference of ndev is not released, leaking the kref. Fix this by adding a goto to the existing put_device label before the early return. Fixes: 31deaeb11ba7 ("nvmet-rdma: avoid circular locking dependency on install_queue()") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Wentao Liang Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index e6e2c3f9afdf..ac26f4f774c4 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -1598,8 +1598,10 @@ static int nvmet_rdma_queue_connect(struct rdma_cm_id *cm_id, pending++; } mutex_unlock(&nvmet_rdma_queue_mutex); - if (pending > NVMET_RDMA_BACKLOG) - return NVME_SC_CONNECT_CTRL_BUSY; + if (pending > NVMET_RDMA_BACKLOG) { + ret = NVME_SC_CONNECT_CTRL_BUSY; + goto put_device; + } } ret = nvmet_rdma_cm_accept(cm_id, queue, &event->param.conn); From a192b8cfa447e1b3701a13434a31c392b2e7ed29 Mon Sep 17 00:00:00 2001 From: Mateusz Nowicki Date: Sat, 23 May 2026 08:28:16 +0000 Subject: [PATCH 043/117] nvme-pci: fix out-of-bounds access in nvme_setup_descriptor_pools nvme_setup_descriptor_pools() indexes dev->descriptor_pools[] using the numa_node forwarded from hctx->numa_node by its single caller, nvme_init_hctx_common(). On a non-NUMA kernel hctx->numa_node is NUMA_NO_NODE (-1). Because the parameter was declared 'unsigned', the value becomes UINT_MAX and the index walks off the array (sized to nr_node_ids), faulting during nvme_alloc_ns() and leaving the namespace without a /dev node. Reproduces on any NVMe controller probed by a CONFIG_NUMA=n kernel: BUG: unable to handle page fault for address: ffff889101603d38 RIP: 0010:nvme_init_hctx_common+0x5a/0x190 [nvme] Call Trace: nvme_init_hctx+0x10/0x20 [nvme] nvme_alloc_ns+0x9e/0xa10 [nvme_core] nvme_scan_ns+0x301/0x3b0 [nvme_core] nvme_scan_ns_async+0x23/0x30 [nvme_core] Switch the parameter to int and fall back to node 0 when it is NUMA_NO_NODE; node 0 is always present. Fixes: d977506f8863 ("nvme-pci: make PRP list DMA pools per-NUMA-node") Link: https://lore.kernel.org/r/20260309062840.2937858-2-iam@sung-woo.kim Reported-by: Sung-woo Kim Reviewed-by: Christoph Hellwig Signed-off-by: Mateusz Nowicki Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 35affda088f4..d20d8722ad96 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -587,11 +587,16 @@ static bool nvme_dbbuf_update_and_check_event(u16 value, __le32 *dbbuf_db, } static struct nvme_descriptor_pools * -nvme_setup_descriptor_pools(struct nvme_dev *dev, unsigned numa_node) +nvme_setup_descriptor_pools(struct nvme_dev *dev, int numa_node) { - struct nvme_descriptor_pools *pools = &dev->descriptor_pools[numa_node]; + struct nvme_descriptor_pools *pools; size_t small_align = NVME_SMALL_POOL_SIZE; + if (numa_node == NUMA_NO_NODE) + numa_node = 0; + + pools = &dev->descriptor_pools[numa_node]; + if (pools->small) return pools; /* already initialized */ From 353c85082a82fa6d78cbb3821749d5982ffed9f4 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 27 May 2026 17:06:46 +0200 Subject: [PATCH 044/117] block: mark biovec_init_pool static Signed-off-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260527150646.2349405-1-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 2 +- include/linux/bio.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/block/bio.c b/block/bio.c index 2d880d1255fe..e9944dab0132 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1873,7 +1873,7 @@ EXPORT_SYMBOL_GPL(bio_trim); * create memory pools for biovec's in a bio_set. * use the global biovec slabs created for general use. */ -int biovec_init_pool(mempool_t *pool, int pool_entries) +static int biovec_init_pool(mempool_t *pool, int pool_entries) { struct biovec_slab *bp = bvec_slabs + ARRAY_SIZE(bvec_slabs) - 1; diff --git a/include/linux/bio.h b/include/linux/bio.h index 7597ae4dc52b..e60d2f5bd3dc 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -347,7 +347,6 @@ enum { }; extern int bioset_init(struct bio_set *, unsigned int, unsigned int, int flags); extern void bioset_exit(struct bio_set *); -extern int biovec_init_pool(mempool_t *pool, int pool_entries); struct bio *bio_alloc_bioset(struct block_device *bdev, unsigned short nr_vecs, blk_opf_t opf, gfp_t gfp, struct bio_set *bs); From 7dea9029721675d475e093116cef569253960e06 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 27 May 2026 17:10:20 +0200 Subject: [PATCH 045/117] loop: cleanup lo_rw_aio Port over the changes from the zloop driver to remove the need for the local bio, bvec and offset variables and clean up the code by that. Signed-off-by: Christoph Hellwig Reviewed-by: Ming Lei Reviewed-by: Chaitanya Kulkarni Reviewed-by: Keith Busch Link: https://patch.msgid.link/20260527151043.2349900-2-hch@lst.de Signed-off-by: Jens Axboe --- drivers/block/loop.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 0000913f7efc..310de0463beb 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -342,23 +342,19 @@ static int lo_rw_aio(struct loop_device *lo, struct loop_cmd *cmd, { struct iov_iter iter; struct req_iterator rq_iter; - struct bio_vec *bvec; struct request *rq = blk_mq_rq_from_pdu(cmd); - struct bio *bio = rq->bio; struct file *file = lo->lo_backing_file; - struct bio_vec tmp; - unsigned int offset; unsigned int nr_bvec; int ret; nr_bvec = blk_rq_nr_bvec(rq); if (rq->bio != rq->biotail) { + struct bio_vec tmp, *bvec; - bvec = kmalloc_objs(struct bio_vec, nr_bvec, GFP_NOIO); - if (!bvec) + cmd->bvec = kmalloc_objs(*cmd->bvec, nr_bvec, GFP_NOIO); + if (!cmd->bvec) return -EIO; - cmd->bvec = bvec; /* * The bios of the request may be started from the middle of @@ -366,26 +362,26 @@ static int lo_rw_aio(struct loop_device *lo, struct loop_cmd *cmd, * copy bio->bi_iov_vec to new bvec. The rq_for_each_bvec * API will take care of all details for us. */ + bvec = cmd->bvec; rq_for_each_bvec(tmp, rq, rq_iter) { *bvec = tmp; bvec++; } - bvec = cmd->bvec; - offset = 0; + iov_iter_bvec(&iter, rw, cmd->bvec, nr_bvec, blk_rq_bytes(rq)); + iter.iov_offset = 0; } else { /* * Same here, this bio may be started from the middle of the * 'bvec' because of bio splitting, so offset from the bvec * must be passed to iov iterator */ - offset = bio->bi_iter.bi_bvec_done; - bvec = __bvec_iter_bvec(bio->bi_io_vec, bio->bi_iter); + iov_iter_bvec(&iter, rw, + __bvec_iter_bvec(rq->bio->bi_io_vec, rq->bio->bi_iter), + nr_bvec, blk_rq_bytes(rq)); + iter.iov_offset = rq->bio->bi_iter.bi_bvec_done; } atomic_set(&cmd->ref, 2); - iov_iter_bvec(&iter, rw, bvec, nr_bvec, blk_rq_bytes(rq)); - iter.iov_offset = offset; - cmd->iocb.ki_pos = pos; cmd->iocb.ki_filp = file; cmd->iocb.ki_ioprio = req_get_ioprio(rq); From adf3a5cef1a839e388dc382b3e07623f52746322 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 27 May 2026 17:10:21 +0200 Subject: [PATCH 046/117] nvme-tcp: cleanup nvme_tcp_init_iter Split the two init cases based on code in the zloop driver. This simplifies the code and makes it easier to follow. Signed-off-by: Christoph Hellwig Reviewed-by: Ming Lei Reviewed-by: Chaitanya Kulkarni Reviewed-by: Keith Busch Link: https://patch.msgid.link/20260527151043.2349900-3-hch@lst.de Signed-off-by: Jens Axboe --- drivers/nvme/host/tcp.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 36b3ec50a9fd..9313ab211c67 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -340,32 +340,25 @@ static void nvme_tcp_init_iter(struct nvme_tcp_request *req, unsigned int dir) { struct request *rq = blk_mq_rq_from_pdu(req); - struct bio_vec *vec; - unsigned int size; - int nr_bvec; - size_t offset; if (rq->rq_flags & RQF_SPECIAL_PAYLOAD) { - vec = &rq->special_vec; - nr_bvec = 1; - size = blk_rq_payload_bytes(rq); - offset = 0; + iov_iter_bvec(&req->iter, dir, &rq->special_vec, 1, + blk_rq_payload_bytes(rq)); + req->iter.iov_offset = 0; } else { struct bio *bio = req->curr_bio; struct bvec_iter bi; struct bio_vec bv; + int nr_bvec = 0; - vec = __bvec_iter_bvec(bio->bi_io_vec, bio->bi_iter); - nr_bvec = 0; - bio_for_each_bvec(bv, bio, bi) { + bio_for_each_bvec(bv, bio, bi) nr_bvec++; - } - size = bio->bi_iter.bi_size; - offset = bio->bi_iter.bi_bvec_done; - } - iov_iter_bvec(&req->iter, dir, vec, nr_bvec, size); - req->iter.iov_offset = offset; + iov_iter_bvec(&req->iter, dir, + __bvec_iter_bvec(bio->bi_io_vec, bio->bi_iter), nr_bvec, + bio->bi_iter.bi_size); + req->iter.iov_offset = bio->bi_iter.bi_bvec_done; + } } static inline void nvme_tcp_advance_req(struct nvme_tcp_request *req, From f6fe52a7b18675d76d7f7dae0c16f412a4e33f9a Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 27 May 2026 17:10:22 +0200 Subject: [PATCH 047/117] bvec: make the bvec_iter helpers inline functions The macros are impossible to follow due to the lack of visual type information and all the braces. Replace them with inline helpers to improve on that. Because the calling conventions are a bit problematic with a lot of passing structures by value, all the helpers are marked as __always_inline so that they are force inlined. Signed-off-by: Christoph Hellwig Reviewed-by: Bart Van Assche Reviewed-by: Caleb Sander Mateos Reviewed-by: Ming Lei Reviewed-by: Chaitanya Kulkarni Reviewed-by: Keith Busch Link: https://patch.msgid.link/20260527151043.2349900-4-hch@lst.de Signed-off-by: Jens Axboe --- include/linux/bvec.h | 95 ++++++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/include/linux/bvec.h b/include/linux/bvec.h index d36dd476feda..f4c7ec282ac9 100644 --- a/include/linux/bvec.h +++ b/include/linux/bvec.h @@ -104,51 +104,78 @@ struct bvec_iter_all { unsigned done; }; -/* - * various member access, note that bio_data should of course not be used - * on highmem page vectors - */ -#define __bvec_iter_bvec(bvec, iter) (&(bvec)[(iter).bi_idx]) +static __always_inline const struct bio_vec * +__bvec_iter_bvec(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return bvecs + iter.bi_idx; +} /* multi-page (mp_bvec) helpers */ -#define mp_bvec_iter_page(bvec, iter) \ - (__bvec_iter_bvec((bvec), (iter))->bv_page) +static __always_inline struct page * +mp_bvec_iter_page(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return __bvec_iter_bvec(bvecs, iter)->bv_page; +} -#define mp_bvec_iter_len(bvec, iter) \ - min((iter).bi_size, \ - __bvec_iter_bvec((bvec), (iter))->bv_len - (iter).bi_bvec_done) +static __always_inline unsigned int +mp_bvec_iter_len(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return min(__bvec_iter_bvec(bvecs, iter)->bv_len - iter.bi_bvec_done, + iter.bi_size); +} -#define mp_bvec_iter_offset(bvec, iter) \ - (__bvec_iter_bvec((bvec), (iter))->bv_offset + (iter).bi_bvec_done) +static __always_inline unsigned int +mp_bvec_iter_offset(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return __bvec_iter_bvec(bvecs, iter)->bv_offset + iter.bi_bvec_done; +} -#define mp_bvec_iter_page_idx(bvec, iter) \ - (mp_bvec_iter_offset((bvec), (iter)) / PAGE_SIZE) +static __always_inline unsigned int +mp_bvec_iter_page_idx(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return mp_bvec_iter_offset(bvecs, iter) / PAGE_SIZE; +} -#define mp_bvec_iter_bvec(bvec, iter) \ -((struct bio_vec) { \ - .bv_page = mp_bvec_iter_page((bvec), (iter)), \ - .bv_len = mp_bvec_iter_len((bvec), (iter)), \ - .bv_offset = mp_bvec_iter_offset((bvec), (iter)), \ -}) +static __always_inline struct bio_vec +mp_bvec_iter_bvec(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return (struct bio_vec) { + .bv_page = mp_bvec_iter_page(bvecs, iter), + .bv_len = mp_bvec_iter_len(bvecs, iter), + .bv_offset = mp_bvec_iter_offset(bvecs, iter), + }; +} /* For building single-page bvec in flight */ - #define bvec_iter_offset(bvec, iter) \ - (mp_bvec_iter_offset((bvec), (iter)) % PAGE_SIZE) +static __always_inline unsigned int +bvec_iter_offset(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return mp_bvec_iter_offset(bvecs, iter) % PAGE_SIZE; +} -#define bvec_iter_len(bvec, iter) \ - min_t(unsigned, mp_bvec_iter_len((bvec), (iter)), \ - PAGE_SIZE - bvec_iter_offset((bvec), (iter))) +static __always_inline unsigned int +bvec_iter_len(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return min(mp_bvec_iter_len(bvecs, iter), + PAGE_SIZE - bvec_iter_offset(bvecs, iter)); +} -#define bvec_iter_page(bvec, iter) \ - (mp_bvec_iter_page((bvec), (iter)) + \ - mp_bvec_iter_page_idx((bvec), (iter))) +static __always_inline struct page * +bvec_iter_page(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return mp_bvec_iter_page(bvecs, iter) + + mp_bvec_iter_page_idx(bvecs, iter); +} -#define bvec_iter_bvec(bvec, iter) \ -((struct bio_vec) { \ - .bv_page = bvec_iter_page((bvec), (iter)), \ - .bv_len = bvec_iter_len((bvec), (iter)), \ - .bv_offset = bvec_iter_offset((bvec), (iter)), \ -}) +static __always_inline struct bio_vec +bvec_iter_bvec(const struct bio_vec *bvecs, const struct bvec_iter iter) +{ + return (struct bio_vec) { + .bv_page = bvec_iter_page(bvecs, iter), + .bv_len = bvec_iter_len(bvecs, iter), + .bv_offset = bvec_iter_offset(bvecs, iter), + }; +} static inline bool bvec_iter_advance(const struct bio_vec *bv, struct bvec_iter *iter, unsigned bytes) From a7d8eaee7fafe2e2c58aef9579bdef778c144029 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 28 May 2026 10:46:13 +0200 Subject: [PATCH 048/117] block: add a bio_endio_status helper Add a helper that sets bi_status and call bio_endio() as that is a very common pattern and convert the core block code over to it. Signed-off-by: Christoph Hellwig Reviewed-by: Keith Busch Reviewed-by: Md Haris Iqbal Reviewed-by: Damien Le Moal Reviewed-by: Hannes Reinecke Link: https://patch.msgid.link/20260528084632.2505277-1-hch@lst.de Signed-off-by: Jens Axboe --- block/blk-core.c | 11 ++++------- block/blk-crypto-fallback.c | 9 +++------ block/blk-crypto.c | 3 +-- block/blk-merge.c | 6 ++---- block/blk-mq.c | 6 ++---- block/fops.c | 3 +-- include/linux/bio.h | 19 +++++++++++++++---- 7 files changed, 28 insertions(+), 29 deletions(-) diff --git a/block/blk-core.c b/block/blk-core.c index 22af5dec112b..b0f0a304ea0b 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -636,12 +636,10 @@ static void __submit_bio(struct bio *bio) struct gendisk *disk = bio->bi_bdev->bd_disk; if ((bio->bi_opf & REQ_POLLED) && - !(disk->queue->limits.features & BLK_FEAT_POLL)) { - bio->bi_status = BLK_STS_NOTSUPP; - bio_endio(bio); - } else { + !(disk->queue->limits.features & BLK_FEAT_POLL)) + bio_endio_status(bio, BLK_STS_NOTSUPP); + else disk->fops->submit_bio(bio); - } blk_queue_exit(disk->queue); } @@ -886,8 +884,7 @@ void submit_bio_noacct(struct bio *bio) not_supported: status = BLK_STS_NOTSUPP; end_io: - bio->bi_status = status; - bio_endio(bio); + bio_endio_status(bio, status); } EXPORT_SYMBOL(submit_bio_noacct); diff --git a/block/blk-crypto-fallback.c b/block/blk-crypto-fallback.c index 61f595410832..8b04d9205b8d 100644 --- a/block/blk-crypto-fallback.c +++ b/block/blk-crypto-fallback.c @@ -361,8 +361,7 @@ static void blk_crypto_fallback_encrypt_bio(struct bio *src_bio) status = blk_crypto_get_keyslot(blk_crypto_fallback_profile, bc->bc_key, &slot); if (status != BLK_STS_OK) { - src_bio->bi_status = status; - bio_endio(src_bio); + bio_endio_status(src_bio, status); return; } __blk_crypto_fallback_encrypt_bio(src_bio, @@ -437,8 +436,7 @@ static void blk_crypto_fallback_decrypt_bio(struct work_struct *work) } mempool_free(f_ctx, bio_fallback_crypt_ctx_pool); - bio->bi_status = status; - bio_endio(bio); + bio_endio_status(bio, status); } /** @@ -499,8 +497,7 @@ bool blk_crypto_fallback_bio_prep(struct bio *bio) if (!__blk_crypto_cfg_supported(blk_crypto_fallback_profile, &bc->bc_key->crypto_cfg)) { - bio->bi_status = BLK_STS_NOTSUPP; - bio_endio(bio); + bio_endio_status(bio, BLK_STS_NOTSUPP); return false; } diff --git a/block/blk-crypto.c b/block/blk-crypto.c index 856d3c5b1fa0..165c9d2cce07 100644 --- a/block/blk-crypto.c +++ b/block/blk-crypto.c @@ -267,8 +267,7 @@ bool __blk_crypto_submit_bio(struct bio *bio) if (!IS_ENABLED(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK)) { pr_warn_once("%pg: crypto API fallback disabled; failing request.\n", bdev); - bio->bi_status = BLK_STS_NOTSUPP; - bio_endio(bio); + bio_endio_status(bio, BLK_STS_NOTSUPP); return false; } return blk_crypto_fallback_bio_prep(bio); diff --git a/block/blk-merge.c b/block/blk-merge.c index fcf09325b22e..7cc82a7a6f4e 100644 --- a/block/blk-merge.c +++ b/block/blk-merge.c @@ -122,8 +122,7 @@ struct bio *bio_submit_split_bioset(struct bio *bio, unsigned int split_sectors, struct bio *split = bio_split(bio, split_sectors, GFP_NOIO, bs); if (IS_ERR(split)) { - bio->bi_status = errno_to_blk_status(PTR_ERR(split)); - bio_endio(bio); + bio_endio_status(bio, errno_to_blk_status(PTR_ERR(split))); return NULL; } @@ -143,8 +142,7 @@ EXPORT_SYMBOL_GPL(bio_submit_split_bioset); static struct bio *bio_submit_split(struct bio *bio, int split_sectors) { if (unlikely(split_sectors < 0)) { - bio->bi_status = errno_to_blk_status(split_sectors); - bio_endio(bio); + bio_endio_status(bio, errno_to_blk_status(split_sectors)); return NULL; } diff --git a/block/blk-mq.c b/block/blk-mq.c index 4c5c16cce4f8..ade9d3a89743 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -3187,8 +3187,7 @@ void blk_mq_submit_bio(struct bio *bio) } if ((bio->bi_opf & REQ_POLLED) && !blk_mq_can_poll(q)) { - bio->bi_status = BLK_STS_NOTSUPP; - bio_endio(bio); + bio_endio_status(bio, BLK_STS_NOTSUPP); goto queue_exit; } @@ -3229,8 +3228,7 @@ void blk_mq_submit_bio(struct bio *bio) ret = blk_crypto_rq_get_keyslot(rq); if (ret != BLK_STS_OK) { - bio->bi_status = ret; - bio_endio(bio); + bio_endio_status(bio, ret); blk_mq_free_request(rq); return; } diff --git a/block/fops.c b/block/fops.c index ffe7b2042f4e..15783a6180de 100644 --- a/block/fops.c +++ b/block/fops.c @@ -218,8 +218,7 @@ static ssize_t __blkdev_direct_IO(struct kiocb *iocb, struct iov_iter *iter, ret = blkdev_iov_iter_get_pages(bio, iter, bdev); if (unlikely(ret)) { - bio->bi_status = BLK_STS_IOERR; - bio_endio(bio); + bio_endio_status(bio, BLK_STS_IOERR); break; } if (iocb->ki_flags & IOCB_NOWAIT) { diff --git a/include/linux/bio.h b/include/linux/bio.h index e60d2f5bd3dc..97f993d4d914 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -370,16 +370,27 @@ void submit_bio(struct bio *bio); extern void bio_endio(struct bio *); +/** + * bio_endio_status - end I/O on a bio with a specific status + * @bio: bio + * @status: status to set + * + * Set @bio->bi_status to @status and call bio_endio(). + **/ +static inline void bio_endio_status(struct bio *bio, blk_status_t status) +{ + bio->bi_status = status; + bio_endio(bio); +} + static inline void bio_io_error(struct bio *bio) { - bio->bi_status = BLK_STS_IOERR; - bio_endio(bio); + bio_endio_status(bio, BLK_STS_IOERR); } static inline void bio_wouldblock_error(struct bio *bio) { - bio->bi_status = BLK_STS_AGAIN; - bio_endio(bio); + bio_endio_status(bio, BLK_STS_AGAIN); } /* From b7f40ab50190e2500c3c297d15e00040dca47feb Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Wed, 27 May 2026 18:00:40 -0700 Subject: [PATCH 049/117] block: export passthrough stats enabled A user can enable io accounting for passthrough requests, so export the helper that checks if the request should be tracked. This will enable stacking drivers to to report iostats for passthrough workloads. Since the stacking request_queue may not be the one providing the request, the API has to add a parameter for the caller to specify which one to check. Reviewed-by: Nilay Shroff Reviewed-by: Nitesh Shetty Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260528010041.1533124-2-kbusch@meta.com Signed-off-by: Jens Axboe --- block/blk-mq.c | 32 +------------------------------- include/linux/blk-mq.h | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index ade9d3a89743..629e16003eb7 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -1088,43 +1088,13 @@ static inline void blk_account_io_done(struct request *req, u64 now) } } -static inline bool blk_rq_passthrough_stats(struct request *req) -{ - struct bio *bio = req->bio; - - if (!blk_queue_passthrough_stat(req->q)) - return false; - - /* Requests without a bio do not transfer data. */ - if (!bio) - return false; - - /* - * Stats are accumulated in the bdev, so must have one attached to a - * bio to track stats. Most drivers do not set the bdev for passthrough - * requests, but nvme is one that will set it. - */ - if (!bio->bi_bdev) - return false; - - /* - * We don't know what a passthrough command does, but we know the - * payload size and data direction. Ensuring the size is aligned to the - * block size filters out most commands with payloads that don't - * represent sector access. - */ - if (blk_rq_bytes(req) & (bdev_logical_block_size(bio->bi_bdev) - 1)) - return false; - return true; -} - static inline void blk_account_io_start(struct request *req) { trace_block_io_start(req); if (!blk_queue_io_stat(req->q)) return; - if (blk_rq_is_passthrough(req) && !blk_rq_passthrough_stats(req)) + if (blk_rq_is_passthrough(req) && !blk_rq_passthrough_stats(req, req->q)) return; req->rq_flags |= RQF_IO_STAT; diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h index 24b4160aeaad..af878597afb8 100644 --- a/include/linux/blk-mq.h +++ b/include/linux/blk-mq.h @@ -1252,4 +1252,44 @@ static inline int blk_rq_map_sg(struct request *rq, struct scatterlist *sglist) } void blk_dump_rq_flags(struct request *, char *); +/** + * blk_rq_passthrough_stats - check if this request should account stats + * @rq: request to check + * @q: the queue accumulating the stats + * + * Note, @q does not necessarily need to be the request_queue that provides + * @rq. + * + * Return: true if stats should be accounted. + */ +static inline bool blk_rq_passthrough_stats(struct request *rq, + struct request_queue *q) +{ + struct bio *bio = rq->bio; + + if (!blk_queue_passthrough_stat(q)) + return false; + + /* Requests without a bio do not transfer data. */ + if (!bio) + return false; + + /* + * Stats are accumulated in the bdev, so must have one attached to a + * bio to track stats. Most drivers do not set the bdev for passthrough + * requests, but nvme is one that will set it. + */ + if (!bio->bi_bdev) + return false; + + /* + * We don't know what a passthrough command does, but we know the + * payload size and data direction. Ensuring the size is aligned to the + * block size filters out most commands with payloads that don't + * represent sector access. + */ + if (blk_rq_bytes(rq) & (bdev_logical_block_size(bio->bi_bdev) - 1)) + return false; + return true; +} #endif /* BLK_MQ_H */ From 7d6eb455ecf0f95c54257ae372ac1272cff834e3 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Wed, 27 May 2026 18:00:41 -0700 Subject: [PATCH 050/117] nvme: add support multipath passthrough iostats Don't skip the io accounting for passthrough commands if the user enabled tracking these. Reviewed-by: Nilay Shroff Reviewed-by: Nitesh Shetty Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260528010041.1533124-3-kbusch@meta.com Signed-off-by: Jens Axboe --- drivers/nvme/host/ioctl.c | 9 +++++++++ drivers/nvme/host/multipath.c | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/ioctl.c b/drivers/nvme/host/ioctl.c index 9597a87cf05d..b449da7798aa 100644 --- a/drivers/nvme/host/ioctl.c +++ b/drivers/nvme/host/ioctl.c @@ -102,8 +102,17 @@ static struct request *nvme_alloc_user_request(struct request_queue *q, struct nvme_command *cmd, blk_opf_t rq_flags, blk_mq_req_flags_t blk_flags) { + struct nvme_ns *ns = q->queuedata; struct request *req; + /* + * The NVME_MPATH flag is set only for IO commands sent to a namespace + * with a multipath enabled head. The request is not eligible for + * failover as passthrough requests also append REQ_FAILFAST_DRIVER. + */ + if (ns && nvme_ns_head_multipath(ns->head)) + rq_flags |= REQ_NVME_MPATH; + req = blk_mq_alloc_request(q, nvme_req_op(cmd) | rq_flags, blk_flags); if (IS_ERR(req)) return req; diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index ff442bbf2937..bca8e7c97519 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -175,9 +175,12 @@ void nvme_mpath_start_request(struct request *rq) nvme_req(rq)->flags |= NVME_MPATH_CNT_ACTIVE; } - if (!blk_queue_io_stat(disk->queue) || blk_rq_is_passthrough(rq) || + if (!blk_queue_io_stat(disk->queue) || (nvme_req(rq)->flags & NVME_MPATH_IO_STATS)) return; + if (blk_rq_is_passthrough(rq) && + !blk_rq_passthrough_stats(rq, disk->queue)) + return; nvme_req(rq)->flags |= NVME_MPATH_IO_STATS; nvme_req(rq)->start_time = bdev_start_io_acct(disk->part0, req_op(rq), From ca36c1aaee095d009d73f6623fb5e536ada80032 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 28 May 2026 18:59:03 +0100 Subject: [PATCH 051/117] block: Add bvec_folio() This is a simple helper which replaces page_folio(bvec->bv_page). Minor improvement in readability, but the real motivation is to reduce the number of references to bvec->bv_page so that it can be changed with less work. Signed-off-by: Matthew Wilcox (Oracle) Cc: Leon Romanovsky Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: William Kucharski Link: https://patch.msgid.link/20260528175905.1102280-2-willy@infradead.org Signed-off-by: Jens Axboe --- block/bio.c | 6 +++--- include/linux/bio.h | 2 +- include/linux/bvec.h | 15 +++++++++++++++ io_uring/rsrc.c | 2 +- mm/page_io.c | 4 ++-- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/block/bio.c b/block/bio.c index e9944dab0132..c3d0a00a27f1 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1299,7 +1299,7 @@ static void bio_free_folios(struct bio *bio) int i; bio_for_each_bvec_all(bv, bio, i) { - struct folio *folio = page_folio(bv->bv_page); + struct folio *folio = bvec_folio(bv); if (!is_zero_folio(folio)) folio_put(folio); @@ -1406,7 +1406,7 @@ int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen) static void bvec_unpin(struct bio_vec *bv, bool mark_dirty) { - struct folio *folio = page_folio(bv->bv_page); + struct folio *folio = bvec_folio(bv); size_t nr_pages = (bv->bv_offset + bv->bv_len - 1) / PAGE_SIZE - bv->bv_offset / PAGE_SIZE + 1; @@ -1440,7 +1440,7 @@ static void bio_iov_iter_unbounce_read(struct bio *bio, bool is_error, bvec_unpin(&bio->bi_io_vec[1 + i], mark_dirty); } - folio_put(page_folio(bio->bi_io_vec[0].bv_page)); + folio_put(bvec_folio(&bio->bi_io_vec[0])); } /** diff --git a/include/linux/bio.h b/include/linux/bio.h index 97f993d4d914..26ebfff93a16 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -283,7 +283,7 @@ static inline void bio_first_folio(struct folio_iter *fi, struct bio *bio, return; } - fi->folio = page_folio(bvec->bv_page); + fi->folio = bvec_folio(bvec); fi->offset = bvec->bv_offset + PAGE_SIZE * folio_page_idx(fi->folio, bvec->bv_page); fi->_seg_count = bvec->bv_len; diff --git a/include/linux/bvec.h b/include/linux/bvec.h index f4c7ec282ac9..c939f2b3be87 100644 --- a/include/linux/bvec.h +++ b/include/linux/bvec.h @@ -74,6 +74,21 @@ static inline void bvec_set_virt(struct bio_vec *bv, void *vaddr, bvec_set_page(bv, virt_to_page(vaddr), len, offset_in_page(vaddr)); } +/** + * bvec_folio - Return the first folio referenced by this bvec + * @bv: bvec to access + * + * A bvec can contain non-folio memory, so this should only be called by + * the creator of the bvec; drivers have no business looking at the owner + * of the memory. It may not even be the right interface for the caller + * to use as a bvec can span multiple folios. You may be better off using + * something like bio_for_each_folio_all() which iterates over all folios. + */ +static inline struct folio *bvec_folio(const struct bio_vec *bv) +{ + return page_folio(bv->bv_page); +} + struct bvec_iter { /* * Current device address in 512 byte sectors. Only updated by the bio diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c index 650303626be6..5d792f70ec1e 100644 --- a/io_uring/rsrc.c +++ b/io_uring/rsrc.c @@ -102,7 +102,7 @@ static void io_release_ubuf(void *priv) unsigned int i; for (i = 0; i < imu->nr_bvecs; i++) { - struct folio *folio = page_folio(imu->bvec[i].bv_page); + struct folio *folio = bvec_folio(&imu->bvec[i]); unpin_user_folio(folio, 1); } diff --git a/mm/page_io.c b/mm/page_io.c index 70cea9e24d2f..a59b73f8bdd9 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -490,7 +490,7 @@ static void sio_read_complete(struct kiocb *iocb, long ret) if (ret == sio->len) { for (p = 0; p < sio->pages; p++) { - struct folio *folio = page_folio(sio->bvec[p].bv_page); + struct folio *folio = bvec_folio(&sio->bvec[p]); count_mthp_stat(folio_order(folio), MTHP_STAT_SWPIN); count_memcg_folio_events(folio, PSWPIN, folio_nr_pages(folio)); @@ -500,7 +500,7 @@ static void sio_read_complete(struct kiocb *iocb, long ret) count_vm_events(PSWPIN, sio->len >> PAGE_SHIFT); } else { for (p = 0; p < sio->pages; p++) { - struct folio *folio = page_folio(sio->bvec[p].bv_page); + struct folio *folio = bvec_folio(&sio->bvec[p]); folio_unlock(folio); } From 11ff85db51e05cf8cbeea36be1085fdacedeab55 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 28 May 2026 18:59:04 +0100 Subject: [PATCH 052/117] block: Include bvec.h kernel-doc in the htmldocs People have gone to the trouble of writing this kernel-doc; the least we can do is publish it. Signed-off-by: Matthew Wilcox (Oracle) Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: William Kucharski Link: https://patch.msgid.link/20260528175905.1102280-3-willy@infradead.org Signed-off-by: Jens Axboe --- Documentation/core-api/kernel-api.rst | 1 + include/linux/bvec.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Documentation/core-api/kernel-api.rst b/Documentation/core-api/kernel-api.rst index e8211c4ca662..4c4a57c1c094 100644 --- a/Documentation/core-api/kernel-api.rst +++ b/Documentation/core-api/kernel-api.rst @@ -307,6 +307,7 @@ Accounting Framework Block Devices ============= +.. kernel-doc:: include/linux/bvec.h .. kernel-doc:: include/linux/bio.h .. kernel-doc:: block/blk-core.c :export: diff --git a/include/linux/bvec.h b/include/linux/bvec.h index c939f2b3be87..92837e2743f1 100644 --- a/include/linux/bvec.h +++ b/include/linux/bvec.h @@ -289,6 +289,7 @@ static inline void *bvec_kmap_local(struct bio_vec *bvec) /** * memcpy_from_bvec - copy data from a bvec + * @to: Kernel virtual address to copy to. * @bvec: bvec to copy from * * Must be called on single-page bvecs only. @@ -301,6 +302,7 @@ static inline void memcpy_from_bvec(char *to, struct bio_vec *bvec) /** * memcpy_to_bvec - copy data to a bvec * @bvec: bvec to copy to + * @from: Kernel virtual address to copy from. * * Must be called on single-page bvecs only. */ From c7696ecf5b71f66799f4426a4c85349e734a27db Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Wed, 20 May 2026 19:21:48 +0800 Subject: [PATCH 053/117] MAINTAINERS: update Yu Kuai's email address Update Yu Kuai's maintainer entries to use the new fygo.io address. Link: https://patch.msgid.link/20260520112627.1264368-1-yukuai@fnnas.com Signed-off-by: Yu Kuai --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 882214b0e7db..89940659ef40 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4478,7 +4478,7 @@ F: Documentation/filesystems/befs.rst F: fs/befs/ BFQ I/O SCHEDULER -M: Yu Kuai +M: Yu Kuai L: linux-block@vger.kernel.org S: Odd Fixes F: Documentation/block/bfq-iosched.rst @@ -24796,7 +24796,7 @@ F: include/linux/property.h SOFTWARE RAID (Multiple Disks) SUPPORT M: Song Liu -M: Yu Kuai +M: Yu Kuai R: Li Nan R: Xiao Ni L: linux-raid@vger.kernel.org From e99e15f4e0a0d5049c5604ef1182a61608905d73 Mon Sep 17 00:00:00 2001 From: Li Nan Date: Fri, 8 May 2026 17:55:13 +0800 Subject: [PATCH 054/117] MAINTAINERS: Update Li Nan's E-mail address Change to my new email address on didiglobal.com. Signed-off-by: Li Nan Link: https://patch.msgid.link/tencent_8F8173BEDF20E98550D5429DF802F34A7108@qq.com Signed-off-by: Yu Kuai --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 89940659ef40..6e5cdf51f293 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24797,7 +24797,7 @@ F: include/linux/property.h SOFTWARE RAID (Multiple Disks) SUPPORT M: Song Liu M: Yu Kuai -R: Li Nan +R: Li Nan R: Xiao Ni L: linux-raid@vger.kernel.org S: Supported From 1d24d4c1ea1d9d3211c1d178e0c8d95be0348975 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Tue, 28 Apr 2026 19:20:10 -0400 Subject: [PATCH 055/117] dm-raid: only requeue bios when dm is suspending Returning DM_MAPIO_REQUEUE from the target map() function only requeues the bio during noflush suspends. During regular operations or during flushing suspends, it fails the bio. Failing the bio during flushing suspends is the correct behavior here. The bio cannot be handled, and dm-raid cannot suspend while it is outstanding. But during normal operations, dm-raid should not push the bio back to dm. Instead, wait for the reshape to be resumed. Signed-off-by: Benjamin Marzinski Reviewed-by: Xiao Ni Link: https://patch.msgid.link/20260428232010.2785514-1-bmarzins@redhat.com Signed-off-by: Yu Kuai --- drivers/md/dm-raid.c | 6 ++++++ drivers/md/md.h | 2 ++ drivers/md/raid5.c | 7 +++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-raid.c b/drivers/md/dm-raid.c index c5dc083c7244..8f5a5e1342a9 100644 --- a/drivers/md/dm-raid.c +++ b/drivers/md/dm-raid.c @@ -3831,6 +3831,7 @@ static void raid_presuspend(struct dm_target *ti) * resume, raid_postsuspend() is too late. */ set_bit(RT_FLAG_RS_FROZEN, &rs->runtime_flags); + set_bit(MD_DM_SUSPENDING, &mddev->flags); if (!reshape_interrupted(mddev)) return; @@ -3847,13 +3848,16 @@ static void raid_presuspend(struct dm_target *ti) static void raid_presuspend_undo(struct dm_target *ti) { struct raid_set *rs = ti->private; + struct mddev *mddev = &rs->md; + clear_bit(MD_DM_SUSPENDING, &mddev->flags); clear_bit(RT_FLAG_RS_FROZEN, &rs->runtime_flags); } static void raid_postsuspend(struct dm_target *ti) { struct raid_set *rs = ti->private; + struct mddev *mddev = &rs->md; if (!test_and_set_bit(RT_FLAG_RS_SUSPENDED, &rs->runtime_flags)) { /* @@ -3864,6 +3868,8 @@ static void raid_postsuspend(struct dm_target *ti) mddev_suspend(&rs->md, false); rs->md.ro = MD_RDONLY; } + clear_bit(MD_DM_SUSPENDING, &mddev->flags); + } static void attempt_restore_of_faulty_devices(struct raid_set *rs) diff --git a/drivers/md/md.h b/drivers/md/md.h index 52c378086046..9e5100609d12 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -346,6 +346,7 @@ struct md_cluster_operations; * @MD_HAS_SUPERBLOCK: There is persistence sb in member disks. * @MD_FAILLAST_DEV: Allow last rdev to be removed. * @MD_SERIALIZE_POLICY: Enforce write IO is not reordered, just used by raid1. + * @MD_DM_SUSPENDING: This DM raid device is suspending. * * change UNSUPPORTED_MDDEV_FLAGS for each array type if new flag is added */ @@ -365,6 +366,7 @@ enum mddev_flags { MD_HAS_SUPERBLOCK, MD_FAILLAST_DEV, MD_SERIALIZE_POLICY, + MD_DM_SUSPENDING, }; enum mddev_sb_flags { diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 0d76e82f4506..65ae7d8930fc 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -6042,8 +6042,11 @@ static enum stripe_result make_stripe_request(struct mddev *mddev, raid5_release_stripe(sh); out: if (ret == STRIPE_SCHEDULE_AND_RETRY && reshape_interrupted(mddev)) { - bi->bi_status = BLK_STS_RESOURCE; - ret = STRIPE_WAIT_RESHAPE; + if (!mddev_is_dm(mddev) || + test_bit(MD_DM_SUSPENDING, &mddev->flags)) { + bi->bi_status = BLK_STS_RESOURCE; + ret = STRIPE_WAIT_RESHAPE; + } pr_err_ratelimited("dm-raid456: io across reshape position while reshape can't make progress"); } return ret; From abaf4783822851678632e5cea98aa5aead99852f Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Tue, 28 Apr 2026 15:05:24 +0200 Subject: [PATCH 056/117] md: skip redundant raid_disks update when value is unchanged Calling update_raid_disks() with the same value as the current one can trigger unnecessary work. For example, RAID1 will reallocate resources such as the mempool for r1bio. Signed-off-by: Abd-Alrhman Masalkhi Link: https://patch.msgid.link/20260428130524.448063-1-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 8b568eee8743..6cb2c452f963 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -4414,9 +4414,10 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len) err = mddev_suspend_and_lock(mddev); if (err) return err; - if (mddev->pers) - err = update_raid_disks(mddev, n); - else if (mddev->reshape_position != MaxSector) { + if (mddev->pers) { + if (n != mddev->raid_disks) + err = update_raid_disks(mddev, n); + } else if (mddev->reshape_position != MaxSector) { struct md_rdev *rdev; int olddisks = mddev->raid_disks - mddev->delta_disks; From 6b8a26af065ddc93de2aa5c9f0df98dce9723442 Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Fri, 15 May 2026 17:30:19 +0800 Subject: [PATCH 057/117] md/raid10: reset read_slot when reusing r10bio for discard put_all_bios() always drops devs[i].bio, but it only drops devs[i].repl_bio when r10_bio->read_slot < 0. If discard reuses an r10bio that was previously used for a read, read_slot can still be non-negative, and discard cleanup can skip bio_put() on repl_bio. Reset read_slot to -1 when preparing an r10bio for discard so the replacement bio is always released correctly. Fixes: d30588b2731f ("md/raid10: improve raid10 discard request") Signed-off-by: Chen Cheng Reviewed-by: Xiao Ni Link: https://patch.msgid.link/20260515093019.3436882-1-chencheng@fnnas.com Signed-off-by: Yu Kuai --- drivers/md/raid10.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 39085e7dd6d2..7dc2a5a127e8 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1727,6 +1727,7 @@ static int raid10_handle_discard(struct mddev *mddev, struct bio *bio) r10_bio->mddev = mddev; r10_bio->state = 0; r10_bio->sectors = 0; + r10_bio->read_slot = -1; memset(r10_bio->devs, 0, sizeof(r10_bio->devs[0]) * geo->raid_disks); wait_blocked_dev(mddev, r10_bio); From 7b15c24f805339a585cfe7d72f446b7e88b9bcc0 Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Fri, 1 May 2026 13:46:49 +0200 Subject: [PATCH 058/117] md/raid1,raid10: fix deadlock in read error recovery path raid1d and raid10d may resubmit a split md cloned bio while handling a read error. In this case, resubmitting the bio can lead to a deadlock if the array is suspended before md_handle_request() acquires an active_io reference via percpu_ref_tryget_live(). Since the cloned bio already holds an active_io reference, trying to acquire another reference via percpu_ref_tryget_live() can lead to a deadlock while the array is suspended. Fix this by using percpu_ref_get() for md cloned bios. Fixes: bb2a9acefaf9 ("md/raid1: switch to use md_account_bio() for io accounting") Fixes: 820455238366 ("md/raid10: switch to use md_account_bio() for io accounting") Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Xiao Ni Reviewed-by: Yu Kuai Link: https://patch.msgid.link/20260501114652.590037-2-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/md.c | 25 ++++++++++++++++--------- drivers/md/md.h | 5 +++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 6cb2c452f963..096bb64e87bd 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -395,17 +395,24 @@ static bool is_suspended(struct mddev *mddev, struct bio *bio) bool md_handle_request(struct mddev *mddev, struct bio *bio) { check_suspended: - if (is_suspended(mddev, bio)) { - /* Bail out if REQ_NOWAIT is set for the bio */ - if (bio->bi_opf & REQ_NOWAIT) { - bio_wouldblock_error(bio); - return true; + if (unlikely(md_cloned_bio(mddev, bio))) { + /* + * This bio is an MD cloned bio and already holds an + * active_io reference, so percpu_ref_get() is safe here. + */ + percpu_ref_get(&mddev->active_io); + } else { + if (is_suspended(mddev, bio)) { + /* Bail out if REQ_NOWAIT is set for the bio */ + if (bio->bi_opf & REQ_NOWAIT) { + bio_wouldblock_error(bio); + return true; + } + wait_event(mddev->sb_wait, !is_suspended(mddev, bio)); } - wait_event(mddev->sb_wait, !is_suspended(mddev, bio)); + if (!percpu_ref_tryget_live(&mddev->active_io)) + goto check_suspended; } - if (!percpu_ref_tryget_live(&mddev->active_io)) - goto check_suspended; - if (!mddev->pers->make_request(mddev, bio)) { percpu_ref_put(&mddev->active_io); if (mddev_is_dm(mddev) && mddev->pers->prepare_suspend) diff --git a/drivers/md/md.h b/drivers/md/md.h index 9e5100609d12..d8daf0f75cbb 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -1044,6 +1044,11 @@ void mddev_update_io_opt(struct mddev *mddev, unsigned int nr_stripes); extern const struct block_device_operations md_fops; +static inline bool md_cloned_bio(struct mddev *mddev, struct bio *bio) +{ + return bio->bi_pool == &mddev->io_clone_set; +} + /* * MD devices can be used undeneath by DM, in which case ->gendisk is NULL. */ From 811545e0926d02a6a0b1a1258bb5544777c164d4 Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Fri, 1 May 2026 13:46:50 +0200 Subject: [PATCH 059/117] md/raid1,raid10: fix error-path detection with md_cloned_bio() Detect the error path using md_cloned_bio() instead of relying on r1_bio in raid1 or r10_bio->read_slot in raid10, which may be NULL or -1 after splitting and resubmitting a failed bio. As a result, the error path may not be recognized and memory allocations can incorrectly use GFP_NOIO instead of (GFP_NOIO | __GFP_HIGH), which can lead to a deadlock under memory pressure. Fixes: 689389a06ce7 ("md/raid1: simplify handle_read_error().") Fixes: 545250f24809 ("md/raid10: simplify handle_read_error()") Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Xiao Ni Link: https://patch.msgid.link/20260501114652.590037-3-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 13 ++++++++++--- drivers/md/raid10.c | 20 ++++++++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 64d970e2ef50..22458df5069e 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -1343,11 +1343,18 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, bool r1bio_existed = !!r1_bio; /* - * If r1_bio is set, we are blocking the raid1d thread - * so there is a tiny risk of deadlock. So ask for + * An md cloned bio indicates we are in the error path. + * This is more reliable than checking r1_bio, which might + * be NULL even in the error path if a failed bio was split. + */ + bool err_path = md_cloned_bio(mddev, bio); + + /* + * If we are in the error path, we are blocking the raid1d + * thread so there is a tiny risk of deadlock. So ask for * emergency memory if needed. */ - gfp_t gfp = r1_bio ? (GFP_NOIO | __GFP_HIGH) : GFP_NOIO; + gfp_t gfp = err_path ? (GFP_NOIO | __GFP_HIGH) : GFP_NOIO; /* * Still need barrier for READ in case that whole diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 7dc2a5a127e8..b38a0ccd4661 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1155,7 +1155,20 @@ static void raid10_read_request(struct mddev *mddev, struct bio *bio, char b[BDEVNAME_SIZE]; int slot = r10_bio->read_slot; struct md_rdev *err_rdev = NULL; - gfp_t gfp = GFP_NOIO; + + /* + * An md cloned bio indicates we are in the error path. + * This is more reliable than checking slot, which might + * be -1 even in the error path if a failed bio was split. + */ + bool err_path = md_cloned_bio(mddev, bio); + + /* + * If we are in the error path, we are blocking the raid10d + * thread so there is a tiny risk of deadlock. So ask for + * emergency memory if needed. + */ + gfp_t gfp = err_path ? (GFP_NOIO | __GFP_HIGH) : GFP_NOIO; if (slot >= 0 && r10_bio->devs[slot].rdev) { /* @@ -1166,11 +1179,6 @@ static void raid10_read_request(struct mddev *mddev, struct bio *bio, * we lose the device name in error messages. */ int disk; - /* - * As we are blocking raid10, it is a little safer to - * use __GFP_HIGH. - */ - gfp = GFP_NOIO | __GFP_HIGH; disk = r10_bio->devs[slot].devnum; err_rdev = conf->mirrors[disk].rdev; From ba976e3501111d11c550848b3b7341a73035f582 Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Fri, 1 May 2026 13:46:51 +0200 Subject: [PATCH 060/117] md/raid1,raid10: fix bio accounting for split md cloned bios Use md_cloned_bio() to control bio accounting instead of relying on r1bio_existed in raid1 or the io_accounting flag in raid10. The previous logic does not reliably reflect whether a bio is an md cloned bio. When a failed bio is split and resubmitted via bio_submit_split_bioset() on the error path, this can lead to either double accounting for md cloned bios, or missing accounting for bios returned from bio_submit_split_bioset() Fix this by using md_cloned_bio() to detect md cloned bios and skip accounting accordingly. Fixes: bb2a9acefaf9 ("md/raid1: switch to use md_account_bio() for io accounting") Fixes: 820455238366 ("md/raid10: switch to use md_account_bio() for io accounting") Signed-off-by: Abd-Alrhman Masalkhi Reviewed-by: Xiao Ni Link: https://patch.msgid.link/20260501114652.590037-4-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 2 +- drivers/md/raid10.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 22458df5069e..603aa09088f0 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -1418,7 +1418,7 @@ static void raid1_read_request(struct mddev *mddev, struct bio *bio, } r1_bio->read_disk = rdisk; - if (!r1bio_existed) { + if (likely(!md_cloned_bio(mddev, bio))) { md_account_bio(mddev, &bio); r1_bio->master_bio = bio; } diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index b38a0ccd4661..5bd7698e0a1b 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1146,7 +1146,7 @@ static bool regular_request_wait(struct mddev *mddev, struct r10conf *conf, } static void raid10_read_request(struct mddev *mddev, struct bio *bio, - struct r10bio *r10_bio, bool io_accounting) + struct r10bio *r10_bio) { struct r10conf *conf = mddev->private; struct bio *read_bio; @@ -1226,7 +1226,7 @@ static void raid10_read_request(struct mddev *mddev, struct bio *bio, } slot = r10_bio->read_slot; - if (io_accounting) { + if (likely(!md_cloned_bio(mddev, bio))) { md_account_bio(mddev, &bio); r10_bio->master_bio = bio; } @@ -1552,7 +1552,7 @@ static void __make_request(struct mddev *mddev, struct bio *bio, int sectors) conf->geo.raid_disks); if (bio_data_dir(bio) == READ) - raid10_read_request(mddev, bio, r10_bio, true); + raid10_read_request(mddev, bio, r10_bio); else raid10_write_request(mddev, bio, r10_bio); } @@ -2867,7 +2867,7 @@ static void handle_read_error(struct mddev *mddev, struct r10bio *r10_bio) rdev_dec_pending(rdev, mddev); r10_bio->state = 0; - raid10_read_request(mddev, r10_bio->master_bio, r10_bio, false); + raid10_read_request(mddev, r10_bio->master_bio, r10_bio); /* * allow_barrier after re-submit to ensure no sync io * can be issued while regular io pending. From fcba8031327ddac251671fdc1be5399786b8dda7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 29 May 2026 07:42:59 +0200 Subject: [PATCH 061/117] md/raid1: cleanup handle_read_error Unwind the main conditional with duplicate conditions and initialize variables at initialization time where possible. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260529054308.2720300-2-hch@lst.de Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 603aa09088f0..98476ab96c52 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -2634,35 +2634,33 @@ static void handle_write_finished(struct r1conf *conf, struct r1bio *r1_bio) static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio) { + struct md_rdev *rdev = conf->mirrors[r1_bio->read_disk].rdev; + struct bio *bio = r1_bio->bios[r1_bio->read_disk]; struct mddev *mddev = conf->mddev; - struct bio *bio; - struct md_rdev *rdev; sector_t sector; clear_bit(R1BIO_ReadError, &r1_bio->state); - /* we got a read error. Maybe the drive is bad. Maybe just - * the block and we can fix it. - * We freeze all other IO, and try reading the block from - * other devices. When we find one, we re-write - * and check it that fixes the read error. - * This is all done synchronously while the array is - * frozen - */ - bio = r1_bio->bios[r1_bio->read_disk]; bio_put(bio); r1_bio->bios[r1_bio->read_disk] = NULL; - rdev = conf->mirrors[r1_bio->read_disk].rdev; - if (mddev->ro == 0 - && !test_bit(FailFast, &rdev->flags)) { + /* + * We got a read error. Maybe the drive is bad. Maybe just the block + * and we can fix it. + * + * If allowed, freeze all other IO, and try reading the block from other + * devices. If we find one, we re-write and check it that fixes the + * read error. This is all done synchronously while the array is + * frozen. + */ + if (mddev->ro) { + r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED; + } else if (test_bit(FailFast, &rdev->flags)) { + md_error(mddev, rdev); + } else { freeze_array(conf, 1); fix_read_error(conf, r1_bio); unfreeze_array(conf); - } else if (mddev->ro == 0 && test_bit(FailFast, &rdev->flags)) { - md_error(mddev, rdev); - } else { - r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED; } rdev_dec_pending(rdev, conf->mddev); From 6e3b0b91334d1dfaa20ca55eac835f5945a3b7c8 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 29 May 2026 07:43:00 +0200 Subject: [PATCH 062/117] md/raid1: move the exceed_read_errors condition out of fix_read_error This condition much better fits into the only caller, limiting fix_read_error to actually fix up data devices after a read error. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260529054308.2720300-3-hch@lst.de Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 98476ab96c52..85a17909d8fe 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -2418,11 +2418,6 @@ static void fix_read_error(struct r1conf *conf, struct r1bio *r1_bio) struct mddev *mddev = conf->mddev; struct md_rdev *rdev = conf->mirrors[read_disk].rdev; - if (exceed_read_errors(mddev, rdev)) { - r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED; - return; - } - while(sectors) { int s = sectors; int d = read_disk; @@ -2659,7 +2654,10 @@ static void handle_read_error(struct r1conf *conf, struct r1bio *r1_bio) md_error(mddev, rdev); } else { freeze_array(conf, 1); - fix_read_error(conf, r1_bio); + if (exceed_read_errors(mddev, rdev)) + r1_bio->bios[r1_bio->read_disk] = IO_BLOCKED; + else + fix_read_error(conf, r1_bio); unfreeze_array(conf); } From 909d9dc3b5730c8ed7b764c68bc788342df2a07b Mon Sep 17 00:00:00 2001 From: Abd-Alrhman Masalkhi Date: Sat, 30 May 2026 15:14:11 +0000 Subject: [PATCH 063/117] raid1: fix nr_pending leak in REQ_ATOMIC bad-block error path In raid1_write_request(), each per-mirror loop iteration begins by incrementing rdev->nr_pending. If a REQ_ATOMIC write encounters a badblock within the requested range, the code jumps to err_handle without dropping the reference taken for the current mirror. err_handle's cleanup loop will only decrements for k < i and r1_bio->bios[k] is non-NULL. The current slot is therefore skipped, leaving its nr_pending reference leaked permanently. The reference prevents the rdev from ever being removed, since raid1_remove_conf() refuses to remove an rdev with nr_pending > 0. Fix this by calling rdev_dec_pending() before jumping to err_handle. Fixes: f2a38abf5f1c ("md/raid1: Atomic write support") Signed-off-by: Abd-Alrhman Masalkhi Link: https://patch.msgid.link/20260530151411.4119-1-abd.masalkhi@gmail.com Signed-off-by: Yu Kuai --- drivers/md/raid1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 85a17909d8fe..b1ed4cc6ade4 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -1603,8 +1603,10 @@ static void raid1_write_request(struct mddev *mddev, struct bio *bio, * complexity of supporting that is not worth * the benefit. */ - if (bio->bi_opf & REQ_ATOMIC) + if (bio->bi_opf & REQ_ATOMIC) { + rdev_dec_pending(rdev, mddev); goto err_handle; + } good_sectors = first_bad - r1_bio->sector; if (good_sectors < max_sectors) From 717359a168bb66ac95f6161715d17e491ee86ca7 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 27 May 2026 16:19:33 +0200 Subject: [PATCH 064/117] md/raid0: use str_plural helper in dump_zones Replace the manual ternary "s" pluralization with str_plural() to simplify the code. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260527141932.1243503-2-thorsten.blum@linux.dev Signed-off-by: Yu Kuai --- drivers/md/raid0.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index 5e38a51e349a..699c2de8983a 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "md.h" #include "raid0.h" @@ -43,7 +44,7 @@ static void dump_zones(struct mddev *mddev) int raid_disks = conf->strip_zone[0].nb_dev; pr_debug("md: RAID0 configuration for %s - %d zone%s\n", mdname(mddev), - conf->nr_strip_zones, conf->nr_strip_zones==1?"":"s"); + conf->nr_strip_zones, str_plural(conf->nr_strip_zones)); for (j = 0; j < conf->nr_strip_zones; j++) { char line[200]; int len = 0; From 2957771379fa335103a4b539db57bb2271e12142 Mon Sep 17 00:00:00 2001 From: Haoze Xie Date: Sat, 30 May 2026 14:11:54 +0800 Subject: [PATCH 065/117] rust: block: fix GenDisk cleanup paths GenDiskBuilder::build() still has fallible work after __blk_mq_alloc_disk(), but its error path only recovers the foreign queue data. That leaks the temporary gendisk and request_queue until later teardown. If the caller moved the last Arc> into build(), the leaked queue can retain blk-mq state after the tag set is dropped. Fix the pre-registration failure path by dropping the temporary gendisk reference with put_disk() before recovering queue_data, so disk_release() can tear down the owned queue. Also pair GenDisk::drop() with put_disk() after del_gendisk(). Once a Rust GenDisk has been added with device_add_disk(), del_gendisk() only unregisters it; the final gendisk reference still has to be dropped to complete the release path. Fixes: 3253aba3408a ("rust: block: introduce `kernel::block::mq` module") Cc: stable@kernel.org Reported-by: Yuan Tan Reported-by: Xin Liu Reviewed-by: Andreas Hindborg Signed-off-by: Haoze Xie Signed-off-by: Ren Wei Link: https://patch.msgid.link/b70aff9a920cc42110fe5cf454c3099561863519.1780063368.git.royenheart@gmail.com Signed-off-by: Jens Axboe --- rust/kernel/block/mq/gen_disk.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs index 912cb805caf5..fc97dd873974 100644 --- a/rust/kernel/block/mq/gen_disk.rs +++ b/rust/kernel/block/mq/gen_disk.rs @@ -150,6 +150,19 @@ pub fn build( // SAFETY: `gendisk` is a valid pointer as we initialized it above unsafe { (*gendisk).fops = &TABLE }; + let cleanup_failure = ScopeGuard::new_with_data((gendisk, data), |(gendisk, data)| { + // SAFETY: `gendisk` came from `__blk_mq_alloc_disk()` above and + // has not been added to the VFS on this cleanup path. + unsafe { bindings::put_disk(gendisk) }; + // SAFETY: `data` came from `into_foreign()` above and has not been + // converted back on this cleanup path. + drop(unsafe { T::QueueData::from_foreign(data) }); + }); + + // The failure guard now owns both pieces of cleanup; the early guard + // must not run on this path anymore. + recover_data.dismiss(); + let mut writer = NullTerminatedFormatter::new( // SAFETY: `gendisk` points to a valid and initialized instance. We // have exclusive access, since the disk is not added to the VFS @@ -172,7 +185,7 @@ pub fn build( }, )?; - recover_data.dismiss(); + cleanup_failure.dismiss(); // INVARIANT: `gendisk` was initialized above. // INVARIANT: `gendisk` was added to the VFS via `device_add_disk` above. @@ -215,6 +228,11 @@ fn drop(&mut self) { // to the VFS. unsafe { bindings::del_gendisk(self.gendisk) }; + // SAFETY: By type invariant, `self.gendisk` was added to the VFS, so + // `put_disk()` must follow `del_gendisk()` to drop the final gendisk + // reference and trigger the remaining release path. + unsafe { bindings::put_disk(self.gendisk) }; + // SAFETY: `queue.queuedata` was created by `GenDiskBuilder::build` with // a call to `ForeignOwnable::into_foreign` to create `queuedata`. // `ForeignOwnable::from_foreign` is only called here. From 2e1b3f4c51ace14f67201bd2a92ca6312a3c3724 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 29 May 2026 18:12:55 -0700 Subject: [PATCH 066/117] rbd: check snap_count against RBD_MAX_SNAP_COUNT snap_count is u32 but the comparison is against a SIZE_MAX-derived value (~2^61 on 64-bit), which clang flags as always false with -Wtautological-constant-out-of-range-compare. The proper check here should be that snap_count does not go over RBD_MAX_SNAP_COUNT. Assisted-by: Opencode:Big-pickle Signed-off-by: Rosen Penev Reviewed-by: Alex Elder Link: https://patch.msgid.link/20260530011255.52916-1-rosenp@gmail.com Signed-off-by: Jens Axboe --- drivers/block/rbd.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index 4065336ebd1f..0a0b0a1af769 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -6094,12 +6094,9 @@ static int rbd_dev_v2_snap_context(struct rbd_device *rbd_dev, /* * Make sure the reported number of snapshot ids wouldn't go - * beyond the end of our buffer. But before checking that, - * make sure the computed size of the snapshot context we - * allocate is representable in a size_t. + * beyond the end of our buffer. */ - if (snap_count > (SIZE_MAX - sizeof (struct ceph_snap_context)) - / sizeof (u64)) { + if (snap_count > RBD_MAX_SNAP_COUNT) { ret = -EINVAL; goto out; } From 9310b955c85ceb4700c7208baff2373a611a5070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=B6hmwalder?= Date: Wed, 13 May 2026 08:55:57 +0200 Subject: [PATCH 067/117] MAINTAINERS: use new drbd-dev mailing list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We are migrating from our own infrastructure to lists.linux.dev, so change the drbd-dev address to point to the new domain. Signed-off-by: Christoph Böhmwalder Link: https://patch.msgid.link/20260513065557.36042-1-christoph.boehmwalder@linbit.com Signed-off-by: Jens Axboe --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 882214b0e7db..9ea58367fb31 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7774,7 +7774,7 @@ DRBD DRIVER M: Philipp Reisner M: Lars Ellenberg M: Christoph Böhmwalder -L: drbd-dev@lists.linbit.com +L: drbd-dev@lists.linux.dev S: Supported W: http://www.drbd.org T: git git://git.linbit.com/linux-drbd.git From f078d1aa52a4481cbf4d12c1543639d65a020d3b Mon Sep 17 00:00:00 2001 From: John Garry Date: Fri, 29 May 2026 09:52:01 +0000 Subject: [PATCH 068/117] nvme-multipath: pass NS head to nvme_mpath_revalidate_paths() In nvme_mpath_revalidate_paths(), we are passed a NS pointer and use that to lookup the NS head and then use that same NS pointer as an iter variable. It makes more sense pass the NS head and use a local variable for the NS iter. Reviewed-by: Christoph Hellwig Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 2 +- drivers/nvme/host/multipath.c | 4 ++-- drivers/nvme/host/nvme.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 5d8af8aa472e..f69e3115d8cf 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2567,7 +2567,7 @@ static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info) set_capacity_and_notify(ns->head->disk, get_capacity(ns->disk)); set_disk_ro(ns->head->disk, nvme_ns_is_readonly(ns, info)); - nvme_mpath_revalidate_paths(ns); + nvme_mpath_revalidate_paths(ns->head); blk_mq_unfreeze_queue(ns->head->disk->queue, memflags); } diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 263161cb8ac0..e00e2842df30 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -254,10 +254,10 @@ void nvme_mpath_clear_ctrl_paths(struct nvme_ctrl *ctrl) srcu_read_unlock(&ctrl->srcu, srcu_idx); } -void nvme_mpath_revalidate_paths(struct nvme_ns *ns) +void nvme_mpath_revalidate_paths(struct nvme_ns_head *head) { - struct nvme_ns_head *head = ns->head; sector_t capacity = get_capacity(head->disk); + struct nvme_ns *ns; int node; int srcu_idx; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 9ccaed0b9dbf..86b09c06b9e0 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -1043,7 +1043,7 @@ void nvme_mpath_update(struct nvme_ctrl *ctrl); void nvme_mpath_uninit(struct nvme_ctrl *ctrl); void nvme_mpath_stop(struct nvme_ctrl *ctrl); bool nvme_mpath_clear_current_path(struct nvme_ns *ns); -void nvme_mpath_revalidate_paths(struct nvme_ns *ns); +void nvme_mpath_revalidate_paths(struct nvme_ns_head *head); void nvme_mpath_clear_ctrl_paths(struct nvme_ctrl *ctrl); void nvme_mpath_remove_disk(struct nvme_ns_head *head); void nvme_mpath_start_request(struct request *rq); @@ -1108,7 +1108,7 @@ static inline bool nvme_mpath_clear_current_path(struct nvme_ns *ns) { return false; } -static inline void nvme_mpath_revalidate_paths(struct nvme_ns *ns) +static inline void nvme_mpath_revalidate_paths(struct nvme_ns_head *head) { } static inline void nvme_mpath_clear_ctrl_paths(struct nvme_ctrl *ctrl) From 4cf06977bdb6a037e2717b4117f3fd636f6e9641 Mon Sep 17 00:00:00 2001 From: liyouhong Date: Fri, 29 May 2026 16:51:43 +0800 Subject: [PATCH 069/117] nvme-multipath: require exact iopolicy names for module parameter The iopolicy module parameter uses strncmp prefix matching, so values like "numax" are accepted as "numa". The per-subsystem sysfs attribute already requires an exact match via sysfs_streq(). Parse both through a shared helper so invalid values are rejected consistently. Reviewed-by: Christoph Hellwig Signed-off-by: liyouhong Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 40 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index e00e2842df30..d6c51f59ff25 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -73,19 +73,29 @@ static const char *nvme_iopolicy_names[] = { static int iopolicy = NVME_IOPOLICY_NUMA; +static int nvme_iopolicy_parse(const char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(nvme_iopolicy_names); i++) { + if (sysfs_streq(str, nvme_iopolicy_names[i])) + return i; + } + return -EINVAL; +} + static int nvme_set_iopolicy(const char *val, const struct kernel_param *kp) { + int policy; + if (!val) return -EINVAL; - if (!strncmp(val, "numa", 4)) - iopolicy = NVME_IOPOLICY_NUMA; - else if (!strncmp(val, "round-robin", 11)) - iopolicy = NVME_IOPOLICY_RR; - else if (!strncmp(val, "queue-depth", 11)) - iopolicy = NVME_IOPOLICY_QD; - else - return -EINVAL; + policy = nvme_iopolicy_parse(val); + if (policy < 0) + return policy; + + iopolicy = policy; return 0; } @@ -1039,16 +1049,14 @@ static ssize_t nvme_subsys_iopolicy_store(struct device *dev, { struct nvme_subsystem *subsys = container_of(dev, struct nvme_subsystem, dev); - int i; + int policy; - for (i = 0; i < ARRAY_SIZE(nvme_iopolicy_names); i++) { - if (sysfs_streq(buf, nvme_iopolicy_names[i])) { - nvme_subsys_iopolicy_update(subsys, i); - return count; - } - } + policy = nvme_iopolicy_parse(buf); + if (policy < 0) + return policy; - return -EINVAL; + nvme_subsys_iopolicy_update(subsys, policy); + return count; } SUBSYS_ATTR_RW(iopolicy, S_IRUGO | S_IWUSR, nvme_subsys_iopolicy_show, nvme_subsys_iopolicy_store); From 88bac2c1a72b8f4f71e9845699aa872df04e5850 Mon Sep 17 00:00:00 2001 From: "Achkinazi, Igor" Date: Thu, 28 May 2026 15:24:27 +0000 Subject: [PATCH 070/117] nvme-multipath: set BIO_REMAPPED on bios remapped to per-path namespace disks When nvme_ns_head_submit_bio() remaps a bio from the multipath head to a per-path namespace, bio_set_dev() clears BIO_REMAPPED. The remapped bio is then resubmitted through submit_bio_noacct() which calls bio_check_eod() because BIO_REMAPPED is not set. This races with nvme_ns_remove() which zeroes the per-path capacity before synchronize_srcu(): CPU 0 (IO submission) --------------------- srcu_read_lock() nvme_find_path() -> ns [NVME_NS_READY is set] CPU 1 (namespace removal) ------------------------- clear_bit(NVME_NS_READY) set_capacity(ns->disk, 0) synchronize_srcu() <- blocks CPU 0 (IO submission) --------------------- bio_set_dev(bio, ns->disk->part0) [clears BIO_REMAPPED] submit_bio_noacct(bio) -> bio_check_eod() sees capacity=0 -> bio fails with IO error The SRCU read lock prevents synchronize_srcu() from completing, but does not prevent set_capacity(0) from executing. The bio fails the EOD check before it reaches the NVMe driver, so nvme_failover_req() never gets a chance to redirect it to another path of multipath. IO errors are reported to the application despite another path being available. On older kernels (before commit 0b64682e78f7 "block: skip unnecessary checks for split bio"), the same race was also reachable through split remainders resubmitted via submit_bio_noacct(). Fix this by setting BIO_REMAPPED after bio_set_dev() in nvme_ns_head_submit_bio(). This skips bio_check_eod() on the per-path device; the EOD check already passed on the multipath head. NVMe per-path namespace devices are always whole disks (bd_partno=0), so the blk_partition_remap() skip also gated by BIO_REMAPPED is a no-op. The flag does not persist across failover and cannot go stale if the namespace geometry changes between attempts: nvme_failover_req() calls bio_set_dev() to redirect the bio back to the multipath head, which clears BIO_REMAPPED. When nvme_requeue_work() resubmits through submit_bio_noacct(), bio_check_eod() runs normally against the current capacity. Same approach as commit 3a905c37c351 ("block: skip bio_check_eod for partition-remapped bios"). Fixes: a7c7f7b2b641 ("nvme: use bio_set_dev to assign ->bi_bdev") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Igor Achkinazi Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index d6c51f59ff25..bd9e8d5a2713 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -521,6 +521,12 @@ static void nvme_ns_head_submit_bio(struct bio *bio) ns = nvme_find_path(head); if (likely(ns)) { bio_set_dev(bio, ns->disk->part0); + /* + * Use BIO_REMAPPED to skip bio_check_eod() when this bio + * enters submit_bio_noacct() for the per-path device. The EOD + * check already passed on the multipath head. + */ + bio_set_flag(bio, BIO_REMAPPED); bio->bi_opf |= REQ_NVME_MPATH; trace_block_bio_remap(bio, disk_devt(ns->head->disk), bio->bi_iter.bi_sector); From 53cd102a7a56079b11b897835bd9b94c14e6322c Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Wed, 27 May 2026 15:00:00 -0500 Subject: [PATCH 071/117] nvmet: fix pre-auth out-of-bounds heap read in Discovery Get Log Page nvmet_execute_disc_get_log_page() validates only the dword alignment of the host-supplied Log Page Offset (lpo). The 64-bit offset is then added to a small kzalloc'd buffer that holds the discovery log page and the result is passed straight to nvmet_copy_to_sgl(), which memcpy()s data_len bytes out to the host with no source-side bound check: u64 offset = nvmet_get_log_page_offset(req->cmd); /* 64-bit host */ size_t data_len = nvmet_get_log_page_len(req->cmd); /* 32-bit host */ ... if (offset & 0x3) { ... } /* only check */ ... alloc_len = sizeof(*hdr) + entry_size * discovery_log_entries(req); buffer = kzalloc(alloc_len, GFP_KERNEL); ... status = nvmet_copy_to_sgl(req, 0, buffer + offset, data_len); The Discovery controller is unauthenticated -- nvmet_host_allowed() returns true unconditionally for the discovery subsystem -- so the call is reachable pre-authentication by any TCP/RDMA/FC peer that can reach the nvmet target. With a discovery log page of ~1 KiB, an attacker requesting up to 4 KiB starting at offset == alloc_len reads the next slab page out and gets its content returned over the fabric (an empirical run on a default nvmet-tcp loopback target leaked 81 canonical kernel pointers in one Get Log Page response). Pointing the offset at unmapped kernel memory faults the in-kernel memcpy and crashes (or panics, on panic_on_oops=1) the target host instead. The attacker-controlled source-side offset pattern "nvmet_copy_to_sgl(req, 0, buffer + ATTACKER_OFFSET, ...)" is unique to nvmet_execute_disc_get_log_page in the entire nvmet codebase: every other Get Log Page handler in admin-cmd.c either ignores lpo (and silently starts every response at offset 0) or tracks a local destination offset with a fixed source pointer. Validate the host-supplied offset against the log page size, cap the copy length to what is actually available, and zero-fill any remainder of the host transfer buffer. The zero-fill matches the existing short-response pattern in nvmet_execute_get_log_changed_ns() (admin-cmd.c) and prevents leaking transport SGL contents when the host asks for more bytes than the log page contains. Fixes: a07b4970f464 ("nvmet: add a generic NVMe target") Cc: stable@vger.kernel.org Reviewed-by: Chaitanya Kulkarni Reviewed-by: Christoph Hellwig Signed-off-by: Bryam Vargas Signed-off-by: Keith Busch --- drivers/nvme/target/discovery.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/discovery.c b/drivers/nvme/target/discovery.c index e9b35549e254..114869d16a1f 100644 --- a/drivers/nvme/target/discovery.c +++ b/drivers/nvme/target/discovery.c @@ -166,6 +166,7 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req) u64 offset = nvmet_get_log_page_offset(req->cmd); size_t data_len = nvmet_get_log_page_len(req->cmd); size_t alloc_len; + size_t copy_len; struct nvmet_subsys_link *p; struct nvmet_port *r; u32 numrec = 0; @@ -242,7 +243,27 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req) up_read(&nvmet_config_sem); - status = nvmet_copy_to_sgl(req, 0, buffer + offset, data_len); + /* + * Validate the host-supplied log page offset before copying out. + * Without this check, the host controls a 64-bit byte offset into + * a small kzalloc'd buffer: a value past the log page lets the + * subsequent memcpy read adjacent kernel heap, and a value aimed + * at unmapped kernel memory faults the in-kernel copy and crashes + * the target host. The Discovery controller is unauthenticated, + * so the bug is reachable from any reachable fabric peer. + */ + if (offset > alloc_len) { + req->error_loc = + offsetof(struct nvme_get_log_page_command, lpo); + status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; + goto out_free_buffer; + } + + copy_len = min_t(size_t, data_len, alloc_len - offset); + status = nvmet_copy_to_sgl(req, 0, buffer + offset, copy_len); + if (!status && copy_len < data_len) + status = nvmet_zero_sgl(req, copy_len, data_len - copy_len); +out_free_buffer: kfree(buffer); out: nvmet_req_complete(req, status); From 8757fd9500cf2fd9b27451cb6eb7e28003c3d202 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 30 May 2026 08:10:57 +0000 Subject: [PATCH 072/117] nvme-tcp: Use WQ_PERCPU explicitly if wq_unbound is false. Since commit 21c05ca88a54 ("workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present"), we must explicitly set WQ_PERCPU or WQ_UNBOUND when creating workqueue. nvme_tcp_init_module() sets WQ_UNBOUND when the module param wq_unbound is set, but otherwise, WQ_PERCPU is missing, triggering the warning below: workqueue: nvme_tcp_wq is using neither WQ_PERCPU or WQ_UNBOUND. Setting WQ_PERCPU. WARNING: kernel/workqueue.c:5856 at __alloc_workqueue+0x1d02/0x2070 kernel/workqueue.c:5855, CPU#0: swapper/0/1 Let's set WQ_PERCPU if wq_unbound is false. Reported-by: syzbot+d078cba4418e65f61984@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a1a9a86.323e8352.141b09.0001.GAE@google.com/ Tested-by: Venkat Rao Bagalkote Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Reviewed-by: Chaitanya Kulkarni Signed-off-by: Kuniyuki Iwashima Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 0552aa8a1150..6241e71130c4 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -3053,6 +3053,8 @@ static int __init nvme_tcp_init_module(void) if (wq_unbound) wq_flags |= WQ_UNBOUND; + else + wq_flags |= WQ_PERCPU; nvme_tcp_wq = alloc_workqueue("nvme_tcp_wq", wq_flags, 0); if (!nvme_tcp_wq) From 0967074f6830718fd2597404ef119bddd0dbfd00 Mon Sep 17 00:00:00 2001 From: liuxixin Date: Thu, 28 May 2026 18:00:01 +0800 Subject: [PATCH 073/117] nvme: fix FDP fdpcidx bounds check The fdpcidx bounds check sets n = NUMFDPC + 1 but used > instead of >=, incorrectly accepting fdp_idx when it equals n (i.e. NUMFDPC + 1). Fixes: 30b5f20bb2dd ("nvme: register fdp parameters with the block layer") Reviewed-by: Nitesh Shetty Reviewed-by: Christoph Hellwig Signed-off-by: liuxixin Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index f69e3115d8cf..ea837b94d3e5 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2261,7 +2261,7 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl, } n = le16_to_cpu(h->numfdpc) + 1; - if (fdp_idx > n) { + if (fdp_idx >= n) { dev_warn(ctrl->device, "FDP index:%d out of range:%d\n", fdp_idx, n); /* Proceed without registering FDP streams */ From 59c0517123f2757c41d7795f841bc4c836577d17 Mon Sep 17 00:00:00 2001 From: Yao Sang Date: Thu, 28 May 2026 15:36:01 +0800 Subject: [PATCH 074/117] nvme: refresh multipath head zoned limits from path limits queue_limits_stack_bdev() updates the multipath head limits from the path queue, but it does not propagate max_open_zones or max_active_zones. As a result, a zoned multipath namespace head can keep stale 0/0 values even after a ready path reports finite zoned resource limits. When refreshing the head limits in nvme_update_ns_info(), stack the zoned resource limits directly after stacking the path queue limits. Use min_not_zero() so the block layer's 0 value keeps its "no limit" meaning while finite limits are combined conservatively. This avoids advertising "no limit" on the multipath head while keeping the zoned-limit handling local to the NVMe multipath update path. Reviewed-by: Christoph Hellwig Signed-off-by: Yao Sang Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index ea837b94d3e5..cad9d9735261 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2491,6 +2491,14 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns, return ret; } +static void nvme_stack_zone_resources(struct queue_limits *t, + const struct queue_limits *b) +{ + t->max_open_zones = min_not_zero(t->max_open_zones, b->max_open_zones); + t->max_active_zones = + min_not_zero(t->max_active_zones, b->max_active_zones); +} + static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info) { bool unsupported = false; @@ -2557,6 +2565,8 @@ static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info) lim.io_opt = ns_lim->io_opt; queue_limits_stack_bdev(&lim, ns->disk->part0, 0, ns->head->disk->disk_name); + if (lim.features & BLK_FEAT_ZONED) + nvme_stack_zone_resources(&lim, ns_lim); if (unsupported) ns->head->disk->flags |= GENHD_FL_HIDDEN; else From 6636e16e60e9f6ac09bf2faf88bce0c5cde7d83c Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 1 Jun 2026 14:15:02 +0800 Subject: [PATCH 075/117] block, bfq: release cgroup stats with bfq_group BFQ cgroup stats contain percpu counters embedded in struct bfq_group, but the old free path destroys them from bfq_pd_free(), which is tied to blkg policy-data teardown. That is not the same lifetime as struct bfq_group. BFQ pins bfq_group while bfq_queue entities refer to it, so bfq_pd_free() can drop the policy-data reference while other bfq_group references still exist. The following blkcg change also defers policy-data release through RCU and leaves BFQ to run the final bfqg_put() from an RCU callback. For that conversion, stats teardown must belong to the last bfq_group put, not to policy-data teardown. Move stats teardown to bfqg_put() so the embedded counters are destroyed exactly when the last bfq_group reference is released, before kfree(bfqg). Without this preparatory change, the RCU-delayed policy-data free conversion reproduced the following KASAN report: BUG: KASAN: slab-use-after-free in percpu_counter_destroy_many+0xf1/0x2e0 Write of size 8 at addr ffff88811d9409e0 by task test_blkcg/535 CPU: 0 UID: 0 PID: 535 Comm: test_blkcg Not tainted 7.1.0-rc2-g1e14adca0199 #1 PREEMPT ea13f83d4b74a12510d20db4a7d9a0fe8275f05c Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-5.fc42 04/01/2014 Call Trace: dump_stack_lvl+0x54/0x70 print_address_description+0x77/0x200 ? percpu_counter_destroy_many+0xf1/0x2e0 print_report+0x64/0x70 kasan_report+0x118/0x150 ? percpu_counter_destroy_many+0xf1/0x2e0 percpu_counter_destroy_many+0xf1/0x2e0 __mmdrop+0x1d8/0x350 finish_task_switch+0x3f5/0x570 __schedule+0xe8e/0x18a0 schedule+0xfe/0x1c0 schedule_timeout+0x7f/0x1d0 __wait_for_common+0x26c/0x3f0 wait_for_completion_state+0x21/0x40 call_usermodehelper_exec+0x271/0x2c0 __request_module+0x296/0x410 elv_iosched_store+0x1bc/0x2c0 queue_attr_store+0x152/0x1c0 kernfs_fop_write_iter+0x1d7/0x280 vfs_write+0x580/0x630 ksys_write+0xec/0x190 do_syscall_64+0x156/0x490 entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task 535: kasan_save_track+0x3e/0x80 __kasan_kmalloc+0x72/0x90 bfq_pd_alloc+0x60/0x100 [bfq] blkg_create+0x3bb/0xbe0 blkg_lookup_create+0x3a2/0x460 blkg_conf_start+0x24a/0x2d0 bfq_io_set_weight+0x17f/0x430 [bfq] cgroup_file_write+0x1c5/0x4b0 kernfs_fop_write_iter+0x1d7/0x280 vfs_write+0x580/0x630 ksys_write+0xec/0x190 do_syscall_64+0x156/0x490 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 0: kasan_save_track+0x3e/0x80 kasan_save_free_info+0x46/0x50 __kasan_slab_free+0x3a/0x60 kfree+0x14e/0x4f0 rcu_core+0x6f3/0xcd0 handle_softirqs+0x1a0/0x550 __irq_exit_rcu+0x8c/0x150 irq_exit_rcu+0xe/0x20 sysvec_apic_timer_interrupt+0x6e/0x80 asm_sysvec_apic_timer_interrupt+0x1a/0x20 Last potentially related work creation: kasan_save_stack+0x3e/0x60 kasan_record_aux_stack+0x99/0xb0 call_rcu+0x55/0x5c0 blkg_free_workfn+0x130/0x220 process_scheduled_works+0x655/0xb60 worker_thread+0x446/0x600 kthread+0x1f4/0x230 ret_from_fork+0x259/0x420 ret_from_fork_asm+0x1a/0x30 Signed-off-by: Yu Kuai Reviewed-by: Jan Kara Link: https://patch.msgid.link/20260601061502.899552-1-yukuai@fygo.io Signed-off-by: Jens Axboe --- block/bfq-cgroup.c | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/block/bfq-cgroup.c b/block/bfq-cgroup.c index ac83b0668764..37ab70930c8d 100644 --- a/block/bfq-cgroup.c +++ b/block/bfq-cgroup.c @@ -300,6 +300,25 @@ static struct bfq_group *bfqg_parent(struct bfq_group *bfqg) return pblkg ? blkg_to_bfqg(pblkg) : NULL; } +static void bfqg_stats_exit(struct bfqg_stats *stats) +{ + blkg_rwstat_exit(&stats->bytes); + blkg_rwstat_exit(&stats->ios); +#ifdef CONFIG_BFQ_CGROUP_DEBUG + blkg_rwstat_exit(&stats->merged); + blkg_rwstat_exit(&stats->service_time); + blkg_rwstat_exit(&stats->wait_time); + blkg_rwstat_exit(&stats->queued); + bfq_stat_exit(&stats->time); + bfq_stat_exit(&stats->avg_queue_size_sum); + bfq_stat_exit(&stats->avg_queue_size_samples); + bfq_stat_exit(&stats->dequeue); + bfq_stat_exit(&stats->group_wait_time); + bfq_stat_exit(&stats->idle_time); + bfq_stat_exit(&stats->empty_time); +#endif +} + struct bfq_group *bfqq_group(struct bfq_queue *bfqq) { struct bfq_entity *group_entity = bfqq->entity.parent; @@ -321,8 +340,10 @@ static void bfqg_get(struct bfq_group *bfqg) static void bfqg_put(struct bfq_group *bfqg) { - if (refcount_dec_and_test(&bfqg->ref)) + if (refcount_dec_and_test(&bfqg->ref)) { + bfqg_stats_exit(&bfqg->stats); kfree(bfqg); + } } static void bfqg_and_blkg_get(struct bfq_group *bfqg) @@ -433,25 +454,6 @@ void bfq_init_entity(struct bfq_entity *entity, struct bfq_group *bfqg) entity->sched_data = &bfqg->sched_data; } -static void bfqg_stats_exit(struct bfqg_stats *stats) -{ - blkg_rwstat_exit(&stats->bytes); - blkg_rwstat_exit(&stats->ios); -#ifdef CONFIG_BFQ_CGROUP_DEBUG - blkg_rwstat_exit(&stats->merged); - blkg_rwstat_exit(&stats->service_time); - blkg_rwstat_exit(&stats->wait_time); - blkg_rwstat_exit(&stats->queued); - bfq_stat_exit(&stats->time); - bfq_stat_exit(&stats->avg_queue_size_sum); - bfq_stat_exit(&stats->avg_queue_size_samples); - bfq_stat_exit(&stats->dequeue); - bfq_stat_exit(&stats->group_wait_time); - bfq_stat_exit(&stats->idle_time); - bfq_stat_exit(&stats->empty_time); -#endif -} - static int bfqg_stats_init(struct bfqg_stats *stats, gfp_t gfp) { if (blkg_rwstat_init(&stats->bytes, gfp) || @@ -552,7 +554,6 @@ static void bfq_pd_free(struct blkg_policy_data *pd) { struct bfq_group *bfqg = pd_to_bfqg(pd); - bfqg_stats_exit(&bfqg->stats); bfqg_put(bfqg); } From aa528cd12ca6e7fda15f855b6d2095fd34d167e0 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 2 Jun 2026 18:07:57 +0200 Subject: [PATCH 076/117] block/partitions/acorn: use min in {riscix,linux}_partition Use min() to replace the open-coded implementations and to simplify riscix_partition() and linux_partition(). Signed-off-by: Thorsten Blum Reviewed-by: Kees Cook Link: https://patch.msgid.link/20260602160757.973736-3-thorsten.blum@linux.dev Signed-off-by: Jens Axboe --- block/partitions/acorn.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/block/partitions/acorn.c b/block/partitions/acorn.c index 9f7389f174d0..067d6a27a3bd 100644 --- a/block/partitions/acorn.c +++ b/block/partitions/acorn.c @@ -9,6 +9,7 @@ */ #include #include +#include #include "check.h" @@ -80,7 +81,7 @@ static int riscix_partition(struct parsed_partitions *state, if (rr->magic == RISCIX_MAGIC) { - unsigned long size = nr_sects > 2 ? 2 : nr_sects; + unsigned long size = min(nr_sects, 2); int part; seq_buf_puts(&state->pp_buf, " <"); @@ -124,7 +125,7 @@ static int linux_partition(struct parsed_partitions *state, { Sector sect; struct linux_part *linuxp; - unsigned long size = nr_sects > 2 ? 2 : nr_sects; + unsigned long size = min(nr_sects, 2); seq_buf_puts(&state->pp_buf, " [Linux]"); From 3f1eccd37282de91efd0575ee8e212af4bde39b1 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 17 May 2026 19:26:17 +0200 Subject: [PATCH 077/117] n64cart: use strscpy in n64cart_probe strcpy() has been deprecated [1] because it performs no bounds checking on the destination buffer, which can lead to buffer overflows. While the current code works correctly, replace strcpy() with the safer strscpy() to follow secure coding best practices. [1] https://www.kernel.org/doc/html/latest/process/deprecated.html#strcpy Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260517172617.3954-2-thorsten.blum@linux.dev Signed-off-by: Jens Axboe --- drivers/block/n64cart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/block/n64cart.c b/drivers/block/n64cart.c index b9fdeff31caf..328da73b6f2c 100644 --- a/drivers/block/n64cart.c +++ b/drivers/block/n64cart.c @@ -12,6 +12,7 @@ #include #include #include +#include enum { PI_DRAM_REG = 0, @@ -145,7 +146,7 @@ static int __init n64cart_probe(struct platform_device *pdev) disk->flags = GENHD_FL_NO_PART; disk->fops = &n64cart_fops; disk->private_data = &pdev->dev; - strcpy(disk->disk_name, "n64cart"); + strscpy(disk->disk_name, "n64cart"); set_capacity(disk, size >> SECTOR_SHIFT); set_disk_ro(disk, 1); From 3a413ece2504c70aa34a20be4dafec04e8c741f9 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 29 May 2026 14:18:39 +0000 Subject: [PATCH 078/117] nvmet-auth: validate reply message payload bounds against transfer length nvmet_auth_reply() accesses the variable-length rval[] array using attacker-controlled hl (hash length) and dhvlen (DH value length) fields without verifying they fit within the allocated buffer of tl bytes. A malicious NVMe-oF initiator can craft a DHCHAP_REPLY message with a small transfer length but large hl/dhvlen values, causing out-of-bounds heap reads when the target processes the DH public key (rval + 2*hl) or performs the host response memcmp. With DH authentication configured, the OOB pointer is passed directly to sg_init_one() and read by crypto_kpp_compute_shared_secret(), reaching up to 526 bytes past the buffer. This is exploitable pre-authentication. Add bounds validation ensuring sizeof(*data) + 2*hl + dhvlen <= tl before any access to the variable-length fields. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") Cc: stable@vger.kernel.org Reviewed-by: Hannes Reinecke Signed-off-by: Tianchu Chen Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd-auth.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index f1e613e7c63e..0a85acf1e5c7 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -132,13 +132,22 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d) return 0; } -static u8 nvmet_auth_reply(struct nvmet_req *req, void *d) +static u8 nvmet_auth_reply(struct nvmet_req *req, void *d, u32 tl) { struct nvmet_ctrl *ctrl = req->sq->ctrl; struct nvmf_auth_dhchap_reply_data *data = d; - u16 dhvlen = le16_to_cpu(data->dhvlen); + u16 dhvlen; u8 *response; + if (tl < sizeof(*data)) + return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; + + dhvlen = le16_to_cpu(data->dhvlen); + + /* Validate that hl and dhvlen fit within the transfer length */ + if (sizeof(*data) + 2 * (size_t)data->hl + dhvlen > tl) + return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; + pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %u\n", __func__, ctrl->cntlid, req->sq->qid, data->hl, data->cvalid, dhvlen); @@ -338,7 +347,7 @@ void nvmet_execute_auth_send(struct nvmet_req *req) switch (data->auth_id) { case NVME_AUTH_DHCHAP_MESSAGE_REPLY: - dhchap_status = nvmet_auth_reply(req, d); + dhchap_status = nvmet_auth_reply(req, d, tl); if (dhchap_status == 0) req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1; From 0ef4daa6534a510d61ea67c8ad9bb5097b0dd5f8 Mon Sep 17 00:00:00 2001 From: liuxixin Date: Tue, 2 Jun 2026 22:00:01 +0800 Subject: [PATCH 079/117] nvme: validate FDP configuration descriptor sizes Validate descriptor sizes while walking the FDP configurations log so dsze == 0 or a descriptor past the log end cannot cause unbounded iteration or reads past the buffer. Reviewed-by: Nitesh Shetty Reviewed-by: Christoph Hellwig Signed-off-by: liuxixin Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index cad9d9735261..23dfce27ace2 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2273,14 +2273,16 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl, desc = log; end = log + size - sizeof(*h); for (i = 0; i < fdp_idx; i++) { - log += le16_to_cpu(desc->dsze); - desc = log; - if (log >= end) { + u16 dsze = le16_to_cpu(desc->dsze); + + if (!dsze || log + dsze > end) { dev_warn(ctrl->device, - "FDP invalid config descriptor list\n"); + "FDP invalid config descriptor at index %d\n", i); ret = 0; goto out; } + log += dsze; + desc = log; } if (le32_to_cpu(desc->nrg) > 1) { From 2caaa52c1a440a3951fb098a148d716dada1ecc2 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Sat, 30 May 2026 14:20:44 +0900 Subject: [PATCH 080/117] nvme-tcp: move nvme_tcp_reclassify_socket() Move nvme_tcp_reclassify_socket() in tcp.c after the struct nvme_tcp_queue definition. This is preparation for adding a reference to struct nvme_tcp_queue in the function, which would otherwise cause a compile failure due to the struct being defined after the function. Move the entire CONFIG_DEBUG_LOCK_ALLOC block along with the function to maintain the code organization. Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 76 ++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 6241e71130c4..353ac6ce9fbd 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -56,44 +56,6 @@ MODULE_PARM_DESC(tls_handshake_timeout, static atomic_t nvme_tcp_cpu_queues[NR_CPUS]; -#ifdef CONFIG_DEBUG_LOCK_ALLOC -/* lockdep can detect a circular dependency of the form - * sk_lock -> mmap_lock (page fault) -> fs locks -> sk_lock - * because dependencies are tracked for both nvme-tcp and user contexts. Using - * a separate class prevents lockdep from conflating nvme-tcp socket use with - * user-space socket API use. - */ -static struct lock_class_key nvme_tcp_sk_key[2]; -static struct lock_class_key nvme_tcp_slock_key[2]; - -static void nvme_tcp_reclassify_socket(struct socket *sock) -{ - struct sock *sk = sock->sk; - - if (WARN_ON_ONCE(!sock_allow_reclassification(sk))) - return; - - switch (sk->sk_family) { - case AF_INET: - sock_lock_init_class_and_name(sk, "slock-AF_INET-NVME", - &nvme_tcp_slock_key[0], - "sk_lock-AF_INET-NVME", - &nvme_tcp_sk_key[0]); - break; - case AF_INET6: - sock_lock_init_class_and_name(sk, "slock-AF_INET6-NVME", - &nvme_tcp_slock_key[1], - "sk_lock-AF_INET6-NVME", - &nvme_tcp_sk_key[1]); - break; - default: - WARN_ON_ONCE(1); - } -} -#else -static void nvme_tcp_reclassify_socket(struct socket *sock) { } -#endif - enum nvme_tcp_send_state { NVME_TCP_SEND_CMD_PDU = 0, NVME_TCP_SEND_H2C_PDU, @@ -207,6 +169,44 @@ static const struct blk_mq_ops nvme_tcp_mq_ops; static const struct blk_mq_ops nvme_tcp_admin_mq_ops; static int nvme_tcp_try_send(struct nvme_tcp_queue *queue); +#ifdef CONFIG_DEBUG_LOCK_ALLOC +/* lockdep can detect a circular dependency of the form + * sk_lock -> mmap_lock (page fault) -> fs locks -> sk_lock + * because dependencies are tracked for both nvme-tcp and user contexts. Using + * a separate class prevents lockdep from conflating nvme-tcp socket use with + * user-space socket API use. + */ +static struct lock_class_key nvme_tcp_sk_key[2]; +static struct lock_class_key nvme_tcp_slock_key[2]; + +static void nvme_tcp_reclassify_socket(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (WARN_ON_ONCE(!sock_allow_reclassification(sk))) + return; + + switch (sk->sk_family) { + case AF_INET: + sock_lock_init_class_and_name(sk, "slock-AF_INET-NVME", + &nvme_tcp_slock_key[0], + "sk_lock-AF_INET-NVME", + &nvme_tcp_sk_key[0]); + break; + case AF_INET6: + sock_lock_init_class_and_name(sk, "slock-AF_INET6-NVME", + &nvme_tcp_slock_key[1], + "sk_lock-AF_INET6-NVME", + &nvme_tcp_sk_key[1]); + break; + default: + WARN_ON_ONCE(1); + } +} +#else +static void nvme_tcp_reclassify_socket(struct socket *sock) { } +#endif + static inline struct nvme_tcp_ctrl *to_tcp_ctrl(struct nvme_ctrl *ctrl) { return container_of(ctrl, struct nvme_tcp_ctrl, ctrl); From 19bdb70c77d3b24239a453291299b64040bdba86 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 4 Jun 2026 11:32:08 +0900 Subject: [PATCH 081/117] nvme-tcp: lockdep: use dynamic lockdep keys per socket instance When NVMe-TCP controller setup and teardown are repeated with lockdep enabled, lockdep reports false positives WARN for the following locks: 1) &q->elevator_lock : IO scheduler change context 2) &q->q_usage_counter(io) : SCSI disk probe context 3) fs_reclaim : CPU hotplug bring-up context 4) cpu_hotplug_lock : socket establishment context 5) sk_lock-AF_INET-NVME : MQ sched dispatch context for the socket 6) set->srcu : NVMe controller delete context The lockdep WARN was observed by running blktests test case nvme/005 for tcp transport on v7.1-rc1 kernel with a patch. Refer to the Link tag for the details of the WARN. This is a false positive because lockdep confuses lock 4) (socket establishment) with lock 5) (socket in use) for different socket instances. The locks belong to different sockets, but lockdep treats them as the same due to shared static lockdep keys. Fix this by using dynamically allocated lockdep keys per socket instance instead of static keys nvme_tcp_sk_key[] and nvme_tcp_slock_key[]. Add nvme_tcp_sk_key and nvme_tcp_slock_key fields to struct nvme_tcp_queue and pass them to sock_lock_init_class_and_name() for proper lockdep tracking. Change the argument of nvme_tcp_reclassify_socket() from 'struct socket *' to 'struct nvme_tcp_queue *' to pass both the socket and the keys. Add CONFIG_DEBUG_LOCK_ALLOC guards to nvme_tcp_alloc_queue() and nvme_tcp_free_queue() to register and unregister the dynamic keys. Additionally, move nvme_tcp_reclassify_socket() inside these guards since it's only needed when lockdep is enabled. Link: https://lore.kernel.org/linux-nvme/afB5syZbUrppgsDQ@shinmob/ Suggested-by: Nilay Shroff Reviewed-by: Nilay Shroff Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 353ac6ce9fbd..9d17c88a6200 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -142,6 +142,11 @@ struct nvme_tcp_queue { void (*state_change)(struct sock *); void (*data_ready)(struct sock *); void (*write_space)(struct sock *); + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + struct lock_class_key nvme_tcp_sk_key; + struct lock_class_key nvme_tcp_slock_key; +#endif }; struct nvme_tcp_ctrl { @@ -176,12 +181,9 @@ static int nvme_tcp_try_send(struct nvme_tcp_queue *queue); * a separate class prevents lockdep from conflating nvme-tcp socket use with * user-space socket API use. */ -static struct lock_class_key nvme_tcp_sk_key[2]; -static struct lock_class_key nvme_tcp_slock_key[2]; - -static void nvme_tcp_reclassify_socket(struct socket *sock) +static void nvme_tcp_reclassify_socket(struct nvme_tcp_queue *queue) { - struct sock *sk = sock->sk; + struct sock *sk = queue->sock->sk; if (WARN_ON_ONCE(!sock_allow_reclassification(sk))) return; @@ -189,22 +191,20 @@ static void nvme_tcp_reclassify_socket(struct socket *sock) switch (sk->sk_family) { case AF_INET: sock_lock_init_class_and_name(sk, "slock-AF_INET-NVME", - &nvme_tcp_slock_key[0], + &queue->nvme_tcp_slock_key, "sk_lock-AF_INET-NVME", - &nvme_tcp_sk_key[0]); + &queue->nvme_tcp_sk_key); break; case AF_INET6: sock_lock_init_class_and_name(sk, "slock-AF_INET6-NVME", - &nvme_tcp_slock_key[1], + &queue->nvme_tcp_slock_key, "sk_lock-AF_INET6-NVME", - &nvme_tcp_sk_key[1]); + &queue->nvme_tcp_sk_key); break; default: WARN_ON_ONCE(1); } } -#else -static void nvme_tcp_reclassify_socket(struct socket *sock) { } #endif static inline struct nvme_tcp_ctrl *to_tcp_ctrl(struct nvme_ctrl *ctrl) @@ -1468,6 +1468,11 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) kfree(queue->pdu); mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + lockdep_unregister_key(&queue->nvme_tcp_sk_key); + lockdep_unregister_key(&queue->nvme_tcp_slock_key); +#endif } static int nvme_tcp_init_connection(struct nvme_tcp_queue *queue) @@ -1813,7 +1818,12 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, } sk_net_refcnt_upgrade(queue->sock->sk); - nvme_tcp_reclassify_socket(queue->sock); + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + lockdep_register_key(&queue->nvme_tcp_sk_key); + lockdep_register_key(&queue->nvme_tcp_slock_key); + nvme_tcp_reclassify_socket(queue); +#endif /* Single syn retry */ tcp_sock_set_syncnt(queue->sock->sk, 1); @@ -1918,6 +1928,10 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, /* Use sync variant - see nvme_tcp_free_queue() for explanation */ __fput_sync(queue->sock->file); queue->sock = NULL; +#ifdef CONFIG_DEBUG_LOCK_ALLOC + lockdep_unregister_key(&queue->nvme_tcp_sk_key); + lockdep_unregister_key(&queue->nvme_tcp_slock_key); +#endif err_destroy_mutex: mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); From 37afebc79a11bd889fe8e0a98c9ae034c3cff323 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:48 +0530 Subject: [PATCH 082/117] nvme: add diag attribute group under sysfs Add a new diag attribute group under: /sys/class/nvme// /sys/block// /sys/block// This new sysfs attribute group will be used to organize NVMe diagnostic and telemetry-related counters under it. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/nvme.h | 1 + drivers/nvme/host/pci.c | 1 + drivers/nvme/host/sysfs.c | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 86b09c06b9e0..46cfce4dbbf6 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -1014,6 +1014,7 @@ extern const struct attribute_group nvme_ns_mpath_attr_group; extern const struct pr_ops nvme_pr_ops; extern const struct block_device_operations nvme_ns_head_ops; extern const struct attribute_group nvme_dev_attrs_group; +extern const struct attribute_group nvme_dev_diag_attrs_group; extern const struct attribute_group *nvme_subsys_attrs_groups[]; extern const struct attribute_group *nvme_dev_attr_groups[]; extern const struct block_device_operations nvme_bdev_ops; diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index d20d8722ad96..cf7192239782 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -2815,6 +2815,7 @@ static const struct attribute_group nvme_pci_dev_attrs_group = { static const struct attribute_group *nvme_pci_dev_attr_groups[] = { &nvme_dev_attrs_group, &nvme_pci_dev_attrs_group, + &nvme_dev_diag_attrs_group, NULL, }; diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 1f471f2cfd25..1d507a835783 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -344,11 +344,28 @@ const struct attribute_group nvme_ns_mpath_attr_group = { }; #endif +static struct attribute *nvme_ns_diag_attrs[] = { + NULL, +}; + +static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + return a->mode; +} + +const struct attribute_group nvme_ns_diag_attr_group = { + .name = "diag", + .attrs = nvme_ns_diag_attrs, + .is_visible = nvme_ns_diag_attrs_are_visible, +}; + const struct attribute_group *nvme_ns_attr_groups[] = { &nvme_ns_attr_group, #ifdef CONFIG_NVME_MULTIPATH &nvme_ns_mpath_attr_group, #endif + &nvme_ns_diag_attr_group, NULL, }; @@ -1018,11 +1035,29 @@ static const struct attribute_group nvme_tls_attrs_group = { }; #endif +static struct attribute *nvme_dev_diag_attrs[] = { + NULL, +}; + +static umode_t nvme_dev_diag_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + return a->mode; +} + +const struct attribute_group nvme_dev_diag_attrs_group = { + .name = "diag", + .attrs = nvme_dev_diag_attrs, + .is_visible = nvme_dev_diag_attrs_are_visible, +}; +EXPORT_SYMBOL_GPL(nvme_dev_diag_attrs_group); + const struct attribute_group *nvme_dev_attr_groups[] = { &nvme_dev_attrs_group, #ifdef CONFIG_NVME_TCP_TLS &nvme_tls_attrs_group, #endif + &nvme_dev_diag_attrs_group, NULL, }; From ab5af2903baa472930c94a421efdd22a49036213 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:49 +0530 Subject: [PATCH 083/117] nvme: export command retry count via sysfs When Advanced Command Retry Enable (ACRE) is configured, a controller may interrupt command execution and return a completion status indicating command interrupted with the DNR bit cleared. In this case, the driver retries the command based on the Command Retry Delay (CRD) value provided in the completion status. Currently, these command retries are handled entirely within the NVMe driver and are not visible to userspace. As a result, there is no observability into retry behavior, which can be a useful diagnostic signal. Expose a per-namespace sysfs attribute command_retries_count, under diag attribute group to provide visibility into retry activity. This information can help identify controller-side congestion under load and enables comparison across paths in multipath setups (for example, detecting cases where one path experiences significantly more retries than another under identical workloads). This exported metric is intended for diagnostics and monitoring tools such as nvme-top, and does not change command retry behavior. A new sysfs attribute named "command_retries_count" is added for this purpose. This attribute is both readable as well as writable. So user could reset this counter if needed. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 4 ++++ drivers/nvme/host/nvme.h | 1 + drivers/nvme/host/sysfs.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 23dfce27ace2..cbc2932556c5 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -323,6 +323,7 @@ static void nvme_retry_req(struct request *req) { unsigned long delay = 0; u16 crd; + struct nvme_ns *ns = req->q->queuedata; /* The mask and shift result must be <= 3 */ crd = (nvme_req(req)->status & NVME_STATUS_CRD) >> 11; @@ -330,6 +331,9 @@ static void nvme_retry_req(struct request *req) delay = nvme_req(req)->ctrl->crdt[crd - 1] * 100; nvme_req(req)->retries++; + if (ns) + atomic_long_inc(&ns->retries); + blk_mq_requeue_request(req, false); blk_mq_delay_kick_requeue_list(req->q, delay); } diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 46cfce4dbbf6..3cf95149aa88 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -592,6 +592,7 @@ struct nvme_ns { enum nvme_ana_state ana_state; u32 ana_grpid; #endif + atomic_long_t retries; struct list_head siblings; struct kref kref; struct nvme_ns_head *head; diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 1d507a835783..9472430934a3 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -344,13 +344,46 @@ const struct attribute_group nvme_ns_mpath_attr_group = { }; #endif +static ssize_t command_retries_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ns *ns = nvme_get_ns_from_dev(dev); + + return sysfs_emit(buf, "%lu\n", atomic_long_read(&ns->retries)); +} + +static ssize_t command_retries_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long retries; + int err; + struct nvme_ns *ns = nvme_get_ns_from_dev(dev); + + err = kstrtoul(buf, 0, &retries); + if (err) + return -EINVAL; + + atomic_long_set(&ns->retries, retries); + + return count; +} +static DEVICE_ATTR_RW(command_retries_count); + static struct attribute *nvme_ns_diag_attrs[] = { + &dev_attr_command_retries_count.attr, NULL, }; static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { + struct device *dev = container_of(kobj, struct device, kobj); + + if (a == &dev_attr_command_retries_count.attr) { + if (nvme_disk_is_ns_head(dev_to_disk(dev))) + return 0; + } + return a->mode; } From 66ee95b3d490d78283b6e92cb4230d4a04c99817 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:50 +0530 Subject: [PATCH 084/117] nvme: export multipath failover count via sysfs When an NVMe command completes with a path-specific error, the NVMe driver may retry the command on an alternate controller or path if one is available. These failover events indicate that I/O was redirected away from the original path. Currently, the number of times requests are failed over to another available path is not visible to userspace. Exposing this information can be useful for diagnosing path health and stability. Export per-path sysfs attribute "multipath_failover_count" under diag attribute group. This attribute is both readable and writable and thus allowing user to reset the counter. This counter can be consumed by monitoring tools such as nvme-top to help identify paths that consistently trigger failovers under load. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 27 +++++++++++++++++++++++++++ drivers/nvme/host/nvme.h | 2 ++ drivers/nvme/host/sysfs.c | 10 +++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index bd9e8d5a2713..51c8d928fc80 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -152,6 +152,7 @@ void nvme_failover_req(struct request *req) struct bio *bio; nvme_mpath_clear_current_path(ns); + atomic_long_inc(&ns->failover); /* * If we got back an ANA error, we know the controller is alive but not @@ -1165,6 +1166,32 @@ static ssize_t delayed_removal_secs_store(struct device *dev, DEVICE_ATTR_RW(delayed_removal_secs); +static ssize_t multipath_failover_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ns *ns = nvme_get_ns_from_dev(dev); + + return sysfs_emit(buf, "%lu\n", atomic_long_read(&ns->failover)); +} + +static ssize_t multipath_failover_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long failover; + int ret; + struct nvme_ns *ns = nvme_get_ns_from_dev(dev); + + ret = kstrtoul(buf, 0, &failover); + if (ret) + return -EINVAL; + + atomic_long_set(&ns->failover, failover); + + return count; +} + +DEVICE_ATTR_RW(multipath_failover_count); + static int nvme_lookup_ana_group_desc(struct nvme_ctrl *ctrl, struct nvme_ana_group_desc *desc, void *data) { diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 3cf95149aa88..73505152fcb1 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -591,6 +591,7 @@ struct nvme_ns { #ifdef CONFIG_NVME_MULTIPATH enum nvme_ana_state ana_state; u32 ana_grpid; + atomic_long_t failover; #endif atomic_long_t retries; struct list_head siblings; @@ -1065,6 +1066,7 @@ extern struct device_attribute dev_attr_ana_state; extern struct device_attribute dev_attr_queue_depth; extern struct device_attribute dev_attr_numa_nodes; extern struct device_attribute dev_attr_delayed_removal_secs; +extern struct device_attribute dev_attr_multipath_failover_count; extern struct device_attribute subsys_attr_iopolicy; static inline bool nvme_disk_is_ns_head(struct gendisk *disk) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 9472430934a3..0e5033db48a3 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -371,6 +371,9 @@ static DEVICE_ATTR_RW(command_retries_count); static struct attribute *nvme_ns_diag_attrs[] = { &dev_attr_command_retries_count.attr, +#ifdef CONFIG_NVME_MULTIPATH + &dev_attr_multipath_failover_count.attr, +#endif NULL, }; @@ -383,7 +386,12 @@ static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj, if (nvme_disk_is_ns_head(dev_to_disk(dev))) return 0; } - +#ifdef CONFIG_NVME_MULTIPATH + if (a == &dev_attr_multipath_failover_count.attr) { + if (nvme_disk_is_ns_head(dev_to_disk(dev))) + return 0; + } +#endif return a->mode; } From 30ab37a128000600dcaae2b35d4a594e304dfe7e Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:51 +0530 Subject: [PATCH 085/117] nvme: export command error counters via sysfs When an NVMe command completes with an error status, the driver logs the error to the kernel log. However, these messages may be lost or overwritten over time since dmesg is a circular buffer. Expose per-path and ctrl sysfs attribute command_error_count, under diag attribute group to provide persistent visibility into error occurrences. This allows users to observe the total number of commands that have failed on a given path over time, which can be useful for diagnosing path health and stability. This attribute is both readable and writable thus allowing user to reset these counters. These counters can also be consumed by observability tools such as nvme-top to provide additional insight into NVMe error behavior. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 10 +++++- drivers/nvme/host/nvme.h | 2 ++ drivers/nvme/host/sysfs.c | 66 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index cbc2932556c5..5f885e0ab930 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -438,11 +438,19 @@ static inline void nvme_end_req_zoned(struct request *req) static inline void __nvme_end_req(struct request *req) { - if (unlikely(nvme_req(req)->status && !(req->rq_flags & RQF_QUIET))) { + struct nvme_ns *ns = req->q->queuedata; + struct nvme_request *nr = nvme_req(req); + + if (unlikely(nr->status && !(req->rq_flags & RQF_QUIET))) { if (blk_rq_is_passthrough(req)) nvme_log_err_passthru(req); else nvme_log_error(req); + + if (ns) + atomic_long_inc(&ns->errors); + else + atomic_long_inc(&nr->ctrl->errors); } nvme_end_req_zoned(req); nvme_trace_bio_complete(req); diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 73505152fcb1..f2734f03682f 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -415,6 +415,7 @@ struct nvme_ctrl { unsigned long ka_last_check_time; struct work_struct fw_act_work; unsigned long events; + atomic_long_t errors; #ifdef CONFIG_NVME_MULTIPATH /* asymmetric namespace access: */ @@ -594,6 +595,7 @@ struct nvme_ns { atomic_long_t failover; #endif atomic_long_t retries; + atomic_long_t errors; struct list_head siblings; struct kref kref; struct nvme_ns_head *head; diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 0e5033db48a3..a03a22c832d8 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -6,6 +6,7 @@ */ #include +#include #include "nvme.h" #include "fabrics.h" @@ -369,8 +370,37 @@ static ssize_t command_retries_count_store(struct device *dev, } static DEVICE_ATTR_RW(command_retries_count); +static ssize_t nvme_io_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ns *ns = nvme_get_ns_from_dev(dev); + + return sysfs_emit(buf, "%lu\n", atomic_long_read(&ns->errors)); +} + +static ssize_t nvme_io_errors_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long errors; + int err; + struct nvme_ns *ns = nvme_get_ns_from_dev(dev); + + err = kstrtoul(buf, 0, &errors); + if (err) + return -EINVAL; + + atomic_long_set(&ns->errors, errors); + + return count; +} + +struct device_attribute dev_attr_io_errors = + __ATTR(command_error_count, 0644, + nvme_io_errors_show, nvme_io_errors_store); + static struct attribute *nvme_ns_diag_attrs[] = { &dev_attr_command_retries_count.attr, + &dev_attr_io_errors.attr, #ifdef CONFIG_NVME_MULTIPATH &dev_attr_multipath_failover_count.attr, #endif @@ -386,6 +416,12 @@ static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj, if (nvme_disk_is_ns_head(dev_to_disk(dev))) return 0; } + if (a == &dev_attr_io_errors.attr) { + struct gendisk *disk = dev_to_disk(dev); + + if (nvme_disk_is_ns_head(disk)) + return 0; + } #ifdef CONFIG_NVME_MULTIPATH if (a == &dev_attr_multipath_failover_count.attr) { if (nvme_disk_is_ns_head(dev_to_disk(dev))) @@ -1076,7 +1112,37 @@ static const struct attribute_group nvme_tls_attrs_group = { }; #endif +static ssize_t nvme_adm_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%lu\n", + (unsigned long)atomic_long_read(&ctrl->errors)); +} + +static ssize_t nvme_adm_errors_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long errors; + int err; + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + err = kstrtoul(buf, 0, &errors); + if (err) + return -EINVAL; + + atomic_long_set(&ctrl->errors, errors); + + return count; +} + +struct device_attribute dev_attr_adm_errors = + __ATTR(command_error_count, 0644, + nvme_adm_errors_show, nvme_adm_errors_store); + static struct attribute *nvme_dev_diag_attrs[] = { + &dev_attr_adm_errors.attr, NULL, }; From 76b5e1591e8cfa986971d177b5de27ce20ca056a Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:52 +0530 Subject: [PATCH 086/117] nvme: export I/O requeue count when no path is usable via sysfs When the NVMe namespace head determines that there is no currently available path to handle I/O (for example, while a controller is resetting/connecting or due to a transient link failure), incoming I/Os are added to the requeue list. Currently, there is no visibility into how many I/Os have been requeued in this situation. Add a new ns-head sysfs counter io_requeue_no_usable_path_count, under diag attribute group to expose the number of I/Os that were requeued due to the absence of an available path. This counter is also writable thus allowing user to reset it, if needed. This statistic can help users understand I/O slowdowns or stalls caused by temporary path unavailability, and can be consumed by monitoring tools such as nvme-top for real-time observability. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 30 ++++++++++++++++++++++++++++++ drivers/nvme/host/nvme.h | 2 ++ drivers/nvme/host/sysfs.c | 5 +++++ 3 files changed, 37 insertions(+) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 51c8d928fc80..9021fd44f193 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -538,6 +538,7 @@ static void nvme_ns_head_submit_bio(struct bio *bio) spin_lock_irq(&head->requeue_lock); bio_list_add(&head->requeue_list, bio); spin_unlock_irq(&head->requeue_lock); + atomic_long_inc(&head->io_requeue_no_usable_path_count); } else { dev_warn_ratelimited(dev, "no available path - failing I/O\n"); @@ -1192,6 +1193,35 @@ static ssize_t multipath_failover_count_store(struct device *dev, DEVICE_ATTR_RW(multipath_failover_count); +static ssize_t io_requeue_no_usable_path_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gendisk *disk = dev_to_disk(dev); + struct nvme_ns_head *head = disk->private_data; + + return sysfs_emit(buf, "%lu\n", + atomic_long_read(&head->io_requeue_no_usable_path_count)); +} + +static ssize_t io_requeue_no_usable_path_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int err; + unsigned long requeue_cnt; + struct gendisk *disk = dev_to_disk(dev); + struct nvme_ns_head *head = disk->private_data; + + err = kstrtoul(buf, 0, &requeue_cnt); + if (err) + return -EINVAL; + + atomic_long_set(&head->io_requeue_no_usable_path_count, requeue_cnt); + + return count; +} + +DEVICE_ATTR_RW(io_requeue_no_usable_path_count); + static int nvme_lookup_ana_group_desc(struct nvme_ctrl *ctrl, struct nvme_ana_group_desc *desc, void *data) { diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index f2734f03682f..bfd427184d69 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -566,6 +566,7 @@ struct nvme_ns_head { unsigned long flags; struct delayed_work remove_work; unsigned int delayed_removal_secs; + atomic_long_t io_requeue_no_usable_path_count; #define NVME_NSHEAD_DISK_LIVE 0 #define NVME_NSHEAD_QUEUE_IF_NO_PATH 1 struct nvme_ns __rcu *current_path[]; @@ -1069,6 +1070,7 @@ extern struct device_attribute dev_attr_queue_depth; extern struct device_attribute dev_attr_numa_nodes; extern struct device_attribute dev_attr_delayed_removal_secs; extern struct device_attribute dev_attr_multipath_failover_count; +extern struct device_attribute dev_attr_io_requeue_no_usable_path_count; extern struct device_attribute subsys_attr_iopolicy; static inline bool nvme_disk_is_ns_head(struct gendisk *disk) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index a03a22c832d8..7f0575b7cdd0 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -403,6 +403,7 @@ static struct attribute *nvme_ns_diag_attrs[] = { &dev_attr_io_errors.attr, #ifdef CONFIG_NVME_MULTIPATH &dev_attr_multipath_failover_count.attr, + &dev_attr_io_requeue_no_usable_path_count.attr, #endif NULL, }; @@ -427,6 +428,10 @@ static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj, if (nvme_disk_is_ns_head(dev_to_disk(dev))) return 0; } + if (a == &dev_attr_io_requeue_no_usable_path_count.attr) { + if (!nvme_disk_is_ns_head(dev_to_disk(dev))) + return 0; + } #endif return a->mode; } From a8e434cb033817b29e7ad03e8df43071a1c7e90e Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:53 +0530 Subject: [PATCH 087/117] nvme: export I/O failure count when no path is available via sysfs When I/O is submitted to the NVMe namespace head and no available path can handle the request, the driver fails the I/O immediately. Currently, such failures are only reported via kernel log messages, which may be lost over time since dmesg is a circular buffer. Add a new ns-head sysfs counter io_fail_no_available_path_count, under diag attribute group to expose the number of I/Os that failed due to the absence of an available path. This provides persistent visibility into path-related I/O failures and can help users diagnose the cause of I/O errors. This counter is also writable and so user may reset its value, if needed. This counter can also be consumed by monitoring tools such as nvme-top. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 30 ++++++++++++++++++++++++++++++ drivers/nvme/host/nvme.h | 2 ++ drivers/nvme/host/sysfs.c | 5 +++++ 3 files changed, 37 insertions(+) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 9021fd44f193..96337ae2b552 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -543,6 +543,7 @@ static void nvme_ns_head_submit_bio(struct bio *bio) dev_warn_ratelimited(dev, "no available path - failing I/O\n"); bio_io_error(bio); + atomic_long_inc(&head->io_fail_no_available_path_count); } srcu_read_unlock(&head->srcu, srcu_idx); @@ -1222,6 +1223,35 @@ static ssize_t io_requeue_no_usable_path_count_store(struct device *dev, DEVICE_ATTR_RW(io_requeue_no_usable_path_count); +static ssize_t io_fail_no_available_path_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gendisk *disk = dev_to_disk(dev); + struct nvme_ns_head *head = disk->private_data; + + return sysfs_emit(buf, "%lu\n", + atomic_long_read(&head->io_fail_no_available_path_count)); +} + +static ssize_t io_fail_no_available_path_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int err; + unsigned long fail_cnt; + struct gendisk *disk = dev_to_disk(dev); + struct nvme_ns_head *head = disk->private_data; + + err = kstrtoul(buf, 0, &fail_cnt); + if (err) + return -EINVAL; + + atomic_long_set(&head->io_fail_no_available_path_count, fail_cnt); + + return count; +} + +DEVICE_ATTR_RW(io_fail_no_available_path_count); + static int nvme_lookup_ana_group_desc(struct nvme_ctrl *ctrl, struct nvme_ana_group_desc *desc, void *data) { diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index bfd427184d69..249f1f8dde40 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -567,6 +567,7 @@ struct nvme_ns_head { struct delayed_work remove_work; unsigned int delayed_removal_secs; atomic_long_t io_requeue_no_usable_path_count; + atomic_long_t io_fail_no_available_path_count; #define NVME_NSHEAD_DISK_LIVE 0 #define NVME_NSHEAD_QUEUE_IF_NO_PATH 1 struct nvme_ns __rcu *current_path[]; @@ -1071,6 +1072,7 @@ extern struct device_attribute dev_attr_numa_nodes; extern struct device_attribute dev_attr_delayed_removal_secs; extern struct device_attribute dev_attr_multipath_failover_count; extern struct device_attribute dev_attr_io_requeue_no_usable_path_count; +extern struct device_attribute dev_attr_io_fail_no_available_path_count; extern struct device_attribute subsys_attr_iopolicy; static inline bool nvme_disk_is_ns_head(struct gendisk *disk) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 7f0575b7cdd0..d2c7d943b23f 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -404,6 +404,7 @@ static struct attribute *nvme_ns_diag_attrs[] = { #ifdef CONFIG_NVME_MULTIPATH &dev_attr_multipath_failover_count.attr, &dev_attr_io_requeue_no_usable_path_count.attr, + &dev_attr_io_fail_no_available_path_count.attr, #endif NULL, }; @@ -432,6 +433,10 @@ static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj, if (!nvme_disk_is_ns_head(dev_to_disk(dev))) return 0; } + if (a == &dev_attr_io_fail_no_available_path_count.attr) { + if (!nvme_disk_is_ns_head(dev_to_disk(dev))) + return 0; + } #endif return a->mode; } From 29aafaaf582b342ef3e2182cefd0c2aac6e9f3a8 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:54 +0530 Subject: [PATCH 088/117] nvme: export controller reset event count via sysfs The NVMe controller transitions into the RESETTING state during error recovery, link instability, firmware activation, or when a reset is explicitly triggered by the user. Expose a per-ctrl sysfs attribute reset_count, under diag attribute group to provide visibility into these RESETTING state transitions. Observing the frequency of reset events can help users identify issues such as PCIe errors or unstable fabric links. This counter is also writable thus allowing user to reset its value, if needed. This counter can also be consumed by monitoring tools such as nvme-top to improve controller-level observability. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 1 + drivers/nvme/host/nvme.h | 1 + drivers/nvme/host/sysfs.c | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 5f885e0ab930..efaddab8296e 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -596,6 +596,7 @@ bool nvme_change_ctrl_state(struct nvme_ctrl *ctrl, case NVME_CTRL_NEW: case NVME_CTRL_LIVE: changed = true; + atomic_long_inc(&ctrl->nr_reset); fallthrough; default: break; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 249f1f8dde40..81f297e995e4 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -416,6 +416,7 @@ struct nvme_ctrl { struct work_struct fw_act_work; unsigned long events; atomic_long_t errors; + atomic_long_t nr_reset; #ifdef CONFIG_NVME_MULTIPATH /* asymmetric namespace access: */ diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index d2c7d943b23f..ff603a9d7b8c 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -1151,8 +1151,35 @@ struct device_attribute dev_attr_adm_errors = __ATTR(command_error_count, 0644, nvme_adm_errors_show, nvme_adm_errors_store); +static ssize_t reset_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%lu\n", atomic_long_read(&ctrl->nr_reset)); +} + +static ssize_t reset_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int err; + unsigned long reset_cnt; + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + err = kstrtoul(buf, 0, &reset_cnt); + if (err) + return -EINVAL; + + atomic_long_set(&ctrl->nr_reset, reset_cnt); + + return count; +} + +static DEVICE_ATTR_RW(reset_count); + static struct attribute *nvme_dev_diag_attrs[] = { &dev_attr_adm_errors.attr, + &dev_attr_reset_count.attr, NULL, }; From 3c8c284dfcdfce81a02fe3c911196d9876468ae4 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Sun, 17 May 2026 00:06:55 +0530 Subject: [PATCH 089/117] nvme: export controller reconnect event count via sysfs When an NVMe-oF link goes down, the driver attempts to recover the connection by repeatedly reconnecting to the remote controller at configured intervals. A maximum number of reconnect attempts is also configured, after which recovery stops and the controller is removed if the connection cannot be re-established. The driver maintains a counter, nr_reconnects, which is incremented on each reconnect attempt. However if in case the reconnect is successful then this counter reset to zero. Moreover, currently, this counter is only reported via kernel log messages and is not exposed to userspace. Since dmesg is a circular buffer, this information may be lost over time. So introduce a new accumulator which accumulates nr_reconnect attempts and also expose this accumulator per-fabric ctrl via a new sysfs attribute reconnect_count, under diag attribute grroup to provide persistent visibility into the number of reconnect attempts made by the host. This information can help users diagnose unstable links or connectivity issues. Furthermore, this sysfs attribute is also writable so user may reset it to zero, if needed. The reconnect_count can also be consumed by monitoring tools such as nvme-top to improve controller-level observability. Tested-by: Venkat Rao Bagalkote Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/fc.c | 3 +++ drivers/nvme/host/nvme.h | 2 ++ drivers/nvme/host/rdma.c | 2 ++ drivers/nvme/host/sysfs.c | 35 +++++++++++++++++++++++++++++++++++ drivers/nvme/host/tcp.c | 2 ++ 5 files changed, 44 insertions(+) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index e4f4528fe2a2..f04eb13dd5e9 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -3148,6 +3148,8 @@ nvme_fc_create_association(struct nvme_fc_ctrl *ctrl) goto out_term_aen_ops; } + /* accumulate reconnect attempts before resetting it to zero */ + atomic_long_add(ctrl->ctrl.nr_reconnects, &ctrl->ctrl.acc_reconnects); ctrl->ctrl.nr_reconnects = 0; nvme_start_ctrl(&ctrl->ctrl); @@ -3470,6 +3472,7 @@ nvme_fc_alloc_ctrl(struct device *dev, struct nvmf_ctrl_options *opts, ctrl->ctrl.opts = opts; ctrl->ctrl.nr_reconnects = 0; + atomic_long_set(&ctrl->ctrl.acc_reconnects, 0); INIT_LIST_HEAD(&ctrl->ctrl_list); ctrl->lport = lport; ctrl->rport = rport; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 81f297e995e4..b367c67dcb37 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -458,6 +458,8 @@ struct nvme_ctrl { u16 icdoff; u16 maxcmd; int nr_reconnects; + /* accumulate reconenct attempts, as nr_reconnects can reset to zero */ + atomic_long_t acc_reconnects; unsigned long flags; struct nvmf_ctrl_options *opts; diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index bf73135c1439..61a91cfb4062 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -1110,6 +1110,8 @@ static void nvme_rdma_reconnect_ctrl_work(struct work_struct *work) dev_info(ctrl->ctrl.device, "Successfully reconnected (%d attempts)\n", ctrl->ctrl.nr_reconnects); + /* accumulate reconnect attempts before resetting it to zero */ + atomic_long_add(ctrl->ctrl.nr_reconnects, &ctrl->ctrl.acc_reconnects); ctrl->ctrl.nr_reconnects = 0; return; diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index ff603a9d7b8c..933a5adfb7af 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -1175,17 +1175,52 @@ static ssize_t reset_count_store(struct device *dev, return count; } +static ssize_t reconnect_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%lu\n", + atomic_long_read(&ctrl->acc_reconnects) + + ctrl->nr_reconnects); +} + +static ssize_t reconnect_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int err; + unsigned long reconnect_cnt; + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + err = kstrtoul(buf, 0, &reconnect_cnt); + if (err) + return -EINVAL; + + atomic_long_set(&ctrl->acc_reconnects, reconnect_cnt); + + return count; +} + +static DEVICE_ATTR_RW(reconnect_count); + static DEVICE_ATTR_RW(reset_count); static struct attribute *nvme_dev_diag_attrs[] = { &dev_attr_adm_errors.attr, &dev_attr_reset_count.attr, + &dev_attr_reconnect_count.attr, NULL, }; static umode_t nvme_dev_diag_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { + struct device *dev = container_of(kobj, struct device, kobj); + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + if (a == &dev_attr_reconnect_count.attr && !ctrl->opts) + return 0; + return a->mode; } diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 9d17c88a6200..9b76b77ffdbb 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -2489,6 +2489,8 @@ static void nvme_tcp_reconnect_ctrl_work(struct work_struct *work) dev_info(ctrl->device, "Successfully reconnected (attempt %d/%d)\n", ctrl->nr_reconnects, ctrl->opts->max_reconnects); + /* accumulate reconnect attempts before resetting it to zero */ + atomic_long_add(ctrl->nr_reconnects, &ctrl->acc_reconnects); ctrl->nr_reconnects = 0; return; From 7e712f292e7f01e91d09e83eb7b9526f77f66c71 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 4 Jun 2026 12:53:47 +0200 Subject: [PATCH 090/117] block: Add WQ_PERCPU to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The refactoring is going to alter the default behavior of alloc_workqueue() to be unbound by default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. For more details see the Link tag below. In order to keep alloc_workqueue() behavior identical, explicitly request WQ_PERCPU. Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Reviewed-by: Chaitanya Kulkarni Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260604105347.168322-1-marco.crivellari@suse.com Signed-off-by: Jens Axboe --- block/blk-zoned.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/blk-zoned.c b/block/blk-zoned.c index 30cad2bb9291..3622211142cf 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -1902,7 +1902,7 @@ static int disk_alloc_zone_resources(struct gendisk *disk, goto free_hash; disk->zone_wplugs_wq = - alloc_workqueue("%s_zwplugs", WQ_MEM_RECLAIM | WQ_HIGHPRI, + alloc_workqueue("%s_zwplugs", WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU, pool_size, disk->disk_name); if (!disk->zone_wplugs_wq) goto destroy_pool; From 08d912bc44dab63f2637677712d2a0b86922389a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:00:54 -0700 Subject: [PATCH 091/117] block: Annotate the queue limits functions Let the thread-safety checker verify whether every start of a queue limits update is followed by a call to a function that finishes a queue limits update. Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/8f71062b6d0fcf2b80bc8cda701c453224755439.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- include/linux/blkdev.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 17270a28c66d..65efbd7fe1a3 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -1092,15 +1092,17 @@ static inline unsigned int blk_boundary_sectors_left(sector_t offset, */ static inline struct queue_limits queue_limits_start_update(struct request_queue *q) + __acquires(&q->limits_lock) { mutex_lock(&q->limits_lock); return q->limits; } int queue_limits_commit_update_frozen(struct request_queue *q, - struct queue_limits *lim); + struct queue_limits *lim) __releases(&q->limits_lock); int queue_limits_commit_update(struct request_queue *q, - struct queue_limits *lim); -int queue_limits_set(struct request_queue *q, struct queue_limits *lim); + struct queue_limits *lim) __releases(&q->limits_lock); +int queue_limits_set(struct request_queue *q, struct queue_limits *lim) + __must_not_hold(&q->limits_lock); int blk_validate_limits(struct queue_limits *lim); /** @@ -1112,6 +1114,7 @@ int blk_validate_limits(struct queue_limits *lim); * starting update. */ static inline void queue_limits_cancel_update(struct request_queue *q) + __releases(&q->limits_lock) { mutex_unlock(&q->limits_lock); } From 3033c86fa1a8bb31d0a13738fe8c5f9e5bbaf98a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:00:55 -0700 Subject: [PATCH 092/117] block/bdev: Annotate the blk_holder_ops callback functions The four callback functions in blk_holder_ops all release the bd_holder_lock. Annotate these functions accordingly. Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/be51cf81110f691ebd5868ac2f15ceb847805bc8.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- include/linux/blkdev.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 65efbd7fe1a3..57e84d59a642 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -1746,22 +1746,26 @@ void blkdev_show(struct seq_file *seqf, off_t offset); #endif struct blk_holder_ops { - void (*mark_dead)(struct block_device *bdev, bool surprise); + void (*mark_dead)(struct block_device *bdev, bool surprise) + __releases(&bdev->bd_holder_lock); /* * Sync the file system mounted on the block device. */ - void (*sync)(struct block_device *bdev); + void (*sync)(struct block_device *bdev) + __releases(&bdev->bd_holder_lock); /* * Freeze the file system mounted on the block device. */ - int (*freeze)(struct block_device *bdev); + int (*freeze)(struct block_device *bdev) + __releases(&bdev->bd_holder_lock); /* * Thaw the file system mounted on the block device. */ - int (*thaw)(struct block_device *bdev); + int (*thaw)(struct block_device *bdev) + __releases(&bdev->bd_holder_lock); }; /* From ea4f575e72df0fa9e4b3f57a6f48c1ae81fac7b4 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:00:56 -0700 Subject: [PATCH 093/117] block/cgroup: Split blkg_conf_prep() Move the blkg_conf_open_bdev() call out of blkg_conf_prep() to make it possible to add lock context annotations to blkg_conf_prep(). Change an if-statement in blkg_conf_open_bdev() into a WARN_ON_ONCE() call. Export blkg_conf_open_bdev() because it is called by the BFQ I/O scheduler and the BFQ I/O scheduler may be built as a kernel module. Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Cc: Tejun Heo Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/e6ea0387f413217c8561a0ca54ce7b846aa5c7c5.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/bfq-cgroup.c | 4 ++++ block/blk-cgroup.c | 18 ++++++++---------- block/blk-iocost.c | 4 ++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/block/bfq-cgroup.c b/block/bfq-cgroup.c index 37ab70930c8d..df7b5a646e96 100644 --- a/block/bfq-cgroup.c +++ b/block/bfq-cgroup.c @@ -1052,6 +1052,10 @@ static ssize_t bfq_io_set_device_weight(struct kernfs_open_file *of, blkg_conf_init(&ctx, buf); + ret = blkg_conf_open_bdev(&ctx); + if (ret) + goto out; + ret = blkg_conf_prep(blkcg, &blkcg_policy_bfq, &ctx); if (ret) goto out; diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index 554c87bb4a86..a8d95d51b866 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -771,10 +771,7 @@ EXPORT_SYMBOL_GPL(blkg_conf_init); * @ctx->input and get and store the matching bdev in @ctx->bdev. @ctx->body is * set to point past the device node prefix. * - * This function may be called multiple times on @ctx and the extra calls become - * NOOPs. blkg_conf_prep() implicitly calls this function. Use this function - * explicitly if bdev access is needed without resolving the blkcg / policy part - * of @ctx->input. Returns -errno on error. + * Returns: -errno on error. */ int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx) { @@ -783,8 +780,8 @@ int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx) struct block_device *bdev; int key_len; - if (ctx->bdev) - return 0; + if (WARN_ON_ONCE(ctx->bdev)) + return -EINVAL; if (sscanf(input, "%u:%u%n", &major, &minor, &key_len) != 2) return -EINVAL; @@ -813,6 +810,8 @@ int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx) ctx->bdev = bdev; return 0; } +EXPORT_SYMBOL_GPL(blkg_conf_open_bdev); + /* * Similar to blkg_conf_open_bdev, but additionally freezes the queue, * ensures the correct locking order between freeze queue and q->rq_qos_mutex. @@ -857,7 +856,7 @@ unsigned long __must_check blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx) * following MAJ:MIN, @ctx->bdev points to the target block device and * @ctx->blkg to the blkg being configured. * - * blkg_conf_open_bdev() may be called on @ctx beforehand. On success, this + * blkg_conf_open_bdev() must be called on @ctx beforehand. On success, this * function returns with queue lock held and must be followed by * blkg_conf_exit(). */ @@ -870,9 +869,8 @@ int blkg_conf_prep(struct blkcg *blkcg, const struct blkcg_policy *pol, struct blkcg_gq *blkg; int ret; - ret = blkg_conf_open_bdev(ctx); - if (ret) - return ret; + if (WARN_ON_ONCE(!ctx->bdev)) + return -EINVAL; disk = ctx->bdev->bd_disk; q = disk->queue; diff --git a/block/blk-iocost.c b/block/blk-iocost.c index 0cca88a366dc..b34f820dedcc 100644 --- a/block/blk-iocost.c +++ b/block/blk-iocost.c @@ -3140,6 +3140,10 @@ static ssize_t ioc_weight_write(struct kernfs_open_file *of, char *buf, blkg_conf_init(&ctx, buf); + ret = blkg_conf_open_bdev(&ctx); + if (ret) + goto err; + ret = blkg_conf_prep(blkcg, &blkcg_policy_iocost, &ctx); if (ret) goto err; From c574c3cc368d68fff465e5fc811f874d9235b940 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:00:57 -0700 Subject: [PATCH 094/117] block/cgroup: Split blkg_conf_exit() Split blkg_conf_exit() into blkg_conf_unprep() and blkg_conf_close_bdev() because blkg_conf_exit() is not compatible with the Clang thread-safety annotations. Remove blkg_conf_exit(). Rename blkg_conf_exit_frozen() into blkg_conf_close_bdev_frozen(). Add thread-safety annotations to the new functions. Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Cc: Tejun Heo Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/c1ec1f1c4b675bc5f187f77b3e6436234c6b244c.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/bfq-cgroup.c | 9 ++++-- block/blk-cgroup.c | 59 ++++++++++++++++++----------------- block/blk-cgroup.h | 6 ++-- block/blk-iocost.c | 71 +++++++++++++++++++++---------------------- block/blk-iolatency.c | 19 +++++++----- block/blk-throttle.c | 34 ++++++++++++--------- 6 files changed, 104 insertions(+), 94 deletions(-) diff --git a/block/bfq-cgroup.c b/block/bfq-cgroup.c index df7b5a646e96..0bd0332b3d78 100644 --- a/block/bfq-cgroup.c +++ b/block/bfq-cgroup.c @@ -1054,11 +1054,11 @@ static ssize_t bfq_io_set_device_weight(struct kernfs_open_file *of, ret = blkg_conf_open_bdev(&ctx); if (ret) - goto out; + return ret; ret = blkg_conf_prep(blkcg, &blkcg_policy_bfq, &ctx); if (ret) - goto out; + goto close_bdev; if (sscanf(ctx.body, "%llu", &v) == 1) { /* require "default" on dfl */ @@ -1079,8 +1079,11 @@ static ssize_t bfq_io_set_device_weight(struct kernfs_open_file *of, bfq_group_set_weight(bfqg, bfqg->entity.weight, v); ret = 0; } + out: - blkg_conf_exit(&ctx); + blkg_conf_unprep(&ctx); +close_bdev: + blkg_conf_close_bdev(&ctx); return ret ?: nbytes; } diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index a8d95d51b866..38d7bcfcbbe8 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -755,7 +755,7 @@ EXPORT_SYMBOL_GPL(__blkg_prfill_u64); * * Initialize @ctx which can be used to parse blkg config input string @input. * Once initialized, @ctx can be used with blkg_conf_open_bdev() and - * blkg_conf_prep(), and must be cleaned up with blkg_conf_exit(). + * blkg_conf_prep(). */ void blkg_conf_init(struct blkg_conf_ctx *ctx, char *input) { @@ -817,8 +817,8 @@ EXPORT_SYMBOL_GPL(blkg_conf_open_bdev); * ensures the correct locking order between freeze queue and q->rq_qos_mutex. * * This function returns negative error on failure. On success it returns - * memflags which must be saved and later passed to blkg_conf_exit_frozen - * for restoring the memalloc scope. + * memflags which must be saved and later passed to + * blkg_conf_close_bdev_frozen() for restoring the memalloc scope. */ unsigned long __must_check blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx) { @@ -858,7 +858,7 @@ unsigned long __must_check blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx) * * blkg_conf_open_bdev() must be called on @ctx beforehand. On success, this * function returns with queue lock held and must be followed by - * blkg_conf_exit(). + * blkg_conf_close_bdev(). */ int blkg_conf_prep(struct blkcg *blkcg, const struct blkcg_policy *pol, struct blkg_conf_ctx *ctx) @@ -968,42 +968,41 @@ int blkg_conf_prep(struct blkcg *blkcg, const struct blkcg_policy *pol, EXPORT_SYMBOL_GPL(blkg_conf_prep); /** - * blkg_conf_exit - clean up per-blkg config update + * blkg_conf_unprep - counterpart of blkg_conf_prep() * @ctx: blkg_conf_ctx initialized with blkg_conf_init() - * - * Clean up after per-blkg config update. This function must be called on all - * blkg_conf_ctx's initialized with blkg_conf_init(). */ -void blkg_conf_exit(struct blkg_conf_ctx *ctx) - __releases(&ctx->bdev->bd_queue->queue_lock) - __releases(&ctx->bdev->bd_queue->rq_qos_mutex) +void blkg_conf_unprep(struct blkg_conf_ctx *ctx) { - if (ctx->blkg) { - spin_unlock_irq(&bdev_get_queue(ctx->bdev)->queue_lock); - ctx->blkg = NULL; - } - - if (ctx->bdev) { - mutex_unlock(&ctx->bdev->bd_queue->rq_qos_mutex); - blkdev_put_no_open(ctx->bdev); - ctx->body = NULL; - ctx->bdev = NULL; - } + WARN_ON_ONCE(!ctx->blkg); + spin_unlock_irq(&ctx->bdev->bd_disk->queue->queue_lock); + ctx->blkg = NULL; } -EXPORT_SYMBOL_GPL(blkg_conf_exit); +EXPORT_SYMBOL_GPL(blkg_conf_unprep); + +/** + * blkg_conf_close_bdev - counterpart of blkg_conf_open_bdev() + * @ctx: blkg_conf_ctx initialized with blkg_conf_init() + */ +void blkg_conf_close_bdev(struct blkg_conf_ctx *ctx) +{ + mutex_unlock(&ctx->bdev->bd_queue->rq_qos_mutex); + blkdev_put_no_open(ctx->bdev); + ctx->body = NULL; + ctx->bdev = NULL; +} +EXPORT_SYMBOL_GPL(blkg_conf_close_bdev); /* - * Similar to blkg_conf_exit, but also unfreezes the queue. Should be used + * Similar to blkg_close_bdev, but also unfreezes the queue. Should be used * when blkg_conf_open_bdev_frozen is used to open the bdev. */ -void blkg_conf_exit_frozen(struct blkg_conf_ctx *ctx, unsigned long memflags) +void blkg_conf_close_bdev_frozen(struct blkg_conf_ctx *ctx, + unsigned long memflags) { - if (ctx->bdev) { - struct request_queue *q = ctx->bdev->bd_queue; + struct request_queue *q = ctx->bdev->bd_queue; - blkg_conf_exit(ctx); - blk_mq_unfreeze_queue(q, memflags); - } + blkg_conf_close_bdev(ctx); + blk_mq_unfreeze_queue(q, memflags); } static void blkg_iostat_add(struct blkg_iostat *dst, struct blkg_iostat *src) diff --git a/block/blk-cgroup.h b/block/blk-cgroup.h index 1cce3294634d..ce90f5b60d52 100644 --- a/block/blk-cgroup.h +++ b/block/blk-cgroup.h @@ -222,8 +222,10 @@ int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx); unsigned long blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx); int blkg_conf_prep(struct blkcg *blkcg, const struct blkcg_policy *pol, struct blkg_conf_ctx *ctx); -void blkg_conf_exit(struct blkg_conf_ctx *ctx); -void blkg_conf_exit_frozen(struct blkg_conf_ctx *ctx, unsigned long memflags); +void blkg_conf_unprep(struct blkg_conf_ctx *ctx); +void blkg_conf_close_bdev(struct blkg_conf_ctx *ctx); +void blkg_conf_close_bdev_frozen(struct blkg_conf_ctx *ctx, + unsigned long memflags); /** * bio_issue_as_root_blkg - see if this bio needs to be issued as root blkg diff --git a/block/blk-iocost.c b/block/blk-iocost.c index b34f820dedcc..3e4e28ecc21f 100644 --- a/block/blk-iocost.c +++ b/block/blk-iocost.c @@ -3142,21 +3142,23 @@ static ssize_t ioc_weight_write(struct kernfs_open_file *of, char *buf, ret = blkg_conf_open_bdev(&ctx); if (ret) - goto err; + return ret; ret = blkg_conf_prep(blkcg, &blkcg_policy_iocost, &ctx); if (ret) - goto err; + goto close_bdev; iocg = blkg_to_iocg(ctx.blkg); + ret = -EINVAL; + if (!strncmp(ctx.body, "default", 7)) { v = 0; } else { if (!sscanf(ctx.body, "%u", &v)) - goto einval; + goto unprep; if (v < CGROUP_WEIGHT_MIN || v > CGROUP_WEIGHT_MAX) - goto einval; + goto unprep; } spin_lock(&iocg->ioc->lock); @@ -3165,14 +3167,15 @@ static ssize_t ioc_weight_write(struct kernfs_open_file *of, char *buf, weight_updated(iocg, &now); spin_unlock(&iocg->ioc->lock); - blkg_conf_exit(&ctx); - return nbytes; + ret = 0; -einval: - ret = -EINVAL; -err: - blkg_conf_exit(&ctx); - return ret; +unprep: + blkg_conf_unprep(&ctx); + +close_bdev: + blkg_conf_close_bdev(&ctx); + + return ret ?: nbytes; } static u64 ioc_qos_prfill(struct seq_file *sf, struct blkg_policy_data *pd, @@ -3241,10 +3244,8 @@ static ssize_t ioc_qos_write(struct kernfs_open_file *of, char *input, blkg_conf_init(&ctx, input); memflags = blkg_conf_open_bdev_frozen(&ctx); - if (IS_ERR_VALUE(memflags)) { - ret = memflags; - goto err; - } + if (IS_ERR_VALUE(memflags)) + return memflags; body = ctx.body; disk = ctx.bdev->bd_disk; @@ -3361,14 +3362,14 @@ static ssize_t ioc_qos_write(struct kernfs_open_file *of, char *input, blk_mq_unquiesce_queue(disk->queue); - blkg_conf_exit_frozen(&ctx, memflags); + blkg_conf_close_bdev_frozen(&ctx, memflags); return nbytes; einval: spin_unlock_irq(&ioc->lock); blk_mq_unquiesce_queue(disk->queue); ret = -EINVAL; err: - blkg_conf_exit_frozen(&ctx, memflags); + blkg_conf_close_bdev_frozen(&ctx, memflags); return ret; } @@ -3434,20 +3435,20 @@ static ssize_t ioc_cost_model_write(struct kernfs_open_file *of, char *input, ret = blkg_conf_open_bdev(&ctx); if (ret) - goto err; + return ret; body = ctx.body; q = bdev_get_queue(ctx.bdev); if (!queue_is_mq(q)) { ret = -EOPNOTSUPP; - goto err; + goto close_bdev; } ioc = q_to_ioc(q); if (!ioc) { ret = blk_iocost_init(ctx.bdev->bd_disk); if (ret) - goto err; + goto close_bdev; ioc = q_to_ioc(q); } @@ -3458,6 +3459,8 @@ static ssize_t ioc_cost_model_write(struct kernfs_open_file *of, char *input, memcpy(u, ioc->params.i_lcoefs, sizeof(u)); user = ioc->user_cost_model; + ret = -EINVAL; + while ((p = strsep(&body, " \t\n"))) { substring_t args[MAX_OPT_ARGS]; char buf[32]; @@ -3475,20 +3478,20 @@ static ssize_t ioc_cost_model_write(struct kernfs_open_file *of, char *input, else if (!strcmp(buf, "user")) user = true; else - goto einval; + goto unlock; continue; case COST_MODEL: match_strlcpy(buf, &args[0], sizeof(buf)); if (strcmp(buf, "linear")) - goto einval; + goto unlock; continue; } tok = match_token(p, i_lcoef_tokens, args); if (tok == NR_I_LCOEFS) - goto einval; + goto unlock; if (match_u64(&args[0], &v)) - goto einval; + goto unlock; u[tok] = v; user = true; } @@ -3500,24 +3503,18 @@ static ssize_t ioc_cost_model_write(struct kernfs_open_file *of, char *input, ioc->user_cost_model = false; } ioc_refresh_params(ioc, true); + + ret = 0; + +unlock: spin_unlock_irq(&ioc->lock); blk_mq_unquiesce_queue(q); blk_mq_unfreeze_queue(q, memflags); - blkg_conf_exit(&ctx); - return nbytes; - -einval: - spin_unlock_irq(&ioc->lock); - - blk_mq_unquiesce_queue(q); - blk_mq_unfreeze_queue(q, memflags); - - ret = -EINVAL; -err: - blkg_conf_exit(&ctx); - return ret; +close_bdev: + blkg_conf_close_bdev(&ctx); + return ret ?: nbytes; } static struct cftype ioc_files[] = { diff --git a/block/blk-iolatency.c b/block/blk-iolatency.c index 53e8dd2dfa8a..1aaee6fb0f59 100644 --- a/block/blk-iolatency.c +++ b/block/blk-iolatency.c @@ -840,7 +840,7 @@ static ssize_t iolatency_set_limit(struct kernfs_open_file *of, char *buf, ret = blkg_conf_open_bdev(&ctx); if (ret) - goto out; + return ret; /* * blk_iolatency_init() may fail after rq_qos_add() succeeds which can @@ -850,11 +850,11 @@ static ssize_t iolatency_set_limit(struct kernfs_open_file *of, char *buf, if (!iolat_rq_qos(ctx.bdev->bd_queue)) ret = blk_iolatency_init(ctx.bdev->bd_disk); if (ret) - goto out; + goto close_bdev; ret = blkg_conf_prep(blkcg, &blkcg_policy_iolatency, &ctx); if (ret) - goto out; + goto close_bdev; iolat = blkg_to_lat(ctx.blkg); p = ctx.body; @@ -865,7 +865,7 @@ static ssize_t iolatency_set_limit(struct kernfs_open_file *of, char *buf, char val[21]; /* 18446744073709551616 */ if (sscanf(tok, "%15[^=]=%20s", key, val) != 2) - goto out; + goto unprep; if (!strcmp(key, "target")) { u64 v; @@ -875,9 +875,9 @@ static ssize_t iolatency_set_limit(struct kernfs_open_file *of, char *buf, else if (sscanf(val, "%llu", &v) == 1) lat_val = v * NSEC_PER_USEC; else - goto out; + goto unprep; } else { - goto out; + goto unprep; } } @@ -889,8 +889,11 @@ static ssize_t iolatency_set_limit(struct kernfs_open_file *of, char *buf, if (oldval != iolat->min_lat_nsec) iolatency_clear_scaling(blkg); ret = 0; -out: - blkg_conf_exit(&ctx); + +unprep: + blkg_conf_unprep(&ctx); +close_bdev: + blkg_conf_close_bdev(&ctx); return ret ?: nbytes; } diff --git a/block/blk-throttle.c b/block/blk-throttle.c index 88986dde1e18..47052ba21d1b 100644 --- a/block/blk-throttle.c +++ b/block/blk-throttle.c @@ -1353,21 +1353,21 @@ static ssize_t tg_set_conf(struct kernfs_open_file *of, ret = blkg_conf_open_bdev(&ctx); if (ret) - goto out_finish; + return ret; if (!blk_throtl_activated(ctx.bdev->bd_queue)) { ret = blk_throtl_init(ctx.bdev->bd_disk); if (ret) - goto out_finish; + goto close_bdev; } ret = blkg_conf_prep(blkcg, &blkcg_policy_throtl, &ctx); if (ret) - goto out_finish; + goto close_bdev; ret = -EINVAL; if (sscanf(ctx.body, "%llu", &v) != 1) - goto out_finish; + goto unprep; if (!v) v = U64_MAX; @@ -1381,8 +1381,12 @@ static ssize_t tg_set_conf(struct kernfs_open_file *of, tg_conf_updated(tg, false); ret = 0; -out_finish: - blkg_conf_exit(&ctx); + +unprep: + blkg_conf_unprep(&ctx); + +close_bdev: + blkg_conf_close_bdev(&ctx); return ret ?: nbytes; } @@ -1537,17 +1541,17 @@ static ssize_t tg_set_limit(struct kernfs_open_file *of, ret = blkg_conf_open_bdev(&ctx); if (ret) - goto out_finish; + return ret; if (!blk_throtl_activated(ctx.bdev->bd_queue)) { ret = blk_throtl_init(ctx.bdev->bd_disk); if (ret) - goto out_finish; + goto close_bdev; } ret = blkg_conf_prep(blkcg, &blkcg_policy_throtl, &ctx); if (ret) - goto out_finish; + goto close_bdev; tg = blkg_to_tg(ctx.blkg); tg_update_carryover(tg); @@ -1573,11 +1577,11 @@ static ssize_t tg_set_limit(struct kernfs_open_file *of, p = tok; strsep(&p, "="); if (!p || (sscanf(p, "%llu", &val) != 1 && strcmp(p, "max"))) - goto out_finish; + goto unprep; ret = -ERANGE; if (!val) - goto out_finish; + goto unprep; ret = -EINVAL; if (!strcmp(tok, "rbps")) @@ -1589,7 +1593,7 @@ static ssize_t tg_set_limit(struct kernfs_open_file *of, else if (!strcmp(tok, "wiops")) v[3] = min_t(u64, val, UINT_MAX); else - goto out_finish; + goto unprep; } tg->bps[READ] = v[0]; @@ -1599,8 +1603,10 @@ static ssize_t tg_set_limit(struct kernfs_open_file *of, tg_conf_updated(tg, false); ret = 0; -out_finish: - blkg_conf_exit(&ctx); +unprep: + blkg_conf_unprep(&ctx); +close_bdev: + blkg_conf_close_bdev(&ctx); return ret ?: nbytes; } From 9865e416644292124865dfc8a4ffd2b8e6764242 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:00:58 -0700 Subject: [PATCH 095/117] block/cgroup: Improve lock context annotations Add lock context annotations where these are missing. Move the blkg_conf_prep() annotation into block/blk-cgroup.h to make it visible to all blkg_conf_prep() callers. Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/58ddd6e2b960bdfa03d0007984386bc0ba351391.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/blk-cgroup.c | 1 - block/blk-cgroup.h | 15 ++++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index 38d7bcfcbbe8..86513c54c217 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -862,7 +862,6 @@ unsigned long __must_check blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx) */ int blkg_conf_prep(struct blkcg *blkcg, const struct blkcg_policy *pol, struct blkg_conf_ctx *ctx) - __acquires(&bdev->bd_queue->queue_lock) { struct gendisk *disk; struct request_queue *q; diff --git a/block/blk-cgroup.h b/block/blk-cgroup.h index ce90f5b60d52..f0a3af520c55 100644 --- a/block/blk-cgroup.h +++ b/block/blk-cgroup.h @@ -218,14 +218,19 @@ struct blkg_conf_ctx { }; void blkg_conf_init(struct blkg_conf_ctx *ctx, char *input); -int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx); +int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx) + __cond_acquires(0, &ctx->bdev->bd_queue->rq_qos_mutex); unsigned long blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx); int blkg_conf_prep(struct blkcg *blkcg, const struct blkcg_policy *pol, - struct blkg_conf_ctx *ctx); -void blkg_conf_unprep(struct blkg_conf_ctx *ctx); -void blkg_conf_close_bdev(struct blkg_conf_ctx *ctx); + struct blkg_conf_ctx *ctx) + __cond_acquires(0, &ctx->bdev->bd_disk->queue->queue_lock); +void blkg_conf_unprep(struct blkg_conf_ctx *ctx) + __releases(ctx->bdev->bd_disk->queue->queue_lock); +void blkg_conf_close_bdev(struct blkg_conf_ctx *ctx) + __releases(&ctx->bdev->bd_queue->rq_qos_mutex); void blkg_conf_close_bdev_frozen(struct blkg_conf_ctx *ctx, - unsigned long memflags); + unsigned long memflags) + __releases(&ctx->bdev->bd_queue->rq_qos_mutex); /** * bio_issue_as_root_blkg - see if this bio needs to be issued as root blkg From 6a7717a2df6c01b2158979f311ddf4cb35b8987f Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:00:59 -0700 Subject: [PATCH 096/117] block/blk-iocost: Combine two error paths in ioc_qos_write() Reduce code duplication by combining two error paths. No functionality has been changed. Suggested-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Cc: Tejun Heo Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/80d4fc1ecd5eaf187c0a31c63a1033a7326d4c7e.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/blk-iocost.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/block/blk-iocost.c b/block/blk-iocost.c index 3e4e28ecc21f..050bfbc6d806 100644 --- a/block/blk-iocost.c +++ b/block/blk-iocost.c @@ -3239,7 +3239,7 @@ static ssize_t ioc_qos_write(struct kernfs_open_file *of, char *input, bool enable, user; char *body, *p; unsigned long memflags; - int ret; + int ret = 0; blkg_conf_init(&ctx, input); @@ -3251,14 +3251,14 @@ static ssize_t ioc_qos_write(struct kernfs_open_file *of, char *input, disk = ctx.bdev->bd_disk; if (!queue_is_mq(disk->queue)) { ret = -EOPNOTSUPP; - goto err; + goto close_bdev; } ioc = q_to_ioc(disk->queue); if (!ioc) { ret = blk_iocost_init(disk); if (ret) - goto err; + goto close_bdev; ioc = q_to_ioc(disk->queue); } @@ -3362,15 +3362,15 @@ static ssize_t ioc_qos_write(struct kernfs_open_file *of, char *input, blk_mq_unquiesce_queue(disk->queue); +close_bdev: blkg_conf_close_bdev_frozen(&ctx, memflags); - return nbytes; + return ret ?: nbytes; + einval: spin_unlock_irq(&ioc->lock); blk_mq_unquiesce_queue(disk->queue); ret = -EINVAL; -err: - blkg_conf_close_bdev_frozen(&ctx, memflags); - return ret; + goto close_bdev; } static u64 ioc_cost_model_prfill(struct seq_file *sf, From 998cda78d4e364f75e576ba715a2533462990aee Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:00 -0700 Subject: [PATCH 097/117] block/cgroup: Inline blkg_conf_{open,close}_bdev_frozen() The blkg_conf_open_bdev_frozen() calling convention is not compatible with lock context annotations. Fold both blkg_conf_open_bdev_frozen() and blkg_conf_close_bdev_frozen() into their only caller. This patch prepares for enabling lock context analysis. The type of 'memflags' has been changed from unsigned long into unsigned int to match the type of current->flags. See also . Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Cc: Tejun Heo Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/05661d1555decc6dd5389174ba448d803b72ed9a.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/blk-cgroup.c | 46 ---------------------------------------------- block/blk-cgroup.h | 4 ---- block/blk-iocost.c | 25 +++++++++++++++++++------ 3 files changed, 19 insertions(+), 56 deletions(-) diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index 86513c54c217..de0f753b8fe5 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -812,39 +812,6 @@ int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx) } EXPORT_SYMBOL_GPL(blkg_conf_open_bdev); -/* - * Similar to blkg_conf_open_bdev, but additionally freezes the queue, - * ensures the correct locking order between freeze queue and q->rq_qos_mutex. - * - * This function returns negative error on failure. On success it returns - * memflags which must be saved and later passed to - * blkg_conf_close_bdev_frozen() for restoring the memalloc scope. - */ -unsigned long __must_check blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx) -{ - int ret; - unsigned long memflags; - - if (ctx->bdev) - return -EINVAL; - - ret = blkg_conf_open_bdev(ctx); - if (ret < 0) - return ret; - /* - * At this point, we haven’t started protecting anything related to QoS, - * so we release q->rq_qos_mutex here, which was first acquired in blkg_ - * conf_open_bdev. Later, we re-acquire q->rq_qos_mutex after freezing - * the queue to maintain the correct locking order. - */ - mutex_unlock(&ctx->bdev->bd_queue->rq_qos_mutex); - - memflags = blk_mq_freeze_queue(ctx->bdev->bd_queue); - mutex_lock(&ctx->bdev->bd_queue->rq_qos_mutex); - - return memflags; -} - /** * blkg_conf_prep - parse and prepare for per-blkg config update * @blkcg: target block cgroup @@ -991,19 +958,6 @@ void blkg_conf_close_bdev(struct blkg_conf_ctx *ctx) } EXPORT_SYMBOL_GPL(blkg_conf_close_bdev); -/* - * Similar to blkg_close_bdev, but also unfreezes the queue. Should be used - * when blkg_conf_open_bdev_frozen is used to open the bdev. - */ -void blkg_conf_close_bdev_frozen(struct blkg_conf_ctx *ctx, - unsigned long memflags) -{ - struct request_queue *q = ctx->bdev->bd_queue; - - blkg_conf_close_bdev(ctx); - blk_mq_unfreeze_queue(q, memflags); -} - static void blkg_iostat_add(struct blkg_iostat *dst, struct blkg_iostat *src) { int i; diff --git a/block/blk-cgroup.h b/block/blk-cgroup.h index f0a3af520c55..f25fecb87c43 100644 --- a/block/blk-cgroup.h +++ b/block/blk-cgroup.h @@ -220,7 +220,6 @@ struct blkg_conf_ctx { void blkg_conf_init(struct blkg_conf_ctx *ctx, char *input); int blkg_conf_open_bdev(struct blkg_conf_ctx *ctx) __cond_acquires(0, &ctx->bdev->bd_queue->rq_qos_mutex); -unsigned long blkg_conf_open_bdev_frozen(struct blkg_conf_ctx *ctx); int blkg_conf_prep(struct blkcg *blkcg, const struct blkcg_policy *pol, struct blkg_conf_ctx *ctx) __cond_acquires(0, &ctx->bdev->bd_disk->queue->queue_lock); @@ -228,9 +227,6 @@ void blkg_conf_unprep(struct blkg_conf_ctx *ctx) __releases(ctx->bdev->bd_disk->queue->queue_lock); void blkg_conf_close_bdev(struct blkg_conf_ctx *ctx) __releases(&ctx->bdev->bd_queue->rq_qos_mutex); -void blkg_conf_close_bdev_frozen(struct blkg_conf_ctx *ctx, - unsigned long memflags) - __releases(&ctx->bdev->bd_queue->rq_qos_mutex); /** * bio_issue_as_root_blkg - see if this bio needs to be issued as root blkg diff --git a/block/blk-iocost.c b/block/blk-iocost.c index 050bfbc6d806..302388e99588 100644 --- a/block/blk-iocost.c +++ b/block/blk-iocost.c @@ -3233,19 +3233,30 @@ static ssize_t ioc_qos_write(struct kernfs_open_file *of, char *input, size_t nbytes, loff_t off) { struct blkg_conf_ctx ctx; + struct request_queue *q; struct gendisk *disk; struct ioc *ioc; u32 qos[NR_QOS_PARAMS]; bool enable, user; char *body, *p; - unsigned long memflags; - int ret = 0; + unsigned int memflags; + int ret; blkg_conf_init(&ctx, input); - memflags = blkg_conf_open_bdev_frozen(&ctx); - if (IS_ERR_VALUE(memflags)) - return memflags; + ret = blkg_conf_open_bdev(&ctx); + if (ret) + return ret; + /* + * At this point, we haven’t started protecting anything related to QoS, + * so we release q->rq_qos_mutex here, which was first acquired in blkg_ + * conf_open_bdev. Later, we re-acquire q->rq_qos_mutex after freezing + * the queue to maintain the correct locking order. + */ + mutex_unlock(&ctx.bdev->bd_queue->rq_qos_mutex); + + memflags = blk_mq_freeze_queue(ctx.bdev->bd_queue); + mutex_lock(&ctx.bdev->bd_queue->rq_qos_mutex); body = ctx.body; disk = ctx.bdev->bd_disk; @@ -3363,7 +3374,9 @@ static ssize_t ioc_qos_write(struct kernfs_open_file *of, char *input, blk_mq_unquiesce_queue(disk->queue); close_bdev: - blkg_conf_close_bdev_frozen(&ctx, memflags); + q = ctx.bdev->bd_queue; + blkg_conf_close_bdev(&ctx); + blk_mq_unfreeze_queue(q, memflags); return ret ?: nbytes; einval: From 73bb2480e3eccc6cb2419691c9e60dea9dc6d719 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:01 -0700 Subject: [PATCH 098/117] block/crypto: Annotate the crypto functions Add the lock context annotations required for Clang's thread-safety analysis. Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Cc: Eric Biggers Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/297b40e43a7f9b7d20e91a6c44b41a69d01f5c63.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/blk-crypto-profile.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/blk-crypto-profile.c b/block/blk-crypto-profile.c index 4ac74443687a..cf447ba4a66e 100644 --- a/block/blk-crypto-profile.c +++ b/block/blk-crypto-profile.c @@ -43,6 +43,7 @@ struct blk_crypto_keyslot { }; static inline void blk_crypto_hw_enter(struct blk_crypto_profile *profile) + __acquires(&profile->lock) { /* * Calling into the driver requires profile->lock held and the device @@ -55,6 +56,7 @@ static inline void blk_crypto_hw_enter(struct blk_crypto_profile *profile) } static inline void blk_crypto_hw_exit(struct blk_crypto_profile *profile) + __releases(&profile->lock) { up_write(&profile->lock); if (profile->dev) From 1ff85a387947890938c05cfe22041dfeef3098dd Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:02 -0700 Subject: [PATCH 099/117] block/blk-iocost: Split ioc_rqos_throttle() Prepare for inlining iocg_lock() and iocg_unlock() by moving the code between these two calls into a new function. No functionality has been changed. Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Cc: Tejun Heo Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/a6d3ed953cef6669d23a80923bf46600733cbdae.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/blk-iocost.c | 163 ++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 69 deletions(-) diff --git a/block/blk-iocost.c b/block/blk-iocost.c index 302388e99588..8f1468444e97 100644 --- a/block/blk-iocost.c +++ b/block/blk-iocost.c @@ -2614,6 +2614,88 @@ static u64 calc_size_vtime_cost(struct request *rq, struct ioc *ioc) return cost; } +enum over_budget_action { + action_retry, + action_commit, + action_wait, + action_return, +}; + +static enum over_budget_action +iocg_handle_over_budget(struct rq_qos *rqos, struct ioc_gq *iocg, + struct bio *bio, struct ioc_now *now, + struct iocg_wait *wait, bool use_debt, bool ioc_locked, + u64 abs_cost, u64 cost) +{ + lockdep_assert_held(&iocg->waitq.lock); + + /* + * @iocg must stay activated for debt and waitq handling. Deactivation + * is synchronized against both ioc->lock and waitq.lock and we won't + * get deactivated as long as we're waiting or have debt, so we're good + * if we're activated here. In the unlikely cases that we aren't, just + * issue the IO. + */ + if (unlikely(list_empty(&iocg->active_list))) + return action_commit; + + /* + * We're over budget. If @bio has to be issued regardless, remember + * the abs_cost instead of advancing vtime. iocg_kick_waitq() will pay + * off the debt before waking more IOs. + * + * This way, the debt is continuously paid off each period with the + * actual budget available to the cgroup. If we just wound vtime, we + * would incorrectly use the current hw_inuse for the entire amount + * which, for example, can lead to the cgroup staying blocked for a + * long time even with substantially raised hw_inuse. + * + * An iocg with vdebt should stay online so that the timer can keep + * deducting its vdebt and [de]activate use_delay mechanism + * accordingly. We don't want to race against the timer trying to + * clear them and leave @iocg inactive w/ dangling use_delay heavily + * penalizing the cgroup and its descendants. + */ + if (use_debt) { + iocg_incur_debt(iocg, abs_cost, now); + if (iocg_kick_delay(iocg, now)) + blkcg_schedule_throttle(rqos->disk, + (bio->bi_opf & REQ_SWAP) == + REQ_SWAP); + return action_return; + } + + /* guarantee that iocgs w/ waiters have maximum inuse */ + if (!iocg->abs_vdebt && iocg->inuse != iocg->active) { + if (!ioc_locked) + return action_retry; + lockdep_assert_held(&iocg->ioc->lock); + propagate_weights(iocg, iocg->active, iocg->active, true, now); + } + + /* + * Append self to the waitq and schedule the wakeup timer if we're + * the first waiter. The timer duration is calculated based on the + * current vrate. vtime and hweight changes can make it too short + * or too long. Each wait entry records the absolute cost it's + * waiting for to allow re-evaluation using a custom wait entry. + * + * If too short, the timer simply reschedules itself. If too long, + * the period timer will notice and trigger wakeups. + * + * All waiters are on iocg->waitq and the wait states are + * synchronized using waitq.lock. + */ + init_wait_func(&wait->wait, iocg_wake_fn); + wait->bio = bio; + wait->abs_cost = abs_cost; + wait->committed = false; /* will be set true by waker */ + + __add_wait_queue_entry_tail(&iocg->waitq, &wait->wait); + iocg_kick_waitq(iocg, ioc_locked, now); + return action_wait; +} + static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio) { struct blkcg_gq *blkg = bio->bi_blkg; @@ -2623,6 +2705,7 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio) struct iocg_wait wait; u64 abs_cost, cost, vtime; bool use_debt, ioc_locked; + enum over_budget_action action; unsigned long flags; /* bypass IOs if disabled, still initializing, or for root cgroup */ @@ -2663,80 +2746,22 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio) ioc_locked = use_debt || READ_ONCE(iocg->abs_vdebt); retry_lock: iocg_lock(iocg, ioc_locked, &flags); - - /* - * @iocg must stay activated for debt and waitq handling. Deactivation - * is synchronized against both ioc->lock and waitq.lock and we won't - * get deactivated as long as we're waiting or has debt, so we're good - * if we're activated here. In the unlikely cases that we aren't, just - * issue the IO. - */ - if (unlikely(list_empty(&iocg->active_list))) { - iocg_unlock(iocg, ioc_locked, &flags); + action = iocg_handle_over_budget(rqos, iocg, bio, &now, &wait, use_debt, + ioc_locked, abs_cost, cost); + iocg_unlock(iocg, ioc_locked, &flags); + switch (action) { + case action_retry: + ioc_locked = true; + goto retry_lock; + case action_commit: iocg_commit_bio(iocg, bio, abs_cost, cost); return; - } - - /* - * We're over budget. If @bio has to be issued regardless, remember - * the abs_cost instead of advancing vtime. iocg_kick_waitq() will pay - * off the debt before waking more IOs. - * - * This way, the debt is continuously paid off each period with the - * actual budget available to the cgroup. If we just wound vtime, we - * would incorrectly use the current hw_inuse for the entire amount - * which, for example, can lead to the cgroup staying blocked for a - * long time even with substantially raised hw_inuse. - * - * An iocg with vdebt should stay online so that the timer can keep - * deducting its vdebt and [de]activate use_delay mechanism - * accordingly. We don't want to race against the timer trying to - * clear them and leave @iocg inactive w/ dangling use_delay heavily - * penalizing the cgroup and its descendants. - */ - if (use_debt) { - iocg_incur_debt(iocg, abs_cost, &now); - if (iocg_kick_delay(iocg, &now)) - blkcg_schedule_throttle(rqos->disk, - (bio->bi_opf & REQ_SWAP) == REQ_SWAP); - iocg_unlock(iocg, ioc_locked, &flags); + case action_return: return; + case action_wait: + break; } - /* guarantee that iocgs w/ waiters have maximum inuse */ - if (!iocg->abs_vdebt && iocg->inuse != iocg->active) { - if (!ioc_locked) { - iocg_unlock(iocg, false, &flags); - ioc_locked = true; - goto retry_lock; - } - propagate_weights(iocg, iocg->active, iocg->active, true, - &now); - } - - /* - * Append self to the waitq and schedule the wakeup timer if we're - * the first waiter. The timer duration is calculated based on the - * current vrate. vtime and hweight changes can make it too short - * or too long. Each wait entry records the absolute cost it's - * waiting for to allow re-evaluation using a custom wait entry. - * - * If too short, the timer simply reschedules itself. If too long, - * the period timer will notice and trigger wakeups. - * - * All waiters are on iocg->waitq and the wait states are - * synchronized using waitq.lock. - */ - init_wait_func(&wait.wait, iocg_wake_fn); - wait.bio = bio; - wait.abs_cost = abs_cost; - wait.committed = false; /* will be set true by waker */ - - __add_wait_queue_entry_tail(&iocg->waitq, &wait.wait); - iocg_kick_waitq(iocg, ioc_locked, &now); - - iocg_unlock(iocg, ioc_locked, &flags); - while (true) { set_current_state(TASK_UNINTERRUPTIBLE); if (wait.committed) From a255026594e9b7eea24c12d2bd4acae0c11eea94 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:03 -0700 Subject: [PATCH 100/117] block/blk-iocost: Inline iocg_lock() and iocg_unlock() Both iocg_lock() and iocg_unlock() use conditional locking. Fold these functions into their callers such that unlocking becomes unconditional. Cc: Tejun Heo Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/f8c9867788957d2e40a32e23c6d9b866e480ad9d.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/blk-iocost.c | 53 +++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/block/blk-iocost.c b/block/blk-iocost.c index 8f1468444e97..563cc7dcf348 100644 --- a/block/blk-iocost.c +++ b/block/blk-iocost.c @@ -727,26 +727,6 @@ static void iocg_commit_bio(struct ioc_gq *iocg, struct bio *bio, put_cpu_ptr(gcs); } -static void iocg_lock(struct ioc_gq *iocg, bool lock_ioc, unsigned long *flags) -{ - if (lock_ioc) { - spin_lock_irqsave(&iocg->ioc->lock, *flags); - spin_lock(&iocg->waitq.lock); - } else { - spin_lock_irqsave(&iocg->waitq.lock, *flags); - } -} - -static void iocg_unlock(struct ioc_gq *iocg, bool unlock_ioc, unsigned long *flags) -{ - if (unlock_ioc) { - spin_unlock(&iocg->waitq.lock); - spin_unlock_irqrestore(&iocg->ioc->lock, *flags); - } else { - spin_unlock_irqrestore(&iocg->waitq.lock, *flags); - } -} - #define CREATE_TRACE_POINTS #include @@ -1589,9 +1569,17 @@ static enum hrtimer_restart iocg_waitq_timer_fn(struct hrtimer *timer) ioc_now(iocg->ioc, &now); - iocg_lock(iocg, pay_debt, &flags); - iocg_kick_waitq(iocg, pay_debt, &now); - iocg_unlock(iocg, pay_debt, &flags); + if (pay_debt) { + spin_lock_irqsave(&iocg->ioc->lock, flags); + spin_lock(&iocg->waitq.lock); + iocg_kick_waitq(iocg, pay_debt, &now); + spin_unlock(&iocg->waitq.lock); + spin_unlock_irqrestore(&iocg->ioc->lock, flags); + } else { + spin_lock_irqsave(&iocg->waitq.lock, flags); + iocg_kick_waitq(iocg, pay_debt, &now); + spin_unlock_irqrestore(&iocg->waitq.lock, flags); + } return HRTIMER_NORESTART; } @@ -2745,10 +2733,21 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio) use_debt = bio_issue_as_root_blkg(bio) || fatal_signal_pending(current); ioc_locked = use_debt || READ_ONCE(iocg->abs_vdebt); retry_lock: - iocg_lock(iocg, ioc_locked, &flags); - action = iocg_handle_over_budget(rqos, iocg, bio, &now, &wait, use_debt, - ioc_locked, abs_cost, cost); - iocg_unlock(iocg, ioc_locked, &flags); + if (ioc_locked) { + spin_lock_irqsave(&iocg->ioc->lock, flags); + spin_lock(&iocg->waitq.lock); + action = iocg_handle_over_budget(rqos, iocg, bio, &now, &wait, + use_debt, ioc_locked, abs_cost, + cost); + spin_unlock(&iocg->waitq.lock); + spin_unlock_irqrestore(&iocg->ioc->lock, flags); + } else { + spin_lock_irqsave(&iocg->waitq.lock, flags); + action = iocg_handle_over_budget(rqos, iocg, bio, &now, &wait, + use_debt, ioc_locked, abs_cost, + cost); + spin_unlock_irqrestore(&iocg->waitq.lock, flags); + } switch (action) { case action_retry: ioc_locked = true; From 131f14125a1840d393c3dec8910483e3fc3daf18 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:04 -0700 Subject: [PATCH 101/117] block/blk-mq-debugfs: Improve lock context annotations Make the existing lock context annotations compatible with Clang. Add the lock context annotations that are missing. Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/f58fe220ff98f9dfddfed4573f40005c773b7fb7.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/blk-mq-debugfs.c | 24 ++++++++++++++++++------ block/blk.h | 4 ++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/block/blk-mq-debugfs.c b/block/blk-mq-debugfs.c index 047ec887456b..6754d8f9449c 100644 --- a/block/blk-mq-debugfs.c +++ b/block/blk-mq-debugfs.c @@ -19,8 +19,10 @@ static int queue_poll_stat_show(void *data, struct seq_file *m) return 0; } +#define TO_REQUEST_QUEUE(m) ((struct request_queue *)m->private) + static void *queue_requeue_list_start(struct seq_file *m, loff_t *pos) - __acquires(&q->requeue_lock) + __acquires(&TO_REQUEST_QUEUE(m)->requeue_lock) { struct request_queue *q = m->private; @@ -36,13 +38,15 @@ static void *queue_requeue_list_next(struct seq_file *m, void *v, loff_t *pos) } static void queue_requeue_list_stop(struct seq_file *m, void *v) - __releases(&q->requeue_lock) + __releases(&TO_REQUEST_QUEUE(m)->requeue_lock) { struct request_queue *q = m->private; spin_unlock_irq(&q->requeue_lock); } +#undef TO_REQUEST_QUEUE + static const struct seq_operations queue_requeue_list_seq_ops = { .start = queue_requeue_list_start, .next = queue_requeue_list_next, @@ -297,8 +301,10 @@ int blk_mq_debugfs_rq_show(struct seq_file *m, void *v) } EXPORT_SYMBOL_GPL(blk_mq_debugfs_rq_show); +#define TO_HCTX(m) ((struct blk_mq_hw_ctx *)m->private) + static void *hctx_dispatch_start(struct seq_file *m, loff_t *pos) - __acquires(&hctx->lock) + __acquires(&TO_HCTX(m)->lock) { struct blk_mq_hw_ctx *hctx = m->private; @@ -314,13 +320,15 @@ static void *hctx_dispatch_next(struct seq_file *m, void *v, loff_t *pos) } static void hctx_dispatch_stop(struct seq_file *m, void *v) - __releases(&hctx->lock) + __releases(&TO_HCTX(m)->lock) { struct blk_mq_hw_ctx *hctx = m->private; spin_unlock(&hctx->lock); } +#undef TO_HCTX + static const struct seq_operations hctx_dispatch_seq_ops = { .start = hctx_dispatch_start, .next = hctx_dispatch_next, @@ -484,9 +492,11 @@ static int hctx_dispatch_busy_show(void *data, struct seq_file *m) return 0; } +#define TO_CTX(m) ((struct blk_mq_ctx *)m->private) + #define CTX_RQ_SEQ_OPS(name, type) \ static void *ctx_##name##_rq_list_start(struct seq_file *m, loff_t *pos) \ - __acquires(&ctx->lock) \ + __acquires(&TO_CTX(m)->lock) \ { \ struct blk_mq_ctx *ctx = m->private; \ \ @@ -503,7 +513,7 @@ static void *ctx_##name##_rq_list_next(struct seq_file *m, void *v, \ } \ \ static void ctx_##name##_rq_list_stop(struct seq_file *m, void *v) \ - __releases(&ctx->lock) \ + __releases(&TO_CTX(m)->lock) \ { \ struct blk_mq_ctx *ctx = m->private; \ \ @@ -521,6 +531,8 @@ CTX_RQ_SEQ_OPS(default, HCTX_TYPE_DEFAULT); CTX_RQ_SEQ_OPS(read, HCTX_TYPE_READ); CTX_RQ_SEQ_OPS(poll, HCTX_TYPE_POLL); +#undef TO_CTX + static int blk_mq_debugfs_show(struct seq_file *m, void *v) { const struct blk_mq_debugfs_attr *attr = m->private; diff --git a/block/blk.h b/block/blk.h index bf1a80493ff1..1a2d9101bba0 100644 --- a/block/blk.h +++ b/block/blk.h @@ -756,16 +756,19 @@ static inline void blk_unfreeze_release_lock(struct request_queue *q) * reclaim from triggering block I/O. */ static inline void blk_debugfs_lock_nomemsave(struct request_queue *q) + __acquires(&q->debugfs_mutex) { mutex_lock(&q->debugfs_mutex); } static inline void blk_debugfs_unlock_nomemrestore(struct request_queue *q) + __releases(&q->debugfs_mutex) { mutex_unlock(&q->debugfs_mutex); } static inline unsigned int __must_check blk_debugfs_lock(struct request_queue *q) + __acquires(&q->debugfs_mutex) { unsigned int memflags = memalloc_noio_save(); @@ -775,6 +778,7 @@ static inline unsigned int __must_check blk_debugfs_lock(struct request_queue *q static inline void blk_debugfs_unlock(struct request_queue *q, unsigned int memflags) + __releases(&q->debugfs_mutex) { blk_debugfs_unlock_nomemrestore(q); memalloc_noio_restore(memflags); From b4591b91526ef53eedefc124221ec1a060bfbe54 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:05 -0700 Subject: [PATCH 102/117] block/Kyber: Make the lock context annotations compatible with Clang While sparse ignores the __acquires() and __releases() arguments, Clang verifies these. Make the arguments of __acquires() and __releases() acceptable for Clang. Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/91cb8c790fc8b26b8aa742569fbf8c2c1d099dac.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/kyber-iosched.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/block/kyber-iosched.c b/block/kyber-iosched.c index b84163d1f851..971818bcdc9d 100644 --- a/block/kyber-iosched.c +++ b/block/kyber-iosched.c @@ -882,6 +882,9 @@ static const struct elv_fs_entry kyber_sched_attrs[] = { }; #undef KYBER_LAT_ATTR +#define HCTX_FROM_SEQ_FILE(m) ((struct blk_mq_hw_ctx *)(m)->private) +#define KYBER_HCTX_DATA(hctx) ((struct kyber_hctx_data *)(hctx)->sched_data) + #ifdef CONFIG_BLK_DEBUG_FS #define KYBER_DEBUGFS_DOMAIN_ATTRS(domain, name) \ static int kyber_##name##_tokens_show(void *data, struct seq_file *m) \ @@ -894,7 +897,7 @@ static int kyber_##name##_tokens_show(void *data, struct seq_file *m) \ } \ \ static void *kyber_##name##_rqs_start(struct seq_file *m, loff_t *pos) \ - __acquires(&khd->lock) \ + __acquires(&KYBER_HCTX_DATA(HCTX_FROM_SEQ_FILE(m))->lock) \ { \ struct blk_mq_hw_ctx *hctx = m->private; \ struct kyber_hctx_data *khd = hctx->sched_data; \ @@ -913,7 +916,7 @@ static void *kyber_##name##_rqs_next(struct seq_file *m, void *v, \ } \ \ static void kyber_##name##_rqs_stop(struct seq_file *m, void *v) \ - __releases(&khd->lock) \ + __releases(&KYBER_HCTX_DATA(HCTX_FROM_SEQ_FILE(m))->lock) \ { \ struct blk_mq_hw_ctx *hctx = m->private; \ struct kyber_hctx_data *khd = hctx->sched_data; \ From f10b2de2af28f90c9d1a0774a474e5c4e4d222da Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:06 -0700 Subject: [PATCH 103/117] block/mq-deadline: Make the lock context annotations compatible with Clang While sparse ignores the __acquires() and __releases() arguments, Clang verifies these. Make the arguments of __acquires() and __releases() acceptable for Clang. Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/3b6e336ced91e27213608ffce205ccd24f4ba285.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/mq-deadline.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/block/mq-deadline.c b/block/mq-deadline.c index 95917a88976f..824bfc17b2c6 100644 --- a/block/mq-deadline.c +++ b/block/mq-deadline.c @@ -794,11 +794,15 @@ static const struct elv_fs_entry deadline_attrs[] = { __ATTR_NULL }; +#define RQ_FROM_SEQ_FILE(m) ((struct request_queue *)(m)->private) +#define DD_DATA_FROM_RQ(rq) \ + ((struct deadline_data *)(rq)->elevator->elevator_data) + #ifdef CONFIG_BLK_DEBUG_FS #define DEADLINE_DEBUGFS_DDIR_ATTRS(prio, data_dir, name) \ static void *deadline_##name##_fifo_start(struct seq_file *m, \ loff_t *pos) \ - __acquires(&dd->lock) \ + __acquires(&DD_DATA_FROM_RQ(RQ_FROM_SEQ_FILE(m))->lock) \ { \ struct request_queue *q = m->private; \ struct deadline_data *dd = q->elevator->elevator_data; \ @@ -819,7 +823,7 @@ static void *deadline_##name##_fifo_next(struct seq_file *m, void *v, \ } \ \ static void deadline_##name##_fifo_stop(struct seq_file *m, void *v) \ - __releases(&dd->lock) \ + __releases(&DD_DATA_FROM_RQ(RQ_FROM_SEQ_FILE(m))->lock) \ { \ struct request_queue *q = m->private; \ struct deadline_data *dd = q->elevator->elevator_data; \ @@ -921,7 +925,7 @@ static int dd_owned_by_driver_show(void *data, struct seq_file *m) } static void *deadline_dispatch_start(struct seq_file *m, loff_t *pos) - __acquires(&dd->lock) + __acquires(&DD_DATA_FROM_RQ(RQ_FROM_SEQ_FILE(m))->lock) { struct request_queue *q = m->private; struct deadline_data *dd = q->elevator->elevator_data; @@ -939,7 +943,7 @@ static void *deadline_dispatch_next(struct seq_file *m, void *v, loff_t *pos) } static void deadline_dispatch_stop(struct seq_file *m, void *v) - __releases(&dd->lock) + __releases(&DD_DATA_FROM_RQ(RQ_FROM_SEQ_FILE(m))->lock) { struct request_queue *q = m->private; struct deadline_data *dd = q->elevator->elevator_data; From 5f0777166e3eaefc02ec0e381658f510f4d068ce Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 5 Jun 2026 11:01:07 -0700 Subject: [PATCH 104/117] block: Enable lock context analysis Now that all block/*.c files have been annotated, enable lock context analysis for all these source files. Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Bart Van Assche Reviewed-by: Chaitanya Kulkarni Link: https://patch.msgid.link/e248ca3aeead238bbc489cf3afdafcbff9e41faf.1780682325.git.bvanassche@acm.org Signed-off-by: Jens Axboe --- block/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/Makefile b/block/Makefile index 7dce2e44276c..54130faacc21 100644 --- a/block/Makefile +++ b/block/Makefile @@ -3,6 +3,8 @@ # Makefile for the kernel block layer # +CONTEXT_ANALYSIS := y + obj-y := bdev.o fops.o bio.o elevator.o blk-core.o blk-sysfs.o \ blk-flush.o blk-settings.o blk-ioc.o blk-map.o \ blk-merge.o blk-timeout.o blk-lib.o blk-mq.o \ From 2dc0bfd2fe355fb930de63c2f2eb8ced8570c579 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sun, 7 Jun 2026 06:41:43 +0000 Subject: [PATCH 105/117] partitions: aix: bound the pp_count scan to the ppe array aix_partition() reads the physical volume descriptor into a fixed-size struct pvd and then scans its physical-partition-extent array: int numpps = be16_to_cpu(pvd->pp_count); ... for (i = 0; i < numpps; i += 1) { struct ppe *p = pvd->ppe + i; ... lp_ix = be16_to_cpu(p->lp_ix); pvd points at a single kmalloc()'d struct pvd whose ppe[] member holds a fixed ARRAY_SIZE(pvd->ppe) (1016) entries, but the loop runs up to the on-disk pp_count. pp_count is an unvalidated __be16 read straight from the descriptor, so a crafted AIX image with pp_count larger than 1016 drives the loop to read pvd->ppe[i] past the end of the allocation (up to 65535 entries, ~2 MB out of bounds). The partition scan runs without mounting anything, when a block device with a crafted AIX/IBM partition table appears (an attacker-supplied image attached with losetup -P, or a device auto-scanned by udev), via msdos_partition() -> aix_partition(). Clamp the scan to the number of entries the ppe[] array can hold. Fixes: 6ceea22bbbc8 ("partitions: add aix lvm partition support files") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Acked-by: Philippe De Muyter Link: https://patch.msgid.link/20260607064137.302574-1-hexlabsecurity@proton.me Signed-off-by: Jens Axboe --- block/partitions/aix.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/partitions/aix.c b/block/partitions/aix.c index 29b8f4cebb63..f3c4174e003e 100644 --- a/block/partitions/aix.c +++ b/block/partitions/aix.c @@ -226,6 +226,15 @@ int aix_partition(struct parsed_partitions *state) int next_lp_ix = 1; int lp_ix; + /* + * pvd was read into a fixed-size struct pvd whose ppe[] array + * holds ARRAY_SIZE(pvd->ppe) entries. pp_count is an + * unvalidated on-disk __be16, so clamp the scan to the array + * size to avoid walking past the allocation. + */ + if (numpps > ARRAY_SIZE(pvd->ppe)) + numpps = ARRAY_SIZE(pvd->ppe); + for (i = 0; i < numpps; i += 1) { struct ppe *p = pvd->ppe + i; unsigned int lv_ix; From 5ef1b0194b382fafe5023b5b014e4db3b948ee15 Mon Sep 17 00:00:00 2001 From: David Laight Date: Sat, 6 Jun 2026 21:27:44 +0100 Subject: [PATCH 106/117] drivers/block/rbd: Use strscpy() to copy strings into arrays Replacing strcpy() with strscpy() ensures than overflow of the target buffer cannot happen. Signed-off-by: David Laight Reviewed-by: Alex Elder Link: https://patch.msgid.link/20260606202744.5113-5-david.laight.linux@gmail.com Signed-off-by: Jens Axboe --- drivers/block/rbd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index 0a0b0a1af769..301d1faacdf2 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -3672,7 +3672,7 @@ static void __rbd_lock(struct rbd_device *rbd_dev, const char *cookie) struct rbd_client_id cid = rbd_get_cid(rbd_dev); rbd_dev->lock_state = RBD_LOCK_STATE_LOCKED; - strcpy(rbd_dev->lock_cookie, cookie); + strscpy(rbd_dev->lock_cookie, cookie); rbd_set_owner_cid(rbd_dev, &cid); queue_work(rbd_dev->task_wq, &rbd_dev->acquired_lock_work); } From 7ed4aab1381f3439f45032eb860f89d9da5e45c2 Mon Sep 17 00:00:00 2001 From: Steven Feng Date: Sat, 6 Jun 2026 10:42:18 +0800 Subject: [PATCH 107/117] block: optimize I/O merge hot path with unlikely() hints Remove redundant '== false' comparisons and add unlikely() branch prediction hints in block I/O merge path functions. These functions (ll_new_hw_segment, ll_merge_requests_fn, and blk_rq_merge_ok) are executed on every I/O request merge attempt, making them critical hot paths. Data integrity check failures are rare events, so marking these conditions as unlikely() helps the CPU optimize the common case by improving branch prediction. Changes: - Replace 'func() == false' with 'unlikely(!func())' for better code style and branch prediction This micro-optimization reduces branch misprediction penalties in high-frequency I/O merge paths. Signed-off-by: Steven Feng Link: https://patch.msgid.link/tencent_79B652BD0CC23E093F27914380F161E7E505@qq.com Signed-off-by: Jens Axboe --- block/blk-merge.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/block/blk-merge.c b/block/blk-merge.c index 7cc82a7a6f4e..ee1d9213f43e 100644 --- a/block/blk-merge.c +++ b/block/blk-merge.c @@ -545,7 +545,7 @@ static inline int ll_new_hw_segment(struct request *req, struct bio *bio, if (!blk_cgroup_mergeable(req, bio)) goto no_merge; - if (blk_integrity_merge_bio(req->q, req, bio) == false) + if (unlikely(!blk_integrity_merge_bio(req->q, req, bio))) goto no_merge; /* discard request merge won't add new segment */ @@ -647,7 +647,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req, if (!blk_cgroup_mergeable(req, next->bio)) return 0; - if (blk_integrity_merge_rq(q, req, next) == false) + if (unlikely(!blk_integrity_merge_rq(q, req, next))) return 0; if (!bio_crypt_ctx_merge_rq(req, next)) @@ -903,7 +903,7 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) if (!blk_cgroup_mergeable(rq, bio)) return false; - if (blk_integrity_merge_bio(rq->q, rq, bio) == false) + if (unlikely(!blk_integrity_merge_bio(rq->q, rq, bio))) return false; if (!bio_crypt_rq_ctx_compatible(rq, bio)) return false; @@ -913,7 +913,7 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) return false; if (rq->bio->bi_ioprio != bio->bi_ioprio) return false; - if (blk_atomic_write_mergeable_rq_bio(rq, bio) == false) + if (unlikely(!blk_atomic_write_mergeable_rq_bio(rq, bio))) return false; return true; From 0fd835f5e9477ebea2439b8ada58f34e1b8cf25a Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 7 Jun 2026 08:48:34 -0400 Subject: [PATCH 108/117] virtio-blk: clamp zone report to the report buffer capacity virtblk_report_zones() trusts the device-reported number of zones when walking the report buffer: nz = min_t(u64, virtio64_to_cpu(vblk->vdev, report->nr_zones), nr_zones); ... for (i = 0; i < nz && zone_idx < nr_zones; i++) { ret = virtblk_parse_zone(vblk, &report->zones[i], ...); The buffer is allocated by virtblk_alloc_report_buffer(), whose size is capped by the queue's max hardware sectors and max segments and can therefore hold fewer descriptors than nr_zones. nz is bounded only by the device-supplied report->nr_zones and the requested nr_zones, never by the buffer's descriptor capacity. At probe time the request count is unbounded (blk_revalidate_disk_zones() calls report_zones() with nr_zones == UINT_MAX), so the device-supplied report->nr_zones is the sole gate: a device that reports more zones than fit in the buffer drives the loop to read report->zones[i] past the end of the allocation. A malicious or buggy virtio-blk device that reports an inflated nr_zones triggers this during zone revalidation at probe. KASAN reports a vmalloc-out-of-bounds read in virtblk_report_zones() against the report buffer allocated a few lines earlier. Clamp nz to the number of descriptors that actually fit in the report buffer. Fixes: 95bfec41bd3d ("virtio-blk: add support for zoned block devices") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Acked-by: Michael S. Tsirkin Reviewed-by: Stefan Hajnoczi Link: https://patch.msgid.link/20260607124834.3059944-1-michael.bommarito@gmail.com Signed-off-by: Jens Axboe --- drivers/block/virtio_blk.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c index b1c9a27fe00f..32bf3ba07a9d 100644 --- a/drivers/block/virtio_blk.c +++ b/drivers/block/virtio_blk.c @@ -689,6 +689,8 @@ static int virtblk_report_zones(struct gendisk *disk, sector_t sector, nz = min_t(u64, virtio64_to_cpu(vblk->vdev, report->nr_zones), nr_zones); + nz = min_t(u64, nz, + (buflen - sizeof(*report)) / sizeof(report->zones[0])); if (!nz) break; From 5bdb8ec58b54b0e86672ba1991087611c7e52de5 Mon Sep 17 00:00:00 2001 From: Tang Yizhou Date: Tue, 26 May 2026 10:15:55 +0800 Subject: [PATCH 109/117] block: propagate in_flight to whole disk on partition I/O MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now when I/O is submitted to a partition, the per-CPU in_flight[] counter is incremented only on the partition's block_device, not on the underlying whole disk. This leads to a problem which can be shown by a fio test: lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS mydev 252:1 0 20G 0 disk └─mydev1 259:0 0 10G 0 part iostat -xp 1 Device r/s rkB/s ... aqu-sz %util mydev 128153.00 512612.00 ... 13.22 72.20 mydev1 128154.00 512616.00 ... 13.22 100.00 %util is different between mydev and mydev1, which is unexpected. This is the cumulative effect of a series of patches. The root cause is commit e016b78201a2 ("block: return just one value from part_in_flight"), which deleted the branch in part_in_flight() that aggregated the whole-disk in_flight count on top of the partition's. Then the second commit is commit 10ec5e86f9b8 ("block: merge part_{inc,dev}_in_flight into their only callers"), which folded the whole-disk in_flight accounting into generic_start_io_acct() and generic_end_io_acct(). Those two helpers were then removed by commit e722fff238bb ("block: remove generic_{start,end}_io_acct"), and from that point on the whole disk's in_flight is no longer accounted at all. In update_io_ticks(), if calling bdev_count_inflight() finds that the inflight value of the whole device is 0, the accumulation of io_ticks will be skipped, causing the reported util% value to be underestimated. Fix it by restoring the whole-disk in_flight accounting. Fixes: e016b78201a2 ("block: return just one value from part_in_flight") Suggested-by: Leon Hwang Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Tang Yizhou Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260526021555.359500-1-yizhou.tang@shopee.com Signed-off-by: Jens Axboe --- block/blk-core.c | 4 ++-- block/blk-merge.c | 3 +-- block/blk-mq.c | 5 ++--- block/blk.h | 21 +++++++++++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/block/blk-core.c b/block/blk-core.c index b0f0a304ea0b..1c637db79e59 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -1038,7 +1038,7 @@ unsigned long bdev_start_io_acct(struct block_device *bdev, enum req_op op, { part_stat_lock(); update_io_ticks(bdev, start_time, false); - part_stat_local_inc(bdev, in_flight[op_is_write(op)]); + bdev_inc_in_flight(bdev, op); part_stat_unlock(); return start_time; @@ -1069,7 +1069,7 @@ void bdev_end_io_acct(struct block_device *bdev, enum req_op op, part_stat_inc(bdev, ios[sgrp]); part_stat_add(bdev, sectors[sgrp], sectors); part_stat_add(bdev, nsecs[sgrp], jiffies_to_nsecs(duration)); - part_stat_local_dec(bdev, in_flight[op_is_write(op)]); + bdev_dec_in_flight(bdev, op); part_stat_unlock(); } EXPORT_SYMBOL(bdev_end_io_acct); diff --git a/block/blk-merge.c b/block/blk-merge.c index ee1d9213f43e..ab1161ca69f1 100644 --- a/block/blk-merge.c +++ b/block/blk-merge.c @@ -721,8 +721,7 @@ static void blk_account_io_merge_request(struct request *req) if (req->rq_flags & RQF_IO_STAT) { part_stat_lock(); part_stat_inc(req->part, merges[op_stat_group(req_op(req))]); - part_stat_local_dec(req->part, - in_flight[op_is_write(req_op(req))]); + bdev_dec_in_flight(req->part, req_op(req)); part_stat_unlock(); } } diff --git a/block/blk-mq.c b/block/blk-mq.c index 629e16003eb7..32ce757e556f 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -1082,8 +1082,7 @@ static inline void blk_account_io_done(struct request *req, u64 now) update_io_ticks(req->part, jiffies, true); part_stat_inc(req->part, ios[sgrp]); part_stat_add(req->part, nsecs[sgrp], now - req->start_time_ns); - part_stat_local_dec(req->part, - in_flight[op_is_write(req_op(req))]); + bdev_dec_in_flight(req->part, req_op(req)); part_stat_unlock(); } } @@ -1113,7 +1112,7 @@ static inline void blk_account_io_start(struct request *req) part_stat_lock(); update_io_ticks(req->part, jiffies, false); - part_stat_local_inc(req->part, in_flight[op_is_write(req_op(req))]); + bdev_inc_in_flight(req->part, req_op(req)); part_stat_unlock(); } diff --git a/block/blk.h b/block/blk.h index 1a2d9101bba0..7fdfb9012ce1 100644 --- a/block/blk.h +++ b/block/blk.h @@ -4,6 +4,7 @@ #include #include +#include #include #include /* for max_pfn/max_low_pfn */ #include @@ -487,6 +488,26 @@ static inline void req_set_nomerge(struct request_queue *q, struct request *req) q->last_merge = NULL; } +static inline void bdev_inc_in_flight(struct block_device *bdev, + enum req_op op) +{ + bool rw = op_is_write(op); + + part_stat_local_inc(bdev, in_flight[rw]); + if (bdev_is_partition(bdev)) + part_stat_local_inc(bdev_whole(bdev), in_flight[rw]); +} + +static inline void bdev_dec_in_flight(struct block_device *bdev, + enum req_op op) +{ + bool rw = op_is_write(op); + + part_stat_local_dec(bdev, in_flight[rw]); + if (bdev_is_partition(bdev)) + part_stat_local_dec(bdev_whole(bdev), in_flight[rw]); +} + /* * Internal io_context interface */ From 64e335790272d9b0468af3a70e34f377924b156f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Wed, 10 Jun 2026 09:27:55 +0200 Subject: [PATCH 110/117] floppy: Drop unused pnp driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pnp_device_id array is only used for module data to support auto-loading the floppy module. So the .driver_data member is unused and this assignment can be dropped. While touching that array, align the coding style to what is used most for these. This patch doesn't modify the compiled array, only its representation in source form benefits. The former was confirmed with x86 and arm64 builds. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Denis Efremov (Oracle) Link: https://patch.msgid.link/99dbf851ffb99229ea1dcfd8f58e9ee6a1f05349.1781075967.git.u.kleine-koenig@baylibre.com Signed-off-by: Jens Axboe --- drivers/block/floppy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index 0509746f8aed..dca495be0683 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -5012,8 +5012,8 @@ MODULE_LICENSE("GPL"); /* This doesn't actually get used other than for module information */ static const struct pnp_device_id floppy_pnpids[] = { - {"PNP0700", 0}, - {} + { .id = "PNP0700" }, + { } }; MODULE_DEVICE_TABLE(pnp, floppy_pnpids); From 8c8ebed16581faf3b3e97336aeca3d8226c4435f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 11 Jun 2026 16:06:44 +0200 Subject: [PATCH 111/117] block: add a macro to initialize the status table Prepare for adding a new value to the error table by adding a macro to fill it. Signed-off-by: Christoph Hellwig Reviewed-by: Bart Van Assche Reviewed-by: Keith Busch Reviewed-by: Damien Le Moal Reviewed-by: Hannes Reinecke Reviewed-by: Md Haris Iqbal Link: https://patch.msgid.link/20260611140703.2401204-2-hch@lst.de Signed-off-by: Jens Axboe --- block/blk-core.c | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/block/blk-core.c b/block/blk-core.c index 1c637db79e59..43121a9f99f0 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -132,39 +132,44 @@ inline const char *blk_op_str(enum req_op op) } EXPORT_SYMBOL_GPL(blk_op_str); +#define ENT(_tag, _errno, _desc) \ +[BLK_STS_##_tag] = { \ + .errno = _errno, \ + .name = _desc, \ +} static const struct { int errno; const char *name; } blk_errors[] = { - [BLK_STS_OK] = { 0, "" }, - [BLK_STS_NOTSUPP] = { -EOPNOTSUPP, "operation not supported" }, - [BLK_STS_TIMEOUT] = { -ETIMEDOUT, "timeout" }, - [BLK_STS_NOSPC] = { -ENOSPC, "critical space allocation" }, - [BLK_STS_TRANSPORT] = { -ENOLINK, "recoverable transport" }, - [BLK_STS_TARGET] = { -EREMOTEIO, "critical target" }, - [BLK_STS_RESV_CONFLICT] = { -EBADE, "reservation conflict" }, - [BLK_STS_MEDIUM] = { -ENODATA, "critical medium" }, - [BLK_STS_PROTECTION] = { -EILSEQ, "protection" }, - [BLK_STS_RESOURCE] = { -ENOMEM, "kernel resource" }, - [BLK_STS_DEV_RESOURCE] = { -EBUSY, "device resource" }, - [BLK_STS_AGAIN] = { -EAGAIN, "nonblocking retry" }, - [BLK_STS_OFFLINE] = { -ENODEV, "device offline" }, + ENT(OK, 0, ""), + ENT(NOTSUPP, -EOPNOTSUPP, "operation not supported"), + ENT(TIMEOUT, -ETIMEDOUT, "timeout"), + ENT(NOSPC, -ENOSPC, "critical space allocation"), + ENT(TRANSPORT, -ENOLINK, "recoverable transport"), + ENT(TARGET, -EREMOTEIO, "critical target"), + ENT(RESV_CONFLICT, -EBADE, "reservation conflict"), + ENT(MEDIUM, -ENODATA, "critical medium"), + ENT(PROTECTION, -EILSEQ, "protection"), + ENT(RESOURCE, -ENOMEM, "kernel resource"), + ENT(DEV_RESOURCE, -EBUSY, "device resource"), + ENT(AGAIN, -EAGAIN, "nonblocking retry"), + ENT(OFFLINE, -ENODEV, "device offline"), /* device mapper special case, should not leak out: */ - [BLK_STS_DM_REQUEUE] = { -EREMCHG, "dm internal retry" }, + ENT(DM_REQUEUE, -EREMCHG, "dm internal retry"), /* zone device specific errors */ - [BLK_STS_ZONE_OPEN_RESOURCE] = { -ETOOMANYREFS, "open zones exceeded" }, - [BLK_STS_ZONE_ACTIVE_RESOURCE] = { -EOVERFLOW, "active zones exceeded" }, + ENT(ZONE_OPEN_RESOURCE, -ETOOMANYREFS, "open zones exceeded"), + ENT(ZONE_ACTIVE_RESOURCE, -EOVERFLOW, "active zones exceeded"), /* Command duration limit device-side timeout */ - [BLK_STS_DURATION_LIMIT] = { -ETIME, "duration limit exceeded" }, - - [BLK_STS_INVAL] = { -EINVAL, "invalid" }, + ENT(DURATION_LIMIT, -ETIME, "duration limit exceeded"), + ENT(INVAL, -EINVAL, "invalid"), /* everything else not covered above: */ - [BLK_STS_IOERR] = { -EIO, "I/O" }, + ENT(IOERR, -EIO, "I/O"), }; +#undef ENT blk_status_t errno_to_blk_status(int errno) { From ce351560b714403acfdeed86ef96675d229da837 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 11 Jun 2026 16:06:45 +0200 Subject: [PATCH 112/117] block: add a "tag" for block status codes The full name of the status codes is not good for user interfaces as it can contain white spaces. Add the name of the status code without the BLK_STS_ prefix as a tag so that it can be used for user interfaces. Signed-off-by: Christoph Hellwig Reviewed-by: Keith Busch Reviewed-by: Damien Le Moal Reviewed-by: Hannes Reinecke Reviewed-by: Md Haris Iqbal Link: https://patch.msgid.link/20260611140703.2401204-3-hch@lst.de Signed-off-by: Jens Axboe --- block/blk-core.c | 28 ++++++++++++++++++++++++++++ block/blk.h | 2 ++ 2 files changed, 30 insertions(+) diff --git a/block/blk-core.c b/block/blk-core.c index 43121a9f99f0..842b5c6f2fb4 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -135,10 +135,12 @@ EXPORT_SYMBOL_GPL(blk_op_str); #define ENT(_tag, _errno, _desc) \ [BLK_STS_##_tag] = { \ .errno = _errno, \ + .tag = __stringify(_tag), \ .name = _desc, \ } static const struct { int errno; + const char *tag; const char *name; } blk_errors[] = { ENT(OK, 0, ""), @@ -203,6 +205,32 @@ const char *blk_status_to_str(blk_status_t status) return blk_errors[idx].name; } +const char *blk_status_to_tag(blk_status_t status) +{ + int idx = (__force int)status; + + if (WARN_ON_ONCE(idx >= ARRAY_SIZE(blk_errors) || !blk_errors[idx].tag)) + return ""; + return blk_errors[idx].tag; +} + +blk_status_t tag_to_blk_status(const char *tag) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(blk_errors); i++) { + if (blk_errors[i].tag && + !strcmp(blk_errors[i].tag, tag)) + return (__force blk_status_t)i; + } + + /* + * Return BLK_STS_OK for mismatches as this function is intended to + * parse error status values. + */ + return BLK_STS_OK; +} + /** * blk_sync_queue - cancel any pending callbacks on a queue * @q: the queue diff --git a/block/blk.h b/block/blk.h index 7fdfb9012ce1..3ab2cdd6ed12 100644 --- a/block/blk.h +++ b/block/blk.h @@ -51,6 +51,8 @@ struct blk_flush_queue *blk_alloc_flush_queue(int node, int cmd_size, void blk_free_flush_queue(struct blk_flush_queue *q); const char *blk_status_to_str(blk_status_t status); +const char *blk_status_to_tag(blk_status_t status); +blk_status_t tag_to_blk_status(const char *tag); bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic); bool blk_queue_start_drain(struct request_queue *q); From d39a63ead381c7ee93cd938ea2d759c17343b522 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 11 Jun 2026 16:06:46 +0200 Subject: [PATCH 113/117] block: add a str_to_blk_op helper Add a helper to find the REQ_OP_XYZ constant from the "XYZ" string. This will be used for the error injection debugfs interface. Signed-off-by: Christoph Hellwig Reviewed-by: Keith Busch Reviewed-by: Damien Le Moal Reviewed-by: Hannes Reinecke Reviewed-by: Md Haris Iqbal Link: https://patch.msgid.link/20260611140703.2401204-4-hch@lst.de Signed-off-by: Jens Axboe --- block/blk-core.c | 10 ++++++++++ block/blk.h | 1 + 2 files changed, 11 insertions(+) diff --git a/block/blk-core.c b/block/blk-core.c index 842b5c6f2fb4..beaab7a71fba 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -132,6 +132,16 @@ inline const char *blk_op_str(enum req_op op) } EXPORT_SYMBOL_GPL(blk_op_str); +enum req_op str_to_blk_op(const char *op) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(blk_op_name); i++) + if (blk_op_name[i] && !strcmp(blk_op_name[i], op)) + return (enum req_op)i; + return REQ_OP_LAST; +} + #define ENT(_tag, _errno, _desc) \ [BLK_STS_##_tag] = { \ .errno = _errno, \ diff --git a/block/blk.h b/block/blk.h index 3ab2cdd6ed12..507ab34a6e90 100644 --- a/block/blk.h +++ b/block/blk.h @@ -53,6 +53,7 @@ void blk_free_flush_queue(struct blk_flush_queue *q); const char *blk_status_to_str(blk_status_t status); const char *blk_status_to_tag(blk_status_t status); blk_status_t tag_to_blk_status(const char *tag); +enum req_op str_to_blk_op(const char *op); bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic); bool blk_queue_start_drain(struct request_queue *q); From e8dcf2d142bd720c8334233ad6cfdf00f0e76b7f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 11 Jun 2026 16:06:47 +0200 Subject: [PATCH 114/117] block: add configurable error injection Add a new block error injection interface that allows to inject specific status code for specific ranges. Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Hannes Reinecke Reviewed-by: Md Haris Iqbal Link: https://patch.msgid.link/20260611140703.2401204-5-hch@lst.de Signed-off-by: Jens Axboe --- Documentation/block/error-injection.rst | 59 +++++ Documentation/block/index.rst | 1 + block/Kconfig | 8 + block/Makefile | 1 + block/blk-core.c | 4 + block/blk-sysfs.c | 5 + block/error-injection.c | 315 ++++++++++++++++++++++++ block/error-injection.h | 21 ++ block/genhd.c | 4 + include/linux/blkdev.h | 6 + 10 files changed, 424 insertions(+) create mode 100644 Documentation/block/error-injection.rst create mode 100644 block/error-injection.c create mode 100644 block/error-injection.h diff --git a/Documentation/block/error-injection.rst b/Documentation/block/error-injection.rst new file mode 100644 index 000000000000..81f31af82e65 --- /dev/null +++ b/Documentation/block/error-injection.rst @@ -0,0 +1,59 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================ +Configurable Error Injection +============================ + +Overview +-------- + +Configurable error injection allows injecting specific block layer status codes +for sector ranges of a block device. Errors can be injected unconditionally, or +with a given probability. + +To use configurable error injection, CONFIG_BLK_ERROR_INJECTION must be enabled. + +The only interface is the error_injection debugfs file, which is created for +each registered gendisk. Writes to this file are used to create or delete rules +and reads return a list of the current error injection sites. + +Options +------- + +The following options specify the operations: + +=================== ======================================================= +add add a new rule +removeall remove all existing rules +=================== ======================================================= + +The following options specify the details of the rule for the add operation: + +=================== ======================================================= +op= block layer operation this rule applies to. This uses + the XYZ for each REQ_OP_XYZ operation, e.g. READ, WRITE + or DISCARD. Mandatory. +status= Status to return. This uses XYZ for each BLK_STS_XYZ + code, e.g. IOERR or MEDIUM. Mandatory. +start= First block layer sector the rule applies to. + Optional, defaults to 0. +nr_sectors= Number of sectors this rule applies. + Optional, defaults to the remainder of the device. +chance= Only return a failure with a likelihood of 1/chance. + Optional, defaults to 1 (always). +=================== ======================================================= + +Example +------- + +Return BLK_STS_IOERR for one in 10 reads of sector 0 of /dev/nvme0n1: + + $ echo 'add,op=READ,start=0,status=IOERR,chance=10' > /sys/kernel/debug/block/nvme0n1/error_injection + +Return BLK_STS_MEDIUM for every write to /dev/nvme0n1: + + $ echo 'add,op=WRITE,start=0,status=MEDIUM' > /sys/kernel/debug/block/nvme0n1/error_injection + +Remove all rules for /dev/nvme0n1: + + $ echo 'removeall' > /sys/kernel/debug/block/nvme0n1/error_injection diff --git a/Documentation/block/index.rst b/Documentation/block/index.rst index 9fea696f9daa..bfa1bbd31ddf 100644 --- a/Documentation/block/index.rst +++ b/Documentation/block/index.rst @@ -22,3 +22,4 @@ Block switching-sched writeback_cache_control ublk + error-injection diff --git a/block/Kconfig b/block/Kconfig index 15027963472d..70e4a66d941f 100644 --- a/block/Kconfig +++ b/block/Kconfig @@ -221,6 +221,14 @@ config BLOCK_HOLDER_DEPRECATED config BLK_MQ_STACKING bool +config BLK_ERROR_INJECTION + bool "Enable block layer error injection" + select JUMP_LABEL if HAVE_ARCH_JUMP_LABEL + help + Enable inserting arbitrary block errors through a debugfs interface. + + See Documentation/block/error-injection.rst for details. + source "block/Kconfig.iosched" endif # BLOCK diff --git a/block/Makefile b/block/Makefile index 54130faacc21..e7bd320e3d69 100644 --- a/block/Makefile +++ b/block/Makefile @@ -13,6 +13,7 @@ obj-y := bdev.o fops.o bio.o elevator.o blk-core.o blk-sysfs.o \ genhd.o ioprio.o badblocks.o partitions/ blk-rq-qos.o \ disk-events.o blk-ia-ranges.o early-lookup.o +obj-$(CONFIG_BLK_ERROR_INJECTION) += error-injection.o obj-$(CONFIG_BLK_DEV_BSG_COMMON) += bsg.o obj-$(CONFIG_BLK_DEV_BSGLIB) += bsg-lib.o obj-$(CONFIG_BLK_CGROUP) += blk-cgroup.o diff --git a/block/blk-core.c b/block/blk-core.c index beaab7a71fba..73a41df98c9a 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -50,6 +50,7 @@ #include "blk-cgroup.h" #include "blk-throttle.h" #include "blk-ioprio.h" +#include "error-injection.h" struct dentry *blk_debugfs_root; @@ -767,6 +768,9 @@ static void __submit_bio_noacct_mq(struct bio *bio) void submit_bio_noacct_nocheck(struct bio *bio, bool split) { + if (unlikely(blk_error_inject(bio))) + return; + blk_cgroup_bio_start(bio); if (!bio_flagged(bio, BIO_TRACE_COMPLETION)) { diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c index f22c1f253eb3..520972676ab4 100644 --- a/block/blk-sysfs.c +++ b/block/blk-sysfs.c @@ -19,6 +19,7 @@ #include "blk-wbt.h" #include "blk-cgroup.h" #include "blk-throttle.h" +#include "error-injection.h" struct queue_sysfs_entry { struct attribute attr; @@ -933,6 +934,8 @@ static void blk_debugfs_remove(struct gendisk *disk) blk_debugfs_lock_nomemsave(q); blk_trace_shutdown(q); + if (IS_ENABLED(CONFIG_BLK_ERROR_INJECTION)) + blk_error_injection_exit(disk); debugfs_remove_recursive(q->debugfs_dir); q->debugfs_dir = NULL; q->sched_debugfs_dir = NULL; @@ -963,6 +966,8 @@ int blk_register_queue(struct gendisk *disk) memflags = blk_debugfs_lock(q); q->debugfs_dir = debugfs_create_dir(disk->disk_name, blk_debugfs_root); + if (IS_ENABLED(CONFIG_BLK_ERROR_INJECTION)) + blk_error_injection_init(disk); if (queue_is_mq(q)) blk_mq_debugfs_register(q); blk_debugfs_unlock(q, memflags); diff --git a/block/error-injection.c b/block/error-injection.c new file mode 100644 index 000000000000..d24c90e9a25f --- /dev/null +++ b/block/error-injection.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Christoph Hellwig. + */ +#include +#include +#include +#include +#include "blk.h" +#include "error-injection.h" + +struct blk_error_inject { + struct list_head entry; + sector_t start; + sector_t end; + enum req_op op; + blk_status_t status; + + /* only inject every 1 / chance times */ + unsigned int chance; +}; + +DEFINE_STATIC_KEY_FALSE(blk_error_injection_enabled); + +bool __blk_error_inject(struct bio *bio) +{ + struct gendisk *disk = bio->bi_bdev->bd_disk; + struct blk_error_inject *inj; + + rcu_read_lock(); + list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) { + if (bio_op(bio) != inj->op) + continue; + /* + * This never matches 0-sized bios like empty WRITEs with + * REQ_PREFLUSH or ZONE_RESET_ALL. While adding a special case + * for them would be trivial, that means any WRITE rule would + * trigger for flushes. So before we can make this work + * properly, we'll need to start using REQ_OP_FLUSH for pure + * flushes at the bio level like we already do in blk-mq. + */ + if (bio->bi_iter.bi_sector > inj->end || + bio_end_sector(bio) <= inj->start) + continue; + if (inj->chance > 1 && (get_random_u32() % inj->chance) != 0) + continue; + + pr_info_ratelimited("%pg: injecting %s error for %s at sector %llu:%u\n", + disk->part0, blk_status_to_str(inj->status), + blk_op_str(inj->op), bio->bi_iter.bi_sector, + bio_sectors(bio)); + bio->bi_status = inj->status; + rcu_read_unlock(); + bio_endio(bio); + return true; + } + rcu_read_unlock(); + return false; +} + +static int error_inject_add(struct gendisk *disk, enum req_op op, + sector_t start, u64 nr_sectors, blk_status_t status, + unsigned int chance) +{ + struct blk_error_inject *inj; + int error = -EINVAL; + + if (op == REQ_OP_LAST) + return -EINVAL; + if (status == BLK_STS_OK) + return -EINVAL; + + inj = kzalloc_obj(*inj); + if (!inj) + return -ENOMEM; + + if (nr_sectors) { + if (U64_MAX - nr_sectors < start) + goto out_free_inj; + inj->end = start + nr_sectors - 1; + } else { + inj->end = U64_MAX; + } + + inj->op = op; + inj->start = start; + inj->status = status; + inj->chance = chance; + + pr_debug_ratelimited("%pg: adding %s injection for %s at sector %llu:%llu\n", + disk->part0, blk_status_to_str(status), + blk_op_str(op), + start, nr_sectors); + + /* + * Add to the front of the list so that newer entries can partially + * override other entries. This also intentionally allows duplicate + * entries as there is no real reason to reject them. + */ + mutex_lock(&disk->error_injection_lock); + if (!disk_live(disk)) { + mutex_unlock(&disk->error_injection_lock); + error = -ENODEV; + goto out_free_inj; + } + if (list_empty(&disk->error_injection_list)) + static_branch_inc(&blk_error_injection_enabled); + list_add_rcu(&inj->entry, &disk->error_injection_list); + set_bit(GD_ERROR_INJECT, &disk->state); + mutex_unlock(&disk->error_injection_lock); + return 0; + +out_free_inj: + kfree(inj); + return error; +} + +static void error_inject_removeall(struct gendisk *disk) +{ + struct blk_error_inject *inj; + + mutex_lock(&disk->error_injection_lock); + clear_bit(GD_ERROR_INJECT, &disk->state); + while ((inj = list_first_entry_or_null(&disk->error_injection_list, + struct blk_error_inject, entry))) { + list_del_rcu(&inj->entry); + kfree_rcu_mightsleep(inj); + } + static_branch_dec(&blk_error_injection_enabled); + mutex_unlock(&disk->error_injection_lock); +} + +enum options { + Opt_add = (1u << 0), + Opt_removeall = (1u << 1), + + Opt_op = (1u << 16), + Opt_start = (1u << 17), + Opt_nr_sectors = (1u << 18), + Opt_status = (1u << 19), + Opt_chance = (1u << 20), + + Opt_invalid, +}; + +static const match_table_t opt_tokens = { + { Opt_add, "add", }, + { Opt_removeall, "removeall", }, + { Opt_op, "op=%s", }, + { Opt_start, "start=%u" }, + { Opt_nr_sectors, "nr_sectors=%u" }, + { Opt_status, "status=%s" }, + { Opt_chance, "chance=%u" }, + { Opt_invalid, NULL, }, +}; + +static int match_op(substring_t *args, enum req_op *op) +{ + const char *tag; + + tag = match_strdup(args); + if (!tag) + return -ENOMEM; + *op = str_to_blk_op(tag); + if (*op == REQ_OP_LAST) + pr_warn("invalid op '%s'\n", tag); + kfree(tag); + return 0; +} + +static int match_status(substring_t *args, blk_status_t *status) +{ + const char *tag; + + tag = match_strdup(args); + if (!tag) + return -ENOMEM; + *status = tag_to_blk_status(tag); + if (!*status) + pr_warn("invalid status '%s'\n", tag); + kfree(tag); + return 0; +} + +static ssize_t blk_error_injection_parse_options(struct gendisk *disk, + char *options) +{ + enum { Unset, Add, Removeall } action = Unset; + unsigned int option_mask = 0, chance = 1; + enum req_op op = REQ_OP_LAST; + u64 start = 0, nr_sectors = 0; + blk_status_t status = BLK_STS_OK; + substring_t args[MAX_OPT_ARGS]; + char *p; + + while ((p = strsep(&options, ",\n")) != NULL) { + int error = 0; + ssize_t token; + + if (!*p) + continue; + token = match_token(p, opt_tokens, args); + option_mask |= token; + switch (token) { + case Opt_add: + if (action != Unset) + return -EINVAL; + action = Add; + break; + case Opt_removeall: + if (action != Unset) + return -EINVAL; + action = Removeall; + break; + case Opt_op: + error = match_op(args, &op); + break; + case Opt_start: + error = match_u64(args, &start); + break; + case Opt_nr_sectors: + error = match_u64(args, &nr_sectors); + break; + case Opt_status: + error = match_status(args, &status); + break; + case Opt_chance: + error = match_uint(args, &chance); + if (!error && chance == 0) + error = -EINVAL; + break; + default: + pr_warn("unknown parameter or missing value '%s'\n", p); + error = -EINVAL; + } + if (error) + return error; + } + + switch (action) { + case Add: + return error_inject_add(disk, op, start, nr_sectors, status, + chance); + case Removeall: + if (option_mask & ~Opt_removeall) + return -EINVAL; + error_inject_removeall(disk); + return 0; + default: + return -EINVAL; + } +} + +static ssize_t blk_error_injection_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *pos) +{ + struct gendisk *disk = file_inode(file)->i_private; + char *options; + int error; + + options = memdup_user_nul(ubuf, count); + if (IS_ERR(options)) + return PTR_ERR(options); + error = blk_error_injection_parse_options(disk, options); + kfree(options); + + if (error) + return error; + return count; +} + +static int blk_error_injection_show(struct seq_file *s, void *private) +{ + struct gendisk *disk = s->private; + struct blk_error_inject *inj; + + rcu_read_lock(); + list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) { + seq_printf(s, "%llu:%llu status=%s,chance=%u", + inj->start, inj->end, + blk_status_to_tag(inj->status), inj->chance); + seq_putc(s, '\n'); + } + rcu_read_unlock(); + return 0; +} + +static int blk_error_injection_open(struct inode *inode, struct file *file) +{ + return single_open(file, blk_error_injection_show, inode->i_private); +} + +static int blk_error_injection_release(struct inode *inode, struct file *file) +{ + return single_release(inode, file); +} + +static const struct file_operations blk_error_injection_fops = { + .owner = THIS_MODULE, + .write = blk_error_injection_write, + .read = seq_read, + .open = blk_error_injection_open, + .release = blk_error_injection_release, +}; + +void blk_error_injection_init(struct gendisk *disk) +{ + debugfs_create_file("error_injection", 0600, disk->queue->debugfs_dir, + disk, &blk_error_injection_fops); +} + +void blk_error_injection_exit(struct gendisk *disk) +{ + error_inject_removeall(disk); +} diff --git a/block/error-injection.h b/block/error-injection.h new file mode 100644 index 000000000000..9821d773abab --- /dev/null +++ b/block/error-injection.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BLK_ERROR_INJECTION_H +#define _BLK_ERROR_INJECTION_H 1 + +#include + +DECLARE_STATIC_KEY_FALSE(blk_error_injection_enabled); + +void blk_error_injection_init(struct gendisk *disk); +void blk_error_injection_exit(struct gendisk *disk); +bool __blk_error_inject(struct bio *bio); +static inline bool blk_error_inject(struct bio *bio) +{ + if (IS_ENABLED(CONFIG_BLK_ERROR_INJECTION) && + static_branch_unlikely(&blk_error_injection_enabled) && + test_bit(GD_ERROR_INJECT, &bio->bi_bdev->bd_disk->state)) + return __blk_error_inject(bio); + return false; +} + +#endif /* _BLK_ERROR_INJECTION_H */ diff --git a/block/genhd.c b/block/genhd.c index 7d6854fd28e9..f84b6a355b57 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -1485,6 +1485,10 @@ struct gendisk *__alloc_disk_node(struct request_queue *q, int node_id, lockdep_init_map(&disk->lockdep_map, "(bio completion)", lkclass, 0); #ifdef CONFIG_BLOCK_HOLDER_DEPRECATED INIT_LIST_HEAD(&disk->slave_bdevs); +#endif +#ifdef CONFIG_BLK_ERROR_INJECTION + mutex_init(&disk->error_injection_lock); + INIT_LIST_HEAD(&disk->error_injection_list); #endif mutex_init(&disk->rqos_state_mutex); kobject_init(&disk->queue_kobj, &blk_queue_ktype); diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 57e84d59a642..5070851cf924 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -176,6 +176,7 @@ struct gendisk { #define GD_SUPPRESS_PART_SCAN 5 #define GD_OWNS_QUEUE 6 #define GD_ZONE_APPEND_USED 7 +#define GD_ERROR_INJECT 8 struct mutex open_mutex; /* open/close mutex */ unsigned open_partitions; /* number of open partitions */ @@ -227,6 +228,11 @@ struct gendisk { */ struct blk_independent_access_ranges *ia_ranges; +#ifdef CONFIG_BLK_ERROR_INJECTION + struct mutex error_injection_lock; + struct list_head error_injection_list; +#endif + struct mutex rqos_state_mutex; /* rqos state change mutex */ }; From d532cddb6c6049ced414d64d83c6ce7149a6421a Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 13 Jun 2026 04:26:19 +0000 Subject: [PATCH 115/117] nbd: Reclassify sockets to avoid lockdep circular dependency syzbot reported a possible circular locking dependency in udp_sendmsg() where fs_reclaim can be triggered while holding sk_lock, and fs_reclaim can eventually depend on another sk_lock (e.g., if NBD is used for swap or writeback and NBD uses TLS/TCP which acquires sk_lock). Since the UDP socket and the NBD TCP/TLS socket are different, this is a false positive. Fix this by reclassifying NBD sockets to a separate lock class when they are added to the NBD device. This is similar to what nvme-tcp and other network block devices do. Fixes: ffa1e7ada456 ("block: Make request_queue lockdep splats show up earlier") Reported-by: syzbot+607cdcf978b3e79da878@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a2cdafe.428ffe26.258b27.0161.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260613042619.1108126-1-edumazet@google.com Signed-off-by: Jens Axboe --- drivers/block/nbd.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c index e2fe9e3308fc..3a585a0c882a 100644 --- a/drivers/block/nbd.c +++ b/drivers/block/nbd.c @@ -1238,6 +1238,42 @@ static struct socket *nbd_get_socket(struct nbd_device *nbd, unsigned long fd, return sock; } +#ifdef CONFIG_DEBUG_LOCK_ALLOC +static struct lock_class_key nbd_key[3]; +static struct lock_class_key nbd_slock_key[3]; + +static void nbd_reclassify_socket(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (WARN_ON_ONCE(!sock_allow_reclassification(sk))) + return; + + switch (sk->sk_family) { + case AF_INET: + sock_lock_init_class_and_name(sk, "slock-AF_INET-NBD", + &nbd_slock_key[0], + "sk_lock-AF_INET-NBD", + &nbd_key[0]); + break; + case AF_INET6: + sock_lock_init_class_and_name(sk, "slock-AF_INET6-NBD", + &nbd_slock_key[1], + "sk_lock-AF_INET6-NBD", + &nbd_key[1]); + break; + case AF_UNIX: + sock_lock_init_class_and_name(sk, "slock-AF_UNIX-NBD", + &nbd_slock_key[2], + "sk_lock-AF_UNIX-NBD", + &nbd_key[2]); + break; + } +} +#else +static inline void nbd_reclassify_socket(struct socket *sock) {} +#endif + static int nbd_add_socket(struct nbd_device *nbd, unsigned long arg, bool netlink) { @@ -1254,6 +1290,7 @@ static int nbd_add_socket(struct nbd_device *nbd, unsigned long arg, sock = nbd_get_socket(nbd, arg, &err); if (!sock) return err; + nbd_reclassify_socket(sock); /* * We need to make sure we don't get any errant requests while we're From 9b0c3673c88588d613d8f09f5931b2b466c6a83d Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Fri, 12 Jun 2026 15:32:04 -0700 Subject: [PATCH 116/117] block: check bio split for unaligned bvec Offsets and lengths need to be validated against the dma alignment. This check was skipped for sufficiently a small bio with a single bvec, which may allow an invalid request dispatched to the driver. Force the validation for an unaligned bvec by forcing the bio split path that handles this condition. Fixes: 7eac33186957 ("iomap: simplify direct io validity check") Fixes: 5ff3f74e145a ("block: simplify direct io validity check") Reported-by: Carlos Maiolino Signed-off-by: Keith Busch Link: https://patch.msgid.link/20260612223205.465913-1-kbusch@meta.com Signed-off-by: Jens Axboe --- block/blk.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/blk.h b/block/blk.h index 507ab34a6e90..25af8ac5ef0f 100644 --- a/block/blk.h +++ b/block/blk.h @@ -408,6 +408,8 @@ static inline bool bio_may_need_split(struct bio *bio, bv = __bvec_iter_bvec(bio->bi_io_vec, bio->bi_iter); if (bio->bi_iter.bi_size > bv->bv_len - bio->bi_iter.bi_bvec_done) return true; + if ((bv->bv_offset | bv->bv_len) & lim->dma_alignment) + return true; return bv->bv_len + bv->bv_offset > lim->max_fast_segment_size; } From c7c76f9232bd34835d821f14abdc5fafc17bc938 Mon Sep 17 00:00:00 2001 From: Coly Li Date: Sat, 13 Jun 2026 23:04:58 +0800 Subject: [PATCH 117/117] MAINTAINERS: Update Coly Li's email address I switch to colyli@fygo.io as my current email address. Signed-off-by: Coly Li Link: https://patch.msgid.link/20260613150458.682707-1-colyli@fygo.io Signed-off-by: Jens Axboe --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 31933ff30048..1754ed7107d1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4440,7 +4440,7 @@ F: include/uapi/linux/batman_adv.h F: net/batman-adv/ BCACHE (BLOCK LAYER CACHE) -M: Coly Li +M: Coly Li M: Kent Overstreet L: linux-bcache@vger.kernel.org S: Maintained