diff --git a/arch/arm64/boot/dts/apple/t8140-j700.dts b/arch/arm64/boot/dts/apple/t8140-j700.dts index ee9b0b63a3c2e..ed543b2c0605d 100644 --- a/arch/arm64/boot/dts/apple/t8140-j700.dts +++ b/arch/arm64/boot/dts/apple/t8140-j700.dts @@ -27,8 +27,9 @@ sep = &sep; serial0 = &serial0; pmp = &pmp; - rtc0 = &smc_rtc; - rtc1 = &pmu_rtc; + /* First the RTC that can wake the system */ + rtc0 = &pmu_rtc; + rtc1 = &smc_rtc; wifi0 = &wifi0; /* used by the loader to hand over the display */ dcp = &dcp; diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index fd318f9071928..847904f7eedbf 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -97,7 +97,7 @@ CONFIG_USB4_APPLE_SOC=m CONFIG_USB_XHCI_PCI_ASMEDIA=y CONFIG_TYPEC_SN201202X=m CONFIG_RTC_DRV_MACSMC=y -CONFIG_RTC_DRV_APPLE_PMU=m +CONFIG_RTC_DRV_APPLE_PMU=y CONFIG_APPLE_ADMAC=m CONFIG_APPLE_SIO=m CONFIG_MFD_MACSMC=y diff --git a/drivers/gpu/drm/asahi/g17/sleep.c b/drivers/gpu/drm/asahi/g17/sleep.c index 57af9cc3dfef0..6207c99613172 100644 --- a/drivers/gpu/drm/asahi/g17/sleep.c +++ b/drivers/gpu/drm/asahi/g17/sleep.c @@ -325,6 +325,12 @@ struct asahi_g17_commit *asahi_g17_commit_prepare(struct device *dev, return ERR_PTR(ret); } +/* Whether only the sleep's commit may stop the GPU: then nothing else may. */ +bool asahi_g17_commit_required(void) +{ + return apple_sleep_commit_stops_coprocs(); +} + /* * After the sleep, refused or not: whether the commit stopped the GPU, and in @saved the * domain's word from before, if it did. Frees @c. diff --git a/drivers/gpu/drm/asahi/g17/sleep.h b/drivers/gpu/drm/asahi/g17/sleep.h index 21b91dfca0a2e..e4473c73e19d3 100644 --- a/drivers/gpu/drm/asahi/g17/sleep.h +++ b/drivers/gpu/drm/asahi/g17/sleep.h @@ -15,6 +15,7 @@ struct asahi_g17_commit *asahi_g17_commit_prepare(struct device *dev, struct apple_rtkit *secondary_rtk, struct apple_mbox *secondary_mbox, bool bridge); bool asahi_g17_commit_finish(struct asahi_g17_commit *c, u32 *saved); +bool asahi_g17_commit_required(void); int asahi_g17_power_auto_off(struct device *dev, u32 *saved); int asahi_g17_power_lower(struct device *dev); int asahi_g17_power_raise(struct device *dev, u32 saved); diff --git a/drivers/gpu/drm/asahi/g17/sleep.rs b/drivers/gpu/drm/asahi/g17/sleep.rs index 0db5f0a9a3d23..fe5779ee5d5e1 100644 --- a/drivers/gpu/drm/asahi/g17/sleep.rs +++ b/drivers/gpu/drm/asahi/g17/sleep.rs @@ -47,6 +47,7 @@ unsafe extern "C" { bridge: bool, ) -> *mut core::ffi::c_void; fn asahi_g17_commit_finish(commit: *mut core::ffi::c_void, saved: *mut u32) -> bool; + fn asahi_g17_commit_required() -> bool; } /// What `.complete` does after a sleep whose stop was left to the commit. @@ -427,13 +428,24 @@ impl Shared { true } - /// Leaves the stop to the commit. False, with the reason logged, if it can't be. - fn sleep_leave_for_commit(&self, firmware: &mut Firmware) -> bool { + /// Leaves the stop to the commit. False, with the reason logged, if it can't be. Where only + /// the commit may stop it, that refuses the sleep instead: a refusal after an earlier stop + /// would leave it stopped. + fn sleep_leave_for_commit(&self, firmware: &mut Firmware) -> Result { match firmware.leave_for_commit() { Ok(commit) => { self.sleep_commit.store(commit, Ordering::Release); dev_dbg!(self.dev.as_ref(), "GPU firmware left running for the sleep's commit\n"); - true + Ok(true) + } + // SAFETY: Reads the sleep driver's state. + Err(error) if unsafe { asahi_g17_commit_required() } => { + dev_err!( + self.dev.as_ref(), + "no GPU stop at the commit ({:?}): refusing the sleep\n", + error + ); + Err(EBUSY) } Err(error) => { dev_warn!( @@ -441,7 +453,7 @@ impl Shared { "no GPU stop at the commit ({:?}): stopping it now\n", error ); - false + Ok(false) } } } @@ -490,7 +502,7 @@ impl Shared { if !firmware.sleep_quiet(super::now_ns())? { return Err(EBUSY); } - if self.sleep_leave_for_commit(firmware) { + if self.sleep_leave_for_commit(firmware)? { return Ok(()); } down(firmware) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 11bf8dfb37055..fe7eac401a1b2 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -913,7 +913,7 @@ struct apple_atcphy { bool sleep_dead; struct device *sleep_darts[ATCPHY_SLEEP_DARTS]; unsigned int sleep_ndarts; - struct device_node *pd_usb, *pd_common; + struct device_node *pd_usb, *pd_common, *pd_aon; u32 *bridge_gate; u32 *bridge_saved; unsigned int bridge_count; @@ -6370,6 +6370,10 @@ static int atcphy_probe_sleep_power(struct apple_atcphy *atcphy) return ret; if (!atcphy->pd_common) return 0; + atcphy->pd_aon = of_parse_phandle(atcphy->pd_common, "power-domains", 0); + ret = devm_add_action_or_reset(dev, atcphy_put_node, atcphy->pd_aon); + if (ret) + return ret; n = of_count_phandle_with_args(ctrl, "iommus", "#iommu-cells"); if (n <= 0 || n > ATCPHY_SLEEP_DARTS) @@ -6819,6 +6823,21 @@ static void atcphy_bridge_gate(struct apple_atcphy *atcphy, bool open) } } +#define ATCPHY_DOMAIN_TRIES 3 + +static int atcphy_sleep_domain_up(struct device_node *pd) +{ + int i, ret; + + for (i = 0; i < ATCPHY_DOMAIN_TRIES; i++) { + ret = apple_sleep_domain_set(pd, true); + if (!ret) + break; + } + + return ret; +} + /* * Back up in macOS's order: ATC0_COMMON, the bridge, ATC0_USB, then the DARTs. -ENODEV if * a domain didn't come on, when nothing in it may be touched. @@ -6829,13 +6848,13 @@ static int atcphy_sleep_power_up(struct apple_atcphy *atcphy) int ret; atcphy->sleep_powered_down = false; - ret = apple_sleep_domain_set(atcphy->pd_common, true); + ret = atcphy_sleep_domain_up(atcphy->pd_common); if (!ret) { atcphy_bridge_gate(atcphy, true); - ret = apple_sleep_domain_set(atcphy->pd_usb, true); + ret = atcphy_sleep_domain_up(atcphy->pd_usb); } if (ret) { - dev_err(atcphy->dev, "USB host's power domains not back on: %d, USB off until a reboot\n", + dev_err(atcphy->dev, "USB host's power domains not back on: %d, USB stays off\n", ret); WRITE_ONCE(atcphy->sleep_dead, true); atcphy->sleep_no_keep = true; @@ -6965,6 +6984,45 @@ static int atcphy_sleep_come_back(struct apple_atcphy *atcphy, bool common_lost, return d0 < 0 ? d0 : ret ?: d0; } +/* + * macOS takes a USB host that fails its raise Off, then starts it cold. The same here, with + * nothing touching the core: clamped in reset, its domains and the always-on one off, then + * on again. The block comes back reset, as after a sleep that took its power, and the host + * starts over. -ENODEV if the core still may be unpowered. + */ +static int atcphy_sleep_recover(struct apple_atcphy *atcphy) +{ + int ret; + + lockdep_assert_held(&atcphy->lock); + if (!atcphy->sleep_power || !atcphy->pd_aon) + return -ENODEV; + dev_warn(atcphy->dev, "USB controller didn't come back: power cycling it, as macOS's Off does\n"); + /* Domains that didn't come on get another try first. */ + if (READ_ONCE(atcphy->sleep_dead)) { + WRITE_ONCE(atcphy->sleep_dead, false); + atcphy->sleep_powered_down = true; + if (atcphy_sleep_power_up(atcphy)) + return -ENODEV; + } + _atcphy_dwc3_reset_assert(atcphy); + atcphy_usb2_sleep_enter(atcphy); + ret = atcphy_sleep_power_down(atcphy); + if (!ret) { + ret = apple_sleep_domain_set(atcphy->pd_aon, false); + ret = atcphy_sleep_domain_up(atcphy->pd_aon) ?: ret; + } + if (ret) + dev_warn(atcphy->dev, "USB power cycle incomplete: %d\n", ret); + ret = atcphy_sleep_come_back(atcphy, true, false); + if (ret < 0) + dev_err(atcphy->dev, "USB controller not back after the power cycle: %d\n", ret); + else + dev_info(atcphy->dev, "USB controller back after a power cycle: the host starts over\n"); + + return ret; +} + static int atcphy_suspend_noirq(struct device *dev) { struct apple_atcphy *atcphy = dev_get_drvdata(dev); @@ -6982,8 +7040,11 @@ static int atcphy_suspend_noirq(struct device *dev) return -EBUSY; } ret = atcphy_controller_hibernate(atcphy); - if (ret) + if (ret) { + if (atcphy->sleep_ret == -ENODEV) + atcphy->sleep_ret = atcphy_sleep_recover(atcphy); return ret; + } atcphy->hibernated = true; ret = atcphy_sleep_power_down(atcphy); if (ret) { @@ -6991,6 +7052,8 @@ static int atcphy_suspend_noirq(struct device *dev) atcphy->sleep_no_keep = true; atcphy->sleep_ret = atcphy_sleep_come_back(atcphy, atcphy->sleep_powered_down, true); + if (atcphy->sleep_ret == -ENODEV) + atcphy->sleep_ret = atcphy_sleep_recover(atcphy); atcphy->hibernated = false; return ret; } @@ -7027,6 +7090,8 @@ static int atcphy_resume_noirq(struct device *dev) apple_sleep_usb_set_asleep(false); /* The PHY took ATC0_COMMON down itself, so this goes for a refused sleep too. */ atcphy->sleep_ret = atcphy_sleep_come_back(atcphy, true, false); + if (atcphy->sleep_ret == -ENODEV) + atcphy->sleep_ret = atcphy_sleep_recover(atcphy); atcphy->hibernated = false; if (!atcphy->sleep_ret) dev_dbg(dev, "USB controller back in D0 after the sleep, always-on side kept, PHY in mode %d\n", @@ -7114,6 +7179,18 @@ int apple_atcphy_sleep_result(struct phy *usb2_phy) } EXPORT_SYMBOL_GPL(apple_atcphy_sleep_result); +int apple_atcphy_sleep_recover(struct phy *usb2_phy) +{ + struct apple_atcphy *atcphy = atcphy_from_usb2_phy(usb2_phy); + + if (!atcphy) + return -EOPNOTSUPP; + guard(mutex)(&atcphy->lock); + + return atcphy_sleep_recover(atcphy); +} +EXPORT_SYMBOL_GPL(apple_atcphy_sleep_recover); + /* * A controller that was restarted after the sleep is set up as at boot and may hibernate * again. Only the PHY's own failures stop it for the later sleeps; a sleep refused for a diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index bae3a9f856c20..a7e6eba6e7910 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -2099,10 +2099,14 @@ config RTC_DRV_APPLE_PMU depends on ARCH_APPLE && NVMEM_APPLE_SPMI && PM_SLEEP help Read the PMU clock on M1 Pro/Max/Ultra, M2, M4 Pro and A18 Pro - systems and use its alarm to wake the system - from suspend-to-idle. Alarms are limited to one hour; deeper - suspend states are not supported. Continue setting the shared - clock through the SMC RTC. + systems and use its alarm to wake the system from suspend-to-idle, + and on A18 Pro from a firmware-driven suspend too. A18 Pro alarms + reach as far as the PMU's comparator, and the clock can be set here; + elsewhere alarms are limited to one hour and the shared clock is set + through the SMC RTC. + + Say Y where this is the system's first RTC (rtc0), so that it sets + the system time at boot. This driver can be built as the module rtc-apple-pmu. diff --git a/drivers/rtc/rtc-apple-pmu.c b/drivers/rtc/rtc-apple-pmu.c index 709c85948fe67..dd1abea676f18 100644 --- a/drivers/rtc/rtc-apple-pmu.c +++ b/drivers/rtc/rtc-apple-pmu.c @@ -17,6 +17,7 @@ #define APPLE_PMU_RTC_ARM BIT(6) #define APPLE_PMU_RTC_ALARM BIT(0) #define APPLE_PMU_RTC_HZ_SHIFT 16 +/* The suspend-to-idle limit, where the alarm is all that wakes the system. */ #define APPLE_PMU_RTC_MAX_DELAY 3600 /* @@ -33,6 +34,8 @@ struct apple_pmu_rtc_hw { u8 control_event_bit; u8 monitor_bit; bool offset_in_seconds; + /* How far ahead an alarm may be, in seconds */ + u32 max_delay; }; static const struct apple_pmu_rtc_hw apple_pmu_rtc_baku = { @@ -43,6 +46,8 @@ static const struct apple_pmu_rtc_hw apple_pmu_rtc_baku = { .event = 0xf80c, .event_bit = BIT(0), .offset_in_seconds = true, + /* As far as the comparator reaches: macOS's own deep-sleep wakes use it. */ + .max_delay = U32_MAX, }; static const struct apple_pmu_rtc_hw apple_pmu_rtc_abbey = { @@ -53,6 +58,7 @@ static const struct apple_pmu_rtc_hw apple_pmu_rtc_abbey = { .event = 0x2000, .event_bit = BIT(1), .monitor_bit = BIT(0), + .max_delay = APPLE_PMU_RTC_MAX_DELAY, }; /* M2 (t8112): Baku's register block with the offset in 1/32768 s. */ @@ -64,6 +70,7 @@ static const struct apple_pmu_rtc_hw apple_pmu_rtc_stowe = { .event = 0xf80c, .event_bit = BIT(0), .monitor_bit = BIT(0), + .max_delay = APPLE_PMU_RTC_MAX_DELAY, }; /* M1 Pro, Max and Ultra (t600x), offset in 1/32768 s */ @@ -76,6 +83,7 @@ static const struct apple_pmu_rtc_hw apple_pmu_rtc_maverick = { .event_bit = BIT(0), .control_event_bit = BIT(1), .monitor_bit = BIT(0), + .max_delay = APPLE_PMU_RTC_MAX_DELAY, }; struct apple_pmu_rtc { @@ -149,6 +157,31 @@ static int apple_pmu_rtc_read_time(struct device *dev, struct rtc_time *time) return 0; } +/* + * Baku's offset is in whole seconds, and this sets it as the SMC RTC does: the + * counter rounded to the nearest second, so the clock is at most half a second off. + */ +static int apple_pmu_rtc_set_time(struct device *dev, struct rtc_time *time) +{ + struct apple_pmu_rtc *rtc = dev_get_drvdata(dev); + __le64 value; + u64 counter; + s64 offset; + int error; + + guard(mutex)(&rtc->transaction); + error = apple_pmu_rtc_counter(rtc, &counter); + if (error) + return error; + offset = rtc_tm_to_time64(time) - + (s64)((counter + BIT(APPLE_PMU_RTC_HZ_SHIFT - 1)) >> APPLE_PMU_RTC_HZ_SHIFT); + if (offset < 0 || offset > RTC_TIMESTAMP_END_9999 - U32_MAX) + return -ERANGE; + value = cpu_to_le64(offset); + error = nvmem_cell_write(rtc->offset, &value, sizeof(value)); + return error < 0 ? error : 0; +} + /* All comparator/control accesses are serialized with the IRQ thread. */ static int apple_pmu_rtc_ack_alarm(struct apple_pmu_rtc *rtc) { @@ -228,7 +261,7 @@ static int apple_pmu_rtc_validate_alarm(struct apple_pmu_rtc *rtc, u64 comparato counter >>= APPLE_PMU_RTC_HZ_SHIFT; if (comparator <= counter) return -ETIME; - if (comparator - counter > APPLE_PMU_RTC_MAX_DELAY) + if (comparator - counter > rtc->hw->max_delay) return -ERANGE; return 0; } @@ -375,6 +408,7 @@ static int apple_pmu_rtc_init_alarm(struct apple_pmu_rtc *rtc) return apple_pmu_rtc_stop_alarm(rtc); } +/* The others' offsets are set through the SMC RTC. */ static const struct rtc_class_ops apple_pmu_rtc_ops = { .read_time = apple_pmu_rtc_read_time, .read_alarm = apple_pmu_rtc_read_alarm, @@ -382,6 +416,14 @@ static const struct rtc_class_ops apple_pmu_rtc_ops = { .alarm_irq_enable = apple_pmu_rtc_alarm_enable, }; +static const struct rtc_class_ops apple_pmu_rtc_seconds_ops = { + .read_time = apple_pmu_rtc_read_time, + .set_time = apple_pmu_rtc_set_time, + .read_alarm = apple_pmu_rtc_read_alarm, + .set_alarm = apple_pmu_rtc_set_alarm, + .alarm_irq_enable = apple_pmu_rtc_alarm_enable, +}; + static int apple_pmu_rtc_prepare(struct device *dev) { struct apple_pmu_rtc *rtc = dev_get_drvdata(dev); @@ -441,10 +483,11 @@ static int apple_pmu_rtc_probe(struct platform_device *pdev) rtc->clock = devm_rtc_allocate_device(dev); if (IS_ERR(rtc->clock)) return PTR_ERR(rtc->clock); - rtc->clock->ops = &apple_pmu_rtc_ops; + rtc->clock->ops = rtc->hw->offset_in_seconds ? &apple_pmu_rtc_seconds_ops : + &apple_pmu_rtc_ops; rtc->clock->range_min = 0; rtc->clock->range_max = RTC_TIMESTAMP_END_9999; - rtc->clock->alarm_offset_max = APPLE_PMU_RTC_MAX_DELAY; + rtc->clock->alarm_offset_max = rtc->hw->max_delay; clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->clock->features); error = device_init_wakeup(dev, true); if (error) diff --git a/drivers/soc/apple/pm.rs b/drivers/soc/apple/pm.rs index a65e5920102f4..843d2a638128b 100644 --- a/drivers/soc/apple/pm.rs +++ b/drivers/soc/apple/pm.rs @@ -37,6 +37,7 @@ extern "C" { fn sep_pm_unregister(); fn sep_pm_keep_domains(dev: *mut c_void) -> c_int; fn sep_pm_wait_gated(dev: *mut c_void, timeout_ms: c_uint) -> c_int; + fn sep_pm_wait_off(dev: *mut c_void, timeout_ms: c_uint) -> c_int; fn sep_pm_clear_dev_disable(dev: *mut c_void) -> c_int; fn sep_pm_dart_prepare(dev: *mut c_void) -> c_int; fn sep_pm_dart_restore(dev: *mut c_void); @@ -92,10 +93,26 @@ impl SleepGate { } } -/// Whether the enclave gated itself within the bound. -pub(crate) fn wait_gated(dev: &kernel::device::Device) -> bool { +/// Whether the enclave gated itself within the bound. Observation errors are preserved. +pub(crate) fn wait_gated(dev: &kernel::device::Device) -> Result { // SAFETY: `dev` is the bound SEP device. - unsafe { sep_pm_wait_gated(dev.as_raw().cast(), GATE_TIMEOUT_MS) == 0 } + let rc = unsafe { sep_pm_wait_gated(dev.as_raw().cast(), GATE_TIMEOUT_MS) }; + match rc { + 0 => Ok(true), + 1 => Ok(false), + _ => Err(Error::from_errno(rc)), + } +} + +/// Whether the gated enclave's domain also went off within the bound. +pub(crate) fn wait_off(dev: &kernel::device::Device) -> Result { + // SAFETY: `dev` is the bound SEP device. + let rc = unsafe { sep_pm_wait_off(dev.as_raw().cast(), GATE_TIMEOUT_MS) }; + match rc { + 0 => Ok(true), + 1 => Ok(false), + _ => Err(Error::from_errno(rc)), + } } /// Saves the enclave's DART while it can be read, before the enclave is told. @@ -110,14 +127,14 @@ pub(crate) fn dart_prepare(dev: &kernel::device::Device) -> Result<()> { /// Leaves `request` to the sleep driver's last refusal, which sends it with interrupts off /// and saves the DART just before. Its answer is the message whose `mask` bits read `reply`. -/// False where there's no such step, and the request goes now. +/// `Ok(false)` where there's no such step, and the request goes now. pub(crate) fn leave_for_commit( dev: &kernel::device::Device, mbox: *mut kernel::bindings::apple_mbox, request: kernel::soc::apple::mailbox::Message, reply: u64, mask: u64, -) -> bool { +) -> Result { // SAFETY: `dev` is the bound SEP device and `mbox` its mailbox, which outlives the sleep: // `commit_finish` takes the request back at the resume. let rc = unsafe { @@ -130,7 +147,15 @@ pub(crate) fn leave_for_commit( mask, ) }; - rc == 0 + if rc == 0 { + Ok(true) + } else { + let err = Error::from_errno(rc); + match err { + EOPNOTSUPP | ENOSYS => Ok(false), + _ => Err(err), + } + } } /// After a sleep whose request was left to its last refusal: whether it went out. `None` if diff --git a/drivers/soc/apple/pm_shim.c b/drivers/soc/apple/pm_shim.c index f7ca2e711e6be..5f15672fee69c 100644 --- a/drivers/soc/apple/pm_shim.c +++ b/drivers/soc/apple/pm_shim.c @@ -27,10 +27,19 @@ #include #include +#include + #include "shim.h" /* The state word of a PMGR power domain, as pmgr-pwrstate has it. */ #define SEP_PS_DEV_DISABLE BIT(10) +#define SEP_PS_ACTUAL GENMASK(7, 4) + +/* Test only, one sleep: refuse it at its last step once the enclave has gated. */ +static bool sleep_refuse_after_gate; +module_param(sleep_refuse_after_gate, bool, 0644); +MODULE_PARM_DESC(sleep_refuse_after_gate, + "Test only: refuse the next deep sleep after the enclave gated, to show it comes back"); static void (*sep_pm_event)(bool entering); static int (*sep_pm_sleep)(void); @@ -89,23 +98,49 @@ static int sep_pm_domain(struct device *dev, struct regmap **regmap, u32 *offset } /* - * Returns 0 once the enclave has gated itself, which shows as DEV_DISABLE, and - * -ENOENT if there is no domain to read. The actual state is no guide: a running - * enclave idles at 0 with auto-enable on. + * Returns 0 once the enclave has gated itself, 1 if the deadline expired, or a + * negative observation error (including -ENOENT if there is no domain to read). + * Keep a read's -ETIMEDOUT distinct from the polling deadline. DEV_DISABLE is + * the evidence: a running enclave idles at actual state 0 with auto-enable on. */ int sep_pm_wait_gated(void *ptr, unsigned int timeout_ms) { struct regmap *regmap; u32 offset, val; - int ret; + int ret, poll_ret; + + ret = sep_pm_domain(ptr, ®map, &offset); + if (ret) + return ret; + + poll_ret = read_poll_timeout(regmap_read, ret, ret || (val & SEP_PS_DEV_DISABLE), + 1000, timeout_ms * USEC_PER_MSEC, false, + regmap, offset, &val); + if (ret) + return ret; + + return !!poll_ret; +} + +/* As sep_pm_wait_gated(), until the gated domain also reads off. */ +int sep_pm_wait_off(void *ptr, unsigned int timeout_ms) +{ + struct regmap *regmap; + u32 offset, val; + int ret, poll_ret; ret = sep_pm_domain(ptr, ®map, &offset); if (ret) return ret; - return regmap_read_poll_timeout(regmap, offset, val, - val & SEP_PS_DEV_DISABLE, - 1000, timeout_ms * USEC_PER_MSEC); + poll_ret = read_poll_timeout(regmap_read, ret, + ret || ((val & SEP_PS_DEV_DISABLE) && !(val & SEP_PS_ACTUAL)), + 1000, timeout_ms * USEC_PER_MSEC, false, + regmap, offset, &val); + if (ret) + return ret; + + return !!poll_ret; } int sep_pm_clear_dev_disable(void *ptr) @@ -168,8 +203,8 @@ void sep_pm_dart_restore(void *ptr) /* * The sleep request, sent by the sleep driver as its very last refusal, with interrupts off: - * an enclave that gated doesn't come back with its power kept, so a sleep refused anywhere - * before must find it never told. The driver hands over the request and its answer's match + * an enclave that gated has to be started again, so a sleep refused anywhere before should + * find it never told. The driver hands over the request and its answer's match * at .suspend, with its receive interrupt held. */ static struct sep_pm_commit { @@ -267,6 +302,10 @@ static int sep_pm_commit_tell(void *data) apple_mbox_set_recv_enabled(c->mbox, false); c->gate_ret = readl_poll_timeout_atomic(c->ps, c->state, c->state & SEP_PS_DEV_DISABLE, SEP_PM_POLL_US, SEP_PM_TIMEOUT_US); + if (!c->gate_ret && READ_ONCE(sleep_refuse_after_gate)) { + WRITE_ONCE(sleep_refuse_after_gate, false); + return -EAGAIN; + } return c->gate_ret; } @@ -285,7 +324,9 @@ int sep_pm_commit_prepare(void *ptr, void *mbox, u64 msg0, u32 msg1, u64 reply, u32 offset; int ret; - if (!READ_ONCE(sep_pm_suspending)) + /* Check capability before resources that legacy providers need not have. */ + if (!READ_ONCE(sep_pm_suspending) || !IS_ENABLED(CONFIG_APPLE_T8140_SLEEP) || + !is_kernel_in_hyp_mode()) return -EOPNOTSUPP; if (c->registered) return -EBUSY; @@ -387,6 +428,8 @@ static int __maybe_unused sep_pm_suspend_noirq(struct device *dev) return 0; ret = sep_pm_wait_gated(dev, 5000); + if (ret > 0) + ret = -ETIMEDOUT; if (ret == -ENOENT) return 0; if (ret) { diff --git a/drivers/soc/apple/pmp-export.c b/drivers/soc/apple/pmp-export.c index 93a8aa9123757..195452b8f0d02 100644 --- a/drivers/soc/apple/pmp-export.c +++ b/drivers/soc/apple/pmp-export.c @@ -209,6 +209,11 @@ static int apple_pmp_suspend_late(struct device *dev) if (pmp_device != dev || pmp_asleep) return 0; at_commit = apple_pmp_commit_available(dev); + /* Idled any earlier, a refusal after it would leave it stopped. */ + if (!at_commit && apple_sleep_commit_stops_coprocs()) { + dev_err(dev, "no stop at the sleep's commit: refusing the sleep\n"); + return -EBUSY; + } apple_pmp_report_sleep(dev, at_commit); ret = at_commit ? apple_pmp_commit_prepare(dev) : apple_pmp_sleep(pmp_data); if (ret) { diff --git a/drivers/soc/apple/sep.rs b/drivers/soc/apple/sep.rs index 41cc5cda7b48f..d5c490f152b4a 100644 --- a/drivers/soc/apple/sep.rs +++ b/drivers/soc/apple/sep.rs @@ -1419,8 +1419,18 @@ impl SepData { return Err(EBUSY); } - if self.sleep_leave_for_commit() { - return Ok(()); + match self.sleep_leave_for_commit() { + Ok(true) => return Ok(()), + Ok(false) => {} + Err(e) => { + dev_err!( + self.dev, + "sleep refused: its last refusal could not be armed ({:?})\n", + e + ); + self.set_sleep_gate(pm::SleepGate::Open); + return Err(e); + } } if let Err(e) = pm::dart_prepare(&self.dev) { @@ -1450,10 +1460,10 @@ impl SepData { } /// Where the sleep driver takes it, the request goes out as its very last refusal, with - /// interrupts off: a gated enclave comes back only through a real sleep, so any earlier - /// refusal has to find it never told. Its answer is polled there, so the receive + /// interrupts off: a gated enclave has to be started again, so any earlier refusal should + /// find it never told. Its answer is polled there, so the receive /// interrupt is held from now on. The gate stays at draining until the wake. - fn sleep_leave_for_commit(&self) -> bool { + fn sleep_leave_for_commit(&self) -> Result { let request = proto::encode_control(&proto::op_sleep(), proto::TAG_SLEEP); let left = match self.mbox.lock().as_ref() { Some(mbox) => pm::leave_for_commit( @@ -1463,13 +1473,13 @@ impl SepData { proto::SLEEP_REPLY, proto::SLEEP_REPLY_MASK, ), - None => false, - }; + None => Err(ENODEV), + }?; if left { self.sleep_hold_rx(); dev_dbg!(self.dev, "the sleep request is left for the sleep's last refusal\n"); } - left + Ok(left) } /// Holds off the receive interrupt, whose handler reads the mailbox. @@ -1492,12 +1502,12 @@ impl SepData { /// Back from a sleep, or a suspend that was aborted. The enclave has kept /// its state and takes no new endpoint setup. /// - /// Reading the mailbox of a gated enclave can stall the CPU for good, so only - /// the power cycle of a real sleep ends one. A sleep refused after the request, - /// which only the request itself can do where the sleep driver sends it, leaves - /// the enclave asleep, and requests fail until the next real sleep or a reboot. - /// If it never gated it was never stopped, and a doorbell would reach an - /// endpoint: ask it something instead. + /// Reading the mailbox of a gated enclave can stall the CPU for good, so it's + /// touched only once the enclave is started again. macOS starts an enclave that + /// took its sleep request the same way whether the system slept or not, so a + /// sleep refused after the enclave gated starts it as a real sleep does, once its + /// domain reads off. If it never gated it was never stopped, and a doorbell would + /// reach an endpoint: ask it something instead. pub(crate) fn sleep_exit(&self) { // Left to the sleep's last refusal: told only if it went out. match pm::commit_finish(&self.dev) { @@ -1520,17 +1530,16 @@ impl SepData { } let answered = if real { self.sleep_kick() - } else if pm::wait_gated(&self.dev) { - self.sleep_hold_rx(); - self.set_sleep_gate(pm::SleepGate::Lost); - dev_warn!( - self.dev, - "the enclave stays asleep until the next real sleep or a reboot\n" - ); - return; } else { - dev_warn!(self.dev, "the enclave never gated its power domain\n"); - self.sleep_probe() + match pm::wait_gated(&self.dev) { + Ok(true) => self.sleep_restart(), + Ok(false) => { + dev_warn!(self.dev, "the enclave did not gate within the timeout\n"); + self.sleep_probe() + } + // Without a domain observation, do not touch the mailbox. + Err(e) => Err(e), + } }; match answered { Ok(()) => { @@ -1539,13 +1548,27 @@ impl SepData { dev_info!(self.dev, "the enclave is back from its sleep request\n"); } Err(e) => { - dev_err!(self.dev, "the enclave did not come back ({:?}); it stays unused\n", e); + dev_err!( + self.dev, + "the enclave did not come back ({:?}); it stays unused until the next real sleep or a reboot\n", + e + ); self.sleep_hold_rx(); self.set_sleep_gate(pm::SleepGate::Lost); } } } + /// Starts an enclave that gated in a sleep that was refused, once its domain is off. + fn sleep_restart(&self) -> Result<()> { + if !pm::wait_off(&self.dev)? { + dev_warn!(self.dev, "the enclave gated, but its domain stayed on\n"); + return Err(ETIMEDOUT); + } + dev_info!(self.dev, "the sleep was refused after the enclave gated: starting it again\n"); + self.sleep_kick() + } + /// Starts a gated enclave and waits for the messages that say it's up. fn sleep_kick(&self) -> Result<()> { pm::clear_dev_disable(&self.dev)?; diff --git a/drivers/soc/apple/shim.h b/drivers/soc/apple/shim.h index a3e0ca6e91bab..e1401fe25f0d9 100644 --- a/drivers/soc/apple/shim.h +++ b/drivers/soc/apple/shim.h @@ -31,6 +31,7 @@ int sep_pm_register(void *dev, void (*event)(bool entering), int (*sleep)(void), void sep_pm_unregister(void); int sep_pm_keep_domains(void *dev); int sep_pm_wait_gated(void *dev, unsigned int timeout_ms); +int sep_pm_wait_off(void *dev, unsigned int timeout_ms); int sep_pm_clear_dev_disable(void *dev); int sep_pm_dart_prepare(void *dev); void sep_pm_dart_restore(void *dev); diff --git a/drivers/soc/apple/t8140-sleep.c b/drivers/soc/apple/t8140-sleep.c index 3870a462ed8ff..4ac3959df122f 100644 --- a/drivers/soc/apple/t8140-sleep.c +++ b/drivers/soc/apple/t8140-sleep.c @@ -802,6 +802,12 @@ bool apple_sleep_coproc_stopped(struct device *dev) } EXPORT_SYMBOL_GPL(apple_sleep_coproc_stopped); +bool apple_sleep_commit_stops_coprocs(void) +{ + return READ_ONCE(t8140_sleep) && pm_suspend_via_firmware(); +} +EXPORT_SYMBOL_GPL(apple_sleep_commit_stops_coprocs); + /* * The last thing that can fail on the way to the commit: no CPU but this one is up and * interrupts are off, so no lock can be held. A coprocessor stopped here comes back only @@ -869,7 +875,7 @@ static int t8140_sleep_cpus_hold(struct t8140_sleep *s) return ret; } -/* Last of all the refusals: an enclave that gated comes back only through a real sleep. */ +/* Last of all the refusals: an enclave that gated has to be started again. */ static int t8140_sleep_enclave_tell(struct t8140_sleep *s) { int ret; diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index 17404410399c9..c3b02c3602d0a 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -187,7 +187,7 @@ struct dwc3_apple { bool sleep_reinit; /* Looks for the fixed hub once a host restarted after a sleep has had time to find it. */ struct delayed_work hub_check; - /* The core didn't come back from a sleep, and nothing may touch it until a reboot. */ + /* The core didn't come back from a sleep or a power cycle: nothing may touch it. */ bool sleep_dead; /* Stopped for a deep sleep: the state to come back to, and the wakeup setting. */ bool sleep_stopped; @@ -1218,13 +1218,43 @@ static int dwc3_apple_setup_role_switch(struct dwc3_apple *appledwc) return 0; } +/* + * A core that may be unpowered gets one more power cycle, as macOS's Off and cold start, and + * the host starts over once it's back. Until then nothing may touch it, and every sleep is + * refused, as xHCI's suspend would touch it. Returns whether it's back. + */ +static bool dwc3_apple_recover(struct dwc3_apple *appledwc) +{ + int ret; + + lockdep_assert_held(&appledwc->lock); + ret = apple_atcphy_sleep_recover(appledwc->usb2_phy); + if (ret < 0) { + if (!appledwc->sleep_dead) + dev_err(appledwc->dev, + "USB host unpowered after the sleep (%d): USB off, sleeps refused until it's back or a reboot\n", + ret); + appledwc->sleep_dead = true; + return false; + } + if (appledwc->sleep_dead) + dev_info(appledwc->dev, "USB host back after a power cycle: restarting it\n"); + appledwc->sleep_dead = false; + ret = dwc3_apple_exit(appledwc); + if (ret) + dev_err(appledwc->dev, "Failed to stop after the power cycle, err=%d\n", ret); + else + dwc3_apple_start_after_sleep(appledwc, DWC3_APPLE_HOST); + + return true; +} + /* * After a sleep in place: the PHY says whether the core came back to D0, and the GUID register * whether it kept its registers. A core set up again at the resume kept its host and devices, * which reset-resumed. Otherwise restart the host as after a stop, and the devices enumerate - * again. With a core that may be unpowered even that would touch it, so USB stays off, and - * later sleeps are refused, as xHCI's suspend would touch it too. Returns whether the host - * was kept. + * again. A core that may be unpowered gets another power cycle first. Returns whether the + * host was kept. */ static bool dwc3_apple_sleep_kept(struct dwc3_apple *appledwc) { @@ -1235,8 +1265,7 @@ static bool dwc3_apple_sleep_kept(struct dwc3_apple *appledwc) appledwc->sleep_in_place = false; ret = apple_atcphy_sleep_result(appledwc->usb2_phy); if (ret == -ENODEV) { - appledwc->sleep_dead = true; - dev_err(appledwc->dev, "the core didn't come back from the sleep: USB off until a reboot\n"); + dwc3_apple_recover(appledwc); return false; } if (appledwc->sleep_reinit) { @@ -1282,15 +1311,17 @@ static int dwc3_apple_pm_notify_locked(struct dwc3_apple *appledwc, unsigned lon switch (action) { case PM_SUSPEND_PREPARE: + /* Any sleep: xHCI's suspend would touch a core that may be unpowered. */ + if (appledwc->sleep_dead && !appledwc->shutting_down && + !dwc3_apple_recover(appledwc)) { + dev_err(dev, "refusing the sleep: the USB host is still unpowered\n"); + return notifier_from_errno(-EIO); + } if (pm_suspend_default_s2idle() || appledwc->shutting_down) { *finish = in_place; *kept = true; break; } - if (appledwc->sleep_dead) { - dev_err(dev, "refusing the sleep: the core is gone until a reboot\n"); - return notifier_from_errno(-EIO); - } /* xHCI suspends and resumes as usual, and the wakeup setting stays. */ if (in_place) { appledwc->sleep_guid = dwc3_readl(&appledwc->dwc, DWC3_GUID); diff --git a/include/linux/phy/apple-atc.h b/include/linux/phy/apple-atc.h index bc91b4206310f..fe4631bf22b39 100644 --- a/include/linux/phy/apple-atc.h +++ b/include/linux/phy/apple-atc.h @@ -31,6 +31,12 @@ int apple_atcphy_sleep_prepare(struct phy *usb2_phy); */ int apple_atcphy_sleep_result(struct phy *usb2_phy); +/* + * For a controller that may be unpowered after a sleep: power cycles it, as macOS's Off + * and cold start do. APPLE_ATCPHY_SLEEP_POWER_LOST once it's back, reset, or -ENODEV. + */ +int apple_atcphy_sleep_recover(struct phy *usb2_phy); + /* * Ends a sleep that prepare took on, with the SuperSpeed side back as the mux wants it. * Without @kept the host was restarted; only a failure of the PHY's own then makes the @@ -52,6 +58,11 @@ static inline int apple_atcphy_sleep_result(struct phy *usb2_phy) return -EOPNOTSUPP; } +static inline int apple_atcphy_sleep_recover(struct phy *usb2_phy) +{ + return -EOPNOTSUPP; +} + static inline void apple_atcphy_sleep_finish(struct phy *usb2_phy, bool kept) { } diff --git a/include/linux/soc/apple/sleep.h b/include/linux/soc/apple/sleep.h index 7f931af1a5e4c..2a22638377a11 100644 --- a/include/linux/soc/apple/sleep.h +++ b/include/linux/soc/apple/sleep.h @@ -43,7 +43,7 @@ void apple_sleep_coproc_unregister(struct device *dev); int apple_sleep_coproc_set_last_check(struct device *dev, int (*last_check)(void *data)); /* - * The enclave's sleep request, which it comes back from only through a real sleep: @tell + * The enclave's sleep request, after which it has to be started again: @tell * runs with interrupts off as the very last refusal, after every other one and before any * coprocessor stops. Nonzero refuses the sleep. */ @@ -56,6 +56,12 @@ void apple_sleep_coproc_set_ready(struct device *dev, bool ready); /* Whether the last step stopped it: after a refused sleep, false means it still runs. */ bool apple_sleep_coproc_stopped(struct device *dev); +/* + * Whether this sleep's commit stops the registered coprocessors. Then one that can't be + * registered refuses the sleep: stopped any earlier, a later refusal would strand it. + */ +bool apple_sleep_commit_stops_coprocs(void); + /* * The USB host sleeps in place for this sleep, as macOS's does: its always-on domain stays * up through the commit. Its PHY sets it once the controller is in its hibernation state, @@ -163,6 +169,11 @@ static inline bool apple_sleep_coproc_stopped(struct device *dev) return false; } +static inline bool apple_sleep_commit_stops_coprocs(void) +{ + return false; +} + static inline void apple_sleep_usb_set_asleep(bool asleep) { } diff --git a/tools/asahi/display26-tests.py b/tools/asahi/display26-tests.py index c43323f0d856f..f0c993fe03624 100644 --- a/tools/asahi/display26-tests.py +++ b/tools/asahi/display26-tests.py @@ -113,7 +113,8 @@ def main(): 'static inline int apple_sleep_coproc_register(struct device *d, void __iomem *a, bool p,\n' '\t\t\t\t\t int (*f)(void *), void *x) { assert(!"no system sleep here"); return -EIO; }\n' 'static inline void apple_sleep_coproc_set_ready(struct device *d, bool r) { assert(!"no system sleep here"); }\n' - 'static inline bool apple_sleep_coproc_stopped(struct device *d) { assert(!"no system sleep here"); return false; }\n') + 'static inline bool apple_sleep_coproc_stopped(struct device *d) { assert(!"no system sleep here"); return false; }\n' + 'static inline bool apple_sleep_commit_stops_coprocs(void) { assert(!"no system sleep here"); return false; }\n') write(tmp, 'linux/soc/apple/pmp-display.h', '#include \nbool apple_pmp_display_retained_board(void);\n') write(tmp, 'mock.h', '''#pragma once #include diff --git a/tools/asahi/pmu-rtc-tests.py b/tools/asahi/pmu-rtc-tests.py index 541990a615941..062597ca80b56 100644 --- a/tools/asahi/pmu-rtc-tests.py +++ b/tools/asahi/pmu-rtc-tests.py @@ -25,6 +25,9 @@ 'control |= rtc->hw->control_event_bit;'), 'stowe-offset': ('static const struct apple_pmu_rtc_hw apple_pmu_rtc_stowe = {', 'static const struct apple_pmu_rtc_hw apple_pmu_rtc_stowe = {\n\t.offset_in_seconds = true,'), + 'baku-limit': ('.max_delay = U32_MAX,', '.max_delay = APPLE_PMU_RTC_MAX_DELAY,'), + 'set-no-round': ('(counter + BIT(APPLE_PMU_RTC_HZ_SHIFT - 1))', '(counter)'), + 'set-negative': ('if (offset < 0 || offset', 'if (offset'), } PRELUDE = r''' @@ -41,12 +44,14 @@ typedef uint64_t u64; typedef int64_t s64; typedef uint32_t __le32; +typedef uint64_t __le64; #define BIT(n) (1U << (n)) #define U32_MAX UINT32_MAX #define RTC_TIMESTAMP_END_9999 253402300799LL #define cpu_to_le32(x) (x) #define le32_to_cpu(x) (x) #define le64_to_cpu(x) (x) +#define cpu_to_le64(x) (x) #define IS_ERR(x) ((intptr_t)(x) < 0) #define PTR_ERR(x) ((int)(intptr_t)(x)) #define kfree free @@ -63,7 +68,7 @@ struct rtc_wkalrm { struct rtc_time time; bool enabled, pending; }; struct rtc_device { int events; }; struct device { void *data; bool wake; int wake_events; }; -struct nvmem_cell { u8 data[8]; size_t bytes; int error; }; +struct nvmem_cell { u8 data[8]; size_t bytes; int error, write_error; }; struct regmap { u8 bytes[65536]; unsigned ctrl, counter, compare, mask, event, bit, control_event; @@ -92,6 +97,10 @@ void *p = malloc(c->bytes); assert(p); memcpy(p, c->data, c->bytes); *bytes = c->bytes; return p; } +static int nvmem_cell_write(struct nvmem_cell *c, void *buf, size_t bytes) { + if (c->write_error) return c->write_error; + assert(bytes == c->bytes); memcpy(c->data, buf, bytes); return bytes; +} static bool io_error(struct regmap *m) { return ++m->ops == m->fail; } static int regmap_read(struct regmap *m, unsigned reg, unsigned *v) { if (io_error(m)) return -EIO; @@ -228,16 +237,59 @@ assert(!apple_pmu_rtc_validate_alarm(&rtc, 101)); assert(apple_pmu_rtc_validate_alarm(&rtc, 100) == -ETIME); assert(!apple_pmu_rtc_validate_alarm(&rtc, 3700)); - assert(apple_pmu_rtc_validate_alarm(&rtc, 3701) == -ERANGE); alarm.time.seconds = 1100; assert(apple_pmu_rtc_set_alarm(&dev, &alarm) == -ETIME); alarm.time.seconds = 4701; - assert(apple_pmu_rtc_set_alarm(&dev, &alarm) == -ERANGE); + if (a) { + /* One hour, where the alarm only ends a suspend-to-idle. */ + assert(apple_pmu_rtc_validate_alarm(&rtc, 3701) == -ERANGE); + assert(apple_pmu_rtc_set_alarm(&dev, &alarm) == -ERANGE); + } else { + /* Baku: as far as the 32-bit comparator reaches. */ + assert(!apple_pmu_rtc_validate_alarm(&rtc, 3701)); + assert(!apple_pmu_rtc_validate_alarm(&rtc, U32_MAX)); + assert(!apple_pmu_rtc_set_alarm(&dev, &alarm) && comparator() == 3701); + alarm.time.seconds = 86400 * 7 + 1000; + assert(!apple_pmu_rtc_set_alarm(&dev, &alarm) && comparator() == 86400 * 7); + alarm.time.seconds = 1000 + (s64)U32_MAX + 1; + assert(apple_pmu_rtc_set_alarm(&dev, &alarm) == -ERANGE); + assert(!apple_pmu_rtc_alarm_enable(&dev, 0)); + } /* Reject malformed offsets and propagate NVMEM errors. */ offset.bytes = a ? 8 : 6; assert(apple_pmu_rtc_offset(&rtc, &ticks) == -EINVAL); offset.error = -EIO; assert(apple_pmu_rtc_read_time(&dev, &time) == -EIO); + if (!a) { + /* Baku sets its whole-second offset, rounding the counter to the second. */ + reset(v); + time.seconds = 5000; + set_counter((100ULL << 16) + 32767); + assert(!apple_pmu_rtc_set_time(&dev, &time)); + assert(get_unaligned_le64(offset.data) == 4900); + assert(!apple_pmu_rtc_read_time(&dev, &time) && time.seconds == 5000); + time.seconds = 5000; + set_counter((100ULL << 16) + 32768); + assert(!apple_pmu_rtc_set_time(&dev, &time)); + assert(get_unaligned_le64(offset.data) == 4899); + /* Alarms follow the new offset. */ + alarm.time.seconds = 5030; + assert(!apple_pmu_rtc_set_alarm(&dev, &alarm) && comparator() == 131); + assert(!apple_pmu_rtc_alarm_enable(&dev, 0)); + /* Before the counter's own time, and past what the offset may hold. */ + time.seconds = 99; + assert(apple_pmu_rtc_set_time(&dev, &time) == -ERANGE); + time.seconds = RTC_TIMESTAMP_END_9999; + assert(apple_pmu_rtc_set_time(&dev, &time) == -ERANGE); + assert(get_unaligned_le64(offset.data) == 4899); + time.seconds = 5000; + offset.write_error = -EIO; + assert(apple_pmu_rtc_set_time(&dev, &time) == -EIO); + offset.write_error = 0; + map.ops = 0; map.fail = 1; + assert(apple_pmu_rtc_set_time(&dev, &time) == -EIO); + assert(get_unaligned_le64(offset.data) == 4899); + } reset(v); set_offset(-32768); if (a) { @@ -285,7 +337,7 @@ } int main(void) { run_variant(BAKU); run_variant(ABBEY); run_variant(STOWE); run_variant(MAVERICK); - puts("PMU RTC: Baku, Abbey, Stowe and Maverick arithmetic, W1C transactions and fault injection passed"); + puts("PMU RTC: Baku, Abbey, Stowe and Maverick arithmetic, W1C transactions, Baku clock setting and fault injection passed"); return 0; } ''' diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index c59c064f7f07e..913c30edfd53e 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -155,12 +155,17 @@ def mutations(flag, names, base, expect=KILL): + [(out('--output') + ['--old-usb'], KILL), (out('--output') + ['--old-regulatory'], KILL)], 'pciec-policy-tests.py': [(out(), PASS)], 'pmgr-bus-tests.py': [([], PASS)], + 'sep-suspend-errors-tests.py': [(out(), PASS)] + + mutations('--mutation', ('arm-fallback', 'read-as-deadline', + 'probe-on-error', 'no-capability-check', 'stay-lost', + 'start-while-on', 'off-ignores-actual'), out()), 'pmp-agx-vote-tests.py': [(out('--output'), PASS), (out('--output') + ['--mutations'], PASS)], 'pmp-j613-hwmon-tests.py': [([], PASS)], 'pmu-rtc-tests.py': [(out(), PASS)] + mutations('--mutation', ('ack-clobber', 'arm-ack', 'offset-seconds', 'offset-sign', 'event-bit', 'wrong-control', - 'maverick-event', 'stowe-offset'), out()), + 'maverick-event', 'stowe-offset', 'baku-limit', + 'set-no-round', 'set-negative'), out()), 'perf-mmap-alias-lifecycle-tests.py': [([], PASS)], 'qspi-nvram-tests.py': [(out('--output'), PASS)], 'rtkit-crashlog-tests.py': [(out(), PASS)] diff --git a/tools/asahi/sep-suspend-errors-tests.py b/tools/asahi/sep-suspend-errors-tests.py new file mode 100644 index 0000000000000..3c6c2274b253e --- /dev/null +++ b/tools/asahi/sep-suspend-errors-tests.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Compile actual SEP suspend/error paths with host-only dependency stubs. + +Extract C prepare/wait/noirq and Rust wrappers/enter/leave/exit/restart verbatim. +Kernel poll macros are also extracted verbatim; virtual time replaces sleeping. +These controls test error classification and AP decisions, not firmware or MMIO +safety against autonomous late gating. No target access or kernel build occurs. +""" +import argparse +from pathlib import Path +import re +import resource +import subprocess + +ROOT = Path(__file__).resolve().parents[2] + + +def function(source, name): + match = re.search(r'^.*\b(?:fn|int(?: __maybe_unused)?) ' + name + r'\([^;]*?\)\s*(?:->[^\{]+)?\{', + source, re.M) + if not match: + raise AssertionError(name) + start = source.index('{', match.start()) + end, depth = start + 1, 1 + while depth: + depth += (source[end] == '{') - (source[end] == '}') + end += 1 + return source[match.start():end] + + +def macro(source, name): + return re.search(r'^#define ' + name + r'\([^\n]*\n(?:.*\\\n)*.*', + source, re.M).group() + + +C_PREFIX = r''' +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; typedef uint64_t u64; typedef long long ktime_t; +#define BIT(n) (1u << (n)) +#define SEP_PS_DEV_DISABLE BIT(10) +#define GENMASK(h, l) (((~0u) << (l)) & (~0u >> (31 - (h)))) +#define SEP_PS_ACTUAL GENMASK(7, 4) +#define USEC_PER_MSEC 1000 +#define READ_ONCE(x) (x) +#define IS_ENABLED(x) (config_enabled) +#define CONFIG_APPLE_T8140_SLEEP 1 +#define __maybe_unused +#define dev_err(...) ((void)0) +#define dev_dbg(...) ((void)0) +struct device_node { struct device_node *parent; }; +struct device { struct device_node *of_node; }; +struct resource { unsigned long start; }; +struct regmap { int unused; }; +struct apple_mbox_msg { u64 msg0; u32 msg1; }; +struct apple_mbox {}; +static struct device_node parent, node = { &parent }; +static struct device device = { &node }, dart; +static struct regmap regmap; +static bool config_enabled, hyp, sep_pm_suspending, dart_present, map_ok; +static int domain_error, read_error, read_error_after, register_error, gate_after, reads; +static int actual_until; +static int puts, maps, unmaps, registrations, lookups; +static ktime_t now; +static bool (*sep_pm_told)(void); +static bool yes(void) { return true; } +static bool pm_suspend_via_firmware(void) { return true; } +static bool is_kernel_in_hyp_mode(void) { return hyp; } +static int sep_pm_domain(void *p, struct regmap **m, u32 *o) { + lookups++; *m = ®map; *o = 0; return domain_error; +} +static int regmap_read(struct regmap *m, u32 o, u32 *v) { + ++reads; if (read_error && reads >= read_error_after) return read_error; + *v = gate_after && reads >= gate_after ? SEP_PS_DEV_DISABLE : 0; + if (reads < actual_until) *v |= SEP_PS_ACTUAL; + return 0; +} +#define might_sleep_if(x) ((void)0) +#define barrier() ((void)0) +#define cpu_relax() ((void)0) +static ktime_t ktime_get(void) { return now; } +static ktime_t ktime_add_us(ktime_t t, u64 us) { return t + us; } +static int ktime_compare(ktime_t a, ktime_t b) { return (a > b) - (a < b); } +static void usleep_range(unsigned long a, unsigned long b) { now += b; } +static struct device_node *of_parse_phandle(struct device_node *n, const char *p, int i) { + lookups++; return domain_error ? NULL : &node; +} +static int of_property_read_u32(struct device_node *n, const char *p, u32 *v) { *v = 0; return 0; } +static int of_address_to_resource(struct device_node *n, int i, struct resource *r) { r->start = 0; return 0; } +static void of_node_put(struct device_node *n) {} +static unsigned resource_size(struct resource *r) { return 4; } +static struct device *sep_pm_dart(struct device *d) { return dart_present ? &dart : NULL; } +static void *dev_get_drvdata(struct device *d) { return d; } +static void *ioremap(unsigned long start, unsigned size) { ++maps; return map_ok ? ®map : NULL; } +static void iounmap(void *p) { ++unmaps; } +static void put_device(struct device *d) { ++puts; } +static int sep_pm_commit_tell(void *data) { assert(0 && "registration must not send"); return 0; } +static int apple_sleep_enclave_register(struct device *d, int (*tell)(void *), void *data) { + ++registrations; return register_error; +} +static struct sep_pm_commit { + struct device *dev, *dart; struct apple_mbox *mbox; void *ps; + struct apple_mbox_msg request; u64 reply, reply_mask; + bool registered, sent; +} sep_pm_commit; +void fixture(void) { + memset(&sep_pm_commit, 0, sizeof(sep_pm_commit)); + config_enabled = hyp = sep_pm_suspending = dart_present = map_ok = true; + domain_error = read_error = register_error = gate_after = reads = 0; + puts = maps = unmaps = registrations = lookups = 0; now = 0; read_error_after = 1; + actual_until = 0; + sep_pm_told = yes; +} +void parameter(int which, int value) { + switch (which) { + case 0: config_enabled = value; break; case 1: hyp = value; break; + case 2: sep_pm_suspending = value; break; case 3: dart_present = value; break; + case 4: map_ok = value; break; case 5: domain_error = value; break; + case 6: read_error = value; break; case 7: register_error = value; break; + case 8: gate_after = value; break; case 9: sep_pm_commit.registered = value; break; + case 10: read_error_after = value; break; case 11: actual_until = value; break; + } +} +int counter(int which) { + switch (which) { case 0: return puts; case 1: return maps; + case 2: return unmaps; case 3: return registrations; case 4: return lookups; + default: return reads; } +} +void *device_pointer(void) { return &device; } +''' + +RUST_PREFIX = r''' +#![allow(dead_code, unused_imports, unused_variables)] +use std::cell::{Cell, RefCell, RefMut}; +use std::ffi::{c_int, c_void}; +use std::sync::atomic::{AtomicU32, Ordering::Relaxed}; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] struct Error(i32); +impl Error { fn from_errno(n: i32) -> Self { assert!(n < 0); Self(n) } } +type Result = std::result::Result; +const EBUSY: Error = Error(-16); const ENODEV: Error = Error(-19); +const ENOMEM: Error = Error(-12); const EIO: Error = Error(-5); +const ENOENT: Error = Error(-2); const ETIMEDOUT: Error = Error(-110); +const EOPNOTSUPP: Error = Error(-95); const ENOSYS: Error = Error(-38); +macro_rules! dev_err { ($($a:tt)*) => {{}}; } +macro_rules! dev_warn { ($($a:tt)*) => {{}}; } +macro_rules! dev_dbg { ($($a:tt)*) => {{}}; } +macro_rules! dev_info { ($($a:tt)*) => {{}}; } +extern "C" { + fn fixture(); fn parameter(which: i32, value: i32); fn counter(which: i32) -> i32; + fn device_pointer() -> *mut c_void; + fn noirq() -> i32; +} +thread_local! { static REAL: Cell = const { Cell::new(false) }; } +mod bindings { pub unsafe fn pm_resume_via_firmware() -> bool { super::REAL.with(|v| v.get()) } } +mod kernel { + pub mod device { + pub struct Device; + impl Device { pub fn as_raw(&self) -> *mut std::ffi::c_void { unsafe { crate::device_pointer() } } } + } + pub mod bindings { #[allow(non_camel_case_types)] pub enum apple_mbox {} } + pub mod soc { pub mod apple { pub mod mailbox { + pub struct Message { pub msg0: u64, pub msg1: u32 } + } } } + pub mod time { + pub struct Delta; impl Delta { pub fn from_millis(_: i64) -> Self { Self } } + pub mod delay { pub fn fsleep(_: super::Delta) {} } + } +} +mod proto { + pub const EP_CONTROL: u32 = 0; pub const CONTROL_TIMEOUT_MS: u32 = 5000; + pub const TAG_SLEEP: u32 = 0; pub const SLEEP_REPLY: u64 = 0; + pub const SLEEP_REPLY_MASK: u64 = 0; + pub fn op_sleep() {} + pub fn encode_control(_: &(), _: u32) -> crate::kernel::soc::apple::mailbox::Message { + crate::kernel::soc::apple::mailbox::Message { msg0: 0, msg1: 0 } + } +} +mod control { + pub fn wait_idle bool, D: FnMut(u32)>(_: u32, _: u32, mut f: F, _: D) -> bool { !f() } +} +struct Lock(RefCell); +impl Lock { fn lock(&self) -> RefMut<'_, T> { self.0.borrow_mut() } } +struct Control; impl Control { fn requests_in_flight(&self) -> bool { false } } +struct Mbox; +impl Mbox { + fn as_raw(&self) -> *mut kernel::bindings::apple_mbox { std::ptr::null_mut() } + fn set_recv_enabled(&self, _: bool) {} +} +const PHASE_READY: u32 = 1; +struct SepData { + dev: kernel::device::Device, phase: AtomicU32, control: Lock, mbox: Lock>, + gate: Cell, held: Cell, probes: Cell, requests: Cell, + kicks: Cell, releases: Cell, finish: Cell>, reply: Cell>, +} +impl SepData { + fn new() -> Self { + Self { dev: kernel::device::Device, phase: AtomicU32::new(PHASE_READY), + control: Lock(RefCell::new(Control)), mbox: Lock(RefCell::new(Some(Mbox))), + gate: Cell::new(pm::SleepGate::Open), held: Cell::new(false), + probes: Cell::new(0), requests: Cell::new(0), kicks: Cell::new(0), releases: Cell::new(0), + finish: Cell::new(None), reply: Cell::new(Ok(())) } + } + fn endpoint_present(&self, _: u32) -> bool { true } + fn sleep_gate(&self) -> pm::SleepGate { self.gate.get() } + fn set_sleep_gate(&self, g: pm::SleepGate) { self.gate.set(g); } + fn sleep_hold_rx(&self) { self.held.set(true); } + fn sleep_release_rx(&self) { self.held.set(false); self.releases.set(self.releases.get()+1); } + fn sleep_request(&self) -> Result<()> { self.requests.set(self.requests.get()+1); Ok(()) } + fn sleep_probe(&self) -> Result<()> { self.probes.set(self.probes.get()+1); self.sleep_release_rx(); self.reply.get() } + fn sleep_kick(&self) -> Result<()> { self.kicks.set(self.kicks.get()+1); self.sleep_release_rx(); self.reply.get() } +} +''' + +RUST_TESTS = r''' +fn reset() { unsafe { fixture() }; REAL.with(|v| v.set(false)); pm::FINISH.with(|v|v.set(None)); } +fn param(n: i32, v: i32) { unsafe { parameter(n, v) } } +fn count(n: i32) -> i32 { unsafe { counter(n) } } +#[test] +fn registration_failures_refuse_unsent() { + for (p,v,e) in [(7,-16,EBUSY),(4,0,ENOMEM),(3,0,ENODEV),(5,-2,ENOENT),(9,1,EBUSY),(7,-19,ENODEV)] { + reset(); param(p,v); let s=SepData::new(); + assert_eq!(s.sleep_enter(),Err(e)); + assert_eq!(s.gate.get(),pm::SleepGate::Open); + assert_eq!(s.requests.get(),0); assert!(!s.held.get()); + assert_eq!(count(0), if p==7 || p==4 {1} else {0}); + assert_eq!(count(2), if p==7 {1} else {0}); + } +} +#[test] +fn explicit_unsupported_keeps_legacy_path() { + for p in [0,1,2] { + reset(); param(p,0); param(5,-2); let s=SepData::new(); + assert_eq!(s.sleep_enter(),Ok(())); assert_eq!(s.requests.get(),1); + assert_eq!(s.gate.get(),pm::SleepGate::Told); assert!(s.held.get()); + assert_eq!(count(4),0); assert_eq!(count(1),0); + } + for e in [-95,-38] { + reset(); param(7,e); let s=SepData::new(); + assert_eq!(s.sleep_enter(),Ok(())); assert_eq!(s.requests.get(),1); + assert_eq!(count(0),1); assert_eq!(count(2),1); + } +} +#[test] +fn successful_registration_defers_and_unsent_refusal_reopens() { + reset(); let s=SepData::new(); assert_eq!(s.sleep_enter(),Ok(())); + assert_eq!(s.requests.get(),0); assert_eq!(s.gate.get(),pm::SleepGate::Draining); + assert!(s.held.get()); assert_eq!(count(3),1); + pm::FINISH.with(|v| v.set(Some(false))); s.sleep_exit(); + assert_eq!(s.gate.get(),pm::SleepGate::Open); assert!(!s.held.get()); + assert_eq!(s.probes.get()+s.kicks.get(),0); +} +#[test] +fn missing_mailbox_is_an_error() { + reset(); let s=SepData::new(); *s.mbox.lock()=None; + assert_eq!(s.sleep_enter(),Err(ENODEV)); assert_eq!(s.requests.get(),0); + assert_eq!(s.gate.get(),pm::SleepGate::Open); +} +#[test] +fn observation_errors_never_probe_or_release_rx() { + for (p,e,at) in [(5,-2,1),(5,-5,1),(6,-5,1),(6,-110,1),(6,-5,3),(6,-110,3)] { + reset(); param(p,e); param(10,at); let s=SepData::new(); + s.gate.set(pm::SleepGate::Told); s.held.set(true); pm::FINISH.with(|v| v.set(Some(true))); + assert_eq!(pm::wait_gated(&s.dev),Err(Error(e))); + s.sleep_exit(); assert_eq!(s.gate.get(),pm::SleepGate::Lost); + assert!(s.held.get()); assert_eq!(s.probes.get()+s.kicks.get()+s.releases.get(),0); + } +} +#[test] +fn deadline_expiry_preserves_existing_probe_outcomes() { + for reply in [Ok(()),Err(EIO)] { + reset(); let s=SepData::new(); s.gate.set(pm::SleepGate::Told); s.held.set(true); s.reply.set(reply); + assert_eq!(pm::wait_gated(&s.dev),Ok(false)); assert!(count(5)>1); + s.sleep_exit(); assert_eq!(s.probes.get(),1); assert_eq!(s.kicks.get(),0); + assert_eq!(s.gate.get(),if reply.is_ok() {pm::SleepGate::Open} else {pm::SleepGate::Lost}); + } +} +#[test] +fn observed_gate_starts_the_enclave_as_a_real_wake_does() { + for reply in [Ok(()),Err(ETIMEDOUT)] { + reset(); param(8,3); let s=SepData::new(); s.gate.set(pm::SleepGate::Told); s.held.set(true); + s.reply.set(reply); pm::FINISH.with(|v| v.set(Some(true))); + s.sleep_exit(); + assert_eq!(s.kicks.get(),1); assert_eq!(s.probes.get(),0); + if reply.is_ok() { + assert_eq!(s.gate.get(),pm::SleepGate::Open); assert!(!s.held.get()); + } else { + assert_eq!(s.gate.get(),pm::SleepGate::Lost); assert!(s.held.get()); + } + } +} +#[test] +fn a_gated_domain_still_on_is_never_started() { + reset(); param(8,3); param(11,i32::MAX); let s=SepData::new(); + s.gate.set(pm::SleepGate::Told); s.held.set(true); + assert_eq!(pm::wait_gated(&s.dev),Ok(true)); + let before=count(5); assert_eq!(pm::wait_off(&s.dev),Ok(false)); assert!(count(5)-before>1); + s.sleep_exit(); assert_eq!(s.gate.get(),pm::SleepGate::Lost); assert!(s.held.get()); + assert_eq!(s.probes.get()+s.kicks.get()+s.releases.get(),0); +} +#[test] +fn the_domain_going_off_late_still_starts_it() { + reset(); param(8,3); param(11,40); let s=SepData::new(); + s.gate.set(pm::SleepGate::Told); s.held.set(true); + s.sleep_exit(); assert_eq!(s.kicks.get(),1); assert_eq!(s.gate.get(),pm::SleepGate::Open); +} +#[test] +fn off_wait_errors_never_start_it() { + for (e,at) in [(-5,4),(-110,4)] { + reset(); param(8,3); param(6,e); param(10,at); let s=SepData::new(); + s.gate.set(pm::SleepGate::Told); s.held.set(true); + s.sleep_exit(); assert_eq!(s.gate.get(),pm::SleepGate::Lost); assert!(s.held.get()); + assert_eq!(s.probes.get()+s.kicks.get()+s.releases.get(),0); + } +} +#[test] +fn the_final_read_after_the_deadline_decides() { + reset(); let s=SepData::new(); assert_eq!(pm::wait_gated(&s.dev),Ok(false)); let n=count(5); + // A gate first seen at the read after the deadline counts. + reset(); param(8,n); assert_eq!(pm::wait_gated(&s.dev),Ok(true)); assert_eq!(count(5),n); + // One read later it was never seen. + reset(); param(8,n+1); assert_eq!(pm::wait_gated(&s.dev),Ok(false)); assert_eq!(count(5),n); + // That last read failing is an error, not a deadline. + for e in [-5,-110] { + reset(); param(6,e); param(10,n); assert_eq!(pm::wait_gated(&s.dev),Err(Error(e))); + } + // The same for the domain going off. + reset(); param(8,1); param(11,n); assert_eq!(pm::wait_off(&s.dev),Ok(true)); assert_eq!(count(5),n); + reset(); param(8,1); param(11,n+1); assert_eq!(pm::wait_off(&s.dev),Ok(false)); + reset(); param(8,1); param(11,i32::MAX); param(6,-5); param(10,n); + assert_eq!(pm::wait_off(&s.dev),Err(EIO)); +} +#[test] +fn real_resume_and_lost_refusal_keep_existing_branches() { + reset(); let s=SepData::new(); s.gate.set(pm::SleepGate::Lost); s.held.set(true); + assert_eq!(s.sleep_enter(),Ok(())); s.sleep_exit(); assert_eq!(s.requests.get()+s.probes.get()+s.kicks.get(),0); + REAL.with(|v| v.set(true)); s.sleep_exit(); assert_eq!(s.kicks.get(),1); + assert_eq!(s.gate.get(),pm::SleepGate::Open); +} +#[test] +fn noirq_preserves_errno_contract_and_legacy_missing_domain() { + for (p,v,expected) in [(8,1,0),(8,0,-110),(5,-2,0),(6,-5,-5),(6,-110,-110)] { + reset(); param(p,v); assert_eq!(unsafe { noirq() },expected); + } +} +''' + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--out', type=Path, required=True) + parser.add_argument('--source', type=Path, default=ROOT) + parser.add_argument('--contracts', type=Path) + parser.add_argument('--mutation', choices=['arm-fallback', 'read-as-deadline', 'probe-on-error', + 'no-capability-check', 'stay-lost', 'start-while-on', + 'off-ignores-actual']) + args = parser.parse_args() + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + args.out.mkdir(parents=True, exist_ok=True) + contracts = args.contracts or args.source + pm = (args.source / 'drivers/soc/apple/pm.rs').read_text() + sep = (args.source / 'drivers/soc/apple/sep.rs').read_text() + shim = (args.source / 'drivers/soc/apple/pm_shim.c').read_text() + if args.mutation == 'arm-fallback': + pm = pm.replace('_ => Err(err),', '_ => Ok(false),') + if args.mutation == 'read-as-deadline': + shim = shim.replace('if (ret)\n\t\treturn ret;\n\n\treturn !!poll_ret;', 'if (ret)\n\t\treturn 1;\n\n\treturn !!poll_ret;') + if args.mutation == 'probe-on-error': + sep = sep.replace('Err(e) => Err(e),', 'Err(_e) => self.sleep_probe(),') + if args.mutation == 'stay-lost': + sep = sep.replace('Ok(true) => self.sleep_restart(),', 'Ok(true) => Err(ETIMEDOUT),') + if args.mutation == 'start-while-on': + sep = sep.replace('if !pm::wait_off(&self.dev)? {', 'if false {') + if args.mutation == 'off-ignores-actual': + shim = shim.replace('((val & SEP_PS_DEV_DISABLE) && !(val & SEP_PS_ACTUAL))', '(val & SEP_PS_DEV_DISABLE)') + if args.mutation == 'no-capability-check': + shim = shim.replace(' || !IS_ENABLED(CONFIG_APPLE_T8140_SLEEP) ||\n\t !is_kernel_in_hyp_mode()', '') + iopoll = (contracts / 'include/linux/iopoll.h').read_text() + regmap = (contracts / 'include/linux/regmap.h').read_text() + c = C_PREFIX + '\n' + '\n'.join([macro(iopoll, 'poll_timeout_us'), macro(iopoll, 'read_poll_timeout'), macro(regmap, 'regmap_read_poll_timeout')]) + c += '\n' + '\n'.join(function(shim, n) for n in ['sep_pm_wait_gated', 'sep_pm_wait_off', 'sep_pm_commit_prepare', 'sep_pm_suspend_noirq']) + c += '\nint noirq(void) { return sep_pm_suspend_noirq(&device); }\n' + cpath, obj = args.out / 'control.c', args.out / 'control.o' + cpath.write_text(c) + subprocess.run(['cc', '-std=gnu11', '-Wall', '-Wextra', '-Wno-unused-parameter', '-c', str(cpath), '-o', str(obj)], check=True) + rust = RUST_PREFIX + '\nmod pm {\nuse crate::*;\nconst GATE_TIMEOUT_MS:u32=5000;\n' + rust += '''extern "C" { + fn sep_pm_wait_gated(dev:*mut c_void, ms:u32)->c_int; + fn sep_pm_wait_off(dev:*mut c_void, ms:u32)->c_int; + fn sep_pm_commit_prepare(dev:*mut c_void, mbox:*mut c_void, a:u64, b:u32, c:u64, d:u64)->c_int; + } + pub fn dart_prepare(_: &kernel::device::Device)->Result<()> { Ok(()) } + pub fn commit_finish(_: &kernel::device::Device)->Option { FINISH.with(|v|v.get()) } + thread_local! { pub static FINISH:Cell>=const { Cell::new(None) }; } +''' + rust += pm[pm.index('#[derive(Clone, Copy, Debug, PartialEq, Eq)]'):pm.index('/// Whether the enclave gated')] + rust += '\n'.join(function(pm, n) for n in ['wait_gated', 'wait_off', 'leave_for_commit']) + '\n}\nimpl SepData {\n' + rust += '\n'.join(function(sep, n) for n in ['sleep_enter', 'sleep_leave_for_commit', 'sleep_exit', 'sleep_restart']) + rust += '\n}\n' + RUST_TESTS + rpath, binary = args.out / 'control.rs', args.out / 'control' + rpath.write_text(rust) + subprocess.run(['rustc', '--edition=2021', '--test', str(rpath), '-C', f'link-arg={obj.resolve()}', '-o', str(binary)], check=True) + result = subprocess.run([str(binary), '--test-threads=1'], check=False) + raise SystemExit(result.returncode) + + +if __name__ == '__main__': + main() diff --git a/tools/aurora-wake/bt-wake-cycle.sh b/tools/aurora-wake/bt-wake-cycle.sh index 3c10162256e97..2eb419b345823 100755 --- a/tools/aurora-wake/bt-wake-cycle.sh +++ b/tools/aurora-wake/bt-wake-cycle.sh @@ -17,9 +17,21 @@ set -u +# The PMU RTC, which can wake the system: rtc0 on J700, rtc1 on others. +pmu_rtc() { + local name + for name in /sys/class/rtc/rtc*/name; do + [[ $(cat "$name" 2> /dev/null) == apple-pmu-rtc* ]] || continue + name=${name%/name} + echo "${name##*/}" + return + done + echo rtc0 +} + mode= secs=120 -rtc=rtc1 +rtc=$(pmu_rtc) addr= out=/var/tmp/bt-wake/cycle-$(date +%Y%m%d-%H%M%S) bdf=0000:01:00.1 diff --git a/tools/aurora-wake/bt-wake-power.sh b/tools/aurora-wake/bt-wake-power.sh index af5d49dadb216..be6d1eb06c8f6 100755 --- a/tools/aurora-wake/bt-wake-power.sh +++ b/tools/aurora-wake/bt-wake-power.sh @@ -9,6 +9,17 @@ # - the battery energy used, when running on battery (energy_now). # Usage: bt-wake-power.sh [-s secs] [-n pairs] [-o outdir] set -u +# The PMU RTC, which can wake the system: rtc0 on J700, rtc1 on others. +pmu_rtc() { + local name + for name in /sys/class/rtc/rtc*/name; do + [[ $(cat "$name" 2> /dev/null) == apple-pmu-rtc* ]] || continue + name=${name%/name} + echo "${name##*/}" + return + done + echo rtc0 +} secs=300 pairs=2 out=/var/tmp/bt-wake/power-$(date +%Y%m%d-%H%M%S) @@ -19,6 +30,7 @@ done dev=/sys/bus/pci/devices/0000:01:00.1 bat=/sys/class/power_supply/macsmc-battery irq=$(awk -F: '/mt7932-bt/ { gsub(/ /, "", $1); print $1; exit }' /proc/interrupts) +rtc=$(pmu_rtc) mkdir -p "$out" orig=$(cat "$dev/power/wakeup") trap 'echo "$orig" > "$dev/power/wakeup"' EXIT @@ -42,7 +54,7 @@ for p in $(seq 1 "$pairs"); do e0=$(cat "$bat/energy_now") st=$(cat "$bat/status") t0=$(date +%s.%N) - rtcwake -d rtc1 -m freeze -s "$secs" > /dev/null 2>&1 + rtcwake -d "$rtc" -m freeze -s "$secs" > /dev/null 2>&1 t1=$(date +%s.%N) b1=$(irq_sum "$irq"); a1=$(irq_sum all); e1=$(cat "$bat/energy_now") dmesg > "$out/dmesg-$p-$mode.txt" diff --git a/tools/aurora-wake/s2idle-rtc-regression.sh b/tools/aurora-wake/s2idle-rtc-regression.sh index 89a131aca0b59..74697560040db 100755 --- a/tools/aurora-wake/s2idle-rtc-regression.sh +++ b/tools/aurora-wake/s2idle-rtc-regression.sh @@ -3,7 +3,7 @@ # # Unattended suspend-to-idle regression test for Apple silicon (J700 and others). # -# Suspends with an RTC alarm (rtc1, apple-pmu-rtc, by default) several times and +# Suspends with an RTC alarm (apple-pmu-rtc by default) several times and # checks after every resume that: # - the time actually spent suspended (CLOCK_BOOTTIME minus CLOCK_MONOTONIC) # lasted until the alarm: no spurious wakeup, e.g. from a running xHC, @@ -24,9 +24,21 @@ set -u +# The PMU RTC, which can wake the system: rtc0 on J700, rtc1 on others. +pmu_rtc() { + local name + for name in /sys/class/rtc/rtc*/name; do + [[ $(cat "$name" 2> /dev/null) == apple-pmu-rtc* ]] || continue + name=${name%/name} + echo "${name##*/}" + return + done + echo rtc0 +} + cycles=3 secs=20 -rtc=rtc1 +rtc=$(pmu_rtc) out= ignore= dry_run=0