Page MenuHomeFreeBSD

D30558.diff
No OneTemporary

D30558.diff

diff --git a/sys/compat/linuxkpi/common/include/linux/pci.h b/sys/compat/linuxkpi/common/include/linux/pci.h
--- a/sys/compat/linuxkpi/common/include/linux/pci.h
+++ b/sys/compat/linuxkpi/common/include/linux/pci.h
@@ -4,6 +4,10 @@
* Copyright (c) 2010 Panasas, Inc.
* Copyright (c) 2013-2016 Mellanox Technologies, Ltd.
* All rights reserved.
+ * Copyright (c) 2020-2021 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Björn Zeeb
+ * under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -209,6 +213,10 @@
#define __devexit_p(x) x
+/*
+ * If we find drivers accessing this from multiple KPIs we may have to
+ * refcount objects of this structure.
+ */
struct pci_mmio_region {
TAILQ_ENTRY(pci_mmio_region) next;
struct resource *res;
@@ -230,16 +238,47 @@
unsigned int devfn;
uint32_t class;
uint8_t revision;
+ bool managed; /* devres "pcim_*(). */
+ bool want_iomap_res;
bool msi_enabled;
+ bool msix_enabled;
phys_addr_t rom;
size_t romlen;
TAILQ_HEAD(, pci_mmio_region) mmio;
};
+/* We need some meta-struct to keep track of these for devres. */
+struct pci_devres {
+ bool enable_io;
+ /* PCIR_MAX_BAR_0 + 1 = 6 => BIT(0..5). */
+ uint8_t region_mask;
+ struct resource *region_table[PCIR_MAX_BAR_0 + 1]; /* Not needed. */
+};
+struct pcim_iomap_devres {
+ void *mmio_table[PCIR_MAX_BAR_0 + 1];
+ struct resource *res_table[PCIR_MAX_BAR_0 + 1];
+};
+
/* Internal helper function(s). */
struct pci_dev *lkpinew_pci_dev(device_t);
+void lkpi_pci_devres_release(struct device *, void *);
+void lkpi_pcim_iomap_table_release(struct device *, void *);
+static inline int
+pci_resource_type(struct pci_dev *pdev, int bar)
+{
+ struct pci_map *pm;
+
+ pm = pci_find_bar(pdev->dev.bsddev, PCIR_BAR(bar));
+ if (!pm)
+ return (-1);
+
+ if (PCI_BAR_IO(pm->pm_value))
+ return (SYS_RES_IOPORT);
+ else
+ return (SYS_RES_MEMORY);
+}
static inline struct resource_list_entry *
linux_pci_get_rle(struct pci_dev *pdev, int type, int rid)
@@ -255,12 +294,13 @@
static inline struct resource_list_entry *
linux_pci_get_bar(struct pci_dev *pdev, int bar)
{
- struct resource_list_entry *rle;
+ int type;
+ type = pci_resource_type(pdev, bar);
+ if (type < 0)
+ return (NULL);
bar = PCIR_BAR(bar);
- if ((rle = linux_pci_get_rle(pdev, SYS_RES_MEMORY, bar)) == NULL)
- rle = linux_pci_get_rle(pdev, SYS_RES_IOPORT, bar);
- return (rle);
+ return (linux_pci_get_rle(pdev, type, bar));
}
static inline struct device *
@@ -282,21 +322,6 @@
return (found);
}
-static inline int
-pci_resource_type(struct pci_dev *pdev, int bar)
-{
- struct pci_map *pm;
-
- pm = pci_find_bar(pdev->dev.bsddev, PCIR_BAR(bar));
- if (!pm)
- return (-1);
-
- if (PCI_BAR_IO(pm->pm_value))
- return (SYS_RES_IOPORT);
- else
- return (SYS_RES_MEMORY);
-}
-
/*
* All drivers just seem to want to inspect the type not flags.
*/
@@ -389,9 +414,37 @@
return (0);
}
+static inline struct pci_devres *
+lkpi_pci_devres_get_alloc(struct pci_dev *pdev)
+{
+ struct pci_devres *dr;
+
+ dr = lkpi_devres_find(&pdev->dev, lkpi_pci_devres_release, NULL, NULL);
+ if (dr == NULL) {
+ dr = lkpi_devres_alloc(lkpi_pci_devres_release, sizeof(*dr),
+ GFP_KERNEL | __GFP_ZERO);
+ if (dr != NULL)
+ lkpi_devres_add(&pdev->dev, dr);
+ }
+
+ return (dr);
+}
+static inline struct pci_devres *
+lkpi_pci_devres_find(struct pci_dev *pdev)
+{
+
+ if (!pdev->managed)
+ return (NULL);
+
+ return (lkpi_pci_devres_get_alloc(pdev));
+}
+
static inline int
pci_request_region(struct pci_dev *pdev, int bar, const char *res_name)
{
+ struct resource *res;
+ struct pci_devres *dr;
+ struct pci_mmio_region *mmio;
int rid;
int type;
@@ -399,9 +452,34 @@
if (type < 0)
return (-ENODEV);
rid = PCIR_BAR(bar);
- if (bus_alloc_resource_any(pdev->dev.bsddev, type, &rid,
- RF_ACTIVE) == NULL)
- return (-EINVAL);
+ res = bus_alloc_resource_any(pdev->dev.bsddev, type, &rid,
+ RF_ACTIVE|RF_SHAREABLE);
+ if (res == NULL) {
+ device_printf(pdev->dev.bsddev, "%s: failed to alloc "
+ "bar %d type %d rid %d\n",
+ __func__, bar, type, PCIR_BAR(bar));
+ return (-ENODEV);
+ }
+
+ /*
+ * It seems there is an implicit devres tracking on these if the device
+ * is managed; otherwise the resources are not automatiaclly freed on
+ * FreeBSD/LinuxKPI tough they should be/are expected to be by Linux
+ * drivers.
+ */
+ dr = lkpi_pci_devres_find(pdev);
+ if (dr != NULL) {
+ dr->region_mask |= (1 << bar);
+ dr->region_table[bar] = res;
+ }
+
+ /* Even if the device is not managed we need to track it for iomap. */
+ mmio = malloc(sizeof(*mmio), M_DEVBUF, M_WAITOK | M_ZERO);
+ mmio->rid = PCIR_BAR(bar);
+ mmio->type = type;
+ mmio->res = res;
+ TAILQ_INSERT_TAIL(&pdev->mmio, mmio, next);
+
return (0);
}
@@ -409,9 +487,32 @@
pci_release_region(struct pci_dev *pdev, int bar)
{
struct resource_list_entry *rle;
+ struct pci_devres *dr;
+ struct pci_mmio_region *mmio, *p;
if ((rle = linux_pci_get_bar(pdev, bar)) == NULL)
return;
+
+ /*
+ * As we implicitly track the requests we also need to clear them on
+ * release. Do clear before resource release.
+ */
+ dr = lkpi_pci_devres_find(pdev);
+ if (dr != NULL) {
+ KASSERT(dr->region_table[bar] == rle->res, ("%s: pdev %p bar %d"
+ " region_table res %p != rel->res %p\n", __func__, pdev,
+ bar, dr->region_table[bar], rle->res));
+ dr->region_table[bar] = NULL;
+ dr->region_mask &= ~(1 << bar);
+ }
+
+ TAILQ_FOREACH_SAFE(mmio, &pdev->mmio, next, p) {
+ if (rle->res != (void *)rman_get_bushandle(mmio->res))
+ continue;
+ TAILQ_REMOVE(&pdev->mmio, mmio, next);
+ free(mmio, M_DEVBUF);
+ }
+
bus_release_resource(pdev->dev.bsddev, rle->type, rle->rid, rle->res);
}
@@ -441,7 +542,7 @@
}
static inline void
-pci_disable_msix(struct pci_dev *pdev)
+lkpi_pci_disable_msix(struct pci_dev *pdev)
{
pci_release_msi(pdev->dev.bsddev);
@@ -454,13 +555,13 @@
*/
pdev->dev.irq_start = 0;
pdev->dev.irq_end = 0;
+ pdev->msix_enabled = false;
}
-
-#define pci_disable_msi(pdev) \
- linux_pci_disable_msi(pdev)
+/* Only for consistency. No conflict on that one. */
+#define pci_disable_msix(pdev) lkpi_pci_disable_msix(pdev)
static inline void
-linux_pci_disable_msi(struct pci_dev *pdev)
+lkpi_pci_disable_msi(struct pci_dev *pdev)
{
pci_release_msi(pdev->dev.bsddev);
@@ -470,9 +571,7 @@
pdev->irq = pdev->dev.irq;
pdev->msi_enabled = false;
}
-
-#define pci_free_irq_vectors(pdev) \
- linux_pci_disable_msi(pdev)
+#define pci_disable_msi(pdev) lkpi_pci_disable_msi(pdev)
unsigned long pci_resource_start(struct pci_dev *pdev, int bar);
unsigned long pci_resource_len(struct pci_dev *pdev, int bar);
@@ -653,6 +752,7 @@
pdev->dev.irq_end = rle->start + avail;
for (i = 0; i < nreq; i++)
entries[i].vector = pdev->dev.irq_start + i;
+ pdev->msix_enabled = true;
return (0);
}
@@ -723,36 +823,71 @@
{
}
-static inline void *
-pci_iomap(struct pci_dev *dev, int mmio_bar, int mmio_size __unused)
+static inline struct resource *
+_lkpi_pci_iomap(struct pci_dev *pdev, int bar, int mmio_size __unused)
{
- struct pci_mmio_region *mmio;
+ struct pci_mmio_region *mmio, *p;
+ int type;
+
+ type = pci_resource_type(pdev, bar);
+ if (type < 0) {
+ device_printf(pdev->dev.bsddev, "%s: bar %d type %d\n",
+ __func__, bar, type);
+ return (NULL);
+ }
+
+ /*
+ * Check for duplicate mappings.
+ * This can happen if a driver calls pci_request_region() first.
+ */
+ TAILQ_FOREACH_SAFE(mmio, &pdev->mmio, next, p) {
+ if (mmio->type == type && mmio->rid == PCIR_BAR(bar)) {
+ return (mmio->res);
+ }
+ }
mmio = malloc(sizeof(*mmio), M_DEVBUF, M_WAITOK | M_ZERO);
- mmio->rid = PCIR_BAR(mmio_bar);
- mmio->type = pci_resource_type(dev, mmio_bar);
- mmio->res = bus_alloc_resource_any(dev->dev.bsddev, mmio->type,
- &mmio->rid, RF_ACTIVE);
+ mmio->rid = PCIR_BAR(bar);
+ mmio->type = type;
+ mmio->res = bus_alloc_resource_any(pdev->dev.bsddev, mmio->type,
+ &mmio->rid, RF_ACTIVE|RF_SHAREABLE);
if (mmio->res == NULL) {
+ device_printf(pdev->dev.bsddev, "%s: failed to alloc "
+ "bar %d type %d rid %d\n",
+ __func__, bar, type, PCIR_BAR(bar));
free(mmio, M_DEVBUF);
return (NULL);
}
- TAILQ_INSERT_TAIL(&dev->mmio, mmio, next);
+ TAILQ_INSERT_TAIL(&pdev->mmio, mmio, next);
+
+ return (mmio->res);
+}
+
+static inline void *
+pci_iomap(struct pci_dev *pdev, int mmio_bar, int mmio_size)
+{
+ struct resource *res;
- return ((void *)rman_get_bushandle(mmio->res));
+ res = _lkpi_pci_iomap(pdev, mmio_bar, mmio_size);
+ if (res == NULL)
+ return (NULL);
+ /* This is a FreeBSD extension so we can use bus_*(). */
+ if (pdev->want_iomap_res)
+ return (res);
+ return ((void *)rman_get_bushandle(res));
}
static inline void
-pci_iounmap(struct pci_dev *dev, void *res)
+pci_iounmap(struct pci_dev *pdev, void *res)
{
struct pci_mmio_region *mmio, *p;
- TAILQ_FOREACH_SAFE(mmio, &dev->mmio, next, p) {
+ TAILQ_FOREACH_SAFE(mmio, &pdev->mmio, next, p) {
if (res != (void *)rman_get_bushandle(mmio->res))
continue;
- bus_release_resource(dev->dev.bsddev,
+ bus_release_resource(pdev->dev.bsddev,
mmio->type, mmio->rid, mmio->res);
- TAILQ_REMOVE(&dev->mmio, mmio, next);
+ TAILQ_REMOVE(&pdev->mmio, mmio, next);
free(mmio, M_DEVBUF);
return;
}
@@ -1239,4 +1374,139 @@
struct pci_dev *lkpi_pci_get_class(unsigned int class, struct pci_dev *from);
#define pci_get_class(class, from) lkpi_pci_get_class(class, from)
+/* -------------------------------------------------------------------------- */
+
+static inline int
+pcim_enable_device(struct pci_dev *pdev)
+{
+ struct pci_devres *dr;
+ int error;
+
+ /* Here we cannot run through the pdev->managed check. */
+ dr = lkpi_pci_devres_get_alloc(pdev);
+ if (dr == NULL)
+ return (-ENOMEM);
+
+ /* If resources were enabled before do not do it again. */
+ if (dr->enable_io)
+ return (0);
+
+ error = pci_enable_device(pdev);
+ if (error == 0)
+ dr->enable_io = true;
+
+ /* This device is not managed. */
+ pdev->managed = true;
+
+ return (error);
+}
+
+static inline struct pcim_iomap_devres *
+lkpi_pcim_iomap_devres_find(struct pci_dev *pdev)
+{
+ struct pcim_iomap_devres *dr;
+
+ dr = lkpi_devres_find(&pdev->dev, lkpi_pcim_iomap_table_release,
+ NULL, NULL);
+ if (dr == NULL) {
+ dr = lkpi_devres_alloc(lkpi_pcim_iomap_table_release,
+ sizeof(*dr), GFP_KERNEL | __GFP_ZERO);
+ if (dr != NULL)
+ lkpi_devres_add(&pdev->dev, dr);
+ }
+
+ if (dr == NULL)
+ device_printf(pdev->dev.bsddev, "%s: NULL\n", __func__);
+
+ return (dr);
+}
+
+static inline void __iomem **
+pcim_iomap_table(struct pci_dev *pdev)
+{
+ struct pcim_iomap_devres *dr;
+
+ dr = lkpi_pcim_iomap_devres_find(pdev);
+ if (dr == NULL)
+ return (NULL);
+
+ /*
+ * If the driver has manually set a flag to be able to request the
+ * resource to use bus_read/write_<n>, return the shadow table.
+ */
+ if (pdev->want_iomap_res)
+ return ((void **)dr->res_table);
+
+ /* This is the Linux default. */
+ return (dr->mmio_table);
+}
+
+static inline int
+pcim_iomap_regions_request_all(struct pci_dev *pdev, uint32_t mask, char *name)
+{
+ struct pcim_iomap_devres *dr;
+ void *res;
+ uint32_t mappings, requests, req_mask;
+ int bar, error;
+
+ dr = lkpi_pcim_iomap_devres_find(pdev);
+ if (dr == NULL)
+ return (-ENOMEM);
+
+ /* Request all the BARs ("regions") we do not iomap. */
+ req_mask = ((1 << (PCIR_MAX_BAR_0 + 1)) - 1) & ~mask;
+ for (bar = requests = 0; requests != req_mask; bar++) {
+ if ((req_mask & (1 << bar)) == 0)
+ continue;
+ error = pci_request_region(pdev, bar, name);
+ if (error != 0 && error != -ENODEV)
+ goto err;
+ requests |= (1 << bar);
+ }
+
+ /* Now iomap all the requested (by "mask") ones. */
+ for (bar = mappings = 0; mappings != mask; bar++) {
+ if ((mask & (1 << bar)) == 0)
+ continue;
+
+ /* Request double is not allowed. */
+ if (dr->mmio_table[bar] != NULL) {
+ device_printf(pdev->dev.bsddev, "%s: bar %d %p\n",
+ __func__, bar, dr->mmio_table[bar]);
+ goto err;
+ }
+
+ res = _lkpi_pci_iomap(pdev, bar, 0);
+ if (res == NULL)
+ goto err;
+ dr->mmio_table[bar] = (void *)rman_get_bushandle(res);
+ dr->res_table[bar] = res;
+
+ mappings |= (1 << bar);
+ }
+
+ return (0);
+
+err:
+ for (bar = PCIR_MAX_BAR_0; bar >= 0; bar--) {
+ if ((mappings & (1 << bar)) != 0) {
+ res = dr->mmio_table[bar];
+ if (res == NULL)
+ continue;
+ pci_iounmap(pdev, res);
+ } else if ((requests & (1 << bar)) != 0) {
+ pci_release_region(pdev, bar);
+ }
+ }
+
+ return (-EINVAL);
+}
+
+/* This is a FreeBSD extension so we can use bus_*(). */
+static inline void
+linuxkpi_pcim_want_to_use_bus_functions(struct pci_dev *pdev)
+{
+ pdev->want_iomap_res = true;
+}
+
#endif /* _LINUX_PCI_H_ */
diff --git a/sys/compat/linuxkpi/common/src/linux_pci.c b/sys/compat/linuxkpi/common/src/linux_pci.c
--- a/sys/compat/linuxkpi/common/src/linux_pci.c
+++ b/sys/compat/linuxkpi/common/src/linux_pci.c
@@ -1,6 +1,10 @@
/*-
* Copyright (c) 2015-2016 Mellanox Technologies, Ltd.
* All rights reserved.
+ * Copyright (c) 2020-2021 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Björn Zeeb
+ * under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -230,6 +234,7 @@
pdev->bus->domain = pci_get_domain(dev);
pdev->dev.bsddev = dev;
pdev->dev.parent = &linux_root_device;
+ pdev->dev.release = lkpi_pci_dev_release;
INIT_LIST_HEAD(&pdev->dev.irqents);
kobject_init(&pdev->dev.kobj, &linux_dev_ktype);
kobject_set_name(&pdev->dev.kobj, device_get_nameunit(dev));
@@ -296,6 +301,14 @@
return (pdev);
}
+static void
+lkpi_pci_dev_release(struct device *dev)
+{
+
+ lkpi_devres_release_free_list(dev);
+ spin_lock_destroy(&dev->devres_lock);
+}
+
static int
linux_pci_probe(device_t dev)
{
@@ -425,6 +438,61 @@
return (0);
}
+static int
+lkpi_pci_disable_dev(struct device *dev)
+{
+
+ (void) pci_disable_io(dev->bsddev, SYS_RES_MEMORY);
+ (void) pci_disable_io(dev->bsddev, SYS_RES_IOPORT);
+ return (0);
+}
+
+void
+lkpi_pci_devres_release(struct device *dev, void *p)
+{
+ struct pci_devres *dr;
+ struct pci_dev *pdev;
+ int bar;
+
+ pdev = to_pci_dev(dev);
+ dr = p;
+
+ if (pdev->msix_enabled)
+ lkpi_pci_disable_msix(pdev);
+ if (pdev->msi_enabled)
+ lkpi_pci_disable_msi(pdev);
+
+ if (dr->enable_io && lkpi_pci_disable_dev(dev) == 0)
+ dr->enable_io = false;
+
+ if (dr->region_mask == 0)
+ return;
+ for (bar = PCIR_MAX_BAR_0; bar >= 0; bar--) {
+
+ if ((dr->region_mask & (1 << bar)) == 0)
+ continue;
+ pci_release_region(pdev, bar);
+ }
+}
+
+void
+lkpi_pcim_iomap_table_release(struct device *dev, void *p)
+{
+ struct pcim_iomap_devres *dr;
+ struct pci_dev *pdev;
+ int bar;
+
+ dr = p;
+ pdev = to_pci_dev(dev);
+ for (bar = PCIR_MAX_BAR_0; bar >= 0; bar--) {
+
+ if (dr->mmio_table[bar] == NULL)
+ continue;
+
+ pci_iounmap(pdev, dr->mmio_table[bar]);
+ }
+}
+
static int
linux_pci_suspend(device_t dev)
{

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 9, 4:46 PM (20 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14563100
Default Alt Text
D30558.diff (14 KB)

Event Timeline