summaryrefslogtreecommitdiffstats
path: root/drivers/pci/pci-efi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/pci-efi.c')
-rw-r--r--drivers/pci/pci-efi.c351
1 files changed, 351 insertions, 0 deletions
diff --git a/drivers/pci/pci-efi.c b/drivers/pci/pci-efi.c
new file mode 100644
index 0000000000..b8cd663161
--- /dev/null
+++ b/drivers/pci/pci-efi.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2019 Ahmad Fatoum <a.fatoum@pengutronix.de>
+ */
+#define pr_fmt(fmt) "pci-efi: " fmt
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <xfuncs.h>
+#include <efi.h>
+#include <efi/efi-payload.h>
+#include <efi/efi-device.h>
+#include <linux/pci.h>
+
+#include "pci-efi.h"
+
+struct efi_pci_priv {
+ struct efi_pci_root_bridge_io_protocol *protocol;
+ struct device *dev;
+ struct pci_controller pci;
+ struct resource mem;
+ struct resource mem_pref;
+ struct resource io;
+ struct list_head children;
+};
+
+struct pci_child_id {
+ size_t segmentno;
+ size_t busno;
+ size_t devno;
+ size_t funcno;
+};
+
+struct pci_child {
+ struct efi_pci_io_protocol *protocol;
+ struct device *dev;
+ struct list_head list;
+ struct pci_child_id id;
+};
+
+static inline bool pci_child_id_equal(struct pci_child_id *a, struct pci_child_id *b)
+{
+ return a->segmentno == b->segmentno
+ && a->busno == b->busno
+ && a->devno == b->devno
+ && a->funcno == b->funcno;
+}
+
+#define host_to_efi_pci(host) container_of(host, struct efi_pci_priv, pci)
+
+static inline u64 efi_pci_addr(struct pci_bus *bus, u32 devfn, int where)
+{
+ return EFI_PCI_ADDRESS(bus->number,
+ PCI_SLOT(devfn), PCI_FUNC(devfn),
+ where);
+}
+
+static int efi_pci_rd_conf(struct pci_bus *bus, u32 devfn, int where,
+ int size, u32 *val)
+{
+ struct efi_pci_priv *priv = host_to_efi_pci(bus->host);
+ efi_status_t efiret;
+ u32 value;
+ enum efi_pci_protocol_width width;
+
+ switch (size) {
+ case 4:
+ width = EFI_PCI_WIDTH_U32;
+ break;
+ case 2:
+ width = EFI_PCI_WIDTH_U16;
+ break;
+ case 1:
+ width = EFI_PCI_WIDTH_U8;
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ efiret = priv->protocol->pci.read(priv->protocol, width,
+ efi_pci_addr(bus, devfn, where),
+ 1, &value);
+
+ *val = 0xFFFFFFFF;
+
+ if (EFI_ERROR(efiret))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ *val = value;
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int efi_pci_wr_conf(struct pci_bus *bus, u32 devfn, int where,
+ int size, u32 val)
+{
+ struct efi_pci_priv *priv = host_to_efi_pci(bus->host);
+ efi_status_t efiret;
+ enum efi_pci_protocol_width width;
+
+ switch (size) {
+ case 4:
+ width = EFI_PCI_WIDTH_U32;
+ break;
+ case 2:
+ width = EFI_PCI_WIDTH_U16;
+ break;
+ case 1:
+ width = EFI_PCI_WIDTH_U8;
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+
+ efiret = priv->protocol->pci.write(priv->protocol, width,
+ efi_pci_addr(bus, devfn, where),
+ 1, &val);
+ if (EFI_ERROR(efiret))
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static inline struct resource build_resource(const char *name,
+ resource_size_t start,
+ resource_size_t len,
+ unsigned long flags)
+{
+ struct resource res;
+
+ res.name = name;
+ res.start = start;
+ res.end = start + len - 1;
+ res.flags = flags;
+ res.parent = NULL;
+ INIT_LIST_HEAD(&res.children);
+ INIT_LIST_HEAD(&res.sibling);
+
+ return res;
+}
+
+static const struct pci_ops efi_pci_ops = {
+ .read = efi_pci_rd_conf,
+ .write = efi_pci_wr_conf,
+};
+
+static u8 *acpi_parse_resource(u8 *next, struct resource *out)
+{
+ struct efi_acpi_resource *res;
+ const char *name = NULL;
+ unsigned long flags = 0;
+
+ do {
+ if (*next == ACPI_RESOURCE_END_TAG)
+ return NULL;
+
+ if (*next != ACPI_RESOURCE_DESC_TAG)
+ return ERR_PTR(-EIO);
+
+ res = container_of(next, struct efi_acpi_resource, asd);
+
+ next = (u8 *)&res[1];
+ } while (res->addr_len == 0);
+
+ switch (res->restype) {
+ case ACPI_RESOURCE_TYPE_MEM:
+ if ((res->typflags & ACPI_RESOURCE_TYPFLAG_MTP_MASK)
+ != ACPI_RESOURCE_TYPFLAG_MTP_MEM)
+ break;
+
+ name = "NP-MEM";
+ flags = IORESOURCE_MEM;
+
+ switch (res->typflags & ACPI_RESOURCE_TYPFLAG_MEM_MASK) {
+ case ACPI_RESOURCE_TYPFLAG_MEM_PREF:
+ name = "P-MEM";
+ flags |= IORESOURCE_PREFETCH;
+ /* fallthrough */
+ case ACPI_RESOURCE_TYPFLAG_MEM_WC:
+ case ACPI_RESOURCE_TYPFLAG_MEM_CACHEABLE:
+ flags |= IORESOURCE_CACHEABLE;
+ }
+
+ if (res->typflags & ACPI_RESOURCE_TYPFLAG_RW_MASK)
+ flags |= IORESOURCE_MEM_WRITEABLE;
+
+ break;
+ case ACPI_RESOURCE_TYPE_IO:
+ name = "IO";
+ flags = IORESOURCE_IO;
+ break;
+ case ACPI_RESOURCE_TYPE_BUSNO:
+ name = "BUS";
+ flags = IORESOURCE_BUS;
+ break;
+ default:
+ return ERR_PTR(-ENXIO);
+ }
+
+ *out = build_resource(name, res->addr_min, res->addr_len, flags);
+
+ pr_debug("%s: %llx-%llx (len=%llx, gr=%lld, xlate_off=%llx, resflags=%08lx)\n",
+ out->name,
+ res->addr_min, res->addr_max, res->addr_len,
+ res->addr_granularity, res->addr_xlate_off,
+ flags);
+
+ return next;
+}
+
+static struct efi_driver efi_pci_driver;
+
+/* EFI already enumerated the bus for us, match our new pci devices with the efi
+ * handles
+ */
+static void efi_pci_fixup_dev_parent(struct pci_dev *dev)
+{
+ struct efi_pci_priv *priv;
+ struct pci_child *child;
+ struct pci_child_id id;
+
+ if (dev->dev.driver != &efi_pci_driver.driver)
+ return;
+
+ priv = host_to_efi_pci(dev->bus->host);
+
+ id.segmentno = priv->protocol->segmentno;
+ id.busno = dev->bus->number;
+ id.devno = PCI_SLOT(dev->devfn);
+ id.funcno = PCI_FUNC(dev->devfn);
+
+ list_for_each_entry(child, &priv->children, list) {
+ if (IS_ERR(child->protocol))
+ continue;
+
+ if (!child->protocol) {
+ struct efi_device *efichild = to_efi_device(child->dev);
+ efi_status_t efiret;
+
+ BS->handle_protocol(efichild->handle, &EFI_PCI_IO_PROTOCOL_GUID,
+ (void **)&child->protocol);
+ if (!child->protocol) {
+ child->protocol = ERR_PTR(-ENODEV);
+ continue;
+ }
+
+ efiret = child->protocol->get_location(child->protocol,
+ &child->id.segmentno,
+ &child->id.busno,
+ &child->id.devno,
+ &child->id.funcno);
+
+ if (EFI_ERROR(efiret)) {
+ child->protocol = ERR_PTR(-efi_errno(efiret));
+ continue;
+ }
+ }
+
+ if (pci_child_id_equal(&child->id, &id)) {
+ dev->dev.priv = child->protocol;
+ dev->dev.parent = child->dev;
+ return;
+ }
+ }
+}
+DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, efi_pci_fixup_dev_parent);
+
+static int efi_pci_probe(struct efi_device *efidev)
+{
+ struct device *child;
+ struct efi_pci_priv *priv;
+ efi_status_t efiret;
+ void *resources;
+ struct resource resource;
+ u8 *res;
+
+ priv = xzalloc(sizeof(*priv));
+
+ priv->pci.parent = &efidev->dev;
+ pci_controller_init(&priv->pci);
+
+ BS->handle_protocol(efidev->handle, &EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GUID,
+ (void **)&priv->protocol);
+ if (!priv->protocol)
+ return -ENODEV;
+
+ efiret = priv->protocol->configuration(priv->protocol, &resources);
+ if (EFI_ERROR(efiret))
+ return -efi_errno(efiret);
+
+ res = resources;
+
+ while (1) {
+ res = acpi_parse_resource(res, &resource);
+
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ if (!res)
+ break;
+
+ if ((resource.flags & (IORESOURCE_MEM | IORESOURCE_PREFETCH))
+ == (IORESOURCE_MEM | IORESOURCE_PREFETCH)) {
+ priv->pci.mem_pref_resource = &priv->mem_pref;
+ priv->mem_pref = resource;
+ } else if (resource.flags & IORESOURCE_MEM) {
+ priv->pci.mem_resource = &priv->mem;
+ priv->mem = resource;
+ } else if (resource.flags & IORESOURCE_IO) {
+ priv->pci.io_resource = &priv->io;
+ priv->io = resource;
+ }
+ }
+
+ priv->pci.pci_ops = &efi_pci_ops;
+
+ INIT_LIST_HEAD(&priv->children);
+
+ device_for_each_child(&efidev->dev, child) {
+ struct pci_child *pci_child;
+ struct efi_device *efichild = to_efi_device(child);
+
+ if (!efi_device_has_guid(efichild, EFI_PCI_IO_PROTOCOL_GUID))
+ continue;
+
+ pci_child = xzalloc(sizeof(*pci_child));
+
+ pci_child->dev = &efichild->dev;
+
+ /*
+ * regiser_pci_controller can reconfigure bridge bus numbers,
+ * thus we only collect the child node handles here, but
+ * don't yet call GetLocation on them
+ */
+ list_add_tail(&pci_child->list, &priv->children);
+ };
+
+ register_pci_controller(&priv->pci);
+
+ return 0;
+}
+
+static struct efi_driver efi_pci_driver = {
+ .driver = {
+ .name = "efi-pci",
+ },
+ .probe = efi_pci_probe,
+ .guid = EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GUID,
+};
+device_efi_driver(efi_pci_driver);