git: 68323f0a0304 - main - nvme: fix Apple S3X controller panic

From: Adrian Chadd <adrian_at_FreeBSD.org>
Date: Mon, 24 Aug 2026 17:03:54 UTC
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=68323f0a0304d770b22ba1823920a52faa7cd4eb

commit 68323f0a0304d770b22ba1823920a52faa7cd4eb
Author:     Abdelkader Boudih <seuros@FreeBSD.org>
AuthorDate: 2026-08-24 17:02:00 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-08-24 17:02:27 +0000

    nvme: fix Apple S3X controller panic
    
    Serialize S3X I/O and cap dtransfers while keeping namespace handling.
    Select 64/128-byte submission queue entries explicitly and set
    CC.IOSQES from the same value used for the software queue stride.
    
    When fatal status is set, wait for pending PCIe transactions and then
    force FLR so a wedged controller doesn't panic or timeout.
    
    MFC: 1 week
    PR: 296946
    Fixes: 5e0ba47aa00e
    
    Reviewed by:    ngie, imp
    Differential Revision:  https://reviews.freebsd.org/D58821
---
 sys/dev/nvme/nvme_ctrlr.c   | 41 +++++++++++++++++++++++++++++++++++++++--
 sys/dev/nvme/nvme_pci.c     |  7 +++++--
 sys/dev/nvme/nvme_private.h | 12 ++++++++++--
 sys/dev/nvme/nvme_qpair.c   | 13 ++++++-------
 4 files changed, 60 insertions(+), 13 deletions(-)

diff --git a/sys/dev/nvme/nvme_ctrlr.c b/sys/dev/nvme/nvme_ctrlr.c
index 4b96966114fe..e8f5ce2a9f28 100644
--- a/sys/dev/nvme/nvme_ctrlr.c
+++ b/sys/dev/nvme/nvme_ctrlr.c
@@ -45,6 +45,8 @@
 #include <vm/vm_extern.h>
 #include <vm/vm_map.h>
 
+#include <dev/pci/pcivar.h>
+
 #include "nvme_private.h"
 #include "nvme_linux.h"
 
@@ -192,6 +194,8 @@ nvme_ctrlr_construct_io_qpairs(struct nvme_controller *ctrlr)
 	if (ctrlr->quirks & QUIRK_APPLE_SHARED_CID_SPACE)
 		num_trackers = min(num_trackers,
 		    NVME_ADMIN_ENTRIES - ctrlr->adminq.num_trackers);
+	if (ctrlr->quirks & QUIRK_APPLE_S3X_SERIALIZE)
+		num_trackers = 1;
 
 	/*
 	 * Our best estimate for the maximum number of I/Os that we should
@@ -199,7 +203,8 @@ nvme_ctrlr_construct_io_qpairs(struct nvme_controller *ctrlr)
 	 * not a hard limit and will need to be revisited when the upper layers
 	 * of the storage system grows multi-queue support.
 	 */
-	ctrlr->max_hw_pend_io = num_trackers * ctrlr->num_io_queues * 3 / 4;
+	ctrlr->max_hw_pend_io = max(1,
+	    num_trackers * ctrlr->num_io_queues * 3 / 4);
 
 	ctrlr->ioq = malloc(ctrlr->num_io_queues * sizeof(struct nvme_qpair),
 	    M_NVME, M_ZERO | M_WAITOK);
@@ -391,7 +396,7 @@ nvme_ctrlr_enable(struct nvme_controller *ctrlr)
 	cc |= NVMEF(NVME_CC_REG_CSS, 0);
 	cc |= NVMEF(NVME_CC_REG_AMS, 0);
 	cc |= NVMEF(NVME_CC_REG_SHN, 0);
-	cc |= NVMEF(NVME_CC_REG_IOSQES, 6); /* SQ entry size == 64 == 2^6 */
+	cc |= NVMEF(NVME_CC_REG_IOSQES, ctrlr->io_sqes);
 	cc |= NVMEF(NVME_CC_REG_IOCQES, 4); /* CQ entry size == 16 == 2^4 */
 
 	/*
@@ -424,9 +429,28 @@ nvme_ctrlr_disable_qpairs(struct nvme_controller *ctrlr)
 	}
 }
 
+static int
+nvme_ctrlr_pcie_flr(struct nvme_controller *ctrlr, uint32_t csts)
+{
+	nvme_printf(ctrlr,
+	    "fatal status; attempting PCIe function level reset\n");
+	pci_save_state(ctrlr->dev);
+	if (!pcie_flr(ctrlr->dev, 1000, true)) {
+		pci_restore_state(ctrlr->dev);
+		nvme_printf(ctrlr, "PCIe function level reset failed\n");
+		nvme_ctrlr_devctl(ctrlr, "FLR_FAILED", "csts=0x%08x", csts);
+		return (ENXIO);
+	}
+	pci_restore_state(ctrlr->dev);
+	nvme_printf(ctrlr, "PCIe function level reset completed\n");
+	nvme_ctrlr_devctl(ctrlr, "FLR_COMPLETED", "csts=0x%08x", csts);
+	return (0);
+}
+
 static int
 nvme_ctrlr_hw_reset(struct nvme_controller *ctrlr)
 {
+	uint32_t csts;
 	int err;
 
 	TSENTER();
@@ -434,6 +458,14 @@ nvme_ctrlr_hw_reset(struct nvme_controller *ctrlr)
 	ctrlr->is_failed_admin = true;
 	nvme_ctrlr_disable_qpairs(ctrlr);
 
+	csts = nvme_mmio_read_4(ctrlr, csts);
+	if ((ctrlr->quirks & QUIRK_PCIE_FLR_ON_FATAL) != 0 &&
+	    csts != NVME_GONE && NVMEV(NVME_CSTS_REG_CFS, csts) != 0) {
+		err = nvme_ctrlr_pcie_flr(ctrlr, csts);
+		if (err != 0)
+			goto out;
+	}
+
 	err = nvme_ctrlr_disable(ctrlr);
 	if (err != 0)
 		goto out;
@@ -490,6 +522,8 @@ nvme_ctrlr_identify(struct nvme_controller *ctrlr)
 		ctrlr->max_xfer_size = min(ctrlr->max_xfer_size,
 		    1 << (ctrlr->cdata.mdts + NVME_MPS_SHIFT +
 			NVME_CAP_HI_MPSMIN(ctrlr->cap_hi)));
+	if (ctrlr->quirks & QUIRK_APPLE_S3X_SERIALIZE)
+		ctrlr->max_xfer_size = min(ctrlr->max_xfer_size, 8192U);
 
 	return (0);
 }
@@ -1653,6 +1687,9 @@ nvme_ctrlr_construct(struct nvme_controller *ctrlr, device_t dev)
 	int		status, timeout_period;
 
 	ctrlr->dev = dev;
+	ctrlr->io_sqes =
+	    (ctrlr->quirks & QUIRK_APPLE_128_BYTE_SQES) != 0 ?
+	    NVME_IOSQES_128 : NVME_IOSQES_64;
 
 	mtx_init(&ctrlr->lock, "nvme ctrlr lock", NULL, MTX_DEF);
 	if (bus_get_domain(dev, &ctrlr->domain) != 0)
diff --git a/sys/dev/nvme/nvme_pci.c b/sys/dev/nvme/nvme_pci.c
index d81b7ccfcb15..a448694de4f2 100644
--- a/sys/dev/nvme/nvme_pci.c
+++ b/sys/dev/nvme/nvme_pci.c
@@ -97,10 +97,13 @@ static struct _pcsid
 	{ 0xa821144d,		0, 0, "Samsung PM1725", QUIRK_DELAY_B4_CHK_RDY },
 	{ 0xa822144d,		0, 0, "Samsung PM1725a", QUIRK_DELAY_B4_CHK_RDY },
 	{ 0x07f015ad,		0, 0, "VMware NVMe Controller" },
-	{ 0x2003106b,		0, 0, "Apple S3X NVMe Controller" },
+	{ 0x2003106b,		0, 0, "Apple S3X NVMe Controller",
+	    QUIRK_APPLE_NO_ASYNC_EVENT | QUIRK_PCIE_FLR_ON_FATAL |
+	    QUIRK_APPLE_S3X_SERIALIZE },
 	{ 0x2005106b,		0, 0, "Apple ANS2 NVMe Controller (T2)",
 	    QUIRK_APPLE_IDENTIFY_CNS_BROKEN | QUIRK_APPLE_SHARED_CID_SPACE |
-	    QUIRK_APPLE_NO_ASYNC_EVENT | QUIRK_APPLE_SINGLE_VECTOR },
+	    QUIRK_APPLE_NO_ASYNC_EVENT | QUIRK_APPLE_SINGLE_VECTOR |
+	    QUIRK_APPLE_128_BYTE_SQES },
 	{ 0x80611d0f,		0, 0, "Amazon EBS NVMe Controller",
 	    QUIRK_EMPTY_NAMESPACE_CHANGED_LOG },
 	{ 0x00000000,		0, 0, NULL  }
diff --git a/sys/dev/nvme/nvme_private.h b/sys/dev/nvme/nvme_private.h
index 5a83966ba729..181fd27e1ecd 100644
--- a/sys/dev/nvme/nvme_private.h
+++ b/sys/dev/nvme/nvme_private.h
@@ -165,7 +165,7 @@ struct nvme_qpair {
 
 	uint32_t		num_entries;
 	uint32_t		num_trackers;
-	uint32_t		sqe_shift;	/* SQE size shift: sqes_max - 6; 0 = 64 bytes, 1 = 128 bytes */
+	uint32_t		sqe_shift;
 	uint16_t		cid_base;	/* CID offset for SHARED_TAGS IO queues */
 	uint32_t		sq_tdbl_off;
 	uint32_t		cq_hdbl_off;
@@ -223,6 +223,7 @@ struct nvme_controller {
 	uint32_t		ready_timeout_in_ms;
 	uint32_t		quirks;
 	uint8_t			max_identify_cns;	/* max CNS value for IDENTIFY (0 = no limit) */
+	uint8_t			io_sqes;
 #define	QUIRK_DELAY_B4_CHK_RDY	1		/* Can't touch MMIO on disable */
 #define	QUIRK_DISABLE_TIMEOUT	2		/* Disable broken completion timeout feature */
 #define	QUIRK_INTEL_ALIGNMENT	4		/* Pre NVMe 1.3 performance alignment */
@@ -233,6 +234,13 @@ struct nvme_controller {
 #define	QUIRK_APPLE_NO_ASYNC_EVENT		0x40	/* Skip NVMe async event requests */
 #define	QUIRK_APPLE_SINGLE_VECTOR		0x80	/* Single MSI vector, one IO queue */
 #define	QUIRK_EMPTY_NAMESPACE_CHANGED_LOG	0x100	/* Change Namespace List Log is always empty */
+#define	QUIRK_APPLE_128_BYTE_SQES		0x400	/* T2 uses 128-byte I/O SQEs */
+#define	QUIRK_PCIE_FLR_ON_FATAL			0x800	/* Use FLR for a fatal controller */
+#define	QUIRK_APPLE_S3X_SERIALIZE		0x1000	/* One S3X I/O at a time */
+
+/* Values programmed into CC.IOSQES (log2 of the SQE size in bytes). */
+#define	NVME_IOSQES_64				6
+#define	NVME_IOSQES_128				7
 
 	int			resource_id;
 	struct resource		*resource;
@@ -333,7 +341,7 @@ struct nvme_controller {
 
 /*
  * Access the idx'th submission queue entry.
- * sqe_shift is sqes_max - 6: 0 for standard 64-byte SQEs, 1 for 128-byte.
+ * sqe_shift is 0 for standard 64-byte SQEs and 1 for 128-byte SQEs.
  */
 #define	NVME_SQE(qpair, idx)	(&(qpair)->cmd[(idx) << (qpair)->sqe_shift])
 
diff --git a/sys/dev/nvme/nvme_qpair.c b/sys/dev/nvme/nvme_qpair.c
index 73af36f977ca..29e373e1b00c 100644
--- a/sys/dev/nvme/nvme_qpair.c
+++ b/sys/dev/nvme/nvme_qpair.c
@@ -531,13 +531,12 @@ nvme_qpair_construct(struct nvme_qpair *qpair,
 	qpair->num_trackers = num_trackers;
 	qpair->ctrlr = ctrlr;
 
-	/* sqes[7:4]: max SQE size exponent; admin always 64 bytes per spec. */
-	if (qpair->id != 0) {
-		uint8_t sqes_max = (ctrlr->cdata.sqes >> 4) & 0xf;
-		qpair->sqe_shift = (sqes_max > 6) ? (sqes_max - 6) : 0;
-	} else {
-		qpair->sqe_shift = 0;
-	}
+	KASSERT(ctrlr->io_sqes == NVME_IOSQES_64 ||
+	    ctrlr->io_sqes == NVME_IOSQES_128,
+	    ("invalid CC.IOSQES value %u", ctrlr->io_sqes));
+	/* Admin SQEs are always 64 bytes. */
+	qpair->sqe_shift = qpair->id == 0 ? 0 :
+	    ctrlr->io_sqes - NVME_IOSQES_64;
 	if ((ctrlr->quirks & QUIRK_APPLE_SHARED_CID_SPACE) && qpair->id != 0)
 		qpair->cid_base = ctrlr->adminq.num_trackers;
 	else