diff --git a/Documentation/devicetree/bindings/net/wireless/apple,centauri.yaml b/Documentation/devicetree/bindings/net/wireless/apple,centauri.yaml new file mode 100644 index 0000000000000..4e46c3573edbf --- /dev/null +++ b/Documentation/devicetree/bindings/net/wireless/apple,centauri.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0-only +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/wireless/apple,centauri.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple N1 Centauri Control PCI function + +maintainers: + - Dj + +description: | + PCI function zero owns the shared N1 firmware bootstrap. The boot producer + supplies the personalized FTAB filename and packed firmware platform ID. + The endpoint must be left in its stopped ROM personality with DMA and MSI + quiesced. A supported APIF PCI host performs the isolated port reset after + ROM bootstrap. This binding does not authorize adopting an existing live + firmware session or releasing another owner's retained DART streams. + +properties: + compatible: + enum: + - pci106b,1900 + - pci106b,1901 + + reg: + maxItems: 1 + description: Standard five-cell PCI function address + + firmware-name: + $ref: /schemas/types.yaml#/definitions/string + description: Personalized FTAB including the required per-device calibration + + apple,centauri-platform-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Firmware chip ID in bits 15..0 and board ID in bits 30..16 + + apple,centauri-protocol: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 27 + description: Shared firmware wire protocol profile + +required: + - compatible + - reg + - firmware-name + - apple,centauri-platform-id + - apple,centauri-protocol + +additionalProperties: false diff --git a/drivers/net/wireless/apple/centauri/centauri_ipc.c b/drivers/net/wireless/apple/centauri/centauri_ipc.c new file mode 100644 index 0000000000000..1b8b8abb00be2 --- /dev/null +++ b/drivers/net/wireless/apple/centauri/centauri_ipc.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "centauri.h" + +struct cen_buffer { + struct list_head list; + struct device *owner; + void *cpu; + dma_addr_t dma; + size_t size; +}; + +struct cen_context { + struct cen_buffer *buffer, *messages; + u32 index[4], mtr, mcr; + u16 tr_count, cr_count, message_head; +}; + +struct centauri_ipc { + struct list_head buffers; + struct pci_dev *function[3]; + bool enabled[3], owned[3]; + bool alpha_pm_domain; + void __iomem *bar[3]; + struct cen_context control, alpha; + struct cen_buffer *secondary, *window, *cchi_rx, *cchi_tx, *debug_rx; + u16 cchi_head, cchi_tail; +}; + +static struct cen_buffer *buffer_alloc(struct centauri *c, unsigned int function, + size_t size) +{ + struct cen_buffer *buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + + if (!buffer) + return NULL; + buffer->owner = get_device(&c->ipc->function[function]->dev); + buffer->size = PAGE_ALIGN(size); + buffer->cpu = dma_alloc_coherent(buffer->owner, buffer->size, &buffer->dma, GFP_KERNEL); + if (!buffer->cpu) { + put_device(buffer->owner); + kfree(buffer); + return NULL; + } + memset(buffer->cpu, 0, buffer->size); + list_add_tail(&buffer->list, &c->ipc->buffers); + return buffer; +} + +static int ftab_entry(const void *image, size_t size, const char tag[4], + u32 *offset, u32 *length, bool sparse) +{ + u32 count, i, off, len; + const u8 *table = image; + + if (size < 0x30 || memcmp(table + 0x24, "ftab", 4)) + return -EINVAL; + count = get_unaligned_le32(table + 0x28); + if (!count || count > 256 || 0x30 + count * 16 > size) + return -EINVAL; + for (i = 0; i < count; i++) { + const u8 *entry = table + 0x30 + 16 * i; + + if (memcmp(entry, tag, 4)) + continue; + off = get_unaligned_le32(entry + 4); + len = get_unaligned_le32(entry + 8); + if (off < 0x30 + count * 16 || !len || (u64)off + len > (sparse ? SZ_64M : size)) + return -EINVAL; + *offset = off; + *length = len; + return 0; + } + return -ENOENT; +} + +static int secondary_init(struct centauri *c) +{ + u32 offset, length, count, i, required; + const u8 *seed; + int ret; + + ret = ftab_entry(c->firmware, c->firmware_size, "2ftb", &offset, &length, false); + if (ret) + return ret; + seed = c->firmware + offset; + if (length < 0x30 || memcmp(seed + 0x24, "ftab", 4)) + return -EINVAL; + count = get_unaligned_le32(seed + 0x28); + if (!count || count > 256 || 0x30 + 16 * count != length) + return -EINVAL; + required = length; + for (i = 0; i < count; i++) { + u64 end = (u64)get_unaligned_le32(seed + 0x34 + i * 16) + + get_unaligned_le32(seed + 0x38 + i * 16); + + if (end > SZ_64M) + return -EINVAL; + required = max_t(u32, required, end); + } + c->ipc->secondary = buffer_alloc(c, 0, required); + if (!c->ipc->secondary) + return -ENOMEM; + memcpy(c->ipc->secondary->cpu, seed, length); + dma_wmb(); + ret = pci_write_config_dword(c->pdev, 0xf88, lower_32_bits(c->ipc->secondary->dma)); + if (!ret) + ret = pci_write_config_dword(c->pdev, 0xf8c, upper_32_bits(c->ipc->secondary->dma)); + if (!ret) + ret = pci_write_config_dword(c->pdev, 0xf90, required); + if (ret) + return pcibios_err_to_errno(ret); + + return 0; +} + +static int context_init(struct centauri *c, struct cen_context *ctx, + unsigned int function, u16 tr, u16 cr, dma_addr_t scratch, size_t scratch_size) +{ + u8 *header; + dma_addr_t dma; + int i; + + ctx->buffer = buffer_alloc(c, function, SZ_32K); + ctx->messages = buffer_alloc(c, function, SZ_16K); + if (!ctx->buffer || !ctx->messages) + return -ENOMEM; + ctx->tr_count = tr; + ctx->cr_count = cr; + ctx->index[0] = 0x78; + ctx->index[1] = ctx->index[0] + cr * 2; + ctx->index[2] = ctx->index[1] + tr * 2; + ctx->index[3] = ctx->index[2] + cr * 2; + ctx->mtr = ALIGN(ctx->index[3] + tr * 2, tr == 192 ? 16 : 2); + ctx->mcr = ALIGN(ctx->mtr + 256, 4); + header = ctx->buffer->cpu; + dma = ctx->buffer->dma; + put_unaligned_le16(768, header); + put_unaligned_le16(0x68, header + 2); + put_unaligned_le64(dma + 0x68, header + 8); + for (i = 0; i < 4; i++) + put_unaligned_le64(dma + ctx->index[i], header + 0x10 + 8 * i); + put_unaligned_le16(cr, header + 0x30); + put_unaligned_le16(tr, header + 0x32); + put_unaligned_le64(dma + ctx->mcr, header + 0x34); + put_unaligned_le64(dma + ctx->mtr, header + 0x3c); + put_unaligned_le16(16, header + 0x44); + put_unaligned_le16(16, header + 0x46); + put_unaligned_le64(scratch ?: dma + ALIGN(ctx->mcr + 256, 8), header + 0x58); + put_unaligned_le64(scratch_size, header + 0x60); + dma_wmb(); + return 0; +} + +static int handshake(void __iomem *bar, struct cen_context *ctx, + u32 status, u32 address, u32 control) +{ + u32 value; + int ret; + + writel(1, bar + control); + ret = readl_poll_timeout(bar + status, value, value == 1, 100, 2000000); + if (ret) { + pr_err("centauri: IPC init timeout status@%#x=%#x control@%#x=%#x context=%pad\n", + status, value, control, readl(bar + control), &ctx->buffer->dma); + return ret; + } + writel(lower_32_bits(ctx->buffer->dma), bar + address); + writel(upper_32_bits(ctx->buffer->dma), bar + address + 4); + dma_wmb(); + writel(2, bar + control); + ret = readl_poll_timeout(bar + status, value, value == 2, 100, 2000000); + if (ret) + pr_err("centauri: IPC run timeout status@%#x=%#x control@%#x=%#x context=%pad\n", + status, value, control, readl(bar + control), &ctx->buffer->dma); + return ret; +} + +static int message(struct cen_context *ctx, void __iomem *doorbell, + const void *payload, size_t length) +{ + u16 head = ctx->message_head; + u8 *descriptor; + u16 tail; + int ret; + + if (head >= 15 || !length || length > 512) + return -ENOSPC; + memcpy(ctx->messages->cpu + 512 * (head + 1), payload, length); + descriptor = ctx->buffer->cpu + ctx->mtr + head * 16; + put_unaligned_le32(1 | (length << 8), descriptor); + put_unaligned_le64(ctx->messages->dma + 512 * (head + 1), descriptor + 4); + put_unaligned_le32(head + 1, descriptor + 12); + dma_wmb(); + writel(head + 1, doorbell); + ret = read_poll_timeout(READ_ONCE, tail, le16_to_cpu(tail) == head + 1, + 100, 1200000, false, *(__le16 *)(ctx->buffer->cpu + ctx->index[1])); + dma_rmb(); + ctx->message_head++; + return ret; +} + +static int open_transfer(struct centauri *c, u16 id, struct cen_buffer *buffer, + u16 footer) +{ + u8 msg[0x34] = { 1 }; + u32 words = footer >> 2, shift = min_t(u32, __ffs(words), 4); + + msg[2] = words >> shift; + msg[3] = shift << 4; + put_unaligned_le16(id, msg + 4); + put_unaligned_le64(buffer->dma, msg + 8); + put_unaligned_le64(~0ULL, msg + 0x10); + put_unaligned_le16(16, msg + 0x18); + put_unaligned_le16(0xffff, msg + 0x1a); + put_unaligned_le16(id, msg + 0x1c); + put_unaligned_le16(0x40, msg + 0x1e); + return message(&c->ipc->control, c->bar + 0x9000, msg, sizeof(msg)); +} + +static int cchi_init(struct centauri *c) +{ + struct centauri_ipc *ipc = c->ipc; + u8 config[4] = {9, 0, 0xff, 0xff}, *entry; + u16 tail; + int i, ret; + + ipc->cchi_rx = buffer_alloc(c, 0, SZ_32K); + ipc->cchi_tx = buffer_alloc(c, 0, SZ_32K); + ipc->debug_rx = buffer_alloc(c, 0, SZ_16K); + if (!ipc->cchi_rx || !ipc->cchi_tx || !ipc->debug_rx) + return -ENOMEM; + ret = message(&ipc->control, c->bar + 0x9000, config, sizeof(config)); + if (ret) + return ret; + ret = open_transfer(c, 4, ipc->cchi_rx, 1024); + if (!ret) + ret = open_transfer(c, 3, ipc->cchi_tx, 1024); + if (!ret) + ret = open_transfer(c, 5, ipc->debug_rx, 256); + if (ret) + return ret; + for (i = 0; i < 15; i++) { + entry = ipc->cchi_rx->cpu + i * 1040; + put_unaligned_le32(1 | (1024 << 8), entry); + put_unaligned_le32(i + 1, entry + 12); + entry = ipc->debug_rx->cpu + i * 272; + put_unaligned_le32(1 | (256 << 8), entry); + put_unaligned_le32(i + 1, entry + 12); + } + dma_wmb(); + writel(15, c->bar + 0x9010); + writel(15, c->bar + 0x9014); + entry = ipc->cchi_tx->cpu; + put_unaligned_le32(1 | (64 << 8), entry); + put_unaligned_le32(1, entry + 12); + put_unaligned_le16(64, entry + 16); + dma_wmb(); + writel(1, c->bar + 0x900c); + ret = read_poll_timeout(READ_ONCE, tail, le16_to_cpu(tail) != 0, + 100, 2000000, false, + *(__le16 *)(ipc->control.buffer->cpu + ipc->control.index[1] + 4 * 2)); + dma_rmb(); + if (ret) + return ret; + entry = ipc->cchi_rx->cpu; + if ((get_unaligned_le32(entry) >> 8) < 8 || entry[18] || entry[19]) + return -EPROTO; + ipc->cchi_head = ipc->cchi_tail = 1; + return 0; +} + +static int memswap_handoff(struct centauri *c, unsigned int function, const char tag[4]) +{ + struct cen_buffer *secondary = c->ipc->secondary, *working; + u32 offset, size; + int ret; + + ret = ftab_entry(secondary->cpu, secondary->size, tag, &offset, &size, false); + if (ret) + return ret; + working = buffer_alloc(c, function, size); + if (!working) + return -ENOMEM; + dma_rmb(); + memcpy(working->cpu, secondary->cpu + offset, size); + dma_wmb(); + ret = pci_write_config_dword(c->ipc->function[function], 0xf88, lower_32_bits(working->dma)); + if (!ret) + ret = pci_write_config_dword(c->ipc->function[function], 0xf8c, upper_32_bits(working->dma)); + if (!ret) + ret = pci_write_config_dword(c->ipc->function[function], 0xf90, size); + if (ret) + return pcibios_err_to_errno(ret); + return 0; +} + +static struct dev_pm_domain centauri_alpha_pm_domain; + +static int enumerate_functions(struct centauri *c) +{ + struct centauri_ipc *ipc = c->ipc; + int i, ret; + + c->pdev->multifunction = 1; + pci_rescan_bus(c->pdev->bus); + for (i = 0; i < 3; i++) { + ipc->function[i] = pci_get_domain_bus_and_slot(pci_domain_nr(c->pdev->bus), + c->pdev->bus->number, PCI_DEVFN(PCI_SLOT(c->pdev->devfn), i)); + if (!ipc->function[i] || ipc->function[i]->vendor != PCI_VENDOR_ID_APPLE || + (i && ipc->function[i]->device != 0x1901 + i)) + return -ENODEV; + if (ipc->function[i]->cfg_size < 0xf94 || + !(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; + ipc->owned[i] = true; + ipc->bar[i] = pci_iomap(ipc->function[i], 0, 0); + if (!ipc->bar[i]) + return -ENOMEM; + if (i) { + ret = pci_enable_device_mem(ipc->function[i]); + if (ret) + return ret; + ipc->enabled[i] = true; + } + if (i == 1 && !ipc->function[i]->dev.driver) { + dev_pm_domain_set(&ipc->function[i]->dev, ¢auri_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; + if (i == 1) { + ret = centauri_request_irqs(&c->alpha_irqs, ipc->function[i], + 16, centauri_alpha_irq, c); + if (ret) + return ret; + } + pci_set_master(ipc->function[i]); + } + return 0; +} + +static int centauri_start_ipc_locked(struct centauri *c) +{ + struct centauri_ipc *ipc; + u32 stage; + int ret; + + if (!c->firmware || !c->link_ready || c->ipc || readl(c->bar + 0x8000) != 1) + return -EBUSY; + ipc = kzalloc(sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + INIT_LIST_HEAD(&ipc->buffers); + c->ipc = ipc; + ret = enumerate_functions(c); + if (ret) + goto failed; + ret = secondary_init(c); + if (ret) + goto failed; + ret = context_init(c, &ipc->control, 0, 13, 1, 0, 0); + if (ret) + goto failed; + writel(lower_32_bits(c->descriptor_dma), c->bar + 0x8044); + writel(upper_32_bits(c->descriptor_dma), c->bar + 0x8048); + writel(0x20, c->bar + 0x804c); + writel(0x62746632, c->bar + 0x8074); + dma_wmb(); + writel(1, c->bar + 0x9070); + ret = readl_poll_timeout(c->bar + 0x8000, stage, stage == 2 || stage == 3, 100, 3000000); + if (ret || stage != 2) { + ret = ret ?: -EIO; + goto failed; + } + ret = handshake(c->bar, &ipc->control, 0x8050, 0x8054, 0x907c); + if (ret) + goto failed; + ret = memswap_handoff(c, 0, "mswc"); + if (!ret) + ret = memswap_handoff(c, 2, "mswb"); + if (!ret) + ret = memswap_handoff(c, 1, "msww"); + if (ret) + goto failed; + ret = cchi_init(c); + if (ret) + goto failed; + ret = readl_poll_timeout(ipc->bar[1] + 8, stage, stage == 2 || stage == 3, 100, 2000000); + if (ret || stage != 2) { + ret = ret ?: -EIO; + goto failed; + } + ipc->window = buffer_alloc(c, 1, CEN_ALPHA_WINDOW_SIZE); + if (!ipc->window) { + ret = -ENOMEM; + goto failed; + } + ret = context_init(c, &ipc->alpha, 1, 192, 234, + ipc->window->dma + CEN_ALPHA_SCRATCH_OFFSET, CEN_ALPHA_SCRATCH_SIZE); + if (ret) + goto failed; + writel(lower_32_bits(ipc->window->dma), ipc->bar[1] + 0x18); + writel(upper_32_bits(ipc->window->dma), ipc->bar[1] + 0x1c); + writel(CEN_ALPHA_WINDOW_SIZE, ipc->bar[1] + 0x20); + ret = handshake(ipc->bar[1], &ipc->alpha, 0xc, 0x10, 0x1028); + if (ret) + goto failed; + ret = centauri_wifi_register(c); + if (ret) + return ret; + + /* Publish usable shared firmware before the Beta consumer probes. */ + smp_store_release(&c->ready, true); + ret = device_attach(&ipc->function[2]->dev); + if (ret < 0) + dev_warn(&c->pdev->dev, "Beta driver attachment failed: %d\n", ret); + return 0; +failed: + dev_err(&c->pdev->dev, "IPC bootstrap failed: %d (Control stage=%#x)\n", + ret, readl(c->bar + 0x8000)); + if (ipc->bar[1]) + dev_err(&c->pdev->dev, "Alpha stage=%#x IPC=%#x window=%#x/%#x/%#x\n", + readl(ipc->bar[1] + 8), readl(ipc->bar[1] + 0xc), + readl(ipc->bar[1] + 0x18), readl(ipc->bar[1] + 0x1c), readl(ipc->bar[1] + 0x20)); + if (ipc->bar[2]) + dev_err(&c->pdev->dev, "Beta stage=%#x IPC=%#x\n", + readl(ipc->bar[2] + 0x18), readl(ipc->bar[2] + 0x1c)); + + return ret; +} + +int centauri_start_ipc(struct centauri *c) +{ + int ret; + + if (!pci_trylock_rescan_remove()) + return -EAGAIN; + ret = READ_ONCE(c->removing) ? -ECANCELED : centauri_start_ipc_locked(c); + pci_unlock_rescan_remove(); + return ret; +} + +void centauri_free_ipc(struct centauri *c) +{ + struct centauri_ipc *ipc = c->ipc; + struct cen_buffer *buffer, *next; + int i; + + if (!ipc) + return; + if (ipc->owned[2]) + device_release_driver(&ipc->function[2]->dev); + centauri_wifi_unregister(c); + centauri_free_irqs(&c->alpha_irqs); + for (i = 0; i < 3; i++) { + if (!ipc->owned[i]) + continue; + pci_clear_master(ipc->function[i]); + } + for (i = 0; i < 3; i++) { + if (ipc->owned[i] && !centauri_stop_dma(ipc->function[i])) { + dev_warn(&c->pdev->dev, + "retaining IPC DMA after pending transaction timeout\n"); + return; + } + } + list_for_each_entry_safe(buffer, next, &ipc->buffers, list) { + memzero_explicit(buffer->cpu, buffer->size); + dma_free_coherent(buffer->owner, buffer->size, buffer->cpu, buffer->dma); + put_device(buffer->owner); + list_del(&buffer->list); + kfree(buffer); + } + if (ipc->alpha_pm_domain) + dev_pm_domain_set(&ipc->function[1]->dev, NULL); + for (i = 0; i < 3; i++) { + if (ipc->bar[i]) + pci_iounmap(ipc->function[i], ipc->bar[i]); + if (ipc->enabled[i]) + pci_disable_device(ipc->function[i]); + pci_dev_put(ipc->function[i]); + } + kfree(ipc); + c->ipc = NULL; +} + +int centauri_alpha_resources(struct centauri *c, struct cen_alpha_resources *resources) +{ + struct centauri_ipc *ipc = c->ipc; + + if (!ipc || !ipc->window || !ipc->alpha.buffer || !ipc->alpha.messages) + return -ENODEV; + resources->pdev = ipc->function[1]; + resources->context = ipc->alpha.buffer->cpu; + resources->context_dma = ipc->alpha.buffer->dma; + resources->messages = ipc->alpha.messages->cpu; + resources->messages_dma = ipc->alpha.messages->dma; + resources->window = ipc->window->cpu; + resources->window_dma = ipc->window->dma; + resources->bar = ipc->bar[1]; + memcpy(resources->index, ipc->alpha.index, sizeof(resources->index)); + resources->mtr = ipc->alpha.mtr; + resources->mcr = ipc->alpha.mcr; + return 0; +} diff --git a/drivers/net/wireless/apple/centauri/centauri_pci.c b/drivers/net/wireless/apple/centauri/centauri_pci.c new file mode 100644 index 0000000000000..3db1a4a77f11d --- /dev/null +++ b/drivers/net/wireless/apple/centauri/centauri_pci.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "centauri.h" + +static struct pci_driver centauri_driver; + +bool apple_centauri_control_ready(struct pci_dev *pdev) +{ + struct centauri *c; + + if (pdev->driver != ¢auri_driver) + return false; + c = pci_get_drvdata(pdev); + return c && !READ_ONCE(c->removing) && smp_load_acquire(&c->ready); +} +EXPORT_SYMBOL_GPL(apple_centauri_control_ready); + +/* No DMA backing is released without verified bus-master shutdown. */ +bool centauri_stop_dma(struct pci_dev *pdev) +{ + u16 command; + int ret; + + pci_clear_master(pdev); + ret = pci_read_config_word(pdev, PCI_COMMAND, &command); + if (ret || (command & PCI_COMMAND_MASTER)) + return false; + return pci_wait_for_pending_transaction(pdev); +} + +void centauri_free_irqs(struct centauri_irqs *irqs) +{ + if (!irqs->pdev) + return; + while (irqs->count) + free_irq(pci_irq_vector(irqs->pdev, --irqs->count), irqs->data); + pci_free_irq_vectors(irqs->pdev); + irqs->pdev = NULL; +} + +int centauri_request_irqs(struct centauri_irqs *irqs, struct pci_dev *pdev, + unsigned int count, irq_handler_t handler, void *data) +{ + int ret; + + ret = pci_alloc_irq_vectors(pdev, count, count, PCI_IRQ_MSI); + if (ret < 0) + return ret; + irqs->pdev = pdev; + irqs->data = data; + for (irqs->count = 0; irqs->count < count; irqs->count++) { + ret = request_irq(pci_irq_vector(pdev, irqs->count), handler, 0, + "centauri", data); + if (ret) { + centauri_free_irqs(irqs); + return ret; + } + } + return 0; +} + +static irqreturn_t centauri_control_irq(int irq, void *data) +{ + return IRQ_HANDLED; +} + +static void centauri_free_boot(struct centauri *c) +{ + struct device *dev = &c->pdev->dev; + + if (!c->link_ready && c->boot_attempted) { + while (c->alpha_irqs.count) + free_irq(pci_irq_vector(c->alpha_irqs.pdev, --c->alpha_irqs.count), c); + while (c->irqs.count) + free_irq(pci_irq_vector(c->pdev, --c->irqs.count), c); + dev_warn(dev, "retaining DMA buffers after failed link transition; reboot required\n"); + return; + } + + centauri_free_ipc(c); + centauri_free_irqs(&c->irqs); + if (c->ipc || !centauri_stop_dma(c->pdev)) { + dev_warn(dev, "retaining boot DMA until outstanding transactions stop\n"); + return; + } + if (c->firmware) + dma_free_coherent(dev, c->firmware_size, c->firmware, c->firmware_dma); + if (c->descriptor) + dma_free_coherent(dev, SZ_16K, c->descriptor, c->descriptor_dma); + c->firmware = NULL; + c->descriptor = NULL; +} + +static int centauri_rom_boot(struct centauri *c) +{ + const struct firmware *fw; + struct device *dev = &c->pdev->dev; + u32 stage; + int ret; + + if (c->pdev->device != 0x1900 && c->pdev->device != 0x1901) + return -ENODEV; + if (!c->link_ready || c->boot_attempted) + return -EBUSY; + stage = readl(c->bar + 0x8000); + if (stage != 0) + return -EBUSY; + + ret = request_firmware_direct(&fw, c->firmware_name, dev); + if (ret) { + dev_err(dev, "required firmware %s unavailable: %d\n", c->firmware_name, ret); + return ret; + } + if (READ_ONCE(c->removing)) { + ret = -ECANCELED; + goto release; + } + if (fw->size < 0x30 || fw->size > SZ_64M || + memcmp(fw->data + 0x20, "rkosftab", 8)) { + ret = -EINVAL; + goto release; + } + c->firmware_size = PAGE_ALIGN(fw->size); + c->firmware = dma_alloc_coherent(dev, c->firmware_size, &c->firmware_dma, GFP_KERNEL); + c->descriptor = dma_alloc_coherent(dev, SZ_16K, &c->descriptor_dma, GFP_KERNEL); + if (!c->firmware || !c->descriptor) { + ret = -ENOMEM; + goto free_buffers; + } + memset(c->firmware, 0, c->firmware_size); + memcpy(c->firmware, fw->data, fw->size); + memset(c->descriptor, 0, SZ_16K); + + put_unaligned_le64(c->firmware_dma, c->descriptor); + put_unaligned_le32(ALIGN(fw->size, SZ_4K), c->descriptor + 8); + ret = centauri_request_irqs(&c->irqs, c->pdev, 4, + centauri_control_irq, c); + if (ret) + goto free_buffers; + pci_set_master(c->pdev); + writel(c->platform_id, c->bar + 0x807c); + writel(0, c->bar + 0x803c); + writel(lower_32_bits(c->descriptor_dma), c->bar + 0x8044); + writel(upper_32_bits(c->descriptor_dma), c->bar + 0x8048); + writel(0x20, c->bar + 0x804c); + dma_wmb(); + c->boot_attempted = true; + writel(1, c->bar + 0x9000); + ret = readl_poll_timeout(c->bar + 0x8040, c->image_response, + READ_ONCE(c->removing) || c->image_response != 0, 20000, 20000000); + if (READ_ONCE(c->removing)) { + ret = -ECANCELED; + goto release; + } + if (!ret && c->image_response != 1) + ret = -EIO; + if (!ret) + ret = readl_poll_timeout(c->bar + 0x8000, stage, + READ_ONCE(c->removing) || stage != 0, 20000, 20000000); + else + stage = readl(c->bar + 0x8000); + if (READ_ONCE(c->removing)) + ret = -ECANCELED; + dma_rmb(); + + if (!ret && stage != 4) + ret = -EIO; + goto release; +free_buffers: + centauri_free_boot(c); +release: + release_firmware(fw); + return ret; +} + +static int centauri_cycle(struct centauri *c) +{ + int ret; + + if (!c->firmware || !c->link_ready || readl(c->bar + 0x8000) != 4) + return -EBUSY; + c->link_ready = false; + ret = apple_apif_pcie_cycle(c->pdev); + if (ret == -EAGAIN) { + c->link_ready = true; + } else if (!ret) { + pci_restore_msi_state(c->pdev); + c->link_ready = true; + } + return ret; +} + +static void centauri_boot_worker(struct work_struct *work) +{ + struct centauri *c = container_of(to_delayed_work(work), struct centauri, boot_work); + bool retry = false; + int ret; + + mutex_lock(&c->boot_lock); + while (!READ_ONCE(c->removing)) { + switch (c->boot_phase) { + case CEN_BOOT_ROM: + ret = centauri_rom_boot(c); + if (!ret) + c->boot_phase = readl(c->bar + 0x8000) == 1 ? + CEN_BOOT_IPC : CEN_BOOT_CYCLE; + break; + case CEN_BOOT_CYCLE: + ret = centauri_cycle(c); + if (!ret) + c->boot_phase = CEN_BOOT_IPC; + break; + case CEN_BOOT_IPC: + ret = centauri_start_ipc(c); + if (!ret) + c->boot_phase = CEN_BOOT_READY; + break; + default: + goto out; + } + if (ret == -EAGAIN && (c->boot_phase == CEN_BOOT_CYCLE || + (c->boot_phase == CEN_BOOT_IPC && !c->ipc))) { + retry = true; + break; + } + if (ret) { + dev_err(&c->pdev->dev, "N1 automatic startup failed in phase%u: %d\n", + c->boot_phase, ret); + c->boot_phase = CEN_BOOT_FAILED; + break; + } + } +out: + if (READ_ONCE(c->removing)) + c->boot_phase = CEN_BOOT_STOPPED; + mutex_unlock(&c->boot_lock); + if (retry && !READ_ONCE(c->removing)) + queue_delayed_work(system_long_wq, &c->boot_work, msecs_to_jiffies(20)); +} + +static int centauri_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct centauri *c; + int ret; + struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus); + + if (!bridge || !bridge->dev.parent || + !of_device_is_compatible(bridge->dev.parent->of_node, "apple,t6050-pcie-apif")) + return -ENODEV; + if (PCI_FUNC(pdev->devfn) != 0) + return -ENODEV; + c = devm_kzalloc(&pdev->dev, sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + c->pdev = pdev; + c->link_ready = true; + mutex_init(&c->boot_lock); + INIT_DELAYED_WORK(&c->boot_work, centauri_boot_worker); + c->boot_phase = CEN_BOOT_ROM; + ret = device_property_read_string(&pdev->dev, "firmware-name", &c->firmware_name); + if (ret) + return dev_err_probe(&pdev->dev, ret, "missing personalized firmware name\n"); + ret = device_property_read_u32(&pdev->dev, "apple,centauri-platform-id", &c->platform_id); + if (ret) + return ret; + ret = device_property_read_u32(&pdev->dev, "apple,centauri-protocol", &c->protocol); + if (ret || c->protocol != 27) + return -EINVAL; + ret = pcim_enable_device(pdev); + if (ret) + return ret; + c->size = pci_resource_len(pdev, 0); + if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) || + c->size < 0x9080) + return dev_err_probe(&pdev->dev, -ENODEV, "invalid ACIPC BAR0\n"); + ret = pcim_iomap_regions(pdev, BIT(0), "apple_centauri"); + if (ret) + return ret; + c->bar = pcim_iomap_table(pdev)[0]; + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); + if (ret) + return dev_err_probe(&pdev->dev, ret, "40-bit DMA unavailable\n"); + + pci_clear_master(pdev); + pci_set_drvdata(pdev, c); + queue_delayed_work(system_long_wq, &c->boot_work, 0); + return 0; +} + +static void centauri_remove(struct pci_dev *pdev) +{ + struct centauri *c = pci_get_drvdata(pdev); + + WRITE_ONCE(c->removing, true); + cancel_delayed_work_sync(&c->boot_work); + mutex_lock(&c->boot_lock); + centauri_wifi_unregister(c); + centauri_free_boot(c); + c->boot_phase = CEN_BOOT_STOPPED; + mutex_unlock(&c->boot_lock); +} + +static int centauri_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct centauri *c = pci_get_drvdata(pdev); + + guard(mutex)(&c->boot_lock); + if (c->boot_phase != CEN_BOOT_READY) + return -EBUSY; + return pci_save_state(pdev); +} + +static int centauri_resume(struct device *dev) +{ + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(centauri_pm_ops, centauri_suspend, centauri_resume); + +static const struct pci_device_id centauri_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x1900) }, + { PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x1901) }, + { } +}; +MODULE_DEVICE_TABLE(pci, centauri_ids); + +static struct pci_driver centauri_driver = { + .name = "apple_centauri", + .id_table = centauri_ids, + .probe = centauri_probe, + .remove = centauri_remove, + .shutdown = centauri_remove, + .driver.pm = pm_sleep_ptr(¢auri_pm_ops), +}; + +module_pci_driver(centauri_driver); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Apple N1 Centauri PCI firmware and ACIPC transport"); diff --git a/tools/testing/selftests/aurora-apif/centauri.py b/tools/testing/selftests/aurora-apif/centauri.py new file mode 100755 index 0000000000000..fd2a6580e3ecc --- /dev/null +++ b/tools/testing/selftests/aurora-apif/centauri.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Check the production FTAB parser and DMA shutdown predicate.""" +from pathlib import Path +import shutil +import subprocess +import tempfile +from common import block + +ROOT = Path(__file__).resolve().parents[4] + + +def main(): + print("TAP version 13\n1..1", flush=True) + if not shutil.which("cc"): + print("ok 1 - N1 firmware and DMA ownership # SKIP cc unavailable") + return 4 + ipc = (ROOT / "drivers/net/wireless/apple/centauri/centauri_ipc.c").read_text() + pci = (ROOT / "drivers/net/wireless/apple/centauri/centauri_pci.c").read_text() + program = r''' +#include +#include +#include +#include +#include +#include +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +#define SZ_64M (64 * 1024 * 1024) +#define PCI_COMMAND 4 +#define PCI_COMMAND_MASTER 4 +struct pci_dev { int dummy; }; +static int config_error, pending_stopped = 1; +static u16 command; +static void pci_clear_master(struct pci_dev *p) {} +static int pci_read_config_word(struct pci_dev *p, int reg, u16 *v) { + *v = command; return config_error; +} +static int pci_wait_for_pending_transaction(struct pci_dev *p) { return pending_stopped; } +static u32 get_unaligned_le32(const void *p) { + const u8 *b = p; + return b[0] | b[1] << 8 | b[2] << 16 | (u32)b[3] << 24; +} +static void put32(u32 v, void *p) { + u8 *b = p; + for (int i = 0; i < 4; i++) b[i] = v >> (8 * i); +} +''' + program += block(ipc, r"^static int ftab_entry\(") + program += block(pci, r"^bool centauri_stop_dma\(") + program += r''' +int main(void) { + u8 image[256] = {0}; u32 off, len; + memcpy(image + 0x24, "ftab", 4); put32(1, image + 0x28); + memcpy(image + 0x30, "mswc", 4); put32(0x40, image + 0x34); put32(16, image + 0x38); + assert(ftab_entry(image, sizeof(image), "mswc", &off, &len, false) == 0); + assert(off == 0x40 && len == 16); + assert(ftab_entry(image, 47, "mswc", &off, &len, false) == -EINVAL); + assert(ftab_entry(image, sizeof(image), "none", &off, &len, false) == -ENOENT); + put32(257, image + 0x28); + assert(ftab_entry(image, sizeof(image), "mswc", &off, &len, false) == -EINVAL); + put32(1, image + 0x28); put32(0x30, image + 0x34); + assert(ftab_entry(image, sizeof(image), "mswc", &off, &len, false) == -EINVAL); + put32(0x1000, image + 0x34); + assert(ftab_entry(image, sizeof(image), "mswc", &off, &len, false) == -EINVAL); + assert(ftab_entry(image, sizeof(image), "mswc", &off, &len, true) == 0); + put32(SZ_64M, image + 0x38); + assert(ftab_entry(image, sizeof(image), "mswc", &off, &len, true) == -EINVAL); + struct pci_dev p; + assert(centauri_stop_dma(&p)); + config_error = 1; assert(!centauri_stop_dma(&p)); + config_error = 0; command = PCI_COMMAND_MASTER; assert(!centauri_stop_dma(&p)); + command = 0; pending_stopped = 0; assert(!centauri_stop_dma(&p)); +} +''' + try: + with tempfile.TemporaryDirectory() as tmp: + src, binary = Path(tmp) / "test.c", Path(tmp) / "test" + src.write_text(program) + subprocess.run(["cc", "-std=gnu11", str(src), "-o", str(binary)], check=True) + subprocess.run([str(binary)], check=True) + except subprocess.CalledProcessError: + print("not ok 1 - N1 firmware and DMA ownership") + return 1 + print("ok 1 - N1 firmware and DMA ownership") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())