From 8dc96c0a61d858251f46b230a75592bef4287279 Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Sat, 31 Oct 2026 14:36:51 -0300 Subject: [PATCH 01/30] spi: apple-qspi: admit both J616s boot NOR parts Commit eac79241035d ("spi: apple-qspi: use the J616s Macronix boot NOR ID and registers") pinned the J616s boot NOR to c22b27, the Macronix ID macOS reports, in place of the W25Q64 ID. Not every J616s has that part: on a Mac16,7 RDID returns ef6517, the ID the driver already calls W25Q64, and SFDP is valid. That Mac now fails every NVRAM program and erase with -ENODEV. flash_status fails as well, because 35h is refused for the whole board although it is an ordinary SR2 read on this part. Let a board name a second JEDEC ID and list W25Q64 on the J616s. Decide 35h from the flash's own RDID, since the table no longer says which part is fitted. An empty second slot admits nothing, so a bus that reads zeroes stays refused. The status checks already follow the ID read from the flash. Fixes: eac79241035d ("spi: apple-qspi: use the J616s Macronix boot NOR ID and registers") Assisted-by: LLM Signed-off-by: Jonathan Goyvaerts --- drivers/spi/spi-apple-qspi.c | 33 +++++++++++++++++++++------------ tools/asahi/qspi-nvram-tests.py | 21 ++++++++++++++++++--- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/drivers/spi/spi-apple-qspi.c b/drivers/spi/spi-apple-qspi.c index 601929b85f1fd..93b8078208362 100644 --- a/drivers/spi/spi-apple-qspi.c +++ b/drivers/spi/spi-apple-qspi.c @@ -100,8 +100,8 @@ struct apple_qspi_soc { /* * A board whose NVRAM may be written: the controller compatible, the * machine, the controller aperture, the nvram partition the device tree - * must describe, the boot NOR JEDEC ID and the SR1 block-protect bits its - * firmware leaves set at boot. + * must describe, the boot NOR JEDEC ID, the SR1 block-protect bits its + * firmware leaves set at boot and the JEDEC ID of a second part, if any. */ struct apple_qspi_board { const char *controller; @@ -111,6 +111,7 @@ struct apple_qspi_board { u32 nvram_size; u8 jedec[3]; u8 bp_ok; + u8 jedec_alt[3]; }; #define APPLE_QSPI_JEDEC_W25Q64 { 0xef, 0x65, 0x17 } @@ -122,9 +123,10 @@ static const struct apple_qspi_board apple_qspi_boards[] = { 0x700000, 0x100000, APPLE_QSPI_JEDEC_W25Q64, 0 }, { "apple,t8132-qspi", "apple,j713", 0x3ad118000ULL, 0x700000, 0x100000, APPLE_QSPI_JEDEC_W25Q64, 0 }, - /* Macronix; the ID macOS reports for the J616s boot NOR. */ + /* Macronix is the ID macOS reports; a J616s also reads W25Q64 on the bus. */ { "apple,t6041-qspi", "apple,j616s", 0x429118000ULL, - 0x700000, 0x100000, APPLE_QSPI_JEDEC_MX_2B27, 0 }, + 0x700000, 0x100000, APPLE_QSPI_JEDEC_MX_2B27, 0, + APPLE_QSPI_JEDEC_W25Q64 }, /* iBoot leaves BP0 set on the J700, over the top 64 KiB. */ { "apple,t8140-qspi", "apple,j700", 0x385110000ULL, 0x24000, 0x1d0000, APPLE_QSPI_JEDEC_W25Q16, 0x1c }, @@ -198,8 +200,9 @@ struct apple_qspi { /* The nvram partition [window_start, window_end). */ u64 window_start; u64 window_end; - /* Expected JEDEC ID and the SR1 block-protect bits left by firmware. */ + /* Expected JEDEC IDs and the SR1 block-protect bits left by firmware. */ u8 jedec[3]; + u8 jedec_alt[3]; u8 bp_ok; bool nvram_write_candidate; bool write_fault; @@ -710,7 +713,8 @@ static int apple_qspi_nvram_modify(struct spi_mem *mem, const struct spi_mem_op ret = apple_qspi_read_register(mem, 0x9f, id, sizeof(id)); if (ret) goto out; - if (memcmp(id, qspi->jedec, sizeof(id))) { + if (memcmp(id, qspi->jedec, sizeof(id)) && + (!qspi->jedec_alt[0] || memcmp(id, qspi->jedec_alt, sizeof(id)))) { ret = -ENODEV; goto out; } @@ -823,14 +827,18 @@ static int apple_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) if (!apple_qspi_supports_op(mem, op) || apple_qspi_shape_op(op, &shape)) return -EOPNOTSUPP; mutex_lock(&apple_qspi_policy_lock); - /* 35h is EQIO, not a register read, on Macronix parts. */ - if (op->cmd.opcode == 0x35 && - qspi->jedec[0] == APPLE_QSPI_MFR_MACRONIX) { - ret = -EOPNOTSUPP; - goto out; - } if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes && apple_qspi_opcode_is_read(op->cmd.opcode)) { + /* 35h is EQIO, not a register read, on Macronix parts. */ + if (op->cmd.opcode == 0x35) { + u8 id[3]; + + ret = apple_qspi_read_register(mem, 0x9f, id, sizeof(id)); + if (!ret && id[0] == APPLE_QSPI_MFR_MACRONIX) + ret = -EOPNOTSUPP; + if (ret) + goto out; + } ret = apple_qspi_transfer_op(mem, op); goto out; } @@ -1006,6 +1014,7 @@ static int apple_qspi_probe(struct platform_device *pdev) if (board && qspi->window_start == board->nvram_start && qspi->window_end == (u64)board->nvram_start + board->nvram_size) { memcpy(qspi->jedec, board->jedec, sizeof(qspi->jedec)); + memcpy(qspi->jedec_alt, board->jedec_alt, sizeof(qspi->jedec_alt)); qspi->bp_ok = board->bp_ok; qspi->nvram_write_candidate = true; } diff --git a/tools/asahi/qspi-nvram-tests.py b/tools/asahi/qspi-nvram-tests.py index a13ce3ac46e17..f0a39ec959190 100644 --- a/tools/asahi/qspi-nvram-tests.py +++ b/tools/asahi/qspi-nvram-tests.py @@ -69,7 +69,7 @@ def function(source, signature): struct apple_qspi { bool nvram_policy, nvram_write_candidate, write_fault, nvram_window; u64 window_start, window_end; - u8 jedec[3]; u8 bp_ok; + u8 jedec[3]; u8 jedec_alt[3]; u8 bp_ok; int irq; }; struct spi_controller; @@ -164,6 +164,7 @@ def function(source, signature): struct device_node t6041={"apple,t6041-qspi"},t8140={"apple,t8140-qspi"}; machine="apple,j616s";qb=apple_qspi_nvram_write_board(&t6041,0x429118000ULL); assert(qb&&qb->nvram_start==0x700000&&qb->nvram_size==0x100000&&qb->jedec[0]==0xc2&&qb->jedec[1]==0x2b&&qb->jedec[2]==0x27&&!qb->bp_ok); + assert(qb->jedec_alt[0]==0xef&&qb->jedec_alt[1]==0x65&&qb->jedec_alt[2]==0x17); machine="apple,j700";qb=apple_qspi_nvram_write_board(&t8140,0x385110000ULL); assert(qb&&qb->nvram_start==0x24000&&qb->nvram_size==0x1d0000&&qb->jedec[2]==0x15&&qb->bp_ok==0x1c); puts("PASS J613/J713/J700/J616s board, controller and full-width aperture qualification (140 combinations)"); @@ -241,12 +242,24 @@ def function(source, signature): erase.addr.val=0x701000; wire_count=0;assert(!apple_qspi_exec_op(&mem,&erase)&&sent(0x20)&&!sent(0x35)); struct spi_mem_op rdcr2=SPI_MEM_OP(SPI_MEM_OP_CMD(0x35,1),SPI_MEM_OP_NO_ADDR,SPI_MEM_OP_NO_DUMMY,SPI_MEM_OP_DATA_IN(1,id,1)); - wire_count=0;assert(apple_qspi_exec_op(&mem,&rdcr2)<0&&!wire_count); + wire_count=0;assert(apple_qspi_exec_op(&mem,&rdcr2)<0&&!sent(0x35)); for(unsigned int bp=0x04;bp<=0x20;bp<<=1){reset();jedec[0]=0xc2;jedec[1]=0x2b;jedec[2]=0x27;memcpy(q.jedec,jedec,3);arm(); sr[0]=bp;wire_count=0;o=program(0x700000,1,data);assert(apple_qspi_exec_op(&mem,&o)==-EACCES&&!sent(0x06)&&!sent(0x35));} reset();memcpy(q.jedec,(u8[]){0xc2,0x2b,0x27},3);arm();wire_count=0;o=program(0x700000,1,data); assert(apple_qspi_exec_op(&mem,&o)==-ENODEV&&!sent(0x06)); puts("PASS Macronix J616s part: no 35h on the wire, SR/CR checks, BP0-BP3 refusal and JEDEC mismatch"); + /* J616s W25Q64 part: the same board entry, and 35h is its SR2 read. */ + reset();memcpy(q.jedec,(u8[]){0xc2,0x2b,0x27},3);memcpy(q.jedec_alt,jedec,3);arm();memset(data,0x55,sizeof(data)); + o=program(0x700000,256,data);wire_count=0;assert(!apple_qspi_exec_op(&mem,&o)&&sent(0x02)&&sent(0x35)); + assert(!memcmp(flash+0x700000,data,256)&&!sr[0]); + wire_count=0;assert(!apple_qspi_exec_op(&mem,&rdcr2)&&sent(0x35)); + /* A second-slot match keeps the Winbond protection checks. */ + sr[1]=0x40;wire_count=0;o=program(0x700100,1,data);assert(apple_qspi_exec_op(&mem,&o)==-EACCES&&!sent(0x06)); + sr[1]=0;jedec[2]^=1;wire_count=0;assert(apple_qspi_exec_op(&mem,&o)==-ENODEV&&!sent(0x06)); + /* An empty second slot admits nothing, even on a bus that reads zeroes. */ + reset();memset(jedec,0,3);arm();wire_count=0;o=program(0x700000,1,data); + assert(apple_qspi_exec_op(&mem,&o)==-ENODEV&&!sent(0x06)); + puts("PASS W25Q64 J616s part: second ID admitted with SR2 read and checks, third ID and empty slot refused"); /* Model spi-mem's outer reference. Every private JEDEC/status/WREN, * completion poll, WRDI and readback must finish before the idle stamp. * Hardware tests must still prove genpd and the actual suspend timer. */ @@ -324,7 +337,9 @@ def main(): ('bp-ok', '!(sr1 & 0x1c & ~bp_ok)', '1'), ('mx-bp3', '!(sr & 0x3c & ~bp_ok)', '!(sr & 0x1c & ~bp_ok)'), ('mx-eqio', 'opcode = macronix;', 'opcode = winbond;'), - ('mx-eqio-read', 'qspi->jedec[0] == APPLE_QSPI_MFR_MACRONIX) {', '0) {'), + ('mx-eqio-read', '!ret && id[0] == APPLE_QSPI_MFR_MACRONIX', '0'), + ('jedec-alt', 'memcmp(id, qspi->jedec_alt, sizeof(id))', '1'), + ('jedec-alt-empty', '!qspi->jedec_alt[0] || ', ''), ('read-only', 'read_only || !qspi->nvram_write_candidate || qspi->write_fault', '!qspi->nvram_write_candidate || qspi->write_fault'), ('fault-latch', 'write_fault_latched = true;', 'write_fault_latched = false;'), From 3581f923166b044058cef06941d1b20133772a6f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 11:47:32 +1100 Subject: [PATCH 02/30] iio: aop_als: report colour temperature as IIO_COLORTEMP The Redbird colour temperature was exported as an IIO_CCT channel, in_cct_input. No other driver uses that type and the IIO ABI does not document it, so userspace looking for an ambient colour temperature (for example an ambient white point policy) reads in_colortemp_*, the documented channel that hid-sensor-als also provides. Report the same firmware-computed Kelvin value as a processed IIO_COLORTEMP channel, in_colortemp_input. Lux, the report decoding and the CT720 and CT817 parts are unchanged. Update the driver ABI note, the driver documentation and the J616s parity collector to the new name. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/ABI/testing/sysfs-driver-aop-als | 5 +++-- Documentation/iio/apple-aop-als.rst | 5 +++-- drivers/iio/common/aop_sensors/aop_als.rs | 2 +- rust/bindings/bindings_helper.h | 2 +- tools/testing/selftests/drivers/apple/j616s-parity.py | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-aop-als b/Documentation/ABI/testing/sysfs-driver-aop-als index c5b6acd796242..410b9d2f1341a 100644 --- a/Documentation/ABI/testing/sysfs-driver-aop-als +++ b/Documentation/ABI/testing/sysfs-driver-aop-als @@ -1,8 +1,9 @@ -What: /sys/bus/iio/devices/iio:deviceX/in_cct_input +What: /sys/bus/iio/devices/iio:deviceX/in_colortemp_input Date: October 2026 Contact: linux-iio@vger.kernel.org Description: - Firmware-computed correlated colour temperature in Kelvin on + Firmware-computed light colour temperature (correlated colour + temperature) in Kelvin, processed, on aop-sensors-als devices with a Redbird (VD6286) sensor. Fractional Kelvin are reported with micro resolution. There is no raw photodiode-to-CCT conversion in the host driver. diff --git a/Documentation/iio/apple-aop-als.rst b/Documentation/iio/apple-aop-als.rst index a19625a84be75..bf96aaebec858 100644 --- a/Documentation/iio/apple-aop-als.rst +++ b/Documentation/iio/apple-aop-als.rst @@ -17,7 +17,8 @@ must not be distributed with test fixtures. The `public Redbird report description `_ identifies a 122-byte report carrying a firmware-computed CCT float at byte -0x45. The driver exports that layout as ``in_cct_input``, in Kelvin, without +0x45. The driver exports that value as the standard IIO colour temperature +channel, ``in_colortemp_input``, processed and in Kelvin, without recalculating colour temperature from raw channels. Other lengths do not supply a CCT reading. This layout must be verified on each firmware family before its CCT readings are used for display policy. The existing CT720 and @@ -29,7 +30,7 @@ device number:: for d in /sys/bus/iio/devices/iio:device*; do [ "$(cat "$d/name" 2>/dev/null)" = aop-sensors-als ] || continue cat "$d/in_illuminance_input" - [ ! -e "$d/in_cct_input" ] || cat "$d/in_cct_input" + [ ! -e "$d/in_colortemp_input" ] || cat "$d/in_colortemp_input" done These attributes contain the last valid reading, not a sample counter. Polling diff --git a/drivers/iio/common/aop_sensors/aop_als.rs b/drivers/iio/common/aop_sensors/aop_als.rs index 5e6c5001ad2b8..e64f7ff26a6e0 100644 --- a/drivers/iio/common/aop_sensors/aop_als.rs +++ b/drivers/iio/common/aop_sensors/aop_als.rs @@ -268,7 +268,7 @@ impl platform::Driver for IIOAopAlsDriver { &adata, service, offset, - [ty, bindings::BINDINGS_IIO_CCT], + [ty, bindings::BINDINGS_IIO_COLORTEMP], )?; ( listener, diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 145711b2ccf00..4062a9f1bc0d8 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -169,7 +169,7 @@ const u32 BINDINGS_IIO_CHAN_INFO_PROCESSED = IIO_CHAN_INFO_PROCESSED; const u32 BINDINGS_IIO_CHAN_INFO_SCALE = IIO_CHAN_INFO_SCALE; const u32 BINDINGS_IIO_ANGL = IIO_ANGL; const u32 BINDINGS_IIO_LIGHT = IIO_LIGHT; -const u32 BINDINGS_IIO_CCT = IIO_CCT; +const u32 BINDINGS_IIO_COLORTEMP = IIO_COLORTEMP; const u32 BINDINGS_IIO_ACCEL = IIO_ACCEL; const u32 BINDINGS_IIO_ANGL_VEL = IIO_ANGL_VEL; const u32 BINDINGS_IIO_TIMESTAMP = IIO_TIMESTAMP; diff --git a/tools/testing/selftests/drivers/apple/j616s-parity.py b/tools/testing/selftests/drivers/apple/j616s-parity.py index d8835771f36c8..78b1594d5cb60 100644 --- a/tools/testing/selftests/drivers/apple/j616s-parity.py +++ b/tools/testing/selftests/drivers/apple/j616s-parity.py @@ -30,7 +30,7 @@ for device in pathlib.Path('/sys/bus/iio/devices').glob('iio:device*'): try: if (device / 'name').read_text().strip() == 'aop-sensors-als': - als += [device / 'in_illuminance_input', device / 'in_cct_input'] + als += [device / 'in_illuminance_input', device / 'in_colortemp_input'] except OSError: pass paths += als From f881285697d7957e4e3065e96a123fbbd9ff2557 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 00:09:43 +1100 Subject: [PATCH 03/30] soc: apple: issue whole-word T6000 DCS commands and validate sparse states T6000 DCS accepts raw states 5, 6, 7 and 9. Write only the selected raw command as a complete 32-bit store, including rollback and T6000 suspend/resume, instead of replaying status bits from an earlier read. Keep fabric accesses separate and reject unsettled current state before a vote. Exercise command words, invalid raw states, table validation and rollback with a hardware-status model in KUnit. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/soc/apple/apple-pmgr-dcs-test.c | 156 +++++++++++++++++++----- drivers/soc/apple/apple-pmgr-misc.c | 51 ++++++-- 2 files changed, 168 insertions(+), 39 deletions(-) diff --git a/drivers/soc/apple/apple-pmgr-dcs-test.c b/drivers/soc/apple/apple-pmgr-dcs-test.c index f1021b6de4f61..82fe4acea2c97 100644 --- a/drivers/soc/apple/apple-pmgr-dcs-test.c +++ b/drivers/soc/apple/apple-pmgr-dcs-test.c @@ -3,15 +3,40 @@ #include #include +struct apple_pmgr_dcs_fixture { + struct apple_pmgr_misc misc; + u32 commands[16]; + unsigned int num_commands; + u32 fail_states; + u32 fail_actual; +}; + +static void apple_pmgr_dcs_fake_command(u32 word, void __iomem *reg) +{ + struct kunit *test = kunit_get_current_test(); + struct apple_pmgr_dcs_fixture *f = test->priv; + u32 actual = word; + + KUNIT_ASSERT_PTR_EQ(test, reg, f->misc.devices[DEV_DCS].base); + KUNIT_ASSERT_LT(test, f->num_commands, ARRAY_SIZE(f->commands)); + f->commands[f->num_commands++] = word; + if (f->fail_states & BIT(word)) + actual = f->fail_actual; + /* Model hardware updating actual independently of the command word. */ + writel(word | FIELD_PREP(APPLE_CLKGEN_PSTATE_ACTUAL, actual), reg); +} + static int apple_pmgr_dcs_test_init(struct kunit *test) { + struct apple_pmgr_dcs_fixture *f; struct apple_pmgr_misc *misc; void *reg; - misc = kunit_kzalloc(test, sizeof(*misc), GFP_KERNEL); - if (!misc) + f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL); + if (!f) return -ENOMEM; - reg = kunit_kzalloc(test, sizeof(u32), GFP_KERNEL); + misc = &f->misc; + reg = kunit_kzalloc(test, 2 * sizeof(u32), GFP_KERNEL); if (!reg) return -ENOMEM; misc->dev = kunit_device_register(test, "pmgr-dcs-test"); @@ -19,13 +44,17 @@ static int apple_pmgr_dcs_test_init(struct kunit *test) return PTR_ERR(misc->dev); mutex_init(&misc->lock); misc->devices[DEV_DCS].base = (void __iomem *)reg; - misc->devices[DEV_DCS].active_state = 10; + misc->devices[DEV_DCS].active_state = 9; misc->devices[DEV_DCS].suspend_state = DCS_DEV_PSTATE_MIN_T600X; - misc->dcs_num_opps = 3; + misc->dcs_valid_states = DCS_VALID_STATES_T600X; + misc->devices[DEV_FABRIC].base = (void __iomem *)(reg + sizeof(u32)); + writel(0xa5000077, misc->devices[DEV_FABRIC].base); + misc->fabric_now = 7; + misc->devices[DEV_FABRIC].active_state = 7; + misc->dcs_num_opps = 2; misc->dcs_opps[0] = (struct apple_pmgr_dcs_opp) { 7, 1000 }; misc->dcs_opps[1] = (struct apple_pmgr_dcs_opp) { 9, 2000 }; - misc->dcs_opps[2] = (struct apple_pmgr_dcs_opp) { 10, 3000 }; - writel(0xa50000aa, misc->devices[DEV_DCS].base); + writel(0xa5000099, misc->devices[DEV_DCS].base); misc->dcs_data = kunit_kzalloc(test, struct_size(misc->dcs_data, nodes, 2), GFP_KERNEL); if (!misc->dcs_data) return -ENOMEM; @@ -37,7 +66,8 @@ static int apple_pmgr_dcs_test_init(struct kunit *test) misc->dcs_data->nodes[i]->data = misc; } dev_set_drvdata(misc->dev, misc); - test->priv = misc; + test->priv = f; + kunit_activate_static_stub(test, apple_pmgr_dcs_command, apple_pmgr_dcs_fake_command); return 0; } @@ -46,7 +76,7 @@ static void apple_pmgr_dcs_table_test(struct kunit *test) struct apple_pmgr_misc *misc = test->priv; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), 0); - swap(misc->dcs_opps[0], misc->dcs_opps[2]); + swap(misc->dcs_opps[0], misc->dcs_opps[1]); KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), 0); KUNIT_EXPECT_EQ(test, misc->dcs_opps[0].state, 7U); misc->dcs_opps[1].state = 7; @@ -58,10 +88,10 @@ static void apple_pmgr_dcs_table_test(struct kunit *test) misc->dcs_opps[0].bw = 0; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), -EINVAL); misc->dcs_opps[0].bw = 1000; - misc->dcs_opps[0].state = 6; + misc->dcs_opps[0].state = 8; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), -EINVAL); misc->dcs_opps[0].state = 7; - misc->devices[DEV_DCS].active_state = 9; + misc->devices[DEV_DCS].active_state = 7; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), -EINVAL); misc->dcs_num_opps = 17; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), -EINVAL); @@ -80,10 +110,10 @@ static void apple_pmgr_dcs_selection_test(struct kunit *test) KUNIT_EXPECT_EQ(test, state, 7U); KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_select(misc, 1001, &state), 0); KUNIT_EXPECT_EQ(test, state, 9U); - KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_select(misc, 3000, &state), 0); - KUNIT_EXPECT_EQ(test, state, 10U); - KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_select(misc, 3001, &state), -ERANGE); - KUNIT_EXPECT_EQ(test, state, 10U); + KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_select(misc, 2000, &state), 0); + KUNIT_EXPECT_EQ(test, state, 9U); + KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_select(misc, 2001, &state), -ERANGE); + KUNIT_EXPECT_EQ(test, state, 9U); } static void apple_pmgr_dcs_aggregation_test(struct kunit *test) @@ -105,11 +135,11 @@ static void apple_pmgr_dcs_commit_test(struct kunit *test) node->avg_bw = 1001; - writel(0xa500009a, misc->devices[DEV_DCS].base); + writel(0x999, misc->devices[DEV_DCS].base); KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_set(node, node), 0); KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 9U); KUNIT_EXPECT_TRUE(test, misc->dcs_bw_active); - KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa5000099U); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0x99U); } static void apple_pmgr_dcs_owner_sleep_test(struct kunit *test) @@ -124,23 +154,29 @@ static void apple_pmgr_dcs_owner_sleep_test(struct kunit *test) misc->pmp_owned = false; misc->sleeping = true; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_set(node, node), -EBUSY); - KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa50000aaU); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa5000099U); } static void apple_pmgr_dcs_rollback_test(struct kunit *test) { struct apple_pmgr_misc *misc = test->priv; struct icc_node *node = misc->dcs_data->nodes[0]; + struct apple_pmgr_dcs_fixture *f = test->priv; node->peak_bw = 1000; - /* Actual stays at 10: request times out, rollback to 10 succeeds. */ + /* Failed down-command, followed by a successful whole-word restore. */ + f->fail_states = BIT(7); + f->fail_actual = 9; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_set(node, node), -ETIMEDOUT); KUNIT_EXPECT_FALSE(test, misc->dcs_failed); - KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 10U); - KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa50000aaU); - /* An unknown actual state makes rollback fail too. */ - writel(0xa500000a, misc->devices[DEV_DCS].base); + KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 9U); + KUNIT_EXPECT_EQ(test, f->commands[0], 7U); + KUNIT_EXPECT_EQ(test, f->commands[1], 9U); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0x99U); + /* Inject failure of the restoration as well; retain the fabric hold. */ + f->fail_states |= BIT(9); + f->fail_actual = 0; KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_set(node, node), -ETIMEDOUT); KUNIT_EXPECT_TRUE(test, misc->dcs_failed); KUNIT_EXPECT_TRUE(test, misc->dcs_bw_active); @@ -154,15 +190,15 @@ static void apple_pmgr_dcs_sync_constraints_test(struct kunit *test) struct icc_node *src = misc->dcs_data->nodes[0]; struct icc_node *dst = misc->dcs_data->nodes[1]; - src->avg_bw = 1001; - dst->init_peak = 3000; + src->avg_bw = 1000; + dst->init_peak = 2000; KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_set(src, src), 0); - KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 10U); + KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 9U); dst->init_peak = 0; - writel(0xa500009a, misc->devices[DEV_DCS].base); + writel(0x999, misc->devices[DEV_DCS].base); /* Releasing destination's boot constraint must retain source demand. */ KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_set(dst, dst), 0); - KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 9U); + KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 7U); } static void apple_pmgr_dcs_suspend_restore_test(struct kunit *test) @@ -172,9 +208,9 @@ static void apple_pmgr_dcs_suspend_restore_test(struct kunit *test) dev_set_drvdata(misc->dev, misc); misc->devices[DEV_DCS].active_state = 9; KUNIT_ASSERT_EQ(test, apple_pmgr_misc_suspend_noirq(misc->dev), 0); - KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa50000a7U); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0x77U); KUNIT_ASSERT_EQ(test, apple_pmgr_misc_resume_noirq(misc->dev), 0); - KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa50000a9U); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0x99U); } static void apple_pmgr_dcs_no_table_test(struct kunit *test) @@ -182,7 +218,7 @@ static void apple_pmgr_dcs_no_table_test(struct kunit *test) struct apple_pmgr_misc *misc = test->priv; KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_init(misc), 0); - KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa50000aaU); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa5000099U); } static void apple_pmgr_dcs_release_test(struct kunit *test) @@ -191,13 +227,69 @@ static void apple_pmgr_dcs_release_test(struct kunit *test) struct icc_node *node = misc->dcs_data->nodes[0]; misc->dcs_bw_active = true; - writel(0xa500007a, misc->devices[DEV_DCS].base); + writel(0x999, misc->devices[DEV_DCS].base); KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_set(node, node), 0); KUNIT_EXPECT_EQ(test, misc->devices[DEV_DCS].active_state, 7U); KUNIT_EXPECT_FALSE(test, misc->dcs_bw_active); } +static void apple_pmgr_dcs_whole_word_test(struct kunit *test) +{ + struct apple_pmgr_misc *misc = test->priv; + struct apple_pmgr_dcs_fixture *f = test->priv; + + writel(0xa5000999, misc->devices[DEV_DCS].base); + KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_write(misc, 7), 0); + KUNIT_EXPECT_EQ(test, f->num_commands, 1U); + KUNIT_EXPECT_EQ(test, f->commands[0], 0x00000007U); + KUNIT_ASSERT_EQ(test, apple_pmgr_dcs_write(misc, 9), 0); + KUNIT_EXPECT_EQ(test, f->commands[1], 0x00000009U); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_FABRIC].base), 0xa5000077U); +} + +static void apple_pmgr_dcs_invalid_state_test(struct kunit *test) +{ + struct apple_pmgr_misc *misc = test->priv; + struct apple_pmgr_dcs_fixture *f = test->priv; + + for (u32 state = 0; state < 32; state++) { + bool valid = state == 5 || state == 6 || state == 7 || state == 9; + + KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_state_valid(misc, state), valid); + if (!valid) { + KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_write(misc, state), -EINVAL); + misc->dcs_opps[0] = (struct apple_pmgr_dcs_opp) { state, 1000 }; + misc->dcs_opps[1] = (struct apple_pmgr_dcs_opp) { 9, 2000 }; + KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), -EINVAL); + } + } + KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_write(misc, U32_MAX), -EINVAL); + KUNIT_EXPECT_EQ(test, f->num_commands, 0U); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0xa5000099U); + misc->dcs_num_opps = 4; + misc->dcs_opps[0] = (struct apple_pmgr_dcs_opp) { 5, 1000 }; + misc->dcs_opps[1] = (struct apple_pmgr_dcs_opp) { 6, 2000 }; + misc->dcs_opps[2] = (struct apple_pmgr_dcs_opp) { 7, 3000 }; + misc->dcs_opps[3] = (struct apple_pmgr_dcs_opp) { 9, 4000 }; + KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_validate(misc), 0); +} + +static void apple_pmgr_dcs_unsettled_test(struct kunit *test) +{ + struct apple_pmgr_misc *misc = test->priv; + struct apple_pmgr_dcs_fixture *f = test->priv; + struct icc_node *node = misc->dcs_data->nodes[0]; + + writel(0x97, misc->devices[DEV_DCS].base); + KUNIT_EXPECT_EQ(test, apple_pmgr_dcs_set(node, node), -EBUSY); + KUNIT_EXPECT_EQ(test, f->num_commands, 0U); + KUNIT_EXPECT_EQ(test, readl(misc->devices[DEV_DCS].base), 0x97U); +} + static struct kunit_case apple_pmgr_dcs_test_cases[] = { + KUNIT_CASE(apple_pmgr_dcs_whole_word_test), + KUNIT_CASE(apple_pmgr_dcs_invalid_state_test), + KUNIT_CASE(apple_pmgr_dcs_unsettled_test), KUNIT_CASE(apple_pmgr_dcs_table_test), KUNIT_CASE(apple_pmgr_dcs_selection_test), KUNIT_CASE(apple_pmgr_dcs_aggregation_test), diff --git a/drivers/soc/apple/apple-pmgr-misc.c b/drivers/soc/apple/apple-pmgr-misc.c index d18f4ed07d8bb..3912f45bb60d3 100644 --- a/drivers/soc/apple/apple-pmgr-misc.c +++ b/drivers/soc/apple/apple-pmgr-misc.c @@ -22,12 +22,16 @@ #include #include #include +#if IS_ENABLED(CONFIG_APPLE_PMGR_DCS_KUNIT_TEST) +#include +#endif #define APPLE_CLKGEN_PSTATE 0 #define APPLE_CLKGEN_PSTATE_DESIRED GENMASK(3, 0) #define APPLE_CLKGEN_PSTATE_ACTUAL GENMASK(7, 4) #define DCS_DEV_PSTATE_MIN_T600X 7 +#define DCS_VALID_STATES_T600X (BIT(5) | BIT(6) | BIT(7) | BIT(9)) #define SYS_DEV_PSTATE_SUSPEND 1 #define APPLE_PMGR_IDLE_POLL_MS 250 @@ -59,6 +63,7 @@ struct apple_pmgr_sys_device { struct apple_pmgr_misc_hw { u32 dev_min_ps[DEV_MAX]; + u32 dcs_valid_states; }; struct apple_pmgr_dcs_opp { @@ -85,6 +90,7 @@ struct apple_pmgr_misc { struct icc_onecell_data *dcs_data; struct apple_pmgr_dcs_opp dcs_opps[16]; unsigned int dcs_num_opps; + u32 dcs_valid_states; bool dcs_failed; bool dcs_bw_active; struct { @@ -121,13 +127,29 @@ static int apple_pmgr_dcs_select(struct apple_pmgr_misc *misc, u32 bw, u32 *stat return -ERANGE; } +static bool apple_pmgr_dcs_state_valid(struct apple_pmgr_misc *misc, u32 state) +{ + return state <= FIELD_MAX(APPLE_CLKGEN_PSTATE_DESIRED) && + (misc->dcs_valid_states & BIT(state)); +} + +static void apple_pmgr_dcs_command(u32 word, void __iomem *reg) +{ +#if IS_ENABLED(CONFIG_APPLE_PMGR_DCS_KUNIT_TEST) + KUNIT_STATIC_STUB_REDIRECT(apple_pmgr_dcs_command, word, reg); +#endif + writel(word, reg); +} + static int apple_pmgr_dcs_write(struct apple_pmgr_misc *misc, u32 state) { void __iomem *reg = misc->devices[DEV_DCS].base + APPLE_CLKGEN_PSTATE; - u32 val = readl(reg); + u32 val; - FIELD_MODIFY(APPLE_CLKGEN_PSTATE_DESIRED, &val, state); - writel(val, reg); + if (!apple_pmgr_dcs_state_valid(misc, state)) + return -EINVAL; + /* DCS takes a complete command word, not a replay of readback bits. */ + apple_pmgr_dcs_command(FIELD_PREP(APPLE_CLKGEN_PSTATE_DESIRED, state), reg); return readl_poll_timeout(reg, val, FIELD_GET(APPLE_CLKGEN_PSTATE_ACTUAL, val) == state, 10, APPLE_PMGR_RAISE_TIMEOUT_US); @@ -136,7 +158,7 @@ static int apple_pmgr_dcs_write(struct apple_pmgr_misc *misc, u32 state) static int apple_pmgr_dcs_set(struct icc_node *src, struct icc_node *dst) { struct apple_pmgr_misc *misc = src->data; - u32 state, old, bw = 0; + u32 state, old, val, bw = 0; unsigned int i; int ret; @@ -157,13 +179,19 @@ static int apple_pmgr_dcs_set(struct icc_node *src, struct icc_node *dst) ret = apple_pmgr_dcs_select(misc, bw, &state); if (ret) return ret; + if (!apple_pmgr_dcs_state_valid(misc, state)) + return -EINVAL; + old = misc->devices[DEV_DCS].active_state; + val = readl(misc->devices[DEV_DCS].base + APPLE_CLKGEN_PSTATE); + if (FIELD_GET(APPLE_CLKGEN_PSTATE_DESIRED, val) != old || + FIELD_GET(APPLE_CLKGEN_PSTATE_ACTUAL, val) != old) + return -EBUSY; /* Untracked DMA clients need the fabric as well as DCS bandwidth. */ if (bw) { ret = apple_pmgr_raise_fabric(misc); if (ret) return ret; } - old = misc->devices[DEV_DCS].active_state; ret = apple_pmgr_dcs_write(misc, state); if (ret) { /* A failed request must not replace the last confirmed vote. */ @@ -207,8 +235,7 @@ static int apple_pmgr_dcs_validate(struct apple_pmgr_misc *misc) for (i = 0; i < misc->dcs_num_opps; i++) { const struct apple_pmgr_dcs_opp *opp = &misc->dcs_opps[i]; - if (!opp->bw || opp->state < DCS_DEV_PSTATE_MIN_T600X || - opp->state > FIELD_MAX(APPLE_CLKGEN_PSTATE_DESIRED)) + if (!opp->bw || !apple_pmgr_dcs_state_valid(misc, opp->state)) return -EINVAL; if (i && (opp->state <= misc->dcs_opps[i - 1].state || opp->bw <= misc->dcs_opps[i - 1].bw)) @@ -327,6 +354,14 @@ static void apple_pmgr_sys_dev_set_pstate(struct apple_pmgr_misc *misc, dev_dbg(misc->dev, "set %d ps to pstate %d\n", dev, pstate); + if (dev == DEV_DCS && misc->dcs_valid_states) { + if (WARN_ON_ONCE(!apple_pmgr_dcs_state_valid(misc, pstate))) + return; + apple_pmgr_dcs_command(FIELD_PREP(APPLE_CLKGEN_PSTATE_DESIRED, pstate), + misc->devices[dev].base + APPLE_CLKGEN_PSTATE); + return; + } + val = readl_relaxed(misc->devices[dev].base + APPLE_CLKGEN_PSTATE); FIELD_MODIFY(APPLE_CLKGEN_PSTATE_DESIRED, &val, pstate); writel_relaxed(val, misc->devices[dev].base + APPLE_CLKGEN_PSTATE); @@ -717,6 +752,7 @@ static int apple_pmgr_misc_probe(struct platform_device *pdev) hw = of_device_get_match_data(dev); if (!hw) return -EINVAL; + misc->dcs_valid_states = hw->dcs_valid_states; ret = apple_pmgr_init_device(misc, hw, DEV_FABRIC, "fabric"); if (ret) @@ -763,6 +799,7 @@ static int apple_pmgr_misc_probe(struct platform_device *pdev) } static const struct apple_pmgr_misc_hw apple_pmgr_misc_hw_t600x = { + .dcs_valid_states = DCS_VALID_STATES_T600X, .dev_min_ps = { [DEV_FABRIC] = SYS_DEV_PSTATE_SUSPEND, [DEV_DCS] = DCS_DEV_PSTATE_MIN_T600X, From c7317452059bfcc87907a74a03dfe3a9906924e5 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 00:09:44 +1100 Subject: [PATCH 04/30] dt-bindings: opp: apple-dcs: describe the sparse T6000 raw states Use the DCS state set 5, 6, 7 and 9. Raw 8 is invalid and fabric state codes belong to a separate controller. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../bindings/opp/operating-points-v2-apple-dcs.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/opp/operating-points-v2-apple-dcs.yaml b/Documentation/devicetree/bindings/opp/operating-points-v2-apple-dcs.yaml index 28315f6f799c4..b035f0515d2a7 100644 --- a/Documentation/devicetree/bindings/opp/operating-points-v2-apple-dcs.yaml +++ b/Documentation/devicetree/bindings/opp/operating-points-v2-apple-dcs.yaml @@ -13,6 +13,8 @@ allOf: - $ref: opp-v2-base.yaml# description: | + T6000 DCS raw states are 5, 6, 7 and 9. State 8 is invalid; fabric + states are a separate controller and are not DCS states. Each OPP maps a verified raw PMGR desired performance state to measured sustainable bandwidth. Values are decimal kB/s, not DRAM clock frequencies, theoretical peak bandwidth or firmware policy thresholds. State codes and @@ -31,8 +33,7 @@ patternProperties: turbo-mode: false opp-level: description: Verified raw PMGR desired state, without a state-index bias - minimum: 7 - maximum: 15 + enum: [5, 6, 7, 9] opp-peak-kBps: description: Measured sustainable bandwidth at this raw state items: From c2db5627c82d7d54642551b4cf01acce2ab407a6 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 00:10:20 +1100 Subject: [PATCH 05/30] docs: apple-pmgr-dcs: correct the T6000 state and command contract Document the sparse raw-state allowlist, whole-word commands and settled-state requirement. The existing sleep floor remains a policy choice, not a validity floor. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/driver-api/apple-pmgr-dcs.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/driver-api/apple-pmgr-dcs.rst b/Documentation/driver-api/apple-pmgr-dcs.rst index edf6fc4af52f5..b7dd79bc7cb7c 100644 --- a/Documentation/driver-api/apple-pmgr-dcs.rst +++ b/Documentation/driver-api/apple-pmgr-dcs.rst @@ -14,8 +14,8 @@ The provider is enabled only by an explicit ``operating-points-v2`` phandle and ``#interconnect-cells = <1>`` on the existing PMGR miscellaneous node. Its table uses ``operating-points-v2-apple-dcs``. Each enabled OPP supplies a raw ``opp-level`` and one ``opp-peak-kBps`` value. The driver validates duplicate -states, zero or decreasing capacities, the T600x sleep floor of 7, and the -boot-state ceiling before publishing the provider. It does not derive states +states, zero or decreasing capacities, the sparse T6000 raw state set +``{5, 6, 7, 9}``, and the boot-state ceiling before publishing the provider. It does not derive states from child order or convert firmware thresholds to bandwidth. Existing DTs without this table retain their boot and suspend policy. @@ -23,8 +23,13 @@ The highest table entry must equal the boot desired state. That entry remains the initial vote until interconnect sync_state releases boot constraints. Nonzero bandwidth also holds the fabric at its boot state so untracked DMA clients cannot lose their fabric bandwidth to the idle governor. Requests -change only the desired low nibble and wait for the actual field to -confirm the requested state. A timeout restores the previous confirmed vote. +write the raw state as one complete 32-bit command word, with all upper +bits zero, and wait for the actual field to confirm it. Readback status +bits must not be preserved in the command. Raw 8 is invalid; raw 9 is the +highest DCS state. Fabric has a separate state map. A timeout restores the +previous confirmed vote with the same complete-word command. The current +desired and actual fields must agree with the last confirmed vote before +a new request. If restoration also times out, the fabric hold remains asserted and subsequent requests and system sleep fail with ``-EIO`` until reboot. Errors propagate to consumers, which must not start work requiring a bandwidth request that failed. Firmware ownership and system sleep reject From 2b2ec81a6744fdf693d8c460f91c72e7b03aa516 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 1 Nov 2026 09:56:17 -0500 Subject: [PATCH 06/30] arm64: dts: apple: add J614s T6040 board --- arch/arm64/boot/dts/apple/Makefile | 1 + arch/arm64/boot/dts/apple/t6040-j614s.dts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/t6040-j614s.dts diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile index 0f6e6714f7de7..11d8adbda4f11 100644 --- a/arch/arm64/boot/dts/apple/Makefile +++ b/arch/arm64/boot/dts/apple/Makefile @@ -94,6 +94,7 @@ dtb-$(CONFIG_ARCH_APPLE) += t6031-j516c.dtb dtb-$(CONFIG_ARCH_APPLE) += t6032-j575d.dtb dtb-$(CONFIG_ARCH_APPLE) += t6034-j514m.dtb dtb-$(CONFIG_ARCH_APPLE) += t6034-j516m.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6040-j614s.dtb dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s.dtb dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s-tether.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb diff --git a/arch/arm64/boot/dts/apple/t6040-j614s.dts b/arch/arm64/boot/dts/apple/t6040-j614s.dts new file mode 100644 index 0000000000000..e383de6943ef5 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6040-j614s.dts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * MacBook Pro (14-inch, M4 Pro, Nov 2024) + * + * target-type: J614s + * + * J614s uses the same T6040 platform topology as the supported J616s + * board. Keep the shared SoC resources and driver bindings in one baseline; + * board-specific firmware supplies the J614s identity at boot. + */ + +#include "t6040-j616s.dts" + +/ { + compatible = "apple,j614s", "apple,t6040", "apple,arm-platform"; + model = "Apple MacBook Pro (14-inch, M4 Pro, Nov 2024)"; +}; From 62109cc021f51b246875bd513471fa2a1bf56bf1 Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Tue, 27 Oct 2026 23:56:08 -0300 Subject: [PATCH 07/30] drm/asahi: g17: keep innocent clients alive across another client's recovery A recovery for one client's hang still killed the others: a CS2 reason-0 recovery cancelled 42 commands and Hyprland got ECANCELED from fail_unpublished(), which Honeykrisp treats as a lost device, so the desktop froze until a reboot. - ECANCELED is not reported as this client's failure from fail_unpublished(), the ordered output publication, or fail_while_owned() for spared work. Work the firmware listed as interrupted keeps reporting its failure. - Publication waits for a pending recovery, or for this owner's spared pair to drain, for up to RECOVERY_WAIT_MS (26 s) instead of the usual 2 s: the compute admission deadline extends (Admission:: extend_for_recovery) in ensure_compute, in the recovery waits of compute_packet including the input-source wait, and in Gate::enter_until, whose gate only a recovery closes; the render waits keep a WaitClock whose usual bound starts afresh once the recovery ends, and render admission extends its deadline while a recovery is pending. A spared quarantined pair waits until the caller's deadline; a guilty pair's publication still fails after a second. - Commands with failed inputs or predecessors stay cancelled: a failed fence does not prove its producer retired. The compute deadline host tests move to the longer recovery budget, and a mutant now checks that removing the extension fails them. Link: https://github.com/aurora-silicon/linux/pull/433 Co-developed-by: Ryan Murray Signed-off-by: Ryan Murray Signed-off-by: Jonathan Goyvaerts Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/completion.rs | 8 +- .../drm/asahi/g17/completion/publication.rs | 4 +- drivers/gpu/drm/asahi/g17/dependency.rs | 11 ++ drivers/gpu/drm/asahi/g17/preparation.rs | 51 +++++-- drivers/gpu/drm/asahi/g17/queue/admission.rs | 38 +++-- drivers/gpu/drm/asahi/g17/runtime.rs | 19 ++- .../drm/asahi/g17/runtime/render/submit.rs | 130 +++++++++++++++--- drivers/gpu/drm/asahi/g17/timeout.rs | 7 + include/uapi/drm/asahi_drm.h | 5 +- tools/asahi/g17-admission-deadline-tests.py | 2 +- tools/asahi/g17-compute-constructor-tests.py | 4 +- tools/asahi/g17-compute-deadline-tests.py | 20 +-- .../g17-compute-growth-deadline-tests.py | 6 +- 13 files changed, 248 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/completion.rs b/drivers/gpu/drm/asahi/g17/completion.rs index 7d887f17d22c6..90f6d77780bff 100644 --- a/drivers/gpu/drm/asahi/g17/completion.rs +++ b/drivers/gpu/drm/asahi/g17/completion.rs @@ -694,7 +694,9 @@ impl Completion { return; } self.status.record_if_device_loss(error); - if self.accepted.load(Ordering::Acquire) { + // As in complete(): a cancellation is not this client's device loss. A guilty VM + // already carries its own sticky error. + if self.accepted.load(Ordering::Acquire) && error != ECANCELED { self.status.report_failure(error); } self.release_healthy(); @@ -713,7 +715,9 @@ impl Completion { guard.quarantine(); } if self.spared() { - self.status.report_failure(error); + if error != ECANCELED || self.interrupted() { + self.status.report_failure(error); + } } else { self.report(error); } diff --git a/drivers/gpu/drm/asahi/g17/completion/publication.rs b/drivers/gpu/drm/asahi/g17/completion/publication.rs index a1e05a9408552..fc4b4f8739015 100644 --- a/drivers/gpu/drm/asahi/g17/completion/publication.rs +++ b/drivers/gpu/drm/asahi/g17/completion/publication.rs @@ -96,7 +96,9 @@ impl Publication { if let Err(error) = result { // Inherited failure is visible to the client without inventing a // permanent admission failure in this command's VM. - self.status.report_failure(error); + if error != ECANCELED { + self.status.report_failure(error); + } } self.output.publish(result); let result = result.map(|_| ()); diff --git a/drivers/gpu/drm/asahi/g17/dependency.rs b/drivers/gpu/drm/asahi/g17/dependency.rs index 387ce9ba2fb3c..b7cb74a59644f 100644 --- a/drivers/gpu/drm/asahi/g17/dependency.rs +++ b/drivers/gpu/drm/asahi/g17/dependency.rs @@ -195,4 +195,15 @@ mod tests { record.note(5, 1, &Probe::new(true)); assert_eq!(earliest(&mut record), None); } + + #[test] + fn failed_predecessor_cannot_be_bypassed() { + let mut frontier = Frontier::new(); + for error in [ECANCELED, EIO, ETIMEDOUT] { + assert_eq!(frontier.include(Some(5), 5, error.to_errno(), 0), Err(ECANCELED)); + } + assert_eq!(frontier.include(Some(5), 5, 1, 0), Ok(())); + assert_eq!(frontier.include(Some(5), 6, EIO.to_errno(), 0), Ok(())); + assert_eq!(frontier.count, 0); + } } diff --git a/drivers/gpu/drm/asahi/g17/preparation.rs b/drivers/gpu/drm/asahi/g17/preparation.rs index 2aa3213115076..c7d1761699a24 100644 --- a/drivers/gpu/drm/asahi/g17/preparation.rs +++ b/drivers/gpu/drm/asahi/g17/preparation.rs @@ -6,6 +6,7 @@ //! the device mutex, then drops that mutex before waiting for builders. A builder's lease is //! stored after its tentative mappings so those mappings are destroyed before the join completes. +use core::cell::Cell; use core::sync::atomic::{AtomicU64, Ordering}; use kernel::{ new_condvar, new_mutex, @@ -24,25 +25,37 @@ const EPOCH: u64 = CLOSED << 3; /// packet cancellation, logical close, VM status and device loss even when no /// capacity/recovery event arrives. Wakes and nested constructors never renew it. pub(super) struct Admission<'a> { - deadline: u64, + deadline: Cell, + started: u64, check: &'a dyn Fn() -> Result, } impl<'a> Admission<'a> { pub(super) fn new(deadline: u64, check: &'a dyn Fn() -> Result) -> Self { - Self { deadline, check } + Self { deadline: Cell::new(deadline), started: super::now_ns(), check } + } + + /// A recovery is pending or this owner's spared work drains: wait as long as the + /// watchdog may take to settle it (RECOVERY_WAIT_MS from the start) instead of failing. + pub(super) fn extend_for_recovery(&self) { + let limit = self + .started + .saturating_add(u64::from(super::timeout::RECOVERY_WAIT_MS) * 1_000_000); + if self.deadline.get() < limit { + self.deadline.set(limit); + } } pub(super) fn check(&self) -> Result { (self.check)()?; - if super::now_ns() >= self.deadline { return Err(EBUSY); } + if super::now_ns() >= self.deadline.get() { return Err(EBUSY); } Ok(()) } /// Ignore condvar remaining-time reports: only the monotonic deadline counts. pub(super) fn slice_ms(&self) -> Result { self.check()?; - let remaining = self.deadline.saturating_sub(super::now_ns()); + let remaining = self.deadline.get().saturating_sub(super::now_ns()); if remaining == 0 { return Err(EBUSY); } Ok(remaining.div_ceil(1_000_000).min(10) as u32) } @@ -137,19 +150,20 @@ impl Gate { } } - /// Packet admission may wait for recovery only within its original budget. + /// Only a recovery closes the gate: packet admission waits for it up to + /// RECOVERY_WAIT_MS from the admission's start (Admission::extend_for_recovery). /// Called with no device mutex or existing preparation lease. pub(super) fn enter_until(self: &Arc, admission: &Admission<'_>) -> Result { admission.check()?; match self.try_enter() { - Err(error) if error == EAGAIN => {} + Err(error) if error == EAGAIN => admission.extend_for_recovery(), result => return result, } let mut wait = self.wait.lock(); loop { admission.check()?; match self.try_enter() { - Err(error) if error == EAGAIN => {} + Err(error) if error == EAGAIN => admission.extend_for_recovery(), result => return result, } let _ = self.changed.wait_timeout( @@ -322,6 +336,20 @@ mod growth_gate_tests { mod compute_deadline_tests { use super::*; + #[test] + fn a_recovery_extends_the_deadline_once_from_the_start() { + let check = || Ok(()); + let admission = Admission::new(super::super::now_ns(), &check); + assert_eq!(admission.check(), Err(EBUSY)); + admission.extend_for_recovery(); + assert_eq!(admission.check(), Ok(())); + let limit = admission.started + + u64::from(super::super::timeout::RECOVERY_WAIT_MS) * 1_000_000; + assert_eq!(admission.deadline.get(), limit); + admission.extend_for_recovery(); + assert_eq!(admission.deadline.get(), limit); + } + #[test] fn spent_budget_refuses_even_an_open_gate() { let gate = Gate::new().unwrap(); @@ -332,13 +360,16 @@ mod compute_deadline_tests { } #[test] - fn closed_gate_expires_without_any_recovery_wake() { + fn closed_gate_waits_past_the_budget_for_recovery() { let gate = Gate::new().unwrap(); gate.close(); let start = super::super::now_ns(); - let check = || Ok(()); + // Cancel well after the 20 ms budget: only the recovery extension keeps it waiting. + let check = || { + if super::super::now_ns() - start >= 60_000_000 { Err(ECANCELED) } else { Ok(()) } + }; let admission = Admission::new(start + 20_000_000, &check); - assert!(matches!(gate.enter_until(&admission), Err(error) if error == EBUSY)); + assert!(matches!(gate.enter_until(&admission), Err(error) if error == ECANCELED)); assert!(super::super::now_ns() - start < 500_000_000); gate.wait_drained(); assert!(gate.reopen()); diff --git a/drivers/gpu/drm/asahi/g17/queue/admission.rs b/drivers/gpu/drm/asahi/g17/queue/admission.rs index ed73323e34e6f..f09e629aa047e 100644 --- a/drivers/gpu/drm/asahi/g17/queue/admission.rs +++ b/drivers/gpu/drm/asahi/g17/queue/admission.rs @@ -494,6 +494,16 @@ impl Pool { } } } + /// The owner's pair is quarantined behind another VM's recovery (spared) and drains. + pub(crate) fn spared_quarantined(&self, owner: u64) -> bool { + let state = self.state.lock(); + state + .owners + .iter() + .position(|id| *id == owner) + .is_some_and(|slot| state.quarantined & state.spared & (1u64 << slot) != 0) + } + pub(crate) fn acquire_render( self: &Arc, owner: u64, @@ -525,17 +535,22 @@ impl Pool { .position(|id| *id == owner) .is_some_and(|slot| state.quarantined & (1u64 << slot) != 0) { - if now >= quarantine_deadline { - // Behind an innocent pair's quarantine (another VM wedged the GPU) only this - // publication fails, not the VM: it recovers once the pair drains. - let spared = state - .owners - .iter() - .position(|id| *id == owner) - .is_some_and(|slot| state.spared & (1u64 << slot) != 0); - return Err(if spared { EBUSY } else { EIO }); + // Behind an innocent pair's quarantine (another VM wedged the GPU) wait for the + // pair to drain until the caller's deadline, which the caller extends while it + // drains (RECOVERY_WAIT_MS); a guilty pair's publication fails after a second. + let spared = state + .owners + .iter() + .position(|id| *id == owner) + .is_some_and(|slot| state.spared & (1u64 << slot) != 0); + if now >= quarantine_deadline && !spared { + return Err(EIO); } - let slice = remaining_ms.min(wait_slice_ms(quarantine_deadline, now)?); + let slice = if spared { + remaining_ms + } else { + remaining_ms.min(wait_slice_ms(quarantine_deadline, now)?) + }; if matches!(self.released.wait_interruptible_timeout( &mut state, kernel::time::msecs_to_jiffies(slice) ), CondVarTimeoutResult::Signal { .. }) { @@ -1001,10 +1016,13 @@ mod fault_capacity_tests { let queue = pool.reserve_queue().unwrap(); let lease = pool.acquire_render(1, None, u64::MAX, || Ok(())).unwrap(); let bit = 1u64 << lease.slot(); + assert!(!pool.spared_quarantined(1)); lease.quarantine(true); assert_ne!(pool.state.lock().spared & bit, 0); + assert!(pool.spared_quarantined(1)); lease.quarantine(false); assert_eq!(pool.state.lock().spared & bit, 0); + assert!(!pool.spared_quarantined(1)); // Once guilty, a later spared failure does not make it spared again. lease.quarantine(true); assert_eq!(pool.state.lock().spared & bit, 0); diff --git a/drivers/gpu/drm/asahi/g17/runtime.rs b/drivers/gpu/drm/asahi/g17/runtime.rs index a6edfd0a80b38..d391fbb3001bb 100644 --- a/drivers/gpu/drm/asahi/g17/runtime.rs +++ b/drivers/gpu/drm/asahi/g17/runtime.rs @@ -1756,8 +1756,10 @@ impl Backend { } } // Other owners continue submitting while this owner waits for its - // queue-local drain witness. No new command has been accepted: the - // spared queue and its retained work stay intact past the deadline. + // queue-local drain witness. No new command has been accepted; if the + // admission deadline passes, the spared queue and its retained work stay + // intact until a later admission observes its witness. + admission.extend_for_recovery(); self.shared.queue_events(); self.wait_release(&self.shared.progress, observed, admission)?; } @@ -2090,6 +2092,13 @@ impl Backend { admission.check()?; let observed = progress.observe(); if !self.sources_published(packet)? { + let recovering = self.shared.state.lock().as_deref().is_some_and(|firmware| { + firmware.recovery.pending() + || super::recovery::Memory::recovery_state(&firmware.init).unwrap_or(0) != 0 + }); + if recovering { + admission.extend_for_recovery(); + } self.shared.queue_events(); self.wait_release(progress, observed, &admission)?; continue; @@ -2131,6 +2140,7 @@ impl Backend { } let recovery_state = super::recovery::Memory::recovery_state(&firmware.init)?; if firmware.recovery.pending() || recovery_state != 0 { + admission.extend_for_recovery(); drop(state); drop(lease); self.shared.queue_events(); @@ -2162,6 +2172,11 @@ impl Backend { { // Recovery or backpressure: nothing is firmware-visible. Other // queues keep submitting and the event worker retires meanwhile. + if firmware.recovery.pending() + || super::recovery::Memory::recovery_state(&firmware.init)? != 0 + { + admission.extend_for_recovery(); + } drop(state); drop(lease); self.shared.queue_events(); diff --git a/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs b/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs index f1a433ddb0db4..970035547dfee 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render/submit.rs @@ -54,13 +54,22 @@ impl Backend { ) -> Result<(RenderLease, bool)> { let pool = &self.admission; let started = super::super::now_ns(); - let deadline_ns = started.saturating_add(u64::from(crate::g17::job::TIMEOUT_MS) * 1_000_000); + let mut deadline_ns = + started.saturating_add(u64::from(crate::g17::job::TIMEOUT_MS) * 1_000_000); + let recovery_deadline_ns = started.saturating_add(recovery_wait_ns()); // A capacity refusal samples the render domain and retries once; only // a refusal after that sample waits, so no release can be missed. let mut observed = None; + let mut capacity = WaitClock::default(); loop { self.admission_open()?; - if super::super::now_ns() >= deadline_ns { return Err(EBUSY); } + if super::super::now_ns() >= deadline_ns { + if deadline_ns < recovery_deadline_ns && self.waiting_for_recovery()? { + deadline_ns = recovery_deadline_ns; + } else { + return Err(EBUSY); + } + } if packet.cancel_requested() { return Err(ECANCELED); } @@ -95,11 +104,13 @@ impl Backend { .reclaim_unused_render_spare(Some(self.owner), false)? }; if reclaimed.is_none() { - self.wait_render_capacity(&mut observed, started, sleep)?; + self.wait_render_capacity(&mut observed, &mut capacity, sleep)?; } drop(reclaimed); continue; } + // The deadline check above decides whether a recovery extends the wait. + Err(error) if error == EBUSY => continue, Err(error) => return Err(error), } }; @@ -133,7 +144,7 @@ impl Backend { drop(state); // A full queue table makes admission reuse a free killed pair instead. if reclaimed.is_none() && !ready && !pool.killed_pair_free() { - self.wait_render_capacity(&mut observed, started, sleep)?; + self.wait_render_capacity(&mut observed, &mut capacity, sleep)?; } drop(reclaimed); } @@ -221,13 +232,29 @@ impl Backend { domain.block(observed, remaining.div_ceil(1_000_000).min(10) as u32, false) } + /// A recovery is pending, or this owner's pair is spared and draining after one: an + /// innocent publication waits up to RECOVERY_WAIT_MS for it instead of failing. + fn waiting_for_recovery(&self) -> Result { + let state = self.shared.state.lock(); + let firmware = (*state).as_deref().ok_or(ENODEV)?; + Ok(firmware.recovery.pending() + || crate::g17::recovery::Memory::recovery_state(&firmware.init)? != 0 + || firmware.queues.admission.spared_quarantined(self.owner)) + } + + /// The deadline of the wait `clock` measures (WaitClock::deadline). + fn wait_deadline(&self, clock: &mut WaitClock, bound_ns: u64) -> Result { + let recovering = self.waiting_for_recovery()?; + Ok(clock.deadline(super::super::now_ns(), recovering, bound_ns)) + } + /// A pair is borrowed, still settling, waiting for firmware room or for the /// release of the scheduler it last ran for. Every pair return, retirement /// and teardown step signals the render domain, and each event pass does /// while a publisher waits; the deadline is only the failure bound. fn wait_render_busy( &self, - since: &mut Option, + clock: &mut WaitClock, observed: &mut Option, sleep: bool, packet: &Packet, @@ -235,8 +262,7 @@ impl Backend { if !sleep { return Err(FALLBACK); } - let now = super::super::now_ns(); - let deadline = since.get_or_insert(now).saturating_add(super::super::WORK_RING_WAIT_NS); + let deadline = self.wait_deadline(clock, super::super::WORK_RING_WAIT_NS)?; // Sample, then retry once: only a refusal after the sample waits. let Some(sample) = observed.take() else { *observed = Some(self.shared.renders.observe()); @@ -249,7 +275,12 @@ impl Backend { /// Render capacity is short: return what idle owners hold, then block until /// a pair, lease or owner returns. No packet is firmware-visible here, and /// neither the device mutex nor a preparation lease is held while waiting. - fn wait_render_capacity(&self, observed: &mut Option, started: u64, sleep: bool) -> Result { + fn wait_render_capacity( + &self, + observed: &mut Option, + clock: &mut WaitClock, + sleep: bool, + ) -> Result { if !sleep { return Err(FALLBACK); } @@ -263,8 +294,8 @@ impl Backend { }; self.shared.queue_events(); self.admission_open()?; - let remaining = started - .saturating_add(super::super::WORK_RING_WAIT_NS) + let remaining = self + .wait_deadline(clock, super::super::WORK_RING_WAIT_NS)? .saturating_sub(super::super::now_ns()); if remaining == 0 { return Err(EBUSY); @@ -367,9 +398,10 @@ impl Backend { return result; } } - let mut busy_since = None; - let capacity_started = super::super::now_ns(); - let deadline_ns = capacity_started.saturating_add(super::super::WORK_RING_WAIT_NS); + let mut busy = WaitClock::default(); + let mut capacity = WaitClock::default(); + let mut state_clock = WaitClock::default(); + let mut recovery_since = None; let mut seen = None; let mut states = None; loop { @@ -407,8 +439,11 @@ impl Backend { let work = packet.completion.work_state()?; match states.take() { None => states = Some(work.returns().observe()), + // Work states free as earlier, possibly spared, work retires. Some(sample) => { - self.wait_domain(work.returns(), sample, deadline_ns, packet)? + let deadline = self + .wait_deadline(&mut state_clock, super::super::WORK_RING_WAIT_NS)?; + self.wait_domain(work.returns(), sample, deadline, packet)? } } continue; @@ -426,19 +461,19 @@ impl Backend { }; match ensured { Err(error) if error == ENOSPC => { - self.wait_render_capacity(&mut seen, capacity_started, sleep)?; + self.wait_render_capacity(&mut seen, &mut capacity, sleep)?; continue; } Err(error) if error == EAGAIN => continue, Err(error) if error == EBUSY => { - self.wait_render_busy(&mut busy_since, &mut seen, sleep, packet)?; + self.wait_render_busy(&mut busy, &mut seen, sleep, packet)?; continue; } result => result?, } match self.prepare_render(slot, packet, deferred, sleep) { Err(error) if !packet.is_published() && error == ENOSPC => { - self.wait_render_capacity(&mut seen, capacity_started, sleep)?; + self.wait_render_capacity(&mut seen, &mut capacity, sleep)?; continue; } Err(error) if !packet.is_published() && [EAGAIN, EBUSY].contains(&error) => { @@ -458,10 +493,13 @@ impl Backend { // Recovery closes the preparation gate before touching // any queue; the next pass blocks in enter() until it // reopens. The worker raises the closure. + let since = *recovery_since.get_or_insert(super::super::now_ns()); self.shared.queue_events(); - self.shared.preparations.wait_closure(gate, deadline_ns)?; + self.shared + .preparations + .wait_closure(gate, since.saturating_add(recovery_wait_ns()))?; } else if error == EBUSY { - self.wait_render_busy(&mut busy_since, &mut seen, sleep, packet)?; + self.wait_render_busy(&mut busy, &mut seen, sleep, packet)?; } else if !sleep { return Err(FALLBACK); } @@ -699,3 +737,57 @@ impl crate::g17::Firmware { result } } + +fn recovery_wait_ns() -> u64 { + u64::from(crate::g17::timeout::RECOVERY_WAIT_MS) * 1_000_000 +} + +/// One bounded wait (Backend::wait_deadline). +#[derive(Default)] +struct WaitClock { + first: Option, + calm: u64, +} + +impl WaitClock { + /// `bound_ns` after the later of the first call and the last call that saw a recovery + /// pending or this owner's spared pair draining (so the usual bound applies afresh once a + /// recovery ends), and at most RECOVERY_WAIT_MS after the first call. + fn deadline(&mut self, now: u64, recovering: bool, bound_ns: u64) -> u64 { + let first = *self.first.get_or_insert(now); + self.calm = if recovering { now } else { self.calm.max(first) }; + first + .saturating_add(recovery_wait_ns()) + .min(self.calm.saturating_add(bound_ns)) + } +} + +#[kunit_tests(asahi_g17_wait_clock)] +mod tests { + use super::*; + + const BOUND: u64 = 2_000_000_000; + + #[test] + fn a_calm_wait_keeps_its_first_deadline() { + let mut clock = WaitClock::default(); + assert_eq!(clock.deadline(100, false, BOUND), 100 + BOUND); + assert_eq!(clock.deadline(500, false, BOUND), 100 + BOUND); + } + + #[test] + fn the_bound_starts_afresh_once_a_recovery_ends() { + let mut clock = WaitClock::default(); + clock.deadline(100, false, BOUND); + assert_eq!(clock.deadline(1_000, true, BOUND), 1_000 + BOUND); + assert_eq!(clock.deadline(2_000, false, BOUND), 1_000 + BOUND); + } + + #[test] + fn a_recovery_cannot_extend_past_the_recovery_wait() { + let mut clock = WaitClock::default(); + clock.deadline(100, true, BOUND); + let limit = 100 + recovery_wait_ns(); + assert_eq!(clock.deadline(limit - 1, true, BOUND), limit); + } +} diff --git a/drivers/gpu/drm/asahi/g17/timeout.rs b/drivers/gpu/drm/asahi/g17/timeout.rs index b83ea5d07f406..ce003e6b18196 100644 --- a/drivers/gpu/drm/asahi/g17/timeout.rs +++ b/drivers/gpu/drm/asahi/g17/timeout.rs @@ -18,6 +18,13 @@ pub(crate) fn transaction_grace(renewals: u8) -> bool { renewals < MAX_RENEWALS pub(crate) const POLL_MS: u32 = 2000; const EXECUTION_RENEWALS: u8 = 4; +/// How long an innocent client's publication waits for a recovery to settle and its +/// spared work to drain, instead of failing with EBUSY: the watchdog defers work behind +/// another VM at most this long before it settles it (`classify`; ray work gets the +/// execution renewals on top). +pub(crate) const RECOVERY_WAIT_MS: u32 = + POLL_MS * (1 + EXECUTION_RENEWALS as u32 + MAX_RENEWALS as u32); + /// A background control/replay poll has no scheduler fence to expire it. /// Its timestamp belongs to the exact transaction and is never renewed by work. pub(crate) fn background_stalled(since: Option, now: u64) -> bool { diff --git a/include/uapi/drm/asahi_drm.h b/include/uapi/drm/asahi_drm.h index dc34315ec8c82..418522205ee6f 100644 --- a/include/uapi/drm/asahi_drm.h +++ b/include/uapi/drm/asahi_drm.h @@ -445,7 +445,10 @@ enum drm_asahi_feature { * The driver publishes an accepted job's failure before signaling its fence. * Thus userspace must query after a successful DRM syncobj wait/poll before * treating that fence as successful execution. Syncobj signal alone is not - * an execution-success indication. + * an execution-success indication. A job cancelled (ECANCELED) because + * another VM's recovery discarded it, an input did not signal in time, or + * its queue was closed, is not a failure of this VM: its fence carries the + * error, error stays 0, and its output is undefined. * The feature bit is the availability contract; older kernels leave it zero. * Kernel errors for invalid requests: EINVAL flags/pad/size, ENOENT foreign or * missing vm_id, EFAULT inaccessible pointer. No output on invalid request. diff --git a/tools/asahi/g17-admission-deadline-tests.py b/tools/asahi/g17-admission-deadline-tests.py index b58715f6fcc58..d6e63e08d1188 100644 --- a/tools/asahi/g17-admission-deadline-tests.py +++ b/tools/asahi/g17-admission-deadline-tests.py @@ -91,7 +91,7 @@ def fixture(): assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"quarantine budget extended"); reset(mode);let pool=new_pool(false,2);pool.state.lock().spared=2; assert!(matches!(pool.acquire_render(1,None,2_000_000_000,||Ok(())),Err(EBUSY))); - assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"spared quarantine budget extended"); + assert!((2_000_000_000..=2_010_000_000).contains(&NOW.load(Ordering::SeqCst)),"a spared quarantine waits for the caller's deadline"); } reset(2);let pool=new_pool(true,0); assert!(matches!(pool.acquire_render(1,Some(Request(&())),2_000_000_000,||Ok(())),Err(ERESTARTSYS))); diff --git a/tools/asahi/g17-compute-constructor-tests.py b/tools/asahi/g17-compute-constructor-tests.py index 8c0c4dc772885..14b736f50436b 100644 --- a/tools/asahi/g17-compute-constructor-tests.py +++ b/tools/asahi/g17-compute-constructor-tests.py @@ -27,14 +27,16 @@ def fixture(): runtime = (ROOT / 'runtime.rs').read_text() preparation = (ROOT / 'preparation.rs').read_text() admission = (item(preparation, "pub(super) struct Admission<'a>") + '\n' + - item(preparation, "impl<'a> Admission<'a>")).replace('pub(super) ', '').replace('super::now_ns()', 'now_ns()') + item(preparation, "impl<'a> Admission<'a>")).replace('pub(super) ', '').replace('super::now_ns()', 'now_ns()').replace('super::timeout::', '') methods = '\n'.join(item(runtime, sig) for sig in [' fn shared_compute(', ' fn compute_scheduler(']) methods = methods.replace('super::object::Allocator', 'Allocator').replace('super::preparation::Lease', 'Lease').replace('super::initdata::', '') return r''' #![allow(dead_code)] +use std::cell::Cell; use std::sync::{Mutex,MutexGuard,atomic::{AtomicBool,AtomicU64,Ordering}}; type Result = std::result::Result; const EIO:i32=-5;const EAGAIN:i32=-11;const ENODEV:i32=-19;const EBUSY:i32=-16;const ECANCELED:i32=-125; +const RECOVERY_WAIT_MS:u32=26_000; static NOW:AtomicU64=AtomicU64::new(0);static CANCEL:AtomicBool=AtomicBool::new(false);static ALLOCATE:AtomicBool=AtomicBool::new(false); fn now_ns()->u64{NOW.load(Ordering::SeqCst)} mod kernel{pub mod time{pub fn msecs_to_jiffies(ms:u32)->u32{ms}}} diff --git a/tools/asahi/g17-compute-deadline-tests.py b/tools/asahi/g17-compute-deadline-tests.py index 98c8751773f9b..0b021d1b8f810 100644 --- a/tools/asahi/g17-compute-deadline-tests.py +++ b/tools/asahi/g17-compute-deadline-tests.py @@ -37,19 +37,21 @@ def fixture(): packet = packet.replace(old, new) ensure = ensure.replace(old, new) admission = (item(preparation, "pub(super) struct Admission<'a>") + '\n' + - item(preparation, "impl<'a> Admission<'a>")).replace('pub(super) ', '').replace('super::now_ns()', 'now_ns()') + item(preparation, "impl<'a> Admission<'a>")).replace('pub(super) ', '').replace('super::now_ns()', 'now_ns()').replace('super::timeout::', '') gate = item(preparation, 'pub(super) fn enter_until(').replace('pub(super) ', '') return r''' #![allow(dead_code,unused_variables,irrefutable_let_patterns)] +use std::cell::Cell; use std::sync::{Arc,Mutex,MutexGuard,atomic::{AtomicBool,AtomicU64,AtomicUsize,Ordering}}; type Result = std::result::Result; const EINVAL:i32=-22;const ECANCELED:i32=-125;const EIO:i32=-5;const ENODEV:i32=-19;const EBUSY:i32=-16;const EAGAIN:i32=-11; const RECOVERY_SETTLE_POLLS:usize=200;const WORK_RING_WAIT_NS:u64=2_000_000_000; +const RECOVERY_WAIT_MS:u32=26_000;const RECOVERY_WAIT_NS:u64=RECOVERY_WAIT_MS as u64*1_000_000; static NOW:AtomicU64=AtomicU64::new(0);static CALLS:AtomicUsize=AtomicUsize::new(0); static MODE:AtomicUsize=AtomicUsize::new(0);static CANCEL:AtomicU64=AtomicU64::new(u64::MAX); static STATUS:AtomicU64=AtomicU64::new(u64::MAX);static CLOSED:AtomicBool=AtomicBool::new(false); fn now_ns()->u64{NOW.load(Ordering::SeqCst)} -fn advance(ns:u64){NOW.fetch_add(ns,Ordering::SeqCst);assert!(now_ns()<=6_000_000_000,"admission budget exceeded");} +fn advance(ns:u64){NOW.fetch_add(ns,Ordering::SeqCst);assert!(now_ns()<=RECOVERY_WAIT_NS+4_000_000_000,"admission budget exceeded");} mod kernel{pub mod time{ pub fn msecs_to_jiffies(ms:u32)->u32{ms} pub struct Delta(pub u64);impl Delta{pub fn from_millis(ms:u64)->Self{Self(ms*1_000_000)}pub fn from_micros(us:i64)->Self{Self(us as u64*1_000)}} @@ -59,7 +61,7 @@ def fixture(): struct Wait; impl Wait{fn lock(&self){}} struct Changed; -impl Changed{fn wait_timeout(&self,_:&mut (),_:u32){advance(1_000_000);assert!(now_ns()<=WORK_RING_WAIT_NS,"gate wake storm must remain bounded");}} +impl Changed{fn wait_timeout(&self,_:&mut (),_:u32){advance(1_000_000);assert!(now_ns()<=RECOVERY_WAIT_NS,"gate wake storm must remain bounded");}} struct Gate{wait:Wait,changed:Changed} struct Lease; impl Lease{fn is_current(&self)->bool{true}} @@ -120,7 +122,8 @@ def fixture(): MODE.store(mode,Ordering::SeqCst);NOW.store(0,Ordering::SeqCst);CALLS.store(0,Ordering::SeqCst); let r=b.compute_packet(&p,&mut DeferredBatch); assert_eq!(r,Err(EBUSY),"one admission budget covers binding theft/recovery/publication"); - assert!(now_ns()<=WORK_RING_WAIT_NS+10_000_000,"admission budget exceeded"); + let budget=if mode==1{RECOVERY_WAIT_NS}else{WORK_RING_WAIT_NS}; + assert!(now_ns()>=budget&&now_ns()<=budget+10_000_000,"admission budget exceeded"); if mode==0{assert_eq!(CALLS.load(Ordering::SeqCst),4);assert_eq!(now_ns(),WORK_RING_WAIT_NS);} println!("PASS mode={} result={:?} calls={} elapsed_ms={}",mode,r,CALLS.load(Ordering::SeqCst),now_ns()/1_000_000); } @@ -147,8 +150,8 @@ def fixture(): NOW.store(0,Ordering::SeqCst);CLOSED.store(true,Ordering::SeqCst); let check=||{if cancel&&now_ns()>=10_000_000{Err(ECANCELED)}else{Ok(())}}; let admission=Admission::new(20_000_000,&check); - assert!(matches!(b.shared.preparations.enter_until(&admission),Err(e) if e==if cancel{ECANCELED}else{EBUSY}),"gate wake storm must check original deadline and cancellation"); - assert_eq!(now_ns(),if cancel{10_000_000}else{20_000_000}); + assert!(matches!(b.shared.preparations.enter_until(&admission),Err(e) if e==if cancel{ECANCELED}else{EBUSY}),"gate wake storm must check the recovery deadline and cancellation"); + assert_eq!(now_ns(),if cancel{10_000_000}else{RECOVERY_WAIT_NS}); } println!("PASS spent budgets, nested capacity cancellation/status, gate recovery wake storms"); } @@ -160,12 +163,13 @@ def main(): mutants = [ ('fixed', source, ''), ('renew-on-binding-loss', source.replace('let admission = Admission::new(deadline, &check);\n let progress = &self.shared.progress;\n loop {', 'let progress = &self.shared.progress;\n loop {\n let admission = Admission::new(now_ns().saturating_add(WORK_RING_WAIT_NS), &check);'), 'admission budget exceeded'), - ('ignore-deadline', source.replace('if now_ns() >= self.deadline', 'if false'), 'admission budget exceeded'), + ('ignore-deadline', source.replace('if now_ns() >= self.deadline.get()', 'if false'), 'admission budget exceeded'), ('render-work-state', source.replace('acquire(crate::g17::job::Engine::Compute)', 'acquire(crate::g17::job::Engine::Render)'), 'compute admission takes a compute work state'), ('omit-gate-check', source.replace(' admission.check()?;\n match self.try_enter()', ' match self.try_enter()').replace('admission.slice_ms()?', '10'), 'gate wake storm'), + ('no-recovery-extension', source.replace('admission.extend_for_recovery();', ''), 'admission budget exceeded'), ] # Bind the old unbounded mutant with the review's eventual cancellation. - mutants[1] = (mutants[1][0], mutants[1][1].replace('for mode in 0..4 {', 'CANCEL.store(10_000_000_000,Ordering::SeqCst);\n for mode in 0..4 {'), mutants[1][2]) + mutants[1] = (mutants[1][0], mutants[1][1].replace('for mode in 0..4 {', 'CANCEL.store(40_000_000_000,Ordering::SeqCst);\n for mode in 0..4 {'), mutants[1][2]) with tempfile.TemporaryDirectory(prefix='g17-compute-deadline-') as tmp: for name, code, expected in mutants: if name != 'fixed': diff --git a/tools/asahi/g17-compute-growth-deadline-tests.py b/tools/asahi/g17-compute-growth-deadline-tests.py index 3c652a96df759..f2c148e5ec46a 100644 --- a/tools/asahi/g17-compute-growth-deadline-tests.py +++ b/tools/asahi/g17-compute-growth-deadline-tests.py @@ -27,15 +27,17 @@ def fixture(): runtime = (ROOT / 'runtime.rs').read_text() prep = (ROOT / 'preparation.rs').read_text() admission = (item(prep, "pub(super) struct Admission<'a>") + '\n' + - item(prep, "impl<'a> Admission<'a>")).replace('pub(super) ', '').replace('super::now_ns()', 'now_ns()') + item(prep, "impl<'a> Admission<'a>")).replace('pub(super) ', '').replace('super::now_ns()', 'now_ns()').replace('super::timeout::', '') methods = '\n'.join(item(runtime, sig) for sig in [' fn grow_compute(', ' fn finish_uninstalled_compute(']) methods = methods.replace('super::object::Allocator', 'Allocator').replace('super::freelist::Pages', 'Pages') return r''' #![allow(dead_code)] +use std::cell::Cell; use std::sync::{Mutex,MutexGuard,atomic::{AtomicBool,AtomicU64,Ordering,fence}}; type Result = std::result::Result; type KBox = Box; const EIO:i32=-5;const EAGAIN:i32=-11;const ENODEV:i32=-19;const EBUSY:i32=-16;const ECANCELED:i32=-125; +const RECOVERY_WAIT_MS:u32=26_000; static NOW:AtomicU64=AtomicU64::new(0);static ALIAS:AtomicBool=AtomicBool::new(false);static PAGES:AtomicBool=AtomicBool::new(false); static RESERVED:AtomicBool=AtomicBool::new(false);static ABORTED:AtomicBool=AtomicBool::new(false); fn now_ns()->u64{NOW.load(Ordering::SeqCst)} @@ -112,7 +114,7 @@ def fixture(): def main(): source = fixture() mutants = [('fixed', source, ''), - ('ignore-shared-deadline', source.replace('if now_ns() >= self.deadline', 'if false').replace('if remaining == 0 { return Err(EBUSY); }', ''), 'growth admission exceeded'), + ('ignore-shared-deadline', source.replace('if now_ns() >= self.deadline.get()', 'if false').replace('if remaining == 0 { return Err(EBUSY); }', ''), 'growth admission exceeded'), ('keep-growth-alias', source.replace(' drop(alias.take());', ''), 'growth aliases must precede')] with tempfile.TemporaryDirectory(prefix='g17-compute-growth-') as tmp: for name, code, expected in mutants: From 432f851c8e3195e77f1c334574130f1743e23407 Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Wed, 28 Oct 2026 09:44:12 -0300 Subject: [PATCH 08/30] drm/asahi: g17: cancel consumers of a failed producer whatever it failed with A command whose input fence, ordered predecessor or compute source failed inherits the failure through inherited_error(). Only EIO, ENODEV and ETIMEDOUT became ECANCELED; any other error passed through unchanged and was reported as the consumer's own failure, which userspace treats as a lost device. A GPU recovery fails the render groups it dropped and cannot replay with ENODATA. One such recovery (render slot 10, 11 groups of a muvm GPU worker) took down Xwayland, whose next command consumed one of the worker's buffers. Xwayland's own commands then failed, so its buffers kept that failure in their fences, and 85 minutes later a newly started muvm GPU worker that imported them failed with ENODATA too, without any recovery. Cancel the consumer for every producer failure. ECANCELED is not reported as a device loss and does not poison the consumer's VM; its fence still carries the error, as the UAPI documents. An interrupted fence wait is not a producer failure and keeps its own error. G15 reports every input failure as a client failure anyway, so it keeps inherited_error() unchanged. Signed-off-by: Jonathan Goyvaerts Assisted-by: LLM --- drivers/gpu/drm/asahi/g17/completion/publication.rs | 8 +++----- drivers/gpu/drm/asahi/g17/dependency.rs | 8 +++++++- drivers/gpu/drm/asahi/g17/fence.rs | 2 +- drivers/gpu/drm/asahi/g17/job.rs | 5 +++-- drivers/gpu/drm/asahi/g17/queue/compute.rs | 4 +--- drivers/gpu/drm/asahi/g17/recovery/safety.rs | 1 + 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/completion/publication.rs b/drivers/gpu/drm/asahi/g17/completion/publication.rs index fc4b4f8739015..8e16a14f05488 100644 --- a/drivers/gpu/drm/asahi/g17/completion/publication.rs +++ b/drivers/gpu/drm/asahi/g17/completion/publication.rs @@ -8,7 +8,7 @@ //! nor waits for another command; the last prerequisite publishes a ready result. use super::Output; -use crate::g17::{dependency::inherited_error, status::VmStatus}; +use crate::g17::{dependency::PRODUCER_FAILED, status::VmStatus}; use kernel::{new_mutex, prelude::*, sync::{Arc, Mutex}}; type Links = Option>; @@ -121,10 +121,8 @@ impl Publication { let ready = { let mut state = self.state.lock(); debug_assert!(state.remaining != 0); - if let Err(error) = result { - if state.predecessor_error.is_none() { - state.predecessor_error = Some(inherited_error(error)); - } + if result.is_err() && state.predecessor_error.is_none() { + state.predecessor_error = Some(PRODUCER_FAILED); } state.remaining -= 1; state.claim() diff --git a/drivers/gpu/drm/asahi/g17/dependency.rs b/drivers/gpu/drm/asahi/g17/dependency.rs index b7cb74a59644f..5a0ed611b1dde 100644 --- a/drivers/gpu/drm/asahi/g17/dependency.rs +++ b/drivers/gpu/drm/asahi/g17/dependency.rs @@ -9,6 +9,7 @@ use super::{ use kernel::prelude::*; /// A failed producer does not establish a fault in its consumer's VM. +#[cfg(CONFIG_DRM_ASAHI_G15)] pub(crate) fn inherited_error(error: Error) -> Error { if error == EIO || error == ENODEV || error == ETIMEDOUT { ECANCELED @@ -17,11 +18,16 @@ pub(crate) fn inherited_error(error: Error) -> Error { } } +/// What a G17 consumer of a failed producer fails with, whatever the producer failed with: its +/// client does not see a cancellation as a lost device. A producer a recovery dropped fails with +/// ENODATA, and passing that on made every consumer of the producer's buffers lose its device too. +pub(crate) const PRODUCER_FAILED: Error = ECANCELED; + /// A replay's original host inputs must still be signalled. Treat an unmet /// input as a failed replay, never as a wait under the device mutex. pub(crate) fn replay_status(status: i32) -> Result { if status == 0 { Err(EBUSY) } - else if status < 0 { Err(inherited_error(Error::from_errno(status))) } + else if status < 0 { Err(PRODUCER_FAILED) } else { Ok(()) } } diff --git a/drivers/gpu/drm/asahi/g17/fence.rs b/drivers/gpu/drm/asahi/g17/fence.rs index 71107371640bd..0b8819bdbd396 100644 --- a/drivers/gpu/drm/asahi/g17/fence.rs +++ b/drivers/gpu/drm/asahi/g17/fence.rs @@ -180,7 +180,7 @@ impl Submission { self.remaining.load(Ordering::Acquire) == 0 || self.retired.load(Ordering::Acquire); let error = self.error.load(Ordering::Acquire); if error != 0 { - return Err(super::dependency::inherited_error(Error::from_errno(error))); + return Err(super::dependency::PRODUCER_FAILED); } Ok(complete) } diff --git a/drivers/gpu/drm/asahi/g17/job.rs b/drivers/gpu/drm/asahi/g17/job.rs index 711dd59b0ecc2..7e43c163ac4a7 100644 --- a/drivers/gpu/drm/asahi/g17/job.rs +++ b/drivers/gpu/drm/asahi/g17/job.rs @@ -177,8 +177,9 @@ impl Packet { kernel::time::msecs_to_jiffies(10) as isize, ) }; + // An interrupted wait is not a producer failure. if waited < 0 { - return Err(super::dependency::inherited_error(Error::from_errno(waited as i32))); + return Err(Error::from_errno(waited as i32)); } if waited > 0 { break; } } @@ -257,7 +258,7 @@ pub(crate) fn fence_status(fence: &Fence) -> i32 { fn check_status(fence: &Fence) -> Result { let status = fence_status(fence); if status < 0 { - Err(super::dependency::inherited_error(Error::from_errno(status))) + Err(super::dependency::PRODUCER_FAILED) } else { Ok(()) } diff --git a/drivers/gpu/drm/asahi/g17/queue/compute.rs b/drivers/gpu/drm/asahi/g17/queue/compute.rs index 368e95f144c24..55994066825f8 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute.rs @@ -2056,9 +2056,7 @@ impl Queue { let ticket = active.ticket.ok_or(EIO)?; let state = crate::g17::job::fence_status(&active.packet.completion.fence()); if state < 0 { - return Err(crate::g17::dependency::inherited_error(Error::from_errno( - state, - ))); + return Err(crate::g17::dependency::PRODUCER_FAILED); } if state > 0 { return Ok(None); diff --git a/drivers/gpu/drm/asahi/g17/recovery/safety.rs b/drivers/gpu/drm/asahi/g17/recovery/safety.rs index 082416f0cb9a6..d71d3cc81553f 100644 --- a/drivers/gpu/drm/asahi/g17/recovery/safety.rs +++ b/drivers/gpu/drm/asahi/g17/recovery/safety.rs @@ -126,5 +126,6 @@ mod tests { assert_eq!(replay_status(1), Ok(())); assert_eq!(replay_status(EIO.to_errno()), Err(ECANCELED)); assert_eq!(replay_status(ETIMEDOUT.to_errno()), Err(ECANCELED)); + assert_eq!(replay_status(ENODATA.to_errno()), Err(ECANCELED)); } } From e8eb30270b85a7359d7266bf7fd7df3bbba75cd2 Mon Sep 17 00:00:00 2001 From: "Michal S." Date: Sun, 1 Nov 2026 20:55:50 +0000 Subject: [PATCH 09/30] soc: apple: sep: allow the T6040 profile on J614s The 14-inch M4 Pro uses the existing T6040 SEP and fingerprint sensor profile. Admit its board compatible alongside J616s. On a J614s with the board-specific boot and firmware setup, fingerprint enrollment and authentication work, including after a reboot. --- drivers/soc/apple/profile.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/soc/apple/profile.rs b/drivers/soc/apple/profile.rs index 0e632c65da81f..aaefc62b9528b 100644 --- a/drivers/soc/apple/profile.rs +++ b/drivers/soc/apple/profile.rs @@ -613,9 +613,10 @@ pub(crate) fn detect() -> Result<&'static PlatformProfile> { if machine_has(b"apple,t8122") && (machine_has(b"apple,j613") || machine_has(b"apple,j615")) { return Ok(&T8122); } - // Only the board whose ADT was read: J616s. The other T6040 Macs share - // the SoC but their sensor wiring is unchecked. - if machine_has(b"apple,t6040") && machine_has(b"apple,j616s") { + // The T6040 profile is tested on J614s and J616s. + if machine_has(b"apple,t6040") + && (machine_has(b"apple,j616s") || machine_has(b"apple,j614s")) + { return Ok(&T6040); } Err(ENODEV) From 08b4d77d6b805db149224247fe47fcc1fa937ce3 Mon Sep 17 00:00:00 2001 From: "Michal S." Date: Sun, 1 Nov 2026 21:08:54 +0000 Subject: [PATCH 10/30] arm64: dts: apple: set J614s panel and firmware identifiers Override the inherited J616s panel, audio chassis, touchpad, radio and fingerprint firmware identifiers with the J614s values used by a working J614s installation. --- arch/arm64/boot/dts/apple/t6040-j614s.dts | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j614s.dts b/arch/arm64/boot/dts/apple/t6040-j614s.dts index e383de6943ef5..ff31753caf203 100644 --- a/arch/arm64/boot/dts/apple/t6040-j614s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j614s.dts @@ -15,3 +15,34 @@ compatible = "apple,j614s", "apple,t6040", "apple,arm-platform"; model = "Apple MacBook Pro (14-inch, M4 Pro, Nov 2024)"; }; + +&dcp { + panel { + compatible = "apple,panel-j614", "apple,panel-mini-led", "apple,panel"; + width-mm = <302>; + height-mm = <196>; + /delete-property/ adj-height-mm; + }; +}; + +&aop_audio { + apple,chassis-name = "J614"; +}; + +&mtp_hid { + multi-touch { + firmware-name = "apple/tpmtfw-j614s.bin"; + }; +}; + +&{/pcie@1cb0000000/pci@0,0/wifi@0,0} { + brcm,board-type = "apple,mriya"; +}; + +&{/pcie@1cb0000000/pci@0,0/bluetooth@0,1} { + brcm,board-type = "apple,mriya"; +}; + +&mesa { + firmware-name = "apple/mesacal-j614s.bin", "apple/msrk-j614s.bin"; +}; From 3bc8642187af77c1a522445bda507b5bb653298f Mon Sep 17 00:00:00 2001 From: "Michal S." Date: Sun, 1 Nov 2026 21:43:15 +0000 Subject: [PATCH 11/30] arm64: dts: apple: enable J614s SD card reader Describe the GL9755 reader, its SMC power enable, PCIe port and DART on J614s. Select both CLKREQ pins on the controller to avoid conflicting claims on the port. --- arch/arm64/boot/dts/apple/t6040-j614s.dts | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j614s.dts b/arch/arm64/boot/dts/apple/t6040-j614s.dts index e383de6943ef5..9387bf5c7cf92 100644 --- a/arch/arm64/boot/dts/apple/t6040-j614s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j614s.dts @@ -15,3 +15,28 @@ compatible = "apple,j614s", "apple,t6040", "apple,arm-platform"; model = "Apple MacBook Pro (14-inch, M4 Pro, Nov 2024)"; }; + +/* The reader's gP19 SMC output controls its supply. */ +&apcie1_dart { + status = "okay"; +}; + +&pcie0 { + bus-range = <0 2>; + pinctrl-0 = <&wifi_clkreq_pins &sd_clkreq_pins>; +}; + +&port01 { + /delete-property/ pinctrl-0; + /delete-property/ pinctrl-names; + bus-range = <2 2>; + pwren-gpios = <&smc_gpio 0x19 GPIO_ACTIVE_HIGH>; + status = "okay"; + + mmc@0,0 { + compatible = "pci17a0,9755"; + reg = <0x20000 0 0 0 0>; + cd-inverted; + wp-inverted; + }; +}; From 2f60c92762f32e19cb0f18169d7e23665f798a02 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 10:03:25 +1100 Subject: [PATCH 12/30] ASoC: dt-bindings: apple,macaudio: Add the J614s cards The J614s (MacBook Pro 14-inch, M4 Pro) has the same speaker amps and headphone codec on the same AOP streams as the J616s, in a different chassis with different speakers. Add the speaker and jack card compatibles for it, so the 14-inch reports its own identity to userspace. Assisted-by: LLM Signed-off-by: Ryan Murray --- Documentation/devicetree/bindings/sound/apple,macaudio.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml index 755c5d195cf0f..e0b330720fdb7 100644 --- a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml +++ b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml @@ -20,6 +20,8 @@ properties: - apple,j274-macaudio - apple,j293-macaudio - apple,j314-macaudio + - apple,j614-macaudio + - apple,j614-macaudio-jack - apple,j616-macaudio - apple,j616-macaudio-jack - apple,j700-macaudio From 304cd0b99eb16d6a031597a660d205a657b0ca9f Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 10:03:26 +1100 Subject: [PATCH 13/30] ASoC: apple: macaudio: Add the J614s speaker and jack cards Apple's J614s device tree describes the same six SN012776 amps, TDM slots, sense layout and CS42L84 jack as the J616s. Only the chassis and speakers differ (acoustic ID 34 against 35). That is rule 2 of the table: same amps and layout, different chassis, so a compatible of its own sharing the J616 data. The card is then AppleJ614, not AppleJ616, so no J616 speaker profile, DSP tuning or UCM file is picked for the 14-inch speakers. There is no qualified J614 speaker protection profile yet. Until one exists the speakers stay at the lock this driver applies at probe: the amp volume is held at its lowest step, and only a protection daemon holding the Speaker Volume Unlock control can lift it. speakersafetyd has no J614 profile. speakerguardd starts only for profiled cards, and the J614 profile in its unreleased set says qualified = false, which it refuses to govern without an explicit opt-in. So nothing takes the lock on this card. Assisted-by: LLM Signed-off-by: Ryan Murray --- sound/soc/apple/macaudio.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 569cf80387dff..545ee2ab0e80f 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -2118,12 +2118,14 @@ struct macaudio_platform_cfg macaudio_j493_cfg = { }; /* - * J616s (T6040, MacBook Pro 16" M4 Pro): the AOP firmware runs MCA cluster - * 0, so the CPU component is the AOP audio driver (front-ends j616-pcm-N, - * back-end spkr). One serializer carries all six SN012776 amps in a 250-bit - * frame at 12 MHz: six 32-bit playback slots, twelve 16-bit sense slots. The - * amps take I2S with an inverted frame clock and a normal bit clock, as on - * the J713 (T8132: the same amp and frame). + * J614s and J616s (T6040, MacBook Pro 14" and 16" M4 Pro): the AOP firmware + * runs MCA cluster 0, so the CPU component is the AOP audio driver + * (front-ends j616-pcm-N, back-end spkr). One serializer carries all six + * SN012776 amps in a 250-bit frame at 12 MHz: six 32-bit playback slots, + * twelve 16-bit sense slots. The amps take I2S with an inverted frame clock + * and a normal bit clock, as on the J713 (T8132: the same amp and frame). + * Apple's device trees for the two boards describe the same amps, slots and + * sense layout; only the speakers and their tuning differ. */ struct macaudio_platform_cfg macaudio_j616_cfg = { .enable_speakers = true, @@ -2142,8 +2144,8 @@ struct macaudio_platform_cfg macaudio_j616_cfg = { }; /* - * The J616s headphone jack, a card of its own: a CS42L84 on the AOP's - * "jack" back-end, which the firmware runs on MCA cluster 2 as a 64-SCLK + * The J614s and J616s headphone jack, a card of its own: a CS42L84 on the + * AOP's "jack" back-end, which the firmware runs on MCA cluster 2 as a 64-SCLK * frame at 48 kHz (3.072 MHz, the primary front-end's ratio) with a * one-SCLK FSYNC pulse, two 32-bit slots and data launched on the rising * SCLK edge: DSP_A with an inverted bit clock, as on the J700. A card binds @@ -2224,6 +2226,10 @@ static const struct of_device_id macaudio_snd_device_id[] = { /* j575 AID33 sn012776 15 1x 1W Compat: apple,j375-macaudio */ /* j613 AID20 sn012776 15 2x 1W+1T Compat: apple,j413-macaudio */ /* j615 AID21 sn012776 15 2x 2W+1T Compat: apple,j415-macaudio */ + /* j614 AID34 sn012776 15 2x 2W+1T (AOP-fed, one serializer) */ + { .compatible = "apple,j614-macaudio", .data = &macaudio_j616_cfg }, + /* j614 AID34 cs42l84 - the jack, a card of its own (AOP-fed) */ + { .compatible = "apple,j614-macaudio-jack", .data = &macaudio_j616_jack_cfg }, /* j616 AID35 sn012776 15 2x 2W+1T (AOP-fed, one serializer) */ { .compatible = "apple,j616-macaudio", .data = &macaudio_j616_cfg }, /* j616 AID35 cs42l84 - the jack, a card of its own (AOP-fed) */ From 35f9376b9fbe956b52099353e00dc36c8f8e1db0 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 10:04:21 +1100 Subject: [PATCH 14/30] arm64: dts: apple: t6040: Share one description for the J614s and J616s Apple's device trees for the J614s and J616s (MacBook Pro 14-inch and 16-inch, M4 Pro) are the same apart from the board identity, the panel, the speaker tuning, the Wi-Fi module, the trackpad and the power limits. Including the whole J616s board file left the 14-inch reporting 16-inch values to drivers and userspace, the J616 speaker card among them. Move the J616s board file into t604x-j614-j616.dtsi without the board values, as the J314/J316, J414/J416 and J514/J516 generations do, and label the Wi-Fi, Bluetooth, panel and trackpad nodes so the board files can set them. Each board file now sets its compatible and model, the brcm,board-type, the panel compatible and size, the AOP audio chassis name, the speaker and jack cards, the trackpad firmware and the Touch ID firmware names. The J616s and J616s tether DTBs decompile to the same tree as before. For the J614s the values are those of https://github.com/aurora-silicon/linux/pull/816 and the SD reader of https://github.com/aurora-silicon/linux/pull/818, with two changes: - The speaker and jack cards are apple,j614-macaudio and apple,j614-macaudio-jack ("MacBook Pro J614"), not the J616 cards. The J616 speaker protection profile and tuning are for the 16-inch speakers. With the J614 cards the speakers stay locked until a J614 protection profile exists. - adj-height-mm is 189, as on the J314s, J414s and J514s, which have the same 3024x1964 panel. It is the height below the notch and is only read while the notch is hidden. The SD reader stays J614s-only for now: Apple's J616s tree describes it the same way, but it has not been tried on a J616s. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j614s.dts | 58 +- arch/arm64/boot/dts/apple/t6040-j616s.dts | 4763 +--------------- .../arm64/boot/dts/apple/t604x-j614-j616.dtsi | 4765 +++++++++++++++++ 3 files changed, 4826 insertions(+), 4760 deletions(-) create mode 100644 arch/arm64/boot/dts/apple/t604x-j614-j616.dtsi diff --git a/arch/arm64/boot/dts/apple/t6040-j614s.dts b/arch/arm64/boot/dts/apple/t6040-j614s.dts index 08446f64fa96f..1b0a6868fad82 100644 --- a/arch/arm64/boot/dts/apple/t6040-j614s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j614s.dts @@ -1,53 +1,69 @@ // SPDX-License-Identifier: GPL-2.0+ OR MIT /* - * MacBook Pro (14-inch, M4 Pro, Nov 2024) + * MacBook Pro (14-inch, M4 Pro) * * target-type: J614s * - * J614s uses the same T6040 platform topology as the supported J616s - * board. Keep the shared SoC resources and driver bindings in one baseline; - * board-specific firmware supplies the J614s identity at boot. + * Copyright The Asahi Linux Contributors */ -#include "t6040-j616s.dts" +/dts-v1/; + +#include "t6040.dtsi" +#include "t604x-j614-j616.dtsi" / { compatible = "apple,j614s", "apple,t6040", "apple,arm-platform"; model = "Apple MacBook Pro (14-inch, M4 Pro, Nov 2024)"; }; -&dcp { - panel { - compatible = "apple,panel-j614", "apple,panel-mini-led", "apple,panel"; - width-mm = <302>; - height-mm = <196>; - /delete-property/ adj-height-mm; - }; +/* ADT /arm-io/wlan module-instance */ +&wifi0 { + brcm,board-type = "apple,mriya"; +}; + +&bluetooth0 { + brcm,board-type = "apple,mriya"; +}; + +&panel { + compatible = "apple,panel-j614", "apple,panel-mini-led", "apple,panel"; + width-mm = <302>; + height-mm = <196>; + adj-height-mm = <189>; }; &aop_audio { apple,chassis-name = "J614"; }; -&mtp_hid { - multi-touch { - firmware-name = "apple/tpmtfw-j614s.bin"; - }; +&sound { + compatible = "apple,j614-macaudio", "apple,macaudio"; + model = "MacBook Pro J614"; }; -&{/pcie@1cb0000000/pci@0,0/wifi@0,0} { - brcm,board-type = "apple,mriya"; +&sound_jack { + compatible = "apple,j614-macaudio-jack", "apple,macaudio"; + model = "MacBook Pro J614 Jack"; }; -&{/pcie@1cb0000000/pci@0,0/bluetooth@0,1} { - brcm,board-type = "apple,mriya"; +&mtp_mt { + firmware-name = "apple/tpmtfw-j614s.bin"; }; +/* + * Per-device factory data from this Mac's own iBoot system container: the + * sealed calibration and the sealed root key. + */ &mesa { firmware-name = "apple/mesacal-j614s.bin", "apple/msrk-j614s.bin"; }; -/* The reader's gP19 SMC output controls its supply. */ +/* + * The GL9755 SD reader. Its supply is the SMC's gP19 output, which the + * J614s firmware describes as active high. Both CLKREQ pins go on the + * controller so that the Wi-Fi and SD ports do not claim the same pins. + */ &apcie1_dart { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index af3c800c577f5..a81644c6cfad5 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ OR MIT /* - * MacBook Pro (16-inch, M4 Pro, Nov 2024) + * MacBook Pro (16-inch, M4 Pro) * * target-type: J616s * @@ -10,4766 +10,51 @@ /dts-v1/; #include "t6040.dtsi" -#include -#include -#include +#include "t604x-j614-j616.dtsi" / { compatible = "apple,j616s", "apple,t6040", "apple,arm-platform"; model = "Apple MacBook Pro (16-inch, M4 Pro, Nov 2024)"; - chassis-type = "laptop"; - - aliases { - keyboard = &keyboard; - nvram = &nvram; - spi0 = &qspi; - wifi0 = "/pcie@1cb0000000/pci@0,0/wifi@0,0"; - bluetooth0 = "/pcie@1cb0000000/pci@0,0/bluetooth@0,1"; - /* used by the loader to hand over the display */ - dcp = &dcp; - disp0 = &display; - disp0-piodma = &disp0_piodma; - /* the boot loader finds the GPU node through this alias */ - gpu = &gpu; - pmp = &pmp; - /* the boot loader reserves the SEP firmware through this alias */ - sep = &sep; - }; - - chosen { - #address-cells = <2>; - #size-cells = <2>; - ranges; - - framebuffer0: framebuffer@0 { - compatible = "apple,simple-framebuffer", "simple-framebuffer"; - reg = <0 0 0 0>; /* To be filled by loader */ - /* Format properties will be added by loader */ - status = "disabled"; - }; - }; - - reserved-memory { - #address-cells = <2>; - #size-cells = <2>; - ranges; - /* The loader adds its own reservations */ - - /* - * The loader must fill reg from ane0 segment-ranges, rename each - * node to match its physical address and enable it. Leave these - * disabled until then; allocating replacement memory would lose - * the personalized preload. TEXT is 0xc0000 bytes and DATA is - * 0x2ac000 bytes. Neither segment may be reclaimed before reboot. - */ - ane_fw_text: ane-firmware-text@0 { - compatible = "apple,asc-mem"; - reg = <0 0 0 0>; - no-map; - status = "disabled"; - }; - - ane_fw_data: ane-firmware-data@0 { - compatible = "apple,asc-mem"; - reg = <0 0 0 0>; - no-map; - status = "disabled"; - }; - - /* - * iBoot loads the ISP firmware below the kernel's memory. Its - * segments are the ADT's isp0 segment-ranges (macOS 26.6.2, - * iBoot mBoot-18000.161.10), mapped where the firmware runs them: - * 0x100_0000_0000 onwards, its 32 MiB main memory. The driver - * checks that the coprocessor's reset vector is this text before - * it starts the firmware. - */ - isp_fw_text: isp-firmware-text@10000c44000 { - reg = <0x100 0x00c44000 0x0 0xca8000>; - iommu-addresses = <&isp 0x100 0x0 0x0 0xca8000>; - no-map; - }; - - isp_fw_data: isp-firmware-data@10001e7c000 { - reg = <0x100 0x01e7c000 0x0 0x1344000>; - iommu-addresses = <&isp 0x100 0xca8000 0x0 0x1344000>; - no-map; - }; - - /* the firmware's main memory, the driver allocates above it */ - isp_heap: isp-heap { - iommu-addresses = <&isp 0x100 0x0 0x0 0x2000000>; - }; - - /* - * Text segment of the loaded GPU firmware (ADT gfx-asc - * segment-ranges[0]); the driver identifies the build from it. - */ - gpu_fw_text: gpu-fw-text { - reg = <0 0 0 0>; - status = "disabled"; - no-map; - }; - - /* - * Data segment of the loaded GPU firmware (ADT sgx gfx-data-*); it - * places its crash log here. - */ - gpu_fw_data: gpu-fw-data { - reg = <0 0 0 0>; - status = "disabled"; - no-map; - }; - - /* - * The UAT ranges (ADT sgx gpu-region-*, gfx-shared-region-*, - * gfx-shared-l2-region-*, gfx-handoff-*). They stay no-map until - * the boot loader adds them to /memory. - */ - uat_ttbs: uat-ttbs { - reg = <0 0 0 0>; - status = "disabled"; - no-map; - }; - - uat_pagetables: uat-pagetables { - reg = <0 0 0 0>; - status = "disabled"; - no-map; - }; - - uat_l2_pagetables: uat-l2-pagetables { - reg = <0 0 0 0>; - status = "disabled"; - no-map; - }; - - uat_handoff: uat-handoff { - reg = <0 0 0 0>; - status = "disabled"; - no-map; - }; - }; - - memory@0 { - device_type = "memory"; - reg = <0 0 0 0>; /* To be filled by loader */ - }; - - led-controller { - compatible = "pwm-leds"; - - led-0 { - pwms = <&fpwm0 0 40000>; - label = "apple::kbd_backlight"; - function = LED_FUNCTION_KBD_BACKLIGHT; - color = ; - max-brightness = <255>; - }; - }; - - /* - * GPU performance states, one opp-microvolt cell per cluster (ADT - * /arm-io/sgx perf-states and perf-states1). The operating points are - * binned per device: the loader fills these placeholders. - * The driver requires a completed calibration handoff. Entries keep the - * order of the GPU performance states, which is not sorted by frequency. - */ - gpu_opp: opp-table-gpu { - compatible = "operating-points-v2"; - - opp00 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp01 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp02 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp03 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp04 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp05 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp06 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp07 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp08 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp09 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp10 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp11 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp12 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp13 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp14 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - opp15 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0 0>; - }; - }; - - /* The GPU's CS clock domain (ADT cs-perf-states) */ - gpu_cs_opp: opp-table-gpucs { - compatible = "operating-points-v2"; - - opp00 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp01 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp02 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp03 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp04 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp05 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp06 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp07 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - }; - - /* The GPU's AFR clock domain (ADT afr-perf-states) */ - gpu_afr_opp: opp-table-gpuafr { - compatible = "operating-points-v2"; - - opp00 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp01 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp02 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp03 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp04 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp05 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp06 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - opp07 { - opp-hz = /bits/ 64 <0>; - opp-microvolt = <0>; - }; - }; - - /* - * CPU die temperature: SMC key TCMz (CPU die max), sensor 0 of - * smc_hwmon. The PMP's fast die-temperature limit regulates: its loop - * holds its own hot-spot sensors at 110 C (ADT fast-die-ctrl-loop), - * and under a sustained all-core load TCMz then settles at 113.4 to - * 114.7 C with the fans running. This zone is the backstop above that: - * passive at 116 C, critical (orderly shutdown) at 118 C, polled once a - * second as the SMC refreshes TCMz. The cpufreq cooling device sits on a - * cluster's policy CPU, so each map lists every CPU of its cluster. - * - * Per-cluster and GPU zones follow, one per SMC die-maximum sensor - * (smc_hwmon channels 1-23, in the order of the temperature nodes - * below): Tex0-3 throttle the E cluster, Tpx0-5 the first P cluster - * and Tpx8-D the second, with the same 116/118 C backstop as TCMz. - * Unlike the fanless J700, this machine has two SMC-driven fans and - * the PMP regulates the dies itself, so these zones only catch a - * failure of that loop; they never act in normal use. - * - * The GPU has no cooling device of its own: its firmware sets the GPU - * performance state and starts limiting at 101 C. So when a GPU die - * sensor reaches the GPU firmware's limit, the seven - * hottest ones (Tg05, Tg0S, Tg0Y, Tg0k, Tg0z, Tg1V, Tg1l) also shed - * CPU power through the CPU clusters, from the same 101 C, leaving the - * shared heat budget to the GPU. They shut down at 118 C. While the - * GPU is powered off its sensors report placeholder values from -4 to - * 4 C. - * - * The fans: left to themselves (SMC mode 3, with no OS asking for - * cooling) they stay off, or switch on late and ramp slowly, while the - * dies heat up under load. So the CPU die zone and the GPU zones also - * run both fans through a ladder of active trips; a fan takes the - * highest state any zone asks for and goes back to the SMC once no - * zone asks for anything. States 1-8 of a fan run it from its minimum - * (1350 rpm) to its maximum (5777 rpm) in even steps. - * - * sustainable-power is what the CPUs dissipate with the die held at - * its operating point: 720 s of all-core load (10 P, 4 E busy loops), - * headless, SMC fans in their own mode. Over the last 600 s TCMz held - * 112.0 to 113.8 C with the PMP limiting the P clusters to a mean of - * about 3.7 GHz and the fans at 3450/3730 rpm (where macOS settles - * too). The SMC core rails then read P0 25.3 W, P1 23.9 W and E 2.1 W - * on average, the units of the energy model. Each zone gets the sum - * over the clusters it throttles. Without it the power allocator - * estimates a budget from the cooling devices' lowest states, which - * throttles far below what this machine sustains. - */ -#define J616S_FAN_TRIP(label, node, temp, hyst) \ - label: node { \ - temperature = ; \ - hysteresis = ; \ - type = "active"; \ - }; - -#define J616S_FAN_TRIPS(zone) \ - J616S_FAN_TRIP(zone##_fan1, trip-fan1, 75000, 10000) \ - J616S_FAN_TRIP(zone##_fan2, trip-fan2, 80000, 5000) \ - J616S_FAN_TRIP(zone##_fan3, trip-fan3, 85000, 5000) \ - J616S_FAN_TRIP(zone##_fan4, trip-fan4, 90000, 5000) \ - J616S_FAN_TRIP(zone##_fan5, trip-fan5, 95000, 5000) - -#define J616S_FAN_MAP(trip_label, node, state) \ - node { \ - trip = <&trip_label>; \ - cooling-device = <&smc_hwmon 0 state state>, \ - <&smc_hwmon 1 state state>; \ - }; - -#define J616S_FAN_MAPS(zone) \ - J616S_FAN_MAP(zone##_fan1, map-fan1, 1) \ - J616S_FAN_MAP(zone##_fan2, map-fan2, 2) \ - J616S_FAN_MAP(zone##_fan3, map-fan3, 3) \ - J616S_FAN_MAP(zone##_fan4, map-fan4, 5) \ - J616S_FAN_MAP(zone##_fan5, map-fan5, 8) - - thermal-zones { - cpu-die-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <51300>; - thermal-sensors = <&smc_hwmon 0>; /* TCMz */ - - trips { - J616S_FAN_TRIPS(cpu_die) - - cpu_die_passive: cpu-die-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - cpu-die-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(cpu_die) - - map-e { - trip = <&cpu_die_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&cpu_die_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&cpu_die_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-e0-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <2100>; - thermal-sensors = <&smc_hwmon 1>; /* Tex0 */ - - trips { - cpu_e0_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-e { - trip = <&cpu_e0_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-e1-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <2100>; - thermal-sensors = <&smc_hwmon 2>; /* Tex1 */ - - trips { - cpu_e1_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-e { - trip = <&cpu_e1_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-e2-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <2100>; - thermal-sensors = <&smc_hwmon 3>; /* Tex2 */ - - trips { - cpu_e2_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-e { - trip = <&cpu_e2_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-e3-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <2100>; - thermal-sensors = <&smc_hwmon 4>; /* Tex3 */ - - trips { - cpu_e3_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-e { - trip = <&cpu_e3_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p00-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <25300>; - thermal-sensors = <&smc_hwmon 5>; /* Tpx0 */ - - trips { - cpu_p00_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p0 { - trip = <&cpu_p00_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p01-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <25300>; - thermal-sensors = <&smc_hwmon 6>; /* Tpx1 */ - - trips { - cpu_p01_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p0 { - trip = <&cpu_p01_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p02-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <25300>; - thermal-sensors = <&smc_hwmon 7>; /* Tpx2 */ - - trips { - cpu_p02_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p0 { - trip = <&cpu_p02_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p03-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <25300>; - thermal-sensors = <&smc_hwmon 8>; /* Tpx3 */ - - trips { - cpu_p03_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p0 { - trip = <&cpu_p03_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p04-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <25300>; - thermal-sensors = <&smc_hwmon 9>; /* Tpx4 */ - - trips { - cpu_p04_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p0 { - trip = <&cpu_p04_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p05-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <25300>; - thermal-sensors = <&smc_hwmon 10>; /* Tpx5 */ - - trips { - cpu_p05_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p0 { - trip = <&cpu_p05_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p10-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <23900>; - thermal-sensors = <&smc_hwmon 11>; /* Tpx8 */ - - trips { - cpu_p10_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p1 { - trip = <&cpu_p10_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p11-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <23900>; - thermal-sensors = <&smc_hwmon 12>; /* Tpx9 */ - - trips { - cpu_p11_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p1 { - trip = <&cpu_p11_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p12-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <23900>; - thermal-sensors = <&smc_hwmon 13>; /* TpxA */ - - trips { - cpu_p12_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p1 { - trip = <&cpu_p12_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p13-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <23900>; - thermal-sensors = <&smc_hwmon 14>; /* TpxB */ - - trips { - cpu_p13_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p1 { - trip = <&cpu_p13_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p14-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <23900>; - thermal-sensors = <&smc_hwmon 15>; /* TpxC */ - - trips { - cpu_p14_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p1 { - trip = <&cpu_p14_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - cpu-p15-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - sustainable-power = <23900>; - thermal-sensors = <&smc_hwmon 16>; /* TpxD */ - - trips { - cpu_p15_passive: trip-passive { - temperature = <116000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - map-p1 { - trip = <&cpu_p15_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - gpu0-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 17>; /* Tg05 */ - - trips { - J616S_FAN_TRIPS(gpu0) - - gpu0_passive: trip-passive { - temperature = <101000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(gpu0) - - map-e { - trip = <&gpu0_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&gpu0_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&gpu0_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - gpu1-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 18>; /* Tg0S */ - - trips { - J616S_FAN_TRIPS(gpu1) - - gpu1_passive: trip-passive { - temperature = <101000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(gpu1) - - map-e { - trip = <&gpu1_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&gpu1_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&gpu1_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - gpu2-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 19>; /* Tg0Y */ - - trips { - J616S_FAN_TRIPS(gpu2) - - gpu2_passive: trip-passive { - temperature = <101000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(gpu2) - - map-e { - trip = <&gpu2_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&gpu2_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&gpu2_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - gpu3-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 20>; /* Tg0k */ - - trips { - J616S_FAN_TRIPS(gpu3) - - gpu3_passive: trip-passive { - temperature = <101000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(gpu3) - - map-e { - trip = <&gpu3_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&gpu3_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&gpu3_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - gpu4-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 21>; /* Tg0z */ - - trips { - J616S_FAN_TRIPS(gpu4) - - gpu4_passive: trip-passive { - temperature = <101000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(gpu4) - - map-e { - trip = <&gpu4_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&gpu4_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&gpu4_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - gpu5-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 22>; /* Tg1V */ - - trips { - J616S_FAN_TRIPS(gpu5) - - gpu5_passive: trip-passive { - temperature = <101000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(gpu5) - - map-e { - trip = <&gpu5_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&gpu5_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&gpu5_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - - gpu6-thermal { - polling-delay-passive = <1000>; - polling-delay = <1000>; - thermal-sensors = <&smc_hwmon 23>; /* Tg1l */ - - trips { - J616S_FAN_TRIPS(gpu6) - - gpu6_passive: trip-passive { - temperature = <101000>; - hysteresis = <5000>; - type = "passive"; - }; - - trip-critical { - temperature = <118000>; - hysteresis = <0>; - type = "critical"; - }; - }; - - cooling-maps { - J616S_FAN_MAPS(gpu6) - - map-e { - trip = <&gpu6_passive>; - cooling-device = - <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p0 { - trip = <&gpu6_passive>; - cooling-device = - <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - - map-p1 { - trip = <&gpu6_passive>; - cooling-device = - <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, - <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; - }; - }; - }; - }; - - /* - * The speaker amps' shared shutdown line (SDZ, AP pin 54, low = - * shutdown). One fixed regulator, the SDZ supply of all six, not - * per-amp shutdown-gpios: with GPIO_SHARED six references become a - * vote, and one amp driving the line low would not shut the other - * five down. - */ - speaker_sdz: regulator-sn012776-sdz { - compatible = "regulator-fixed"; - regulator-name = "sn012776-sdz"; - gpios = <&pinctrl 54 GPIO_ACTIVE_HIGH>; - enable-active-high; - }; - - /* - * The AOP firmware runs the stream (MCA cluster 0, one serializer for - * all six amps); the J616 variant of macaudio links the AOP audio - * component's speaker back-end (DAI 3) to the amps. The codecs are - * listed in the ADT provider order, so each gets its native playback - * slot (LW1 0, RW1 1, LW2 2, RW2 3, LT 4, RT 5). The idle masks are the - * ADT amp-tx-zd-config rows 0 and 1: LW1 drives zeros in the right - * amps' sense slots, RW1 in the left amps'. - */ - sound: sound { - compatible = "apple,j616-macaudio", "apple,macaudio"; - model = "MacBook Pro J616"; - speaker-id-gpios = <&pinctrl 52 GPIO_ACTIVE_HIGH>, - <&pinctrl 53 GPIO_ACTIVE_HIGH>; - #address-cells = <1>; - #size-cells = <0>; - - dai-link@0 { - reg = <0>; - link-name = "Speakers"; - dai-tdm-idle-mode-0 = "zero"; - dai-tdm-idle-mode-1 = "zero"; - dai-tdm-slot-tx-idle-mask-0 = <0xf0f0f0>; - dai-tdm-slot-tx-idle-mask-1 = <0x0f0f0f>; - - cpu { - sound-dai = <&aop_audio 3>; - }; - - codec { - sound-dai = <&speaker_left_woof1>, <&speaker_right_woof1>, - <&speaker_left_woof2>, <&speaker_right_woof2>, - <&speaker_left_tweet>, <&speaker_right_tweet>; - }; - }; - }; - - /* - * The headphone jack: the AOP audio component's jack back-end (DAI 4) - * to the CS42L84. A card of its own, so a codec that does not answer - * costs the jack only, never the speakers. - */ - sound_jack: sound-jack { - compatible = "apple,j616-macaudio-jack", "apple,macaudio"; - model = "MacBook Pro J616 Jack"; - #address-cells = <1>; - #size-cells = <0>; - - dai-link@0 { - reg = <0>; - link-name = "Headphone Jack"; - - cpu { - sound-dai = <&aop_audio 4>; - }; - - codec { - sound-dai = <&jack_codec>; - }; - }; - }; -}; - -&cpuidle { - status = "okay"; -}; - -&smc { - status = "okay"; -}; - -&smc_mbox { - status = "okay"; -}; - -&nub_spmi0 { - status = "okay"; -}; - -&pinctrl { - status = "okay"; - - wifi_clkreq_pins: wifi-clkreq-pins { - pinmux = ; - }; -}; - -/* - * The AP takes none of the always-on bank's interrupts: the AOP firmware - * owns some of its pins and interrupt registers, and a chained handler here - * would see their pending bits without a handler of ours. - */ -&pinctrl_nub { - status = "okay"; - /delete-property/ interrupt-controller; - /delete-property/ #interrupt-cells; - /delete-property/ interrupts; -}; - -&fpwm0 { - status = "okay"; -}; - -/* apple_sep enables the SEP and its DART when it loads. */ -&sep_mbox { - status = "okay"; -}; - -/* - * Boot NOR. The partition is the J616s ADT /arm-io/qspi/spinor/anvram, - * flash-relative; asahi-bless and asahi-nvram find it by its label. - */ -&qspi { - status = "okay"; - - flash@0 { - compatible = "jedec,spi-nor"; - reg = <0>; - spi-max-frequency = <10000000>; - spi-rx-bus-width = <1>; - spi-tx-bus-width = <1>; - - partitions { - compatible = "fixed-partitions"; - #address-cells = <1>; - #size-cells = <1>; - - nvram: partition@700000 { - label = "nvram"; - reg = <0x700000 0x100000>; - }; - }; - }; -}; - -/* The QSPI controller cannot probe while its power state is disabled. */ -&ps_qspi { - status = "okay"; -}; - -/* - * Touch ID sensor (Mesa 0x3356). Its power is NUB GPIO 53 and its - * data-ready line is AP GPIO 116, as the J616s ADT describes them. - */ -&spi2 { - status = "okay"; -}; - -&mesa { - status = "okay"; - enable-gpios = <&pinctrl_nub 53 GPIO_ACTIVE_HIGH>; - interrupt-parent = <&pinctrl>; - interrupts = <116 IRQ_TYPE_EDGE_RISING>; - spi-cs-setup-delay-ns = <20>; - spi-cs-hold-delay-ns = <20>; - /* - * Per-device factory data from this Mac's own iBoot system container: - * the sealed calibration and the sealed root key. - */ - firmware-name = "apple/mesacal-j616s.bin", "apple/msrk-j616s.bin"; -}; - -/* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ -&i2c6 { - status = "okay"; -}; - -&mtp { - status = "okay"; -}; - -&mtp_mbox { - status = "okay"; -}; - -&mtp_dart { - status = "okay"; -}; - -&mtp_dockchannel { - status = "okay"; -}; - -&mtp_hid { - /* SMC key gp1c powers the trackpad analog front end. */ - apple,afe-smc = <&smc>; - - multi-touch { - firmware-name = "apple/tpmtfw-j616s.bin"; - }; - - keyboard: keyboard { - hid-country-code = <0>; - apple,keyboard-layout-id = <2>; - }; - - stm { - }; - - actuator { - }; - - tp-accel { - }; - - mtp { - }; -}; - -&smc_hwmon { - /* - * TCMz and the 23 zone sensors after it must stay the first - * temperature children: the thermal zones name a sensor by its index, - * so the shared hwmon includes below only append after them. - * - * Every key below is in this machine's SMC key table as a readable - * float and was read on the J616s (SMC firmware of macOS 26.6.2). - * Keys that only ever read a fixed placeholder (Tz1*, Ta0*) or a - * fixed limit (TVMx, TVmS, TVms, PHPB, PHPM) are left out; rails that - * read 0 W while idle are kept. GPU (Tg*), P-cluster (Tp*) and - * E-cluster (Te*) labels follow load tests: Tg* only read live while - * the GPU is powered and rise with GPU load, Tp* and Te* rise with CPU - * load. Other names follow the SMC key groups and are not otherwise - * verified. SBA1-3 are the three cells of the 3S battery. - */ - temperature-TCMz { - apple,key-id = "TCMz"; - label = "CPU Die Max"; - }; - - temperature-Tex0 { - apple,key-id = "Tex0"; - label = "E-cluster Tex0"; - }; - - temperature-Tex1 { - apple,key-id = "Tex1"; - label = "E-cluster Tex1"; - }; - - temperature-Tex2 { - apple,key-id = "Tex2"; - label = "E-cluster Tex2"; - }; - - temperature-Tex3 { - apple,key-id = "Tex3"; - label = "E-cluster Tex3"; - }; - - temperature-Tpx0 { - apple,key-id = "Tpx0"; - label = "P-cluster Tpx0"; - }; - - temperature-Tpx1 { - apple,key-id = "Tpx1"; - label = "P-cluster Tpx1"; - }; - - temperature-Tpx2 { - apple,key-id = "Tpx2"; - label = "P-cluster Tpx2"; - }; - - temperature-Tpx3 { - apple,key-id = "Tpx3"; - label = "P-cluster Tpx3"; - }; - - temperature-Tpx4 { - apple,key-id = "Tpx4"; - label = "P-cluster Tpx4"; - }; - - temperature-Tpx5 { - apple,key-id = "Tpx5"; - label = "P-cluster Tpx5"; - }; - - temperature-Tpx8 { - apple,key-id = "Tpx8"; - label = "P-cluster Tpx8"; - }; - - temperature-Tpx9 { - apple,key-id = "Tpx9"; - label = "P-cluster Tpx9"; - }; - - temperature-TpxA { - apple,key-id = "TpxA"; - label = "P-cluster TpxA"; - }; - - temperature-TpxB { - apple,key-id = "TpxB"; - label = "P-cluster TpxB"; - }; - - temperature-TpxC { - apple,key-id = "TpxC"; - label = "P-cluster TpxC"; - }; - - temperature-TpxD { - apple,key-id = "TpxD"; - label = "P-cluster TpxD"; - }; - - temperature-Tg05 { - apple,key-id = "Tg05"; - label = "GPU Tg05"; - }; - - temperature-Tg0S { - apple,key-id = "Tg0S"; - label = "GPU Tg0S"; - }; - - temperature-Tg0Y { - apple,key-id = "Tg0Y"; - label = "GPU Tg0Y"; - }; - - temperature-Tg0k { - apple,key-id = "Tg0k"; - label = "GPU Tg0k"; - }; - - temperature-Tg0z { - apple,key-id = "Tg0z"; - label = "GPU Tg0z"; - }; - - temperature-Tg1V { - apple,key-id = "Tg1V"; - label = "GPU Tg1V"; - }; - - temperature-Tg1l { - apple,key-id = "Tg1l"; - label = "GPU Tg1l"; - }; - - temperature-TAOL { - apple,key-id = "TAOL"; - label = "SMC TAOL"; - }; - - temperature-TB1T { - apple,key-id = "TB1T"; - label = "Battery 1"; - }; - - temperature-TB2T { - apple,key-id = "TB2T"; - label = "Battery 2"; - }; - - temperature-TCMb { - apple,key-id = "TCMb"; - label = "SMC TCMb"; - }; - - temperature-TD00 { - apple,key-id = "TD00"; - label = "SMC TD00"; - }; - - temperature-TD01 { - apple,key-id = "TD01"; - label = "SMC TD01"; - }; - - temperature-TD02 { - apple,key-id = "TD02"; - label = "SMC TD02"; - }; - - temperature-TD03 { - apple,key-id = "TD03"; - label = "SMC TD03"; - }; - - temperature-TD04 { - apple,key-id = "TD04"; - label = "SMC TD04"; - }; - - temperature-TD10 { - apple,key-id = "TD10"; - label = "SMC TD10"; - }; - - temperature-TD11 { - apple,key-id = "TD11"; - label = "SMC TD11"; - }; - - temperature-TD12 { - apple,key-id = "TD12"; - label = "SMC TD12"; - }; - - temperature-TD13 { - apple,key-id = "TD13"; - label = "SMC TD13"; - }; - - temperature-TD14 { - apple,key-id = "TD14"; - label = "SMC TD14"; - }; - - temperature-TD20 { - apple,key-id = "TD20"; - label = "SMC TD20"; - }; - - temperature-TD21 { - apple,key-id = "TD21"; - label = "SMC TD21"; - }; - - temperature-TD22 { - apple,key-id = "TD22"; - label = "SMC TD22"; - }; - - temperature-TD23 { - apple,key-id = "TD23"; - label = "SMC TD23"; - }; - - temperature-TD24 { - apple,key-id = "TD24"; - label = "SMC TD24"; - }; - - temperature-TDBP { - apple,key-id = "TDBP"; - label = "SMC TDBP"; - }; - - temperature-TDCR { - apple,key-id = "TDCR"; - label = "SMC TDCR"; - }; - - temperature-TDEC { - apple,key-id = "TDEC"; - label = "SMC TDEC"; - }; - - temperature-TDEL { - apple,key-id = "TDEL"; - label = "SMC TDEL"; - }; - - temperature-TDER { - apple,key-id = "TDER"; - label = "SMC TDER"; - }; - - temperature-TDTP { - apple,key-id = "TDTP"; - label = "SMC TDTP"; - }; - - temperature-TDVx { - apple,key-id = "TDVx"; - label = "SMC TDVx"; - }; - - temperature-TDeL { - apple,key-id = "TDeL"; - label = "SMC TDeL"; - }; - - temperature-TDeR { - apple,key-id = "TDeR"; - label = "SMC TDeR"; - }; - - temperature-TF2S { - apple,key-id = "TF2S"; - label = "SMC TF2S"; - }; - - temperature-TH0a { - apple,key-id = "TH0a"; - label = "SMC TH0a"; - }; - - temperature-TH0b { - apple,key-id = "TH0b"; - label = "SMC TH0b"; - }; - - temperature-TMVR { - apple,key-id = "TMVR"; - label = "SMC TMVR"; - }; - - temperature-TPD0 { - apple,key-id = "TPD0"; - label = "SMC TPD0"; - }; - - temperature-TPD1 { - apple,key-id = "TPD1"; - label = "SMC TPD1"; - }; - - temperature-TPD2 { - apple,key-id = "TPD2"; - label = "SMC TPD2"; - }; - - temperature-TPD3 { - apple,key-id = "TPD3"; - label = "SMC TPD3"; - }; - - temperature-TPD4 { - apple,key-id = "TPD4"; - label = "SMC TPD4"; - }; - - temperature-TPD5 { - apple,key-id = "TPD5"; - label = "SMC TPD5"; - }; - - temperature-TPD6 { - apple,key-id = "TPD6"; - label = "SMC TPD6"; - }; - - temperature-TPD7 { - apple,key-id = "TPD7"; - label = "SMC TPD7"; - }; - - temperature-TPD8 { - apple,key-id = "TPD8"; - label = "SMC TPD8"; - }; - - temperature-TPD9 { - apple,key-id = "TPD9"; - label = "SMC TPD9"; - }; - - temperature-TPDX { - apple,key-id = "TPDX"; - label = "SMC TPDX"; - }; - - temperature-TPDa { - apple,key-id = "TPDa"; - label = "SMC TPDa"; - }; - - temperature-TPDb { - apple,key-id = "TPDb"; - label = "SMC TPDb"; - }; - - temperature-TPDc { - apple,key-id = "TPDc"; - label = "SMC TPDc"; - }; - - temperature-TPDd { - apple,key-id = "TPDd"; - label = "SMC TPDd"; - }; - - temperature-TPDe { - apple,key-id = "TPDe"; - label = "SMC TPDe"; - }; - - temperature-TPDf { - apple,key-id = "TPDf"; - label = "SMC TPDf"; - }; - - temperature-TPH3 { - apple,key-id = "TPH3"; - label = "SMC TPH3"; - }; - - temperature-TPMP { - apple,key-id = "TPMP"; - label = "SMC TPMP"; - }; - - temperature-TPSP { - apple,key-id = "TPSP"; - label = "SMC TPSP"; - }; - - temperature-TRD0 { - apple,key-id = "TRD0"; - label = "SMC TRD0"; - }; - - temperature-TRD1 { - apple,key-id = "TRD1"; - label = "SMC TRD1"; - }; - - temperature-TRD2 { - apple,key-id = "TRD2"; - label = "SMC TRD2"; - }; - - temperature-TRD3 { - apple,key-id = "TRD3"; - label = "SMC TRD3"; - }; - - temperature-TRD4 { - apple,key-id = "TRD4"; - label = "SMC TRD4"; - }; - - temperature-TRD5 { - apple,key-id = "TRD5"; - label = "SMC TRD5"; - }; - - temperature-TRD6 { - apple,key-id = "TRD6"; - label = "SMC TRD6"; - }; - - temperature-TRD7 { - apple,key-id = "TRD7"; - label = "SMC TRD7"; - }; - - temperature-TRD8 { - apple,key-id = "TRD8"; - label = "SMC TRD8"; - }; - - temperature-TRD9 { - apple,key-id = "TRD9"; - label = "SMC TRD9"; - }; - - temperature-TRDX { - apple,key-id = "TRDX"; - label = "SMC TRDX"; - }; - - temperature-TRDa { - apple,key-id = "TRDa"; - label = "SMC TRDa"; - }; - - temperature-TRDb { - apple,key-id = "TRDb"; - label = "SMC TRDb"; - }; - - temperature-TRDc { - apple,key-id = "TRDc"; - label = "SMC TRDc"; - }; - - temperature-TRDd { - apple,key-id = "TRDd"; - label = "SMC TRDd"; - }; - - temperature-TRDe { - apple,key-id = "TRDe"; - label = "SMC TRDe"; - }; - - temperature-TRDf { - apple,key-id = "TRDf"; - label = "SMC TRDf"; - }; - - temperature-TS0P { - apple,key-id = "TS0P"; - label = "SMC TS0P"; - }; - - temperature-TSG1 { - apple,key-id = "TSG1"; - label = "SMC TSG1"; - }; - - temperature-TUD0 { - apple,key-id = "TUD0"; - label = "SMC TUD0"; - }; - - temperature-TUD1 { - apple,key-id = "TUD1"; - label = "SMC TUD1"; - }; - - temperature-TUD2 { - apple,key-id = "TUD2"; - label = "SMC TUD2"; - }; - - temperature-TUD3 { - apple,key-id = "TUD3"; - label = "SMC TUD3"; - }; - - temperature-TUD4 { - apple,key-id = "TUD4"; - label = "SMC TUD4"; - }; - - temperature-TUD5 { - apple,key-id = "TUD5"; - label = "SMC TUD5"; - }; - - temperature-TUD6 { - apple,key-id = "TUD6"; - label = "SMC TUD6"; - }; - - temperature-TUD7 { - apple,key-id = "TUD7"; - label = "SMC TUD7"; - }; - - temperature-TUD8 { - apple,key-id = "TUD8"; - label = "SMC TUD8"; - }; - - temperature-TUD9 { - apple,key-id = "TUD9"; - label = "SMC TUD9"; - }; - - temperature-TUDX { - apple,key-id = "TUDX"; - label = "SMC TUDX"; - }; - - temperature-TUDa { - apple,key-id = "TUDa"; - label = "SMC TUDa"; - }; - - temperature-TUDb { - apple,key-id = "TUDb"; - label = "SMC TUDb"; - }; - - temperature-TUDc { - apple,key-id = "TUDc"; - label = "SMC TUDc"; - }; - - temperature-TUDd { - apple,key-id = "TUDd"; - label = "SMC TUDd"; - }; - - temperature-TUDe { - apple,key-id = "TUDe"; - label = "SMC TUDe"; - }; - - temperature-TUDf { - apple,key-id = "TUDf"; - label = "SMC TUDf"; - }; - - temperature-TVA0 { - apple,key-id = "TVA0"; - label = "SMC TVA0"; - }; - - temperature-TVD0 { - apple,key-id = "TVD0"; - label = "SMC TVD0"; - }; - - temperature-TVMR { - apple,key-id = "TVMR"; - label = "SMC TVMR"; - }; - - temperature-TVMX { - apple,key-id = "TVMX"; - label = "SMC TVMX"; - }; - - temperature-TVMr { - apple,key-id = "TVMr"; - label = "SMC TVMr"; - }; - - temperature-TVS0 { - apple,key-id = "TVS0"; - label = "SMC TVS0"; - }; - - temperature-TVS1 { - apple,key-id = "TVS1"; - label = "SMC TVS1"; - }; - - temperature-TVS2 { - apple,key-id = "TVS2"; - label = "SMC TVS2"; - }; - - temperature-TVV0 { - apple,key-id = "TVV0"; - label = "SMC TVV0"; - }; - - temperature-TVXX { - apple,key-id = "TVXX"; - label = "SMC TVXX"; - }; - - temperature-TVXh { - apple,key-id = "TVXh"; - label = "SMC TVXh"; - }; - - temperature-TVXm { - apple,key-id = "TVXm"; - label = "SMC TVXm"; - }; - - temperature-TVh0 { - apple,key-id = "TVh0"; - label = "SMC TVh0"; - }; - - temperature-TVh1 { - apple,key-id = "TVh1"; - label = "SMC TVh1"; - }; - - temperature-TVm0 { - apple,key-id = "TVm0"; - label = "SMC TVm0"; - }; - - temperature-TVxx { - apple,key-id = "TVxx"; - label = "SMC TVxx"; - }; - - temperature-TaLP { - apple,key-id = "TaLP"; - label = "SMC TaLP"; - }; - - temperature-TaLT { - apple,key-id = "TaLT"; - label = "SMC TaLT"; - }; - - temperature-TaLW { - apple,key-id = "TaLW"; - label = "SMC TaLW"; - }; - - temperature-TaRF { - apple,key-id = "TaRF"; - label = "SMC TaRF"; - }; - - temperature-TaRT { - apple,key-id = "TaRT"; - label = "SMC TaRT"; - }; - - temperature-TaRW { - apple,key-id = "TaRW"; - label = "SMC TaRW"; - }; - - temperature-TaTP { - apple,key-id = "TaTP"; - label = "SMC TaTP"; - }; - - temperature-Te04 { - apple,key-id = "Te04"; - label = "E-cluster Te04"; - }; - - temperature-Te05 { - apple,key-id = "Te05"; - label = "E-cluster Te05"; - }; - - temperature-Te06 { - apple,key-id = "Te06"; - label = "E-cluster Te06"; - }; - - temperature-Te0R { - apple,key-id = "Te0R"; - label = "E-cluster Te0R"; - }; - - temperature-Te0S { - apple,key-id = "Te0S"; - label = "E-cluster Te0S"; - }; - - temperature-Te0T { - apple,key-id = "Te0T"; - label = "E-cluster Te0T"; - }; - - temperature-TfC0 { - apple,key-id = "TfC0"; - label = "SMC TfC0"; - }; - - temperature-TfC1 { - apple,key-id = "TfC1"; - label = "SMC TfC1"; - }; - - temperature-TfC2 { - apple,key-id = "TfC2"; - label = "SMC TfC2"; - }; - - temperature-TfC3 { - apple,key-id = "TfC3"; - label = "SMC TfC3"; - }; - - temperature-TfC4 { - apple,key-id = "TfC4"; - label = "SMC TfC4"; - }; - - temperature-Tg04 { - apple,key-id = "Tg04"; - label = "GPU Tg04"; - }; - - temperature-Tg0K { - apple,key-id = "Tg0K"; - label = "GPU Tg0K"; - }; - - temperature-Tg0L { - apple,key-id = "Tg0L"; - label = "GPU Tg0L"; - }; - - temperature-Tg0R { - apple,key-id = "Tg0R"; - label = "GPU Tg0R"; - }; - - temperature-Tg0X { - apple,key-id = "Tg0X"; - label = "GPU Tg0X"; - }; - - temperature-Tg0d { - apple,key-id = "Tg0d"; - label = "GPU Tg0d"; - }; - - temperature-Tg0e { - apple,key-id = "Tg0e"; - label = "GPU Tg0e"; - }; - - temperature-Tg0j { - apple,key-id = "Tg0j"; - label = "GPU Tg0j"; - }; - - temperature-Tg0y { - apple,key-id = "Tg0y"; - label = "GPU Tg0y"; - }; - - temperature-Tg1E { - apple,key-id = "Tg1E"; - label = "GPU Tg1E"; - }; - - temperature-Tg1F { - apple,key-id = "Tg1F"; - label = "GPU Tg1F"; - }; - - temperature-Tg1U { - apple,key-id = "Tg1U"; - label = "GPU Tg1U"; - }; - - temperature-Tg1c { - apple,key-id = "Tg1c"; - label = "GPU Tg1c"; - }; - - temperature-Tg1d { - apple,key-id = "Tg1d"; - label = "GPU Tg1d"; - }; - - temperature-Tg1k { - apple,key-id = "Tg1k"; - label = "GPU Tg1k"; - }; - - temperature-Tp00 { - apple,key-id = "Tp00"; - label = "P-cluster Tp00"; - }; - - temperature-Tp01 { - apple,key-id = "Tp01"; - label = "P-cluster Tp01"; - }; - - temperature-Tp02 { - apple,key-id = "Tp02"; - label = "P-cluster Tp02"; - }; - - temperature-Tp04 { - apple,key-id = "Tp04"; - label = "P-cluster Tp04"; - }; - - temperature-Tp05 { - apple,key-id = "Tp05"; - label = "P-cluster Tp05"; - }; - - temperature-Tp06 { - apple,key-id = "Tp06"; - label = "P-cluster Tp06"; - }; - - temperature-Tp08 { - apple,key-id = "Tp08"; - label = "P-cluster Tp08"; - }; - - temperature-Tp09 { - apple,key-id = "Tp09"; - label = "P-cluster Tp09"; - }; - - temperature-Tp0A { - apple,key-id = "Tp0A"; - label = "P-cluster Tp0A"; - }; - - temperature-Tp0C { - apple,key-id = "Tp0C"; - label = "P-cluster Tp0C"; - }; - - temperature-Tp0D { - apple,key-id = "Tp0D"; - label = "P-cluster Tp0D"; - }; - - temperature-Tp0E { - apple,key-id = "Tp0E"; - label = "P-cluster Tp0E"; - }; - - temperature-Tp0G { - apple,key-id = "Tp0G"; - label = "P-cluster Tp0G"; - }; - - temperature-Tp0H { - apple,key-id = "Tp0H"; - label = "P-cluster Tp0H"; - }; - - temperature-Tp0I { - apple,key-id = "Tp0I"; - label = "P-cluster Tp0I"; - }; - - temperature-Tp0K { - apple,key-id = "Tp0K"; - label = "P-cluster Tp0K"; - }; - - temperature-Tp0L { - apple,key-id = "Tp0L"; - label = "P-cluster Tp0L"; - }; - - temperature-Tp0M { - apple,key-id = "Tp0M"; - label = "P-cluster Tp0M"; - }; - - temperature-Tp0O { - apple,key-id = "Tp0O"; - label = "P-cluster Tp0O"; - }; - - temperature-Tp0P { - apple,key-id = "Tp0P"; - label = "P-cluster Tp0P"; - }; - - temperature-Tp0Q { - apple,key-id = "Tp0Q"; - label = "P-cluster Tp0Q"; - }; - - temperature-Tp0S { - apple,key-id = "Tp0S"; - label = "P-cluster Tp0S"; - }; - - temperature-Tp0T { - apple,key-id = "Tp0T"; - label = "P-cluster Tp0T"; - }; - - temperature-Tp0U { - apple,key-id = "Tp0U"; - label = "P-cluster Tp0U"; - }; - - temperature-Tp0W { - apple,key-id = "Tp0W"; - label = "P-cluster Tp0W"; - }; - - temperature-Tp0X { - apple,key-id = "Tp0X"; - label = "P-cluster Tp0X"; - }; - - temperature-Tp0Y { - apple,key-id = "Tp0Y"; - label = "P-cluster Tp0Y"; - }; - - temperature-Tp0a { - apple,key-id = "Tp0a"; - label = "P-cluster Tp0a"; - }; - - temperature-Tp0b { - apple,key-id = "Tp0b"; - label = "P-cluster Tp0b"; - }; - - temperature-Tp0c { - apple,key-id = "Tp0c"; - label = "P-cluster Tp0c"; - }; - - temperature-Tp0d { - apple,key-id = "Tp0d"; - label = "P-cluster Tp0d"; - }; - - temperature-Tp0e { - apple,key-id = "Tp0e"; - label = "P-cluster Tp0e"; - }; - - temperature-Tp0f { - apple,key-id = "Tp0f"; - label = "P-cluster Tp0f"; - }; - - temperature-Tp0g { - apple,key-id = "Tp0g"; - label = "P-cluster Tp0g"; - }; - - temperature-Tp0i { - apple,key-id = "Tp0i"; - label = "P-cluster Tp0i"; - }; - - temperature-Tp0k { - apple,key-id = "Tp0k"; - label = "P-cluster Tp0k"; - }; - - temperature-Tp0l { - apple,key-id = "Tp0l"; - label = "P-cluster Tp0l"; - }; - - temperature-Tp0m { - apple,key-id = "Tp0m"; - label = "P-cluster Tp0m"; - }; - - temperature-Tp0n { - apple,key-id = "Tp0n"; - label = "P-cluster Tp0n"; - }; - - temperature-Tp0o { - apple,key-id = "Tp0o"; - label = "P-cluster Tp0o"; - }; - - temperature-Tp0q { - apple,key-id = "Tp0q"; - label = "P-cluster Tp0q"; - }; - - temperature-Tp0t { - apple,key-id = "Tp0t"; - label = "P-cluster Tp0t"; - }; - - temperature-Tp0u { - apple,key-id = "Tp0u"; - label = "P-cluster Tp0u"; - }; - - temperature-Tp0v { - apple,key-id = "Tp0v"; - label = "P-cluster Tp0v"; - }; - - temperature-Tp0w { - apple,key-id = "Tp0w"; - label = "P-cluster Tp0w"; - }; - - temperature-Tp1i { - apple,key-id = "Tp1i"; - label = "P-cluster Tp1i"; - }; - - temperature-Tp1j { - apple,key-id = "Tp1j"; - label = "P-cluster Tp1j"; - }; - - temperature-Tp1k { - apple,key-id = "Tp1k"; - label = "P-cluster Tp1k"; - }; - - temperature-Tp1m { - apple,key-id = "Tp1m"; - label = "P-cluster Tp1m"; - }; - - temperature-Tp1n { - apple,key-id = "Tp1n"; - label = "P-cluster Tp1n"; - }; - - temperature-Tp1o { - apple,key-id = "Tp1o"; - label = "P-cluster Tp1o"; - }; - - temperature-Tp1q { - apple,key-id = "Tp1q"; - label = "P-cluster Tp1q"; - }; - - temperature-Tp1t { - apple,key-id = "Tp1t"; - label = "P-cluster Tp1t"; - }; - - temperature-Tp1u { - apple,key-id = "Tp1u"; - label = "P-cluster Tp1u"; - }; - - temperature-Tp1v { - apple,key-id = "Tp1v"; - label = "P-cluster Tp1v"; - }; - - temperature-Tp1w { - apple,key-id = "Tp1w"; - label = "P-cluster Tp1w"; - }; - - temperature-Tp1x { - apple,key-id = "Tp1x"; - label = "P-cluster Tp1x"; - }; - - temperature-Tp1y { - apple,key-id = "Tp1y"; - label = "P-cluster Tp1y"; - }; - - temperature-Tp1z { - apple,key-id = "Tp1z"; - label = "P-cluster Tp1z"; - }; - - temperature-Tp20 { - apple,key-id = "Tp20"; - label = "P-cluster Tp20"; - }; - - temperature-Tp21 { - apple,key-id = "Tp21"; - label = "P-cluster Tp21"; - }; - - temperature-Tp22 { - apple,key-id = "Tp22"; - label = "P-cluster Tp22"; - }; - - temperature-Tp23 { - apple,key-id = "Tp23"; - label = "P-cluster Tp23"; - }; - - temperature-Tp24 { - apple,key-id = "Tp24"; - label = "P-cluster Tp24"; - }; - - temperature-Tp25 { - apple,key-id = "Tp25"; - label = "P-cluster Tp25"; - }; - - temperature-Tp26 { - apple,key-id = "Tp26"; - label = "P-cluster Tp26"; - }; - - temperature-Tp27 { - apple,key-id = "Tp27"; - label = "P-cluster Tp27"; - }; - - temperature-Tp28 { - apple,key-id = "Tp28"; - label = "P-cluster Tp28"; - }; - - temperature-Tp29 { - apple,key-id = "Tp29"; - label = "P-cluster Tp29"; - }; - - temperature-Tp2A { - apple,key-id = "Tp2A"; - label = "P-cluster Tp2A"; - }; - - temperature-Tp2B { - apple,key-id = "Tp2B"; - label = "P-cluster Tp2B"; - }; - - temperature-Tp2C { - apple,key-id = "Tp2C"; - label = "P-cluster Tp2C"; - }; - - temperature-Tp2D { - apple,key-id = "Tp2D"; - label = "P-cluster Tp2D"; - }; - - temperature-Tp2E { - apple,key-id = "Tp2E"; - label = "P-cluster Tp2E"; - }; - - temperature-Tp2G { - apple,key-id = "Tp2G"; - label = "P-cluster Tp2G"; - }; - - temperature-Tp2I { - apple,key-id = "Tp2I"; - label = "P-cluster Tp2I"; - }; - - temperature-Tp2J { - apple,key-id = "Tp2J"; - label = "P-cluster Tp2J"; - }; - - temperature-Tp2K { - apple,key-id = "Tp2K"; - label = "P-cluster Tp2K"; - }; - - temperature-Tp2L { - apple,key-id = "Tp2L"; - label = "P-cluster Tp2L"; - }; - - temperature-Tp2M { - apple,key-id = "Tp2M"; - label = "P-cluster Tp2M"; - }; - - temperature-Tp2N { - apple,key-id = "Tp2N"; - label = "P-cluster Tp2N"; - }; - - temperature-Tp2O { - apple,key-id = "Tp2O"; - label = "P-cluster Tp2O"; - }; - - temperature-Tp2Q { - apple,key-id = "Tp2Q"; - label = "P-cluster Tp2Q"; - }; - - temperature-Tp2R { - apple,key-id = "Tp2R"; - label = "P-cluster Tp2R"; - }; - - temperature-Tp2S { - apple,key-id = "Tp2S"; - label = "P-cluster Tp2S"; - }; - - temperature-Tp2T { - apple,key-id = "Tp2T"; - label = "P-cluster Tp2T"; - }; - - temperature-Tp2U { - apple,key-id = "Tp2U"; - label = "P-cluster Tp2U"; - }; - - temperature-Tp2V { - apple,key-id = "Tp2V"; - label = "P-cluster Tp2V"; - }; - - temperature-Tp2W { - apple,key-id = "Tp2W"; - label = "P-cluster Tp2W"; - }; - - temperature-Tp2X { - apple,key-id = "Tp2X"; - label = "P-cluster Tp2X"; - }; - - temperature-Ts00 { - apple,key-id = "Ts00"; - label = "SMC Ts00"; - }; - - temperature-Ts01 { - apple,key-id = "Ts01"; - label = "SMC Ts01"; - }; - - temperature-Ts02 { - apple,key-id = "Ts02"; - label = "SMC Ts02"; - }; - - temperature-Ts04 { - apple,key-id = "Ts04"; - label = "SMC Ts04"; - }; - - temperature-Ts05 { - apple,key-id = "Ts05"; - label = "SMC Ts05"; - }; - - temperature-Ts06 { - apple,key-id = "Ts06"; - label = "SMC Ts06"; - }; - - temperature-Ts08 { - apple,key-id = "Ts08"; - label = "SMC Ts08"; - }; - - temperature-Ts09 { - apple,key-id = "Ts09"; - label = "SMC Ts09"; - }; - - temperature-Ts0A { - apple,key-id = "Ts0A"; - label = "SMC Ts0A"; - }; - - temperature-Ts0C { - apple,key-id = "Ts0C"; - label = "SMC Ts0C"; - }; - - temperature-Ts0D { - apple,key-id = "Ts0D"; - label = "SMC Ts0D"; - }; - - temperature-Ts0E { - apple,key-id = "Ts0E"; - label = "SMC Ts0E"; - }; - - temperature-Ts0G { - apple,key-id = "Ts0G"; - label = "SMC Ts0G"; - }; - - temperature-Ts0H { - apple,key-id = "Ts0H"; - label = "SMC Ts0H"; - }; - - temperature-Ts0I { - apple,key-id = "Ts0I"; - label = "SMC Ts0I"; - }; - - temperature-Ts0K { - apple,key-id = "Ts0K"; - label = "SMC Ts0K"; - }; - - temperature-Ts0L { - apple,key-id = "Ts0L"; - label = "SMC Ts0L"; - }; - - temperature-Ts0M { - apple,key-id = "Ts0M"; - label = "SMC Ts0M"; - }; - - temperature-Ts0O { - apple,key-id = "Ts0O"; - label = "SMC Ts0O"; - }; - - temperature-Ts0P { - apple,key-id = "Ts0P"; - label = "SMC Ts0P"; - }; - - temperature-Ts0Q { - apple,key-id = "Ts0Q"; - label = "SMC Ts0Q"; - }; - - temperature-Ts0R { - apple,key-id = "Ts0R"; - label = "SMC Ts0R"; - }; - - temperature-Ts0T { - apple,key-id = "Ts0T"; - label = "SMC Ts0T"; - }; - - temperature-Ts0U { - apple,key-id = "Ts0U"; - label = "SMC Ts0U"; - }; - - temperature-Ts0W { - apple,key-id = "Ts0W"; - label = "SMC Ts0W"; - }; - - temperature-Ts1P { - apple,key-id = "Ts1P"; - label = "SMC Ts1P"; - }; - - temperature-Tsx0 { - apple,key-id = "Tsx0"; - label = "SMC Tsx0"; - }; - - temperature-Tsx1 { - apple,key-id = "Tsx1"; - label = "SMC Tsx1"; - }; - - voltage-VDM2 { - apple,key-id = "VDM2"; - label = "SMC VDM2"; - }; - - voltage-VDMA { - apple,key-id = "VDMA"; - label = "SMC VDMA"; - }; - - voltage-VDMM { - apple,key-id = "VDMM"; - label = "SMC VDMM"; - }; - - voltage-VP0R { - apple,key-id = "VP0R"; - label = "SMC VP0R"; - }; - - voltage-VP0b { - apple,key-id = "VP0b"; - label = "SMC VP0b"; - }; - - voltage-VP1b { - apple,key-id = "VP1b"; - label = "SMC VP1b"; - }; - - voltage-VP1l { - apple,key-id = "VP1l"; - label = "SMC VP1l"; - }; - - voltage-VP2b { - apple,key-id = "VP2b"; - label = "SMC VP2b"; - }; - - voltage-VP3b { - apple,key-id = "VP3b"; - label = "SMC VP3b"; - }; - - voltage-VP4b { - apple,key-id = "VP4b"; - label = "SMC VP4b"; - }; - - voltage-VP5l { - apple,key-id = "VP5l"; - label = "SMC VP5l"; - }; - - voltage-VP7b { - apple,key-id = "VP7b"; - label = "SMC VP7b"; - }; - - voltage-VP7l { - apple,key-id = "VP7l"; - label = "SMC VP7l"; - }; - - voltage-VP9b { - apple,key-id = "VP9b"; - label = "SMC VP9b"; - }; - - voltage-VP9l { - apple,key-id = "VP9l"; - label = "SMC VP9l"; - }; - - voltage-VPal { - apple,key-id = "VPal"; - label = "SMC VPal"; - }; - - voltage-VPbb { - apple,key-id = "VPbb"; - label = "SMC VPbb"; - }; - - voltage-VPdb { - apple,key-id = "VPdb"; - label = "SMC VPdb"; - }; - - voltage-VPeb { - apple,key-id = "VPeb"; - label = "SMC VPeb"; - }; - - voltage-VR0b { - apple,key-id = "VR0b"; - label = "SMC VR0b"; - }; - - voltage-VR1b { - apple,key-id = "VR1b"; - label = "SMC VR1b"; - }; - - voltage-VR2b { - apple,key-id = "VR2b"; - label = "SMC VR2b"; - }; - - voltage-VR3b { - apple,key-id = "VR3b"; - label = "SMC VR3b"; - }; - - voltage-VR3l { - apple,key-id = "VR3l"; - label = "SMC VR3l"; - }; - - voltage-VR4b { - apple,key-id = "VR4b"; - label = "SMC VR4b"; - }; - - voltage-VR7b { - apple,key-id = "VR7b"; - label = "SMC VR7b"; - }; - - voltage-VR8l { - apple,key-id = "VR8l"; - label = "SMC VR8l"; - }; - - voltage-VR9b { - apple,key-id = "VR9b"; - label = "SMC VR9b"; - }; - - voltage-VR9l { - apple,key-id = "VR9l"; - label = "SMC VR9l"; - }; - - voltage-VRal { - apple,key-id = "VRal"; - label = "SMC VRal"; - }; - - voltage-VRbb { - apple,key-id = "VRbb"; - label = "SMC VRbb"; - }; - - voltage-VRdb { - apple,key-id = "VRdb"; - label = "SMC VRdb"; - }; - - voltage-VReb { - apple,key-id = "VReb"; - label = "SMC VReb"; - }; - - voltage-VS0b { - apple,key-id = "VS0b"; - label = "SMC VS0b"; - }; - - voltage-VS1b { - apple,key-id = "VS1b"; - label = "SMC VS1b"; - }; - - voltage-VS2b { - apple,key-id = "VS2b"; - label = "SMC VS2b"; - }; - - voltage-VS3b { - apple,key-id = "VS3b"; - label = "SMC VS3b"; - }; - - voltage-VS4b { - apple,key-id = "VS4b"; - label = "SMC VS4b"; - }; - - voltage-VS7b { - apple,key-id = "VS7b"; - label = "SMC VS7b"; - }; - - voltage-VS9b { - apple,key-id = "VS9b"; - label = "SMC VS9b"; - }; - - voltage-VS9l { - apple,key-id = "VS9l"; - label = "SMC VS9l"; - }; - - voltage-VSal { - apple,key-id = "VSal"; - label = "SMC VSal"; - }; - - voltage-VSbb { - apple,key-id = "VSbb"; - label = "SMC VSbb"; - }; - - voltage-VSeb { - apple,key-id = "VSeb"; - label = "SMC VSeb"; - }; - - voltage-Vb0f { - apple,key-id = "Vb0f"; - label = "SMC Vb0f"; - }; - - voltage-Vb1f { - apple,key-id = "Vb1f"; - label = "SMC Vb1f"; - }; - - voltage-SBA1 { - apple,key-id = "SBA1"; - label = "Battery Cell 1"; - }; - - voltage-SBA2 { - apple,key-id = "SBA2"; - label = "Battery Cell 2"; - }; - - voltage-SBA3 { - apple,key-id = "SBA3"; - label = "Battery Cell 3"; - }; - - current-I3F2 { - apple,key-id = "I3F2"; - label = "SMC I3F2"; - }; - - current-I5SR { - apple,key-id = "I5SR"; - label = "SMC I5SR"; - }; - - current-IALR { - apple,key-id = "IALR"; - label = "SMC IALR"; - }; - - current-IARR { - apple,key-id = "IARR"; - label = "SMC IARR"; - }; - - current-ICMC { - apple,key-id = "ICMC"; - label = "SMC ICMC"; - }; - - current-ID3C { - apple,key-id = "ID3C"; - label = "SMC ID3C"; - }; - - current-IDBC { - apple,key-id = "IDBC"; - label = "SMC IDBC"; - }; - - current-IDBR { - apple,key-id = "IDBR"; - label = "SMC IDBR"; - }; - - current-IFLR { - apple,key-id = "IFLR"; - label = "SMC IFLR"; - }; - - current-IH0R { - apple,key-id = "IH0R"; - label = "SMC IH0R"; - }; - - current-IHCR { - apple,key-id = "IHCR"; - label = "SMC IHCR"; - }; - - current-IIDR { - apple,key-id = "IIDR"; - label = "SMC IIDR"; - }; - - current-IINC { - apple,key-id = "IINC"; - label = "SMC IINC"; - }; - - current-IMVC { - apple,key-id = "IMVC"; - label = "SMC IMVC"; - }; - - current-IMVR { - apple,key-id = "IMVR"; - label = "SMC IMVR"; - }; - - current-IP0b { - apple,key-id = "IP0b"; - label = "SMC IP0b"; - }; - - current-IP1b { - apple,key-id = "IP1b"; - label = "SMC IP1b"; - }; - - current-IP1l { - apple,key-id = "IP1l"; - label = "SMC IP1l"; - }; - - current-IP2b { - apple,key-id = "IP2b"; - label = "SMC IP2b"; - }; - - current-IP3b { - apple,key-id = "IP3b"; - label = "SMC IP3b"; - }; - - current-IP4b { - apple,key-id = "IP4b"; - label = "SMC IP4b"; - }; - - current-IP5l { - apple,key-id = "IP5l"; - label = "SMC IP5l"; - }; - - current-IP7b { - apple,key-id = "IP7b"; - label = "SMC IP7b"; - }; - - current-IP7l { - apple,key-id = "IP7l"; - label = "SMC IP7l"; - }; - - current-IP9b { - apple,key-id = "IP9b"; - label = "SMC IP9b"; - }; - - current-IP9l { - apple,key-id = "IP9l"; - label = "SMC IP9l"; - }; - - current-IPBR { - apple,key-id = "IPBR"; - label = "SMC IPBR"; - }; - - current-IPMC { - apple,key-id = "IPMC"; - label = "SMC IPMC"; - }; - - current-IPSC { - apple,key-id = "IPSC"; - label = "SMC IPSC"; - }; - - current-IPal { - apple,key-id = "IPal"; - label = "SMC IPal"; - }; - - current-IPbb { - apple,key-id = "IPbb"; - label = "SMC IPbb"; - }; - - current-IPdb { - apple,key-id = "IPdb"; - label = "SMC IPdb"; - }; - - current-IPeb { - apple,key-id = "IPeb"; - label = "SMC IPeb"; - }; - - current-IR0b { - apple,key-id = "IR0b"; - label = "SMC IR0b"; - }; - - current-IR1b { - apple,key-id = "IR1b"; - label = "SMC IR1b"; - }; - - current-IR2b { - apple,key-id = "IR2b"; - label = "SMC IR2b"; - }; - - current-IR3b { - apple,key-id = "IR3b"; - label = "SMC IR3b"; - }; - - current-IR3l { - apple,key-id = "IR3l"; - label = "SMC IR3l"; - }; - - current-IR4b { - apple,key-id = "IR4b"; - label = "SMC IR4b"; - }; - - current-IR7b { - apple,key-id = "IR7b"; - label = "SMC IR7b"; - }; - - current-IR8l { - apple,key-id = "IR8l"; - label = "SMC IR8l"; - }; - - current-IR9b { - apple,key-id = "IR9b"; - label = "SMC IR9b"; - }; - - current-IR9l { - apple,key-id = "IR9l"; - label = "SMC IR9l"; - }; - - current-IRal { - apple,key-id = "IRal"; - label = "SMC IRal"; - }; - - current-IRbb { - apple,key-id = "IRbb"; - label = "SMC IRbb"; - }; - - current-IRdb { - apple,key-id = "IRdb"; - label = "SMC IRdb"; - }; - - current-IReb { - apple,key-id = "IReb"; - label = "SMC IReb"; - }; - - current-IS0b { - apple,key-id = "IS0b"; - label = "SMC IS0b"; - }; - - current-IS1b { - apple,key-id = "IS1b"; - label = "SMC IS1b"; - }; - - current-IS2b { - apple,key-id = "IS2b"; - label = "SMC IS2b"; - }; - - current-IS3b { - apple,key-id = "IS3b"; - label = "SMC IS3b"; - }; - - current-IS4b { - apple,key-id = "IS4b"; - label = "SMC IS4b"; - }; - - current-IS7b { - apple,key-id = "IS7b"; - label = "SMC IS7b"; - }; - - current-IS9b { - apple,key-id = "IS9b"; - label = "SMC IS9b"; - }; - - current-IS9l { - apple,key-id = "IS9l"; - label = "SMC IS9l"; - }; - - current-ISDR { - apple,key-id = "ISDR"; - label = "SMC ISDR"; - }; - - current-ISal { - apple,key-id = "ISal"; - label = "SMC ISal"; - }; - - current-ISbb { - apple,key-id = "ISbb"; - label = "SMC ISbb"; - }; - - current-ISeb { - apple,key-id = "ISeb"; - label = "SMC ISeb"; - }; - - current-IU1C { - apple,key-id = "IU1C"; - label = "SMC IU1C"; - }; - - current-IU1R { - apple,key-id = "IU1R"; - label = "SMC IU1R"; - }; - - current-IU2C { - apple,key-id = "IU2C"; - label = "SMC IU2C"; - }; - - current-IU2R { - apple,key-id = "IU2R"; - label = "SMC IU2R"; - }; - - current-IW3C { - apple,key-id = "IW3C"; - label = "SMC IW3C"; - }; - - current-Ib0f { - apple,key-id = "Ib0f"; - label = "SMC Ib0f"; - }; - - current-Ib8f { - apple,key-id = "Ib8f"; - label = "SMC Ib8f"; - }; - - current-SBAC { - apple,key-id = "SBAC"; - label = "Battery Current"; - }; - - power-P3F2 { - apple,key-id = "P3F2"; - label = "SMC P3F2"; - }; - - power-P5SR { - apple,key-id = "P5SR"; - label = "SMC P5SR"; - }; - - power-PALR { - apple,key-id = "PALR"; - label = "SMC PALR"; - }; - - power-PARR { - apple,key-id = "PARR"; - label = "SMC PARR"; - }; - - power-PCMC { - apple,key-id = "PCMC"; - label = "SMC PCMC"; - }; - - power-PD3C { - apple,key-id = "PD3C"; - label = "SMC PD3C"; - }; - - power-PDBC { - apple,key-id = "PDBC"; - label = "SMC PDBC"; - }; - - power-PDBR { - apple,key-id = "PDBR"; - label = "SMC PDBR"; - }; - - power-PFLR { - apple,key-id = "PFLR"; - label = "SMC PFLR"; - }; - - power-PH0R { - apple,key-id = "PH0R"; - label = "SMC PH0R"; - }; - - power-PHCR { - apple,key-id = "PHCR"; - label = "SMC PHCR"; - }; - - power-PIDR { - apple,key-id = "PIDR"; - label = "SMC PIDR"; - }; - - power-PINC { - apple,key-id = "PINC"; - label = "SMC PINC"; - }; - - power-PMVC { - apple,key-id = "PMVC"; - label = "SMC PMVC"; - }; - - power-PP0b { - apple,key-id = "PP0b"; - label = "SMC PP0b"; - }; - - power-PP1b { - apple,key-id = "PP1b"; - label = "SMC PP1b"; - }; - - power-PP1l { - apple,key-id = "PP1l"; - label = "SMC PP1l"; - }; - - power-PP2b { - apple,key-id = "PP2b"; - label = "SMC PP2b"; - }; - - power-PP3b { - apple,key-id = "PP3b"; - label = "SMC PP3b"; - }; - - power-PP4b { - apple,key-id = "PP4b"; - label = "SMC PP4b"; - }; - - power-PP5l { - apple,key-id = "PP5l"; - label = "SMC PP5l"; - }; - - power-PP7b { - apple,key-id = "PP7b"; - label = "SMC PP7b"; - }; - - power-PP7l { - apple,key-id = "PP7l"; - label = "SMC PP7l"; - }; - - power-PP9b { - apple,key-id = "PP9b"; - label = "SMC PP9b"; - }; - - power-PP9l { - apple,key-id = "PP9l"; - label = "SMC PP9l"; - }; - - power-PPBR { - apple,key-id = "PPBR"; - label = "SMC PPBR"; - }; - - power-PPMC { - apple,key-id = "PPMC"; - label = "SMC PPMC"; - }; - - power-PPSC { - apple,key-id = "PPSC"; - label = "SMC PPSC"; - }; - - power-PPal { - apple,key-id = "PPal"; - label = "SMC PPal"; - }; - - power-PPbb { - apple,key-id = "PPbb"; - label = "SMC PPbb"; - }; - - power-PPdb { - apple,key-id = "PPdb"; - label = "SMC PPdb"; - }; - - power-PPeb { - apple,key-id = "PPeb"; - label = "SMC PPeb"; - }; - - power-PR0b { - apple,key-id = "PR0b"; - label = "SMC PR0b"; - }; - - power-PR1b { - apple,key-id = "PR1b"; - label = "SMC PR1b"; - }; - - power-PR2b { - apple,key-id = "PR2b"; - label = "SMC PR2b"; - }; - - power-PR3b { - apple,key-id = "PR3b"; - label = "SMC PR3b"; - }; - - power-PR3l { - apple,key-id = "PR3l"; - label = "SMC PR3l"; - }; - - power-PR4b { - apple,key-id = "PR4b"; - label = "SMC PR4b"; - }; - - power-PR7b { - apple,key-id = "PR7b"; - label = "SMC PR7b"; - }; - - power-PR8l { - apple,key-id = "PR8l"; - label = "SMC PR8l"; - }; - - power-PR9b { - apple,key-id = "PR9b"; - label = "SMC PR9b"; - }; - - power-PR9l { - apple,key-id = "PR9l"; - label = "SMC PR9l"; - }; - - power-PRal { - apple,key-id = "PRal"; - label = "SMC PRal"; - }; - - power-PRbb { - apple,key-id = "PRbb"; - label = "SMC PRbb"; - }; - - power-PRdb { - apple,key-id = "PRdb"; - label = "SMC PRdb"; - }; - - power-PReb { - apple,key-id = "PReb"; - label = "SMC PReb"; - }; - - power-PS0b { - apple,key-id = "PS0b"; - label = "SMC PS0b"; - }; - - power-PS1b { - apple,key-id = "PS1b"; - label = "SMC PS1b"; - }; - - power-PS2b { - apple,key-id = "PS2b"; - label = "SMC PS2b"; - }; - - power-PS3b { - apple,key-id = "PS3b"; - label = "SMC PS3b"; - }; - - power-PS4b { - apple,key-id = "PS4b"; - label = "SMC PS4b"; - }; - - power-PS7b { - apple,key-id = "PS7b"; - label = "SMC PS7b"; - }; - - power-PS9b { - apple,key-id = "PS9b"; - label = "SMC PS9b"; - }; - - power-PS9l { - apple,key-id = "PS9l"; - label = "SMC PS9l"; - }; - - power-PSDR { - apple,key-id = "PSDR"; - label = "SMC PSDR"; - }; - - power-PSal { - apple,key-id = "PSal"; - label = "SMC PSal"; - }; - - power-PSbb { - apple,key-id = "PSbb"; - label = "SMC PSbb"; - }; - - power-PSeb { - apple,key-id = "PSeb"; - label = "SMC PSeb"; - }; - - power-PTHS { - apple,key-id = "PTHS"; - label = "SMC PTHS"; - }; - - power-PU1C { - apple,key-id = "PU1C"; - label = "SMC PU1C"; - }; - - power-PU1R { - apple,key-id = "PU1R"; - label = "SMC PU1R"; - }; - - power-PU2C { - apple,key-id = "PU2C"; - label = "SMC PU2C"; - }; - - power-PU2R { - apple,key-id = "PU2R"; - label = "SMC PU2R"; - }; - - power-PW3C { - apple,key-id = "PW3C"; - label = "SMC PW3C"; - }; - - power-PZC0 { - apple,key-id = "PZC0"; - label = "SMC PZC0"; - }; - - power-PZC1 { - apple,key-id = "PZC1"; - label = "SMC PZC1"; - }; - - power-PZCB { - apple,key-id = "PZCB"; - label = "SMC PZCB"; - }; - - power-PZCU { - apple,key-id = "PZCU"; - label = "SMC PZCU"; - }; - - power-PZD1 { - apple,key-id = "PZD1"; - label = "SMC PZD1"; - }; - - power-PZT0 { - apple,key-id = "PZT0"; - label = "SMC PZT0"; - }; - - power-Pb0f { - apple,key-id = "Pb0f"; - label = "SMC Pb0f"; - }; - - power-SBAP { - apple,key-id = "SBAP"; - label = "Battery Power"; - }; -}; - -/* Fan monitoring and explicit userspace manual control. */ -#include "hwmon-common.dtsi" -#include "hwmon-fan-dual.dtsi" -#include "hwmon-laptop.dtsi" - -&smc_hwmon { - apple,thermal-pressure-key = "mTPL"; - - /* - * The SMC accepts a manual speed for both fans once its fan test - * key unlocks them, and returns them to system mode when the - * request is dropped. - */ - fan-F0Ac { - apple,fan-manual-control; - }; - - fan-F1Ac { - apple,fan-manual-control; - }; -}; - -&pcie_dart { - status = "okay"; -}; - -/* - * Wi-Fi and Bluetooth (BCM4388). The boot loader finds these nodes by - * the paths in the wifi0 and bluetooth0 aliases and adds the per-unit - * calibration, the antenna SKU and the addresses to them. - */ -/ { - pcie0: pcie@1cb0000000 { - compatible = "apple,t6041-pcie", "apple,t6020-pcie"; - reg = <0x1c 0xb0000000 0x0 0x1000000>, /* config */ - <0x4 0x14000000 0x0 0x4000>, /* rc */ - <0x4 0x10028000 0x0 0x4000>, /* port0 */ - <0x4 0x11028000 0x0 0x4000>, /* port1 */ - <0x4 0x12028000 0x0 0x4000>, /* port2 */ - <0x4 0x13028000 0x0 0x4000>, /* port3 */ - <0x4 0x17020000 0x0 0x4000>, /* phy0 */ - <0x4 0x17024000 0x0 0x4000>, /* phy1 */ - <0x4 0x17028000 0x0 0x4000>, /* phy2 */ - <0x4 0x1702c000 0x0 0x4000>; /* phy3 */ - reg-names = "config", "rc", - "port0", "port1", "port2", "port3", - "phy0", "phy1", "phy2", "phy3"; - ranges = <0x43000000 0xb 0xc0000000 0xb 0xc0000000 0 0x20000000 0x2000000 0 0x80000000 0xb 0x80000000 0 0x40000000>; - pinctrl-names = "default"; - pinctrl-0 = <&wifi_clkreq_pins>; - #size-cells = <2>; - #address-cells = <3>; - bus-range = <0 1>; - iommu-map-mask = <0xffff>; - iommu-map = <0x100 &pcie_dart 1 2>; - msi-ranges = <&aic AIC_IRQ 2071 IRQ_TYPE_EDGE_RISING 32>; - msi-parent = <&pcie0>; - msi-controller; - interrupts = <0 0x6bb 4>; - interrupt-parent = <&aic>; - device_type = "pci"; - - port00: pci@0,0 { - reg = <0 0 0 0 0>; - ranges; - interrupt-map = <0 0 0 1 &port00 0 0 0 0 0 0 0 2 &port00 0 0 0 1 0 0 0 3 &port00 0 0 0 2 0 0 0 4 &port00 0 0 0 3>; - interrupt-map-mask = <0 0 0 7>; - #interrupt-cells = <1>; - interrupt-controller; - #size-cells = <2>; - #address-cells = <3>; - pwren-gpios = <&smc_gpio 0x13 0>; - reset-gpios = <&pinctrl 4 1>; - device_type = "pci"; - - bluetooth@0,1 { - compatible = "pci14e4,5f72"; - reg = <0x100 0 0 0 0>; - /* To be filled by the loader */ - local-bd-address = [00 00 00 00 00 00]; - brcm,board-type = "apple,hostomel"; - status = "okay"; - }; - - wifi@0,0 { - compatible = "pci14e4,4434", "brcm,bcm4329-fmac"; - reg = <0 0 0 0 0>; - brcm,ccode-map-trivial; - brcm,board-type = "apple,hostomel"; - }; - }; - }; -}; - -&ans_mbox { - status = "okay"; -}; - -&ans_sart { - status = "okay"; -}; - -&nvme { - status = "okay"; -}; - -&pmp_dart { - status = "okay"; -}; - -&pmp_report { - status = "okay"; -}; - -&pmp { - status = "okay"; -}; - -&pmp_mbox { - status = "okay"; -}; - -&dcp { - panel { - compatible = "apple,panel-j616", "apple,panel-mini-led", "apple,panel"; - width-mm = <345>; - height-mm = <223>; - adj-height-mm = <216>; - status = "disabled"; - }; -}; - -&pmc { - status = "okay"; -}; - -&mcc { - status = "okay"; -}; - -&aop { - status = "okay"; -}; - -&aop_mbox { - status = "okay"; -}; - -&aop_dart { - status = "okay"; }; -/* The sensor is on the logic board, in the base. */ -&aop_accel { - label = "accel-base"; +/* ADT /arm-io/wlan module-instance */ +&wifi0 { + brcm,board-type = "apple,hostomel"; }; -&admac0 { - status = "okay"; +&bluetooth0 { + brcm,board-type = "apple,hostomel"; }; -&admac1 { - status = "okay"; +&panel { + compatible = "apple,panel-j616", "apple,panel-mini-led", "apple,panel"; + width-mm = <345>; + height-mm = <223>; + adj-height-mm = <216>; }; &aop_audio { apple,chassis-name = "J616"; - apple,machine-kind = "MacBook Pro"; -}; - -/* - * The six SN012776 speaker amps, held in hardware shutdown until a sound - * card enables them (ADT /arm-io/i2c1/audio-speaker@38): one interrupt line - * on AP pin 55, level low; sense slots from speaker-config (speaker i at RX - * bytes 4i and 4i + 2, order LW1 RW1 LW2 RW2 LT RT). Names as on the J314 - * and J316, which speakersafetyd's profile uses. Every amp joins the SDZ - * fail-closed group: an over-temperature or over-current fault on any one - * forces the shared shutdown line low. - */ -&i2c1 { - status = "okay"; - - speaker_left_woof1: codec@38 { - compatible = "ti,sn012776", "ti,tas2764"; - reg = <0x38>; - SDZ-supply = <&speaker_sdz>; - apple,sdz-group-fail-closed; - #sound-dai-cells = <0>; - sound-name-prefix = "Left Woofer 1"; - interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; - ti,imon-slot-no = <0>; - ti,vmon-slot-no = <2>; - }; - - speaker_left_woof2: codec@39 { - compatible = "ti,sn012776", "ti,tas2764"; - reg = <0x39>; - SDZ-supply = <&speaker_sdz>; - apple,sdz-group-fail-closed; - #sound-dai-cells = <0>; - sound-name-prefix = "Left Woofer 2"; - interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; - ti,imon-slot-no = <8>; - ti,vmon-slot-no = <10>; - }; - - speaker_left_tweet: codec@3a { - compatible = "ti,sn012776", "ti,tas2764"; - reg = <0x3a>; - SDZ-supply = <&speaker_sdz>; - apple,sdz-group-fail-closed; - #sound-dai-cells = <0>; - sound-name-prefix = "Left Tweeter"; - interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; - ti,imon-slot-no = <16>; - ti,vmon-slot-no = <18>; - }; -}; - -&i2c3 { - status = "okay"; - - speaker_right_woof1: codec@3b { - compatible = "ti,sn012776", "ti,tas2764"; - reg = <0x3b>; - SDZ-supply = <&speaker_sdz>; - apple,sdz-group-fail-closed; - #sound-dai-cells = <0>; - sound-name-prefix = "Right Woofer 1"; - interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; - ti,imon-slot-no = <4>; - ti,vmon-slot-no = <6>; - }; - - speaker_right_woof2: codec@3c { - compatible = "ti,sn012776", "ti,tas2764"; - reg = <0x3c>; - SDZ-supply = <&speaker_sdz>; - apple,sdz-group-fail-closed; - #sound-dai-cells = <0>; - sound-name-prefix = "Right Woofer 2"; - interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; - ti,imon-slot-no = <12>; - ti,vmon-slot-no = <14>; - }; - - speaker_right_tweet: codec@3d { - compatible = "ti,sn012776", "ti,tas2764"; - reg = <0x3d>; - SDZ-supply = <&speaker_sdz>; - apple,sdz-group-fail-closed; - #sound-dai-cells = <0>; - sound-name-prefix = "Right Tweeter"; - interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; - ti,imon-slot-no = <20>; - ti,vmon-slot-no = <22>; - }; -}; - -/* - * The CS42L84 headphone codec (ADT /arm-io/i2c2/audio-codec-output@4B). Its - * reset line is nub GPIO pin 58, driven high to run the codec (the driver's - * "reset" is an enable, as on the J514/J516); its interrupt is AP pin 106, - * level low. The AOP firmware runs the jack's stream on MCA cluster 2. - */ -&i2c2 { - status = "okay"; - - jack_codec: codec@4b { - compatible = "cirrus,cs42l84"; - reg = <0x4b>; - reset-gpios = <&pinctrl_nub 58 GPIO_ACTIVE_HIGH>; - #sound-dai-cells = <0>; - interrupts-extended = <&pinctrl 106 IRQ_TYPE_LEVEL_LOW>; - sound-name-prefix = "Jack"; - }; -}; - -&isp { - memory-region = <&isp_fw_text>, <&isp_fw_data>, <&isp_heap>; - memory-region-names = "text", "data", "heap"; - apple,platform-id = <5>; - /* ADT isp0 cam-connections-scheme */ - apple,cam-connections-scheme = <0x10>; - status = "okay"; - - /* - * The front camera, an IMX958: firmware configuration 5 as the - * firmware reports it. - */ - sensor-presets { - /* 1920x1080 */ - preset0 { - apple,config-index = <5>; - apple,input-size = <1920 2160>; - apple,output-size = <1920 1080>; - apple,crop = <0 0 1920 1080>; - }; - /* 1280x720 */ - preset1 { - apple,config-index = <5>; - apple,input-size = <1920 2160>; - apple,output-size = <1280 720>; - apple,crop = <0 0 1280 720>; - }; - }; -}; - -&isp_dart0 { - status = "okay"; -}; - -&isp_dart1 { - status = "okay"; -}; - -&isp_dart2 { - status = "okay"; -}; - -/* - * The loaded GPU firmware keeps running across system sleep. Like the Neo's - * GFX domain, these domains must not be switched off while that image lives. - * The PMP report vote is still owned by the GPU consumer. - */ -&ps_afr { - apple,always-on; -}; - -&ps_gfx { - apple,always-on; -}; - -/* The loader fills the operating points, calibration and all six memory regions. */ -&gfx_mbox { - status = "okay"; -}; - -&gpu { - memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, - <&gpu_fw_data>, <&uat_l2_pagetables>, <&gpu_fw_text>; - memory-region-names = "ttbs", "pagetables", "handoff", "fw-data", - "l2-pagetables", "fw-text"; - operating-points-v2 = <&gpu_opp>; - apple,cs-opp = <&gpu_cs_opp>; - apple,afr-opp = <&gpu_afr_opp>; - /* Avoid a core power cycle between interactive submissions, then release. */ - apple,idle-off-delay-ms = <16>; - status = "okay"; -}; - -/* - * The Type-C ports (ADT nub-spmi-a0 hpm0/hpm1, nub-spmi-a1 hpm2) and the - * MagSafe controller (nub-spmi-a0 hpm5). The ADT lists the event, sleep and - * wake interrupts; select is event + 2. - */ -&nub_spmi_a0 { - status = "okay"; - - hpm0: usb-pd@c { - compatible = "apple,sn201202x"; - reg = <0xc SPMI_USID>; - interrupts = <11 IRQ_TYPE_EDGE_RISING>, - <13 IRQ_TYPE_EDGE_RISING>, - <17 IRQ_TYPE_EDGE_RISING>, - <19 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "irq", "select", "sleep", "wake"; - - typec0: connector { - compatible = "usb-c-connector"; - label = "USB-C Left Back"; - power-role = "dual"; - data-role = "dual"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - typec0_con_hs: endpoint { - remote-endpoint = <&usb_drd0_hs>; - }; - }; - - port@1 { - reg = <1>; - typec0_con_ss: endpoint { - remote-endpoint = <&atcphy0_ss>; - }; - }; - }; - }; - }; - - hpm1: usb-pd@a { - compatible = "apple,sn201202x"; - reg = <0xa SPMI_USID>; - interrupts = <37 IRQ_TYPE_EDGE_RISING>, - <39 IRQ_TYPE_EDGE_RISING>, - <43 IRQ_TYPE_EDGE_RISING>, - <45 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "irq", "select", "sleep", "wake"; - - typec1: connector { - compatible = "usb-c-connector"; - label = "USB-C Left Front"; - power-role = "dual"; - data-role = "dual"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - typec1_con_hs: endpoint { - remote-endpoint = <&usb_drd1_hs>; - }; - }; - - port@1 { - reg = <1>; - typec1_con_ss: endpoint { - remote-endpoint = <&atcphy1_ss>; - }; - }; - }; - }; - }; - - /* MagSafe port */ - hpm5: usb-pd@8 { - compatible = "apple,sn201202x"; - reg = <0x8 SPMI_USID>; - interrupts = <63 IRQ_TYPE_EDGE_RISING>, - <65 IRQ_TYPE_EDGE_RISING>, - <69 IRQ_TYPE_EDGE_RISING>, - <71 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "irq", "select", "sleep", "wake"; - }; -}; - -&nub_spmi_a1 { - status = "okay"; - - hpm2: usb-pd@c { - compatible = "apple,sn201202x"; - reg = <0xc SPMI_USID>; - interrupts = <11 IRQ_TYPE_EDGE_RISING>, - <13 IRQ_TYPE_EDGE_RISING>, - <17 IRQ_TYPE_EDGE_RISING>, - <19 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "irq", "select", "sleep", "wake"; - - typec2: connector { - compatible = "usb-c-connector"; - label = "USB-C Right"; - power-role = "dual"; - data-role = "dual"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - typec2_con_hs: endpoint { - remote-endpoint = <&usb_drd2_hs>; - }; - }; - - port@1 { - reg = <1>; - typec2_con_ss: endpoint { - remote-endpoint = <&atcphy2_ss>; - }; - }; - }; - }; - }; -}; - -&atcphy0 { - status = "okay"; - - port { - atcphy0_ss: endpoint { - remote-endpoint = <&typec0_con_ss>; - }; - }; -}; - -&atcphy0_mbox { - status = "okay"; -}; - -&usb_drd0_dart_0 { - status = "okay"; -}; - -&usb_drd0_dart_1 { - status = "okay"; -}; - -&usb_drd0 { - status = "okay"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - usb_drd0_hs: endpoint { - remote-endpoint = <&typec0_con_hs>; - }; - }; - }; -}; - -&atcphy1 { - status = "okay"; - - port { - atcphy1_ss: endpoint { - remote-endpoint = <&typec1_con_ss>; - }; - }; -}; - -&atcphy1_mbox { - status = "okay"; -}; - -&usb_drd1_dart_0 { - status = "okay"; -}; - -&usb_drd1_dart_1 { - status = "okay"; -}; - -&usb_drd1 { - status = "okay"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - usb_drd1_hs: endpoint { - remote-endpoint = <&typec1_con_hs>; - }; - }; - }; -}; - -&atcphy2 { - status = "okay"; - - port { - atcphy2_ss: endpoint { - remote-endpoint = <&typec2_con_ss>; - }; - }; -}; - -&atcphy2_mbox { - status = "okay"; -}; - -&usb_drd2_dart_0 { - status = "okay"; -}; - -&usb_drd2_dart_1 { - status = "okay"; -}; - -&usb_drd2 { - status = "okay"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - usb_drd2_hs: endpoint { - remote-endpoint = <&typec2_con_hs>; - }; - }; - }; -}; - -/* The video encoder, its DARTs and its power domains. */ -&ps_venc0_sys { - status = "okay"; -}; - -&ps_venc0_dma { - status = "okay"; -}; - -&ps_venc0_pipe4 { - status = "okay"; -}; - -&ps_venc0_pipe5 { - status = "okay"; -}; - -&ps_venc0_me0 { - status = "okay"; -}; - -&ps_venc0_me1 { - status = "okay"; -}; - -&ave0_dart0 { - status = "okay"; -}; - -&ave0_dart1 { - status = "okay"; -}; - -&ave { - status = "okay"; -}; - -#include "t6040-j616s-usb3.dtsi" - -/* External display chain, all disabled: the gate in drivers/soc/apple/display-gate.c enables it. */ -#include "t6040-j616s-extdisp.dtsi" -#include "t6040-j616s-extdisp-mem-loader.dtsi" -#include "t6040-j616s-extdisp-hdmi.dtsi" -#include "t6040-j616s-extdisp-usbc.dtsi" -#include "t6040-j616s-dpaudio.dtsi" -/* Additional Abbey PMICs on the two independent NUB SPMI buses. */ -&nub_spmi1 { - status = "okay"; - - pmic@e { - compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; - reg = <0xe SPMI_USID>; - }; -}; - -&nub_spmi2 { - status = "okay"; - - pmic@e { - compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; - reg = <0xe SPMI_USID>; - }; -}; - -&ps_nub_spmi_ahb { - status = "okay"; -}; - -&ps_nub_spmi1 { - status = "okay"; -}; - -&ps_nub_spmi2 { - status = "okay"; }; -&pinctrl { - sd_clkreq_pins: sd-clkreq-pins { - pinmux = ; - }; +&sound { + compatible = "apple,j616-macaudio", "apple,macaudio"; + model = "MacBook Pro J616"; }; -&pcie0 { - interrupts = , - ; - /* Widen bus-range only with the SD power/port ownership handoff. */ - iommu-map = <0x100 &pcie_dart 1 2>, <0x200 &apcie1_dart 1 1>; - - port01: pci@1,0 { - device_type = "pci"; - reg = <0x800 0 0 0 0>; - reset-gpios = <&pinctrl 5 GPIO_ACTIVE_LOW>; - pinctrl-names = "default"; - pinctrl-0 = <&sd_clkreq_pins>; - #address-cells = <3>; - #size-cells = <2>; - ranges; - interrupt-controller; - #interrupt-cells = <1>; - interrupt-map-mask = <0 0 0 7>; - interrupt-map = <0 0 0 1 &port01 0 0 0 0>, - <0 0 0 2 &port01 0 0 0 1>, - <0 0 0 3 &port01 0 0 0 2>, - <0 0 0 4 &port01 0 0 0 3>; - /* The SD power switch needs an SMC service binding. */ - status = "disabled"; - }; +&sound_jack { + compatible = "apple,j616-macaudio-jack", "apple,macaudio"; + model = "MacBook Pro J616 Jack"; }; -/* - * The SMC owns automatic fan policy. Drop the host fan trip/map instances - * after the board's thermal descriptions, retaining all passive and critical - * protection. Keep this final policy separate from ladder descriptions so - * independent ladder updates cannot restore autonomous SMC writes. - */ - -#define J616S_DELETE_FAN_TRIPS \ - /delete-node/ trip-fan1; \ - /delete-node/ trip-fan2; \ - /delete-node/ trip-fan3; \ - /delete-node/ trip-fan4; \ - /delete-node/ trip-fan5; - -/* Some CPU ladders also map full fan speed to the passive trip. */ -#define J616S_DELETE_FAN_MAPS \ - /delete-node/ map-fan1; \ - /delete-node/ map-fan2; \ - /delete-node/ map-fan3; \ - /delete-node/ map-fan4; \ - /delete-node/ map-fan5; \ - /delete-node/ map-fan6; - -/ { - thermal-zones { - cpu-die-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - - gpu0-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - - gpu1-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - - gpu2-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - - gpu3-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - - gpu4-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - - gpu5-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - - gpu6-thermal { - trips { - J616S_DELETE_FAN_TRIPS - }; - - cooling-maps { - J616S_DELETE_FAN_MAPS - }; - }; - }; +&mtp_mt { + firmware-name = "apple/tpmtfw-j616s.bin"; }; -#undef J616S_DELETE_FAN_TRIPS -#undef J616S_DELETE_FAN_MAPS - /* - * Proven on the J616s (MobileNetV1 top-1 16/16). The ANE stays idle until - * the loader fills and enables the two preload reservations above. + * Per-device factory data from this Mac's own iBoot system container: the + * sealed calibration and the sealed root key. */ -&ane0_dart0 { - status = "okay"; -}; - -&ane0_dart1 { - status = "okay"; -}; - -&ane0_dart2 { - status = "okay"; -}; - -&ane { - memory-region = <&ane_fw_text>, <&ane_fw_data>; - memory-region-names = "firmware-text", "firmware-data"; - status = "okay"; +&mesa { + firmware-name = "apple/mesacal-j616s.bin", "apple/msrk-j616s.bin"; }; diff --git a/arch/arm64/boot/dts/apple/t604x-j614-j616.dtsi b/arch/arm64/boot/dts/apple/t604x-j614-j616.dtsi new file mode 100644 index 0000000000000..63fcaed9f6c50 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t604x-j614-j616.dtsi @@ -0,0 +1,4765 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * MacBook Pro (14/16-inch, M4 Pro) + * + * This file contains the parts common to the J614s and J616s. Apple's + * device trees for the two boards differ only in the panel, the speaker + * tuning, the Wi-Fi module, the trackpad and the power limits; the board + * files set those. + * + * target-type: J614s / J616s + * + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include + +/ { + chassis-type = "laptop"; + + aliases { + keyboard = &keyboard; + nvram = &nvram; + spi0 = &qspi; + wifi0 = "/pcie@1cb0000000/pci@0,0/wifi@0,0"; + bluetooth0 = "/pcie@1cb0000000/pci@0,0/bluetooth@0,1"; + /* used by the loader to hand over the display */ + dcp = &dcp; + disp0 = &display; + disp0-piodma = &disp0_piodma; + /* the boot loader finds the GPU node through this alias */ + gpu = &gpu; + pmp = &pmp; + /* the boot loader reserves the SEP firmware through this alias */ + sep = &sep; + }; + + chosen { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + framebuffer0: framebuffer@0 { + compatible = "apple,simple-framebuffer", "simple-framebuffer"; + reg = <0 0 0 0>; /* To be filled by loader */ + /* Format properties will be added by loader */ + status = "disabled"; + }; + }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + /* The loader adds its own reservations */ + + /* + * The loader must fill reg from ane0 segment-ranges, rename each + * node to match its physical address and enable it. Leave these + * disabled until then; allocating replacement memory would lose + * the personalized preload. TEXT is 0xc0000 bytes and DATA is + * 0x2ac000 bytes. Neither segment may be reclaimed before reboot. + */ + ane_fw_text: ane-firmware-text@0 { + compatible = "apple,asc-mem"; + reg = <0 0 0 0>; + no-map; + status = "disabled"; + }; + + ane_fw_data: ane-firmware-data@0 { + compatible = "apple,asc-mem"; + reg = <0 0 0 0>; + no-map; + status = "disabled"; + }; + + /* + * iBoot loads the ISP firmware below the kernel's memory. Its + * segments are the ADT's isp0 segment-ranges (macOS 26.6.2, + * iBoot mBoot-18000.161.10), mapped where the firmware runs them: + * 0x100_0000_0000 onwards, its 32 MiB main memory. The driver + * checks that the coprocessor's reset vector is this text before + * it starts the firmware. + */ + isp_fw_text: isp-firmware-text@10000c44000 { + reg = <0x100 0x00c44000 0x0 0xca8000>; + iommu-addresses = <&isp 0x100 0x0 0x0 0xca8000>; + no-map; + }; + + isp_fw_data: isp-firmware-data@10001e7c000 { + reg = <0x100 0x01e7c000 0x0 0x1344000>; + iommu-addresses = <&isp 0x100 0xca8000 0x0 0x1344000>; + no-map; + }; + + /* the firmware's main memory, the driver allocates above it */ + isp_heap: isp-heap { + iommu-addresses = <&isp 0x100 0x0 0x0 0x2000000>; + }; + + /* + * Text segment of the loaded GPU firmware (ADT gfx-asc + * segment-ranges[0]); the driver identifies the build from it. + */ + gpu_fw_text: gpu-fw-text { + reg = <0 0 0 0>; + status = "disabled"; + no-map; + }; + + /* + * Data segment of the loaded GPU firmware (ADT sgx gfx-data-*); it + * places its crash log here. + */ + gpu_fw_data: gpu-fw-data { + reg = <0 0 0 0>; + status = "disabled"; + no-map; + }; + + /* + * The UAT ranges (ADT sgx gpu-region-*, gfx-shared-region-*, + * gfx-shared-l2-region-*, gfx-handoff-*). They stay no-map until + * the boot loader adds them to /memory. + */ + uat_ttbs: uat-ttbs { + reg = <0 0 0 0>; + status = "disabled"; + no-map; + }; + + uat_pagetables: uat-pagetables { + reg = <0 0 0 0>; + status = "disabled"; + no-map; + }; + + uat_l2_pagetables: uat-l2-pagetables { + reg = <0 0 0 0>; + status = "disabled"; + no-map; + }; + + uat_handoff: uat-handoff { + reg = <0 0 0 0>; + status = "disabled"; + no-map; + }; + }; + + memory@0 { + device_type = "memory"; + reg = <0 0 0 0>; /* To be filled by loader */ + }; + + led-controller { + compatible = "pwm-leds"; + + led-0 { + pwms = <&fpwm0 0 40000>; + label = "apple::kbd_backlight"; + function = LED_FUNCTION_KBD_BACKLIGHT; + color = ; + max-brightness = <255>; + }; + }; + + /* + * GPU performance states, one opp-microvolt cell per cluster (ADT + * /arm-io/sgx perf-states and perf-states1). The operating points are + * binned per device: the loader fills these placeholders. + * The driver requires a completed calibration handoff. Entries keep the + * order of the GPU performance states, which is not sorted by frequency. + */ + gpu_opp: opp-table-gpu { + compatible = "operating-points-v2"; + + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp01 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp02 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp03 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp04 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp05 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp06 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp07 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp08 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp09 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp10 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp11 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp12 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp13 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp14 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + opp15 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; + }; + }; + + /* The GPU's CS clock domain (ADT cs-perf-states) */ + gpu_cs_opp: opp-table-gpucs { + compatible = "operating-points-v2"; + + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp01 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp02 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp03 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp04 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp05 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp06 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp07 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + }; + + /* The GPU's AFR clock domain (ADT afr-perf-states) */ + gpu_afr_opp: opp-table-gpuafr { + compatible = "operating-points-v2"; + + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp01 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp02 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp03 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp04 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp05 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp06 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + opp07 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; + }; + }; + + /* + * CPU die temperature: SMC key TCMz (CPU die max), sensor 0 of + * smc_hwmon. The PMP's fast die-temperature limit regulates: its loop + * holds its own hot-spot sensors at 110 C (ADT fast-die-ctrl-loop), + * and under a sustained all-core load TCMz then settles at 113.4 to + * 114.7 C with the fans running. This zone is the backstop above that: + * passive at 116 C, critical (orderly shutdown) at 118 C, polled once a + * second as the SMC refreshes TCMz. The cpufreq cooling device sits on a + * cluster's policy CPU, so each map lists every CPU of its cluster. + * + * Per-cluster and GPU zones follow, one per SMC die-maximum sensor + * (smc_hwmon channels 1-23, in the order of the temperature nodes + * below): Tex0-3 throttle the E cluster, Tpx0-5 the first P cluster + * and Tpx8-D the second, with the same 116/118 C backstop as TCMz. + * Unlike the fanless J700, this machine has two SMC-driven fans and + * the PMP regulates the dies itself, so these zones only catch a + * failure of that loop; they never act in normal use. + * + * The GPU has no cooling device of its own: its firmware sets the GPU + * performance state and starts limiting at 101 C. So when a GPU die + * sensor reaches the GPU firmware's limit, the seven + * hottest ones (Tg05, Tg0S, Tg0Y, Tg0k, Tg0z, Tg1V, Tg1l) also shed + * CPU power through the CPU clusters, from the same 101 C, leaving the + * shared heat budget to the GPU. They shut down at 118 C. While the + * GPU is powered off its sensors report placeholder values from -4 to + * 4 C. + * + * The fans: left to themselves (SMC mode 3, with no OS asking for + * cooling) they stay off, or switch on late and ramp slowly, while the + * dies heat up under load. So the CPU die zone and the GPU zones also + * run both fans through a ladder of active trips; a fan takes the + * highest state any zone asks for and goes back to the SMC once no + * zone asks for anything. States 1-8 of a fan run it from its minimum + * (1350 rpm) to its maximum (5777 rpm) in even steps. + * + * sustainable-power is what the CPUs dissipate with the die held at + * its operating point: 720 s of all-core load (10 P, 4 E busy loops), + * headless, SMC fans in their own mode. Over the last 600 s TCMz held + * 112.0 to 113.8 C with the PMP limiting the P clusters to a mean of + * about 3.7 GHz and the fans at 3450/3730 rpm (where macOS settles + * too). The SMC core rails then read P0 25.3 W, P1 23.9 W and E 2.1 W + * on average, the units of the energy model. Each zone gets the sum + * over the clusters it throttles. Without it the power allocator + * estimates a budget from the cooling devices' lowest states, which + * throttles far below what this machine sustains. + * + * All of these values were measured on the J616s. The J614s inherits + * them until it is measured: Apple's package power limits for it are + * about 78% (CPU) and 90% (GPU) of the J616s ones, so its budgets are + * generous here, but these zones are a backstop behind the PMP. + */ +#define J616S_FAN_TRIP(label, node, temp, hyst) \ + label: node { \ + temperature = ; \ + hysteresis = ; \ + type = "active"; \ + }; + +#define J616S_FAN_TRIPS(zone) \ + J616S_FAN_TRIP(zone##_fan1, trip-fan1, 75000, 10000) \ + J616S_FAN_TRIP(zone##_fan2, trip-fan2, 80000, 5000) \ + J616S_FAN_TRIP(zone##_fan3, trip-fan3, 85000, 5000) \ + J616S_FAN_TRIP(zone##_fan4, trip-fan4, 90000, 5000) \ + J616S_FAN_TRIP(zone##_fan5, trip-fan5, 95000, 5000) + +#define J616S_FAN_MAP(trip_label, node, state) \ + node { \ + trip = <&trip_label>; \ + cooling-device = <&smc_hwmon 0 state state>, \ + <&smc_hwmon 1 state state>; \ + }; + +#define J616S_FAN_MAPS(zone) \ + J616S_FAN_MAP(zone##_fan1, map-fan1, 1) \ + J616S_FAN_MAP(zone##_fan2, map-fan2, 2) \ + J616S_FAN_MAP(zone##_fan3, map-fan3, 3) \ + J616S_FAN_MAP(zone##_fan4, map-fan4, 5) \ + J616S_FAN_MAP(zone##_fan5, map-fan5, 8) + + thermal-zones { + cpu-die-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <51300>; + thermal-sensors = <&smc_hwmon 0>; /* TCMz */ + + trips { + J616S_FAN_TRIPS(cpu_die) + + cpu_die_passive: cpu-die-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + cpu-die-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(cpu_die) + + map-e { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e0-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <2100>; + thermal-sensors = <&smc_hwmon 1>; /* Tex0 */ + + trips { + cpu_e0_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e0_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e1-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <2100>; + thermal-sensors = <&smc_hwmon 2>; /* Tex1 */ + + trips { + cpu_e1_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e1_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e2-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <2100>; + thermal-sensors = <&smc_hwmon 3>; /* Tex2 */ + + trips { + cpu_e2_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e2_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-e3-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <2100>; + thermal-sensors = <&smc_hwmon 4>; /* Tex3 */ + + trips { + cpu_e3_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_e3_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p00-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <25300>; + thermal-sensors = <&smc_hwmon 5>; /* Tpx0 */ + + trips { + cpu_p00_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p00_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p01-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <25300>; + thermal-sensors = <&smc_hwmon 6>; /* Tpx1 */ + + trips { + cpu_p01_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p01_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p02-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <25300>; + thermal-sensors = <&smc_hwmon 7>; /* Tpx2 */ + + trips { + cpu_p02_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p02_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p03-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <25300>; + thermal-sensors = <&smc_hwmon 8>; /* Tpx3 */ + + trips { + cpu_p03_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p03_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p04-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <25300>; + thermal-sensors = <&smc_hwmon 9>; /* Tpx4 */ + + trips { + cpu_p04_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p04_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p05-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <25300>; + thermal-sensors = <&smc_hwmon 10>; /* Tpx5 */ + + trips { + cpu_p05_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p0 { + trip = <&cpu_p05_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p10-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <23900>; + thermal-sensors = <&smc_hwmon 11>; /* Tpx8 */ + + trips { + cpu_p10_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p10_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p11-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <23900>; + thermal-sensors = <&smc_hwmon 12>; /* Tpx9 */ + + trips { + cpu_p11_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p11_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p12-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <23900>; + thermal-sensors = <&smc_hwmon 13>; /* TpxA */ + + trips { + cpu_p12_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p12_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p13-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <23900>; + thermal-sensors = <&smc_hwmon 14>; /* TpxB */ + + trips { + cpu_p13_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p13_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p14-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <23900>; + thermal-sensors = <&smc_hwmon 15>; /* TpxC */ + + trips { + cpu_p14_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p14_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + cpu-p15-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + sustainable-power = <23900>; + thermal-sensors = <&smc_hwmon 16>; /* TpxD */ + + trips { + cpu_p15_passive: trip-passive { + temperature = <116000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-p1 { + trip = <&cpu_p15_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu0-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 17>; /* Tg05 */ + + trips { + J616S_FAN_TRIPS(gpu0) + + gpu0_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(gpu0) + + map-e { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu0_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu1-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 18>; /* Tg0S */ + + trips { + J616S_FAN_TRIPS(gpu1) + + gpu1_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(gpu1) + + map-e { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu1_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu2-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 19>; /* Tg0Y */ + + trips { + J616S_FAN_TRIPS(gpu2) + + gpu2_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(gpu2) + + map-e { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu2_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu3-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 20>; /* Tg0k */ + + trips { + J616S_FAN_TRIPS(gpu3) + + gpu3_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(gpu3) + + map-e { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu3_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu4-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 21>; /* Tg0z */ + + trips { + J616S_FAN_TRIPS(gpu4) + + gpu4_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(gpu4) + + map-e { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu4_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu5-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 22>; /* Tg1V */ + + trips { + J616S_FAN_TRIPS(gpu5) + + gpu5_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(gpu5) + + map-e { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu5_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu6-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 23>; /* Tg1l */ + + trips { + J616S_FAN_TRIPS(gpu6) + + gpu6_passive: trip-passive { + temperature = <101000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + J616S_FAN_MAPS(gpu6) + + map-e { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&gpu6_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + }; + + /* + * The speaker amps' shared shutdown line (SDZ, AP pin 54, low = + * shutdown). One fixed regulator, the SDZ supply of all six, not + * per-amp shutdown-gpios: with GPIO_SHARED six references become a + * vote, and one amp driving the line low would not shut the other + * five down. + */ + speaker_sdz: regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + gpios = <&pinctrl 54 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + /* + * The AOP firmware runs the stream (MCA cluster 0, one serializer for + * all six amps); the board's variant of macaudio links the AOP audio + * component's speaker back-end (DAI 3) to the amps. The codecs are + * listed in the ADT provider order, so each gets its native playback + * slot (LW1 0, RW1 1, LW2 2, RW2 3, LT 4, RT 5). The idle masks are the + * ADT amp-tx-zd-config rows 0 and 1: LW1 drives zeros in the right + * amps' sense slots, RW1 in the left amps'. + */ + sound: sound { + speaker-id-gpios = <&pinctrl 52 GPIO_ACTIVE_HIGH>, + <&pinctrl 53 GPIO_ACTIVE_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Speakers"; + dai-tdm-idle-mode-0 = "zero"; + dai-tdm-idle-mode-1 = "zero"; + dai-tdm-slot-tx-idle-mask-0 = <0xf0f0f0>; + dai-tdm-slot-tx-idle-mask-1 = <0x0f0f0f>; + + cpu { + sound-dai = <&aop_audio 3>; + }; + + codec { + sound-dai = <&speaker_left_woof1>, <&speaker_right_woof1>, + <&speaker_left_woof2>, <&speaker_right_woof2>, + <&speaker_left_tweet>, <&speaker_right_tweet>; + }; + }; + }; + + /* + * The headphone jack: the AOP audio component's jack back-end (DAI 4) + * to the CS42L84. A card of its own, so a codec that does not answer + * costs the jack only, never the speakers. + */ + sound_jack: sound-jack { + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&aop_audio 4>; + }; + + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + +&cpuidle { + status = "okay"; +}; + +&smc { + status = "okay"; +}; + +&smc_mbox { + status = "okay"; +}; + +&nub_spmi0 { + status = "okay"; +}; + +&pinctrl { + status = "okay"; + + wifi_clkreq_pins: wifi-clkreq-pins { + pinmux = ; + }; +}; + +/* + * The AP takes none of the always-on bank's interrupts: the AOP firmware + * owns some of its pins and interrupt registers, and a chained handler here + * would see their pending bits without a handler of ours. + */ +&pinctrl_nub { + status = "okay"; + /delete-property/ interrupt-controller; + /delete-property/ #interrupt-cells; + /delete-property/ interrupts; +}; + +&fpwm0 { + status = "okay"; +}; + +/* apple_sep enables the SEP and its DART when it loads. */ +&sep_mbox { + status = "okay"; +}; + +/* + * Boot NOR. The partition is the J616s ADT /arm-io/qspi/spinor/anvram, + * flash-relative; asahi-bless and asahi-nvram find it by its label. + */ +&qspi { + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <10000000>; + spi-rx-bus-width = <1>; + spi-tx-bus-width = <1>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + nvram: partition@700000 { + label = "nvram"; + reg = <0x700000 0x100000>; + }; + }; + }; +}; + +/* The QSPI controller cannot probe while its power state is disabled. */ +&ps_qspi { + status = "okay"; +}; + +/* + * Touch ID sensor (Mesa 0x3356). Its power is NUB GPIO 53 and its + * data-ready line is AP GPIO 116, as the J616s ADT describes them. + */ +&spi2 { + status = "okay"; +}; + +&mesa { + status = "okay"; + enable-gpios = <&pinctrl_nub 53 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl>; + interrupts = <116 IRQ_TYPE_EDGE_RISING>; + spi-cs-setup-delay-ns = <20>; + spi-cs-hold-delay-ns = <20>; + /* The board file names the per-device factory data. */ +}; + +/* The Type-C retimers (ADT atcrt at 0x18/0x19/0x1a) sit on this bus; no driver yet. */ +&i2c6 { + status = "okay"; +}; + +&mtp { + status = "okay"; +}; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + /* SMC key gp1c powers the trackpad analog front end. */ + apple,afe-smc = <&smc>; + + mtp_mt: multi-touch { + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <2>; + }; + + stm { + }; + + actuator { + }; + + tp-accel { + }; + + mtp { + }; +}; + +&smc_hwmon { + /* + * TCMz and the 23 zone sensors after it must stay the first + * temperature children: the thermal zones name a sensor by its index, + * so the shared hwmon includes below only append after them. + * + * Every key below is in this machine's SMC key table as a readable + * float and was read on the J616s (SMC firmware of macOS 26.6.2). + * The J614s key table has not been read yet. + * Keys that only ever read a fixed placeholder (Tz1*, Ta0*) or a + * fixed limit (TVMx, TVmS, TVms, PHPB, PHPM) are left out; rails that + * read 0 W while idle are kept. GPU (Tg*), P-cluster (Tp*) and + * E-cluster (Te*) labels follow load tests: Tg* only read live while + * the GPU is powered and rise with GPU load, Tp* and Te* rise with CPU + * load. Other names follow the SMC key groups and are not otherwise + * verified. SBA1-3 are the three cells of the 3S battery. + */ + temperature-TCMz { + apple,key-id = "TCMz"; + label = "CPU Die Max"; + }; + + temperature-Tex0 { + apple,key-id = "Tex0"; + label = "E-cluster Tex0"; + }; + + temperature-Tex1 { + apple,key-id = "Tex1"; + label = "E-cluster Tex1"; + }; + + temperature-Tex2 { + apple,key-id = "Tex2"; + label = "E-cluster Tex2"; + }; + + temperature-Tex3 { + apple,key-id = "Tex3"; + label = "E-cluster Tex3"; + }; + + temperature-Tpx0 { + apple,key-id = "Tpx0"; + label = "P-cluster Tpx0"; + }; + + temperature-Tpx1 { + apple,key-id = "Tpx1"; + label = "P-cluster Tpx1"; + }; + + temperature-Tpx2 { + apple,key-id = "Tpx2"; + label = "P-cluster Tpx2"; + }; + + temperature-Tpx3 { + apple,key-id = "Tpx3"; + label = "P-cluster Tpx3"; + }; + + temperature-Tpx4 { + apple,key-id = "Tpx4"; + label = "P-cluster Tpx4"; + }; + + temperature-Tpx5 { + apple,key-id = "Tpx5"; + label = "P-cluster Tpx5"; + }; + + temperature-Tpx8 { + apple,key-id = "Tpx8"; + label = "P-cluster Tpx8"; + }; + + temperature-Tpx9 { + apple,key-id = "Tpx9"; + label = "P-cluster Tpx9"; + }; + + temperature-TpxA { + apple,key-id = "TpxA"; + label = "P-cluster TpxA"; + }; + + temperature-TpxB { + apple,key-id = "TpxB"; + label = "P-cluster TpxB"; + }; + + temperature-TpxC { + apple,key-id = "TpxC"; + label = "P-cluster TpxC"; + }; + + temperature-TpxD { + apple,key-id = "TpxD"; + label = "P-cluster TpxD"; + }; + + temperature-Tg05 { + apple,key-id = "Tg05"; + label = "GPU Tg05"; + }; + + temperature-Tg0S { + apple,key-id = "Tg0S"; + label = "GPU Tg0S"; + }; + + temperature-Tg0Y { + apple,key-id = "Tg0Y"; + label = "GPU Tg0Y"; + }; + + temperature-Tg0k { + apple,key-id = "Tg0k"; + label = "GPU Tg0k"; + }; + + temperature-Tg0z { + apple,key-id = "Tg0z"; + label = "GPU Tg0z"; + }; + + temperature-Tg1V { + apple,key-id = "Tg1V"; + label = "GPU Tg1V"; + }; + + temperature-Tg1l { + apple,key-id = "Tg1l"; + label = "GPU Tg1l"; + }; + + temperature-TAOL { + apple,key-id = "TAOL"; + label = "SMC TAOL"; + }; + + temperature-TB1T { + apple,key-id = "TB1T"; + label = "Battery 1"; + }; + + temperature-TB2T { + apple,key-id = "TB2T"; + label = "Battery 2"; + }; + + temperature-TCMb { + apple,key-id = "TCMb"; + label = "SMC TCMb"; + }; + + temperature-TD00 { + apple,key-id = "TD00"; + label = "SMC TD00"; + }; + + temperature-TD01 { + apple,key-id = "TD01"; + label = "SMC TD01"; + }; + + temperature-TD02 { + apple,key-id = "TD02"; + label = "SMC TD02"; + }; + + temperature-TD03 { + apple,key-id = "TD03"; + label = "SMC TD03"; + }; + + temperature-TD04 { + apple,key-id = "TD04"; + label = "SMC TD04"; + }; + + temperature-TD10 { + apple,key-id = "TD10"; + label = "SMC TD10"; + }; + + temperature-TD11 { + apple,key-id = "TD11"; + label = "SMC TD11"; + }; + + temperature-TD12 { + apple,key-id = "TD12"; + label = "SMC TD12"; + }; + + temperature-TD13 { + apple,key-id = "TD13"; + label = "SMC TD13"; + }; + + temperature-TD14 { + apple,key-id = "TD14"; + label = "SMC TD14"; + }; + + temperature-TD20 { + apple,key-id = "TD20"; + label = "SMC TD20"; + }; + + temperature-TD21 { + apple,key-id = "TD21"; + label = "SMC TD21"; + }; + + temperature-TD22 { + apple,key-id = "TD22"; + label = "SMC TD22"; + }; + + temperature-TD23 { + apple,key-id = "TD23"; + label = "SMC TD23"; + }; + + temperature-TD24 { + apple,key-id = "TD24"; + label = "SMC TD24"; + }; + + temperature-TDBP { + apple,key-id = "TDBP"; + label = "SMC TDBP"; + }; + + temperature-TDCR { + apple,key-id = "TDCR"; + label = "SMC TDCR"; + }; + + temperature-TDEC { + apple,key-id = "TDEC"; + label = "SMC TDEC"; + }; + + temperature-TDEL { + apple,key-id = "TDEL"; + label = "SMC TDEL"; + }; + + temperature-TDER { + apple,key-id = "TDER"; + label = "SMC TDER"; + }; + + temperature-TDTP { + apple,key-id = "TDTP"; + label = "SMC TDTP"; + }; + + temperature-TDVx { + apple,key-id = "TDVx"; + label = "SMC TDVx"; + }; + + temperature-TDeL { + apple,key-id = "TDeL"; + label = "SMC TDeL"; + }; + + temperature-TDeR { + apple,key-id = "TDeR"; + label = "SMC TDeR"; + }; + + temperature-TF2S { + apple,key-id = "TF2S"; + label = "SMC TF2S"; + }; + + temperature-TH0a { + apple,key-id = "TH0a"; + label = "SMC TH0a"; + }; + + temperature-TH0b { + apple,key-id = "TH0b"; + label = "SMC TH0b"; + }; + + temperature-TMVR { + apple,key-id = "TMVR"; + label = "SMC TMVR"; + }; + + temperature-TPD0 { + apple,key-id = "TPD0"; + label = "SMC TPD0"; + }; + + temperature-TPD1 { + apple,key-id = "TPD1"; + label = "SMC TPD1"; + }; + + temperature-TPD2 { + apple,key-id = "TPD2"; + label = "SMC TPD2"; + }; + + temperature-TPD3 { + apple,key-id = "TPD3"; + label = "SMC TPD3"; + }; + + temperature-TPD4 { + apple,key-id = "TPD4"; + label = "SMC TPD4"; + }; + + temperature-TPD5 { + apple,key-id = "TPD5"; + label = "SMC TPD5"; + }; + + temperature-TPD6 { + apple,key-id = "TPD6"; + label = "SMC TPD6"; + }; + + temperature-TPD7 { + apple,key-id = "TPD7"; + label = "SMC TPD7"; + }; + + temperature-TPD8 { + apple,key-id = "TPD8"; + label = "SMC TPD8"; + }; + + temperature-TPD9 { + apple,key-id = "TPD9"; + label = "SMC TPD9"; + }; + + temperature-TPDX { + apple,key-id = "TPDX"; + label = "SMC TPDX"; + }; + + temperature-TPDa { + apple,key-id = "TPDa"; + label = "SMC TPDa"; + }; + + temperature-TPDb { + apple,key-id = "TPDb"; + label = "SMC TPDb"; + }; + + temperature-TPDc { + apple,key-id = "TPDc"; + label = "SMC TPDc"; + }; + + temperature-TPDd { + apple,key-id = "TPDd"; + label = "SMC TPDd"; + }; + + temperature-TPDe { + apple,key-id = "TPDe"; + label = "SMC TPDe"; + }; + + temperature-TPDf { + apple,key-id = "TPDf"; + label = "SMC TPDf"; + }; + + temperature-TPH3 { + apple,key-id = "TPH3"; + label = "SMC TPH3"; + }; + + temperature-TPMP { + apple,key-id = "TPMP"; + label = "SMC TPMP"; + }; + + temperature-TPSP { + apple,key-id = "TPSP"; + label = "SMC TPSP"; + }; + + temperature-TRD0 { + apple,key-id = "TRD0"; + label = "SMC TRD0"; + }; + + temperature-TRD1 { + apple,key-id = "TRD1"; + label = "SMC TRD1"; + }; + + temperature-TRD2 { + apple,key-id = "TRD2"; + label = "SMC TRD2"; + }; + + temperature-TRD3 { + apple,key-id = "TRD3"; + label = "SMC TRD3"; + }; + + temperature-TRD4 { + apple,key-id = "TRD4"; + label = "SMC TRD4"; + }; + + temperature-TRD5 { + apple,key-id = "TRD5"; + label = "SMC TRD5"; + }; + + temperature-TRD6 { + apple,key-id = "TRD6"; + label = "SMC TRD6"; + }; + + temperature-TRD7 { + apple,key-id = "TRD7"; + label = "SMC TRD7"; + }; + + temperature-TRD8 { + apple,key-id = "TRD8"; + label = "SMC TRD8"; + }; + + temperature-TRD9 { + apple,key-id = "TRD9"; + label = "SMC TRD9"; + }; + + temperature-TRDX { + apple,key-id = "TRDX"; + label = "SMC TRDX"; + }; + + temperature-TRDa { + apple,key-id = "TRDa"; + label = "SMC TRDa"; + }; + + temperature-TRDb { + apple,key-id = "TRDb"; + label = "SMC TRDb"; + }; + + temperature-TRDc { + apple,key-id = "TRDc"; + label = "SMC TRDc"; + }; + + temperature-TRDd { + apple,key-id = "TRDd"; + label = "SMC TRDd"; + }; + + temperature-TRDe { + apple,key-id = "TRDe"; + label = "SMC TRDe"; + }; + + temperature-TRDf { + apple,key-id = "TRDf"; + label = "SMC TRDf"; + }; + + temperature-TS0P { + apple,key-id = "TS0P"; + label = "SMC TS0P"; + }; + + temperature-TSG1 { + apple,key-id = "TSG1"; + label = "SMC TSG1"; + }; + + temperature-TUD0 { + apple,key-id = "TUD0"; + label = "SMC TUD0"; + }; + + temperature-TUD1 { + apple,key-id = "TUD1"; + label = "SMC TUD1"; + }; + + temperature-TUD2 { + apple,key-id = "TUD2"; + label = "SMC TUD2"; + }; + + temperature-TUD3 { + apple,key-id = "TUD3"; + label = "SMC TUD3"; + }; + + temperature-TUD4 { + apple,key-id = "TUD4"; + label = "SMC TUD4"; + }; + + temperature-TUD5 { + apple,key-id = "TUD5"; + label = "SMC TUD5"; + }; + + temperature-TUD6 { + apple,key-id = "TUD6"; + label = "SMC TUD6"; + }; + + temperature-TUD7 { + apple,key-id = "TUD7"; + label = "SMC TUD7"; + }; + + temperature-TUD8 { + apple,key-id = "TUD8"; + label = "SMC TUD8"; + }; + + temperature-TUD9 { + apple,key-id = "TUD9"; + label = "SMC TUD9"; + }; + + temperature-TUDX { + apple,key-id = "TUDX"; + label = "SMC TUDX"; + }; + + temperature-TUDa { + apple,key-id = "TUDa"; + label = "SMC TUDa"; + }; + + temperature-TUDb { + apple,key-id = "TUDb"; + label = "SMC TUDb"; + }; + + temperature-TUDc { + apple,key-id = "TUDc"; + label = "SMC TUDc"; + }; + + temperature-TUDd { + apple,key-id = "TUDd"; + label = "SMC TUDd"; + }; + + temperature-TUDe { + apple,key-id = "TUDe"; + label = "SMC TUDe"; + }; + + temperature-TUDf { + apple,key-id = "TUDf"; + label = "SMC TUDf"; + }; + + temperature-TVA0 { + apple,key-id = "TVA0"; + label = "SMC TVA0"; + }; + + temperature-TVD0 { + apple,key-id = "TVD0"; + label = "SMC TVD0"; + }; + + temperature-TVMR { + apple,key-id = "TVMR"; + label = "SMC TVMR"; + }; + + temperature-TVMX { + apple,key-id = "TVMX"; + label = "SMC TVMX"; + }; + + temperature-TVMr { + apple,key-id = "TVMr"; + label = "SMC TVMr"; + }; + + temperature-TVS0 { + apple,key-id = "TVS0"; + label = "SMC TVS0"; + }; + + temperature-TVS1 { + apple,key-id = "TVS1"; + label = "SMC TVS1"; + }; + + temperature-TVS2 { + apple,key-id = "TVS2"; + label = "SMC TVS2"; + }; + + temperature-TVV0 { + apple,key-id = "TVV0"; + label = "SMC TVV0"; + }; + + temperature-TVXX { + apple,key-id = "TVXX"; + label = "SMC TVXX"; + }; + + temperature-TVXh { + apple,key-id = "TVXh"; + label = "SMC TVXh"; + }; + + temperature-TVXm { + apple,key-id = "TVXm"; + label = "SMC TVXm"; + }; + + temperature-TVh0 { + apple,key-id = "TVh0"; + label = "SMC TVh0"; + }; + + temperature-TVh1 { + apple,key-id = "TVh1"; + label = "SMC TVh1"; + }; + + temperature-TVm0 { + apple,key-id = "TVm0"; + label = "SMC TVm0"; + }; + + temperature-TVxx { + apple,key-id = "TVxx"; + label = "SMC TVxx"; + }; + + temperature-TaLP { + apple,key-id = "TaLP"; + label = "SMC TaLP"; + }; + + temperature-TaLT { + apple,key-id = "TaLT"; + label = "SMC TaLT"; + }; + + temperature-TaLW { + apple,key-id = "TaLW"; + label = "SMC TaLW"; + }; + + temperature-TaRF { + apple,key-id = "TaRF"; + label = "SMC TaRF"; + }; + + temperature-TaRT { + apple,key-id = "TaRT"; + label = "SMC TaRT"; + }; + + temperature-TaRW { + apple,key-id = "TaRW"; + label = "SMC TaRW"; + }; + + temperature-TaTP { + apple,key-id = "TaTP"; + label = "SMC TaTP"; + }; + + temperature-Te04 { + apple,key-id = "Te04"; + label = "E-cluster Te04"; + }; + + temperature-Te05 { + apple,key-id = "Te05"; + label = "E-cluster Te05"; + }; + + temperature-Te06 { + apple,key-id = "Te06"; + label = "E-cluster Te06"; + }; + + temperature-Te0R { + apple,key-id = "Te0R"; + label = "E-cluster Te0R"; + }; + + temperature-Te0S { + apple,key-id = "Te0S"; + label = "E-cluster Te0S"; + }; + + temperature-Te0T { + apple,key-id = "Te0T"; + label = "E-cluster Te0T"; + }; + + temperature-TfC0 { + apple,key-id = "TfC0"; + label = "SMC TfC0"; + }; + + temperature-TfC1 { + apple,key-id = "TfC1"; + label = "SMC TfC1"; + }; + + temperature-TfC2 { + apple,key-id = "TfC2"; + label = "SMC TfC2"; + }; + + temperature-TfC3 { + apple,key-id = "TfC3"; + label = "SMC TfC3"; + }; + + temperature-TfC4 { + apple,key-id = "TfC4"; + label = "SMC TfC4"; + }; + + temperature-Tg04 { + apple,key-id = "Tg04"; + label = "GPU Tg04"; + }; + + temperature-Tg0K { + apple,key-id = "Tg0K"; + label = "GPU Tg0K"; + }; + + temperature-Tg0L { + apple,key-id = "Tg0L"; + label = "GPU Tg0L"; + }; + + temperature-Tg0R { + apple,key-id = "Tg0R"; + label = "GPU Tg0R"; + }; + + temperature-Tg0X { + apple,key-id = "Tg0X"; + label = "GPU Tg0X"; + }; + + temperature-Tg0d { + apple,key-id = "Tg0d"; + label = "GPU Tg0d"; + }; + + temperature-Tg0e { + apple,key-id = "Tg0e"; + label = "GPU Tg0e"; + }; + + temperature-Tg0j { + apple,key-id = "Tg0j"; + label = "GPU Tg0j"; + }; + + temperature-Tg0y { + apple,key-id = "Tg0y"; + label = "GPU Tg0y"; + }; + + temperature-Tg1E { + apple,key-id = "Tg1E"; + label = "GPU Tg1E"; + }; + + temperature-Tg1F { + apple,key-id = "Tg1F"; + label = "GPU Tg1F"; + }; + + temperature-Tg1U { + apple,key-id = "Tg1U"; + label = "GPU Tg1U"; + }; + + temperature-Tg1c { + apple,key-id = "Tg1c"; + label = "GPU Tg1c"; + }; + + temperature-Tg1d { + apple,key-id = "Tg1d"; + label = "GPU Tg1d"; + }; + + temperature-Tg1k { + apple,key-id = "Tg1k"; + label = "GPU Tg1k"; + }; + + temperature-Tp00 { + apple,key-id = "Tp00"; + label = "P-cluster Tp00"; + }; + + temperature-Tp01 { + apple,key-id = "Tp01"; + label = "P-cluster Tp01"; + }; + + temperature-Tp02 { + apple,key-id = "Tp02"; + label = "P-cluster Tp02"; + }; + + temperature-Tp04 { + apple,key-id = "Tp04"; + label = "P-cluster Tp04"; + }; + + temperature-Tp05 { + apple,key-id = "Tp05"; + label = "P-cluster Tp05"; + }; + + temperature-Tp06 { + apple,key-id = "Tp06"; + label = "P-cluster Tp06"; + }; + + temperature-Tp08 { + apple,key-id = "Tp08"; + label = "P-cluster Tp08"; + }; + + temperature-Tp09 { + apple,key-id = "Tp09"; + label = "P-cluster Tp09"; + }; + + temperature-Tp0A { + apple,key-id = "Tp0A"; + label = "P-cluster Tp0A"; + }; + + temperature-Tp0C { + apple,key-id = "Tp0C"; + label = "P-cluster Tp0C"; + }; + + temperature-Tp0D { + apple,key-id = "Tp0D"; + label = "P-cluster Tp0D"; + }; + + temperature-Tp0E { + apple,key-id = "Tp0E"; + label = "P-cluster Tp0E"; + }; + + temperature-Tp0G { + apple,key-id = "Tp0G"; + label = "P-cluster Tp0G"; + }; + + temperature-Tp0H { + apple,key-id = "Tp0H"; + label = "P-cluster Tp0H"; + }; + + temperature-Tp0I { + apple,key-id = "Tp0I"; + label = "P-cluster Tp0I"; + }; + + temperature-Tp0K { + apple,key-id = "Tp0K"; + label = "P-cluster Tp0K"; + }; + + temperature-Tp0L { + apple,key-id = "Tp0L"; + label = "P-cluster Tp0L"; + }; + + temperature-Tp0M { + apple,key-id = "Tp0M"; + label = "P-cluster Tp0M"; + }; + + temperature-Tp0O { + apple,key-id = "Tp0O"; + label = "P-cluster Tp0O"; + }; + + temperature-Tp0P { + apple,key-id = "Tp0P"; + label = "P-cluster Tp0P"; + }; + + temperature-Tp0Q { + apple,key-id = "Tp0Q"; + label = "P-cluster Tp0Q"; + }; + + temperature-Tp0S { + apple,key-id = "Tp0S"; + label = "P-cluster Tp0S"; + }; + + temperature-Tp0T { + apple,key-id = "Tp0T"; + label = "P-cluster Tp0T"; + }; + + temperature-Tp0U { + apple,key-id = "Tp0U"; + label = "P-cluster Tp0U"; + }; + + temperature-Tp0W { + apple,key-id = "Tp0W"; + label = "P-cluster Tp0W"; + }; + + temperature-Tp0X { + apple,key-id = "Tp0X"; + label = "P-cluster Tp0X"; + }; + + temperature-Tp0Y { + apple,key-id = "Tp0Y"; + label = "P-cluster Tp0Y"; + }; + + temperature-Tp0a { + apple,key-id = "Tp0a"; + label = "P-cluster Tp0a"; + }; + + temperature-Tp0b { + apple,key-id = "Tp0b"; + label = "P-cluster Tp0b"; + }; + + temperature-Tp0c { + apple,key-id = "Tp0c"; + label = "P-cluster Tp0c"; + }; + + temperature-Tp0d { + apple,key-id = "Tp0d"; + label = "P-cluster Tp0d"; + }; + + temperature-Tp0e { + apple,key-id = "Tp0e"; + label = "P-cluster Tp0e"; + }; + + temperature-Tp0f { + apple,key-id = "Tp0f"; + label = "P-cluster Tp0f"; + }; + + temperature-Tp0g { + apple,key-id = "Tp0g"; + label = "P-cluster Tp0g"; + }; + + temperature-Tp0i { + apple,key-id = "Tp0i"; + label = "P-cluster Tp0i"; + }; + + temperature-Tp0k { + apple,key-id = "Tp0k"; + label = "P-cluster Tp0k"; + }; + + temperature-Tp0l { + apple,key-id = "Tp0l"; + label = "P-cluster Tp0l"; + }; + + temperature-Tp0m { + apple,key-id = "Tp0m"; + label = "P-cluster Tp0m"; + }; + + temperature-Tp0n { + apple,key-id = "Tp0n"; + label = "P-cluster Tp0n"; + }; + + temperature-Tp0o { + apple,key-id = "Tp0o"; + label = "P-cluster Tp0o"; + }; + + temperature-Tp0q { + apple,key-id = "Tp0q"; + label = "P-cluster Tp0q"; + }; + + temperature-Tp0t { + apple,key-id = "Tp0t"; + label = "P-cluster Tp0t"; + }; + + temperature-Tp0u { + apple,key-id = "Tp0u"; + label = "P-cluster Tp0u"; + }; + + temperature-Tp0v { + apple,key-id = "Tp0v"; + label = "P-cluster Tp0v"; + }; + + temperature-Tp0w { + apple,key-id = "Tp0w"; + label = "P-cluster Tp0w"; + }; + + temperature-Tp1i { + apple,key-id = "Tp1i"; + label = "P-cluster Tp1i"; + }; + + temperature-Tp1j { + apple,key-id = "Tp1j"; + label = "P-cluster Tp1j"; + }; + + temperature-Tp1k { + apple,key-id = "Tp1k"; + label = "P-cluster Tp1k"; + }; + + temperature-Tp1m { + apple,key-id = "Tp1m"; + label = "P-cluster Tp1m"; + }; + + temperature-Tp1n { + apple,key-id = "Tp1n"; + label = "P-cluster Tp1n"; + }; + + temperature-Tp1o { + apple,key-id = "Tp1o"; + label = "P-cluster Tp1o"; + }; + + temperature-Tp1q { + apple,key-id = "Tp1q"; + label = "P-cluster Tp1q"; + }; + + temperature-Tp1t { + apple,key-id = "Tp1t"; + label = "P-cluster Tp1t"; + }; + + temperature-Tp1u { + apple,key-id = "Tp1u"; + label = "P-cluster Tp1u"; + }; + + temperature-Tp1v { + apple,key-id = "Tp1v"; + label = "P-cluster Tp1v"; + }; + + temperature-Tp1w { + apple,key-id = "Tp1w"; + label = "P-cluster Tp1w"; + }; + + temperature-Tp1x { + apple,key-id = "Tp1x"; + label = "P-cluster Tp1x"; + }; + + temperature-Tp1y { + apple,key-id = "Tp1y"; + label = "P-cluster Tp1y"; + }; + + temperature-Tp1z { + apple,key-id = "Tp1z"; + label = "P-cluster Tp1z"; + }; + + temperature-Tp20 { + apple,key-id = "Tp20"; + label = "P-cluster Tp20"; + }; + + temperature-Tp21 { + apple,key-id = "Tp21"; + label = "P-cluster Tp21"; + }; + + temperature-Tp22 { + apple,key-id = "Tp22"; + label = "P-cluster Tp22"; + }; + + temperature-Tp23 { + apple,key-id = "Tp23"; + label = "P-cluster Tp23"; + }; + + temperature-Tp24 { + apple,key-id = "Tp24"; + label = "P-cluster Tp24"; + }; + + temperature-Tp25 { + apple,key-id = "Tp25"; + label = "P-cluster Tp25"; + }; + + temperature-Tp26 { + apple,key-id = "Tp26"; + label = "P-cluster Tp26"; + }; + + temperature-Tp27 { + apple,key-id = "Tp27"; + label = "P-cluster Tp27"; + }; + + temperature-Tp28 { + apple,key-id = "Tp28"; + label = "P-cluster Tp28"; + }; + + temperature-Tp29 { + apple,key-id = "Tp29"; + label = "P-cluster Tp29"; + }; + + temperature-Tp2A { + apple,key-id = "Tp2A"; + label = "P-cluster Tp2A"; + }; + + temperature-Tp2B { + apple,key-id = "Tp2B"; + label = "P-cluster Tp2B"; + }; + + temperature-Tp2C { + apple,key-id = "Tp2C"; + label = "P-cluster Tp2C"; + }; + + temperature-Tp2D { + apple,key-id = "Tp2D"; + label = "P-cluster Tp2D"; + }; + + temperature-Tp2E { + apple,key-id = "Tp2E"; + label = "P-cluster Tp2E"; + }; + + temperature-Tp2G { + apple,key-id = "Tp2G"; + label = "P-cluster Tp2G"; + }; + + temperature-Tp2I { + apple,key-id = "Tp2I"; + label = "P-cluster Tp2I"; + }; + + temperature-Tp2J { + apple,key-id = "Tp2J"; + label = "P-cluster Tp2J"; + }; + + temperature-Tp2K { + apple,key-id = "Tp2K"; + label = "P-cluster Tp2K"; + }; + + temperature-Tp2L { + apple,key-id = "Tp2L"; + label = "P-cluster Tp2L"; + }; + + temperature-Tp2M { + apple,key-id = "Tp2M"; + label = "P-cluster Tp2M"; + }; + + temperature-Tp2N { + apple,key-id = "Tp2N"; + label = "P-cluster Tp2N"; + }; + + temperature-Tp2O { + apple,key-id = "Tp2O"; + label = "P-cluster Tp2O"; + }; + + temperature-Tp2Q { + apple,key-id = "Tp2Q"; + label = "P-cluster Tp2Q"; + }; + + temperature-Tp2R { + apple,key-id = "Tp2R"; + label = "P-cluster Tp2R"; + }; + + temperature-Tp2S { + apple,key-id = "Tp2S"; + label = "P-cluster Tp2S"; + }; + + temperature-Tp2T { + apple,key-id = "Tp2T"; + label = "P-cluster Tp2T"; + }; + + temperature-Tp2U { + apple,key-id = "Tp2U"; + label = "P-cluster Tp2U"; + }; + + temperature-Tp2V { + apple,key-id = "Tp2V"; + label = "P-cluster Tp2V"; + }; + + temperature-Tp2W { + apple,key-id = "Tp2W"; + label = "P-cluster Tp2W"; + }; + + temperature-Tp2X { + apple,key-id = "Tp2X"; + label = "P-cluster Tp2X"; + }; + + temperature-Ts00 { + apple,key-id = "Ts00"; + label = "SMC Ts00"; + }; + + temperature-Ts01 { + apple,key-id = "Ts01"; + label = "SMC Ts01"; + }; + + temperature-Ts02 { + apple,key-id = "Ts02"; + label = "SMC Ts02"; + }; + + temperature-Ts04 { + apple,key-id = "Ts04"; + label = "SMC Ts04"; + }; + + temperature-Ts05 { + apple,key-id = "Ts05"; + label = "SMC Ts05"; + }; + + temperature-Ts06 { + apple,key-id = "Ts06"; + label = "SMC Ts06"; + }; + + temperature-Ts08 { + apple,key-id = "Ts08"; + label = "SMC Ts08"; + }; + + temperature-Ts09 { + apple,key-id = "Ts09"; + label = "SMC Ts09"; + }; + + temperature-Ts0A { + apple,key-id = "Ts0A"; + label = "SMC Ts0A"; + }; + + temperature-Ts0C { + apple,key-id = "Ts0C"; + label = "SMC Ts0C"; + }; + + temperature-Ts0D { + apple,key-id = "Ts0D"; + label = "SMC Ts0D"; + }; + + temperature-Ts0E { + apple,key-id = "Ts0E"; + label = "SMC Ts0E"; + }; + + temperature-Ts0G { + apple,key-id = "Ts0G"; + label = "SMC Ts0G"; + }; + + temperature-Ts0H { + apple,key-id = "Ts0H"; + label = "SMC Ts0H"; + }; + + temperature-Ts0I { + apple,key-id = "Ts0I"; + label = "SMC Ts0I"; + }; + + temperature-Ts0K { + apple,key-id = "Ts0K"; + label = "SMC Ts0K"; + }; + + temperature-Ts0L { + apple,key-id = "Ts0L"; + label = "SMC Ts0L"; + }; + + temperature-Ts0M { + apple,key-id = "Ts0M"; + label = "SMC Ts0M"; + }; + + temperature-Ts0O { + apple,key-id = "Ts0O"; + label = "SMC Ts0O"; + }; + + temperature-Ts0P { + apple,key-id = "Ts0P"; + label = "SMC Ts0P"; + }; + + temperature-Ts0Q { + apple,key-id = "Ts0Q"; + label = "SMC Ts0Q"; + }; + + temperature-Ts0R { + apple,key-id = "Ts0R"; + label = "SMC Ts0R"; + }; + + temperature-Ts0T { + apple,key-id = "Ts0T"; + label = "SMC Ts0T"; + }; + + temperature-Ts0U { + apple,key-id = "Ts0U"; + label = "SMC Ts0U"; + }; + + temperature-Ts0W { + apple,key-id = "Ts0W"; + label = "SMC Ts0W"; + }; + + temperature-Ts1P { + apple,key-id = "Ts1P"; + label = "SMC Ts1P"; + }; + + temperature-Tsx0 { + apple,key-id = "Tsx0"; + label = "SMC Tsx0"; + }; + + temperature-Tsx1 { + apple,key-id = "Tsx1"; + label = "SMC Tsx1"; + }; + + voltage-VDM2 { + apple,key-id = "VDM2"; + label = "SMC VDM2"; + }; + + voltage-VDMA { + apple,key-id = "VDMA"; + label = "SMC VDMA"; + }; + + voltage-VDMM { + apple,key-id = "VDMM"; + label = "SMC VDMM"; + }; + + voltage-VP0R { + apple,key-id = "VP0R"; + label = "SMC VP0R"; + }; + + voltage-VP0b { + apple,key-id = "VP0b"; + label = "SMC VP0b"; + }; + + voltage-VP1b { + apple,key-id = "VP1b"; + label = "SMC VP1b"; + }; + + voltage-VP1l { + apple,key-id = "VP1l"; + label = "SMC VP1l"; + }; + + voltage-VP2b { + apple,key-id = "VP2b"; + label = "SMC VP2b"; + }; + + voltage-VP3b { + apple,key-id = "VP3b"; + label = "SMC VP3b"; + }; + + voltage-VP4b { + apple,key-id = "VP4b"; + label = "SMC VP4b"; + }; + + voltage-VP5l { + apple,key-id = "VP5l"; + label = "SMC VP5l"; + }; + + voltage-VP7b { + apple,key-id = "VP7b"; + label = "SMC VP7b"; + }; + + voltage-VP7l { + apple,key-id = "VP7l"; + label = "SMC VP7l"; + }; + + voltage-VP9b { + apple,key-id = "VP9b"; + label = "SMC VP9b"; + }; + + voltage-VP9l { + apple,key-id = "VP9l"; + label = "SMC VP9l"; + }; + + voltage-VPal { + apple,key-id = "VPal"; + label = "SMC VPal"; + }; + + voltage-VPbb { + apple,key-id = "VPbb"; + label = "SMC VPbb"; + }; + + voltage-VPdb { + apple,key-id = "VPdb"; + label = "SMC VPdb"; + }; + + voltage-VPeb { + apple,key-id = "VPeb"; + label = "SMC VPeb"; + }; + + voltage-VR0b { + apple,key-id = "VR0b"; + label = "SMC VR0b"; + }; + + voltage-VR1b { + apple,key-id = "VR1b"; + label = "SMC VR1b"; + }; + + voltage-VR2b { + apple,key-id = "VR2b"; + label = "SMC VR2b"; + }; + + voltage-VR3b { + apple,key-id = "VR3b"; + label = "SMC VR3b"; + }; + + voltage-VR3l { + apple,key-id = "VR3l"; + label = "SMC VR3l"; + }; + + voltage-VR4b { + apple,key-id = "VR4b"; + label = "SMC VR4b"; + }; + + voltage-VR7b { + apple,key-id = "VR7b"; + label = "SMC VR7b"; + }; + + voltage-VR8l { + apple,key-id = "VR8l"; + label = "SMC VR8l"; + }; + + voltage-VR9b { + apple,key-id = "VR9b"; + label = "SMC VR9b"; + }; + + voltage-VR9l { + apple,key-id = "VR9l"; + label = "SMC VR9l"; + }; + + voltage-VRal { + apple,key-id = "VRal"; + label = "SMC VRal"; + }; + + voltage-VRbb { + apple,key-id = "VRbb"; + label = "SMC VRbb"; + }; + + voltage-VRdb { + apple,key-id = "VRdb"; + label = "SMC VRdb"; + }; + + voltage-VReb { + apple,key-id = "VReb"; + label = "SMC VReb"; + }; + + voltage-VS0b { + apple,key-id = "VS0b"; + label = "SMC VS0b"; + }; + + voltage-VS1b { + apple,key-id = "VS1b"; + label = "SMC VS1b"; + }; + + voltage-VS2b { + apple,key-id = "VS2b"; + label = "SMC VS2b"; + }; + + voltage-VS3b { + apple,key-id = "VS3b"; + label = "SMC VS3b"; + }; + + voltage-VS4b { + apple,key-id = "VS4b"; + label = "SMC VS4b"; + }; + + voltage-VS7b { + apple,key-id = "VS7b"; + label = "SMC VS7b"; + }; + + voltage-VS9b { + apple,key-id = "VS9b"; + label = "SMC VS9b"; + }; + + voltage-VS9l { + apple,key-id = "VS9l"; + label = "SMC VS9l"; + }; + + voltage-VSal { + apple,key-id = "VSal"; + label = "SMC VSal"; + }; + + voltage-VSbb { + apple,key-id = "VSbb"; + label = "SMC VSbb"; + }; + + voltage-VSeb { + apple,key-id = "VSeb"; + label = "SMC VSeb"; + }; + + voltage-Vb0f { + apple,key-id = "Vb0f"; + label = "SMC Vb0f"; + }; + + voltage-Vb1f { + apple,key-id = "Vb1f"; + label = "SMC Vb1f"; + }; + + voltage-SBA1 { + apple,key-id = "SBA1"; + label = "Battery Cell 1"; + }; + + voltage-SBA2 { + apple,key-id = "SBA2"; + label = "Battery Cell 2"; + }; + + voltage-SBA3 { + apple,key-id = "SBA3"; + label = "Battery Cell 3"; + }; + + current-I3F2 { + apple,key-id = "I3F2"; + label = "SMC I3F2"; + }; + + current-I5SR { + apple,key-id = "I5SR"; + label = "SMC I5SR"; + }; + + current-IALR { + apple,key-id = "IALR"; + label = "SMC IALR"; + }; + + current-IARR { + apple,key-id = "IARR"; + label = "SMC IARR"; + }; + + current-ICMC { + apple,key-id = "ICMC"; + label = "SMC ICMC"; + }; + + current-ID3C { + apple,key-id = "ID3C"; + label = "SMC ID3C"; + }; + + current-IDBC { + apple,key-id = "IDBC"; + label = "SMC IDBC"; + }; + + current-IDBR { + apple,key-id = "IDBR"; + label = "SMC IDBR"; + }; + + current-IFLR { + apple,key-id = "IFLR"; + label = "SMC IFLR"; + }; + + current-IH0R { + apple,key-id = "IH0R"; + label = "SMC IH0R"; + }; + + current-IHCR { + apple,key-id = "IHCR"; + label = "SMC IHCR"; + }; + + current-IIDR { + apple,key-id = "IIDR"; + label = "SMC IIDR"; + }; + + current-IINC { + apple,key-id = "IINC"; + label = "SMC IINC"; + }; + + current-IMVC { + apple,key-id = "IMVC"; + label = "SMC IMVC"; + }; + + current-IMVR { + apple,key-id = "IMVR"; + label = "SMC IMVR"; + }; + + current-IP0b { + apple,key-id = "IP0b"; + label = "SMC IP0b"; + }; + + current-IP1b { + apple,key-id = "IP1b"; + label = "SMC IP1b"; + }; + + current-IP1l { + apple,key-id = "IP1l"; + label = "SMC IP1l"; + }; + + current-IP2b { + apple,key-id = "IP2b"; + label = "SMC IP2b"; + }; + + current-IP3b { + apple,key-id = "IP3b"; + label = "SMC IP3b"; + }; + + current-IP4b { + apple,key-id = "IP4b"; + label = "SMC IP4b"; + }; + + current-IP5l { + apple,key-id = "IP5l"; + label = "SMC IP5l"; + }; + + current-IP7b { + apple,key-id = "IP7b"; + label = "SMC IP7b"; + }; + + current-IP7l { + apple,key-id = "IP7l"; + label = "SMC IP7l"; + }; + + current-IP9b { + apple,key-id = "IP9b"; + label = "SMC IP9b"; + }; + + current-IP9l { + apple,key-id = "IP9l"; + label = "SMC IP9l"; + }; + + current-IPBR { + apple,key-id = "IPBR"; + label = "SMC IPBR"; + }; + + current-IPMC { + apple,key-id = "IPMC"; + label = "SMC IPMC"; + }; + + current-IPSC { + apple,key-id = "IPSC"; + label = "SMC IPSC"; + }; + + current-IPal { + apple,key-id = "IPal"; + label = "SMC IPal"; + }; + + current-IPbb { + apple,key-id = "IPbb"; + label = "SMC IPbb"; + }; + + current-IPdb { + apple,key-id = "IPdb"; + label = "SMC IPdb"; + }; + + current-IPeb { + apple,key-id = "IPeb"; + label = "SMC IPeb"; + }; + + current-IR0b { + apple,key-id = "IR0b"; + label = "SMC IR0b"; + }; + + current-IR1b { + apple,key-id = "IR1b"; + label = "SMC IR1b"; + }; + + current-IR2b { + apple,key-id = "IR2b"; + label = "SMC IR2b"; + }; + + current-IR3b { + apple,key-id = "IR3b"; + label = "SMC IR3b"; + }; + + current-IR3l { + apple,key-id = "IR3l"; + label = "SMC IR3l"; + }; + + current-IR4b { + apple,key-id = "IR4b"; + label = "SMC IR4b"; + }; + + current-IR7b { + apple,key-id = "IR7b"; + label = "SMC IR7b"; + }; + + current-IR8l { + apple,key-id = "IR8l"; + label = "SMC IR8l"; + }; + + current-IR9b { + apple,key-id = "IR9b"; + label = "SMC IR9b"; + }; + + current-IR9l { + apple,key-id = "IR9l"; + label = "SMC IR9l"; + }; + + current-IRal { + apple,key-id = "IRal"; + label = "SMC IRal"; + }; + + current-IRbb { + apple,key-id = "IRbb"; + label = "SMC IRbb"; + }; + + current-IRdb { + apple,key-id = "IRdb"; + label = "SMC IRdb"; + }; + + current-IReb { + apple,key-id = "IReb"; + label = "SMC IReb"; + }; + + current-IS0b { + apple,key-id = "IS0b"; + label = "SMC IS0b"; + }; + + current-IS1b { + apple,key-id = "IS1b"; + label = "SMC IS1b"; + }; + + current-IS2b { + apple,key-id = "IS2b"; + label = "SMC IS2b"; + }; + + current-IS3b { + apple,key-id = "IS3b"; + label = "SMC IS3b"; + }; + + current-IS4b { + apple,key-id = "IS4b"; + label = "SMC IS4b"; + }; + + current-IS7b { + apple,key-id = "IS7b"; + label = "SMC IS7b"; + }; + + current-IS9b { + apple,key-id = "IS9b"; + label = "SMC IS9b"; + }; + + current-IS9l { + apple,key-id = "IS9l"; + label = "SMC IS9l"; + }; + + current-ISDR { + apple,key-id = "ISDR"; + label = "SMC ISDR"; + }; + + current-ISal { + apple,key-id = "ISal"; + label = "SMC ISal"; + }; + + current-ISbb { + apple,key-id = "ISbb"; + label = "SMC ISbb"; + }; + + current-ISeb { + apple,key-id = "ISeb"; + label = "SMC ISeb"; + }; + + current-IU1C { + apple,key-id = "IU1C"; + label = "SMC IU1C"; + }; + + current-IU1R { + apple,key-id = "IU1R"; + label = "SMC IU1R"; + }; + + current-IU2C { + apple,key-id = "IU2C"; + label = "SMC IU2C"; + }; + + current-IU2R { + apple,key-id = "IU2R"; + label = "SMC IU2R"; + }; + + current-IW3C { + apple,key-id = "IW3C"; + label = "SMC IW3C"; + }; + + current-Ib0f { + apple,key-id = "Ib0f"; + label = "SMC Ib0f"; + }; + + current-Ib8f { + apple,key-id = "Ib8f"; + label = "SMC Ib8f"; + }; + + current-SBAC { + apple,key-id = "SBAC"; + label = "Battery Current"; + }; + + power-P3F2 { + apple,key-id = "P3F2"; + label = "SMC P3F2"; + }; + + power-P5SR { + apple,key-id = "P5SR"; + label = "SMC P5SR"; + }; + + power-PALR { + apple,key-id = "PALR"; + label = "SMC PALR"; + }; + + power-PARR { + apple,key-id = "PARR"; + label = "SMC PARR"; + }; + + power-PCMC { + apple,key-id = "PCMC"; + label = "SMC PCMC"; + }; + + power-PD3C { + apple,key-id = "PD3C"; + label = "SMC PD3C"; + }; + + power-PDBC { + apple,key-id = "PDBC"; + label = "SMC PDBC"; + }; + + power-PDBR { + apple,key-id = "PDBR"; + label = "SMC PDBR"; + }; + + power-PFLR { + apple,key-id = "PFLR"; + label = "SMC PFLR"; + }; + + power-PH0R { + apple,key-id = "PH0R"; + label = "SMC PH0R"; + }; + + power-PHCR { + apple,key-id = "PHCR"; + label = "SMC PHCR"; + }; + + power-PIDR { + apple,key-id = "PIDR"; + label = "SMC PIDR"; + }; + + power-PINC { + apple,key-id = "PINC"; + label = "SMC PINC"; + }; + + power-PMVC { + apple,key-id = "PMVC"; + label = "SMC PMVC"; + }; + + power-PP0b { + apple,key-id = "PP0b"; + label = "SMC PP0b"; + }; + + power-PP1b { + apple,key-id = "PP1b"; + label = "SMC PP1b"; + }; + + power-PP1l { + apple,key-id = "PP1l"; + label = "SMC PP1l"; + }; + + power-PP2b { + apple,key-id = "PP2b"; + label = "SMC PP2b"; + }; + + power-PP3b { + apple,key-id = "PP3b"; + label = "SMC PP3b"; + }; + + power-PP4b { + apple,key-id = "PP4b"; + label = "SMC PP4b"; + }; + + power-PP5l { + apple,key-id = "PP5l"; + label = "SMC PP5l"; + }; + + power-PP7b { + apple,key-id = "PP7b"; + label = "SMC PP7b"; + }; + + power-PP7l { + apple,key-id = "PP7l"; + label = "SMC PP7l"; + }; + + power-PP9b { + apple,key-id = "PP9b"; + label = "SMC PP9b"; + }; + + power-PP9l { + apple,key-id = "PP9l"; + label = "SMC PP9l"; + }; + + power-PPBR { + apple,key-id = "PPBR"; + label = "SMC PPBR"; + }; + + power-PPMC { + apple,key-id = "PPMC"; + label = "SMC PPMC"; + }; + + power-PPSC { + apple,key-id = "PPSC"; + label = "SMC PPSC"; + }; + + power-PPal { + apple,key-id = "PPal"; + label = "SMC PPal"; + }; + + power-PPbb { + apple,key-id = "PPbb"; + label = "SMC PPbb"; + }; + + power-PPdb { + apple,key-id = "PPdb"; + label = "SMC PPdb"; + }; + + power-PPeb { + apple,key-id = "PPeb"; + label = "SMC PPeb"; + }; + + power-PR0b { + apple,key-id = "PR0b"; + label = "SMC PR0b"; + }; + + power-PR1b { + apple,key-id = "PR1b"; + label = "SMC PR1b"; + }; + + power-PR2b { + apple,key-id = "PR2b"; + label = "SMC PR2b"; + }; + + power-PR3b { + apple,key-id = "PR3b"; + label = "SMC PR3b"; + }; + + power-PR3l { + apple,key-id = "PR3l"; + label = "SMC PR3l"; + }; + + power-PR4b { + apple,key-id = "PR4b"; + label = "SMC PR4b"; + }; + + power-PR7b { + apple,key-id = "PR7b"; + label = "SMC PR7b"; + }; + + power-PR8l { + apple,key-id = "PR8l"; + label = "SMC PR8l"; + }; + + power-PR9b { + apple,key-id = "PR9b"; + label = "SMC PR9b"; + }; + + power-PR9l { + apple,key-id = "PR9l"; + label = "SMC PR9l"; + }; + + power-PRal { + apple,key-id = "PRal"; + label = "SMC PRal"; + }; + + power-PRbb { + apple,key-id = "PRbb"; + label = "SMC PRbb"; + }; + + power-PRdb { + apple,key-id = "PRdb"; + label = "SMC PRdb"; + }; + + power-PReb { + apple,key-id = "PReb"; + label = "SMC PReb"; + }; + + power-PS0b { + apple,key-id = "PS0b"; + label = "SMC PS0b"; + }; + + power-PS1b { + apple,key-id = "PS1b"; + label = "SMC PS1b"; + }; + + power-PS2b { + apple,key-id = "PS2b"; + label = "SMC PS2b"; + }; + + power-PS3b { + apple,key-id = "PS3b"; + label = "SMC PS3b"; + }; + + power-PS4b { + apple,key-id = "PS4b"; + label = "SMC PS4b"; + }; + + power-PS7b { + apple,key-id = "PS7b"; + label = "SMC PS7b"; + }; + + power-PS9b { + apple,key-id = "PS9b"; + label = "SMC PS9b"; + }; + + power-PS9l { + apple,key-id = "PS9l"; + label = "SMC PS9l"; + }; + + power-PSDR { + apple,key-id = "PSDR"; + label = "SMC PSDR"; + }; + + power-PSal { + apple,key-id = "PSal"; + label = "SMC PSal"; + }; + + power-PSbb { + apple,key-id = "PSbb"; + label = "SMC PSbb"; + }; + + power-PSeb { + apple,key-id = "PSeb"; + label = "SMC PSeb"; + }; + + power-PTHS { + apple,key-id = "PTHS"; + label = "SMC PTHS"; + }; + + power-PU1C { + apple,key-id = "PU1C"; + label = "SMC PU1C"; + }; + + power-PU1R { + apple,key-id = "PU1R"; + label = "SMC PU1R"; + }; + + power-PU2C { + apple,key-id = "PU2C"; + label = "SMC PU2C"; + }; + + power-PU2R { + apple,key-id = "PU2R"; + label = "SMC PU2R"; + }; + + power-PW3C { + apple,key-id = "PW3C"; + label = "SMC PW3C"; + }; + + power-PZC0 { + apple,key-id = "PZC0"; + label = "SMC PZC0"; + }; + + power-PZC1 { + apple,key-id = "PZC1"; + label = "SMC PZC1"; + }; + + power-PZCB { + apple,key-id = "PZCB"; + label = "SMC PZCB"; + }; + + power-PZCU { + apple,key-id = "PZCU"; + label = "SMC PZCU"; + }; + + power-PZD1 { + apple,key-id = "PZD1"; + label = "SMC PZD1"; + }; + + power-PZT0 { + apple,key-id = "PZT0"; + label = "SMC PZT0"; + }; + + power-Pb0f { + apple,key-id = "Pb0f"; + label = "SMC Pb0f"; + }; + + power-SBAP { + apple,key-id = "SBAP"; + label = "Battery Power"; + }; +}; + +/* Fan monitoring and explicit userspace manual control. */ +#include "hwmon-common.dtsi" +#include "hwmon-fan-dual.dtsi" +#include "hwmon-laptop.dtsi" + +&smc_hwmon { + apple,thermal-pressure-key = "mTPL"; + + /* + * The SMC accepts a manual speed for both fans once its fan test + * key unlocks them, and returns them to system mode when the + * request is dropped. + */ + fan-F0Ac { + apple,fan-manual-control; + }; + + fan-F1Ac { + apple,fan-manual-control; + }; +}; + +&pcie_dart { + status = "okay"; +}; + +/* + * Wi-Fi and Bluetooth (BCM4388). The boot loader finds these nodes by + * the paths in the wifi0 and bluetooth0 aliases and adds the per-unit + * calibration, the antenna SKU and the addresses to them. + */ +/ { + pcie0: pcie@1cb0000000 { + compatible = "apple,t6041-pcie", "apple,t6020-pcie"; + reg = <0x1c 0xb0000000 0x0 0x1000000>, /* config */ + <0x4 0x14000000 0x0 0x4000>, /* rc */ + <0x4 0x10028000 0x0 0x4000>, /* port0 */ + <0x4 0x11028000 0x0 0x4000>, /* port1 */ + <0x4 0x12028000 0x0 0x4000>, /* port2 */ + <0x4 0x13028000 0x0 0x4000>, /* port3 */ + <0x4 0x17020000 0x0 0x4000>, /* phy0 */ + <0x4 0x17024000 0x0 0x4000>, /* phy1 */ + <0x4 0x17028000 0x0 0x4000>, /* phy2 */ + <0x4 0x1702c000 0x0 0x4000>; /* phy3 */ + reg-names = "config", "rc", + "port0", "port1", "port2", "port3", + "phy0", "phy1", "phy2", "phy3"; + ranges = <0x43000000 0xb 0xc0000000 0xb 0xc0000000 0 0x20000000 0x2000000 0 0x80000000 0xb 0x80000000 0 0x40000000>; + pinctrl-names = "default"; + pinctrl-0 = <&wifi_clkreq_pins>; + #size-cells = <2>; + #address-cells = <3>; + bus-range = <0 1>; + iommu-map-mask = <0xffff>; + iommu-map = <0x100 &pcie_dart 1 2>; + msi-ranges = <&aic AIC_IRQ 2071 IRQ_TYPE_EDGE_RISING 32>; + msi-parent = <&pcie0>; + msi-controller; + interrupts = <0 0x6bb 4>; + interrupt-parent = <&aic>; + device_type = "pci"; + + port00: pci@0,0 { + reg = <0 0 0 0 0>; + ranges; + interrupt-map = <0 0 0 1 &port00 0 0 0 0 0 0 0 2 &port00 0 0 0 1 0 0 0 3 &port00 0 0 0 2 0 0 0 4 &port00 0 0 0 3>; + interrupt-map-mask = <0 0 0 7>; + #interrupt-cells = <1>; + interrupt-controller; + #size-cells = <2>; + #address-cells = <3>; + pwren-gpios = <&smc_gpio 0x13 0>; + reset-gpios = <&pinctrl 4 1>; + device_type = "pci"; + + bluetooth0: bluetooth@0,1 { + compatible = "pci14e4,5f72"; + reg = <0x100 0 0 0 0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + status = "okay"; + }; + + wifi0: wifi@0,0 { + compatible = "pci14e4,4434", "brcm,bcm4329-fmac"; + reg = <0 0 0 0 0>; + brcm,ccode-map-trivial; + }; + }; + }; +}; + +&ans_mbox { + status = "okay"; +}; + +&ans_sart { + status = "okay"; +}; + +&nvme { + status = "okay"; +}; + +&pmp_dart { + status = "okay"; +}; + +&pmp_report { + status = "okay"; +}; + +&pmp { + status = "okay"; +}; + +&pmp_mbox { + status = "okay"; +}; + +&dcp { + panel: panel { + status = "disabled"; + }; +}; + +&pmc { + status = "okay"; +}; + +&mcc { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +/* The sensor is on the logic board, in the base. */ +&aop_accel { + label = "accel-base"; +}; + +&admac0 { + status = "okay"; +}; + +&admac1 { + status = "okay"; +}; + +&aop_audio { + apple,machine-kind = "MacBook Pro"; +}; + +/* + * The six SN012776 speaker amps, held in hardware shutdown until a sound + * card enables them (ADT /arm-io/i2c1/audio-speaker@38): one interrupt line + * on AP pin 55, level low; sense slots from speaker-config (speaker i at RX + * bytes 4i and 4i + 2, order LW1 RW1 LW2 RW2 LT RT). Names as on the J314 + * and J316, which speakersafetyd's profile uses. Every amp joins the SDZ + * fail-closed group: an over-temperature or over-current fault on any one + * forces the shared shutdown line low. + */ +&i2c1 { + status = "okay"; + + speaker_left_woof1: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 1"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + }; + + speaker_left_woof2: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 2"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <8>; + ti,vmon-slot-no = <10>; + }; + + speaker_left_tweet: codec@3a { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3a>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Tweeter"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <16>; + ti,vmon-slot-no = <18>; + }; +}; + +&i2c3 { + status = "okay"; + + speaker_right_woof1: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 1"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + }; + + speaker_right_woof2: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 2"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <12>; + ti,vmon-slot-no = <14>; + }; + + speaker_right_tweet: codec@3d { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3d>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Tweeter"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <20>; + ti,vmon-slot-no = <22>; + }; +}; + +/* + * The CS42L84 headphone codec (ADT /arm-io/i2c2/audio-codec-output@4B). Its + * reset line is nub GPIO pin 58, driven high to run the codec (the driver's + * "reset" is an enable, as on the J514/J516); its interrupt is AP pin 106, + * level low. The AOP firmware runs the jack's stream on MCA cluster 2. + */ +&i2c2 { + status = "okay"; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 58 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl 106 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + +&isp { + memory-region = <&isp_fw_text>, <&isp_fw_data>, <&isp_heap>; + memory-region-names = "text", "data", "heap"; + apple,platform-id = <5>; + /* ADT isp0 cam-connections-scheme */ + apple,cam-connections-scheme = <0x10>; + status = "okay"; + + /* + * The front camera, an IMX958: firmware configuration 5 as the + * firmware reports it. + */ + sensor-presets { + /* 1920x1080 */ + preset0 { + apple,config-index = <5>; + apple,input-size = <1920 2160>; + apple,output-size = <1920 1080>; + apple,crop = <0 0 1920 1080>; + }; + /* 1280x720 */ + preset1 { + apple,config-index = <5>; + apple,input-size = <1920 2160>; + apple,output-size = <1280 720>; + apple,crop = <0 0 1280 720>; + }; + }; +}; + +&isp_dart0 { + status = "okay"; +}; + +&isp_dart1 { + status = "okay"; +}; + +&isp_dart2 { + status = "okay"; +}; + +/* + * The loaded GPU firmware keeps running across system sleep. Like the Neo's + * GFX domain, these domains must not be switched off while that image lives. + * The PMP report vote is still owned by the GPU consumer. + */ +&ps_afr { + apple,always-on; +}; + +&ps_gfx { + apple,always-on; +}; + +/* The loader fills the operating points, calibration and all six memory regions. */ +&gfx_mbox { + status = "okay"; +}; + +&gpu { + memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, + <&gpu_fw_data>, <&uat_l2_pagetables>, <&gpu_fw_text>; + memory-region-names = "ttbs", "pagetables", "handoff", "fw-data", + "l2-pagetables", "fw-text"; + operating-points-v2 = <&gpu_opp>; + apple,cs-opp = <&gpu_cs_opp>; + apple,afr-opp = <&gpu_afr_opp>; + /* Avoid a core power cycle between interactive submissions, then release. */ + apple,idle-off-delay-ms = <16>; + status = "okay"; +}; + +/* + * The Type-C ports (ADT nub-spmi-a0 hpm0/hpm1, nub-spmi-a1 hpm2) and the + * MagSafe controller (nub-spmi-a0 hpm5). The ADT lists the event, sleep and + * wake interrupts; select is event + 2. + */ +&nub_spmi_a0 { + status = "okay"; + + hpm0: usb-pd@c { + compatible = "apple,sn201202x"; + reg = <0xc SPMI_USID>; + interrupts = <11 IRQ_TYPE_EDGE_RISING>, + <13 IRQ_TYPE_EDGE_RISING>, + <17 IRQ_TYPE_EDGE_RISING>, + <19 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + + typec0: connector { + compatible = "usb-c-connector"; + label = "USB-C Left Back"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec0_con_hs: endpoint { + remote-endpoint = <&usb_drd0_hs>; + }; + }; + + port@1 { + reg = <1>; + typec0_con_ss: endpoint { + remote-endpoint = <&atcphy0_ss>; + }; + }; + }; + }; + }; + + hpm1: usb-pd@a { + compatible = "apple,sn201202x"; + reg = <0xa SPMI_USID>; + interrupts = <37 IRQ_TYPE_EDGE_RISING>, + <39 IRQ_TYPE_EDGE_RISING>, + <43 IRQ_TYPE_EDGE_RISING>, + <45 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Left Front"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec1_con_hs: endpoint { + remote-endpoint = <&usb_drd1_hs>; + }; + }; + + port@1 { + reg = <1>; + typec1_con_ss: endpoint { + remote-endpoint = <&atcphy1_ss>; + }; + }; + }; + }; + }; + + /* MagSafe port */ + hpm5: usb-pd@8 { + compatible = "apple,sn201202x"; + reg = <0x8 SPMI_USID>; + interrupts = <63 IRQ_TYPE_EDGE_RISING>, + <65 IRQ_TYPE_EDGE_RISING>, + <69 IRQ_TYPE_EDGE_RISING>, + <71 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + }; +}; + +&nub_spmi_a1 { + status = "okay"; + + hpm2: usb-pd@c { + compatible = "apple,sn201202x"; + reg = <0xc SPMI_USID>; + interrupts = <11 IRQ_TYPE_EDGE_RISING>, + <13 IRQ_TYPE_EDGE_RISING>, + <17 IRQ_TYPE_EDGE_RISING>, + <19 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec2_con_hs: endpoint { + remote-endpoint = <&usb_drd2_hs>; + }; + }; + + port@1 { + reg = <1>; + typec2_con_ss: endpoint { + remote-endpoint = <&atcphy2_ss>; + }; + }; + }; + }; + }; +}; + +&atcphy0 { + status = "okay"; + + port { + atcphy0_ss: endpoint { + remote-endpoint = <&typec0_con_ss>; + }; + }; +}; + +&atcphy0_mbox { + status = "okay"; +}; + +&usb_drd0_dart_0 { + status = "okay"; +}; + +&usb_drd0_dart_1 { + status = "okay"; +}; + +&usb_drd0 { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd0_hs: endpoint { + remote-endpoint = <&typec0_con_hs>; + }; + }; + }; +}; + +&atcphy1 { + status = "okay"; + + port { + atcphy1_ss: endpoint { + remote-endpoint = <&typec1_con_ss>; + }; + }; +}; + +&atcphy1_mbox { + status = "okay"; +}; + +&usb_drd1_dart_0 { + status = "okay"; +}; + +&usb_drd1_dart_1 { + status = "okay"; +}; + +&usb_drd1 { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd1_hs: endpoint { + remote-endpoint = <&typec1_con_hs>; + }; + }; + }; +}; + +&atcphy2 { + status = "okay"; + + port { + atcphy2_ss: endpoint { + remote-endpoint = <&typec2_con_ss>; + }; + }; +}; + +&atcphy2_mbox { + status = "okay"; +}; + +&usb_drd2_dart_0 { + status = "okay"; +}; + +&usb_drd2_dart_1 { + status = "okay"; +}; + +&usb_drd2 { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd2_hs: endpoint { + remote-endpoint = <&typec2_con_hs>; + }; + }; + }; +}; + +/* The video encoder, its DARTs and its power domains. */ +&ps_venc0_sys { + status = "okay"; +}; + +&ps_venc0_dma { + status = "okay"; +}; + +&ps_venc0_pipe4 { + status = "okay"; +}; + +&ps_venc0_pipe5 { + status = "okay"; +}; + +&ps_venc0_me0 { + status = "okay"; +}; + +&ps_venc0_me1 { + status = "okay"; +}; + +&ave0_dart0 { + status = "okay"; +}; + +&ave0_dart1 { + status = "okay"; +}; + +&ave { + status = "okay"; +}; + +#include "t6040-j616s-usb3.dtsi" + +/* External display chain, all disabled: the gate in drivers/soc/apple/display-gate.c enables it. */ +#include "t6040-j616s-extdisp.dtsi" +#include "t6040-j616s-extdisp-mem-loader.dtsi" +#include "t6040-j616s-extdisp-hdmi.dtsi" +#include "t6040-j616s-extdisp-usbc.dtsi" +#include "t6040-j616s-dpaudio.dtsi" +/* Additional Abbey PMICs on the two independent NUB SPMI buses. */ +&nub_spmi1 { + status = "okay"; + + pmic@e { + compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; + reg = <0xe SPMI_USID>; + }; +}; + +&nub_spmi2 { + status = "okay"; + + pmic@e { + compatible = "apple,abbey-pmic", "apple,spmi-nvmem"; + reg = <0xe SPMI_USID>; + }; +}; + +&ps_nub_spmi_ahb { + status = "okay"; +}; + +&ps_nub_spmi1 { + status = "okay"; +}; + +&ps_nub_spmi2 { + status = "okay"; +}; + +&pinctrl { + sd_clkreq_pins: sd-clkreq-pins { + pinmux = ; + }; +}; + +&pcie0 { + interrupts = , + ; + /* Widen bus-range only with the SD power/port ownership handoff. */ + iommu-map = <0x100 &pcie_dart 1 2>, <0x200 &apcie1_dart 1 1>; + + port01: pci@1,0 { + device_type = "pci"; + reg = <0x800 0 0 0 0>; + reset-gpios = <&pinctrl 5 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&sd_clkreq_pins>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port01 0 0 0 0>, + <0 0 0 2 &port01 0 0 0 1>, + <0 0 0 3 &port01 0 0 0 2>, + <0 0 0 4 &port01 0 0 0 3>; + /* The SD power switch needs an SMC service binding. */ + status = "disabled"; + }; +}; + +/* + * The SMC owns automatic fan policy. Drop the host fan trip/map instances + * after the board's thermal descriptions, retaining all passive and critical + * protection. Keep this final policy separate from ladder descriptions so + * independent ladder updates cannot restore autonomous SMC writes. + */ + +#define J616S_DELETE_FAN_TRIPS \ + /delete-node/ trip-fan1; \ + /delete-node/ trip-fan2; \ + /delete-node/ trip-fan3; \ + /delete-node/ trip-fan4; \ + /delete-node/ trip-fan5; + +/* Some CPU ladders also map full fan speed to the passive trip. */ +#define J616S_DELETE_FAN_MAPS \ + /delete-node/ map-fan1; \ + /delete-node/ map-fan2; \ + /delete-node/ map-fan3; \ + /delete-node/ map-fan4; \ + /delete-node/ map-fan5; \ + /delete-node/ map-fan6; + +/ { + thermal-zones { + cpu-die-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + + gpu0-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + + gpu1-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + + gpu2-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + + gpu3-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + + gpu4-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + + gpu5-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + + gpu6-thermal { + trips { + J616S_DELETE_FAN_TRIPS + }; + + cooling-maps { + J616S_DELETE_FAN_MAPS + }; + }; + }; +}; + +#undef J616S_DELETE_FAN_TRIPS +#undef J616S_DELETE_FAN_MAPS + +/* + * Proven on the J616s (MobileNetV1 top-1 16/16). The ANE stays idle until + * the loader fills and enables the two preload reservations above. + */ +&ane0_dart0 { + status = "okay"; +}; + +&ane0_dart1 { + status = "okay"; +}; + +&ane0_dart2 { + status = "okay"; +}; + +&ane { + memory-region = <&ane_fw_text>, <&ane_fw_data>; + memory-region-names = "firmware-text", "firmware-data"; + status = "okay"; +}; From dc054fb02bfb002e41f2dd8f43a3dce2983cf537 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 10:05:02 +1100 Subject: [PATCH 15/30] arm64: dts: apple: t6040: Name the M4 Pro MacBook Pros without a date Our model strings name the machine without its release date. Use "Apple MacBook Pro (14-inch, M4 Pro)" and "Apple MacBook Pro (16-inch, M4 Pro)". The boot loader already drops the date when it builds the SMBIOS tables, so About pages do not change. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j614s.dts | 2 +- arch/arm64/boot/dts/apple/t6040-j616s.dts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6040-j614s.dts b/arch/arm64/boot/dts/apple/t6040-j614s.dts index 1b0a6868fad82..4373a41b6f430 100644 --- a/arch/arm64/boot/dts/apple/t6040-j614s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j614s.dts @@ -14,7 +14,7 @@ / { compatible = "apple,j614s", "apple,t6040", "apple,arm-platform"; - model = "Apple MacBook Pro (14-inch, M4 Pro, Nov 2024)"; + model = "Apple MacBook Pro (14-inch, M4 Pro)"; }; /* ADT /arm-io/wlan module-instance */ diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index a81644c6cfad5..5895e848d99ce 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -14,7 +14,7 @@ / { compatible = "apple,j616s", "apple,t6040", "apple,arm-platform"; - model = "Apple MacBook Pro (16-inch, M4 Pro, Nov 2024)"; + model = "Apple MacBook Pro (16-inch, M4 Pro)"; }; /* ADT /arm-io/wlan module-instance */ From ebc55caa6661b71be46afef1a7f2d9fdb041b364 Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:04:32 +1100 Subject: [PATCH 16/30] accel/ane: snapshot H16 startup rings and scratch on timeout --- drivers/accel/ane/ane_h16_boot.h | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/accel/ane/ane_h16_boot.h b/drivers/accel/ane/ane_h16_boot.h index 373d07b70885f..89191f275dd75 100644 --- a/drivers/accel/ane/ane_h16_boot.h +++ b/drivers/accel/ane/ane_h16_boot.h @@ -568,6 +568,38 @@ static inline int ane_h16_scratch_boot(struct ane_h16_boot *b) return ret; } +/* Read-only evidence: never acknowledge an IRQ or change a cursor on timeout. */ +static inline void ane_h16_startup_timeout(struct ane_h16_boot *b) +{ + struct ane_fw_transport *t = &b->transport; + const struct ane_fw_channel *c = &t->channels[ANE_FW_SHAREDMALLOC]; + unsigned int i; + u8 *p; + + b->ops->log(b->ctx, "startup timeout IRQ/polls", + t->ops->read32(t->ctx, t->soc->irq_status), t->polls); + b->ops->log(b->ctx, "startup SHAREDMALLOC head/slots", + t->head[ANE_FW_SHAREDMALLOC], c->slots); + /* Geometry was validated before startup; include slots behind the cursor. */ + for (i = 0; i < c->slots; i++) { + p = (u8 *)t->ipc.cpu + c->offset + i * ANE_FW_SLOT_SIZE; + t->ops->read_barrier(t->ctx); + b->ops->log(b->ctx, "startup SHAREDMALLOC slot/word0", i, + t->ops->load64(t->ctx, p)); + b->ops->log(b->ctx, "startup SHAREDMALLOC word1/word2", + t->ops->load64(t->ctx, p + 8), + t->ops->load64(t->ctx, p + 16)); + } + p = ane_fw_transport_slot(t, ANE_FW_IO); + t->ops->read_barrier(t->ctx); + b->ops->log(b->ctx, "startup IO head/word0", t->head[ANE_FW_IO], + t->ops->load64(t->ctx, p)); + b->ops->log(b->ctx, "startup IO word1/word2", t->ops->load64(t->ctx, p + 8), + t->ops->load64(t->ctx, p + 16)); + for (i = 0; i < 8; i++) + b->ops->log(b->ctx, "startup timeout scratch", i, ane_h16_read(b, i)); +} + static inline int ane_h16_startup(struct ane_h16_boot *b) { const struct ane_h16_build *f = b->build; @@ -588,6 +620,8 @@ static inline int ane_h16_startup(struct ane_h16_boot *b) reply = (u8 *)b->pool.cpu + (b->transport.pending[ANE_FW_IO].dva - b->pool.dva); if (ret) { + if (ret == -ETIMEDOUT) + ane_h16_startup_timeout(b); if (ret == -EREMOTEIO) b->ops->log(b->ctx, "startup refusal ID/ack", f->startup[i].id, get_unaligned_le16(reply + 6)); From 1f6216b416d18e3f605619d9cb2fc9f87e8b8c83 Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:09:47 +1100 Subject: [PATCH 17/30] accel/ane: test H16 startup timeout evidence and retained ownership --- drivers/accel/ane/ane_h16_boot_test.c | 49 +++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/drivers/accel/ane/ane_h16_boot_test.c b/drivers/accel/ane/ane_h16_boot_test.c index 32172b41f44da..aba9e256ea18a 100644 --- a/drivers/accel/ane/ane_h16_boot_test.c +++ b/drivers/accel/ane/ane_h16_boot_test.c @@ -28,6 +28,10 @@ struct ane_h16_sim { u32 pins; u32 quarantines; u32 allocations; + u32 timeout_slots; + u32 timeout_bodies; + u32 timeout_scratch; + u64 timeout_head; u32 lock; u32 fault; u32 channel_request; @@ -45,6 +49,7 @@ struct ane_h16_sim { bool wrong_table; bool stale_table; bool no_clear; + bool no_startup_reply; bool shared_pending; bool shared_replied; bool ce_timeout; @@ -213,6 +218,8 @@ static void sim_write32(void *ctx, enum ane_h16_window w, u32 off, u32 value) if (s->command_count < 4) KUNIT_EXPECT_EQ(s->test, tx->id, f->startup[s->command_count].id); s->command_count++; + if (s->no_startup_reply) + return; if (tx->id == 3 && s->lifecycle_mode) { u8 *slot = ane_fw_transport_slot(t, ANE_FW_SHAREDMALLOC); u64 dva = s->lifecycle_mode == 2 ? 0 : s->build.shared_dva; @@ -362,6 +369,16 @@ static void sim_mailbox(void *ctx) static void sim_log(void *ctx, const char *item, u64 a, u64 b) { + struct ane_h16_sim *s = ctx; + + if (!strcmp(item, "startup SHAREDMALLOC head/slots")) + s->timeout_head = a; + if (!strcmp(item, "startup SHAREDMALLOC slot/word0")) + s->timeout_slots++; + if (!strcmp(item, "startup SHAREDMALLOC word1/word2")) + s->timeout_bodies++; + if (!strcmp(item, "startup timeout scratch")) + s->timeout_scratch++; } static void sim_quarantine(void *ctx, int error) @@ -696,6 +713,37 @@ static void ane_h16_scratch3_timeout_test(struct kunit *test) KUNIT_EXPECT_EQ(test, s->command_count, 0U); } +/* A timeout snapshots the ring without answering, moving it or trying again. */ +static void ane_h16_startup_timeout_test(struct kunit *test) +{ + struct ane_h16_sim *s = test->priv; + struct ane_fw_transport *t = &s->boot.transport; + u8 before[ANE_FW_SLOT_SIZE]; + u8 *slot; + + KUNIT_ASSERT_EQ(test, ane_h16_run(&s->boot, ANE_H16_BOOT), 0); + slot = ane_fw_transport_slot(t, ANE_FW_SHAREDMALLOC); + /* An earlier reply still belongs to firmware; the host must leave it. */ + put_unaligned_le64(s->build.shared_dva | 1, slot); + put_unaligned_le64(0, slot + 8); + put_unaligned_le64(1, slot + 16); + memcpy(before, slot, sizeof(before)); + s->no_startup_reply = true; + s->build.command_timeout_ns = 2000000; + KUNIT_EXPECT_EQ(test, ane_h16_run(&s->boot, ANE_H16_STARTUP), -ETIMEDOUT); + KUNIT_EXPECT_TRUE(test, s->boot.quarantined); + KUNIT_EXPECT_EQ(test, s->command_count, 1U); + KUNIT_EXPECT_TRUE(test, t->pending[ANE_FW_IO].active); + KUNIT_EXPECT_EQ(test, t->head[ANE_FW_SHAREDMALLOC], 0U); + KUNIT_EXPECT_EQ(test, s->timeout_head, 0ULL); + KUNIT_EXPECT_EQ(test, s->timeout_slots, t->channels[ANE_FW_SHAREDMALLOC].slots); + KUNIT_EXPECT_EQ(test, s->timeout_bodies, s->timeout_slots); + KUNIT_EXPECT_EQ(test, s->timeout_scratch, 8U); + KUNIT_EXPECT_MEMEQ(test, before, slot, sizeof(before)); + KUNIT_EXPECT_EQ(test, ane_h16_run(&s->boot, ANE_H16_STARTUP), -EBUSY); + KUNIT_EXPECT_EQ(test, s->command_count, 1U); +} + /* Stages advance one write at a time, each at most once, in one firmware session. */ static void ane_h16_incremental_test(struct kunit *test) { @@ -931,6 +979,7 @@ static struct kunit_case ane_h16_boot_cases[] = { KUNIT_CASE(ane_h16_ce_timeout_test), KUNIT_CASE(ane_h16_zero_tick_test), KUNIT_CASE(ane_h16_scratch3_timeout_test), + KUNIT_CASE(ane_h16_startup_timeout_test), {} }; From d8790434a13ffac54c07eb0bc412bb228a1d63cd Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:13:33 +1100 Subject: [PATCH 18/30] accel/ane: qualify shared-ring service with the notified batch Fresh SHAREDMALLOC slots and allocation requests both have a zero header. Polling their body without the matching channel notification can answer while firmware is still publishing the request. Snapshot the notified batch after the exact IRQ acknowledgment, before sending any answer, and retain a count across service calls. This preserves coalesced requests and prevents a later request from borrowing an old notification. Keep the existing absolute deadline and reply barriers. Add KUnit cases for delayed and unrelated notifications, batch wraparound, descriptor-selected IRQ bits and acknowledgment-before-read ordering. The boot simulator now publishes real notifications instead of relying on unconditional scanning. --- Documentation/accel/apple-ane.rst | 7 +- drivers/accel/ane/ane_fw_transport.h | 43 +++++++++++ drivers/accel/ane/ane_fw_transport_test.c | 89 +++++++++++++++++++++++ drivers/accel/ane/ane_h16_boot_test.c | 9 ++- 4 files changed, 146 insertions(+), 2 deletions(-) diff --git a/Documentation/accel/apple-ane.rst b/Documentation/accel/apple-ane.rst index 80feec377a251..63eed84a36287 100644 --- a/Documentation/accel/apple-ane.rst +++ b/Documentation/accel/apple-ane.rst @@ -334,7 +334,12 @@ cannot distinguish a control request; its lookup request layout remains unproved. No control reply is invented. IRQ service reads pending once, rejects bits above 7 and writes the exact -nonzero snapshot to acknowledge. The wait helper services one allocation at +nonzero snapshot to acknowledge. Only a SHAREDMALLOC notification authorizes +allocation service: a fresh slot and an allocation request both have a zero +header, so the body becoming visible does not establish publication. After +acknowledging, the transport captures the number of queued entries before +answering any of them. This handles coalesced notifications without admitting +a later request using an earlier notification. The wait helper services one allocation at a time and checks the original absolute monotonic deadline, including time spent inside the allocation callback. Servicing never starts a new timeout. diff --git a/drivers/accel/ane/ane_fw_transport.h b/drivers/accel/ane/ane_fw_transport.h index 99279b5d74bec..dfff6ed184adc 100644 --- a/drivers/accel/ane/ane_fw_transport.h +++ b/drivers/accel/ane/ane_fw_transport.h @@ -118,6 +118,8 @@ struct ane_fw_transport { struct ane_fw_channel channels[ANE_FW_CHANNELS]; struct ane_fw_transaction pending[ANE_FW_CHANNELS]; u32 head[ANE_FW_CHANNELS]; + /* SHAREDMALLOC entries captured after acknowledging their notification. */ + u32 shared_ready; u8 table[ANE_FW_TABLE_SIZE]; size_t table_offset; size_t pool_used; @@ -266,6 +268,8 @@ static inline void *ane_fw_transport_slot(struct ane_fw_transport *t, unsigned i static inline void ane_fw_transport_advance(struct ane_fw_transport *t, unsigned int ch) { t->head[ch] = (t->head[ch] + 1) % t->channels[ch].slots; + if (ch == ANE_FW_SHAREDMALLOC) + t->shared_ready--; } static inline void ane_fw_transport_doorbell(struct ane_fw_transport *t, unsigned int ch) @@ -655,12 +659,15 @@ ane_fw_shared_reusable(const struct ane_fw_transport *t, size_t size, u64 tag) return t->shared_count; } +static inline int ane_fw_transport_irq(struct ane_fw_transport *t, u32 *pending); + static inline int ane_fw_transport_shared(struct ane_fw_transport *t) { struct ane_fw_surface s = {}; u64 word0, word1, word2; size_t size; unsigned int i; + u32 pending; u8 *p; int ret = ane_fw_transport_check(t); @@ -668,6 +675,13 @@ static inline int ane_fw_transport_shared(struct ane_fw_transport *t) return ret; if (!t->armed) return -EINVAL; + if (!t->shared_ready) { + ret = ane_fw_transport_irq(t, &pending); + if (ret) + return ret; + if (!t->shared_ready) + return -EAGAIN; + } p = ane_fw_transport_slot(t, ANE_FW_SHAREDMALLOC); word0 = t->ops->load64(t->ctx, p); t->ops->read_barrier(t->ctx); @@ -753,6 +767,35 @@ static inline int ane_fw_transport_irq(struct ane_fw_transport *t, u32 *pending) *pending = value; if (value) t->ops->write32(t->ctx, t->soc->irq_ack, value); + if (t->armed && (value & BIT(t->channels[ANE_FW_SHAREDMALLOC].bit))) { + const struct ane_fw_channel *c = &t->channels[ANE_FW_SHAREDMALLOC]; + u32 n; + + /* + * An allocation's header is zero, including on a never-used slot. + * Its body can be visible before firmware posts the notification; + * answering then races the producer's publication/wakeup sequence. + * Capture the notified batch BEFORE replying to any entry. Draining + * until empty after a reply could pick up the next unnotified request. + * One pending bit may cover several already queued entries. + */ + t->ops->read_barrier(t->ctx); + for (n = 0; n < c->slots; n++) { + u32 slot = (t->head[ANE_FW_SHAREDMALLOC] + n) % c->slots; + u8 *p = (u8 *)t->ipc.cpu + c->offset + slot * ANE_FW_SLOT_SIZE; + u64 word0 = t->ops->load64(t->ctx, p); + u64 word1, word2; + + if (word0 & 1) + break; + t->ops->read_barrier(t->ctx); + word1 = t->ops->load64(t->ctx, p + 8); + word2 = t->ops->load64(t->ctx, p + 16); + if (!word0 && !word1 && !word2) + break; + } + t->shared_ready = n; + } return 0; } diff --git a/drivers/accel/ane/ane_fw_transport_test.c b/drivers/accel/ane/ane_fw_transport_test.c index 710845960b5d1..ddd1378c80c29 100644 --- a/drivers/accel/ane/ane_fw_transport_test.c +++ b/drivers/accel/ane/ane_fw_transport_test.c @@ -92,6 +92,8 @@ static void ane_test_write32(void *ctx, u32 reg, u32 value) f->last_reg = reg; f->last_value = value; ane_test_access(f, 'o', NULL, value); + if (reg == f->soc.irq_ack) + f->irq &= ~value; } static u64 ane_test_now(void *ctx) @@ -255,6 +257,7 @@ static void ane_test_shared_request(struct ane_transport_test *f, u64 w0, u64 w1 put_unaligned_le64(w0, p); put_unaligned_le64(w1, p + 8); put_unaligned_le64(w2, p + 16); + f->irq |= BIT(f->t.channels[ANE_FW_SHAREDMALLOC].bit); } static unsigned int ane_test_writes(struct ane_transport_test *f) @@ -722,6 +725,90 @@ static void ane_transport_shared_test(struct kunit *test) KUNIT_EXPECT_EQ(test, f->t.shared_count, 2U); } +/* The body of a fresh zero-header request is not its publication boundary. */ +static void ane_transport_shared_notification_test(struct kunit *test) +{ + struct ane_transport_test *f = test->priv; + u32 bit = BIT(f->t.channels[ANE_FW_SHAREDMALLOC].bit); + u8 *slot; + + KUNIT_ASSERT_EQ(test, ane_fw_transport_arm(&f->t), 0); + ane_test_shared_request(f, 0, SZ_16K, 0x1234); + slot = ane_test_slot(f, ANE_FW_SHAREDMALLOC); + /* Firmware filled the slot but has not published its notification yet. */ + f->irq = 0; + f->count = 0; + KUNIT_EXPECT_EQ(test, ane_fw_transport_shared(&f->t), -EAGAIN); + KUNIT_EXPECT_EQ(test, ane_test_writes(f), 0U); + KUNIT_EXPECT_EQ(test, f->allocs, 0U); + KUNIT_EXPECT_EQ(test, get_unaligned_le64(slot), 0ULL); + /* An unrelated channel notification cannot authorize an answer either. */ + f->irq = BIT(4); + KUNIT_EXPECT_EQ(test, ane_fw_transport_shared(&f->t), -EAGAIN); + KUNIT_EXPECT_EQ(test, f->allocs, 0U); + f->irq = bit; + f->count = 0; + KUNIT_ASSERT_EQ(test, ane_fw_transport_shared(&f->t), 0); + KUNIT_EXPECT_EQ(test, f->allocs, 1U); + KUNIT_EXPECT_EQ(test, f->irq, 0U); + KUNIT_EXPECT_EQ(test, f->t.shared_ready, 0U); + /* A later request cannot borrow the first request's notification. */ + ane_test_shared_request(f, 0, SZ_16K, 0x5678); + f->irq = 0; + KUNIT_EXPECT_EQ(test, ane_fw_transport_shared(&f->t), -EAGAIN); + KUNIT_EXPECT_EQ(test, f->allocs, 1U); + KUNIT_EXPECT_EQ(test, f->t.head[ANE_FW_SHAREDMALLOC], 1U); + f->irq = bit; + KUNIT_ASSERT_EQ(test, ane_fw_transport_shared(&f->t), 0); + KUNIT_EXPECT_EQ(test, f->allocs, 2U); + KUNIT_EXPECT_EQ(test, f->t.head[ANE_FW_SHAREDMALLOC], 0U); +} + +/* One bit can announce a batch, including a batch that wraps the ring. */ +static void ane_transport_shared_batch_test(struct kunit *test) +{ + struct ane_transport_test *f = test->priv; + u8 *desc = (u8 *)f->ipc.cpu + ANE_FW_SHAREDMALLOC * ANE_FW_DESCRIPTOR_SIZE; + u32 pending, i, ack = 0, first_read = 0; + u8 *slot; + + /* The descriptor, rather than logical channel 5, selects the IRQ bit. */ + put_unaligned_le32(3, desc + 0x44); + KUNIT_ASSERT_EQ(test, ane_test_init_transport(f), 0); + KUNIT_ASSERT_EQ(test, ane_fw_transport_arm(&f->t), 0); + f->t.head[ANE_FW_SHAREDMALLOC] = 1; + for (i = 0; i < 2; i++) { + slot = (u8 *)f->ipc.cpu + f->t.channels[ANE_FW_SHAREDMALLOC].offset + + i * ANE_FW_SLOT_SIZE; + put_unaligned_le64(SZ_16K, slot + 8); + put_unaligned_le64(0x1234 + i, slot + 16); + } + slot = ane_test_slot(f, ANE_FW_SHAREDMALLOC); + f->irq = BIT(3); + f->count = 0; + KUNIT_ASSERT_EQ(test, ane_fw_transport_irq(&f->t, &pending), 0); + KUNIT_EXPECT_EQ(test, pending, (u32)BIT(3)); + KUNIT_EXPECT_EQ(test, f->t.shared_ready, 2U); + /* Exact IRQ acknowledgment and a DMA read barrier precede slot reads. */ + for (i = 0; i < f->count; i++) { + if (f->access[i].kind == 'o') + ack = i; + if (f->access[i].kind == 'r' && f->access[i].p == slot) + first_read = i; + } + KUNIT_EXPECT_LT(test, ack, first_read); + KUNIT_EXPECT_EQ(test, f->access[ack].value, (u64)BIT(3)); + KUNIT_EXPECT_EQ(test, f->access[ack + 1].kind, 'R'); + KUNIT_ASSERT_EQ(test, ane_fw_transport_shared(&f->t), 0); + KUNIT_EXPECT_EQ(test, f->t.head[ANE_FW_SHAREDMALLOC], 0U); + KUNIT_EXPECT_EQ(test, f->t.shared_ready, 1U); + KUNIT_ASSERT_EQ(test, ane_fw_transport_shared(&f->t), 0); + KUNIT_EXPECT_EQ(test, f->t.head[ANE_FW_SHAREDMALLOC], 1U); + KUNIT_EXPECT_EQ(test, f->t.shared_ready, 0U); + KUNIT_EXPECT_EQ(test, f->allocs, 2U); + KUNIT_EXPECT_EQ(test, ane_fw_transport_shared(&f->t), -EAGAIN); +} + static void ane_transport_shared_rejections_test(struct kunit *test) { static const u64 tuples[][3] = { @@ -1403,6 +1490,8 @@ static struct kunit_case ane_transport_cases[] = { KUNIT_CASE(ane_transport_report_word2_test), KUNIT_CASE(ane_transport_presence_test), KUNIT_CASE(ane_transport_shared_test), + KUNIT_CASE(ane_transport_shared_notification_test), + KUNIT_CASE(ane_transport_shared_batch_test), KUNIT_CASE(ane_transport_shared_rejections_test), KUNIT_CASE(ane_transport_allocator_rejections_test), KUNIT_CASE(ane_transport_shared_overlap_test), diff --git a/drivers/accel/ane/ane_h16_boot_test.c b/drivers/accel/ane/ane_h16_boot_test.c index aba9e256ea18a..581bd4b41eee6 100644 --- a/drivers/accel/ane/ane_h16_boot_test.c +++ b/drivers/accel/ane/ane_h16_boot_test.c @@ -11,6 +11,7 @@ struct ane_h16_sim { struct ane_h16_mmio mmio; struct ane_fw_platform soc; u32 scratch[8]; + u32 irq; u32 dapf[SZ_16K / 4]; u32 dart_params; u32 dapf_reads; @@ -113,7 +114,7 @@ static u32 sim_read32(void *ctx, enum ane_h16_window w, u32 off) if (off >= s->soc.scratch && off < s->soc.scratch + 32) return s->scratch[(off - s->soc.scratch) / 4]; if (off == s->soc.irq_status) - return 0; + return s->irq; } return 0xa5a5a5a5; } @@ -172,6 +173,10 @@ static void sim_write32(void *ctx, enum ane_h16_window w, u32 off, u32 value) return; } KUNIT_EXPECT_TRUE(s->test, w == ANE_H16_ENGINE || w == ANE_H16_XL); + if (w == ANE_H16_ENGINE && off == s->soc.irq_ack) { + s->irq &= ~value; + return; + } if (w == ANE_H16_ENGINE && off == f->cpu_control) { s->cpu = value; if (value == f->run && s->fault_after_run) @@ -231,6 +236,7 @@ static void sim_write32(void *ctx, enum ane_h16_window w, u32 off, u32 value) put_unaligned_le64(0, slot + 8); put_unaligned_le64(s->lifecycle_mode == 2 ? 1 : 0, slot + 16); s->lifecycle_requested = true; + s->irq |= BIT(5); } else if (!tx->id) { u8 *slot = ane_fw_transport_slot(t, ANE_FW_SHAREDMALLOC); @@ -238,6 +244,7 @@ static void sim_write32(void *ctx, enum ane_h16_window w, u32 off, u32 value) put_unaligned_le64(SZ_16K, slot + 8); put_unaligned_le64(0, slot + 16); s->shared_pending = true; + s->irq |= BIT(5); } else { sim_reply(s); } From 877887a2086b92a958714fd053dec4905f753eb2 Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:15:37 +1100 Subject: [PATCH 19/30] accel/ane: complete shared transport IRQ acknowledgments before ring reads Document the MMIO accessor ordering contract and give the H17 backend the full barrier already used by H16. Include the notified batch count in H16 startup timeout evidence. --- drivers/accel/ane/ane_fw_cold.c | 2 ++ drivers/accel/ane/ane_fw_transport.h | 1 + drivers/accel/ane/ane_h16_boot.h | 2 ++ 3 files changed, 5 insertions(+) diff --git a/drivers/accel/ane/ane_fw_cold.c b/drivers/accel/ane/ane_fw_cold.c index 6781ff1202711..6cda208333436 100644 --- a/drivers/accel/ane/ane_fw_cold.c +++ b/drivers/accel/ane/ane_fw_cold.c @@ -1026,6 +1026,8 @@ static void ane_fw_write32(void *ctx, u32 reg, u32 value) struct ane_fw_backend *b = ctx; ane_wr32(b, value, b->engine + reg); + if (reg == b->soc->irq_ack) + mb(); } static u64 ane_fw_now(void *ctx) diff --git a/drivers/accel/ane/ane_fw_transport.h b/drivers/accel/ane/ane_fw_transport.h index dfff6ed184adc..992f58618455c 100644 --- a/drivers/accel/ane/ane_fw_transport.h +++ b/drivers/accel/ane/ane_fw_transport.h @@ -80,6 +80,7 @@ struct ane_fw_transport_ops { void (*read_barrier)(void *ctx); void (*write_barrier)(void *ctx); u32 (*read32)(void *ctx, u32 reg); + /* Complete/order an irq_ack write before subsequent coherent ring reads. */ void (*write32)(void *ctx, u32 reg, u32 value); u64 (*now_ns)(void *ctx); /* Wait before the next poll of a wait that began waited_ns ago. */ diff --git a/drivers/accel/ane/ane_h16_boot.h b/drivers/accel/ane/ane_h16_boot.h index 89191f275dd75..e3c66b89e1265 100644 --- a/drivers/accel/ane/ane_h16_boot.h +++ b/drivers/accel/ane/ane_h16_boot.h @@ -580,6 +580,8 @@ static inline void ane_h16_startup_timeout(struct ane_h16_boot *b) t->ops->read32(t->ctx, t->soc->irq_status), t->polls); b->ops->log(b->ctx, "startup SHAREDMALLOC head/slots", t->head[ANE_FW_SHAREDMALLOC], c->slots); + b->ops->log(b->ctx, "startup SHAREDMALLOC ready/allocations", + t->shared_ready, t->shared_count); /* Geometry was validated before startup; include slots behind the cursor. */ for (i = 0; i < c->slots; i++) { p = (u8 *)t->ipc.cpu + c->offset + i * ANE_FW_SLOT_SIZE; From f800795d3077216b19aadd871b631d3cb887688a Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:18:22 +1100 Subject: [PATCH 20/30] accel/ane: inject the next request during shared allocation in KUnit --- drivers/accel/ane/ane_fw_transport_test.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/accel/ane/ane_fw_transport_test.c b/drivers/accel/ane/ane_fw_transport_test.c index ddd1378c80c29..b1d0c0f685b5d 100644 --- a/drivers/accel/ane/ane_fw_transport_test.c +++ b/drivers/accel/ane/ane_fw_transport_test.c @@ -41,6 +41,7 @@ struct ane_transport_test { int refused_error; int alloc_error; int alloc_bad; + bool post_next_unnotified; }; static void ane_test_access(struct ane_transport_test *f, char kind, const void *p, u64 value) @@ -115,6 +116,17 @@ static int ane_test_alloc(void *ctx, size_t size, u32 tag, struct ane_fw_surface f->allocs++; f->now += f->alloc_time; + if (f->post_next_unnotified) { + const struct ane_fw_channel *c = &f->t.channels[ANE_FW_SHAREDMALLOC]; + u32 next = (f->t.head[ANE_FW_SHAREDMALLOC] + 1) % c->slots; + u8 *p = (u8 *)f->ipc.cpu + c->offset + next * ANE_FW_SLOT_SIZE; + + /* Arrives after the batch snapshot, before the first reply. */ + put_unaligned_le64(0, p); + put_unaligned_le64(SZ_16K, p + 8); + put_unaligned_le64(0x5678, p + 16); + f->post_next_unnotified = false; + } if (f->alloc_error) return f->alloc_error; *s = (struct ane_fw_surface) { @@ -748,13 +760,12 @@ static void ane_transport_shared_notification_test(struct kunit *test) KUNIT_EXPECT_EQ(test, f->allocs, 0U); f->irq = bit; f->count = 0; + f->post_next_unnotified = true; KUNIT_ASSERT_EQ(test, ane_fw_transport_shared(&f->t), 0); KUNIT_EXPECT_EQ(test, f->allocs, 1U); KUNIT_EXPECT_EQ(test, f->irq, 0U); KUNIT_EXPECT_EQ(test, f->t.shared_ready, 0U); /* A later request cannot borrow the first request's notification. */ - ane_test_shared_request(f, 0, SZ_16K, 0x5678); - f->irq = 0; KUNIT_EXPECT_EQ(test, ane_fw_transport_shared(&f->t), -EAGAIN); KUNIT_EXPECT_EQ(test, f->allocs, 1U); KUNIT_EXPECT_EQ(test, f->t.head[ANE_FW_SHAREDMALLOC], 1U); From 0f8779a00b833370c5da19914bcd70d9d3ed678a Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:21:22 +1100 Subject: [PATCH 21/30] accel/ane: distinguish IRQ acknowledgments from refused ring replies in tests --- drivers/accel/ane/ane_fw_transport_test.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/accel/ane/ane_fw_transport_test.c b/drivers/accel/ane/ane_fw_transport_test.c index b1d0c0f685b5d..f1a69baa86630 100644 --- a/drivers/accel/ane/ane_fw_transport_test.c +++ b/drivers/accel/ane/ane_fw_transport_test.c @@ -849,7 +849,9 @@ static void ane_transport_shared_rejections_test(struct kunit *test) KUNIT_EXPECT_MEMEQ(test, f->refused, tuples[i], sizeof(f->refused)); KUNIT_EXPECT_EQ(test, f->refused_error, error); KUNIT_EXPECT_EQ(test, f->allocs, 0U); - KUNIT_EXPECT_EQ(test, ane_test_writes(f), 0U); + /* The notification is acknowledged; no ring reply is written. */ + KUNIT_EXPECT_EQ(test, ane_test_writes(f), 1U); + KUNIT_EXPECT_EQ(test, f->last_reg, f->soc.irq_ack); } } @@ -867,7 +869,9 @@ static void ane_transport_allocator_rejections_test(struct kunit *test) KUNIT_EXPECT_EQ_MSG(test, ane_fw_transport_shared(&f->t), -EPROTO, "allocator result %u", i); KUNIT_EXPECT_EQ(test, f->t.shared_count, 0U); - KUNIT_EXPECT_EQ(test, ane_test_writes(f), 0U); + /* The notification is acknowledged; no ring reply is written. */ + KUNIT_EXPECT_EQ(test, ane_test_writes(f), 1U); + KUNIT_EXPECT_EQ(test, f->last_reg, f->soc.irq_ack); } KUNIT_ASSERT_EQ(test, ane_test_init_transport(f), 0); KUNIT_ASSERT_EQ(test, ane_fw_transport_arm(&f->t), 0); @@ -1068,7 +1072,9 @@ static void ane_transport_free_test(struct kunit *test) ane_test_shared_request(f, dva, 0, 0); f->count = 0; KUNIT_EXPECT_EQ(test, ane_fw_transport_shared(&f->t), -ENOENT); - KUNIT_EXPECT_EQ(test, ane_test_writes(f), 0U); + /* The notification is acknowledged; no ring reply is written. */ + KUNIT_EXPECT_EQ(test, ane_test_writes(f), 1U); + KUNIT_EXPECT_EQ(test, f->last_reg, f->soc.irq_ack); } /* @@ -1333,7 +1339,9 @@ static void ane_transport_free_refusal_test(struct kunit *test) f->count = 0; KUNIT_EXPECT_EQ(test, ane_fw_transport_shared(&f->t), -EOPNOTSUPP); KUNIT_EXPECT_FALSE(test, f->t.shared_retired[0]); - KUNIT_EXPECT_EQ(test, ane_test_writes(f), 0U); + /* The notification is acknowledged; no ring reply is written. */ + KUNIT_EXPECT_EQ(test, ane_test_writes(f), 1U); + KUNIT_EXPECT_EQ(test, f->last_reg, f->soc.irq_ack); } /* Publish one EXECUTE on the data channel; return its flags word as copied. */ From 322048cb447da169bbf6866aa6ca86a8915ee702 Mon Sep 17 00:00:00 2001 From: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:21:48 +1100 Subject: [PATCH 22/30] accel/ane: explain the IRQ acknowledgment barrier --- drivers/accel/ane/ane_fw_cold.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/accel/ane/ane_fw_cold.c b/drivers/accel/ane/ane_fw_cold.c index 6cda208333436..acfc4cc5aac8c 100644 --- a/drivers/accel/ane/ane_fw_cold.c +++ b/drivers/accel/ane/ane_fw_cold.c @@ -1026,8 +1026,10 @@ static void ane_fw_write32(void *ctx, u32 reg, u32 value) struct ane_fw_backend *b = ctx; ane_wr32(b, value, b->engine + reg); - if (reg == b->soc->irq_ack) + if (reg == b->soc->irq_ack) { + /* Complete the snapshot acknowledgment before coherent ring reads. */ mb(); + } } static u64 ane_fw_now(void *ctx) From d46849554b512fe175f28ed8bf765a228d60ba54 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 23:05:30 +1100 Subject: [PATCH 23/30] drm/apple: afk: replace compact services re-announced after poweron Desktop DCP firmware announces every compact EPIC service again under a fresh interface after each DCP poweron, on every endpoint, without closing the previous interface. Only the hotplug endpoints 0x27-0x29 replaced such services in place, so on the J616s each s2idle resume filled one more slot on endpoint 0x2a (both DCPs) and 0x2b (internal DCP) until "too many enabled compact services" stopped new services from binding. Count iomfb poweron generations per DCP and record the generation on each compact service. A service announced with the same binding, name and unit as an enabled one from an earlier generation replaces it in place, on any endpoint. Services from the current generation stay, so DPAVCTRL's same-name, no-unit siblings still coexist, and the hotplug endpoints keep replacing within a generation as before. Extend the AFKV2 host test to 64 poweron generations over every compact endpoint on two DCPs, plus hotplug, sibling, identity and capacity cases. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/gpu/drm/apple/afk.c | 57 +++-- drivers/gpu/drm/apple/afk.h | 2 + drivers/gpu/drm/apple/dcp-internal.h | 2 + drivers/gpu/drm/apple/iomfb_template.c | 2 + .../drivers/gpu/apple-afkv2/Makefile | 2 +- .../selftests/drivers/gpu/apple-afkv2/README | 11 + .../drivers/gpu/apple-afkv2/compact.c | 223 ++++++++++++++++++ .../gpu/apple-afkv2/extract-compact.py | 24 ++ .../gpu/apple-afkv2/run-afkv2-tests.sh | 4 +- 9 files changed, 302 insertions(+), 25 deletions(-) create mode 100644 tools/testing/selftests/drivers/gpu/apple-afkv2/compact.c create mode 100644 tools/testing/selftests/drivers/gpu/apple-afkv2/extract-compact.py diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 688b2d2f5c622..77a7d06fe45b7 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -728,33 +728,40 @@ afk_compact_slot(struct apple_dcp_afkep *ep, const struct apple_epic_service_ops *ops, const char *name, s64 unit) { + u32 gen = READ_ONCE(ep->dcp->afk_power_gen); struct apple_epic_service *service; unsigned long flags; - bool free; + bool hotplug, free; u32 i; /* - * The DPDEV, DPAVSERV and AV services are announced again under a - * fresh interface after every external hotplug, without a teardown - * for the previous one: replace the same logical service in place. - * Other endpoints (DPAVCTRL) announce several services with the same - * name and no unit, so the name is no key there. + * The firmware announces every compact service again under a fresh + * interface after a DCP poweron, on every endpoint, without closing + * the previous one. The DPDEV, DPAVSERV and AV endpoints (0x27-0x29) + * also do so after an external hotplug without a poweron. Replace an + * enabled service with the same binding, name and unit in place when + * it comes from an earlier poweron, or from any announcement on the + * hotplug endpoints. Services announced since the latest poweron stay: + * DPAVCTRL announces several services with the same name and no unit + * in one generation, and those are siblings, not replacements. */ - if (ep->endpoint >= 0x27 && ep->endpoint <= 0x29) { - for (i = 0; i < ep->num_channels; i++) { - service = &ep->services[i]; - if (service->enabled && service->ops == ops && - service->unit == unit && !strcmp(service->name, name)) { - dev_info(ep->dcp->dev, - "AFK[ep:%02x]: replacing compact service %s interface %u\n", - ep->endpoint, name, service->channel); - afk_remove_service_debugfs(service); - afk_service_retire(service); - if (service->ops->teardown) - service->ops->teardown(service); - return service; - } - } + hotplug = ep->endpoint >= 0x27 && ep->endpoint <= 0x29; + for (i = 0; i < ep->num_channels; i++) { + service = &ep->services[i]; + if (!service->enabled || service->ops != ops || + service->unit != unit || strcmp(service->name, name)) + continue; + if (!hotplug && service->power_gen == gen) + continue; + dev_info(ep->dcp->dev, + "AFK[ep:%02x]: replacing compact service %s interface %u\n", + ep->endpoint, name, service->channel); + afk_remove_service_debugfs(service); + afk_service_retire(service); + if (service->ops->teardown) + service->ops->teardown(service); + service->power_gen = gen; + return service; } for (i = 0; i < ep->num_channels; i++) { @@ -763,13 +770,17 @@ afk_compact_slot(struct apple_dcp_afkep *ep, free = !service->enabled && !service->users && bitmap_empty(service->cmd_map, MAX_PENDING_CMDS); spin_unlock_irqrestore(&service->lock, flags); - if (free) + if (free) { + service->power_gen = gen; return service; + } } if (ep->num_channels >= AFK_MAX_CHANNEL) return NULL; - return &ep->services[ep->num_channels++]; + service = &ep->services[ep->num_channels++]; + service->power_gen = gen; + return service; } static void afk_recv_handle_compact_init(struct apple_dcp_afkep *ep, diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 291ee12402292..a4f2afb38588f 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -76,6 +76,8 @@ struct apple_epic_service { /* Compact EPIC (desktop 26.x): the announced identity and OPEN state. */ char name[33]; s64 unit; + /* DCP poweron generation that announced this compact service. */ + u32 power_gen; bool defer_start; bool started; diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index f01925807d804..65daae9a3f5dc 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -414,6 +414,8 @@ struct apple_dcp { /* firmware version and compatible firmware version */ enum dcp_firmware_version fw_compat; + /* Bumped by every iomfb poweron; see afk_compact_slot(). */ + u32 afk_power_gen; bool external; /* * T8122 dcpext: the panel's DRM device never waits on this processor. diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index ba283faf6c1a3..2d7268f84e290 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -2200,6 +2200,8 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) u32 handle; #endif dev_info(dcp->dev, "dcp_poweron() starting\n"); + /* The firmware announces its compact services again after poweron. */ + WRITE_ONCE(dcp->afk_power_gen, dcp->afk_power_gen + 1); #if DCP_FW_H17(DCP_FW_VER) if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/Makefile b/tools/testing/selftests/drivers/gpu/apple-afkv2/Makefile index 95677dedc897e..b593b96456b4d 100644 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/Makefile +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/Makefile @@ -4,6 +4,6 @@ export HOSTCC export AFKV2_SOURCE_ROOT := $(abspath ../../../../../..) TEST_PROGS := run-afkv2-tests.sh test-admission.py -TEST_FILES := codec.c owned-prefix.c owned-suffix.c extract-owned.py inline-edid.c extract-service.py extract-hpd.py extract-send.py extract-edid.py extract-ring.py extract-notify.py host-include +TEST_FILES := codec.c owned-prefix.c owned-suffix.c extract-owned.py inline-edid.c extract-service.py extract-hpd.py extract-send.py extract-edid.py extract-ring.py extract-notify.py extract-compact.py compact.c host-include include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/README b/tools/testing/selftests/drivers/gpu/apple-afkv2/README index bffcd55fba642..0b0423fbb8e4e 100644 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/README +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/README @@ -43,3 +43,14 @@ consumes 192 bytes at a 64-byte stride, and needs a 256-byte empty ring. The ring-admission block is extracted from the real transmitter to check this. The late-publication notifier is extracted to test asynchronous hotplug queueing and connector-registration, quiesce, routing and legacy guards. + +The compact slot allocator is extracted to model DCP poweron cycles without +service teardown, on every compact endpoint (0x20-0x2f) and both DCPs, for +64 generations. A service re-announced after a poweron replaces the same +name, unit and binding in place, so the slot count stays bounded; the +hotplug endpoints 0x27-0x29 also replace within a generation. DPAVCTRL's +same-name, no-unit siblings announced in one generation stay separate, as +do different identities. Free-slot capacity, reader pins and pending +commands are checked +separately. Publication and teardown are mocked; this does not test firmware +power transitions or concurrent service users. diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/compact.c b/tools/testing/selftests/drivers/gpu/apple-afkv2/compact.c new file mode 100644 index 0000000000000..815748791fd1d --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/compact.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Host fixture: the slot allocator is extracted from afk.c at build time. */ +#undef NDEBUG +#include +#include +#include +#include +#include + +typedef uint32_t u32; +typedef int64_t s64; +#define MAX_PENDING_CMDS 8 +#define READ_ONCE(x) (x) +#define dev_info(...) do { } while (0) +#define spin_lock_irqsave(lock, flags) do { (void)(lock); (flags) = 0; } while (0) +#define spin_unlock_irqrestore(lock, flags) do { (void)(lock); (void)(flags); } while (0) +#define bitmap_empty(map, bits) ((void)(bits), *(map) == 0) + +struct apple_epic_service; +struct apple_epic_service_ops { + void (*teardown)(struct apple_epic_service *service); +}; + +struct apple_epic_service { + bool enabled, torndown, debugfs; + unsigned int users, channel, lock; + unsigned long cmd_map[1]; + const struct apple_epic_service_ops *ops; + char name[33]; + s64 unit; + u32 power_gen; +}; + +struct apple_dcp { + void *dev; + u32 afk_power_gen; +}; + +struct apple_dcp_afkep { + struct apple_dcp *dcp; + u32 endpoint, num_channels; + struct apple_epic_service services[AFK_MAX_CHANNEL]; +}; + +static unsigned int teardowns; + +static void afk_remove_service_debugfs(struct apple_epic_service *service) +{ + assert(service->enabled && service->debugfs); + service->debugfs = false; +} + +static void afk_service_retire(struct apple_epic_service *service) +{ + assert(service->enabled && !service->debugfs); + service->enabled = false; + service->torndown = true; +} + +static void teardown(struct apple_epic_service *service) +{ + assert(!service->enabled && service->torndown && !service->debugfs); + teardowns++; +} + +/* PRODUCTION_SLOT */ + +static const struct apple_epic_service_ops ops = { .teardown = teardown }; +static const struct apple_epic_service_ops other_ops; + +/* Model publication after allocation; poweroff performs no AFK teardown. */ +static struct apple_epic_service * +announce(struct apple_dcp_afkep *ep, const char *name, s64 unit, + unsigned int interface) +{ + struct apple_epic_service *service = afk_compact_slot(ep, &ops, name, unit); + + assert(service); + service->ops = &ops; + strcpy(service->name, name); + service->unit = unit; + service->channel = interface; + service->enabled = true; + service->torndown = false; + service->debugfs = true; + return service; +} + +/* What one DCP poweron announces on an endpoint. */ +#define SIBLINGS 4 +static unsigned int poweron_announce(struct apple_dcp_afkep *ep, + unsigned int interface) +{ + bool hotplug = ep->endpoint >= 0x27 && ep->endpoint <= 0x29; + + announce(ep, "dcpav-service-epic", 0, interface++); + announce(ep, "dcpdp-service-epic", 0, interface++); + announce(ep, "dcpav-video-interface-epi", -1, interface++); + /* DPAVCTRL-style siblings: same name, no unit, one generation. */ + if (!hotplug) + for (unsigned int i = 0; i < SIBLINGS; i++) + announce(ep, "dcpav-controller-epic:", -1, interface++); + return interface; +} + +/* Every endpoint, both DCPs, more power cycles than there are slots. */ +static void power_cycle_test(u32 endpoint) +{ + struct apple_dcp dcps[2] = { 0 }; + struct apple_dcp_afkep eps[2] = { { .dcp = &dcps[0], .endpoint = endpoint }, + { .dcp = &dcps[1], .endpoint = endpoint } }; + bool hotplug = endpoint >= 0x27 && endpoint <= 0x29; + unsigned int per_gen = hotplug ? 3 : 3 + SIBLINGS; + unsigned int interface[2] = { 1, 1 }, cycles = 64; + unsigned int before = teardowns; + + for (unsigned int cycle = 0; cycle < cycles; cycle++) { + for (unsigned int d = 0; d < 2; d++) { + struct apple_dcp_afkep *ep = &eps[d]; + unsigned int first = interface[d], enabled = 0; + + dcps[d].afk_power_gen++; /* iomfb_poweron() */ + interface[d] = poweron_announce(ep, first); + assert(ep->num_channels == per_gen); + for (unsigned int i = 0; i < ep->num_channels; i++) { + struct apple_epic_service *s = &ep->services[i]; + + if (!s->enabled) + continue; + enabled++; + /* Only this generation's interfaces are bound. */ + assert(s->channel >= first && s->channel < interface[d]); + assert(s->power_gen == dcps[d].afk_power_gen); + } + assert(enabled == per_gen); + } + } + assert(teardowns - before == 2 * per_gen * (cycles - 1)); +} + +/* Hotplug endpoints also replace within one poweron generation. */ +static void hotplug_test(void) +{ + struct apple_dcp dcp = { .afk_power_gen = 1 }; + struct apple_dcp_afkep ep = { .dcp = &dcp, .endpoint = 0x28 }; + struct apple_epic_service *av = announce(&ep, "dcpav-service-epic", 0, 1); + + for (unsigned int i = 0; i < 2 * AFK_MAX_CHANNEL; i++) + assert(announce(&ep, "dcpav-service-epic", 0, 3 + i) == av); + assert(ep.num_channels == 1 && av->channel == 2 + 2 * AFK_MAX_CHANNEL); +} + +/* Same-generation siblings are not replacements on non-hotplug endpoints. */ +static void controller_test(void) +{ + struct apple_dcp dcp = { .afk_power_gen = 7 }; + struct apple_dcp_afkep ep = { .dcp = &dcp, .endpoint = 0x24 }; + unsigned int before = teardowns; + + for (unsigned int i = 0; i < 4; i++) + assert(announce(&ep, "dcpav-controller-epic:", -1, 2 * i + 1) == + &ep.services[i]); + assert(ep.num_channels == 4 && teardowns == before); + for (unsigned int i = 0; i < 4; i++) + assert(ep.services[i].enabled && ep.services[i].channel == 2 * i + 1); + /* After the next poweron each sibling replaces one from before. */ + dcp.afk_power_gen++; + for (unsigned int i = 0; i < 4; i++) + assert(announce(&ep, "dcpav-controller-epic:", -1, 20 + i) == + &ep.services[i]); + assert(ep.num_channels == 4 && teardowns == before + 4); +} + +static void identity_test(void) +{ + struct apple_dcp dcp = { .afk_power_gen = 1 }; + struct apple_dcp_afkep ep = { .dcp = &dcp, .endpoint = 0x2a }; + struct apple_epic_service *first, *second, *third; + + first = announce(&ep, "dcpav-service-epic", 0, 1); + second = announce(&ep, "dcpav-service-epic", 1, 3); + third = announce(&ep, "dcpdp-service-epic", 0, 5); + assert(first != second && first != third && second != third); + dcp.afk_power_gen++; + assert(announce(&ep, "dcpav-service-epic", 0, 7) == first); + assert(second->channel == 3 && third->channel == 5); + /* A different binding must not take over the same name and unit. */ + assert(afk_compact_slot(&ep, &other_ops, "dcpav-service-epic", 0) == + &ep.services[3]); + /* No-unit names remain distinct from unit zero. */ + assert(announce(&ep, "dcpav-service-epic", -1, 9) == &ep.services[3]); +} + +static void free_slot_test(void) +{ + struct apple_dcp dcp = { 0 }; + struct apple_dcp_afkep ep = { .dcp = &dcp, .endpoint = 0x2a }; + + ep.num_channels = AFK_MAX_CHANNEL; + for (unsigned int i = 0; i < AFK_MAX_CHANNEL; i++) + ep.services[i].enabled = true; + assert(!afk_compact_slot(&ep, &ops, "new", 0)); + ep.services[2].enabled = false; + ep.services[2].users = 1; + assert(!afk_compact_slot(&ep, &ops, "new", 0)); + ep.services[2].users = 0; + ep.services[2].cmd_map[0] = 1; + assert(!afk_compact_slot(&ep, &ops, "new", 0)); + ep.services[2].cmd_map[0] = 0; + assert(afk_compact_slot(&ep, &ops, "new", 0) == &ep.services[2]); +} + +int main(void) +{ + for (u32 endpoint = 0x20; endpoint <= 0x2f; endpoint++) + power_cycle_test(endpoint); + hotplug_test(); + controller_test(); + identity_test(); + free_slot_test(); + puts("PASS: compact slots: 64 poweron cycles, 16 endpoints, 2 DCPs"); + return 0; +} diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-compact.py b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-compact.py new file mode 100644 index 0000000000000..4b3fbf1b810fe --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-compact.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Exercise the production compact slot allocator across DCP power cycles. + +Poweroff leaves compact services enabled; poweron announces fresh interfaces +without teardown. Synchronization, debugfs and the owner teardown are mocked. +""" +import hashlib +from pathlib import Path +import re +import sys + +source = Path(sys.argv[1]).read_text() +start = source.index("static struct apple_epic_service *\nafk_compact_slot(") +end = source.index("\nstatic void afk_recv_handle_compact_init(", start) +body = source[start:end] +header = Path(sys.argv[1]).with_name("afk.h").read_text() +limit = re.search(r"^#define AFK_MAX_CHANNEL\s+(\d+)$", header, re.M).group(1) +directory = Path(__file__).resolve().parent +fixture = (directory / "compact.c").read_text() +assert fixture.count("/* PRODUCTION_SLOT */") == 1 +Path(sys.argv[2]).write_text("#define AFK_MAX_CHANNEL " + limit + "\n" + + fixture.replace("/* PRODUCTION_SLOT */", body)) +print("Production compact slot SHA256:", hashlib.sha256(body.encode()).hexdigest()) diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh b/tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh index 5351b80edbcc5..179d8e15d2f6f 100755 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh @@ -83,7 +83,9 @@ $afkv2_cc -std=gnu11 -Wall -Wextra -Werror ${CFLAGS:-} $afkv2_sanitizers \ "$afkv2_root/drivers/gpu/drm/apple/afk.c" "$afkv2_tmp/ring.c" "$afkv2_python" "$afkv2_dir/extract-notify.py" \ "$afkv2_root/drivers/gpu/drm/apple/epic/dpavservep.c" "$afkv2_tmp/notify.c" -for afkv2_test in ring notify; do +"$afkv2_python" "$afkv2_dir/extract-compact.py" \ + "$afkv2_root/drivers/gpu/drm/apple/afk.c" "$afkv2_tmp/compact.c" +for afkv2_test in ring notify compact; do # shellcheck disable=SC2086 $afkv2_cc -std=gnu11 -Wall -Wextra -Werror ${CFLAGS:-} $afkv2_sanitizers \ -I"$afkv2_dir/host-include" -I"$afkv2_root/drivers/gpu/drm/apple" \ From 37935a9ffda3dc39b8070e3b57124f915d4532e3 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 2 Nov 2026 06:40:47 -0500 Subject: [PATCH 24/30] usb: typec: tipd: take a USB4 router that binds after the port A CD321x port waits for its USB4/Thunderbolt router only until the driver core stops waiting for missing dependencies, ten seconds after the last driver bound. After that it continues without the router for good: a Thunderbolt or USB4 dock still enters TBT mode on the Type-C side, but ACIO is never started, the NHI is never populated and only the dock's USB 2.0 hubs appear. Fedora's initramfs carries tps6598x for USB keyboards at an encrypted root's passphrase prompt, but not thunderbolt_apple. While the prompt waits, the deferred probe timeout fires and the ports give up; the router module loads from the root filesystem twenty seconds later and registers its switches with nobody to take them. On a J414s with LUKS: [14.308] tps6598x 0-003b: deferred probe timeout, ignoring dependency [14.308] tps6598x 0-003b: USB4/Thunderbolt router unavailable, continuing without it [31.156] systemd[1]: Switching root. The same machine's Arch install, without LUKS, switches root at 2 s and its docks work. Keep a port that gave up on its router watching for it. The typec core now notifies watchers when a USB4/Thunderbolt switch registers; the port looks its router up again, takes the switch and reapplies the current cable through it with the restore event used after system sleep, so a dock attached since boot gets its tunnels too. The watch starts after the probe-time lookup and is followed by one lookup of its own, so a router that registers in between is not missed. Removal stops the watch and drains a pending lookup before it releases the switch. Assisted-by: Claude Opus 5.5 Signed-off-by: DjDeveloperr --- drivers/usb/typec/mux.c | 34 +++++ drivers/usb/typec/tipd/cd321x-tbt-test.c | 167 ++++++++++++++++++++++- drivers/usb/typec/tipd/core.c | 106 +++++++++++++- drivers/usb/typec/tipd/tps6598x.h | 10 +- include/linux/usb/typec_mux.h | 13 ++ 5 files changed, 317 insertions(+), 13 deletions(-) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 1df58b4f9012e..db28d50cf61b6 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -965,6 +965,37 @@ void typec_thunderbolt_switch_notify_restore(struct typec_thunderbolt_switch_dev } EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_notify_restore); +/* Type-C ports that probed before their USB4/Thunderbolt router */ +static BLOCKING_NOTIFIER_HEAD(typec_thunderbolt_switch_watchers); + +/** + * typec_thunderbolt_switch_watch - Watch for USB4/Thunderbolt switch registration + * @nb: Notifier called with TYPEC_THUNDERBOLT_SWITCH_ADDED and the new switch + * + * Lets a consumer that probed without its switch look it up again later. The + * callback runs under the registering driver's locks, so it must only schedule + * work. Look the switch up once more after registering the watcher: it may + * have registered in between. + */ +int typec_thunderbolt_switch_watch(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&typec_thunderbolt_switch_watchers, + nb); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_watch); + +/** + * typec_thunderbolt_switch_unwatch - Stop watching for switch registration + * @nb: Notifier registered with typec_thunderbolt_switch_watch() + * + * Waits for a callback that is already running to return. + */ +void typec_thunderbolt_switch_unwatch(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&typec_thunderbolt_switch_watchers, nb); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_unwatch); + static void typec_thunderbolt_switch_release(struct device *dev) { kfree(to_typec_thunderbolt_switch_dev(dev)); @@ -1023,6 +1054,9 @@ typec_thunderbolt_switch_register(struct device *parent, return ERR_PTR(ret); } + blocking_notifier_call_chain(&typec_thunderbolt_switch_watchers, + TYPEC_THUNDERBOLT_SWITCH_ADDED, sw_dev); + return sw_dev; } EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_register); diff --git a/drivers/usb/typec/tipd/cd321x-tbt-test.c b/drivers/usb/typec/tipd/cd321x-tbt-test.c index 63858db80d509..ee46db0734c41 100644 --- a/drivers/usb/typec/tipd/cd321x-tbt-test.c +++ b/drivers/usb/typec/tipd/cd321x-tbt-test.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "tps6598x.h" @@ -78,6 +79,12 @@ static const struct software_node cd321x_test_plain_connector = { .name = "cd321x-test-plain-connector", }; +/* The router of another port */ +static const struct software_node cd321x_test_other_router = { + .name = "cd321x-test-other-router", + .properties = cd321x_test_router_props, +}; + static const struct software_node * const cd321x_test_nodes[] = { &cd321x_test_router, &cd321x_test_router_port, @@ -86,6 +93,7 @@ static const struct software_node * const cd321x_test_nodes[] = { &cd321x_test_connector_port, &cd321x_test_connector_ep, &cd321x_test_plain_connector, + &cd321x_test_other_router, NULL }; @@ -148,10 +156,13 @@ static void cd321x_tbt_no_router_test(struct kunit *test) { struct cd321x_tbt_test *ctx = test->priv; struct typec_thunderbolt_switch *sw; + bool late = true; sw = cd321x_tbt_switch_get(ctx->dev, - software_node_fwnode(&cd321x_test_plain_connector)); + software_node_fwnode(&cd321x_test_plain_connector), + &late); KUNIT_EXPECT_NULL(test, sw); + KUNIT_EXPECT_FALSE(test, late); KUNIT_EXPECT_EQ(test, ctx->keep_waiting_calls, 0); } @@ -166,6 +177,7 @@ static void cd321x_tbt_router_bound_test(struct kunit *test) struct typec_thunderbolt_switch_dev *sw_dev; struct typec_thunderbolt_switch *sw; struct device *router; + bool late = true; int ret; router = kunit_device_register(test, "router"); @@ -180,8 +192,10 @@ static void cd321x_tbt_router_bound_test(struct kunit *test) KUNIT_ASSERT_EQ(test, ret, 0); sw = cd321x_tbt_switch_get(ctx->dev, - software_node_fwnode(&cd321x_test_connector)); + software_node_fwnode(&cd321x_test_connector), + &late); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, sw); + KUNIT_EXPECT_FALSE(test, late); KUNIT_EXPECT_EQ(test, ctx->keep_waiting_calls, 0); typec_thunderbolt_switch_put(sw); } @@ -191,36 +205,177 @@ static void cd321x_tbt_router_pending_test(struct kunit *test) { struct cd321x_tbt_test *ctx = test->priv; struct typec_thunderbolt_switch *sw; + bool late = true; ctx->keep_waiting = true; sw = cd321x_tbt_switch_get(ctx->dev, - software_node_fwnode(&cd321x_test_connector)); + software_node_fwnode(&cd321x_test_connector), + &late); KUNIT_EXPECT_PTR_EQ(test, sw, ERR_PTR(-EPROBE_DEFER)); + KUNIT_EXPECT_FALSE(test, late); KUNIT_EXPECT_EQ(test, ctx->keep_waiting_calls, 1); } /* * Once the driver core stops waiting for missing dependencies, a router that - * never registered its switch, for example because its driver is not built, - * no longer keeps the port from probing. + * has not registered its switch no longer keeps the port from probing, and the + * port is told to take the router if it binds later. */ static void cd321x_tbt_router_never_binds_test(struct kunit *test) { struct cd321x_tbt_test *ctx = test->priv; struct typec_thunderbolt_switch *sw; + bool late = false; ctx->keep_waiting = false; sw = cd321x_tbt_switch_get(ctx->dev, - software_node_fwnode(&cd321x_test_connector)); + software_node_fwnode(&cd321x_test_connector), + &late); KUNIT_EXPECT_NULL(test, sw); + KUNIT_EXPECT_TRUE(test, late); KUNIT_EXPECT_EQ(test, ctx->keep_waiting_calls, 1); } +static int cd321x_tbt_test_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + return NOTIFY_OK; +} + +/* Safe to repeat: tests release a port early to check the result. */ +static void cd321x_tbt_test_release_port(struct cd321x *cd) +{ + cd321x_stop_work(cd); + typec_thunderbolt_switch_put(cd->tbt_switch); + cd->tbt_switch = NULL; + fwnode_handle_put(cd->tbt_fwnode); + cd->tbt_fwnode = NULL; +} + +static void cd321x_tbt_test_release_port_action(void *cd) +{ + cd321x_tbt_test_release_port(cd); +} + +/* A port that probed without its router, with no hardware behind it */ +static struct cd321x *cd321x_tbt_test_late_port(struct kunit *test) +{ + struct cd321x_tbt_test *ctx = test->priv; + struct cd321x *cd; + int ret; + + cd = kunit_kzalloc(test, sizeof(*cd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cd); + mutex_init(&cd->tps.lock); + cd->tps.dev = ctx->dev; + /* Still initializing: the queued cable read stops before the hardware. */ + cd321x_init_work(cd); + cd->tbt_notifier.notifier_call = cd321x_tbt_test_notify; + /* Leave no freed port on the global watch list if an assertion fails. */ + ret = kunit_add_action_or_reset(test, cd321x_tbt_test_release_port_action, + cd); + KUNIT_ASSERT_EQ(test, ret, 0); + return cd; +} + +static void cd321x_tbt_test_add_router(struct kunit *test, + const struct software_node *node) +{ + struct typec_thunderbolt_switch_desc desc = { + .fwnode = software_node_fwnode(node), + .set = cd321x_tbt_test_set, + .name = node->name, + }; + struct typec_thunderbolt_switch_dev *sw_dev; + struct device *router; + int ret; + + router = kunit_device_register(test, node->name); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, router); + sw_dev = typec_thunderbolt_switch_register(router, &desc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sw_dev); + ret = kunit_add_action_or_reset(test, cd321x_tbt_test_unregister_switch, + sw_dev); + KUNIT_ASSERT_EQ(test, ret, 0); +} + +/* A router that registers after the port gave up is taken; the cable is reread. */ +static void cd321x_tbt_router_binds_late_test(struct kunit *test) +{ + struct cd321x *cd = cd321x_tbt_test_late_port(test); + int ret; + + ret = cd321x_tbt_watch_late(cd, software_node_fwnode(&cd321x_test_connector)); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, cd->tbt_watch_registered); + flush_work(&cd->tbt_attach_work); + KUNIT_EXPECT_NULL(test, cd->tbt_switch); + KUNIT_EXPECT_EQ(test, atomic_read(&cd->restore_event), 0); + + cd321x_tbt_test_add_router(test, &cd321x_test_router); + flush_work(&cd->tbt_attach_work); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, cd->tbt_switch); + KUNIT_EXPECT_TRUE(test, cd->tbt_notifier_registered); + KUNIT_EXPECT_EQ(test, atomic_read(&cd->restore_event), 1); + + cd321x_tbt_test_release_port(cd); + KUNIT_EXPECT_FALSE(test, cd->tbt_watch_registered); + KUNIT_EXPECT_FALSE(test, cd->tbt_notifier_registered); +} + +/* A router that registered before the watch started is not missed. */ +static void cd321x_tbt_router_bound_before_watch_test(struct kunit *test) +{ + struct cd321x *cd = cd321x_tbt_test_late_port(test); + int ret; + + cd321x_tbt_test_add_router(test, &cd321x_test_router); + ret = cd321x_tbt_watch_late(cd, software_node_fwnode(&cd321x_test_connector)); + KUNIT_ASSERT_EQ(test, ret, 0); + flush_work(&cd->tbt_attach_work); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, cd->tbt_switch); + KUNIT_EXPECT_EQ(test, atomic_read(&cd->restore_event), 1); +} + +/* Another port's router binding leaves this port waiting for its own. */ +static void cd321x_tbt_other_router_binds_test(struct kunit *test) +{ + struct cd321x *cd = cd321x_tbt_test_late_port(test); + int ret; + + ret = cd321x_tbt_watch_late(cd, software_node_fwnode(&cd321x_test_connector)); + KUNIT_ASSERT_EQ(test, ret, 0); + cd321x_tbt_test_add_router(test, &cd321x_test_other_router); + flush_work(&cd->tbt_attach_work); + KUNIT_EXPECT_NULL(test, cd->tbt_switch); + KUNIT_EXPECT_EQ(test, atomic_read(&cd->restore_event), 0); + KUNIT_EXPECT_TRUE(test, cd->tbt_watch_registered); +} + +/* A port that is going away no longer takes a router that binds. */ +static void cd321x_tbt_router_binds_after_removal_test(struct kunit *test) +{ + struct cd321x *cd = cd321x_tbt_test_late_port(test); + int ret; + + ret = cd321x_tbt_watch_late(cd, software_node_fwnode(&cd321x_test_connector)); + KUNIT_ASSERT_EQ(test, ret, 0); + cd321x_stop_work(cd); + cd321x_tbt_test_add_router(test, &cd321x_test_router); + flush_work(&cd->tbt_attach_work); + KUNIT_EXPECT_NULL(test, cd->tbt_switch); + KUNIT_EXPECT_FALSE(test, cd->tbt_notifier_registered); +} + static struct kunit_case cd321x_tbt_cases[] = { KUNIT_CASE(cd321x_tbt_no_router_test), KUNIT_CASE(cd321x_tbt_router_bound_test), KUNIT_CASE(cd321x_tbt_router_pending_test), KUNIT_CASE(cd321x_tbt_router_never_binds_test), + KUNIT_CASE(cd321x_tbt_router_binds_late_test), + KUNIT_CASE(cd321x_tbt_router_bound_before_watch_test), + KUNIT_CASE(cd321x_tbt_other_router_binds_test), + KUNIT_CASE(cd321x_tbt_router_binds_after_removal_test), {} }; diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index cc6a15384ca9f..872dc2c02c2e6 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -2088,11 +2088,18 @@ static void cd321x_ready(struct tps6598x *tps) } } -static void cd321x_stop_work(struct cd321x *cd321x) +VISIBLE_IF_KUNIT void cd321x_stop_work(struct cd321x *cd321x) { scoped_guard(mutex, &cd321x->tps.lock) cd321x_pm_remove(&cd321x->pm); + /* A router that binds from now on must not hand this port its switch. */ + if (cd321x->tbt_watch_registered) { + typec_thunderbolt_switch_unwatch(&cd321x->tbt_watch); + cd321x->tbt_watch_registered = false; + } + cancel_work_sync(&cd321x->tbt_attach_work); + /* Drain producers before cancelling work, including on probe rollback. */ if (cd321x->tbt_notifier_registered) { typec_thunderbolt_switch_unregister_notifier(cd321x->tbt_switch, @@ -2102,6 +2109,7 @@ static void cd321x_stop_work(struct cd321x *cd321x) cancel_delayed_work_sync(&cd321x->resume_work); cancel_delayed_work_sync(&cd321x->update_work); } +EXPORT_SYMBOL_IF_KUNIT(cd321x_stop_work); /* Separate so that the KUnit tests can choose the driver core's answer. */ VISIBLE_IF_KUNIT bool cd321x_tbt_switch_keep_waiting(struct device *dev) @@ -2114,30 +2122,109 @@ EXPORT_SYMBOL_IF_KUNIT(cd321x_tbt_switch_keep_waiting); /* * The USB4/Thunderbolt router is only needed once a USB4 or Thunderbolt link - * comes up. If its driver is not built or never binds, do not leave power + * comes up. If its driver is not built or has not bound yet, do not leave power * delivery, USB and DisplayPort on this port without a driver as well: stop * waiting for the router when the driver core stops waiting for missing - * dependencies. + * dependencies. The router can still bind later, so set @late to take it then. */ VISIBLE_IF_KUNIT struct typec_thunderbolt_switch * -cd321x_tbt_switch_get(struct device *dev, struct fwnode_handle *fwnode) +cd321x_tbt_switch_get(struct device *dev, struct fwnode_handle *fwnode, + bool *late) { struct typec_thunderbolt_switch *sw; + *late = false; sw = fwnode_typec_thunderbolt_switch_get(fwnode); if (sw != ERR_PTR(-EPROBE_DEFER) || cd321x_tbt_switch_keep_waiting(dev)) return sw; - dev_info(dev, "USB4/Thunderbolt router unavailable, continuing without it\n"); + dev_info(dev, "USB4/Thunderbolt router unavailable, continuing without it until it binds\n"); + *late = true; return NULL; } EXPORT_SYMBOL_IF_KUNIT(cd321x_tbt_switch_get); +/* + * Take a router that bound after the port gave up on it. An attached cable + * raises no new event, so reapply it through the router as after system sleep. + */ +static void cd321x_tbt_attach_work(struct work_struct *work) +{ + struct cd321x *cd321x = container_of(work, struct cd321x, + tbt_attach_work); + struct tps6598x *tps = &cd321x->tps; + struct typec_thunderbolt_switch *sw; + int ret; + + scoped_guard(mutex, &tps->lock) { + if (cd321x->tbt_switch || cd321x->pm.phase == CD321X_PM_REMOVED) + return; + } + + /* Another port's router, or this one is not usable yet: keep watching. */ + sw = fwnode_typec_thunderbolt_switch_get(cd321x->tbt_fwnode); + if (IS_ERR_OR_NULL(sw)) + return; + + ret = typec_thunderbolt_switch_register_notifier(sw, + &cd321x->tbt_notifier); + if (ret) { + typec_thunderbolt_switch_put(sw); + dev_warn(tps->dev, + "failed to watch the USB4/Thunderbolt router: %d\n", ret); + return; + } + + scoped_guard(mutex, &tps->lock) { + cd321x->tbt_switch = sw; + cd321x->tbt_notifier_registered = true; + } + dev_info(tps->dev, "USB4/Thunderbolt router bound, reapplying the cable state\n"); + atomic_set(&cd321x->restore_event, 1); + mod_delayed_work(system_freezable_wq, &cd321x->resume_work, 0); +} + +/* Runs under the registering router's locks, so only queue the lookup. */ +static int cd321x_tbt_watch(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct cd321x *cd321x = container_of(nb, struct cd321x, tbt_watch); + + if (event != TYPEC_THUNDERBOLT_SWITCH_ADDED) + return NOTIFY_DONE; + + queue_work(system_freezable_wq, &cd321x->tbt_attach_work); + return NOTIFY_OK; +} + +/* Watch for the router of a port that had to probe without it. */ +VISIBLE_IF_KUNIT int cd321x_tbt_watch_late(struct cd321x *cd321x, + struct fwnode_handle *fwnode) +{ + int ret; + + cd321x->tbt_fwnode = fwnode_handle_get(fwnode); + cd321x->tbt_watch.notifier_call = cd321x_tbt_watch; + ret = typec_thunderbolt_switch_watch(&cd321x->tbt_watch); + if (ret) { + fwnode_handle_put(cd321x->tbt_fwnode); + cd321x->tbt_fwnode = NULL; + return ret; + } + cd321x->tbt_watch_registered = true; + + /* The router may have registered since the lookup at probe. */ + queue_work(system_freezable_wq, &cd321x->tbt_attach_work); + return 0; +} +EXPORT_SYMBOL_IF_KUNIT(cd321x_tbt_watch_late); + /* Separate so that the KUnit tests can drive the workers without a port. */ VISIBLE_IF_KUNIT void cd321x_init_work(struct cd321x *cd321x) { INIT_DELAYED_WORK(&cd321x->update_work, cd321x_update_work); INIT_DELAYED_WORK(&cd321x->resume_work, cd321x_resume_work); + INIT_WORK(&cd321x->tbt_attach_work, cd321x_tbt_attach_work); atomic_set(&cd321x->link_event, 0); atomic_set(&cd321x->ready_event, 0); atomic_set(&cd321x->restore_event, 0); @@ -2171,6 +2258,7 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) { struct fwnode_handle *connector_fwnode; struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + bool tbt_late; int ret; cd321x_init_work(cd321x); @@ -2196,7 +2284,7 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) if (!IS_ERR_OR_NULL(connector_fwnode)) cd321x->connector_fwnode = connector_fwnode; - cd321x->tbt_switch = cd321x_tbt_switch_get(tps->dev, fwnode); + cd321x->tbt_switch = cd321x_tbt_switch_get(tps->dev, fwnode, &tbt_late); if (IS_ERR(cd321x->tbt_switch)) { ret = PTR_ERR(cd321x->tbt_switch); goto err_put_mux; @@ -2218,6 +2306,10 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) goto err_put_switch; cd321x->tbt_notifier_registered = true; + /* Keep the port without the router rather than fail it: see above. */ + if (tbt_late && cd321x_tbt_watch_late(cd321x, fwnode)) + dev_warn(tps->dev, "cannot watch for the USB4/Thunderbolt router\n"); + return 0; err_put_switch: @@ -2252,6 +2344,8 @@ cd321x_unregister_port(struct tps6598x *tps) cd321x_stop_work(cd321x); fwnode_handle_put(cd321x->connector_fwnode); cd321x->connector_fwnode = NULL; + fwnode_handle_put(cd321x->tbt_fwnode); + cd321x->tbt_fwnode = NULL; typec_thunderbolt_switch_put(cd321x->tbt_switch); cd321x->tbt_switch = NULL; typec_mux_put(cd321x->mux); diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 28900eb3ac9a7..5a1b73a6d4601 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -434,6 +434,11 @@ struct cd321x { struct typec_thunderbolt_switch *tbt_switch; struct notifier_block tbt_notifier; bool tbt_notifier_registered; + /* Router that had not bound at probe: look it up when a switch registers */ + struct fwnode_handle *tbt_fwnode; + struct notifier_block tbt_watch; + struct work_struct tbt_attach_work; + bool tbt_watch_registered; atomic_t link_event; atomic_t ready_event; atomic_t restore_event; @@ -491,9 +496,12 @@ static inline void tipd_debugfs_exit(void) { } #if IS_ENABLED(CONFIG_KUNIT) void cd321x_init_work(struct cd321x *cd321x); int cd321x_init_mux(struct cd321x *cd321x); +void cd321x_stop_work(struct cd321x *cd321x); bool cd321x_tbt_switch_keep_waiting(struct device *dev); struct typec_thunderbolt_switch * -cd321x_tbt_switch_get(struct device *dev, struct fwnode_handle *fwnode); +cd321x_tbt_switch_get(struct device *dev, struct fwnode_handle *fwnode, + bool *late); +int cd321x_tbt_watch_late(struct cd321x *cd321x, struct fwnode_handle *fwnode); int sn201202x_suspend(struct device *dev); int sn201202x_resume(struct device *dev); #endif diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index b4f023de0eee9..ea0d697629611 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -205,6 +205,8 @@ struct typec_thunderbolt_switch_dev; #define TYPEC_THUNDERBOLT_SWITCH_READY 2 /* The consumer powered down for system sleep and needs a fresh cable read. */ #define TYPEC_THUNDERBOLT_SWITCH_RESTORE 3 +/* Sent to typec_thunderbolt_switch_watch() notifiers: a switch registered. */ +#define TYPEC_THUNDERBOLT_SWITCH_ADDED 4 enum typec_thunderbolt_switch_state { TYPEC_THUNDERBOLT_SWITCH_OFF, @@ -260,6 +262,8 @@ void typec_thunderbolt_switch_unregister_notifier(struct typec_thunderbolt_switc void typec_thunderbolt_switch_notify(struct typec_thunderbolt_switch_dev *sw); void typec_thunderbolt_switch_notify_ready(struct typec_thunderbolt_switch_dev *sw); void typec_thunderbolt_switch_notify_restore(struct typec_thunderbolt_switch_dev *sw); +int typec_thunderbolt_switch_watch(struct notifier_block *nb); +void typec_thunderbolt_switch_unwatch(struct notifier_block *nb); struct typec_thunderbolt_switch_dev * typec_thunderbolt_switch_register(struct device *parent, @@ -326,6 +330,15 @@ typec_thunderbolt_switch_notify_restore(struct typec_thunderbolt_switch_dev *sw) { } +static inline int typec_thunderbolt_switch_watch(struct notifier_block *nb) +{ + return 0; +} + +static inline void typec_thunderbolt_switch_unwatch(struct notifier_block *nb) +{ +} + static inline void typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw) { From 8aabbb50f73b1a13a2433d50f2f0b4bf0f556d31 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 23:14:27 +1100 Subject: [PATCH 25/30] arm64: dts: apple: t6040-j616s: switch the power allocator on near the trips Every thermal zone on the J616s runs the power allocator, and every one has a single passive trip. With one passive trip the allocator has no switch-on point, so it computes a budget and throttles at any temperature. The seven GPU zones carry no sustainable-power either, so the allocator estimates it from the CPU cooling devices' lowest states, about 2.6 W. While the GPU is off, those zones read placeholder values near 3 C, and their output is about 7.5 W for all 14 CPUs. The thermal core applies the deepest state any zone asks for, so under any all-core load the clusters sat at 1.26/1.02/1.26 GHz with the die near 55 C. This started when the CPU cooling devices got power actors from the energy model; before that the same load ran at 4.5 GHz. Give each zone a passive switch-on trip below its control trip: 100 C for the die and CPU zones (control 116 C) and 90 C for the GPU zones (control 101 C). Below the switch-on point the allocator resets and grants full power, which is what these zones are meant to do: catch a failure of the PMP's own die loop, not act in normal use. Give the GPU zones the die's sustainable power, 51300 mW, instead of the estimate. The placeholder readings then stay far below the switch-on point. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 161 ++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 693f136a849f4..b3e9b0e3eb126 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -349,6 +349,16 @@ * GPU is powered off its sensors report placeholder values from -4 to * 4 C. * + * Every zone runs the power allocator, which acts at any temperature + * unless a zone has a second passive trip: the first one is then its + * switch-on point, below which it resets and grants full power. So each + * zone has one, 16 C below the die and CPU control trip (100/116 C) and + * 11 C below the GPU one (90/101 C). Without them the GPU zones, whose + * placeholder readings never warm up, held all 14 CPUs to about 7.5 W + * (1.26/1.02/1.26 GHz) under any all-core load. The GPU zones also + * carry the die's sustainable power rather than the allocator's + * estimate from the lowest CPU states (about 2.6 W). + * * The fans: left to themselves (SMC mode 3, with no OS asking for * cooling) they stay off, or switch on late and ramp slowly, while the * dies heat up under load. So the CPU die zone and the GPU zones also @@ -406,6 +416,12 @@ trips { J616S_FAN_TRIPS(cpu_die) + cpu_die_switch_on: cpu-die-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_die_passive: cpu-die-passive { temperature = <116000>; hysteresis = <5000>; @@ -460,6 +476,12 @@ thermal-sensors = <&smc_hwmon 1>; /* Tex0 */ trips { + cpu_e0_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_e0_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -492,6 +514,12 @@ thermal-sensors = <&smc_hwmon 2>; /* Tex1 */ trips { + cpu_e1_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_e1_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -524,6 +552,12 @@ thermal-sensors = <&smc_hwmon 3>; /* Tex2 */ trips { + cpu_e2_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_e2_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -556,6 +590,12 @@ thermal-sensors = <&smc_hwmon 4>; /* Tex3 */ trips { + cpu_e3_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_e3_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -588,6 +628,12 @@ thermal-sensors = <&smc_hwmon 5>; /* Tpx0 */ trips { + cpu_p00_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p00_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -621,6 +667,12 @@ thermal-sensors = <&smc_hwmon 6>; /* Tpx1 */ trips { + cpu_p01_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p01_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -654,6 +706,12 @@ thermal-sensors = <&smc_hwmon 7>; /* Tpx2 */ trips { + cpu_p02_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p02_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -687,6 +745,12 @@ thermal-sensors = <&smc_hwmon 8>; /* Tpx3 */ trips { + cpu_p03_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p03_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -720,6 +784,12 @@ thermal-sensors = <&smc_hwmon 9>; /* Tpx4 */ trips { + cpu_p04_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p04_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -753,6 +823,12 @@ thermal-sensors = <&smc_hwmon 10>; /* Tpx5 */ trips { + cpu_p05_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p05_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -786,6 +862,12 @@ thermal-sensors = <&smc_hwmon 11>; /* Tpx8 */ trips { + cpu_p10_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p10_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -819,6 +901,12 @@ thermal-sensors = <&smc_hwmon 12>; /* Tpx9 */ trips { + cpu_p11_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p11_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -852,6 +940,12 @@ thermal-sensors = <&smc_hwmon 13>; /* TpxA */ trips { + cpu_p12_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p12_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -885,6 +979,12 @@ thermal-sensors = <&smc_hwmon 14>; /* TpxB */ trips { + cpu_p13_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p13_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -918,6 +1018,12 @@ thermal-sensors = <&smc_hwmon 15>; /* TpxC */ trips { + cpu_p14_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p14_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -951,6 +1057,12 @@ thermal-sensors = <&smc_hwmon 16>; /* TpxD */ trips { + cpu_p15_switch_on: trip-switch-on { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_p15_passive: trip-passive { temperature = <116000>; hysteresis = <5000>; @@ -980,11 +1092,18 @@ gpu0-thermal { polling-delay-passive = <1000>; polling-delay = <1000>; + sustainable-power = <51300>; thermal-sensors = <&smc_hwmon 17>; /* Tg05 */ trips { J616S_FAN_TRIPS(gpu0) + gpu0_switch_on: trip-switch-on { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + gpu0_passive: trip-passive { temperature = <101000>; hysteresis = <5000>; @@ -1035,11 +1154,18 @@ gpu1-thermal { polling-delay-passive = <1000>; polling-delay = <1000>; + sustainable-power = <51300>; thermal-sensors = <&smc_hwmon 18>; /* Tg0S */ trips { J616S_FAN_TRIPS(gpu1) + gpu1_switch_on: trip-switch-on { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + gpu1_passive: trip-passive { temperature = <101000>; hysteresis = <5000>; @@ -1090,11 +1216,18 @@ gpu2-thermal { polling-delay-passive = <1000>; polling-delay = <1000>; + sustainable-power = <51300>; thermal-sensors = <&smc_hwmon 19>; /* Tg0Y */ trips { J616S_FAN_TRIPS(gpu2) + gpu2_switch_on: trip-switch-on { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + gpu2_passive: trip-passive { temperature = <101000>; hysteresis = <5000>; @@ -1145,11 +1278,18 @@ gpu3-thermal { polling-delay-passive = <1000>; polling-delay = <1000>; + sustainable-power = <51300>; thermal-sensors = <&smc_hwmon 20>; /* Tg0k */ trips { J616S_FAN_TRIPS(gpu3) + gpu3_switch_on: trip-switch-on { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + gpu3_passive: trip-passive { temperature = <101000>; hysteresis = <5000>; @@ -1200,11 +1340,18 @@ gpu4-thermal { polling-delay-passive = <1000>; polling-delay = <1000>; + sustainable-power = <51300>; thermal-sensors = <&smc_hwmon 21>; /* Tg0z */ trips { J616S_FAN_TRIPS(gpu4) + gpu4_switch_on: trip-switch-on { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + gpu4_passive: trip-passive { temperature = <101000>; hysteresis = <5000>; @@ -1255,11 +1402,18 @@ gpu5-thermal { polling-delay-passive = <1000>; polling-delay = <1000>; + sustainable-power = <51300>; thermal-sensors = <&smc_hwmon 22>; /* Tg1V */ trips { J616S_FAN_TRIPS(gpu5) + gpu5_switch_on: trip-switch-on { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + gpu5_passive: trip-passive { temperature = <101000>; hysteresis = <5000>; @@ -1310,11 +1464,18 @@ gpu6-thermal { polling-delay-passive = <1000>; polling-delay = <1000>; + sustainable-power = <51300>; thermal-sensors = <&smc_hwmon 23>; /* Tg1l */ trips { J616S_FAN_TRIPS(gpu6) + gpu6_switch_on: trip-switch-on { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + gpu6_passive: trip-passive { temperature = <101000>; hysteresis = <5000>; From 7915d686b4560fe54cdfaefbe9f995f7febafcb8 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 2 Nov 2026 23:23:40 +1100 Subject: [PATCH 26/30] ASoC: apple: macaudio, arm64: dts: t6040-j614s: Keep the J614s speakers off The J614s ships experimental and its speakers need their own bring-up. There is no J614 protection profile and the speakers have not been measured, so nothing may drive them. Give the J614 speaker card its own configuration with the speakers disabled, so the driver never binds the speaker link even if the card is enabled. In the J614s device tree disable the speaker card and the six SN012776 amps as well, so no amp driver probes and no stream can reach them. The headphone jack card is unchanged. The J616s keeps its configuration and its DTB is unchanged. Assisted-by: LLM Signed-off-by: Ryan Murray --- arch/arm64/boot/dts/apple/t6040-j614s.dts | 30 +++++++++++++++++++++++ sound/soc/apple/macaudio.c | 25 +++++++++++++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6040-j614s.dts b/arch/arm64/boot/dts/apple/t6040-j614s.dts index 4373a41b6f430..9ef0cf1194eec 100644 --- a/arch/arm64/boot/dts/apple/t6040-j614s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j614s.dts @@ -37,9 +37,39 @@ apple,chassis-name = "J614"; }; +/* + * The speakers wait for their own bring-up and a J614 protection profile. + * Until then the speaker card and the six amps stay disabled, so nothing + * can drive them; the headphone jack card is unaffected. + */ &sound { compatible = "apple,j614-macaudio", "apple,macaudio"; model = "MacBook Pro J614"; + status = "disabled"; +}; + +&speaker_left_woof1 { + status = "disabled"; +}; + +&speaker_left_woof2 { + status = "disabled"; +}; + +&speaker_left_tweet { + status = "disabled"; +}; + +&speaker_right_woof1 { + status = "disabled"; +}; + +&speaker_right_woof2 { + status = "disabled"; +}; + +&speaker_right_tweet { + status = "disabled"; }; &sound_jack { diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 545ee2ab0e80f..1a7519c2eaa74 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -2143,6 +2143,27 @@ struct macaudio_platform_cfg macaudio_j616_cfg = { .route_only_fes = BIT(0), }; +/* + * J614s: the same amps and frame as the J616s, but its speakers have not + * been brought up and have no protection profile. Keep them off entirely + * until they get their own bring-up: the speaker link is not bound, so no + * stream ever reaches the amps. + */ +struct macaudio_platform_cfg macaudio_j614_cfg = { + .enable_speakers = false, + .amp = AMP_SN012776, + .speakers = SPKR_2W1T, + .stereo = true, + .amp_gain = 15, + .safe_vol = -20, + .fe_dai_names = { "j616-pcm-0", "j616-pcm-1", "j616-pcm-2" }, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC | + SND_SOC_DAIFMT_GATED | SND_SOC_DAIFMT_NB_IF, + .secondary_bclk_ratio = 250, + .fe_cpu_is_platform = true, + .route_only_fes = BIT(0), +}; + /* * The J614s and J616s headphone jack, a card of its own: a CS42L84 on the * AOP's "jack" back-end, which the firmware runs on MCA cluster 2 as a 64-SCLK @@ -2226,8 +2247,8 @@ static const struct of_device_id macaudio_snd_device_id[] = { /* j575 AID33 sn012776 15 1x 1W Compat: apple,j375-macaudio */ /* j613 AID20 sn012776 15 2x 1W+1T Compat: apple,j413-macaudio */ /* j615 AID21 sn012776 15 2x 2W+1T Compat: apple,j415-macaudio */ - /* j614 AID34 sn012776 15 2x 2W+1T (AOP-fed, one serializer) */ - { .compatible = "apple,j614-macaudio", .data = &macaudio_j616_cfg }, + /* j614 AID34 sn012776 15 2x 2W+1T (AOP-fed; speakers off until bring-up) */ + { .compatible = "apple,j614-macaudio", .data = &macaudio_j614_cfg }, /* j614 AID34 cs42l84 - the jack, a card of its own (AOP-fed) */ { .compatible = "apple,j614-macaudio-jack", .data = &macaudio_j616_jack_cfg }, /* j616 AID35 sn012776 15 2x 2W+1T (AOP-fed, one serializer) */ From b7ee2b72269583c644808fe84907efef8817b4c6 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 2 Nov 2026 07:02:24 -0500 Subject: [PATCH 27/30] soc: apple: sep: let keybag creation rewrite its shared xART record On a MacBook Pro M2 Pro (T6020, J414s), a second Linux install never got a Touch ID keybag. CREATE_KEYBAG makes the enclave rewrite one existing kind-4 xART record. That record is one of the four kind-4 records the enclave reads at every attach. A 2026-09-26 capture from the first Linux install already has it, and macOS's enclave rewrote it on 2026-11-02 while no Linux install was running. It predates every Linux install, so it is never on Linux's list, and xart_guard=1 refused it. The enclave did not fail the create. It stopped answering, and the hardware RNG and every later key-store request timed out. The driver also kept its create intent, so every later boot refused to create a keybag ("identity keybag state is ambiguous: EEXIST"), and trusted keys failed with EEXIST. The other Linux install is not the cause. Every guarded keybag creation on that Mac asks for the same rewrite. The first install has a keybag only because it was made before the guard existed. An M1 Pro (T6000) rewrites one pre-Linux attach record the same way. It runs the same stub sepOS, so the risk to macOS is the same, and so far it has needed xart_guard=0. Run the identity CREATE_KEYBAG in the window a boot policy change already uses. It allows one rewrite per boot, of one kind-4 record that existed at probe. It never allows a delete or another kind, the roots follow the level, the previous value is saved on the host first, and nothing is adopted. The exchange now waits up to 6 s, as a 13.5 key store's does, because the saved value and the new slot are flushed inside it. Every other session record that existed at probe stays refused, before, during and after the create. That includes any record another Linux install made. Make an unfinished create recoverable. The intent now records how long the guard's list was. New session records are listed before they are written, so a later boot whose list has not grown knows the create left nothing behind, and sends it again. An intent from an earlier kernel records no length. It is sent again only while the list is empty, which recovers the installs this refusal already left stuck. A guarded cold-boot Mac is no longer reported as matching "blocked" just for being cold-boot. That rule assumed its keybag could only be created with the guard off. It is now "unverified", like a warm-attach Mac, until the guard refuses a write. aurora-sep-ready drops the matching reason, and the ABI doc and XART-SAFETY.md follow. KUnit covers the window's terms, the list length kept from load, the retry rule, and the intent format, including the zero-UUID intent that earlier kernels wrote. Signed-off-by: DjDeveloperr Assisted-by: Claude:claude-opus-5-5 --- .../ABI/testing/sysfs-driver-apple-sep | 15 +- drivers/soc/apple/diag.rs | 3 - drivers/soc/apple/keybag.rs | 184 ++++++++- drivers/soc/apple/sks.rs | 64 +++- drivers/soc/apple/xart_guard.rs | 356 +++++++++++++----- drivers/soc/apple/xart_store.rs | 25 +- tools/aurora-sep/XART-SAFETY.md | 75 +++- tools/aurora-sep/aurora-sep-ready | 5 +- .../packaging/arch/aurora-sep/PKGBUILD | 4 +- tools/aurora-sep/tests/test_diag_sensor.py | 2 +- tools/aurora-sep/tests/test_sep_ready.py | 17 +- 11 files changed, 595 insertions(+), 155 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-apple-sep b/Documentation/ABI/testing/sysfs-driver-apple-sep index b11ef8f266150..b48d26187f67d 100644 --- a/Documentation/ABI/testing/sysfs-driver-apple-sep +++ b/Documentation/ABI/testing/sysfs-driver-apple-sep @@ -341,21 +341,16 @@ Description: ========== ========================================== usable xART writes are enabled and no guard runs. - unverified A guard runs with its list loaded on a - warm-attach Mac (boot "warm") and has refused - nothing yet. Whether every record a match - rewrites is on the list is not known until a - match is tried. + unverified A guard runs with its list loaded and has + refused nothing yet. Whether every record a + match rewrites is on the list is not known + until a match is tried. blocked A match's write is refused or known to be: xART writes are disabled (xart "disabled"), the guard has refused a write (xart_guard_refusals above 0), the guard has no list (xart_guard_list - "unavailable"), or a guard runs on a - cold-boot Mac (boot "cold"). On a cold-boot - Mac, creating the keybag needs xart_guard=0, - so the records a match rewrites are not on - the list. + "unavailable"). ========== ========================================== For the guard cases the documented opt-out is xart_guard=0, diff --git a/drivers/soc/apple/diag.rs b/drivers/soc/apple/diag.rs index 288c92a3cd94c..1b6fdd2288c52 100644 --- a/drivers/soc/apple/diag.rs +++ b/drivers/soc/apple/diag.rs @@ -90,7 +90,6 @@ pub(crate) fn register( // The facts `matching` is derived from, kept for the guard's refusals. // Probe runs before the guard can refuse anything. XART_WRITABLE.store(xart_writable, Relaxed); - COLD.store(cold, Relaxed); let (level, list_loaded) = guard.unwrap_or((0, false)); GUARD_LEVEL.store(u32::from(level), Relaxed); GUARD_LIST.store(list_loaded, Relaxed); @@ -177,7 +176,6 @@ pub(crate) fn restore(state: Restore) { } static XART_WRITABLE: Atomic = Atomic::new(false); -static COLD: Atomic = Atomic::new(false); static GUARD_LEVEL: Atomic = Atomic::new(0); static GUARD_LIST: Atomic = Atomic::new(false); static REFUSALS: Atomic = Atomic::new(0); @@ -188,7 +186,6 @@ fn matching() -> crate::xart_guard::Matching { let level = GUARD_LEVEL.load(Relaxed); MatchFacts { xart_writable: XART_WRITABLE.load(Relaxed), - cold: COLD.load(Relaxed), guard: (level != 0).then(|| { ( u8::try_from(level).unwrap_or(u8::MAX), diff --git a/drivers/soc/apple/keybag.rs b/drivers/soc/apple/keybag.rs index ef629ae4fd484..919035b1a09de 100644 --- a/drivers/soc/apple/keybag.rs +++ b/drivers/soc/apple/keybag.rs @@ -9,6 +9,7 @@ use crate::shim; use crate::store::crc16_ccitt_false; +use crate::xart_guard::Unfinished; use kernel::prelude::*; pub(crate) const KEYBAG_PATH: &CStr = c"/var/lib/aurora-sep-keybag.bin"; @@ -30,6 +31,9 @@ const MAGIC: [u8; 16] = *b"APPLE-SEP-KBAG01"; /// v2 stores the bag secret; a v1 record is refused, not reinterpreted. const VERSION: u32 = 2; +/// Written before CREATE_KEYBAG is sent: until it is replaced, a bag may +/// exist unnamed. +const STATE_INTENT: u32 = 0; const STATE_COMMITTED: u32 = 1; /// Create refused by the enclave: nothing exists, retry allowed (unlike an /// intent record, where a bag may exist unnamed — block retry). @@ -236,8 +240,88 @@ pub(crate) fn read(slot: Slot) -> Result { })) } -pub(crate) fn write_intent(slot: Slot) -> Result<()> { - write_record(slot, 0, &[], &[0; UUID_LEN], &[]) +/// An intent's UUID field records Linux's list ([`Unfinished`]): `AXGI`, 1 if +/// guarded, three zero bytes, then the entry count, little-endian. Older +/// drivers left it zero; every driver reads either as an intent. +const MARK_MAGIC: &[u8; 4] = b"AXGI"; + +fn encode_mark(unfinished: Unfinished) -> [u8; UUID_LEN] { + let mut uuid = [0u8; UUID_LEN]; + let (guarded, entries) = match unfinished { + Unfinished::Unmarked => return uuid, + Unfinished::Unguarded => (0, 0), + Unfinished::Guarded(entries) => (1, entries), + }; + uuid[..4].copy_from_slice(MARK_MAGIC); + uuid[4] = guarded; + uuid[8..].copy_from_slice(&entries.to_le_bytes()); + uuid +} + +fn decode_mark(uuid: &[u8; UUID_LEN]) -> Option { + if *uuid == [0; UUID_LEN] { + return Some(Unfinished::Unmarked); + } + if &uuid[..4] != MARK_MAGIC || uuid[5..8] != [0; 3] { + return None; + } + let mut entries = [0u8; 8]; + entries.copy_from_slice(&uuid[8..]); + let entries = u64::from_le_bytes(entries); + match uuid[4] { + 0 if entries == 0 => Some(Unfinished::Unguarded), + 1 => Some(Unfinished::Guarded(entries)), + _ => None, + } +} + +/// Records the intent, with Linux's list as it is now. +pub(crate) fn write_intent(slot: Slot, unfinished: Unfinished) -> Result<()> { + write_record(slot, STATE_INTENT, &[], &encode_mark(unfinished), &[]) +} + +/// Decodes an intent header; an error for anything but one intact intent. +fn decode_intent(head: &[u8; HEADER]) -> Result { + let word = + |off: usize| u32::from_le_bytes([head[off], head[off + 1], head[off + 2], head[off + 3]]); + if head[..MAGIC.len()] != MAGIC { + return Err(EINVAL); + } + if word(OFF_VERSION) != VERSION { + return Err(ENOTSYNC); + } + if word(OFF_STATE) != STATE_INTENT || word(OFF_LEN) != 0 || word(OFF_SECRET_LEN) != 0 { + return Err(EEXIST); + } + let mut uuid = [0u8; UUID_LEN]; + uuid.copy_from_slice(&head[OFF_UUID..OFF_UUID + UUID_LEN]); + let crc = u16::from_le_bytes([head[OFF_CRC], head[OFF_CRC + 1]]); + if head[OFF_CRC + 2..OFF_CRC + 4] != [0; 2] + || crc16_ccitt_false(&checksum_input(STATE_INTENT, &[], &uuid, &[])?) != crc + { + return Err(EINVAL); + } + decode_mark(&uuid).ok_or(EEXIST) +} + +/// The intent in `slot`, which [`read`] reports as `EEXIST`. +pub(crate) fn unfinished(slot: Slot) -> Result { + let file = shim::StoreFile::open_readonly(slot.path())?; + if file.size()? != HEADER as u64 { + return Err(EINVAL); + } + let mut head = [0u8; HEADER]; + file.read_exact(0, &mut head)?; + decode_intent(&head) +} + +/// The no-bag proof for sending an unfinished create again; the file must +/// still hold the intent that was judged. +pub(crate) fn retry_unfinished(slot: Slot, seen: Unfinished) -> Result { + if unfinished(slot)? != seen { + return Err(EEXIST); + } + Ok(NoStoredKeyBag { slot }) } pub(crate) fn mark_refused(slot: Slot) -> Result<()> { @@ -319,6 +403,27 @@ fn write_record( uuid: &[u8; UUID_LEN], secret: &[u8], ) -> Result<()> { + let head = header(state, wrapped, uuid, secret)?; + + let file = shim::StoreFile::open_trunc(slot.path())?; + // Body first, then the header that vouches for it: a crash between leaves a + // bad checksum, which `read` refuses — the safe side. + if !wrapped.is_empty() { + file.write_all(HEADER as u64, wrapped)?; + } + if !secret.is_empty() { + file.write_all((HEADER + wrapped.len()) as u64, secret)?; + } + file.write_all(0, &head)?; + file.sync() +} + +fn header( + state: u32, + wrapped: &[u8], + uuid: &[u8; UUID_LEN], + secret: &[u8], +) -> Result<[u8; HEADER]> { let crc = crc16_ccitt_false(&checksum_input(state, wrapped, uuid, secret)?); let mut head = [0u8; HEADER]; @@ -329,16 +434,73 @@ fn write_record( head[OFF_CRC..OFF_CRC + 2].copy_from_slice(&crc.to_le_bytes()); head[OFF_UUID..OFF_UUID + UUID_LEN].copy_from_slice(uuid); head[OFF_SECRET_LEN..OFF_SECRET_LEN + 4].copy_from_slice(&(secret.len() as u32).to_le_bytes()); + Ok(head) +} - let file = shim::StoreFile::open_trunc(slot.path())?; - // Body first, then the header that vouches for it: a crash between leaves a - // bad checksum, which `read` refuses — the safe side. - if !wrapped.is_empty() { - file.write_all(HEADER as u64, wrapped)?; +#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] +#[::kernel::macros::kunit_tests(apple_sep_keybag)] +mod tests { + use super::*; + + fn intent(unfinished: Unfinished) -> Result<[u8; HEADER]> { + header(STATE_INTENT, &[], &encode_mark(unfinished), &[]) } - if !secret.is_empty() { - file.write_all((HEADER + wrapped.len()) as u64, secret)?; + + #[test] + fn intents_round_trip() -> Result<()> { + for unfinished in [ + Unfinished::Unmarked, + Unfinished::Unguarded, + Unfinished::Guarded(0), + Unfinished::Guarded(7), + Unfinished::Guarded(u64::MAX), + ] { + assert_eq!(decode_intent(&intent(unfinished)?), Ok(unfinished)); + } + Ok(()) + } + + #[test] + fn an_earlier_drivers_intent_is_unmarked() -> Result<()> { + // What drivers before the mark wrote: a zero UUID. + let head = header(STATE_INTENT, &[], &[0; UUID_LEN], &[])?; + assert_eq!(decode_intent(&head), Ok(Unfinished::Unmarked)); + Ok(()) + } + + #[test] + fn damaged_or_foreign_intents_stay_ambiguous() -> Result<()> { + let good = intent(Unfinished::Guarded(3))?; + // A flipped mark bit no longer matches the checksum. + let mut flipped = good; + flipped[OFF_UUID + 8] ^= 1; + assert_eq!(decode_intent(&flipped), Err(EINVAL)); + // A UUID that is neither zero nor a mark, with a matching checksum. + let mut other = [0u8; UUID_LEN]; + other[0] = 1; + assert_eq!( + decode_intent(&header(STATE_INTENT, &[], &other, &[])?), + Err(EEXIST) + ); + // An unguarded mark with a length is not one this driver writes. + let mut odd = encode_mark(Unfinished::Unguarded); + odd[8] = 1; + assert_eq!( + decode_intent(&header(STATE_INTENT, &[], &odd, &[])?), + Err(EEXIST) + ); + // Committed and refused records are not intents. + let committed = header(STATE_COMMITTED, &[1], &[2; UUID_LEN], &[3])?; + assert_eq!(decode_intent(&committed), Err(EEXIST)); + let refused = header(STATE_REFUSED, &[], &[0; UUID_LEN], &[])?; + assert_eq!(decode_intent(&refused), Err(EEXIST)); + // Another format. + let mut magic = good; + magic[0] ^= 1; + assert_eq!(decode_intent(&magic), Err(EINVAL)); + let mut version = good; + version[OFF_VERSION] = 1; + assert_eq!(decode_intent(&version), Err(ENOTSYNC)); + Ok(()) } - file.write_all(0, &head)?; - file.sync() } diff --git a/drivers/soc/apple/sks.rs b/drivers/soc/apple/sks.rs index a6e9253d98e8e..812244de792e9 100644 --- a/drivers/soc/apple/sks.rs +++ b/drivers/soc/apple/sks.rs @@ -15,8 +15,12 @@ struct KeybagCreateIntent<'a> { } impl<'a> KeybagCreateIntent<'a> { - fn new(dev: &'a device::Device, slot: keybag::Slot) -> Option { - if let Err(e) = keybag::write_intent(slot) { + fn new( + dev: &'a device::Device, + slot: keybag::Slot, + unfinished: crate::xart_guard::Unfinished, + ) -> Option { + if let Err(e) = keybag::write_intent(slot, unfinished) { dev_err!(dev, "sks: cannot persist keybag-create intent: {:?}\n", e); return None; } @@ -626,10 +630,41 @@ impl SepData { } } + /// The no-bag proof for an unfinished create, if Linux's list shows it + /// left nothing behind. + fn sks_retry_unfinished_create(&self, slot: keybag::Slot) -> Option { + let unfinished = keybag::unfinished(slot).ok()?; + let listed = self + .with_store(|store| store.guard_listed_at_load()) + .flatten(); + if !crate::xart_guard::may_retry_create(unfinished, listed) { + dev_err!( + self.dev, + "sks: an earlier CREATE_KEYBAG never completed and may have left a bag (intent {:?}, xART guard list {:?}); not creating another\n", + unfinished, + listed + ); + return None; + } + let proof = keybag::retry_unfinished(slot, unfinished).ok()?; + dev_notice!( + self.dev, + "sks: an earlier CREATE_KEYBAG never completed and left no record; creating the identity keybag again\n" + ); + Some(proof) + } + pub(crate) fn sks_provision_identity_keybag(&self) -> bool { - let proof = match keybag::read(keybag::Slot::Identity) { + let identity = keybag::Slot::Identity; + let proof = match keybag::read(identity) { Ok(keybag::State::Present(_)) => return true, - Ok(keybag::State::Absent(proof)) => proof, + Ok(keybag::State::Absent(proof)) => Ok(proof), + // An intent: a create was sent and never committed. + Err(e) if e == EEXIST => self.sks_retry_unfinished_create(identity).ok_or(e), + Err(e) => Err(e), + }; + let proof = match proof { + Ok(proof) => proof, Err(e) => { dev_err!( self.dev, @@ -640,7 +675,13 @@ impl SepData { } }; let slot = proof.slot(); - let mut intent = match KeybagCreateIntent::new(&self.dev, slot) { + // A later boot compares this with Linux's list to tell whether the + // create left anything. + let unfinished = match self.with_store(|store| store.guard_listed_now()).flatten() { + Some(entries) => crate::xart_guard::Unfinished::Guarded(entries), + None => crate::xart_guard::Unfinished::Unguarded, + }; + let mut intent = match KeybagCreateIntent::new(&self.dev, slot, unfinished) { Some(intent) => intent, None => return false, }; @@ -670,7 +711,18 @@ impl SepData { } }; intent.sending(); - let Some(out) = self.sks_exchange(request.name, request.msg, &request.img) else { + // The window's host writes and flushes land inside this exchange, so + // it gets the 13.5 key store's longer wait. + let exchanged = crate::xart_guard::keybag_create_window(|| { + self.sks_exchange_patient( + request.name, + request.msg, + &request.img, + SKS_TIMEOUT_13_MS, + true, + ) + }); + let Some(out) = exchanged else { dev_err!( self.dev, "sks: CREATE_KEYBAG exchange did not complete (buffers unregistered, wedged, or OOL write failed)\n" diff --git a/drivers/soc/apple/xart_guard.rs b/drivers/soc/apple/xart_guard.rs index 975f41d624bb4..5c65ffe085e56 100644 --- a/drivers/soc/apple/xart_guard.rs +++ b/drivers/soc/apple/xart_guard.rs @@ -50,6 +50,11 @@ //! ([`ALLOW_PATH`]): one record, named or the first of its kind, that the //! enclave may rewrite during the next boot only, on the same terms. The //! driver empties the file before it uses the allowance. +//! +//! Creating Linux's identity keybag also rewrites one existing kind-4 +//! record, so the create runs in the same window ([`keybag_create_window`]). +//! An unfinished create is sent again only if Linux's list shows it left no +//! record behind ([`may_retry_create`]). use crate::shim::StoreFile; use crate::xart_store::{crc32_ieee, Key}; @@ -60,29 +65,97 @@ use kernel::sync::atomic::{Atomic, Relaxed}; pub(crate) const OWNED_PATH: &CStr = c"/var/lib/aurora-sep-xart-owned.bin"; /// Host file holding the one-time allowance for a boot policy change. pub(crate) const ALLOW_PATH: &CStr = c"/var/lib/aurora-sep-xart-allow-once.bin"; -/// Host file keeping the previous value of every record rewritten for a -/// boot policy change. +/// Host file keeping the previous value of every existing record a window +/// or allowance let the enclave rewrite. pub(crate) const PREVIOUS_PATH: &CStr = c"/var/lib/aurora-sep-xart-policy-previous.bin"; const PREVIOUS_MAGIC: &[u8; 4] = b"AXGP"; -/// The kind of session record a boot policy change rewrites. +/// The kind of session record a window or allowance may rewrite. const POLICY_KIND: u8 = 4; -/// Whether the first unlock of an existing identity keybag has started this -/// boot, and whether it is running now. +/// Why the guard let the enclave rewrite an existing record Linux did not +/// create. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Rewrite { + /// The first unlock of an existing keybag, or a one-time allowance. + PolicyChange = 1, + /// The creation of Linux's identity keybag. + KeybagCreate = 2, +} + +impl Rewrite { + fn from_raw(raw: u32) -> Option { + match raw { + 1 => Some(Rewrite::PolicyChange), + 2 => Some(Rewrite::KeybagCreate), + _ => None, + } + } + + /// The reason as the kernel log gives it. + pub(crate) fn describe(self) -> &'static str { + match self { + Rewrite::PolicyChange => "for a boot policy change", + Rewrite::KeybagCreate => "while Linux's identity keybag was created", + } + } +} + +/// Whether this boot's one window has started, and the [`Rewrite`] it is +/// open for now (0 when closed). static WINDOW_STARTED: Atomic = Atomic::new(false); -static WINDOW_OPEN: Atomic = Atomic::new(false); +static WINDOW_OPEN: Atomic = Atomic::new(0); + +/// The window open now. +fn open_window() -> Option { + Rewrite::from_raw(WINDOW_OPEN.load(Relaxed)) +} + +/// Runs `f` with the window open for `why`, unless this boot had one already. +fn with_window(why: Rewrite, f: impl FnOnce() -> T) -> T { + if WINDOW_STARTED.xchg(true, Relaxed) { + return f(); + } + WINDOW_OPEN.store(why as u32, Relaxed); + let result = f(); + WINDOW_OPEN.store(0, Relaxed); + result +} /// Runs `unlock`, the unlock of an identity keybag that already existed, /// with the boot policy window open if it is this boot's first. Later /// unlocks run with it closed. pub(crate) fn policy_window(unlock: impl FnOnce() -> T) -> T { - if WINDOW_STARTED.xchg(true, Relaxed) { - return unlock(); + with_window(Rewrite::PolicyChange, unlock) +} + +/// Runs `create`, the identity keybag's CREATE_KEYBAG exchange, with the +/// window open: the enclave rewrites one existing kind-4 record to create it. +pub(crate) fn keybag_create_window(create: impl FnOnce() -> T) -> T { + with_window(Rewrite::KeybagCreate, create) +} + +/// Linux's list as an unfinished create's intent recorded it. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Unfinished { + /// Written by a driver that did not record it. + Unmarked, + /// No guard ran with a list. + Unguarded, + /// The list had this many entries. + Guarded(u64), +} + +/// Whether an unfinished create may be sent again, given the entries in +/// Linux's list at load (`None` without a list). Every new session record is +/// listed before it is written, so an unchanged list means the create left +/// nothing; an unmarked intent needs an empty list. +pub(crate) fn may_retry_create(unfinished: Unfinished, listed: Option) -> bool { + match (unfinished, listed) { + (_, None) => false, + (Unfinished::Guarded(then), Some(now)) => now == then, + (Unfinished::Unmarked, Some(now)) => now == 0, + (Unfinished::Unguarded, Some(_)) => false, } - WINDOW_OPEN.store(true, Relaxed); - let result = unlock(); - WINDOW_OPEN.store(false, Relaxed); - result } /// Keeps the window shut for the rest of the boot: a keybag or ref-key @@ -91,8 +164,8 @@ pub(crate) fn close_policy_window() { WINDOW_STARTED.store(true, Relaxed); } -/// Saves `value`, the current value of `key`, before it is rewritten for a -/// boot policy change. The rewrite goes ahead only once this is durable. +/// Saves `value`, the current value of `key`, before the enclave rewrites +/// it. The rewrite goes ahead only once this is durable. pub(crate) fn save_previous(key: &Key, value: &[u8]) -> Result<()> { let (file, _created) = StoreFile::open_host(PREVIOUS_PATH)?; file.sync_parent()?; @@ -177,8 +250,6 @@ pub(crate) enum Matching { pub(crate) struct MatchFacts { /// The store opened with writes enabled. pub(crate) xart_writable: bool, - /// The platform cold-boots its enclave. - pub(crate) cold: bool, /// The guard's effective level and whether its list loaded, or `None` /// when no guard runs. pub(crate) guard: Option<(u8, bool)>, @@ -187,22 +258,15 @@ pub(crate) struct MatchFacts { } impl MatchFacts { - /// A match rewrites session records that existed before it (seen on a - /// T6000). It is blocked when xART is read-only, once the guard has - /// refused anything, when the guard has no list to allow Linux's records - /// by, and on a cold-boot Mac: creating the keybag there needs the guard - /// off, so the records a match rewrites were never listed. - /// - /// Nothing else proves an unguarded setup. An empty list in particular - /// does not: a guarded keybag creation on an M2 writes no record, and a - /// rollback can empty the list. So a guarded warm-attach Mac is - /// unverified until a refusal says otherwise. + /// A match rewrites existing session records, so it is blocked when + /// xART is read-only, after a refusal, or without a list. Otherwise a + /// guard leaves it unverified: not even an empty list proves the setup + /// ran unguarded. pub(crate) fn matching(&self) -> Matching { match self.guard { _ if !self.xart_writable || self.refusals != 0 => Matching::Blocked, None => Matching::Usable, Some((_, false)) => Matching::Blocked, - Some(_) if self.cold => Matching::Blocked, Some(_) => Matching::Unverified, } } @@ -363,18 +427,20 @@ pub(crate) struct Guard { file: Option, /// Byte offset of the next entry. end: u64, + /// Entries in the list at load, torn ones included. + loaded_entries: u64, owned: KVec, existed: KVec, refused: usize, /// The one-time allowance for a boot policy change, until it is used. allowance: Option, - /// The boot policy window let its one rewrite through. + /// This boot's window rewrite is used. window_used: bool, - /// The write just allowed is a boot policy change rewrite: its previous - /// value must be saved first ([`Guard::take_save_previous`]). - save_previous: bool, - /// Whether the boot policy window is open; the tests use their own. - window_open: fn() -> bool, + /// Why the write just allowed rewrites an existing record; its previous + /// value is saved first. + save_previous: Option, + /// The window open now; the tests set their own. + window: fn() -> Option, } impl Guard { @@ -399,13 +465,14 @@ impl Guard { level, file: None, end: 0, + loaded_entries: 0, owned: KVec::new(), existed: existing, refused: 0, allowance: None, window_used: false, - save_previous: false, - window_open: || WINDOW_OPEN.load(Relaxed), + save_previous: None, + window: open_window, }; // A list created this boot (or by a crashed one) is only safe to adopt // into once its name is durable: fsync() of the file alone does not @@ -471,6 +538,7 @@ impl Guard { } // A torn tail entry is overwritten by the next adoption. self.end = (whole * ENTRY_SIZE) as u64; + self.loaded_entries = whole as u64; Ok(()) } @@ -484,6 +552,16 @@ impl Guard { self.file.is_some() } + /// Entries in Linux's list now, or `None` without a list. + pub(crate) fn listed_now(&self) -> Option { + self.file.as_ref().map(|_| self.end / ENTRY_SIZE as u64) + } + + /// Entries in Linux's list at load, or `None` without a list. + pub(crate) fn listed_at_load(&self) -> Option { + self.file.as_ref().map(|_| self.loaded_entries) + } + fn adopt(&mut self, key: &Key) -> Result<()> { let file = self.file.as_ref().ok_or(ENOENT)?; // The file's length, not the number of distinct keys: a torn or @@ -522,56 +600,62 @@ impl Guard { e ) }), - Verdict::Refuse if write && self.take_allowance_for(key) => { - self.save_previous = true; - Ok(()) - } - Verdict::Refuse => { - crate::diag::xart_guard_refused(); - self.refused += 1; - if self.refused <= LOGGED_REFUSALS { - let action = if write { "rewrite" } else { "delete" }; - pr_warn!( - "xART guard: refused to {} kind-{} record {}, which this Linux install did not create\n", - action, - key.kind, - Id(&key.uuid) - ); + Verdict::Refuse if write => match self.take_allowance_for(key) { + Some(why) => { + self.save_previous = Some(why); + Ok(()) } - Err(EPERM) - } + None => Err(self.refuse(key, write)), + }, + Verdict::Refuse => Err(self.refuse(key, write)), } } - /// Uses up the one-time allowance if it covers `key`, or else the boot - /// policy window's one rewrite. - fn take_allowance_for(&mut self, key: &Key) -> bool { + fn refuse(&mut self, key: &Key, write: bool) -> Error { + crate::diag::xart_guard_refused(); + self.refused += 1; + if self.refused <= LOGGED_REFUSALS { + let action = if write { "rewrite" } else { "delete" }; + pr_warn!( + "xART guard: refused to {} kind-{} record {}, which this Linux install did not create\n", + action, + key.kind, + Id(&key.uuid) + ); + } + EPERM + } + + /// Uses up the allowance if it covers `key`, else this boot's window + /// rewrite. + fn take_allowance_for(&mut self, key: &Key) -> Option { if self.allowance.is_some_and(|allowance| allowance.covers(key)) { self.allowance = None; - return true; + return Some(Rewrite::PolicyChange); } - if key.kind == POLICY_KIND && !self.window_used && (self.window_open)() { - self.window_used = true; - return true; + if key.kind != POLICY_KIND || self.window_used { + return None; } - false + let why = (self.window)()?; + self.window_used = true; + Some(why) } - /// Whether the write [`Guard::check`] just allowed rewrites a record for - /// a boot policy change, whose previous value must be saved first. - pub(crate) fn take_save_previous(&mut self) -> bool { - core::mem::take(&mut self.save_previous) + /// Why the write just allowed rewrites an existing record, if it does. + pub(crate) fn take_save_previous(&mut self) -> Option { + self.save_previous.take() } /// The previous value could not be saved: the rewrite is refused after /// all. - pub(crate) fn refuse_unsaved(&mut self, key: &Key, e: Error) { + pub(crate) fn refuse_unsaved(&mut self, key: &Key, why: Rewrite, e: Error) { crate::diag::xart_guard_refused(); self.refused += 1; pr_warn!( - "xART guard: could not save kind-{} record {} before its boot policy rewrite ({:?}); refusing it\n", + "xART guard: could not save kind-{} record {} before rewriting it {} ({:?}); refusing it\n", key.kind, Id(&key.uuid), + why.describe(), e ); } @@ -587,15 +671,9 @@ mod tests { Key::new(4, [byte; 16]) } - fn facts( - xart_writable: bool, - cold: bool, - guard: Option<(u8, bool)>, - refusals: u32, - ) -> Matching { + fn facts(xart_writable: bool, guard: Option<(u8, bool)>, refusals: u32) -> Matching { MatchFacts { xart_writable, - cold, guard, refusals, } @@ -604,30 +682,25 @@ mod tests { #[test] fn matching_without_a_guard_is_usable() { - // The Neo (warm, level 0) and an M1 Pro pinned to xart_guard=0 (cold). - assert_eq!(facts(true, false, None, 0), Matching::Usable); - assert_eq!(facts(true, true, None, 0), Matching::Usable); + assert_eq!(facts(true, None, 0), Matching::Usable); } #[test] - fn matching_under_a_listed_warm_guard_is_unverified() { - // An M2 set up under the guard. An empty list is still "loaded", and - // does not make the setup unguarded. - assert_eq!(facts(true, false, Some((1, true)), 0), Matching::Unverified); - assert_eq!(facts(true, false, Some((2, true)), 0), Matching::Unverified); + fn matching_under_a_listed_guard_is_unverified() { + // An empty list is still "loaded", and does not make the setup + // unguarded. + assert_eq!(facts(true, Some((1, true)), 0), Matching::Unverified); + assert_eq!(facts(true, Some((2, true)), 0), Matching::Unverified); } #[test] fn matching_is_blocked_where_it_is_known_to_be() { - // A cold-boot M1-family Mac under the default level 1. - assert_eq!(facts(true, true, Some((1, true)), 0), Matching::Blocked); // No list: nothing that existed at probe is Linux's. - assert_eq!(facts(true, false, Some((1, false)), 0), Matching::Blocked); + assert_eq!(facts(true, Some((1, false)), 0), Matching::Blocked); // A refusal seen. - assert_eq!(facts(true, false, Some((1, true)), 1), Matching::Blocked); + assert_eq!(facts(true, Some((1, true)), 1), Matching::Blocked); // xART read-only: no guard runs, and no write can land. - assert_eq!(facts(false, false, None, 0), Matching::Blocked); - assert_eq!(facts(false, true, None, 0), Matching::Blocked); + assert_eq!(facts(false, None, 0), Matching::Blocked); } #[test] @@ -1039,7 +1112,7 @@ mod tests { assert_eq!(guard.check(&policy, false), Err(EPERM)); assert_eq!(guard.check(&other, true), Err(EPERM)); assert_eq!(guard.check(&policy, true), Ok(())); - assert!(guard.take_save_previous()); + assert_eq!(guard.take_save_previous(), Some(Rewrite::PolicyChange)); assert_eq!(guard.check(&policy, true), Err(EPERM)); // The record is not adopted. assert!(bytes.borrow().is_empty()); @@ -1078,18 +1151,117 @@ mod tests { let (a, b, c) = (session(1), session(2), Key::new(3, [3; 16])); let mut guard = Guard::with_file(1, keys(&[a, b, c])?, Ok(mem(&bytes))); // Outside the window nothing changes. - guard.window_open = || false; + guard.window = || None; assert_eq!(guard.check(&a, true), Err(EPERM)); - guard.window_open = || true; + guard.window = || Some(Rewrite::PolicyChange); assert_eq!(guard.check(&c, true), Err(EPERM)); assert_eq!(guard.check(&a, false), Err(EPERM)); assert_eq!(guard.check(&a, true), Ok(())); - assert!(guard.take_save_previous()); - assert!(!guard.take_save_previous()); + assert_eq!(guard.take_save_previous(), Some(Rewrite::PolicyChange)); + assert_eq!(guard.take_save_previous(), None); assert_eq!(guard.check(&b, true), Err(EPERM)); assert_eq!(guard.check(&a, true), Err(EPERM)); // Not adopted. assert!(bytes.borrow().is_empty()); Ok(()) } + + #[test] + fn the_create_window_lets_one_kind_4_rewrite_through() -> Result<()> { + // A create: one existing kind-4 record rewritten, a new record and a + // root written. + let bytes = RefCell::new(KVec::new()); + let attach = session(1); + let other = session(2); + let kind3 = Key::new(3, [3; 16]); + let new = session(9); + let mut guard = Guard::with_file(1, keys(&[attach, other, kind3])?, Ok(mem(&bytes))); + guard.window = || Some(Rewrite::KeybagCreate); + // Never another kind, never a delete. + assert_eq!(guard.check(&kind3, true), Err(EPERM)); + assert_eq!(guard.check(&attach, false), Err(EPERM)); + // A new record is adopted as before and does not use the window. + assert_eq!(guard.check(&new, true), Ok(())); + assert_eq!(guard.take_save_previous(), None); + assert_eq!(guard.check(&Key::root(2), true), Ok(())); + assert_eq!(guard.take_save_previous(), None); + // The one rewrite, which saves the previous value first. + assert_eq!(guard.check(&attach, true), Ok(())); + assert_eq!(guard.take_save_previous(), Some(Rewrite::KeybagCreate)); + // Only one: neither the same record again nor another one. + assert_eq!(guard.check(&attach, true), Err(EPERM)); + assert_eq!(guard.check(&other, true), Err(EPERM)); + // Only the new record is Linux's. + assert_eq!(listed(&bytes).as_slice(), &[Some(new)]); + Ok(()) + } + + #[test] + fn level_2_keeps_the_roots_in_the_create_window() -> Result<()> { + let bytes = RefCell::new(KVec::new()); + let mut guard = Guard::with_file(2, keys(&[session(1)])?, Ok(mem(&bytes))); + guard.window = || Some(Rewrite::KeybagCreate); + assert_eq!(guard.check(&Key::root(1), true), Err(EPERM)); + assert_eq!(guard.check(&Key::root(2), true), Err(EPERM)); + assert_eq!(guard.take_save_previous(), None); + Ok(()) + } + + #[test] + fn an_allowance_is_used_before_the_create_window() -> Result<()> { + let bytes = RefCell::new(KVec::new()); + let (named, other) = (session(1), session(2)); + let mut guard = Guard::with_file(1, keys(&[named, other])?, Ok(mem(&bytes))); + guard.allowance = Some(Allowance::Record(named)); + guard.window = || Some(Rewrite::KeybagCreate); + assert_eq!(guard.check(&named, true), Ok(())); + assert_eq!(guard.take_save_previous(), Some(Rewrite::PolicyChange)); + // The window still has its one rewrite. + assert_eq!(guard.check(&other, true), Ok(())); + assert_eq!(guard.take_save_previous(), Some(Rewrite::KeybagCreate)); + Ok(()) + } + + #[test] + fn the_list_length_is_kept_from_load() -> Result<()> { + let first = session(4); + let mut list = list_of(&[first])?; + // A torn tail is not an entry yet. + list.extend_from_slice(&encode(&session(5))[..10], GFP_KERNEL)?; + let bytes = RefCell::new(list); + let mut guard = Guard::with_file(1, keys(&[first])?, Ok(mem(&bytes))); + assert_eq!(guard.listed_at_load(), Some(1)); + assert_eq!(guard.listed_now(), Some(1)); + assert_eq!(guard.check(&session(6), true), Ok(())); + assert_eq!(guard.listed_at_load(), Some(1)); + assert_eq!(guard.listed_now(), Some(2)); + let unlisted = Guard::>::with_file(1, KVec::new(), Err(ENOENT)); + assert_eq!(unlisted.listed_at_load(), None); + assert_eq!(unlisted.listed_now(), None); + Ok(()) + } + + #[test] + fn a_create_that_listed_nothing_may_be_sent_again() { + // The list did not grow since the intent: the enclave kept nothing. + assert!(may_retry_create(Unfinished::Guarded(0), Some(0))); + assert!(may_retry_create(Unfinished::Guarded(3), Some(3))); + // It did: a record of that create may exist. + assert!(!may_retry_create(Unfinished::Guarded(3), Some(4))); + // A list shorter than at the intent was rolled back or replaced. + assert!(!may_retry_create(Unfinished::Guarded(3), Some(2))); + // No guard, or no list, now: nothing proves it. + assert!(!may_retry_create(Unfinished::Guarded(3), None)); + assert!(!may_retry_create(Unfinished::Unmarked, None)); + // Sent without a guard: the enclave's records were never listed. + assert!(!may_retry_create(Unfinished::Unguarded, Some(0))); + assert!(!may_retry_create(Unfinished::Unguarded, None)); + } + + #[test] + fn an_older_intent_may_be_sent_again_only_with_an_empty_list() { + // A create the guard refused before intents recorded the list. + assert!(may_retry_create(Unfinished::Unmarked, Some(0))); + assert!(!may_retry_create(Unfinished::Unmarked, Some(1))); + } } diff --git a/drivers/soc/apple/xart_store.rs b/drivers/soc/apple/xart_store.rs index 1f632f898a259..4c3b4f95a9500 100644 --- a/drivers/soc/apple/xart_store.rs +++ b/drivers/soc/apple/xart_store.rs @@ -353,8 +353,8 @@ impl Store { if let Some(guard) = self.guard.as_mut() { guard.check(key, true)?; - if guard.take_save_previous() { - self.save_previous(key)?; + if let Some(why) = guard.take_save_previous() { + self.save_previous(key, why)?; } } @@ -395,9 +395,9 @@ impl Store { Ok(()) } - /// Saves the current value of `key` before the guard's boot policy - /// rewrite of it, and logs the rewrite. Refuses it when that fails. - fn save_previous(&mut self, key: &Key) -> Result<()> { + /// Saves the current value of `key` before the guard lets the enclave + /// rewrite it, and logs the rewrite. Refuses it when that fails. + fn save_previous(&mut self, key: &Key, why: crate::xart_guard::Rewrite) -> Result<()> { let saved = match self.read(key) { Ok(Some(value)) => crate::xart_guard::save_previous(key, &value), Ok(None) => Err(ENOENT), @@ -406,16 +406,17 @@ impl Store { match saved { Ok(()) => { pr_notice!( - "xART guard: allowed one rewrite of kind-{} record {} for a boot policy change; its previous value is in {}\n", + "xART guard: allowed one rewrite of kind-{} record {} {}; its previous value is in {}\n", key.kind, crate::xart_guard::Id(&key.uuid), + why.describe(), crate::xart_guard::PREVIOUS_PATH ); Ok(()) } Err(e) => { if let Some(guard) = self.guard.as_mut() { - guard.refuse_unsaved(key, e); + guard.refuse_unsaved(key, why, e); } Err(EPERM) } @@ -464,6 +465,16 @@ impl Store { Ok(()) } + /// Entries in the guard's list now, or `None` without a guarded list. + pub(crate) fn guard_listed_now(&self) -> Option { + self.guard.as_ref().and_then(|guard| guard.listed_now()) + } + + /// Entries in the guard's list at load, or `None` without a guarded list. + pub(crate) fn guard_listed_at_load(&self) -> Option { + self.guard.as_ref().and_then(|guard| guard.listed_at_load()) + } + /// The guard's effective level and whether its list was loaded, or /// `None` when no guard runs. pub(crate) fn guard_state(&self) -> Option<(u8, bool)> { diff --git a/tools/aurora-sep/XART-SAFETY.md b/tools/aurora-sep/XART-SAFETY.md index dc06aed51473e..98f5a65fa6f69 100644 --- a/tools/aurora-sep/XART-SAFETY.md +++ b/tools/aurora-sep/XART-SAFETY.md @@ -59,9 +59,8 @@ records are allowed only if they are on Linux's list: records the enclave created while the guard was on. Which records they are has not been established, so: -- On a Mac whose Touch ID was set up with `xart_guard=0` (every cold-boot - Mac, see below, and any Mac that used Touch ID before the guard existed), - none of Linux's records are on the list. A guarded boot restores the +- On a Mac whose Touch ID was set up with `xart_guard=0`, or before the + guard existed, none of Linux's records are on the list. A guarded boot restores the fingerprints and then refuses the match's write ("xART guard: refused to rewrite a kind-4 record this Linux install did not create"), and the verify fails. Such a Mac needs `xart_guard=0` to use Touch ID, not only to @@ -77,9 +76,10 @@ another finger into an existing keybag does not create one. | Platform | Bootstrap | Keybag creation under level 1 | | --- | --- | --- | -| M1 (T8103), M1 Pro/Max (T6000/T6001) | cold boot | Refused: it rewrites a pre-Linux session record (seen on T6000; T8103 and T6001 untested). Needs `xart_guard=0`. | +| M1 (T8103), M1 Pro/Max (T6000/T6001) | cold boot | Rewrites one pre-Linux session record (T6000). The keybag creation window lets it through if it is kind-4, which is unconfirmed there. | | M2 (T8112) | warm attach | No xART writes (J413), so the guard does not affect it. | -| M2 Pro/Max (T6020/T6021), M3 (T8122) | warm attach | Untested. | +| M2 Pro/Max (T6020/T6021) | warm attach | Rewrites one existing kind-4 record (J414s, 2026-11-02); the keybag creation window lets it through. | +| M3 (T8122) | warm attach | Untested. | | MacBook Neo (T8140) | warm attach | Guard off by default. | Whether the enclave writes xART records while enrolling a finger has not @@ -90,7 +90,7 @@ been measured on a guarded Mac; a refused write fails the enrolment closed. A refusal fails closed: the enclave's request fails, no record changes, and the kernel log names the refused kind. What to do depends on the case: -- A cold-boot Mac, or any Mac set up with the guard off: set +- A Mac set up with the guard off: set `options apple_sep xart_guard=0` in `/etc/modprobe.d/` and reboot. Do it only once the rewrite of the shared, pre-Linux record has been accepted for that Mac (see the next section), and treat macOS Touch ID as unverified @@ -99,6 +99,10 @@ the kernel log names the refused kind. What to do depends on the case: the log line. `xart_guard=0` restores use at the cost of the protection. - After a boot policy change (a new Stage 1, another boot object), see "Boot policy changes" below: one record, one boot, with a backup first. +- A keybag create that never answered ("identity keybag state is + ambiguous: EEXIST" on every later boot): see "An unfinished keybag + create" below. A kernel with that rule sends the create again by itself + when the list proves nothing was created. - A list that could not be read: every record that existed at boot is protected and nothing can be adopted, so new records cannot be written either. @@ -178,6 +182,47 @@ The copy of the gigalocker is evidence and a last resort, not an undo: writing an old copy back does not roll back the enclave's anti-replay counters, and the enclave may refuse it. +### The keybag creation window + +Creating Linux's identity keybag makes the enclave rewrite one existing +kind-4 session record. The record is shared enclave state that the enclave +reads at every attach, under every OS, and it predates every Linux install, +so it is never on Linux's list. Refused, the enclave stops answering the +create, and the key store is unusable for the rest of the boot. + +So the driver runs the identity CREATE_KEYBAG exchange in a window +(`keybag_create_window` in `xart_guard.rs`), on the boot policy window's +terms: + +- only during that exchange, which runs only when the host holds no keybag; +- one rewrite per boot, shared with the boot policy window, of one kind-4 + record that existed at probe; never a delete or another kind, and the + roots still follow the level; +- only once the record's previous value is durable in + `/var/lib/aurora-sep-xart-policy-previous.bin`; +- the record is not added to Linux's list. + +The kernel log names the record ("allowed one rewrite of kind-4 record ... +while Linux's identity keybag was created"). Every other existing session +record stays refused, including any record another Linux install made. +Another install's keybag, ref-key and fingerprints are files on its own +root file system. + +### An unfinished keybag create + +The driver writes an intent to `/var/lib/aurora-sep-keybag.bin` before it +sends CREATE_KEYBAG and replaces it once the enclave answers. If a later +boot finds the intent, a bag may exist that nothing names, so the driver +does not create another ("identity keybag state is ambiguous: EEXIST"). + +The intent records how many entries Linux's list had. Every new session +record is listed before it is written, so if the list has not grown, the +create left nothing and a later boot sends it again ("creating the identity +keybag again"). An intent from an older kernel records no length, and is +sent again only while the list is empty. That is wrong only for a create +sent with the guard off that did keep a record. It then leaves one unnamed +bag, as `install-aurora-sep.sh --reset-touchid` does. + ### What `aurora-sep-ready` reports "Touch ID: restored N" says the fingerprints came back; it does not say a @@ -190,8 +235,8 @@ and from those `matching`: | `matching` | When | `aurora-sep-ready` | | --- | --- | --- | | `usable` | xART writes enabled, no guard | ready | -| `unverified` | guard on a warm-attach Mac, list loaded, nothing refused yet | restored, matching UNVERIFIED (not called ready; exit 0) | -| `blocked` | xART writes disabled, a refusal seen, no list, or a guard on a cold-boot Mac | NOT ready (exit 1), with the reason and the `xart_guard=0` opt-out | +| `unverified` | guard on, list loaded, nothing refused yet | restored, matching UNVERIFIED (not called ready; exit 0) | +| `blocked` | xART writes disabled, a refusal seen, or no list | NOT ready (exit 1), with the reason and the `xart_guard=0` opt-out | | absent | a kernel that does not report it | restored, matching UNVERIFIED (exit 0) | The diag ABI stays at version 2: these attributes were added to it, and a @@ -224,8 +269,7 @@ it: - A reinstall, or deleting the file, empties the list. Every Linux record then looks like another OS's. Copy the file back from the old install if you have it. Without it, those records stay protected; resetting Touch ID - (`install-aurora-sep.sh --reset-touchid`) creates a new keybag, which on a - cold-boot Mac needs `xart_guard=0` as above. + (`install-aurora-sep.sh --reset-touchid`) creates a new keybag. - A torn last entry, from a crash while appending, is ignored at the next probe and overwritten by the next new record. @@ -247,12 +291,13 @@ Creating the keybag makes the enclave do three things: The rewritten record is one of the four session records the enclave reads at every attach, and it grows by 71 bytes per keybag created. It is not tied to the OS identity, so giving Linux its own identity does not keep this write -away from records macOS also reads. A driver that refuses to rewrite records -Linux did not create cannot create a keybag on these Macs. +away from records macOS also reads. The keybag creation window lets that one +rewrite through if the record is kind-4. -Warm-attach Macs behave differently. On an M2 (T8112, J413), keybag creation -made no xART writes at all. On the J700, the macOS-era records stayed -byte-identical through Linux enrolment. +Warm-attach Macs differ. On an M2 (T8112, J413), keybag creation made no xART +writes at all, and on the J700 the macOS-era records stayed byte-identical +through Linux enrolment. An M2 Pro (T6020, J414s) rewrites one kind-4 record, +as here. On a T600x Mac where Linux has created its keybag, treat macOS Touch ID as unverified until macOS Touch ID unlock and keychain access have been checked diff --git a/tools/aurora-sep/aurora-sep-ready b/tools/aurora-sep/aurora-sep-ready index a00c9be3e2019..34d5ca6dfe3db 100644 --- a/tools/aurora-sep/aurora-sep-ready +++ b/tools/aurora-sep/aurora-sep-ready @@ -16,8 +16,7 @@ # # Restored is not enough to match: a match rewrites xART session records, # and the xART guard (apple_sep xart_guard) refuses that where it is known -# to, for instance on a cold-boot Mac whose Touch ID was set up with the -# guard off. So, from diag "matching": +# to, for instance once it has refused a write. So, from diag "matching": # usable ready: no guard runs and xART is writable; # blocked NOT ready, with the reason and, for the guard, the # documented xart_guard=0 opt-out and what it gives up; @@ -111,8 +110,6 @@ blocked_reason() { echo "the xART guard (level $(attr xart_guard)) refused $refusals write(s) of records this Linux install did not create. $optout" elif [ "$(attr xart_guard_list)" = unavailable ]; then echo "the xART guard could not load its list of Linux's records, so it refuses every session write. $optout" - elif [ "$(attr boot)" = cold ]; then - echo "on this cold-boot Mac the keybag can only be created with the xART guard off, so the session records a match rewrites are not on the guard's list. $optout" else echo "the xART guard refuses a match's write. $optout" fi diff --git a/tools/aurora-sep/packaging/arch/aurora-sep/PKGBUILD b/tools/aurora-sep/packaging/arch/aurora-sep/PKGBUILD index cb658d657b121..18ee14983be99 100644 --- a/tools/aurora-sep/packaging/arch/aurora-sep/PKGBUILD +++ b/tools/aurora-sep/packaging/arch/aurora-sep/PKGBUILD @@ -38,7 +38,7 @@ source=(load-driver sha256sums=('129225c2602290221fc38f754c486e1cba8f0b160e56c5aee2446100aa4e8a04' '2f401629733d280a1b8a5a74f5b668d42307f9885f916abf9e377a811e358b9a' 'f8d0c4b9385e6716d5ac5dd9941a57459fbc551a8238383bcde36cd929d0523f' - '40f0d21446a85bab3e3800b31342e94e553af0fcde2c985503147519737e011d' + 'a353bb89022816bbd6acf446e9e5d09945e06a75cddecbb077a3d99c25aef061' 'f38421360c2f885bb102bc5076d81006d352d03d12b5fb35b4da8a5132f4ccc0' '8d8df0ba2a6861b2edb2110e606906d84ca4848617621ad3cb48493a1522971d' '0695bd9ce4c207241db13c3ea07819a032d8213417021378f5015ab9317cde12' @@ -47,7 +47,7 @@ sha256sums=('129225c2602290221fc38f754c486e1cba8f0b160e56c5aee2446100aa4e8a04' 'fb58f07738b7654b15173fe0ffce2dff2f14b588d8edd24545e6c2c739605026' '6d96cdd20bc993c00eb0b2d7f77d52f81531c72e9d7141455822690ddec348bb' '0d3cafa62973fe86284652066c2d2f94958380c20314702f45a172fd4c7d649f' - '5c799f27a424162200071b4e19aba6fae6bc9d2152a0b5a518512ae12d62104a' + '2a2e10a0de0140d884cdce789c0389a142df96f4190e5afe0d1061d29b42f07b' '52020e636e62b60f0c3e6251c3b67b8a8c409bff0486bc3463f356fd3073384b' '6209d874a8d2950e5c6a68e2a9c19ffd4ba6e86a27bde3351f37e69290830349') diff --git a/tools/aurora-sep/tests/test_diag_sensor.py b/tools/aurora-sep/tests/test_diag_sensor.py index b5c88e5f193fe..01c7a8c1d97ba 100644 --- a/tools/aurora-sep/tests/test_diag_sensor.py +++ b/tools/aurora-sep/tests/test_diag_sensor.py @@ -300,7 +300,7 @@ def test_matching_follows_the_guard(self): ((1, 1, unverified, 2, blocked), '2 1 loaded 2 blocked'), # ... after refusals ((2, 1, unverified, 0, 0), '2 2 loaded 0 unverified'), ((1, 0, blocked, 0, 0), '2 1 unavailable 0 blocked'), # no list - ((1, 1, blocked, 0, 0), '2 1 loaded 0 blocked'), # cold boot, level 1 + ((1, 1, blocked, 0, 0), '2 1 loaded 0 blocked'), # blocked at probe ((0, 0, 0, 0, 0), '2 0 none 0 unknown'), # never set ((0, 0, 7, 0, 0), '2 0 none 0 unknown'), # out of range ] diff --git a/tools/aurora-sep/tests/test_sep_ready.py b/tools/aurora-sep/tests/test_sep_ready.py index b667ba60d17f8..1391c71fd1f61 100644 --- a/tools/aurora-sep/tests/test_sep_ready.py +++ b/tools/aurora-sep/tests/test_sep_ready.py @@ -120,14 +120,23 @@ def assert_blocked(self, log, *reasons, optout=True): else: self.assertNotIn("xart_guard=0", log) - def test_cold_boot_guard_blocks_matching(self): - # A cold-boot M1-family Mac under the default level 1: restored, but - # every match is refused. + def test_cold_boot_guard_is_unverified(self): + # A cold-boot Mac can now be set up under the guard, so it reports + # unverified like a warm-attach one. + self.attached(matching="unverified", xart="enabled", xart_guard="1", + xart_guard_list="loaded", xart_guard_refusals="0", boot="cold") + rc, log = self.run_ready() + self.assertEqual(rc, 0, log) + self.assert_unverified(log) + + def test_blocked_without_a_known_reason(self): + # An older kernel reports a guarded cold-boot Mac as blocked. self.attached(matching="blocked", xart="enabled", xart_guard="1", xart_guard_list="loaded", xart_guard_refusals="0", boot="cold") rc, log = self.run_ready() self.assertEqual(rc, 1, log) - self.assert_blocked(log, "the enrolled fingerprints are restored", "cold-boot") + self.assert_blocked(log, "the enrolled fingerprints are restored", + "refuses a match's write") def test_refusals_block_matching(self): self.attached(matching="blocked", xart="enabled", xart_guard="1", From 0790bf57ad615016160f3993b716ee4b2fbc980a Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 3 Nov 2026 00:11:03 +1100 Subject: [PATCH 28/30] selftests: apple-dcp: follow the late USB4 router watch in cd321x probe The typec-probe and probe-mux-pm-dispatch units extract the real cd321x_register_port() and cd321x_unregister_port(). Since the port keeps watching for a USB4/Thunderbolt router that binds after it gave up, both use cd321x_tbt_watch_late(), INIT_WORK() and three new struct cd321x members, and the units no longer build. Mock the watch the way the driver uses it: it holds the port fwnode until the port is unregistered and cd321x_stop_work() ends it. Check that a port probed without its router watches for it, that a watch which cannot start leaves the port probed without one, that a router present at probe needs no watch, and that every case leaves the watch and fwnode balanced. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../selftests/drivers/gpu/apple-dcp/pm.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py index 37537310d333e..8cae88d722d88 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py @@ -558,6 +558,27 @@ def function(text, name): init_mux = block(core, 'VISIBLE_IF_KUNIT int cd321x_init_mux(') production += init_mux.replace('VISIBLE_IF_KUNIT', 'static', 1) + '\n' production += function(core, 'cd321x_register_port') + '\n' + function(core, 'cd321x_unregister_port') + # A port that probed without its USB4/Thunderbolt router keeps watching for it. + if 'cd321x_tbt_watch_late(' in core: + mocks = mocks.replace(' bool state_valid,tbt_notifier_registered;\n', + ' bool state_valid,tbt_notifier_registered;\n' + ' struct work tbt_attach_work;\n struct fwnode_handle *tbt_fwnode;\n' + ' struct notifier_block tbt_watch;\n bool tbt_watch_registered;\n', 1) + mocks = mocks.replace('static void cd321x_stop_work(struct cd321x *c) {\n', r'''#define INIT_WORK(p,fn) ((p)->pending=false) +#define dev_warn(...) ((void)0) +static int watches, watch_error; +/* Like the real watch, hold the port fwnode until the port is unregistered. */ +static int cd321x_tbt_watch_late(struct cd321x *c,struct fwnode_handle *f) { + if(watch_error)return watch_error; + f->refs++;c->tbt_fwnode=f;c->tbt_watch_registered=true;watches++;return 0; +} +static void cd321x_stop_work(struct cd321x *c) { + if(c->tbt_watch_registered){assert(watches>0);watches--;c->tbt_watch_registered=false;} +''', 1) + assert 'cd321x_tbt_watch_late(struct cd321x' in mocks and 'tbt_watch_registered;' in mocks + late_watch = True + else: + late_watch = False controls = r''' static void reset(struct cd321x *c) { memset(c,0,sizeof(*c));connector.refs=0; @@ -708,6 +729,29 @@ def function(text, name): puts("PASS actual held mux gives up after its retries until an event; other failures end the hold-up"); ''') + if late_watch: + quiet = ' mux_error=notifier_error=switch_error=0;' + assert controls.count(quiet) == 1 + controls = controls.replace(quiet, ' assert(!watches);watch_error=0;\n' + quiet, 1) + even = ' assert(!ports&&!altmodes&&!muxes&&!switches&&!notifiers&&!connector.refs);\n}' + assert controls.count(even) == 1 + controls = controls.replace(even, even[:-2] + '\n assert(!watches);\n}', 1) + optional = ' assert(cd321x_register_port(&c.tps,&connector)==0&&c.state_valid&&!c.tbt_switch);\n cd321x_unregister_port(&c.tps);balanced();\n' + assert controls.count(optional) == 1 + controls = controls.replace(optional, r''' assert(cd321x_register_port(&c.tps,&connector)==0&&c.state_valid&&!c.tbt_switch); + /* ...and the port keeps watching for a router that binds later. */ + assert(watches==1&&c.tbt_watch_registered&&c.tbt_fwnode==&connector); + cd321x_unregister_port(&c.tps);balanced(); + /* A watch that cannot start leaves the port probed without it. */ + reset(&c);switch_error=-EPROBE_DEFER;keep_waiting=false;watch_error=-ENOMEM; + assert(cd321x_register_port(&c.tps,&connector)==0&&c.state_valid&&!c.tbt_switch); + assert(!watches&&!c.tbt_watch_registered&&!c.tbt_fwnode); + cd321x_unregister_port(&c.tps);balanced(); + /* A router present at probe needs no watch. */ + reset(&c);assert(cd321x_register_port(&c.tps,&connector)==0&&c.tbt_switch&&!watches); + cd321x_unregister_port(&c.tps);balanced(); +''', 1) + return prefix + pm + mocks + production + '\n#define TEST_DEFER 1\n#define TEST_FWNODE 1\n' + controls From 4fb4d880ed556c3714eac769a9e5057889aa4853 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 3 Nov 2026 00:43:06 +1100 Subject: [PATCH 29/30] accel/ane: count SHAREDMALLOC bodies seen before their notification The H16 host now serves the SHAREDMALLOC ring only after the firmware raises its notification bit, as macOS does. Count the polls that found a request body in the ring before that bit, and log the count with the number of allocations at the end of every startup. A nonzero count marks a start where the earlier host would have answered the request while the firmware was still posting it, which is the window behind the intermittent startup and resume stalls. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/accel/ane/ane_fw_transport.h | 11 ++++++++++- drivers/accel/ane/ane_h16_boot.h | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/accel/ane/ane_fw_transport.h b/drivers/accel/ane/ane_fw_transport.h index 992f58618455c..53c44178f528b 100644 --- a/drivers/accel/ane/ane_fw_transport.h +++ b/drivers/accel/ane/ane_fw_transport.h @@ -121,6 +121,8 @@ struct ane_fw_transport { u32 head[ANE_FW_CHANNELS]; /* SHAREDMALLOC entries captured after acknowledging their notification. */ u32 shared_ready; + /* Polls that saw a request body before its notification (held, not served). */ + u32 shared_early; u8 table[ANE_FW_TABLE_SIZE]; size_t table_offset; size_t pool_used; @@ -680,8 +682,15 @@ static inline int ane_fw_transport_shared(struct ane_fw_transport *t) ret = ane_fw_transport_irq(t, &pending); if (ret) return ret; - if (!t->shared_ready) + if (!t->shared_ready) { + p = ane_fw_transport_slot(t, ANE_FW_SHAREDMALLOC); + t->ops->read_barrier(t->ctx); + word0 = t->ops->load64(t->ctx, p); + if (!(word0 & 1) && (word0 || t->ops->load64(t->ctx, p + 8) || + t->ops->load64(t->ctx, p + 16))) + t->shared_early++; return -EAGAIN; + } } p = ane_fw_transport_slot(t, ANE_FW_SHAREDMALLOC); word0 = t->ops->load64(t->ctx, p); diff --git a/drivers/accel/ane/ane_h16_boot.h b/drivers/accel/ane/ane_h16_boot.h index e3c66b89e1265..d8c9c8e6eb102 100644 --- a/drivers/accel/ane/ane_h16_boot.h +++ b/drivers/accel/ane/ane_h16_boot.h @@ -654,6 +654,9 @@ static inline int ane_h16_startup(struct ane_h16_boot *b) b->pmu_base_set = true; } } + /* How often a request body was seen before its notification and held. */ + b->ops->log(b->ctx, "startup SHAREDMALLOC allocations/early polls held", + b->transport.shared_count, b->transport.shared_early); return 0; } From 49e6d49ee86f21baa2c2439dc142751b13defde6 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Tue, 3 Nov 2026 00:56:39 +1100 Subject: [PATCH 30/30] spi: apple-qspi: admit the J700 GigaDevice boot NOR Some J700s carry a GigaDevice GD25UF16E boot NOR (RDID c8 83 15) in place of the Winbond W25Q16 the board entry names. On those Macs every NVRAM program and erase fails with -ENODEV, so asahi-bless cannot select a boot volume. The part is also 2 MiB, takes 3-byte addresses and uses the same 02h page program and 20h 4 KiB erase, so the J700 partition and mutation policy hold unchanged. Its status registers differ from Winbond: - SR1 bits 6:2 are BP4 to BP0. Winbond has SEC and TB at bits 6:5, which the Winbond check leaves as found. - SR2 bit 2 is SUS2, program suspend. It is reserved on Winbond. - SR3 has no WPS. Bit 2 there is LPE, a low-power setting. Name the part in the second JEDEC slot of the J700 entry and give GigaDevice its own status check: WIP, WEL, CMP, SUS1 and SUS2 are refused, BP2 to BP0 only where the board allows them, and BP3 and BP4 always on the J700. The check is picked from the manufacturer the flash reports. flash_status prints all three registers for GigaDevice parts too. The bit layout is from the GD25UF16E datasheet, Rev 1.0 (DS-01151). Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/spi/spi-apple-qspi-policy-test.c | 35 +++++++++++++++++++++ drivers/spi/spi-apple-qspi-policy.h | 37 ++++++++++++++++++++++ drivers/spi/spi-apple-qspi.c | 22 ++++++++----- tools/asahi/qspi-nvram-tests.py | 39 ++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-apple-qspi-policy-test.c b/drivers/spi/spi-apple-qspi-policy-test.c index f6dc555057443..5418c3a4d63ab 100644 --- a/drivers/spi/spi-apple-qspi-policy-test.c +++ b/drivers/spi/spi-apple-qspi-policy-test.c @@ -175,6 +175,39 @@ static void qspi_policy_mx_status(struct kunit *test) KUNIT_EXPECT_TRUE(test, apple_qspi_mx_status_allows_write(0x3c, 0x3c)); } +static void qspi_policy_gd_status(struct kunit *test) +{ + /* The J700 GD25UF16E part: QE is set for good and BP0 by iBoot. */ + KUNIT_EXPECT_TRUE(test, apple_qspi_gd_status_allows_write(0x00, 0x02, 0x1c)); + KUNIT_EXPECT_TRUE(test, apple_qspi_gd_status_allows_write(0x04, 0x02, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0x04, 0x02, 0)); + /* SRP0, SRP1 and LB1 to LB3 stay as found and do not block a write. */ + KUNIT_EXPECT_TRUE(test, apple_qspi_gd_status_allows_write(0x80, 0x3b, 0x1c)); + /* WIP and WEL are never admitted. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0x01, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0x02, 0, 0x1c)); + /* BP3 and BP4, which Winbond calls TB and SEC. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0x20, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0x40, 0, 0x1c)); + /* SUS1, CMP and SUS2, which is reserved on Winbond parts. */ + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0, 0x80, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0, 0x40, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_gd_status_allows_write(0, 0x04, 0x1c)); +} + +static void qspi_policy_part_status(struct kunit *test) +{ + /* The checks follow the manufacturer the flash reports. */ + KUNIT_EXPECT_TRUE(test, apple_qspi_part_status_allows_write(0xef, 0x20, 0, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_part_status_allows_write(0xc8, 0x20, 0, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_part_status_allows_write(0xef, 0, 0, 0x04, 0x1c)); + /* GigaDevice SR3 bit 2 is LPE, a power setting. */ + KUNIT_EXPECT_TRUE(test, apple_qspi_part_status_allows_write(0xc8, 0, 0, 0x04, 0x1c)); + KUNIT_EXPECT_TRUE(test, apple_qspi_part_status_allows_write(0xef, 0, 0x04, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_part_status_allows_write(0xc8, 0, 0x04, 0, 0x1c)); + KUNIT_EXPECT_FALSE(test, apple_qspi_part_status_allows_write(0xc2, 0x20, 0, 0, 0x1c)); +} + static struct kunit_case apple_qspi_policy_cases[] = { KUNIT_CASE(qspi_policy_write_enable), KUNIT_CASE(qspi_policy_program), @@ -185,6 +218,8 @@ static struct kunit_case apple_qspi_policy_cases[] = { KUNIT_CASE(qspi_policy_nvram_700000), KUNIT_CASE(qspi_policy_status), KUNIT_CASE(qspi_policy_mx_status), + KUNIT_CASE(qspi_policy_gd_status), + KUNIT_CASE(qspi_policy_part_status), {} }; diff --git a/drivers/spi/spi-apple-qspi-policy.h b/drivers/spi/spi-apple-qspi-policy.h index 5acd9a62d5291..361ecc689fca7 100644 --- a/drivers/spi/spi-apple-qspi-policy.h +++ b/drivers/spi/spi-apple-qspi-policy.h @@ -76,4 +76,41 @@ static inline int apple_qspi_mx_status_allows_write(unsigned int sr, { return !(sr & 0x03) && !(sr & 0x3c & ~bp_ok); } + +/* JEDEC manufacturer ID of GigaDevice parts. */ +#define APPLE_QSPI_MFR_GIGADEVICE 0xc8 + +/* + * GigaDevice GD25UF16E. SR1 holds WIP, WEL, five block-protect bits BP[4:0] + * at bits 6:2 and SRP0. SR2 holds SRP1, QE, SUS2 at bit 2, LB[3:1], CMP and + * SUS1 at bit 7. SR3 holds output drive, dummy-cycle and low-power settings + * and has no write-protect selection. BUSY and WEL, both suspend bits and + * CMP are always refused. BP[2:0] sit where they do on Winbond parts and + * are tolerated only where bp_ok allows them; BP3 and BP4 move or shrink + * the protected range and are refused unless bp_ok names them. SRP0, SRP1, + * QE and LB stay unchanged. + */ +static inline int apple_qspi_gd_status_allows_write(unsigned int sr1, + unsigned int sr2, + unsigned int bp_ok) +{ + return !(sr1 & 0x03) && !(sr1 & 0x7c & ~bp_ok) && !(sr2 & 0xc4); +} + +/* Pick the status checks from the manufacturer the flash itself reports. */ +static inline int apple_qspi_part_status_allows_write(unsigned int mfr, + unsigned int sr1, + unsigned int sr2, + unsigned int sr3, + unsigned int bp_ok) +{ + switch (mfr) { + case APPLE_QSPI_MFR_MACRONIX: + return apple_qspi_mx_status_allows_write(sr1, bp_ok); + case APPLE_QSPI_MFR_GIGADEVICE: + return apple_qspi_gd_status_allows_write(sr1, sr2, bp_ok); + default: + return apple_qspi_status_allows_write(sr1, sr2, sr3, bp_ok); + } +} #endif diff --git a/drivers/spi/spi-apple-qspi.c b/drivers/spi/spi-apple-qspi.c index 93b8078208362..11fb1d5553105 100644 --- a/drivers/spi/spi-apple-qspi.c +++ b/drivers/spi/spi-apple-qspi.c @@ -117,6 +117,7 @@ struct apple_qspi_board { #define APPLE_QSPI_JEDEC_W25Q64 { 0xef, 0x65, 0x17 } #define APPLE_QSPI_JEDEC_W25Q16 { 0xef, 0x65, 0x15 } #define APPLE_QSPI_JEDEC_MX_2B27 { 0xc2, 0x2b, 0x27 } +#define APPLE_QSPI_JEDEC_GD25UF16E { 0xc8, 0x83, 0x15 } static const struct apple_qspi_board apple_qspi_boards[] = { { "apple,t8122-qspi", "apple,j613", 0x2a1118000ULL, @@ -127,9 +128,13 @@ static const struct apple_qspi_board apple_qspi_boards[] = { { "apple,t6041-qspi", "apple,j616s", 0x429118000ULL, 0x700000, 0x100000, APPLE_QSPI_JEDEC_MX_2B27, 0, APPLE_QSPI_JEDEC_W25Q64 }, - /* iBoot leaves BP0 set on the J700, over the top 64 KiB. */ + /* + * iBoot leaves BP0 set on the J700, over the top 64 KiB. Some J700s + * carry a GigaDevice GD25UF16E, where BP0 covers the same range. + */ { "apple,t8140-qspi", "apple,j700", 0x385110000ULL, - 0x24000, 0x1d0000, APPLE_QSPI_JEDEC_W25Q16, 0x1c }, + 0x24000, 0x1d0000, APPLE_QSPI_JEDEC_W25Q16, 0x1c, + APPLE_QSPI_JEDEC_GD25UF16E }, }; static DEFINE_MUTEX(apple_qspi_policy_lock); @@ -673,7 +678,8 @@ static int apple_qspi_read_array(struct spi_mem *mem, u64 addr, u8 *buf, u32 len } /* - * Winbond parts have three status registers (05h, 35h, 15h). On Macronix + * Winbond and GigaDevice parts have three status registers (05h, 35h, 15h), + * whose bits differ (see the policy header). On Macronix * parts 35h is EQIO, which switches the flash to QPI mode, so only the * status (05h) and configuration (15h) registers are read there. */ @@ -722,10 +728,8 @@ static int apple_qspi_nvram_modify(struct spi_mem *mem, const struct spi_mem_op if (ret) goto out; /* No unlocking. Conservatively refuse BP/CMP/WPS, BUSY/WEL or suspend. */ - if (id[0] == APPLE_QSPI_MFR_MACRONIX ? - !apple_qspi_mx_status_allows_write(before[0], qspi->bp_ok) : - !apple_qspi_status_allows_write(before[0], before[1], before[2], - qspi->bp_ok)) { + if (!apple_qspi_part_status_allows_write(id[0], before[0], before[1], + before[2], qspi->bp_ok)) { ret = -EACCES; goto out; } @@ -1133,7 +1137,8 @@ static ssize_t flash_status_show(struct device *dev, /* spi-mem supplies queue, bus and I/O serialization for each read. * These are sequential samples, not an atomic protection snapshot. * The JEDEC ID decides which registers exist: 35h is SR2 on Winbond - * parts but EQIO on Macronix ones, so it is sent to Winbond only. + * and GigaDevice parts but EQIO on Macronix ones, so it is never sent + * to Macronix. */ ret = apple_qspi_flash_read_reg(&mem, 0x9f, data, 3) ?: apple_qspi_flash_read_reg(&mem, 0x05, data + 3, 1); @@ -1141,6 +1146,7 @@ static ssize_t flash_status_show(struct device *dev, goto out_free; switch (data[0]) { case 0xef: + case APPLE_QSPI_MFR_GIGADEVICE: ret = apple_qspi_flash_read_reg(&mem, 0x35, data + 4, 1) ?: apple_qspi_flash_read_reg(&mem, 0x15, data + 5, 1); if (!ret) diff --git a/tools/asahi/qspi-nvram-tests.py b/tools/asahi/qspi-nvram-tests.py index f0a39ec959190..3e377466e9444 100644 --- a/tools/asahi/qspi-nvram-tests.py +++ b/tools/asahi/qspi-nvram-tests.py @@ -167,6 +167,7 @@ def function(source, signature): assert(qb->jedec_alt[0]==0xef&&qb->jedec_alt[1]==0x65&&qb->jedec_alt[2]==0x17); machine="apple,j700";qb=apple_qspi_nvram_write_board(&t8140,0x385110000ULL); assert(qb&&qb->nvram_start==0x24000&&qb->nvram_size==0x1d0000&&qb->jedec[2]==0x15&&qb->bp_ok==0x1c); + assert(qb->jedec_alt[0]==0xc8&&qb->jedec_alt[1]==0x83&&qb->jedec_alt[2]==0x15); puts("PASS J613/J713/J700/J616s board, controller and full-width aperture qualification (140 combinations)"); u8 data[256];memset(data,0x55,sizeof(data));reset(); struct spi_mem_op wren=SPI_MEM_OP(SPI_MEM_OP_CMD(0x06,1),SPI_MEM_OP_NO_ADDR,SPI_MEM_OP_NO_DUMMY,SPI_MEM_OP_NO_DATA); @@ -260,6 +261,37 @@ def function(source, signature): reset();memset(jedec,0,3);arm();wire_count=0;o=program(0x700000,1,data); assert(apple_qspi_exec_op(&mem,&o)==-ENODEV&&!sent(0x06)); puts("PASS W25Q64 J616s part: second ID admitted with SR2 read and checks, third ID and empty slot refused"); + /* J700 GD25UF16E part: the second slot of the J700 entry, GigaDevice checks. */ + const u8 w25q16[3]={0xef,0x65,0x15},gd25uf16e[3]={0xc8,0x83,0x15}; + reset();memcpy(jedec,gd25uf16e,3);memcpy(q.jedec,w25q16,3);memcpy(q.jedec_alt,gd25uf16e,3);q.bp_ok=0x1c;arm(); + memset(data,0x55,sizeof(data));sr[0]=0x04;sr[1]=0x02; + o=program(0x700000,256,data);wire_count=0;assert(!apple_qspi_exec_op(&mem,&o)&&sent(0x02)&&sent(0x35)&&sent(0x15)); + assert(!memcmp(flash+0x700000,data,256)&&sr[0]==0x04&&sr[1]==0x02); + erase.addr.val=0x701000;memset(flash+0x701000,0,4096); + wire_count=0;assert(!apple_qspi_exec_op(&mem,&erase)&&sent(0x20)); + for(unsigned int i=0;i<4096;i++)assert(flash[0x701000+i]==0xff); + wire_count=0;assert(!apple_qspi_exec_op(&mem,&rdcr2)&&sent(0x35)); + /* SRP0, SRP1, LB1-LB3 and the SR3 drive, dummy and low-power bits stay as found. */ + sr[0]=0x84;sr[1]=0x3b;sr[2]=0x67;o=program(0x700200,1,data);wire_count=0; + assert(!apple_qspi_exec_op(&mem,&o)&&sent(0x02)&&flash[0x700200]==0x55); + /* Refused before WREN: WIP, WEL, BP3, BP4, CMP, SUS1 and SUS2. */ + const u8 gd_refused[][3]={{0x01,0x02,0},{0x02,0x02,0},{0x24,0x02,0},{0x44,0x02,0}, + {0x04,0x42,0},{0x04,0x82,0},{0x04,0x06,0}}; + for(unsigned int i=0;ijedec_alt, sizeof(id))', '1'), ('jedec-alt-empty', '!qspi->jedec_alt[0] || ', ''), + ('gd-dispatch', 'return apple_qspi_gd_status_allows_write(sr1, sr2, bp_ok);', + 'return apple_qspi_status_allows_write(sr1, sr2, sr3, bp_ok);'), + ('gd-bp34', '!(sr1 & 0x7c & ~bp_ok)', '!(sr1 & 0x1c & ~bp_ok)'), + ('gd-sus2', '!(sr2 & 0xc4)', '!(sr2 & 0xc0)'), + ('gd-cmp', '!(sr2 & 0xc4)', '!(sr2 & 0x84)'), + ('j700-gd-id', 'APPLE_QSPI_JEDEC_GD25UF16E\t{ 0xc8, 0x83, 0x15 }', + 'APPLE_QSPI_JEDEC_GD25UF16E\t{ 0xc8, 0x83, 0x16 }'), ('read-only', 'read_only || !qspi->nvram_write_candidate || qspi->write_fault', '!qspi->nvram_write_candidate || qspi->write_fault'), ('fault-latch', 'write_fault_latched = true;', 'write_fault_latched = false;'),