git: 417d6470cbd4 - main - nvme: limit visible namespaces on Apple S3X

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

URL: https://cgit.FreeBSD.org/src/commit/?id=417d6470cbd40c26ddf8abb5ddf6ab5125e85e75

commit 417d6470cbd40c26ddf8abb5ddf6ab5125e85e75
Author:     Abdelkader Boudih <seuros@FreeBSD.org>
AuthorDate: 2026-08-24 17:02:44 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-08-24 17:02:46 +0000

    nvme: limit visible namespaces on Apple S3X
    
    The Apple S3X controller exposes internal namespaces beyond NSID 1
    that aren't meant to be visible to the OS. Added QUIRK_APPLE_S3X_NS1_ONLY
    and nvme_ctrlr_num_namespaces()/nvme_ctrlr_nsid_visible() helpers, and
    route namespace construction, notification, and AER namespace-changed
    handling through them instead of a raw cdata.nn count.
    
    MFC after:      1 week
    
    Reviewed by:    imp
    Differential Revision:  https://reviews.freebsd.org/D58844
---
 sys/dev/nvme/nvme_ctrlr.c   | 14 +++++++++++---
 sys/dev/nvme/nvme_pci.c     |  4 ++--
 sys/dev/nvme/nvme_private.h | 18 ++++++++++++++++++
 sys/dev/nvme/nvme_sim.c     |  6 +++---
 4 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/sys/dev/nvme/nvme_ctrlr.c b/sys/dev/nvme/nvme_ctrlr.c
index e8f5ce2a9f28..5c90e252b748 100644
--- a/sys/dev/nvme/nvme_ctrlr.c
+++ b/sys/dev/nvme/nvme_ctrlr.c
@@ -633,7 +633,7 @@ nvme_ctrlr_construct_namespaces(struct nvme_controller *ctrlr)
 	struct nvme_namespace	*ns;
 	uint32_t 		i;
 
-	for (i = 0; i < min(ctrlr->cdata.nn, NVME_MAX_NAMESPACES); i++) {
+	for (i = 0; i < nvme_ctrlr_num_namespaces(ctrlr); i++) {
 		ns = &ctrlr->ns[i];
 		nvme_ns_construct(ns, i+1, ctrlr);
 	}
@@ -1172,6 +1172,12 @@ nvme_ctrlr_start_config_hook(void *arg)
 	if (!ctrlr->is_failed) {
 		device_t child;
 
+		if (bootverbose &&
+		    (ctrlr->quirks & QUIRK_APPLE_S3X_NS1_ONLY) != 0 &&
+		    ctrlr->cdata.nn > nvme_ctrlr_num_namespaces(ctrlr))
+			nvme_printf(ctrlr,
+			    "ignoring Apple-internal namespaces above NSID 1\n");
+
 		ctrlr->is_initialized = true;
 		child = device_add_child(ctrlr->dev, NULL, DEVICE_UNIT_ANY);
 		device_set_ivars(child, ctrlr);
@@ -1180,7 +1186,7 @@ nvme_ctrlr_start_config_hook(void *arg)
 		/*
 		 * Now notify the child of all the known namepsaces
 		 */
-		for (int i = 0; i < min(ctrlr->cdata.nn, NVME_MAX_NAMESPACES); i++) {
+		for (int i = 0; i < nvme_ctrlr_num_namespaces(ctrlr); i++) {
 			struct nvme_namespace	*ns = &ctrlr->ns[i];
 
 			if (ns->data.nsze == 0)
@@ -1333,6 +1339,8 @@ nvme_ctrlr_aer_task(void *arg, int pending)
 		}
 		nsl = (struct nvme_ns_list *)aer->log_page_buffer;
 		for (int i = 0; i < nitems(nsl->ns) && nsl->ns[i] != 0; i++) {
+			if (!nvme_ctrlr_nsid_visible(ctrlr, nsl->ns[i]))
+				continue;
 			/*
 			 * I think we need to query the name space here and see
 			 * if it went away, arrived, or changed in size and call
@@ -1343,7 +1351,7 @@ nvme_ctrlr_aer_task(void *arg, int pending)
 				NVME_NS_CHANGED(children[j], nsl->ns[i]);
 		}
 		if (nsl->ns[0] == 0 && ctrlr->quirks & QUIRK_EMPTY_NAMESPACE_CHANGED_LOG) {
-			for (int i = 0; i < min(ctrlr->cdata.nn, NVME_MAX_NAMESPACES); i++)
+			for (int i = 0; i < nvme_ctrlr_num_namespaces(ctrlr); i++)
 				for (int j = 0; j < n_children; j++)
 					NVME_NS_CHANGED(children[j], i + 1);
 		}
diff --git a/sys/dev/nvme/nvme_pci.c b/sys/dev/nvme/nvme_pci.c
index a448694de4f2..8ea31850d09b 100644
--- a/sys/dev/nvme/nvme_pci.c
+++ b/sys/dev/nvme/nvme_pci.c
@@ -98,8 +98,8 @@ static struct _pcsid
 	{ 0xa822144d,		0, 0, "Samsung PM1725a", QUIRK_DELAY_B4_CHK_RDY },
 	{ 0x07f015ad,		0, 0, "VMware NVMe Controller" },
 	{ 0x2003106b,		0, 0, "Apple S3X NVMe Controller",
-	    QUIRK_APPLE_NO_ASYNC_EVENT | QUIRK_PCIE_FLR_ON_FATAL |
-	    QUIRK_APPLE_S3X_SERIALIZE },
+	    QUIRK_APPLE_NO_ASYNC_EVENT | QUIRK_APPLE_S3X_NS1_ONLY |
+	    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 |
diff --git a/sys/dev/nvme/nvme_private.h b/sys/dev/nvme/nvme_private.h
index 181fd27e1ecd..f96729608ed5 100644
--- a/sys/dev/nvme/nvme_private.h
+++ b/sys/dev/nvme/nvme_private.h
@@ -234,6 +234,7 @@ 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_S3X_NS1_ONLY		0x200	/* Ignore Apple-internal namespace 2 */
 #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 */
@@ -339,6 +340,23 @@ struct nvme_controller {
 	counter_u64_t			alignment_splits;
 };
 
+static inline uint32_t
+nvme_ctrlr_num_namespaces(const struct nvme_controller *ctrlr)
+{
+	uint32_t nn;
+
+	nn = min(ctrlr->cdata.nn, NVME_MAX_NAMESPACES);
+	if ((ctrlr->quirks & QUIRK_APPLE_S3X_NS1_ONLY) != 0)
+		nn = min(nn, 1U);
+	return (nn);
+}
+
+static inline bool
+nvme_ctrlr_nsid_visible(const struct nvme_controller *ctrlr, uint32_t nsid)
+{
+	return (nsid >= 1 && nsid <= nvme_ctrlr_num_namespaces(ctrlr));
+}
+
 /*
  * Access the idx'th submission queue entry.
  * sqe_shift is 0 for standard 64-byte SQEs and 1 for 128-byte SQEs.
diff --git a/sys/dev/nvme/nvme_sim.c b/sys/dev/nvme/nvme_sim.c
index 8f6cef4109f0..a58bd2026575 100644
--- a/sys/dev/nvme/nvme_sim.c
+++ b/sys/dev/nvme/nvme_sim.c
@@ -199,7 +199,7 @@ nvme_sim_action(struct cam_sim *sim, union ccb *ccb)
 		cpi->hba_misc =  PIM_UNMAPPED | PIM_NOSCAN;
 		cpi->hba_eng_cnt = 0;
 		cpi->max_target = 0;
-		cpi->max_lun = ctrlr->cdata.nn;
+		cpi->max_lun = nvme_ctrlr_num_namespaces(ctrlr);
 		cpi->maxio = ctrlr->max_xfer_size;
 		cpi->initiator_id = 0;
 		cpi->bus_id = cam_sim_bus(sim);
@@ -365,7 +365,7 @@ nvme_sim_attach(device_t dev)
 		goto err3;
 	}
 
-	for (int i = 0; i < min(ctrlr->cdata.nn, NVME_MAX_NAMESPACES); i++) {
+	for (int i = 0; i < nvme_ctrlr_num_namespaces(ctrlr); i++) {
 		struct nvme_namespace	*ns = &ctrlr->ns[i];
 
 		if (ns->data.nsze == 0)
@@ -388,7 +388,7 @@ nvme_sim_fail_all_ns(device_t dev)
 	struct nvme_sim_softc *sc = device_get_softc(dev);
 	struct nvme_controller *ctrlr = sc->s_ctrlr;
 
-	for (int i = 0; i < min(ctrlr->cdata.nn, NVME_MAX_NAMESPACES); i++) {
+	for (int i = 0; i < nvme_ctrlr_num_namespaces(ctrlr); i++) {
 		struct nvme_namespace	*ns = &ctrlr->ns[i];
 
 		if (ns->data.nsze == 0)