git: 68323f0a0304 - main - nvme: fix Apple S3X controller panic
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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