From 6890cb821799a9583361a35cb9ac7c4c0cad352a Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 02:23:17 -0800 Subject: [PATCH 1/5] dt-bindings: iommu: apple,sart: Add the T6050 SART The T6050 (M5 Ultra) SART keeps SARTv3's entries but moves the address and size arrays, so it is not compatible with apple,t6000-sart and has no fallback. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- Documentation/devicetree/bindings/iommu/apple,sart.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/apple,sart.yaml b/Documentation/devicetree/bindings/iommu/apple,sart.yaml index a75b4c8f05d5b..b451662226545 100644 --- a/Documentation/devicetree/bindings/iommu/apple,sart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,sart.yaml @@ -26,6 +26,9 @@ description: SART3, first seen in M1 Pro/Max, extends both the address space and filter entry size to 42 bit. + SART4, seen on M5 Ultra (T6050), keeps SART3's entries but moves the + address and size arrays, so it has no SART3 fallback. + properties: compatible: oneOf: @@ -40,6 +43,7 @@ properties: - const: apple,t6000-sart - enum: - apple,t6000-sart + - apple,t6050-sart - apple,t8015-sart - apple,t8103-sart From 33bcbda5c33c2e56a02bf8e80d31ae50baefed42 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 02:21:36 -0800 Subject: [PATCH 2/5] soc: apple: sart: Add SARTv4 for T6050 The SART that filters the T6050 (M5 Ultra) ANS's DMA keeps SARTv3's entry format (config, page address and size, 4 KiB units) but moves the address and size arrays from 0x40/0x80 to 0x60/0xc0. Read on J775D before Linux started: the config words at 0x00+4i and seven live entries whose addresses at 0x60+4i and sizes at 0xc0+4i name ANS's MMIO and two DRAM ranges. Let the v3 ops carry the array offsets, so v3 and v4 share one implementation, and match apple,t6050-sart with the v4 offsets. There is no v3 fallback compatible: a v3 driver would read the wrong registers. Log the number of entries the boot loader left (protected) at debug level. The APIF SART (apple,sart-apif) is a separate backend and is not affected; v2 and v3 SARTs keep their offsets. Testing: on J775D, every boot since protects the seven entries the boot loader left, and ANS's NVMe runs behind it (Linux boots from and writes to its NVMe partition). Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/soc/apple/sart.c | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/soc/apple/sart.c b/drivers/soc/apple/sart.c index 6cedcb867c61b..8270a5a731f2c 100644 --- a/drivers/soc/apple/sart.c +++ b/drivers/soc/apple/sart.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -54,15 +55,19 @@ /* SARTv3 registers */ #define APPLE_SART3_CONFIG(idx) (0x00 + 4 * (idx)) -#define APPLE_SART3_PADDR(idx) (0x40 + 4 * (idx)) +#define APPLE_SART3_PADDR 0x40 #define APPLE_SART3_PADDR_SHIFT 12 -#define APPLE_SART3_SIZE(idx) (0x80 + 4 * (idx)) +#define APPLE_SART3_SIZE 0x80 #define APPLE_SART3_SIZE_SHIFT 12 #define APPLE_SART3_SIZE_MAX GENMASK(29, 0) #define APPLE_SART3_FLAGS_ALLOW 0xff +/* SARTv4 (M5): SARTv3's entries, with the address and size arrays moved up */ +#define APPLE_SART4_PADDR 0x60 +#define APPLE_SART4_SIZE 0xc0 + struct apple_sart_ops { void (*get_entry)(struct apple_sart *sart, int index, u8 *flags, phys_addr_t *paddr, size_t *size); @@ -70,6 +75,9 @@ struct apple_sart_ops { phys_addr_t paddr_shifted, size_t size_shifted); /* This is probably a bitfield but the exact meaning of each bit is unknown. */ unsigned int flags_allow; + /* SARTv3/v4: where the address and size arrays start */ + unsigned int paddr_reg; + unsigned int size_reg; unsigned int size_shift; unsigned int paddr_shift; size_t size_max; @@ -179,8 +187,8 @@ static struct apple_sart_ops sart_ops_v2 = { static void sart3_get_entry(struct apple_sart *sart, int index, u8 *flags, phys_addr_t *paddr, size_t *size) { - phys_addr_t paddr_ = readl(sart->regs + APPLE_SART3_PADDR(index)); - size_t size_ = readl(sart->regs + APPLE_SART3_SIZE(index)); + phys_addr_t paddr_ = readl(sart->regs + sart->ops->paddr_reg + 4 * index); + size_t size_ = readl(sart->regs + sart->ops->size_reg + 4 * index); *flags = readl(sart->regs + APPLE_SART3_CONFIG(index)); *size = size_ << APPLE_SART3_SIZE_SHIFT; @@ -190,8 +198,8 @@ static void sart3_get_entry(struct apple_sart *sart, int index, u8 *flags, static void sart3_set_entry(struct apple_sart *sart, int index, u8 flags, phys_addr_t paddr_shifted, size_t size_shifted) { - writel(paddr_shifted, sart->regs + APPLE_SART3_PADDR(index)); - writel(size_shifted, sart->regs + APPLE_SART3_SIZE(index)); + writel(paddr_shifted, sart->regs + sart->ops->paddr_reg + 4 * index); + writel(size_shifted, sart->regs + sart->ops->size_reg + 4 * index); writel(flags, sart->regs + APPLE_SART3_CONFIG(index)); } @@ -199,6 +207,19 @@ static struct apple_sart_ops sart_ops_v3 = { .get_entry = sart3_get_entry, .set_entry = sart3_set_entry, .flags_allow = APPLE_SART3_FLAGS_ALLOW, + .paddr_reg = APPLE_SART3_PADDR, + .size_reg = APPLE_SART3_SIZE, + .size_shift = APPLE_SART3_SIZE_SHIFT, + .paddr_shift = APPLE_SART3_PADDR_SHIFT, + .size_max = APPLE_SART3_SIZE_MAX, +}; + +static struct apple_sart_ops sart_ops_v4 = { + .get_entry = sart3_get_entry, + .set_entry = sart3_set_entry, + .flags_allow = APPLE_SART3_FLAGS_ALLOW, + .paddr_reg = APPLE_SART4_PADDR, + .size_reg = APPLE_SART4_SIZE, .size_shift = APPLE_SART3_SIZE_SHIFT, .paddr_shift = APPLE_SART3_PADDR_SHIFT, .size_max = APPLE_SART3_SIZE_MAX, @@ -236,6 +257,7 @@ static int apple_sart_probe(struct platform_device *pdev) i, flags, &paddr, size); set_bit(i, &sart->protected_entries); } + dev_dbg(dev, "%lu bootloader entries protected\n", hweight_long(sart->protected_entries)); platform_set_drvdata(pdev, sart); return 0; @@ -409,6 +431,10 @@ static void apple_sart_shutdown(struct platform_device *pdev) } static const struct of_device_id apple_sart_of_match[] = { + { + .compatible = "apple,t6050-sart", + .data = &sart_ops_v4, + }, { .compatible = "apple,t6000-sart", .data = &sart_ops_v3, From c7d0a51952fb754d7d8a0b5fbae37dc4731635a8 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 02:22:04 -0800 Subject: [PATCH 3/5] nvme: apple: Name the optional controller reset as a profile flag The APIF profile takes its reset as optional because the guest never resets ANS through PMGR. The native T6050 profile in the next commit needs the same, for another reason, so state it once: a reset_optional flag in the hardware profile, set by the APIF profile. No functional change. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/nvme/host/apple.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index d9e30a7da30d6..8f803ad6b4b1b 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -212,6 +212,8 @@ struct apple_nvme_hw { /* T8132 needs a PMGR reset when starting a stopped coprocessor. */ bool cold_reset; bool apif; + /* The ANS is never reset through PMGR here, so no reset is named. */ + bool reset_optional; u32 max_queue_depth; unsigned long quirks; }; @@ -2405,7 +2407,7 @@ static struct apple_nvme *apple_nvme_alloc(struct platform_device *pdev) goto out_detach_genpd; } - anv->reset = anv->hw->apif ? + anv->reset = anv->hw->reset_optional ? devm_reset_control_array_get_optional_exclusive(anv->dev) : devm_reset_control_array_get_exclusive(anv->dev); if (IS_ERR(anv->reset)) { @@ -2764,6 +2766,7 @@ static const struct apple_nvme_hw apple_nvme_apif_hw = { .has_lsq_nvmmu = true, .needs_ioq_registers = true, .apif = true, + .reset_optional = true, .max_queue_depth = 64, .quirks = NVME_QUIRK_BROKEN_FUA, }; From 62877e61d54e6b21d65f7185a1466ac7e6985137 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 02:23:18 -0800 Subject: [PATCH 4/5] dt-bindings: nvme: apple: Add the T6050 ANS Add apple,t6050-nvme-ans2: T6040's three-region layout (nvme, ans, nvmmu) and power domains. The T6050 ANS is adopted running and never reset through PMGR, so it requires no reset. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- .../bindings/nvme/apple,nvme-ans.yaml | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml b/Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml index 85f0fd2b6fa1e..3e1dfc807b846 100644 --- a/Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml +++ b/Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml @@ -14,6 +14,7 @@ properties: oneOf: - const: apple,t8015-nvme-ans2 - const: apple,t6041-nvme-ans2 + - const: apple,t6050-nvme-ans2 - items: - enum: - apple,t6020-nvme-ans2 @@ -145,6 +146,7 @@ allOf: contains: enum: - apple,t6041-nvme-ans2 + - apple,t6050-nvme-ans2 - apple,t8132-nvme-ans2 then: properties: @@ -159,13 +161,29 @@ allOf: reg-names: maxItems: 2 + - if: + properties: + compatible: + contains: + const: apple,t6050-nvme-ans2 + then: + description: + The T6050 ANS is adopted running, its power stays with the firmware + and it is never reset through PMGR, so it names no power domain and + no reset. + properties: + power-domains: false + power-domain-names: false + else: + required: + - resets + - power-domains + - power-domain-names + required: - compatible - reg - reg-names - - resets - - power-domains - - power-domain-names - mboxes - interrupts - apple,sart From 8414234eda77a888c72bf96fc97648f7dc4db3e1 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 02:23:05 -0800 Subject: [PATCH 5/5] nvme: apple: Add the T6050 ANS on bare metal Add apple,t6050-nvme-ans2 for the M5 Ultra's ANS when Linux runs on the hardware rather than as an APIF guest. Its layout is T6040's, as the ADT and the registers read before Linux started show: a 64 KiB "nvme" block, a separate NVMMU and linear submission queues. The controller reports 254 queue entries (CAP.MQES 0xfd); the profile uses T6040's 64. FUA writes are not shown durable here, so the profile flushes instead (NVME_QUIRK_BROKEN_FUA), as the APIF guest's profile does. The boot loader leaves ANS's firmware running and the controller shut down (CC.EN 0, CSTS.RDY 0). The driver adopts that session, and, like every post-M4 ANS without cold_reset, never resets it through PMGR, so the profile sets reset_optional and the binding requires no reset for it. The APIF guest's profile (apple,nvme-apif) is unchanged. Testing: on J775D, every boot since 2026-10-30 adopts ANS's running firmware and enables the controller with this profile; Linux runs from its NVMe root partition (ext4) through daily builds and reboots without an NVMe error. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/nvme/host/apple.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 8f803ad6b4b1b..0f898ba5203d3 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -2762,6 +2762,23 @@ static const struct apple_nvme_hw apple_nvme_t6040_hw = { .quirks = NVME_QUIRK_NO_APST, }; +/* + * T6050 (M5 Ultra) ANS on bare metal: T6040's split layout (a 64 KiB "nvme" + * block, a separate NVMMU, linear submission queues), 64 queue entries. Like + * every post-M4 ANS without cold_reset it is never reset through PMGR, so no + * reset is named. + */ +static const struct apple_nvme_hw apple_nvme_t6050_hw = { + .has_lsq_nvmmu = true, + .has_separate_nvmmu = true, + .needs_ioq_registers = true, + .lsq_in_nvmmu = true, + .reset_optional = true, + .max_queue_depth = 64, + /* FUA is not shown durable on this ANS: flush instead, as the APIF profile does. */ + .quirks = NVME_QUIRK_NO_APST | NVME_QUIRK_BROKEN_FUA, +}; + static const struct apple_nvme_hw apple_nvme_apif_hw = { .has_lsq_nvmmu = true, .needs_ioq_registers = true, @@ -2774,6 +2791,7 @@ static const struct apple_nvme_hw apple_nvme_apif_hw = { static const struct of_device_id apple_nvme_of_match[] = { { .compatible = "apple,nvme-apif", .data = &apple_nvme_apif_hw }, { .compatible = "apple,t6041-nvme-ans2", .data = &apple_nvme_t6040_hw }, + { .compatible = "apple,t6050-nvme-ans2", .data = &apple_nvme_t6050_hw }, { .compatible = "apple,t8015-nvme-ans2", .data = &apple_nvme_t8015_hw }, { .compatible = "apple,t8103-nvme-ans2", .data = &apple_nvme_t8103_hw }, { .compatible = "apple,t8132-nvme-ans2", .data = &apple_nvme_t8132_hw },