diff --git a/MAINTAINERS b/MAINTAINERS index 82291a41d6f20..e3386f4aea0a3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2769,6 +2769,7 @@ F: Documentation/hwmon/macsmc-hwmon.rst F: Documentation/power/apple-s2idle.rst F: arch/arm64/boot/dts/apple/ F: drivers/bluetooth/hci_bcm4377.c +F: tools/asahi/bcm4377-sleep-config-tests.* F: drivers/clk/clk-apple-nco.c F: drivers/cpufreq/apple-soc-cpufreq.c F: drivers/dma/apple-admac.c diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index dc24c3e5057a0..02cc8fccbc27c 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -3098,6 +3100,15 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id) hdev); } +/* Final release runs after every device resume, including reversible unwind. */ +static void bcm4377_sleep_finish(struct device *dev) +{ + struct bcm4377_data *bcm4377 = pci_get_drvdata(to_pci_dev(dev)); + + WRITE_ONCE(bcm4377->setup_retry_blocked, false); + bcm4377_queue_setup_retry(bcm4377); +} + static int bcm4377_resume(struct device *dev); static int bcm4377_suspend(struct device *dev) @@ -3106,6 +3117,12 @@ static int bcm4377_suspend(struct device *dev) struct bcm4377_data *bcm4377 = pci_get_drvdata(pdev); int ret; + ret = apple_radio_sleep_endpoint_prepare(dev, bcm4377_sleep_finish); + if (ret) + return ret; + if (apple_radio_sleep_owned(dev) && device_may_wakeup(dev)) + return -EBUSY; + bcm4377_resume_recovery_stop(bcm4377); WRITE_ONCE(bcm4377->setup_retry_blocked, true); cancel_delayed_work_sync(&bcm4377->setup_retry_work); @@ -3116,8 +3133,10 @@ static int bcm4377_suspend(struct device *dev) ret = hci_suspend_dev(bcm4377->hdev); if (ret) { bcm4377->resume_powered = false; - WRITE_ONCE(bcm4377->setup_retry_blocked, false); - bcm4377_queue_setup_retry(bcm4377); + if (!apple_radio_sleep_owned(dev)) { + WRITE_ONCE(bcm4377->setup_retry_blocked, false); + bcm4377_queue_setup_retry(bcm4377); + } return ret; } @@ -3152,8 +3171,11 @@ static int bcm4377_suspend(struct device *dev) /* Flush the posted write before the system goes to sleep. */ ioread32(bcm4377->bar0 + BCM4377_BAR0_SLEEP_CONTROL); bcm4377->quiesced = true; - - return 0; + /* This records the command/readflush, not a firmware idle acknowledgment. */ + ret = apple_radio_sleep_endpoint_suspended(dev); + if (ret) + bcm4377_resume(dev); + return ret; } static int bcm4377_resume(struct device *dev) @@ -3163,6 +3185,10 @@ static int bcm4377_resume(struct device *dev) unsigned long flags; int ret; + ret = apple_radio_sleep_endpoint_resume(dev); + if (ret) + return ret; + if (bcm4377->wake_armed) { disable_irq_wake(pdev->irq); bcm4377->wake_armed = false; @@ -3186,13 +3212,46 @@ static int bcm4377_resume(struct device *dev) bcm4377->resume_powered = false; ret = hci_resume_dev(bcm4377->hdev); - WRITE_ONCE(bcm4377->setup_retry_blocked, false); - if (!ret) - bcm4377_queue_setup_retry(bcm4377); + apple_radio_sleep_endpoint_resumed(dev, ret); + if (!apple_radio_sleep_owned(dev)) { + WRITE_ONCE(bcm4377->setup_retry_blocked, false); + if (!ret) + bcm4377_queue_setup_retry(bcm4377); + } return ret; } -static DEFINE_SIMPLE_DEV_PM_OPS(bcm4377_ops, bcm4377_suspend, bcm4377_resume); +static int bcm4377_resume_noirq(struct device *dev) +{ + struct bcm4377_data *bcm4377 = pci_get_drvdata(to_pci_dev(dev)); + u32 before, after; + int ret; + + if (!apple_radio_sleep_owned(dev) || !pm_resume_via_firmware()) + return 0; + + /* + * PERST resets the private BAR windows, which PCI's standard saved state + * does not cover. Restore them before the IRQ handler or UNQUIESCE can + * access the retained controller through those windows. + */ + ret = pci_read_config_dword(bcm4377->pdev, BCM4377_PCIECFG_BAR0_WINDOW1, &before); + if (!ret) + ret = bcm4377_init_cfg(bcm4377); + if (!ret) + ret = pci_read_config_dword(bcm4377->pdev, BCM4377_PCIECFG_BAR0_WINDOW1, + &after); + if (ret || after != bcm4377->hw->bar0_window1) + panic("J713 Bluetooth PCI window restore failed: %d", ret); + dev_info(dev, "private PCI windows restored after sleep (%#x -> %#x)\n", + before, after); + return 0; +} + +static const struct dev_pm_ops bcm4377_ops = { + SYSTEM_SLEEP_PM_OPS(bcm4377_suspend, bcm4377_resume) + .resume_noirq = bcm4377_resume_noirq, +}; static const struct dmi_system_id bcm4377_dmi_board_table[] = { { diff --git a/tools/asahi/bcm4377-sleep-config-tests.c b/tools/asahi/bcm4377-sleep-config-tests.c new file mode 100644 index 0000000000000..8740031cc6bd3 --- /dev/null +++ b/tools/asahi/bcm4377-sleep-config-tests.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; +#define BIT(n) (1U << (n)) +#define dev_info(...) ((void)0) +struct device { + int unused; +}; +struct pci_dev { + int unused; +}; +struct hardware { + u32 bar0_window1, bar0_window2, bar0_core2_window2; + bool has_bar0_core2_window2, clear_pciecfg_subsystem_ctrl_bit19; +}; +struct bcm4377_data { + struct pci_dev *pdev; + struct hardware *hw; +}; +static struct pci_dev pci; +static struct hardware hw = { 0x18002000, 0x18109000, 0x18106000, true, false }; +static struct bcm4377_data data = { &pci, &hw }; +static bool owned, physical, ignore_window; +static unsigned int ops, writes, fail; +static u32 cfg[64]; +static jmp_buf fatal; +static struct pci_dev *to_pci_dev(struct device *d) +{ + (void)d; + return &pci; +} +static struct bcm4377_data *pci_get_drvdata(struct pci_dev *d) +{ + (void)d; + return &data; +} +static bool apple_radio_sleep_owned(struct device *d) +{ + (void)d; + return owned; +} +static bool pm_resume_via_firmware(void) +{ + return physical; +} +static int pci_read_config_dword(struct pci_dev *d, unsigned int off, u32 *v) +{ + (void)d; + if (++ops == fail) + return 0x87; + *v = cfg[off / 4]; + return 0; +} +static int pci_write_config_dword(struct pci_dev *d, unsigned int off, u32 v) +{ + (void)d; + writes++; + if (++ops == fail) + return 0x87; + if (!(ignore_window && off == 0x80)) + cfg[off / 4] = v; + return 0; +} +#define panic(...) longjmp(fatal, 1) +/* ACTUAL */ +static void fresh(void) +{ + owned = physical = true; + ignore_window = false; + ops = writes = fail = 0; + memset(cfg, 0, sizeof(cfg)); + cfg[0x88 / 4] = BIT(19) | 0x80; +} +int main(void) +{ + struct device dev = {}; + + fresh(); + + assert(!bcm4377_resume_noirq(&dev)); + assert(cfg[0x80 / 4] == hw.bar0_window1); + assert(cfg[0x70 / 4] == hw.bar0_window2); + assert(cfg[0x74 / 4] == 0x18011000); + assert(cfg[0x78 / 4] == hw.bar0_core2_window2); + assert(cfg[0x84 / 4] == 0x19000000); + assert(cfg[0x88 / 4] == (BIT(19) | BIT(16) | 0x80)); + unsigned int count = ops; + + fresh(); + + owned = false; + assert(!bcm4377_resume_noirq(&dev)); + assert(!ops); + fresh(); + physical = false; + assert(!bcm4377_resume_noirq(&dev)); + assert(!ops); + for (unsigned int i = 1; i <= count; i++) { + fresh(); + fail = i; + if (!setjmp(fatal)) { + bcm4377_resume_noirq(&dev); + assert(0); + } + assert(ops == i); + } + fresh(); + ignore_window = true; + if (!setjmp(fatal)) { + bcm4377_resume_noirq(&dev); + assert(0); + } + puts("PASS: private PCI windows, retained-path skip and restore failures"); + return 0; +} diff --git a/tools/asahi/bcm4377-sleep-config-tests.py b/tools/asahi/bcm4377-sleep-config-tests.py new file mode 100644 index 0000000000000..cdbdf0f7e9054 --- /dev/null +++ b/tools/asahi/bcm4377-sleep-config-tests.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Exercise private PCI-window restoration before Bluetooth resume IRQs.""" + +from pathlib import Path +import re +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[2] +source = (root / "drivers/bluetooth/hci_bcm4377.c").read_text() +functions = [] +for name in ("bcm4377_init_cfg", "bcm4377_resume_noirq"): + match = re.search( + r"^static int " + name + r"\([^;]+?\{.*?\n\}", source, re.M | re.S + ) + assert match, name + functions.append(match.group()) +constants = "\n".join( + line for line in source.splitlines() if line.startswith("#define BCM4377_PCIECFG_") +) +code = ( + Path(__file__) + .with_suffix(".c") + .read_text() + .replace("/* ACTUAL */", constants + "\n" + "\n".join(functions)) +) +with tempfile.TemporaryDirectory() as tmp: + c = Path(tmp) / "test.c" + c.write_text(code) + exe = Path(tmp) / "test" + subprocess.run( + ["cc", "-std=gnu11", "-Wall", "-Wextra", "-Werror", str(c), "-o", str(exe)], + check=True, + ) + subprocess.run([str(exe)], check=True)