summaryrefslogtreecommitdiffstats
path: root/drivers/misc/rp1
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
commit3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch)
treeb767d7f6e26bc64334d3f14f8422d187239dc45d /drivers/misc/rp1
downloadlinux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz
linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe: - Fix a task_work add use-after-free with SQPOLL. The sqpoll thread could pop and complete the last request while io_req_normal_work_add() was still looking at them after the mpscq push. Use the same approach as DEFER_TASKRUN to protect from that, holding an RCU read lock across the add, and have exit wait for an RCU grace period for SQPOLL rings as well. - CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED rings - Mark the source filter table as COW when cloning bpf filters, so registering another filter on the source doesn't modify the shared table in place - Initialize the task context before running the BPF loop - Requeue zcrx multishot receives stopped by a local resource - End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit) * tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: io_uring: fix task_work add use-after-free with SQPOLL io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full io_uring/zcrx: requeue multishot receives stopped by a local resource io_uring: initialize task context before running the BPF loop io_uring: zero big_cqe for aux CQEs on CQE32 rings io_uring: fix free entry check for 32b CQEs on CQE32 rings io_uring: only post the dummy skip CQE on CQE_MIXED rings io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'drivers/misc/rp1')
-rw-r--r--drivers/misc/rp1/Kconfig16
-rw-r--r--drivers/misc/rp1/Makefile2
-rw-r--r--drivers/misc/rp1/rp1_pci.c307
3 files changed, 325 insertions, 0 deletions
diff --git a/drivers/misc/rp1/Kconfig b/drivers/misc/rp1/Kconfig
new file mode 100644
index 000000000..2c13b3968
--- /dev/null
+++ b/drivers/misc/rp1/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# RaspberryPi RP1 misc device
+#
+
+config MISC_RP1
+ tristate "RaspberryPi RP1 misc device"
+ depends on OF_IRQ && PCI_MSI
+ help
+ Support the RP1 peripheral chip found on Raspberry Pi 5 board.
+
+ This device supports several sub-devices including e.g. Ethernet
+ controller, USB controller, I2C, SPI and UART.
+
+ The driver is responsible for enabling the DT node once the PCIe
+ endpoint has been configured, and handling interrupts.
diff --git a/drivers/misc/rp1/Makefile b/drivers/misc/rp1/Makefile
new file mode 100644
index 000000000..ab32b433d
--- /dev/null
+++ b/drivers/misc/rp1/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_MISC_RP1) += rp1_pci.o
diff --git a/drivers/misc/rp1/rp1_pci.c b/drivers/misc/rp1/rp1_pci.c
new file mode 100644
index 000000000..207a67691
--- /dev/null
+++ b/drivers/misc/rp1/rp1_pci.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2025 Raspberry Pi Ltd.
+ *
+ * All rights reserved.
+ */
+
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+
+#define RP1_HW_IRQ_MASK GENMASK(5, 0)
+
+#define REG_SET 0x800
+#define REG_CLR 0xc00
+
+/* MSI-X CFG registers start at 0x8 */
+#define MSIX_CFG(x) (0x8 + (4 * (x)))
+
+#define MSIX_CFG_IACK_EN BIT(3)
+#define MSIX_CFG_IACK BIT(2)
+#define MSIX_CFG_ENABLE BIT(0)
+
+/* Address map */
+#define RP1_PCIE_APBS_BASE 0x108000
+
+/* Interrupts */
+#define RP1_INT_END 61
+
+struct rp1_dev {
+ struct pci_dev *pdev;
+ struct irq_domain *domain;
+ struct irq_data *pcie_irqds[64];
+ void __iomem *bar1;
+ bool level_triggered_irq[RP1_INT_END];
+};
+
+static void msix_cfg_set(struct rp1_dev *rp1, unsigned int hwirq, u32 value)
+{
+ iowrite32(value, rp1->bar1 + RP1_PCIE_APBS_BASE + REG_SET + MSIX_CFG(hwirq));
+}
+
+static void msix_cfg_clr(struct rp1_dev *rp1, unsigned int hwirq, u32 value)
+{
+ iowrite32(value, rp1->bar1 + RP1_PCIE_APBS_BASE + REG_CLR + MSIX_CFG(hwirq));
+}
+
+static void rp1_mask_irq(struct irq_data *irqd)
+{
+ struct rp1_dev *rp1 = irqd->domain->host_data;
+ struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+ pci_msi_mask_irq(pcie_irqd);
+}
+
+static void rp1_unmask_irq(struct irq_data *irqd)
+{
+ struct rp1_dev *rp1 = irqd->domain->host_data;
+ struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+ pci_msi_unmask_irq(pcie_irqd);
+}
+
+static int rp1_irq_set_type(struct irq_data *irqd, unsigned int type)
+{
+ struct rp1_dev *rp1 = irqd->domain->host_data;
+ unsigned int hwirq = (unsigned int)irqd->hwirq;
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ dev_dbg(&rp1->pdev->dev, "MSIX IACK EN for IRQ %u\n", hwirq);
+ msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK_EN);
+ rp1->level_triggered_irq[hwirq] = true;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ msix_cfg_clr(rp1, hwirq, MSIX_CFG_IACK_EN);
+ rp1->level_triggered_irq[hwirq] = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct irq_chip rp1_irq_chip = {
+ .name = "rp1_irq_chip",
+ .irq_mask = rp1_mask_irq,
+ .irq_unmask = rp1_unmask_irq,
+ .irq_set_type = rp1_irq_set_type,
+};
+
+static void rp1_chained_handle_irq(struct irq_desc *desc)
+{
+ unsigned int hwirq = desc->irq_data.hwirq & RP1_HW_IRQ_MASK;
+ struct rp1_dev *rp1 = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int virq;
+
+ chained_irq_enter(chip, desc);
+
+ virq = irq_find_mapping(rp1->domain, hwirq);
+ generic_handle_irq(virq);
+ if (rp1->level_triggered_irq[hwirq])
+ msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK);
+
+ chained_irq_exit(chip, desc);
+}
+
+static int rp1_irq_xlate(struct irq_domain *d, struct device_node *node,
+ const u32 *intspec, unsigned int intsize,
+ unsigned long *out_hwirq, unsigned int *out_type)
+{
+ struct rp1_dev *rp1 = d->host_data;
+ struct irq_data *pcie_irqd;
+ unsigned long hwirq;
+ int pcie_irq;
+ int ret;
+
+ ret = irq_domain_xlate_twocell(d, node, intspec, intsize,
+ &hwirq, out_type);
+ if (ret)
+ return ret;
+
+ pcie_irq = pci_irq_vector(rp1->pdev, hwirq);
+ pcie_irqd = irq_get_irq_data(pcie_irq);
+ rp1->pcie_irqds[hwirq] = pcie_irqd;
+ *out_hwirq = hwirq;
+
+ return 0;
+}
+
+static int rp1_irq_activate(struct irq_domain *d, struct irq_data *irqd,
+ bool reserve)
+{
+ struct rp1_dev *rp1 = d->host_data;
+
+ msix_cfg_set(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE);
+ msix_cfg_set(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_IACK);
+
+ return 0;
+}
+
+static void rp1_irq_deactivate(struct irq_domain *d, struct irq_data *irqd)
+{
+ struct rp1_dev *rp1 = d->host_data;
+
+ msix_cfg_clr(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE);
+}
+
+static const struct irq_domain_ops rp1_domain_ops = {
+ .xlate = rp1_irq_xlate,
+ .activate = rp1_irq_activate,
+ .deactivate = rp1_irq_deactivate,
+};
+
+static void rp1_unregister_interrupts(struct pci_dev *pdev)
+{
+ struct rp1_dev *rp1 = pci_get_drvdata(pdev);
+ int irq, i;
+
+ for (i = 0; i < RP1_INT_END; i++)
+ irq_set_chained_handler_and_data(pci_irq_vector(pdev, i), NULL, NULL);
+
+ if (rp1->domain) {
+ for (i = 0; i < RP1_INT_END; i++) {
+ irq = irq_find_mapping(rp1->domain, i);
+ irq_dispose_mapping(irq);
+ }
+
+ irq_domain_remove(rp1->domain);
+ }
+
+ pci_free_irq_vectors(pdev);
+}
+
+static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *rp1_node;
+ struct rp1_dev *rp1;
+ int err = 0;
+ int i;
+
+ rp1_node = dev_of_node(dev);
+
+ if (!rp1_node) {
+ dev_err(dev, "Missing of_node for device\n");
+ err = -EINVAL;
+ goto err_out;
+ }
+
+ rp1 = devm_kzalloc(&pdev->dev, sizeof(*rp1), GFP_KERNEL);
+ if (!rp1) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ rp1->pdev = pdev;
+
+ if (pci_resource_len(pdev, 1) <= 0x10000) {
+ dev_err(&pdev->dev,
+ "Not initialized - is the firmware running?\n");
+ err = -EINVAL;
+ goto err_out;
+ }
+
+ err = pcim_enable_device(pdev);
+ if (err < 0) {
+ err = dev_err_probe(&pdev->dev, err,
+ "Enabling PCI device has failed");
+ goto err_out;
+ }
+
+ rp1->bar1 = pcim_iomap(pdev, 1, 0);
+ if (!rp1->bar1) {
+ dev_err(&pdev->dev, "Cannot map PCI BAR\n");
+ err = -EIO;
+ goto err_out;
+ }
+
+ pci_set_master(pdev);
+
+ err = pci_alloc_irq_vectors(pdev, RP1_INT_END, RP1_INT_END,
+ PCI_IRQ_MSIX);
+ if (err < 0) {
+ err = dev_err_probe(&pdev->dev, err,
+ "Failed to allocate MSI-X vectors\n");
+ goto err_out;
+ } else if (err != RP1_INT_END) {
+ dev_err(&pdev->dev, "Cannot allocate enough interrupts\n");
+ err = -EINVAL;
+ goto err_out;
+ }
+
+ pci_set_drvdata(pdev, rp1);
+ rp1->domain = irq_domain_create_linear(of_fwnode_handle(rp1_node), RP1_INT_END,
+ &rp1_domain_ops, rp1);
+ if (!rp1->domain) {
+ dev_err(&pdev->dev, "Error creating IRQ domain\n");
+ err = -ENOMEM;
+ goto err_unregister_interrupts;
+ }
+
+ for (i = 0; i < RP1_INT_END; i++) {
+ unsigned int irq = irq_create_mapping(rp1->domain, i);
+
+ if (!irq) {
+ dev_err(&pdev->dev, "Failed to create IRQ mapping\n");
+ err = -EINVAL;
+ goto err_unregister_interrupts;
+ }
+
+ irq_set_chip_and_handler(irq, &rp1_irq_chip, handle_level_irq);
+ irq_set_probe(irq);
+ irq_set_chained_handler_and_data(pci_irq_vector(pdev, i),
+ rp1_chained_handle_irq, rp1);
+ }
+
+ err = of_platform_default_populate(rp1_node, NULL, dev);
+ if (err) {
+ dev_err_probe(&pdev->dev, err, "Error populating devicetree\n");
+ goto err_unregister_interrupts;
+ }
+
+ return 0;
+
+err_unregister_interrupts:
+ rp1_unregister_interrupts(pdev);
+err_out:
+ return err;
+}
+
+static void rp1_remove(struct pci_dev *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ of_platform_depopulate(dev);
+ rp1_unregister_interrupts(pdev);
+}
+
+static const struct pci_device_id dev_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_RPI, PCI_DEVICE_ID_RPI_RP1_C0) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, dev_id_table);
+
+static struct pci_driver rp1_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = dev_id_table,
+ .probe = rp1_probe,
+ .remove = rp1_remove,
+};
+
+module_pci_driver(rp1_driver);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_AUTHOR("Andrea della Porta <andrea.porta@suse.com>");
+MODULE_DESCRIPTION("RaspberryPi RP1 misc device");
+MODULE_LICENSE("GPL");