diff --git a/drivers/net/wireless/apple/centauri/t6050/centauri_ipc.c b/drivers/net/wireless/apple/centauri/t6050/centauri_ipc.c index 2ff1fbf61b85a..3036676cfe694 100644 --- a/drivers/net/wireless/apple/centauri/t6050/centauri_ipc.c +++ b/drivers/net/wireless/apple/centauri/t6050/centauri_ipc.c @@ -323,32 +323,49 @@ static int enumerate_functions(struct centauri_t6050 *c) !(pci_resource_flags(ipc->function[i], 0) & IORESOURCE_MEM) || pci_resource_len(ipc->function[i], 0) < (i ? 0x2000 : 0x9080)) return -ENODEV; - if (i && ipc->function[i]->dev.driver) - return -EBUSY; + if (i) { + /* + * The driver core sets dev.driver before calling probe. + * Beta can be probing only to defer until c->ready. + * Wait for that probe before deciding it is owned. + */ + device_lock(&ipc->function[i]->dev); + if (ipc->function[i]->dev.driver) { + ret = -EBUSY; + goto unlock; + } + } ipc->owned[i] = true; ipc->bar[i] = pci_iomap(ipc->function[i], 0, 0); - if (!ipc->bar[i]) - return -ENOMEM; + if (!ipc->bar[i]) { + ret = -ENOMEM; + goto unlock; + } if (i) { ret = pci_enable_device_mem(ipc->function[i]); if (ret) - return ret; + goto unlock; ipc->enabled[i] = true; } - if (i == 1 && !ipc->function[i]->dev.driver) { + if (i == 1) { dev_pm_domain_set(&ipc->function[i]->dev, ¢auri_t6050_alpha_pm_domain); ipc->alpha_pm_domain = true; } ret = dma_set_mask_and_coherent(&ipc->function[i]->dev, DMA_BIT_MASK(40)); if (ret) - return ret; + goto unlock; if (i == 1) { ret = centauri_t6050_request_irqs(&c->alpha_irqs, ipc->function[i], 16, centauri_t6050_alpha_irq, c); if (ret) - return ret; + goto unlock; } pci_set_master(ipc->function[i]); +unlock: + if (i) + device_unlock(&ipc->function[i]->dev); + if (ret) + return ret; } return 0; } diff --git a/tools/testing/selftests/drivers/apple/Makefile b/tools/testing/selftests/drivers/apple/Makefile index a325eed4b3ba3..f76d69fcdf822 100644 --- a/tools/testing/selftests/drivers/apple/Makefile +++ b/tools/testing/selftests/drivers/apple/Makefile @@ -7,10 +7,11 @@ # bcm4377 tests exercise DMA ownership, context addresses and firmware handshakes. # aop-las.py checks the production angle decoder and IIO callback on the host. # aop-motion.py checks the motion report decoder and calibration selector. +# centauri-t6050-probe.py checks sibling probe serialization on the build host. TEST_PROGS := macsmc-j713.py atc-t8132-usb3.py pmgr-inherited.py dwc3-initial-role.py \ sn201202x-transport.py bcm4377-dma-window.py bcm4377-radio-sids.py \ bcm4377-state-wait.py bcm4377-rti-address.py aop-las.py \ - aop-motion.py + aop-motion.py centauri-t6050-probe.py TEST_FILES := j813-peripherals.sh include ../../lib.mk diff --git a/tools/testing/selftests/drivers/apple/centauri-t6050-probe.py b/tools/testing/selftests/drivers/apple/centauri-t6050-probe.py new file mode 100755 index 0000000000000..d377a1b75233c --- /dev/null +++ b/tools/testing/selftests/drivers/apple/centauri-t6050-probe.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +# Adapted from betizzel/omarchy-m5 patches/linux-aurora-n1/smoke-bootstrap.py. +"""Run the actual Centauri PCI enumeration against a deterministic probe race. + +No hardware access. The driver-core fixture holds Beta's device lock with +its driver pointer set, as a probe which will return -EPROBE_DEFER does. +The enumeration thread must wait for that probe, not report permanent +ownership and abort shared Wi-Fi/Bluetooth startup. Also cover genuine +ownership and lock release after early/late resource failures. +""" +import argparse +from pathlib import Path +import subprocess +import tempfile + +PRELUDE = r""" +#include +#include +#include +#include +#include +#include +#include +#include + +struct device { + pthread_mutex_t mutex; + _Atomic(void *) driver; + int function; +}; +struct pci_bus { int number; }; +struct pci_dev { + struct device dev; + struct pci_bus *bus; + uint16_t vendor, device; + unsigned cfg_size, devfn, multifunction; + bool master; +}; +struct centauri_t6050_ipc { + struct pci_dev *function[3]; + void *bar[3]; + bool owned[3], enabled[3], alpha_pm_domain; +}; +struct centauri_t6050 { + struct pci_dev *pdev; + struct centauri_t6050_ipc *ipc; + int alpha_irqs; +}; +static struct pci_bus bus; +static struct pci_dev functions[3]; +static struct centauri_t6050_ipc ipc; +static struct centauri_t6050 control; +static int centauri_t6050_alpha_pm_domain; +static int bound_driver; +static enum { NONE, MAP_FAILURE, ENABLE_FAILURE, DMA_FAILURE, IRQ_FAILURE } fault; +static pthread_mutex_t gate = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t changed = PTHREAD_COND_INITIALIZER; +static bool beta_lock_attempted, enumeration_done; +static int enumeration_result; + +#define PCI_VENDOR_ID_APPLE 0x106b +#define IORESOURCE_MEM 1 +#define DMA_BIT_MASK(bits) ((UINT64_C(1) << (bits)) - 1) +#define PCI_SLOT(devfn) ((devfn) >> 3) +#define PCI_DEVFN(slot, function) (((slot) << 3) | (function)) +#define pci_domain_nr(bus) 0 +#define pci_rescan_bus(bus) ((void)0) +#define pci_resource_flags(pdev, bar) IORESOURCE_MEM +#define pci_resource_len(pdev, bar) 0x10000 +#define centauri_t6050_alpha_irq 0 + +static void device_lock(struct device *dev) +{ + assert(dev->function != 0); + if (dev->function == 2) { + assert(!pthread_mutex_lock(&gate)); + beta_lock_attempted = true; + assert(!pthread_cond_signal(&changed)); + assert(!pthread_mutex_unlock(&gate)); + } + assert(!pthread_mutex_lock(&dev->mutex)); +} +static void device_unlock(struct device *dev) +{ + assert(!pthread_mutex_unlock(&dev->mutex)); +} +static struct pci_dev *pci_get_domain_bus_and_slot(int domain, int busno, int devfn) +{ + (void)domain; + (void)busno; + assert(devfn >= 0 && devfn < 3); + return &functions[devfn]; +} +static void *pci_iomap(struct pci_dev *pdev, int bar, int length) +{ + (void)bar; + (void)length; + return fault == MAP_FAILURE && pdev->dev.function == 1 ? NULL : pdev; +} +static int pci_enable_device_mem(struct pci_dev *pdev) +{ + return fault == ENABLE_FAILURE && pdev->dev.function == 1 ? -EIO : 0; +} +static void dev_pm_domain_set(struct device *dev, int *domain) +{ + assert(pthread_mutex_trylock(&dev->mutex) == EBUSY); + (void)domain; +} +static int dma_set_mask_and_coherent(struct device *dev, uint64_t mask) +{ + (void)mask; + return fault == DMA_FAILURE && dev->function == 2 ? -ERANGE : 0; +} +static int centauri_t6050_request_irqs(int *irqs, struct pci_dev *pdev, + unsigned count, int handler, void *data) +{ + (void)irqs; + (void)pdev; + (void)count; + (void)handler; + (void)data; + return fault == IRQ_FAILURE ? -ENOSPC : 0; +} +static void pci_set_master(struct pci_dev *pdev) +{ + pdev->master = true; +} +""" + +TESTS = r""" +static void reset_fixture(void) +{ + memset(&ipc, 0, sizeof(ipc)); + fault = NONE; + beta_lock_attempted = enumeration_done = false; + for (int i = 0; i < 3; i++) { + functions[i].dev.driver = NULL; + functions[i].master = false; + } +} +static void assert_sibling_locks_released(void) +{ + for (int i = 1; i < 3; i++) { + assert(!pthread_mutex_trylock(&functions[i].dev.mutex)); + assert(!pthread_mutex_unlock(&functions[i].dev.mutex)); + } +} +static void *enumeration_thread(void *unused) +{ + (void)unused; + enumeration_result = enumerate_functions(&control); + assert(!pthread_mutex_lock(&gate)); + enumeration_done = true; + assert(!pthread_cond_signal(&changed)); + assert(!pthread_mutex_unlock(&gate)); + return NULL; +} +int main(void) +{ + pthread_t thread; + + for (int i = 0; i < 3; i++) { + functions[i].dev.function = i; + assert(!pthread_mutex_init(&functions[i].dev.mutex, NULL)); + functions[i].bus = &bus; + functions[i].vendor = PCI_VENDOR_ID_APPLE; + functions[i].device = i ? 0x1901 + i : 0x1900; + functions[i].cfg_size = 0x1000; + functions[i].devfn = i; + } + control.pdev = &functions[0]; + control.ipc = &ipc; + + reset_fixture(); + /* Emulate driver core inside Beta's not-ready probe. No sleeps or races. */ + assert(!pthread_mutex_lock(&functions[2].dev.mutex)); + functions[2].dev.driver = &bound_driver; + assert(!pthread_create(&thread, NULL, enumeration_thread, NULL)); + assert(!pthread_mutex_lock(&gate)); + while (!beta_lock_attempted && !enumeration_done) + assert(!pthread_cond_wait(&changed, &gate)); + assert(!pthread_mutex_unlock(&gate)); + /* Returning -EPROBE_DEFER clears dev.driver before unlocking the device. */ + functions[2].dev.driver = NULL; + assert(!pthread_mutex_unlock(&functions[2].dev.mutex)); + assert(!pthread_join(thread, NULL)); + if (enumeration_result) { + fprintf(stderr, "FAIL: deferred Beta probe aborted IPC: %d\n", enumeration_result); + return 1; + } + for (int i = 0; i < 3; i++) + assert(ipc.owned[i] && ipc.bar[i] && functions[i].master); + assert(ipc.enabled[1] && ipc.enabled[2]); + assert_sibling_locks_released(); + puts("PASS: concurrent deferred Beta probe does not abort shared startup"); + + reset_fixture(); + functions[2].dev.driver = &bound_driver; + assert(enumerate_functions(&control) == -EBUSY); + assert(!ipc.owned[2] && !ipc.bar[2] && !functions[2].master); + assert(functions[2].dev.driver == &bound_driver); + assert_sibling_locks_released(); + puts("PASS: genuinely bound Beta is rejected without taking ownership"); + + reset_fixture(); + fault = MAP_FAILURE; + assert(enumerate_functions(&control) == -ENOMEM); + assert(!ipc.enabled[1] && !functions[1].master); + assert_sibling_locks_released(); + puts("PASS: early mapping failure releases the sibling device lock"); + + reset_fixture(); + fault = ENABLE_FAILURE; + assert(enumerate_functions(&control) == -EIO); + assert(ipc.owned[1] && ipc.bar[1] && !ipc.enabled[1] && !functions[1].master); + assert_sibling_locks_released(); + puts("PASS: device-enable failure releases the sibling device lock"); + + reset_fixture(); + fault = DMA_FAILURE; + assert(enumerate_functions(&control) == -ERANGE); + assert(ipc.owned[2] && ipc.enabled[2] && !functions[2].master); + assert_sibling_locks_released(); + puts("PASS: DMA-mask failure releases the sibling device lock"); + + reset_fixture(); + fault = IRQ_FAILURE; + assert(enumerate_functions(&control) == -ENOSPC); + assert(ipc.enabled[1] && !functions[1].master && !ipc.owned[2]); + assert_sibling_locks_released(); + puts("PASS: IRQ allocation failure releases the sibling device lock"); + return 0; +} +""" + + +def main(): + root = Path(__file__).resolve().parents[5] + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, default=root) + parser.add_argument("--cc", default="cc") + args = parser.parse_args() + path = args.source / "drivers/net/wireless/apple/centauri/t6050/centauri_ipc.c" + source = path.read_text() + start = source.index("static int enumerate_functions(") + end = source.index("\nstatic int centauri_t6050_start_ipc_locked(", start) + with tempfile.TemporaryDirectory(prefix="centauri-bootstrap-smoke-") as directory: + directory = Path(directory) + unit = directory / "bootstrap.c" + unit.write_text(PRELUDE + source[start:end] + TESTS) + binary = directory / "bootstrap" + subprocess.run([args.cc, "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror", + "-Wno-unused-function", "-pthread", str(unit), "-o", str(binary)], + check=True) + subprocess.run([str(binary)], check=True, timeout=10) + + +if __name__ == "__main__": + main()