From 57c10915f2c16c90e0d46ad00876bf39ece40fc2 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sun, 14 Jun 2026 00:36:11 -0500 Subject: [PATCH 01/25] Input: synaptics-rmi4 - bound the F3A keymap to the GPIO count rmi_f3a_initialize() takes the GPIO count from the device query register (f3a->gpio_count = buf & RMI_F3A_GPIO_COUNT, range 0..127). rmi_f3a_map_gpios() then allocates gpio_key_map with min(gpio_count, TRACKSTICK_RANGE_END) == at most 6 entries, but rmi_f3a_attention() iterates the full gpio_count and dereferences gpio_key_map[i], and input->keycodemax is set to the full gpio_count while input->keycode points at the 6-entry allocation. A device that reports gpio_count > 6 therefore causes an out-of-bounds read of gpio_key_map[] on every attention interrupt, and out-of-bounds accesses through the input core's default keymap ioctls: EVIOCGKEYCODE reads past the buffer (leaking adjacent slab memory to user space) and EVIOCSKEYCODE writes a caller-controlled value past it, for any process able to open the evdev node, since input_default_getkeycode() and input_default_setkeycode() only bound the index against keycodemax. Size the keymap for the full gpio_count. The mapping loop is unchanged: it still assigns only the first min(gpio_count, TRACKSTICK_RANGE_END) entries; the remaining slots stay KEY_RESERVED (devm_kcalloc zero-fills) and are skipped when reporting. Fixes: 9e4c596bfd00 ("Input: synaptics-rmi4 - add support for F3A") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260614-b4-disp-818d6bda-v1-1-cf39a3615085@proton.me Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f3a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_f3a.c b/drivers/input/rmi4/rmi_f3a.c index 0e8baed84dbb5a..a0777644eef078 100644 --- a/drivers/input/rmi4/rmi_f3a.c +++ b/drivers/input/rmi4/rmi_f3a.c @@ -132,7 +132,7 @@ static int rmi_f3a_map_gpios(struct rmi_function *fn, struct f3a_data *f3a, int button_count = min_t(u8, f3a->gpio_count, TRACKSTICK_RANGE_END); f3a->gpio_key_map = devm_kcalloc(&fn->dev, - button_count, + f3a->gpio_count, sizeof(f3a->gpio_key_map[0]), GFP_KERNEL); if (!f3a->gpio_key_map) { From d577e46785d45484b2ab7e7309c49b18764bf56c Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sun, 14 Jun 2026 00:36:12 -0500 Subject: [PATCH 02/25] Input: synaptics-rmi4 - bound the F30 keymap to the GPIO/LED count rmi_f30_map_gpios() allocates gpioled_key_map with min(gpioled_count, TRACKSTICK_RANGE_END) == at most 6 entries, but rmi_f30_attention() iterates the full f30->gpioled_count (device query register, range 0..31) and dereferences gpioled_key_map[i], and input->keycodemax is set to the full gpioled_count while input->keycode points at the 6-entry allocation. A device that reports gpioled_count > 6 with GPIO support enabled therefore causes an out-of-bounds read on the attention interrupt and out-of-bounds read/write through the EVIOCGKEYCODE/EVIOCSKEYCODE ioctls, which bound the index only against keycodemax. This is the same defect as the F3A handler, which was copied from F30. Size the keymap for the full gpioled_count; the mapping loop still assigns only the first min(gpioled_count, TRACKSTICK_RANGE_END) entries. Fixes: 3e64fcbdbd10 ("Input: synaptics-rmi4 - limit the range of what GPIOs are buttons") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260614-b4-disp-818d6bda-v1-2-cf39a3615085@proton.me Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f30.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c index 35045f161dc2f9..b2155c8e20e756 100644 --- a/drivers/input/rmi4/rmi_f30.c +++ b/drivers/input/rmi4/rmi_f30.c @@ -233,7 +233,7 @@ static int rmi_f30_map_gpios(struct rmi_function *fn, int button_count = min_t(u8, f30->gpioled_count, TRACKSTICK_RANGE_END); f30->gpioled_key_map = devm_kcalloc(&fn->dev, - button_count, + f30->gpioled_count, sizeof(f30->gpioled_key_map[0]), GFP_KERNEL); if (!f30->gpioled_key_map) { From 4fc0625cf9c6c11f1f9b09e1f2e35fa2dd46ea6a Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sun, 8 Feb 2026 08:49:00 -0800 Subject: [PATCH 03/25] ntb_hw_amd: Fix incorrect debug message in link disable path amd_ntb_link_disable() prints "Enabling Link" which is misleading. Update the message to reflect that the link is being disabled. Signed-off-by: Alok Tiwari Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/amd/ntb_hw_amd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ntb/hw/amd/ntb_hw_amd.c b/drivers/ntb/hw/amd/ntb_hw_amd.c index 1a163596ddf54e..df9150669ad3a4 100644 --- a/drivers/ntb/hw/amd/ntb_hw_amd.c +++ b/drivers/ntb/hw/amd/ntb_hw_amd.c @@ -376,7 +376,7 @@ static int amd_ntb_link_disable(struct ntb_dev *ntb) if (ndev->ntb.topo == NTB_TOPO_SEC) return -EINVAL; - dev_dbg(&ntb->pdev->dev, "Enabling Link.\n"); + dev_dbg(&ntb->pdev->dev, "Disabling Link.\n"); return 0; } From d876153680e3d721d385e554def919bce3d18c74 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 4 Mar 2026 11:05:27 +0900 Subject: [PATCH 04/25] NTB: epf: Avoid pci_iounmap() with offset when PEER_SPAD and CONFIG share BAR When BAR_PEER_SPAD and BAR_CONFIG share one PCI BAR, the module teardown path ends up calling pci_iounmap() on the same iomem with some offset, which is unnecessary and triggers a kernel warning like the following: Trying to vunmap() nonexistent vm area (0000000069a5ffe8) WARNING: mm/vmalloc.c:3470 at vunmap+0x58/0x68, CPU#5: modprobe/2937 [...] Call trace: vunmap+0x58/0x68 (P) iounmap+0x34/0x48 pci_iounmap+0x2c/0x40 ntb_epf_pci_remove+0x44/0x80 [ntb_hw_epf] pci_device_remove+0x48/0xf8 device_remove+0x50/0x88 device_release_driver_internal+0x1c8/0x228 driver_detach+0x50/0xb0 bus_remove_driver+0x74/0x100 driver_unregister+0x34/0x68 pci_unregister_driver+0x34/0xa0 ntb_epf_pci_driver_exit+0x14/0xfe0 [ntb_hw_epf] [...] Fix it by unmapping only when PEER_SPAD and CONFIG use difference bars. Cc: stable@vger.kernel.org Fixes: e75d5ae8ab88 ("NTB: epf: Allow more flexibility in the memory BAR map method") Reviewed-by: Frank Li Signed-off-by: Koichiro Den Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/epf/ntb_hw_epf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index d3ecf25a516253..9935da48a52ea8 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -646,7 +646,8 @@ static void ntb_epf_deinit_pci(struct ntb_epf_dev *ndev) struct pci_dev *pdev = ndev->ntb.pdev; pci_iounmap(pdev, ndev->ctrl_reg); - pci_iounmap(pdev, ndev->peer_spad_reg); + if (ndev->barno_map[BAR_PEER_SPAD] != ndev->barno_map[BAR_CONFIG]) + pci_iounmap(pdev, ndev->peer_spad_reg); pci_iounmap(pdev, ndev->db_reg); pci_release_regions(pdev); From 8df969463bc26a7250707f485ee3ac61426d671f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 11 Mar 2026 22:14:15 -0700 Subject: [PATCH 05/25] NTB: fix kernel-doc warnings in ntb.h Correct a function name and function parameter name to avoid kernel-doc warnings: Warning: include/linux/ntb.h:575 expecting prototype for ntb_default_port_count(). Prototype was for ntb_default_peer_port_count() instead Warning: include/linux/ntb.h:590 function parameter 'pidx' not described in 'ntb_default_peer_port_number' Signed-off-by: Randy Dunlap Acked-by: Dave Jiang Signed-off-by: Jon Mason --- include/linux/ntb.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ntb.h b/include/linux/ntb.h index 879c3e89e026ff..ae72caa0385972 100644 --- a/include/linux/ntb.h +++ b/include/linux/ntb.h @@ -567,7 +567,7 @@ void ntb_msg_event(struct ntb_dev *ntb); int ntb_default_port_number(struct ntb_dev *ntb); /** - * ntb_default_port_count() - get the default number of peer device ports + * ntb_default_peer_port_count() - get the default number of peer device ports * @ntb: NTB device context. * * By default hardware driver supports just one peer device. @@ -582,7 +582,7 @@ int ntb_default_peer_port_count(struct ntb_dev *ntb); /** * ntb_default_peer_port_number() - get the default peer port by given index * @ntb: NTB device context. - * @idx: Peer port index (should not differ from zero). + * @pidx: Peer port index (should not differ from zero). * * By default hardware driver supports just one peer device, so this method * shall return the corresponding value from enum ntb_default_port. From d1c3d45f87e89e5c1fa0769a72a48d1ad99106fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 7 May 2026 09:41:01 +0200 Subject: [PATCH 06/25] ntb: amd: Use named initializer for pci_device_id::driver_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current list initialisation depends on the well hidden two zeros in the PCI_VDEVICE macro. Instead use a named initialisation that is more robust and easier to understand. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Dave Jiang Signed-off-by: Jon Mason --- drivers/ntb/hw/amd/ntb_hw_amd.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/ntb/hw/amd/ntb_hw_amd.c b/drivers/ntb/hw/amd/ntb_hw_amd.c index df9150669ad3a4..36bee0d8ac6cd0 100644 --- a/drivers/ntb/hw/amd/ntb_hw_amd.c +++ b/drivers/ntb/hw/amd/ntb_hw_amd.c @@ -1328,15 +1328,15 @@ static const struct ntb_dev_data dev_data[] = { }; static const struct pci_device_id amd_ntb_pci_tbl[] = { - { PCI_VDEVICE(AMD, 0x145b), (kernel_ulong_t)&dev_data[0] }, - { PCI_VDEVICE(AMD, 0x148b), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x14c0), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x14c3), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x155a), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x17d4), (kernel_ulong_t)&dev_data[1] }, - { PCI_VDEVICE(AMD, 0x17d7), (kernel_ulong_t)&dev_data[2] }, - { PCI_VDEVICE(HYGON, 0x145b), (kernel_ulong_t)&dev_data[0] }, - { 0, } + { PCI_VDEVICE(AMD, 0x145b), .driver_data = (kernel_ulong_t)&dev_data[0] }, + { PCI_VDEVICE(AMD, 0x148b), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x14c0), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x14c3), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x155a), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x17d4), .driver_data = (kernel_ulong_t)&dev_data[1] }, + { PCI_VDEVICE(AMD, 0x17d7), .driver_data = (kernel_ulong_t)&dev_data[2] }, + { PCI_VDEVICE(HYGON, 0x145b), .driver_data = (kernel_ulong_t)&dev_data[0] }, + { } }; MODULE_DEVICE_TABLE(pci, amd_ntb_pci_tbl); From d85589879f19ad8514c508709865f064be761df5 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 26 Jun 2026 17:42:10 -0700 Subject: [PATCH 07/25] Revert "Input: rmi4 - fix register descriptor address calculation" The register descriptor presence register is a packet register, which means its bytes share a single RMI address. It does not occupy consecutive addresses, and the register structure that follows it is located at the next RMI address (presence_address + 1), not (presence_address + presence_size). Revert the incorrect address calculation introduced in commit a98518e72439. Reported-by: "Barry K. Nathan" Tested-by: "Barry K. Nathan" Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index 49a59da6a84111..a28eef1b765e53 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -643,7 +643,7 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, ret = rmi_read_block(d, addr, buf, size_presence_reg); if (ret) return ret; - addr += size_presence_reg; + ++addr; if (buf[0] == 0) { if (size_presence_reg < 3) From 2d6d33e45dd4fb768758d5f6e747deadcd66b9fc Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 26 Jun 2026 16:33:21 -0700 Subject: [PATCH 08/25] Input: rmi4 - tolerate short register descriptor structure Some touchpads (e.g. ThinkPad T14 Gen 1) have buggy firmware that reports a register descriptor structure size that is too small for the number of registers it claims to have in the presence map. The remaining bytes in the structure are 0, which with the new strict bounds checking causes the parser to fail with -EIO, aborting the device probe. Tolerate such short reads by dropping the remaining (unparseable or 0-size) registers from the list instead of failing the probe, preventing the driver from trying to use them. Fixes: 0adb483fbf2d ("Input: rmi4 - refactor register descriptor parsing") Reported-by: Barry K. Nathan Tested-by: Barry K. Nathan Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_driver.c | 37 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index a28eef1b765e53..5d49a9021c7d05 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -616,8 +616,8 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, unsigned int presence_offset; unsigned int map_offset; unsigned int offset; + unsigned int num_registers; unsigned int reg; - int i; int b; int ret; @@ -657,7 +657,7 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, memset(presence_map, 0, sizeof(presence_map)); map_offset = 0; - for (i = presence_offset; i < size_presence_reg; i++) { + for (int i = presence_offset; i < size_presence_reg; i++) { for (b = 0; b < 8; b++) { if (buf[i] & BIT(b)) { if (map_offset >= RMI_REG_DESC_PRESENCE_BITS) @@ -697,28 +697,41 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr, if (ret) return ret; - reg = find_first_bit(presence_map, RMI_REG_DESC_PRESENCE_BITS); offset = 0; - for (i = 0; i < rdesc->num_registers; i++) { - struct rmi_register_desc_item *item = &rdesc->registers[i]; + num_registers = 0; + for_each_set_bit(reg, presence_map, RMI_REG_DESC_PRESENCE_BITS) { + struct rmi_register_desc_item *item = &rdesc->registers[num_registers]; int item_size; + if (offset >= rdesc->struct_size) + break; + item_size = rmi_parse_register_desc_item(item, &struct_buf[offset], rdesc->struct_size - offset); - if (item_size < 0) - return item_size; + if (item_size < 0) { + dev_warn(&d->dev, + "%s: Failed to parse register %d descriptor, ignoring it\n", + __func__, reg); + break; + } item->reg = reg; offset += item_size; - rmi_dbg(RMI_DEBUG_CORE, &d->dev, - "%s: reg: %d reg size: %u subpackets: %d\n", __func__, - item->reg, item->reg_size, item->num_subpackets); + if (item->reg_size == 0) { + dev_warn(&d->dev, + "%s: Register %d has 0 size, ignoring it\n", + __func__, item->reg); + } else { + rmi_dbg(RMI_DEBUG_CORE, &d->dev, + "%s: reg: %d reg size: %u subpackets: %d\n", __func__, + item->reg, item->reg_size, item->num_subpackets); - reg = find_next_bit(presence_map, - RMI_REG_DESC_PRESENCE_BITS, reg + 1); + num_registers++; + } } + rdesc->num_registers = num_registers; return 0; } From d86d4f8cbb5a55a3b9b86f7b5ab8c4cdda600a3f Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Wed, 24 Jun 2026 17:47:39 +0800 Subject: [PATCH 09/25] Input: gscps2 - advance receive buffer write index Commit 44f920069911 ("Input: gscps2 - use guard notation when acquiring spinlock") moved the receive loop into gscps2_read_data() and gscps2_report_data(). While moving the code, it preserved the writes to buffer[ps2port->append], but omitted the following producer index update from the original loop: ps2port->append = (ps2port->append + 1) & BUFFER_SIZE; As a result, append never advances. Since gscps2_report_data() only reports bytes while act != append, the receive buffer always appears empty and no keyboard or mouse data reaches the serio core. Restore the omitted index update. Fixes: 44f920069911 ("Input: gscps2 - use guard notation when acquiring spinlock") Cc: stable@vger.kernel.org # 6.13+ Signed-off-by: Xu Rao Link: https://patch.msgid.link/460B5655BA580C60+20260624094739.850306-1-raoxu@uniontech.com Signed-off-by: Dmitry Torokhov --- drivers/input/serio/gscps2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c index 22b2f57fd91f5a..bf9b993f573308 100644 --- a/drivers/input/serio/gscps2.c +++ b/drivers/input/serio/gscps2.c @@ -219,6 +219,7 @@ static void gscps2_read_data(struct gscps2port *ps2port) ps2port->buffer[ps2port->append].str = status; ps2port->buffer[ps2port->append].data = gscps2_readb_input(ps2port->addr); + ps2port->append = (ps2port->append + 1) & BUFFER_SIZE; } while (true); } From dc59e4fea9d83f03bad6bddf3fa2e52491777482 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 28 Jun 2026 12:01:31 -0700 Subject: [PATCH 10/25] Linux 7.2-rc1 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 12fd3d2d47b582..b9c5792c79e016 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 7 -PATCHLEVEL = 1 +PATCHLEVEL = 2 SUBLEVEL = 0 -EXTRAVERSION = +EXTRAVERSION = -rc1 NAME = Baby Opossum Posse # *DOCUMENTATION* From 9bd6ac16e6e53d8d5c7548d04e3017a244c22720 Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 30 Jun 2026 14:29:25 -0400 Subject: [PATCH 11/25] Revert "scsi: qla2xxx: Sanitize payload size to prevent member overflow" This reverts commit 19bc5f2a6962dfaa0e32d0e0bc2271993d85d414. So we have been seeing failures in our internal qla2xxx FPIN testing for a while. These failures are due to this commit and has been reported internally at Red Hat. Recently Sustaining Engineering asked us to put in a patch to resolve this issue. The fix is to rever the patch: commit 19bc5f2a6962dfaa0e32d0e0bc2271993d85d414 Author: Jiasheng Jiang Date: Tue Jan 6 20:53:44 2026 +0000 scsi: qla2xxx: Sanitize payload size to prevent member overflow Unfortunately this fix does not work. Moveover, truncating the received FPIN data seemed like a bad idea in general. There was a previous attempt to fix this, but it did not work right either: commit 6f4b10226b6b1e7d1ff3cdb006cf0f6da6eed71e Author: Gustavo A. R. Silva Date: Wed Aug 13 16:07:44 2025 -0400 scsi: qla2xxx: Fix memcpy() field-spanning write issue So John reverted it: commit 285654d58a747eb431ee538c803c077d90525674 Author: John Meneghini Date: Thu Sep 25 09:07:29 2025 -0400 Revert "scsi: qla2xxx: Fix memcpy() field-spanning write issue" Signed-off-by: John Meneghini Reviewed-by: Ewan D. Milne Reviewed-by: David Jeffery --- drivers/scsi/qla2xxx/qla_isr.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index 33776330956c93..7e67e0ce3cbd25 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -878,9 +878,6 @@ qla27xx_copy_multiple_pkt(struct scsi_qla_host *vha, void **pkt, payload_size = sizeof(purex->els_frame_payload); } - if (total_bytes > sizeof(item->iocb.iocb)) - total_bytes = sizeof(item->iocb.iocb); - pending_bytes = total_bytes; no_bytes = (pending_bytes > payload_size) ? payload_size : pending_bytes; @@ -1166,10 +1163,6 @@ qla27xx_copy_fpin_pkt(struct scsi_qla_host *vha, void **pkt, total_bytes = (le16_to_cpu(purex->frame_size) & 0x0FFF) - PURX_ELS_HEADER_SIZE; - - if (total_bytes > sizeof(item->iocb.iocb)) - total_bytes = sizeof(item->iocb.iocb); - pending_bytes = total_bytes; entry_count = entry_count_remaining = purex->entry_count; no_bytes = (pending_bytes > sizeof(purex->els_frame_payload)) ? From 74b86d95f6101fc67b9713390779f59c4cbd206e Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 23 Dec 2025 20:18:14 -0500 Subject: [PATCH 12/25] scsi: lpfc/qla2xxx: makefile change - do not commit Signed-off-by: John Meneghini --- drivers/scsi/lpfc/Makefile | 1 + drivers/scsi/qla2xxx/Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/scsi/lpfc/Makefile b/drivers/scsi/lpfc/Makefile index bbd1faf41e8065..04a8b17c0091f0 100644 --- a/drivers/scsi/lpfc/Makefile +++ b/drivers/scsi/lpfc/Makefile @@ -24,6 +24,7 @@ ccflags-$(GCOV) := -fprofile-arcs -ftest-coverage ccflags-$(GCOV) += -O0 +ccflags-y += -Wflex-array-member-not-at-end ifdef WARNINGS_BECOME_ERRORS ccflags-y += -Werror endif diff --git a/drivers/scsi/qla2xxx/Makefile b/drivers/scsi/qla2xxx/Makefile index cbc1303e761e43..b52cf96943f01b 100644 --- a/drivers/scsi/qla2xxx/Makefile +++ b/drivers/scsi/qla2xxx/Makefile @@ -6,3 +6,4 @@ qla2xxx-y := qla_os.o qla_init.o qla_mbx.o qla_iocb.o qla_isr.o qla_gs.o \ obj-$(CONFIG_SCSI_QLA_FC) += qla2xxx.o obj-$(CONFIG_TCM_QLA2XXX) += tcm_qla2xxx.o +ccflags-y += -Wflex-array-member-not-at-end From f320dce7a664f986d9f0eea82b312d120443753d Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Fri, 10 Jul 2026 17:10:18 -0400 Subject: [PATCH 13/25] scsi: qla2xxx: Fix flex array member not at end In qla_edif_bsg.h: `struct fc_bsg_reply` and `struct fc_bsg_request` have flexible array members, thus they must be the last member of the parent structure. Contininging in the effort to add `-Wflex-array-member-not-at-end`, move the structs to the end of the parent structures, `struct qla_bsg_auth_els_reply` and `struct qla_bsg_auth_els_request `. Suggested-by: John Meneghini Signed-off-by: Jesse Taube --- drivers/scsi/qla2xxx/qla_edif_bsg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_edif_bsg.h b/drivers/scsi/qla2xxx/qla_edif_bsg.h index 514c265ba86e2f..bcc35d149c1f11 100644 --- a/drivers/scsi/qla2xxx/qla_edif_bsg.h +++ b/drivers/scsi/qla2xxx/qla_edif_bsg.h @@ -36,16 +36,16 @@ struct extra_auth_els { } __packed; struct qla_bsg_auth_els_request { - struct fc_bsg_request r; struct extra_auth_els e; + struct fc_bsg_request r; }; struct qla_bsg_auth_els_reply { - struct fc_bsg_reply r; uint32_t rx_xchg_address; uint8_t version; uint8_t pad[VND_CMD_PAD_SIZE]; uint8_t reserved[VND_CMD_APP_RESERVED_SIZE]; + struct fc_bsg_reply r; }; struct app_id { From 856eef111e4c484361c332936c35258db56f271d Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Tue, 24 Jun 2025 16:20:15 -0400 Subject: [PATCH 14/25] fc_els: use 'union fc_tlv_desc' Introduce 'union fc_tlv_desc' to have a common structure for all FC ELS TLV structures and avoid type casts. Fixes: 44b6169ada7f ("scsi: fc: Avoid -Wflex-array-member-not-at-end warnings") Commit 44b6169ada7f ("scsi: fc: Avoid -Wflex-array-member-not-at-end warnings") conflicts with this change. To solve the problem I moved the fc_els_rdf_hdr and fc_els_rdf_resp_hdr definitions out of fc_els.h and into lpfc_hw4.h. Cc: Gustavo A. R. Silva Co-developed-by: John Meneghini Signed-off-by: John Meneghini Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney --- drivers/scsi/lpfc/lpfc_els.c | 77 +++++++------- drivers/scsi/lpfc/lpfc_hw4.h | 12 ++- drivers/scsi/scsi_transport_fc.c | 27 ++--- include/uapi/scsi/fc/fc_els.h | 174 ++++++++++++++++--------------- 4 files changed, 146 insertions(+), 144 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_els.c b/drivers/scsi/lpfc/lpfc_els.c index 52fc5058976d99..11143da24b54d7 100644 --- a/drivers/scsi/lpfc/lpfc_els.c +++ b/drivers/scsi/lpfc/lpfc_els.c @@ -3888,7 +3888,7 @@ lpfc_issue_els_rdf(struct lpfc_vport *vport, uint8_t retry) memset(prdf, 0, cmdsize); prdf->rdf.fpin_cmd = ELS_RDF; prdf->rdf.desc_len = cpu_to_be32(sizeof(struct lpfc_els_rdf_req) - - sizeof(struct fc_els_rdf_hdr)); + offsetof(struct fc_els_rdf, desc)); prdf->reg_d1.reg_desc.desc_tag = cpu_to_be32(ELS_DTAG_FPIN_REGISTER); prdf->reg_d1.reg_desc.desc_len = cpu_to_be32( FC_TLV_DESC_LENGTH_FROM_SZ(prdf->reg_d1)); @@ -4075,7 +4075,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, { IOCB_t *irsp_iocb; struct fc_els_edc_resp *edc_rsp; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; struct fc_diag_cg_sig_desc *pcgd; struct fc_diag_lnkflt_desc *plnkflt; struct lpfc_dmabuf *pcmd, *prsp; @@ -4166,7 +4166,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, goto out; } - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_FAULT_CAP: if (bytes_remain < FC_TLV_DESC_SZ_FROM_LENGTH(tlv) || @@ -4181,7 +4181,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, sizeof(struct fc_diag_lnkflt_desc)); goto out; } - plnkflt = (struct fc_diag_lnkflt_desc *)tlv; + plnkflt = &tlv->lnkflt; lpfc_printf_log(phba, KERN_INFO, LOG_ELS | LOG_LDS_EVENT, "4617 Link Fault Desc Data: 0x%08x 0x%08x " @@ -4208,7 +4208,7 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, goto out; } - pcgd = (struct fc_diag_cg_sig_desc *)tlv; + pcgd = &tlv->cg_sig; lpfc_printf_log( phba, KERN_INFO, LOG_ELS | LOG_CGN_MGMT, "4616 CGN Desc Data: 0x%08x 0x%08x " @@ -4263,10 +4263,8 @@ lpfc_cmpl_els_edc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, } static void -lpfc_format_edc_lft_desc(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_format_edc_lft_desc(struct lpfc_hba *phba, struct fc_diag_lnkflt_desc *lft) { - struct fc_diag_lnkflt_desc *lft = (struct fc_diag_lnkflt_desc *)tlv; - lft->desc_tag = cpu_to_be32(ELS_DTAG_LNK_FAULT_CAP); lft->desc_len = cpu_to_be32( FC_TLV_DESC_LENGTH_FROM_SZ(struct fc_diag_lnkflt_desc)); @@ -4279,10 +4277,8 @@ lpfc_format_edc_lft_desc(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) } static void -lpfc_format_edc_cgn_desc(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_format_edc_cgn_desc(struct lpfc_hba *phba, struct fc_diag_cg_sig_desc *cgd) { - struct fc_diag_cg_sig_desc *cgd = (struct fc_diag_cg_sig_desc *)tlv; - /* We are assuming cgd was zero'ed before calling this routine */ /* Configure the congestion detection capability */ @@ -4381,7 +4377,7 @@ lpfc_issue_els_edc(struct lpfc_vport *vport, uint8_t retry) struct lpfc_hba *phba = vport->phba; struct lpfc_iocbq *elsiocb; struct fc_els_edc *edc_req; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; u16 cmdsize; struct lpfc_nodelist *ndlp; u8 *pcmd = NULL; @@ -4420,13 +4416,13 @@ lpfc_issue_els_edc(struct lpfc_vport *vport, uint8_t retry) tlv = edc_req->desc; if (cgn_desc_size) { - lpfc_format_edc_cgn_desc(phba, tlv); + lpfc_format_edc_cgn_desc(phba, &tlv->cg_sig); phba->cgn_sig_freq = lpfc_fabric_cgn_frequency; tlv = fc_tlv_next_desc(tlv); } if (lft_desc_size) - lpfc_format_edc_lft_desc(phba, tlv); + lpfc_format_edc_lft_desc(phba, &tlv->lnkflt); lpfc_printf_vlog(vport, KERN_INFO, LOG_ELS | LOG_CGN_MGMT, "4623 Xmit EDC to remote " @@ -5999,7 +5995,7 @@ lpfc_issue_els_edc_rsp(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, { struct lpfc_hba *phba = vport->phba; struct fc_els_edc_resp *edc_rsp; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; struct lpfc_iocbq *elsiocb; IOCB_t *icmd, *cmd; union lpfc_wqe128 *wqe; @@ -6043,10 +6039,10 @@ lpfc_issue_els_edc_rsp(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, FC_TLV_DESC_LENGTH_FROM_SZ(struct fc_els_lsri_desc)); edc_rsp->lsri.rqst_w0.cmd = ELS_EDC; tlv = edc_rsp->desc; - lpfc_format_edc_cgn_desc(phba, tlv); + lpfc_format_edc_cgn_desc(phba, &tlv->cg_sig); tlv = fc_tlv_next_desc(tlv); if (lft_desc_size) - lpfc_format_edc_lft_desc(phba, tlv); + lpfc_format_edc_lft_desc(phba, &tlv->lnkflt); lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_RSP, "Issue EDC ACC: did:x%x flg:x%lx refcnt %d", @@ -9439,7 +9435,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, { struct lpfc_hba *phba = vport->phba; struct fc_els_edc *edc_req; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; uint8_t *payload; uint32_t *ptr, dtag; const char *dtag_nm; @@ -9482,7 +9478,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, goto out; } - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_FAULT_CAP: if (bytes_remain < FC_TLV_DESC_SZ_FROM_LENGTH(tlv) || @@ -9497,7 +9493,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, sizeof(struct fc_diag_lnkflt_desc)); goto out; } - plnkflt = (struct fc_diag_lnkflt_desc *)tlv; + plnkflt = &tlv->lnkflt; lpfc_printf_log(phba, KERN_INFO, LOG_ELS | LOG_LDS_EVENT, "4626 Link Fault Desc Data: x%08x len x%x " @@ -9534,7 +9530,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, phba->cgn_sig_freq = lpfc_fabric_cgn_frequency; lpfc_least_capable_settings( - phba, (struct fc_diag_cg_sig_desc *)tlv); + phba, &tlv->cg_sig); break; default: dtag_nm = lpfc_get_tlv_dtag_nm(dtag); @@ -10124,14 +10120,13 @@ lpfc_display_fpin_wwpn(struct lpfc_hba *phba, __be64 *wwnlist, u32 cnt) /** * lpfc_els_rcv_fpin_li - Process an FPIN Link Integrity Event. * @phba: Pointer to phba object. - * @tlv: Pointer to the Link Integrity Notification Descriptor. + * @li: Pointer to the Link Integrity Notification Descriptor. * * This function processes a Link Integrity FPIN event by logging a message. **/ static void -lpfc_els_rcv_fpin_li(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_li(struct lpfc_hba *phba, struct fc_fn_li_desc *li) { - struct fc_fn_li_desc *li = (struct fc_fn_li_desc *)tlv; const char *li_evt_str; u32 li_evt, cnt; @@ -10155,14 +10150,13 @@ lpfc_els_rcv_fpin_li(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) /** * lpfc_els_rcv_fpin_del - Process an FPIN Delivery Event. * @phba: Pointer to hba object. - * @tlv: Pointer to the Delivery Notification Descriptor TLV + * @del: Pointer to the Delivery Notification Descriptor TLV * * This function processes a Delivery FPIN event by logging a message. **/ static void -lpfc_els_rcv_fpin_del(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_del(struct lpfc_hba *phba, struct fc_fn_deli_desc *del) { - struct fc_fn_deli_desc *del = (struct fc_fn_deli_desc *)tlv; const char *del_rsn_str; u32 del_rsn; __be32 *frame; @@ -10193,14 +10187,14 @@ lpfc_els_rcv_fpin_del(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) /** * lpfc_els_rcv_fpin_peer_cgn - Process a FPIN Peer Congestion Event. * @phba: Pointer to hba object. - * @tlv: Pointer to the Peer Congestion Notification Descriptor TLV + * @pc: Pointer to the Peer Congestion Notification Descriptor TLV * * This function processes a Peer Congestion FPIN event by logging a message. **/ static void -lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, + struct fc_fn_peer_congn_desc *pc) { - struct fc_fn_peer_congn_desc *pc = (struct fc_fn_peer_congn_desc *)tlv; const char *pc_evt_str; u32 pc_evt, cnt; @@ -10228,7 +10222,7 @@ lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) /** * lpfc_els_rcv_fpin_cgn - Process an FPIN Congestion notification * @phba: Pointer to hba object. - * @tlv: Pointer to the Congestion Notification Descriptor TLV + * @cgn: Pointer to the Congestion Notification Descriptor TLV * * This function processes an FPIN Congestion Notifiction. The notification * could be an Alarm or Warning. This routine feeds that data into driver's @@ -10237,10 +10231,9 @@ lpfc_els_rcv_fpin_peer_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) * to the upper layer or 0 to indicate don't deliver it. **/ static int -lpfc_els_rcv_fpin_cgn(struct lpfc_hba *phba, struct fc_tlv_desc *tlv) +lpfc_els_rcv_fpin_cgn(struct lpfc_hba *phba, struct fc_fn_congn_desc *cgn) { struct lpfc_cgn_info *cp; - struct fc_fn_congn_desc *cgn = (struct fc_fn_congn_desc *)tlv; const char *cgn_evt_str; u32 cgn_evt; const char *cgn_sev_str; @@ -10341,7 +10334,7 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) { struct lpfc_hba *phba = vport->phba; struct fc_els_fpin *fpin = (struct fc_els_fpin *)p; - struct fc_tlv_desc *tlv, *first_tlv, *current_tlv; + union fc_tlv_desc *tlv, *first_tlv, *current_tlv; const char *dtag_nm; int desc_cnt = 0, bytes_remain, cnt; u32 dtag, deliver = 0; @@ -10366,7 +10359,7 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) return; } - tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0]; + tlv = &fpin->fpin_desc[0]; first_tlv = tlv; bytes_remain = fpin_length - offsetof(struct fc_els_fpin, fpin_desc); bytes_remain = min_t(u32, bytes_remain, be32_to_cpu(fpin->desc_len)); @@ -10374,22 +10367,22 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) /* process each descriptor separately */ while (bytes_remain >= FC_TLV_DESC_HDR_SZ && bytes_remain >= FC_TLV_DESC_SZ_FROM_LENGTH(tlv)) { - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_INTEGRITY: - lpfc_els_rcv_fpin_li(phba, tlv); + lpfc_els_rcv_fpin_li(phba, &tlv->li); deliver = 1; break; case ELS_DTAG_DELIVERY: - lpfc_els_rcv_fpin_del(phba, tlv); + lpfc_els_rcv_fpin_del(phba, &tlv->deli); deliver = 1; break; case ELS_DTAG_PEER_CONGEST: - lpfc_els_rcv_fpin_peer_cgn(phba, tlv); + lpfc_els_rcv_fpin_peer_cgn(phba, &tlv->peer_congn); deliver = 1; break; case ELS_DTAG_CONGESTION: - deliver = lpfc_els_rcv_fpin_cgn(phba, tlv); + deliver = lpfc_els_rcv_fpin_cgn(phba, &tlv->congn); break; default: dtag_nm = lpfc_get_tlv_dtag_nm(dtag); @@ -10402,12 +10395,12 @@ lpfc_els_rcv_fpin(struct lpfc_vport *vport, void *p, u32 fpin_length) return; } lpfc_cgn_update_stat(phba, dtag); - cnt = be32_to_cpu(tlv->desc_len); + cnt = be32_to_cpu(tlv->hdr.desc_len); /* Sanity check descriptor length. The desc_len value does not * include space for the desc_tag and the desc_len fields. */ - len -= (cnt + sizeof(struct fc_tlv_desc)); + len -= (cnt + sizeof(struct fc_tlv_desc_hdr)); if (len < 0) { dtag_nm = lpfc_get_tlv_dtag_nm(dtag); lpfc_printf_log(phba, KERN_WARNING, LOG_CGN_MGMT, diff --git a/drivers/scsi/lpfc/lpfc_hw4.h b/drivers/scsi/lpfc/lpfc_hw4.h index 41fa8f3329daec..10490b3a05cd54 100644 --- a/drivers/scsi/lpfc/lpfc_hw4.h +++ b/drivers/scsi/lpfc/lpfc_hw4.h @@ -4926,12 +4926,20 @@ struct lpfc_els_rdf_reg_desc { }; struct lpfc_els_rdf_req { - struct fc_els_rdf_hdr rdf; /* hdr up to descriptors */ + struct { + __u8 fpin_cmd; /* command (0x19) */ + __u8 fpin_zero[3]; /* specified as zero - part of cmd */ + __be32 desc_len; /* Length of Descriptor List (in bytes) */ + } rdf; /* hdr up to descriptors */ struct lpfc_els_rdf_reg_desc reg_d1; /* 1st descriptor */ }; struct lpfc_els_rdf_rsp { - struct fc_els_rdf_resp_hdr rdf_resp; /* hdr up to descriptors */ + struct { + struct fc_els_ls_acc acc_hdr; + __be32 desc_list_len; /* Length of response (in bytes) */ + struct fc_els_lsri_desc lsri; + } rdf_resp; /* hdr up to descriptors */ struct lpfc_els_rdf_reg_desc reg_d1; /* 1st descriptor */ }; diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index 173ed6373f04ba..02c58f4591d838 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -776,12 +776,11 @@ fc_fpin_pname_stats_update(struct Scsi_Host *shost, * */ static void -fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv) +fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_fn_li_desc *li_desc) { struct fc_rport *rport = NULL; struct fc_rport *attach_rport = NULL; struct fc_host_attrs *fc_host = shost_to_fc_host(shost); - struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv; u16 event_type = be16_to_cpu(li_desc->event_type); rport = fc_find_rport_by_wwpn(shost, @@ -812,12 +811,11 @@ fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv) */ static void fc_fpin_delivery_stats_update(struct Scsi_Host *shost, - struct fc_tlv_desc *tlv) + struct fc_fn_deli_desc *dn_desc) { struct fc_rport *rport = NULL; struct fc_rport *attach_rport = NULL; struct fc_host_attrs *fc_host = shost_to_fc_host(shost); - struct fc_fn_deli_desc *dn_desc = (struct fc_fn_deli_desc *)tlv; u32 reason_code = be32_to_cpu(dn_desc->deli_reason_code); rport = fc_find_rport_by_wwpn(shost, @@ -843,12 +841,10 @@ fc_fpin_delivery_stats_update(struct Scsi_Host *shost, */ static void fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost, - struct fc_tlv_desc *tlv) + struct fc_fn_peer_congn_desc *pc_desc) { struct fc_rport *rport = NULL; struct fc_rport *attach_rport = NULL; - struct fc_fn_peer_congn_desc *pc_desc = - (struct fc_fn_peer_congn_desc *)tlv; u16 event_type = be16_to_cpu(pc_desc->event_type); rport = fc_find_rport_by_wwpn(shost, @@ -876,10 +872,9 @@ fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost, */ static void fc_fpin_congn_stats_update(struct Scsi_Host *shost, - struct fc_tlv_desc *tlv) + struct fc_fn_congn_desc *congn) { struct fc_host_attrs *fc_host = shost_to_fc_host(shost); - struct fc_fn_congn_desc *congn = (struct fc_fn_congn_desc *)tlv; fc_cn_stats_update(be16_to_cpu(congn->event_type), &fc_host->fpin_stats); @@ -899,32 +894,32 @@ fc_host_fpin_rcv(struct Scsi_Host *shost, u32 fpin_len, char *fpin_buf, u8 event_acknowledge) { struct fc_els_fpin *fpin = (struct fc_els_fpin *)fpin_buf; - struct fc_tlv_desc *tlv; + union fc_tlv_desc *tlv; u32 bytes_remain; u32 dtag; enum fc_host_event_code event_code = event_acknowledge ? FCH_EVT_LINK_FPIN_ACK : FCH_EVT_LINK_FPIN; /* Update Statistics */ - tlv = (struct fc_tlv_desc *)&fpin->fpin_desc[0]; + tlv = &fpin->fpin_desc[0]; bytes_remain = fpin_len - offsetof(struct fc_els_fpin, fpin_desc); bytes_remain = min_t(u32, bytes_remain, be32_to_cpu(fpin->desc_len)); while (bytes_remain >= FC_TLV_DESC_HDR_SZ && bytes_remain >= FC_TLV_DESC_SZ_FROM_LENGTH(tlv)) { - dtag = be32_to_cpu(tlv->desc_tag); + dtag = be32_to_cpu(tlv->hdr.desc_tag); switch (dtag) { case ELS_DTAG_LNK_INTEGRITY: - fc_fpin_li_stats_update(shost, tlv); + fc_fpin_li_stats_update(shost, &tlv->li); break; case ELS_DTAG_DELIVERY: - fc_fpin_delivery_stats_update(shost, tlv); + fc_fpin_delivery_stats_update(shost, &tlv->deli); break; case ELS_DTAG_PEER_CONGEST: - fc_fpin_peer_congn_stats_update(shost, tlv); + fc_fpin_peer_congn_stats_update(shost, &tlv->peer_congn); break; case ELS_DTAG_CONGESTION: - fc_fpin_congn_stats_update(shost, tlv); + fc_fpin_congn_stats_update(shost, &tlv->congn); } bytes_remain -= FC_TLV_DESC_SZ_FROM_LENGTH(tlv); diff --git a/include/uapi/scsi/fc/fc_els.h b/include/uapi/scsi/fc/fc_els.h index dca6a28f4e862a..b5653e1dee13c7 100644 --- a/include/uapi/scsi/fc/fc_els.h +++ b/include/uapi/scsi/fc/fc_els.h @@ -259,12 +259,12 @@ enum fc_ls_tlv_dtag { /* - * Generic Link Service TLV Descriptor format + * Generic Link Service TLV Descriptor header * * This structure, as it defines no payload, will also be referred to * as the "tlv header" - which contains the tag and len fields. */ -struct fc_tlv_desc { +struct fc_tlv_desc_hdr { __be32 desc_tag; /* Notification Descriptor Tag */ __be32 desc_len; /* Length of Descriptor (in bytes). * Size of descriptor excluding @@ -273,36 +273,6 @@ struct fc_tlv_desc { __u8 desc_value[]; /* Descriptor Value */ }; -/* Descriptor tag and len fields are considered the mandatory header - * for a descriptor - */ -#define FC_TLV_DESC_HDR_SZ sizeof(struct fc_tlv_desc) - -/* - * Macro, used when initializing payloads, to return the descriptor length. - * Length is size of descriptor minus the tag and len fields. - */ -#define FC_TLV_DESC_LENGTH_FROM_SZ(desc) \ - (sizeof(desc) - FC_TLV_DESC_HDR_SZ) - -/* Macro, used on received payloads, to return the descriptor length */ -#define FC_TLV_DESC_SZ_FROM_LENGTH(tlv) \ - (__be32_to_cpu((tlv)->desc_len) + FC_TLV_DESC_HDR_SZ) - -/* - * This helper is used to walk descriptors in a descriptor list. - * Given the address of the current descriptor, which minimally contains a - * tag and len field, calculate the address of the next descriptor based - * on the len field. - */ -static inline void *fc_tlv_next_desc(void *desc) -{ - struct fc_tlv_desc *tlv = desc; - - return (desc + FC_TLV_DESC_SZ_FROM_LENGTH(tlv)); -} - - /* * Link Service Request Information Descriptor */ @@ -1100,19 +1070,6 @@ struct fc_fn_congn_desc { __u8 resv[3]; /* reserved - must be zero */ }; -/* - * ELS_FPIN - Fabric Performance Impact Notification - */ -struct fc_els_fpin { - __u8 fpin_cmd; /* command (0x16) */ - __u8 fpin_zero[3]; /* specified as zero - part of cmd */ - __be32 desc_len; /* Length of Descriptor List (in bytes). - * Size of ELS excluding fpin_cmd, - * fpin_zero and desc_len fields. - */ - struct fc_tlv_desc fpin_desc[]; /* Descriptor list */ -}; - /* Diagnostic Function Descriptor - FPIN Registration */ struct fc_df_desc_fpin_reg { /* New members MUST be added within the __struct_group() macro below. */ @@ -1136,42 +1093,6 @@ struct fc_df_desc_fpin_reg { _Static_assert(offsetof(struct fc_df_desc_fpin_reg, desc_tags) == sizeof(struct fc_df_desc_fpin_reg_hdr), "struct member likely outside of __struct_group()"); -/* - * ELS_RDF - Register Diagnostic Functions - */ -struct fc_els_rdf { - /* New members MUST be added within the __struct_group() macro below. */ - __struct_group(fc_els_rdf_hdr, __hdr, /* no attrs */, - __u8 fpin_cmd; /* command (0x19) */ - __u8 fpin_zero[3]; /* specified as zero - part of cmd */ - __be32 desc_len; /* Length of Descriptor List (in bytes). - * Size of ELS excluding fpin_cmd, - * fpin_zero and desc_len fields. - */ - ); - struct fc_tlv_desc desc[]; /* Descriptor list */ -}; -_Static_assert(offsetof(struct fc_els_rdf, desc) == sizeof(struct fc_els_rdf_hdr), - "struct member likely outside of __struct_group()"); - -/* - * ELS RDF LS_ACC Response. - */ -struct fc_els_rdf_resp { - /* New members MUST be added within the __struct_group() macro below. */ - __struct_group(fc_els_rdf_resp_hdr, __hdr, /* no attrs */, - struct fc_els_ls_acc acc_hdr; - __be32 desc_list_len; /* Length of response (in - * bytes). Excludes acc_hdr - * and desc_list_len fields. - */ - struct fc_els_lsri_desc lsri; - ); - struct fc_tlv_desc desc[]; /* Supported Descriptor list */ -}; -_Static_assert(offsetof(struct fc_els_rdf_resp, desc) == sizeof(struct fc_els_rdf_resp_hdr), - "struct member likely outside of __struct_group()"); - /* * Diagnostic Capability Descriptors for EDC ELS */ @@ -1241,6 +1162,65 @@ struct fc_diag_cg_sig_desc { struct fc_diag_cg_sig_freq rcv_signal_frequency; }; +/* + * Generic Link Service TLV Descriptor format + * + * This structure, as it defines no payload, will also be referred to + * as the "tlv header" - which contains the tag and len fields. + */ +union fc_tlv_desc { + struct fc_tlv_desc_hdr hdr; + struct fc_els_lsri_desc lsri; + struct fc_fn_li_desc li; + struct fc_fn_deli_desc deli; + struct fc_fn_peer_congn_desc peer_congn; + struct fc_fn_congn_desc congn; + struct fc_df_desc_fpin_reg fpin_reg; + struct fc_diag_lnkflt_desc lnkflt; + struct fc_diag_cg_sig_desc cg_sig; +}; + +/* Descriptor tag and len fields are considered the mandatory header + * for a descriptor + */ +#define FC_TLV_DESC_HDR_SZ sizeof(struct fc_tlv_desc_hdr) + +/* + * Macro, used when initializing payloads, to return the descriptor length. + * Length is size of descriptor minus the tag and len fields. + */ +#define FC_TLV_DESC_LENGTH_FROM_SZ(desc) \ + (sizeof(desc) - FC_TLV_DESC_HDR_SZ) + +/* Macro, used on received payloads, to return the descriptor length */ +#define FC_TLV_DESC_SZ_FROM_LENGTH(tlv) \ + (__be32_to_cpu((tlv)->hdr.desc_len) + FC_TLV_DESC_HDR_SZ) + +/* + * This helper is used to walk descriptors in a descriptor list. + * Given the address of the current descriptor, which minimally contains a + * tag and len field, calculate the address of the next descriptor based + * on the len field. + */ +static inline union fc_tlv_desc *fc_tlv_next_desc(union fc_tlv_desc *desc) +{ + return (union fc_tlv_desc *)((__u8 *)desc + FC_TLV_DESC_SZ_FROM_LENGTH(desc)); +} + + +/* + * ELS_FPIN - Fabric Performance Impact Notification + */ +struct fc_els_fpin { + __u8 fpin_cmd; /* command (0x16) */ + __u8 fpin_zero[3]; /* specified as zero - part of cmd */ + __be32 desc_len; /* Length of Descriptor List (in bytes). + * Size of ELS excluding fpin_cmd, + * fpin_zero and desc_len fields. + */ + union fc_tlv_desc fpin_desc[]; /* Descriptor list */ +}; + /* * ELS_EDC - Exchange Diagnostic Capabilities */ @@ -1251,10 +1231,37 @@ struct fc_els_edc { * Size of ELS excluding edc_cmd, * edc_zero and desc_len fields. */ - struct fc_tlv_desc desc[]; + union fc_tlv_desc desc[]; /* Diagnostic Descriptor list */ }; +/* + * ELS_RDF - Register Diagnostic Functions + */ +struct fc_els_rdf { + __u8 fpin_cmd; /* command (0x19) */ + __u8 fpin_zero[3]; /* specified as zero - part of cmd */ + __be32 desc_len; /* Length of Descriptor List (in bytes). + * Size of ELS excluding fpin_cmd, + * fpin_zero and desc_len fields. + */ + union fc_tlv_desc desc[]; /* Descriptor list */ +}; + +/* + * ELS RDF LS_ACC Response. + */ +struct fc_els_rdf_resp { + struct fc_els_ls_acc acc_hdr; + __be32 desc_list_len; /* Length of response (in + * bytes). Excludes acc_hdr + * and desc_list_len fields. + */ + struct fc_els_lsri_desc lsri; + union fc_tlv_desc desc[]; /* Supported Descriptor list */ +}; + + /* * ELS EDC LS_ACC Response. */ @@ -1265,9 +1272,8 @@ struct fc_els_edc_resp { * and desc_list_len fields. */ struct fc_els_lsri_desc lsri; - struct fc_tlv_desc desc[]; + union fc_tlv_desc desc[]; /* Supported Diagnostic Descriptor list */ }; - #endif /* _FC_ELS_H_ */ From 2559f4d527fd5573fa3a2f36796363b021658ec0 Mon Sep 17 00:00:00 2001 From: Bryan Gurney Date: Mon, 7 Jul 2025 08:48:26 -0400 Subject: [PATCH 15/25] nvme: add NVME_CTRL_MARGINAL flag Add a new controller flag, NVME_CTRL_MARGINAL, to help multipath I/O policies to react to a path that is set to a "marginal" state. The flag is cleared on controller reset, which is often the case when faulty cabling or transceiver hardware is replaced. Signed-off-by: Bryan Gurney --- drivers/nvme/host/core.c | 1 + drivers/nvme/host/fc.c | 4 ++++ drivers/nvme/host/nvme.h | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 453c1f0b2dd094..957090af217126 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -5165,6 +5165,7 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev, WRITE_ONCE(ctrl->state, NVME_CTRL_NEW); ctrl->passthru_err_log_enabled = false; clear_bit(NVME_CTRL_FAILFAST_EXPIRED, &ctrl->flags); + clear_bit(NVME_CTRL_MARGINAL, &ctrl->flags); spin_lock_init(&ctrl->lock); mutex_init(&ctrl->namespaces_lock); diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 04363b9c4489e0..d2ca65f70a0c55 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -788,6 +788,10 @@ nvme_fc_ctrl_connectivity_loss(struct nvme_fc_ctrl *ctrl) "Reconnect", ctrl->cnum); set_bit(ASSOC_FAILED, &ctrl->flags); + + /* clear 'marginal' flag as controller will be reset */ + clear_bit(NVME_CTRL_MARGINAL, &ctrl->flags); + nvme_reset_ctrl(&ctrl->ctrl); } diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 824651cc898dbe..3616742d6788a7 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -329,6 +329,7 @@ enum nvme_ctrl_flags { NVME_CTRL_SKIP_ID_CNS_CS = 4, NVME_CTRL_DIRTY_CAPABILITY = 5, NVME_CTRL_FROZEN = 6, + NVME_CTRL_MARGINAL = 7, }; struct nvme_ctrl { @@ -479,6 +480,11 @@ static inline enum nvme_ctrl_state nvme_ctrl_state(struct nvme_ctrl *ctrl) return READ_ONCE(ctrl->state); } +static inline bool nvme_ctrl_is_marginal(struct nvme_ctrl *ctrl) +{ + return test_bit(NVME_CTRL_MARGINAL, &ctrl->flags); +} + enum nvme_iopolicy { NVME_IOPOLICY_NUMA, NVME_IOPOLICY_RR, From 9441feca54e28a34d6925b5d0f75b374228564f3 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 22 Jun 2026 11:00:19 -0400 Subject: [PATCH 16/25] nvme-multipath: numa support for marginal paths FPIN LI (link integrity) messages are received when the attached fabric detects hardware errors. In response to these messages I/O should be directed away from the affected ports, and only used if no other non-marginal paths are available. To handle this a new controller flag 'NVME_CTRL_MARGINAL' is added which will cause the multipath scheduler to skip these paths when checking for 'optimized' paths. This is a distinct change from the previous version which treated marginal paths as non-optimized but still usable. This changes the priority of marginal paths to be lower than non-optimized paths. Signed-off-by: Jesse Taube Reviewed-by: John Meneghini --- drivers/nvme/host/multipath.c | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 9b9a657fa330ff..02936c580a3734 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -305,10 +305,44 @@ static bool nvme_path_is_disabled(struct nvme_ns *ns) return false; } +/* + * Returns true if the new distance is better than the old one. + */ +static bool is_best_distance(bool found_is_marginal, bool marginal, + int old_distance, int distance) +{ + if (found_is_marginal) { + if (marginal) { + /* + * A marginal path has already been found, + * or this is the first path found. + * This one is also marginal, but closer + * to the NUMA node, so prefer it. + */ + if (distance < old_distance) + return true; + } else { + /* Found a non-marginal path, use it over a marginal one. */ + return true; + } + } else { + /* A non-marginal path has already found. This one is marginal, so skip it. */ + if (marginal) + return false; + + /* Found a closer non-marginal path, use it. */ + if (distance < old_distance) + return true; + } + + return false; +} + static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) { int found_distance = INT_MAX, fallback_distance = INT_MAX, distance; struct nvme_ns *found = NULL, *fallback = NULL, *ns; + bool found_is_marginal = true, fallback_is_marginal = true; list_for_each_entry_srcu(ns, &head->list, siblings, srcu_read_lock_held(&head->srcu)) { @@ -323,15 +357,19 @@ static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) switch (ns->ana_state) { case NVME_ANA_OPTIMIZED: - if (distance < found_distance) { + if (is_best_distance(found_is_marginal, nvme_ctrl_is_marginal(ns->ctrl), + found_distance, distance)) { found_distance = distance; found = ns; + found_is_marginal = nvme_ctrl_is_marginal(ns->ctrl); } break; case NVME_ANA_NONOPTIMIZED: - if (distance < fallback_distance) { + if (is_best_distance(fallback_is_marginal, nvme_ctrl_is_marginal(ns->ctrl), + fallback_distance, distance)) { fallback_distance = distance; fallback = ns; + fallback_is_marginal = nvme_ctrl_is_marginal(ns->ctrl); } break; default: @@ -339,6 +377,14 @@ static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) } } + /* + * Use non-optimized path only if it is not marginal + * and no optimized path is marginal. + */ + if (found_is_marginal && !fallback_is_marginal) + found = fallback; + + /* No optimized path found, use the fallback */ if (!found) found = fallback; if (found) @@ -444,7 +490,8 @@ static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) static inline bool nvme_path_is_optimized(struct nvme_ns *ns) { return nvme_ctrl_state(ns->ctrl) == NVME_CTRL_LIVE && - ns->ana_state == NVME_ANA_OPTIMIZED; + ns->ana_state == NVME_ANA_OPTIMIZED && + !nvme_ctrl_is_marginal(ns->ctrl); } static struct nvme_ns *nvme_numa_path(struct nvme_ns_head *head) From ab9b145cfba215afec759df04302f28bf4138515 Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 1 Jul 2025 13:57:42 -0400 Subject: [PATCH 17/25] nvme-multipath: queue-depth support for marginal paths Exclude marginal paths from queue-depth io policy. In the case where all paths are marginal and no optimized or non-optimized path is found, we fall back and select the best marginal path. Signed-off-by: Jesse Taube Signed-off-by: John Meneghini --- drivers/nvme/host/multipath.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 02936c580a3734..dc34634842be8e 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -453,7 +453,9 @@ static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) { struct nvme_ns *best_opt = NULL, *best_nonopt = NULL, *ns; - unsigned int min_depth_opt = UINT_MAX, min_depth_nonopt = UINT_MAX; + int min_depth_opt = INT_MAX, min_depth_nonopt = INT_MAX; + bool opt_is_marginal = true, nonopt_is_marginal = true, marginal; + unsigned int depth; list_for_each_entry_srcu(ns, &head->list, siblings, @@ -462,28 +464,40 @@ static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) continue; depth = atomic_read(&ns->ctrl->nr_active); + marginal = nvme_ctrl_is_marginal(ns->ctrl); switch (ns->ana_state) { case NVME_ANA_OPTIMIZED: - if (depth < min_depth_opt) { + if (is_best_distance(opt_is_marginal, marginal, + min_depth_opt, depth)) { min_depth_opt = depth; best_opt = ns; + opt_is_marginal = marginal; } break; case NVME_ANA_NONOPTIMIZED: - if (depth < min_depth_nonopt) { + if (is_best_distance(nonopt_is_marginal, marginal, + min_depth_nonopt, depth)) { min_depth_nonopt = depth; best_nonopt = ns; + nonopt_is_marginal = marginal; } break; default: break; } - if (min_depth_opt == 0) + if (min_depth_opt == 0 && !opt_is_marginal) return best_opt; } + /* + * Prefer non-marginal non-optimized path + * over a marginal optimized path. + */ + if (opt_is_marginal && !nonopt_is_marginal && best_nonopt) + return best_nonopt; + return best_opt ? best_opt : best_nonopt; } From afbbc0e746f63fb3d40cbbab7c51e3893d1e6b41 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Thu, 18 Jun 2026 14:55:21 -0400 Subject: [PATCH 18/25] nvme-multipath: round-robin support for marginal paths Exclude marginal paths from round-robin io policy. In the case where all paths are marginal and no optimized or non-optimized path is found, we fall back and perform round-robin on the marginal paths. Signed-off-by: Jesse Taube --- drivers/nvme/host/multipath.c | 47 +++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index dc34634842be8e..11f474704fdd71 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -405,6 +405,7 @@ static struct nvme_ns *nvme_next_ns(struct nvme_ns_head *head, static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) { struct nvme_ns *ns, *found = NULL; + bool found_is_marginal = true; int node = numa_node_id(); struct nvme_ns *old = srcu_dereference(head->current_path[node], &head->srcu); @@ -425,22 +426,58 @@ static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) continue; if (ns->ana_state == NVME_ANA_OPTIMIZED) { + if (found_is_marginal && nvme_ctrl_is_marginal(ns->ctrl)) { + /* + * A marginal path has already found, + * or this is the first path found. + * This one is also marginal, but optimized, + * so prefer it. + */ + found = ns; + found_is_marginal = 1; + continue; + } + + + /* + * A non-marginal path has already found. + * This one is marginal, so skip it. + */ + if (nvme_ctrl_is_marginal(ns->ctrl)) + continue; + + /* Found a non-marginal, optimized path use it. */ found = ns; goto out; } - if (ns->ana_state == NVME_ANA_NONOPTIMIZED) + if (ns->ana_state == NVME_ANA_NONOPTIMIZED) { + /* + * A path has already found. This one is marginal, + * so skip it. + */ + if (found && nvme_ctrl_is_marginal(ns->ctrl)) + continue; found = ns; + found_is_marginal = nvme_ctrl_is_marginal(ns->ctrl); + } } /* * The loop above skips the current path for round-robin semantics. * Fall back to the current path if either: - * - no other optimized path found and current is optimized, + * - no other non-marginal optimized path found and current is, + * optimized and not marginal. * - no other usable path found and current is usable. */ - if (!nvme_path_is_disabled(old) && - (old->ana_state == NVME_ANA_OPTIMIZED || - (!found && old->ana_state == NVME_ANA_NONOPTIMIZED))) + /* no other usable path found and current is usable. */ + if (!nvme_path_is_disabled(old) && !found) + return old; + /* + * no other non-marginal optimized path found and current is, + * optimized and not marginal. + */ + if (!nvme_path_is_disabled(old) && !nvme_ctrl_is_marginal(old->ctrl) && + (old->ana_state == NVME_ANA_OPTIMIZED || found_is_marginal)) return old; if (!found) From 8ec0d54f783436c1304b0def6185d92d8658f6c4 Mon Sep 17 00:00:00 2001 From: Bryan Gurney Date: Tue, 24 Jun 2025 16:20:20 -0400 Subject: [PATCH 19/25] nvme: sysfs: emit the marginal path state in show_state() If a controller has received a link integrity or congestion event, and has the NVME_CTRL_MARGINAL flag set, emit "marginal" in the state instead of "live", to identify the marginal paths. Co-developed-by: John Meneghini Signed-off-by: John Meneghini Reviewed-by: Hannes Reinecke Reviewed-by: Chaitanya Kulkarni Tested-by: Muneendra Kumar Signed-off-by: Bryan Gurney --- drivers/nvme/host/sysfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 75b2d69b59578e..01c771dc88cb9f 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -527,7 +527,9 @@ static ssize_t nvme_sysfs_show_state(struct device *dev, }; if (state < ARRAY_SIZE(state_name) && state_name[state]) - return sysfs_emit(buf, "%s\n", state_name[state]); + return sysfs_emit(buf, "%s\n", + (nvme_ctrl_is_marginal(ctrl)) ? "marginal" : + state_name[state]); return sysfs_emit(buf, "unknown state\n"); } From ac2aa89bf5355be03699d476a3af2472893df8c4 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 8 Jun 2026 14:52:17 -0400 Subject: [PATCH 20/25] scsi: scsi_transport_fc: Add set_rport_marginal to fc_function_template Add fc_fpin_set_marginal function to evaluate the FPIN LI TLV information and set the 'marginal' path status for all affected nvme rports. Then call set_rport_marginal() callback added to fc_function_template. The implementation of set_rport_marginal() is almost identical to set_rport_dev_loss_tmo implementation. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- why do we check rport->roles & FC_PORT_ROLE_NVME_TARGET but not in the sysfs commit --- drivers/scsi/scsi_transport_fc.c | 20 ++++++++++++++++++++ include/scsi/scsi_transport_fc.h | 1 + 2 files changed, 21 insertions(+) diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index 02c58f4591d838..df1edf3c23fa8d 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -737,6 +737,25 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats) } } +static void fc_fpin_set_marginal(struct Scsi_Host *shost, struct fc_rport *rport) +{ + struct fc_internal *i = to_fc_internal(shost->transportt); + unsigned long flags; + + spin_lock_irqsave(shost->host_lock, flags); + + if (rport->port_state == FC_PORTSTATE_ONLINE && + rport->roles & FC_PORT_ROLE_NVME_TARGET) { + rport->port_state = FC_PORTSTATE_MARGINAL; + spin_unlock_irqrestore(shost->host_lock, flags); + if (i->f->set_rport_marginal) + i->f->set_rport_marginal(rport, true); + return; + } + + spin_unlock_irqrestore(shost->host_lock, flags); +} + static void fc_fpin_pname_stats_update(struct Scsi_Host *shost, struct fc_rport *attach_rport, u16 event_type, @@ -764,6 +783,7 @@ fc_fpin_pname_stats_update(struct Scsi_Host *shost, if (rport == attach_rport) continue; stats_update(event_type, &rport->fpin_stats); + fc_fpin_set_marginal(shost, rport); } } } diff --git a/include/scsi/scsi_transport_fc.h b/include/scsi/scsi_transport_fc.h index 9f30625aa0d304..bc7c3f8323992c 100644 --- a/include/scsi/scsi_transport_fc.h +++ b/include/scsi/scsi_transport_fc.h @@ -684,6 +684,7 @@ struct fc_host_attrs { struct fc_function_template { void (*get_rport_dev_loss_tmo)(struct fc_rport *); void (*set_rport_dev_loss_tmo)(struct fc_rport *, u32); + void (*set_rport_marginal)(struct fc_rport *rport, bool marginal); void (*get_starget_node_name)(struct scsi_target *); void (*get_starget_port_name)(struct scsi_target *); From 0b30c08aa1b2f532f1629cc24acd336c8773aa03 Mon Sep 17 00:00:00 2001 From: John Meneghini Date: Tue, 23 Sep 2025 20:00:57 -0400 Subject: [PATCH 21/25] scsi: scsi_transport_fc: user support for clearing NVME_CTRL_MARGINAL Refactor and fc_rport_set_marginal_state smp safe by holding `shost->host_lock` around all `rport->port_state` accesses. Call nvme_fc_modify_rport_fpin_state() when FC_PORTSTATE_MARGINAL is set or cleared. This allows the user to quickly set or clear the NVME_CTRL_MARGINAL state from sysfs. E.g.: echo "Marginal" > /sys/class/fc_remote_ports/rport-13:0-5/port_state echo "Online" > /sys/class/fc_remote_ports/rport-13:0-5/port_state Note: nvme_fc_modify_rport_fpin_state() will only affect rports that have FC_PORT_ROLE_NVME_TARGET set. Signed-off-by: John Meneghini --- drivers/scsi/scsi_transport_fc.c | 42 +++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index df1edf3c23fa8d..af309a7bdc2215 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -1251,34 +1251,58 @@ static ssize_t fc_rport_set_marginal_state(struct device *dev, const char *buf, size_t count) { struct fc_rport *rport = transport_class_to_rport(dev); + struct Scsi_Host *shost = rport_to_shost(rport); + struct fc_internal *i = to_fc_internal(shost->transportt); enum fc_port_state port_state; int ret = 0; + unsigned long flags; ret = get_fc_port_state_match(buf, &port_state); if (ret) return -EINVAL; - if (port_state == FC_PORTSTATE_MARGINAL) { + + spin_lock_irqsave(shost->host_lock, flags); + + switch (port_state) { + case FC_PORTSTATE_MARGINAL: /* * Change the state to Marginal only if the * current rport state is Online * Allow only Online->Marginal */ - if (rport->port_state == FC_PORTSTATE_ONLINE) + if (rport->port_state == FC_PORTSTATE_ONLINE) { rport->port_state = port_state; - else if (port_state != rport->port_state) - return -EINVAL; - } else if (port_state == FC_PORTSTATE_ONLINE) { + spin_unlock_irqrestore(shost->host_lock, flags); + if (i->f->set_rport_marginal) + i->f->set_rport_marginal(rport, true); + return count; + } + break; + + case FC_PORTSTATE_ONLINE: /* * Change the state to Online only if the * current rport state is Marginal * Allow only Marginal->Online */ - if (rport->port_state == FC_PORTSTATE_MARGINAL) + if (rport->port_state == FC_PORTSTATE_MARGINAL) { rport->port_state = port_state; - else if (port_state != rport->port_state) - return -EINVAL; - } else + spin_unlock_irqrestore(shost->host_lock, flags); + if (i->f->set_rport_marginal) + i->f->set_rport_marginal(rport, false); + return count; + } + break; + default: + break; + } + + if (port_state != rport->port_state) { + spin_unlock_irqrestore(shost->host_lock, flags); return -EINVAL; + } + + spin_unlock_irqrestore(shost->host_lock, flags); return count; } From 1888068a183fa0c53573e9e3a494a1894b31f42e Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Tue, 9 Jun 2026 16:58:46 -0400 Subject: [PATCH 22/25] nvme-fc: add nvme_fc_set_remoteport_fpin() Add nvme_fc_set_remoteport_fpin() and supporting functions. This function is called by the SCSI FC transport and driver layer to set or clear the 'marginal' path status for a specific rport. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- drivers/nvme/host/fc.c | 16 ++++++++++++++++ include/linux/nvme-fc-driver.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index d2ca65f70a0c55..4acf23ea8aea22 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -895,6 +895,22 @@ nvme_fc_set_remoteport_devloss(struct nvme_fc_remote_port *portptr, } EXPORT_SYMBOL_GPL(nvme_fc_set_remoteport_devloss); +void +nvme_fc_set_remoteport_fpin(struct nvme_fc_remote_port *portptr, bool marginal) +{ + struct nvme_fc_rport *rport = remoteport_to_rport(portptr); + struct nvme_fc_ctrl *ctrl; + + spin_lock_irq(&rport->lock); + list_for_each_entry(ctrl, &rport->ctrl_list, ctrl_list) { + if (marginal) + set_bit(NVME_CTRL_MARGINAL, &ctrl->ctrl.flags); + else + clear_bit(NVME_CTRL_MARGINAL, &ctrl->ctrl.flags); + } + spin_unlock_irq(&rport->lock); +} +EXPORT_SYMBOL_GPL(nvme_fc_set_remoteport_fpin); /* *********************** FC-NVME DMA Handling **************************** */ diff --git a/include/linux/nvme-fc-driver.h b/include/linux/nvme-fc-driver.h index 9f6acadfe0c868..95d79386d126a5 100644 --- a/include/linux/nvme-fc-driver.h +++ b/include/linux/nvme-fc-driver.h @@ -536,6 +536,8 @@ void nvme_fc_rescan_remoteport(struct nvme_fc_remote_port *remoteport); int nvme_fc_set_remoteport_devloss(struct nvme_fc_remote_port *remoteport, u32 dev_loss_tmo); +void nvme_fc_set_remoteport_fpin(struct nvme_fc_remote_port *portptr, bool marginal); + /* * Routine called to pass a NVME-FC LS request, received by the lldd, * to the nvme-fc transport. From f8f67c9040d5b53e4180b21e33d9ce67d1806bb6 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 8 Jun 2026 14:59:59 -0400 Subject: [PATCH 23/25] scsi: qla2xxx: enable FPIN notification for NVMe Implement set_rport_marginal callback using nvme_fc_set_remoteport_fpin() to enable FPIN notifications for NVMe. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- drivers/scsi/qla2xxx/qla_attr.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/scsi/qla2xxx/qla_attr.c b/drivers/scsi/qla2xxx/qla_attr.c index 6a05ce195aa05b..d97d58e7eacc81 100644 --- a/drivers/scsi/qla2xxx/qla_attr.c +++ b/drivers/scsi/qla2xxx/qla_attr.c @@ -2757,6 +2757,15 @@ qla2x00_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout) rport->dev_loss_tmo); } +static inline void +qla2x00_set_rport_marginal(struct fc_rport *rport, bool marginal) +{ + fc_port_t *fcport = *(fc_port_t **)rport->dd_data; + + if (IS_ENABLED(CONFIG_NVME_FC) && fcport && fcport->nvme_remote_port) + nvme_fc_set_remoteport_fpin(fcport->nvme_remote_port, marginal); +} + static void qla2x00_dev_loss_tmo_callbk(struct fc_rport *rport) { @@ -3346,6 +3355,8 @@ struct fc_function_template qla2xxx_transport_functions = { .set_rport_dev_loss_tmo = qla2x00_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = qla2x00_set_rport_marginal, + .issue_fc_host_lip = qla2x00_issue_lip, .dev_loss_tmo_callbk = qla2x00_dev_loss_tmo_callbk, .terminate_rport_io = qla2x00_terminate_rport_io, @@ -3394,6 +3405,8 @@ struct fc_function_template qla2xxx_transport_vport_functions = { .set_rport_dev_loss_tmo = qla2x00_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = qla2x00_set_rport_marginal, + .issue_fc_host_lip = qla2x00_issue_lip, .dev_loss_tmo_callbk = qla2x00_dev_loss_tmo_callbk, .terminate_rport_io = qla2x00_terminate_rport_io, From 0b5a6eccaf58afdab5004dafa577bb3e943f33bd Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Mon, 8 Jun 2026 15:07:26 -0400 Subject: [PATCH 24/25] scsi: lpfc: enable FPIN notification for NVMe Implement set_rport_marginal callback using nvme_fc_set_remoteport_fpin() to enable FPIN notifications for NVMe. Co-developed-by: Hannes Reinecke Signed-off-by: Hannes Reinecke Tested-by: Bryan Gurney Signed-off-by: John Meneghini --- drivers/scsi/lpfc/lpfc_attr.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/scsi/lpfc/lpfc_attr.c b/drivers/scsi/lpfc/lpfc_attr.c index f4e8164b94abba..4de44a69a6a39d 100644 --- a/drivers/scsi/lpfc/lpfc_attr.c +++ b/drivers/scsi/lpfc/lpfc_attr.c @@ -7134,6 +7134,30 @@ lpfc_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout) #endif } +static void +lpfc_set_rport_marginal(struct fc_rport *rport, bool marginal) +{ + struct lpfc_rport_data *rdata = rport->dd_data; + struct lpfc_nodelist *ndlp = rdata->pnode; + struct lpfc_nvme_rport *nrport = NULL; + + /* Break early if NVME_FC is not enabled */ + if (!IS_ENABLED(CONFIG_NVME_FC)) + return; + + if (!ndlp) { + dev_info(&rport->dev, "Cannot find remote node to " + "set rport dev loss tmo, port_id x%x\n", + rport->port_id); + return; + } + + nrport = lpfc_ndlp_get_nrport(ndlp); + + if (nrport && nrport->remoteport) + nvme_fc_set_remoteport_fpin(nrport->remoteport, marginal); +} + /* * lpfc_rport_show_function - Return rport target information * @@ -7244,6 +7268,8 @@ struct fc_function_template lpfc_transport_functions = { .set_rport_dev_loss_tmo = lpfc_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = lpfc_set_rport_marginal, + .get_starget_port_id = lpfc_get_starget_port_id, .show_starget_port_id = 1, @@ -7315,6 +7341,8 @@ struct fc_function_template lpfc_vport_transport_functions = { .set_rport_dev_loss_tmo = lpfc_set_rport_loss_tmo, .show_rport_dev_loss_tmo = 1, + .set_rport_marginal = lpfc_set_rport_marginal, + .get_starget_port_id = lpfc_get_starget_port_id, .show_starget_port_id = 1, From a4921310cb93f8321d79cf00bf857894febdd363 Mon Sep 17 00:00:00 2001 From: Jesse Taube Date: Tue, 7 Jul 2026 17:11:38 -0400 Subject: [PATCH 25/25] nvme: fcloop: Add set_rport_marginal to sysfs To allow testing of multipath failover, add a sysfs attribute to set a remote port as marginal. This will allow the fcloop LLDD to set the marginal flag on a remote port, simulating a marginal link. Example: Turn on marginal for a remote port matching wwnn and wwpn: `echo 'wwnn=0x200000109b5f2956,wwpn=0x100000109b5f2956,marginal=1' > /sys/class/fcloop/ctl/set_rport_marginal` Turn off marginal for a remote port matching wwnn and wwpn: `echo 'wwnn=0x200000109b5f2956,wwpn=0x100000109b5f2956,marginal=0' > /sys/class/fcloop/ctl/set_rport_marginal` Suggested-by: John Meneghini Signed-off-by: Jesse Taube (cherry picked from commit 22b51a29c3de281bc3220e5c5a3841c24c893646) --- drivers/nvme/target/fcloop.c | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/nvme/target/fcloop.c b/drivers/nvme/target/fcloop.c index b63af3b643a648..fd2250c7bb0dc2 100644 --- a/drivers/nvme/target/fcloop.c +++ b/drivers/nvme/target/fcloop.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "../host/nvme.h" #include "../target/nvmet.h" @@ -21,6 +22,7 @@ enum { NVMF_OPT_FCADDR = 1 << 3, NVMF_OPT_LPWWNN = 1 << 4, NVMF_OPT_LPWWPN = 1 << 5, + NVMF_OPT_MARGINAL = 1 << 6, }; struct fcloop_ctrl_options { @@ -31,6 +33,7 @@ struct fcloop_ctrl_options { u32 fcaddr; u64 lpwwnn; u64 lpwwpn; + u32 marginal; }; static const match_table_t opt_tokens = { @@ -40,6 +43,7 @@ static const match_table_t opt_tokens = { { NVMF_OPT_FCADDR, "fcaddr=%x" }, { NVMF_OPT_LPWWNN, "lpwwnn=%s" }, { NVMF_OPT_LPWWPN, "lpwwpn=%s" }, + { NVMF_OPT_MARGINAL, "marginal=%d" }, { NVMF_OPT_ERR, NULL } }; @@ -120,6 +124,13 @@ fcloop_parse_options(struct fcloop_ctrl_options *opts, } opts->lpwwpn = token64; break; + case NVMF_OPT_MARGINAL: + if (match_int(args, &token)) { + ret = -EINVAL; + goto out_free_options; + } + opts->marginal = token; + break; default: pr_warn("unknown parameter or missing value '%s'\n", p); ret = -EINVAL; @@ -199,6 +210,9 @@ fcloop_parse_nm_options(struct device *dev, u64 *nname, u64 *pname, #define TGTPORT_OPTS (NVMF_OPT_WWNN | NVMF_OPT_WWPN) +#define MARGINAL_OPTS (NVMF_OPT_WWNN | NVMF_OPT_WWPN | \ + NVMF_OPT_MARGINAL) + static DEFINE_SPINLOCK(fcloop_lock); static LIST_HEAD(fcloop_lports); @@ -1663,6 +1677,30 @@ fcloop_set_cmd_drop(struct device *dev, struct device_attribute *attr, return count; } +static ssize_t +fcloop_set_rport_marginal(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fcloop_nport *nport; + struct fcloop_ctrl_options opts; + int ret; + + ret = fcloop_parse_options(&opts, buf); + if (ret) + return ret; + + /* everything there ? */ + if ((opts.mask & MARGINAL_OPTS) != MARGINAL_OPTS) + return -EINVAL; + + nport = fcloop_nport_lookup(opts.wwnn, opts.wwpn); + if (!nport || !nport->tport || !nport->tport->remoteport) + return -ENOENT; + + nvme_fc_set_remoteport_fpin(nport->tport->remoteport, opts.marginal); + + return count; +} static DEVICE_ATTR(add_local_port, 0200, NULL, fcloop_create_local_port); static DEVICE_ATTR(del_local_port, 0200, NULL, fcloop_delete_local_port); @@ -1671,6 +1709,7 @@ static DEVICE_ATTR(del_remote_port, 0200, NULL, fcloop_delete_remote_port); static DEVICE_ATTR(add_target_port, 0200, NULL, fcloop_create_target_port); static DEVICE_ATTR(del_target_port, 0200, NULL, fcloop_delete_target_port); static DEVICE_ATTR(set_cmd_drop, 0200, NULL, fcloop_set_cmd_drop); +static DEVICE_ATTR(set_rport_marginal, 0200, NULL, fcloop_set_rport_marginal); static struct attribute *fcloop_dev_attrs[] = { &dev_attr_add_local_port.attr, @@ -1680,6 +1719,7 @@ static struct attribute *fcloop_dev_attrs[] = { &dev_attr_add_target_port.attr, &dev_attr_del_target_port.attr, &dev_attr_set_cmd_drop.attr, + &dev_attr_set_rport_marginal.attr, NULL };