mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-28 18:10:32 +00:00
PCI: Add managed partial-BAR request and map infrastructure
The pcim_iomap_devres table tracks entire-BAR mappings, so we can't use it to build a managed version of pci_iomap_range(), which maps partial BARs. Add struct pcim_addr_devres, which can track request and mapping of both entire BARs and partial BARs. Add the following internal devres functions based on struct pcim_addr_devres: pcim_iomap_region() # request & map entire BAR pcim_iounmap_region() # unmap & release entire BAR pcim_request_region() # request entire BAR pcim_release_region() # release entire BAR pcim_request_all_regions() # request all entire BARs pcim_release_all_regions() # release all entire BARs Rework the following public interfaces using the new infrastructure listed above: pcim_iomap() # map partial BAR pcim_iounmap() # unmap partial BAR pcim_iomap_regions() # request & map specified BARs pcim_iomap_regions_request_all() # request all BARs, map specified BARs pcim_iounmap_regions() # unmap & release specified BARs Link: https://lore.kernel.org/r/20240613115032.29098-4-pstanner@redhat.com Signed-off-by: Philipp Stanner <pstanner@redhat.com> Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org> [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
This commit is contained in:
parent
d5fe8207d8
commit
bbaff68bf4
@ -4,14 +4,246 @@
|
|||||||
#include "pci.h"
|
#include "pci.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PCI iomap devres
|
* On the state of PCI's devres implementation:
|
||||||
|
*
|
||||||
|
* The older devres API for PCI has two significant problems:
|
||||||
|
*
|
||||||
|
* 1. It is very strongly tied to the statically allocated mapping table in
|
||||||
|
* struct pcim_iomap_devres below. This is mostly solved in the sense of the
|
||||||
|
* pcim_ functions in this file providing things like ranged mapping by
|
||||||
|
* bypassing this table, whereas the functions that were present in the old
|
||||||
|
* API still enter the mapping addresses into the table for users of the old
|
||||||
|
* API.
|
||||||
|
*
|
||||||
|
* 2. The region-request-functions in pci.c do become managed IF the device has
|
||||||
|
* been enabled with pcim_enable_device() instead of pci_enable_device().
|
||||||
|
* This resulted in the API becoming inconsistent: Some functions have an
|
||||||
|
* obviously managed counter-part (e.g., pci_iomap() <-> pcim_iomap()),
|
||||||
|
* whereas some don't and are never managed, while others don't and are
|
||||||
|
* _sometimes_ managed (e.g. pci_request_region()).
|
||||||
|
*
|
||||||
|
* Consequently, in the new API, region requests performed by the pcim_
|
||||||
|
* functions are automatically cleaned up through the devres callback
|
||||||
|
* pcim_addr_resource_release(), while requests performed by
|
||||||
|
* pcim_enable_device() + pci_*region*() are automatically cleaned up
|
||||||
|
* through the for-loop in pcim_release().
|
||||||
|
*
|
||||||
|
* TODO 1:
|
||||||
|
* Remove the legacy table entirely once all calls to pcim_iomap_table() in
|
||||||
|
* the kernel have been removed.
|
||||||
|
*
|
||||||
|
* TODO 2:
|
||||||
|
* Port everyone calling pcim_enable_device() + pci_*region*() to using the
|
||||||
|
* pcim_ functions. Then, remove all devres functionality from pci_*region*()
|
||||||
|
* functions and remove the associated cleanups described above in point #2.
|
||||||
*/
|
*/
|
||||||
#define PCIM_IOMAP_MAX PCI_STD_NUM_BARS
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Legacy struct storing addresses to whole mapped BARs.
|
||||||
|
*/
|
||||||
struct pcim_iomap_devres {
|
struct pcim_iomap_devres {
|
||||||
void __iomem *table[PCIM_IOMAP_MAX];
|
void __iomem *table[PCI_STD_NUM_BARS];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum pcim_addr_devres_type {
|
||||||
|
/* Default initializer. */
|
||||||
|
PCIM_ADDR_DEVRES_TYPE_INVALID,
|
||||||
|
|
||||||
|
/* A requested region spanning an entire BAR. */
|
||||||
|
PCIM_ADDR_DEVRES_TYPE_REGION,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A requested region spanning an entire BAR, and a mapping for
|
||||||
|
* the entire BAR.
|
||||||
|
*/
|
||||||
|
PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A mapping within a BAR, either spanning the whole BAR or just a
|
||||||
|
* range. Without a requested region.
|
||||||
|
*/
|
||||||
|
PCIM_ADDR_DEVRES_TYPE_MAPPING,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This struct envelops IO or MEM addresses, i.e., mappings and region
|
||||||
|
* requests, because those are very frequently requested and released
|
||||||
|
* together.
|
||||||
|
*/
|
||||||
|
struct pcim_addr_devres {
|
||||||
|
enum pcim_addr_devres_type type;
|
||||||
|
void __iomem *baseaddr;
|
||||||
|
unsigned long offset;
|
||||||
|
unsigned long len;
|
||||||
|
int bar;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void pcim_addr_devres_clear(struct pcim_addr_devres *res)
|
||||||
|
{
|
||||||
|
memset(res, 0, sizeof(*res));
|
||||||
|
res->bar = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following functions, __pcim_*_region*, exist as counterparts to the
|
||||||
|
* versions from pci.c - which, unfortunately, can be in "hybrid mode", i.e.,
|
||||||
|
* sometimes managed, sometimes not.
|
||||||
|
*
|
||||||
|
* To separate the APIs cleanly, we define our own, simplified versions here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __pcim_request_region_range - Request a ranged region
|
||||||
|
* @pdev: PCI device the region belongs to
|
||||||
|
* @bar: BAR the range is within
|
||||||
|
* @offset: offset from the BAR's start address
|
||||||
|
* @maxlen: length in bytes, beginning at @offset
|
||||||
|
* @name: name associated with the request
|
||||||
|
* @req_flags: flags for the request, e.g., for kernel-exclusive requests
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, a negative error code on failure.
|
||||||
|
*
|
||||||
|
* Request a range within a device's PCI BAR. Sanity check the input.
|
||||||
|
*/
|
||||||
|
static int __pcim_request_region_range(struct pci_dev *pdev, int bar,
|
||||||
|
unsigned long offset,
|
||||||
|
unsigned long maxlen,
|
||||||
|
const char *name, int req_flags)
|
||||||
|
{
|
||||||
|
resource_size_t start = pci_resource_start(pdev, bar);
|
||||||
|
resource_size_t len = pci_resource_len(pdev, bar);
|
||||||
|
unsigned long dev_flags = pci_resource_flags(pdev, bar);
|
||||||
|
|
||||||
|
if (start == 0 || len == 0) /* Unused BAR. */
|
||||||
|
return 0;
|
||||||
|
if (len <= offset)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
start += offset;
|
||||||
|
len -= offset;
|
||||||
|
|
||||||
|
if (len > maxlen && maxlen != 0)
|
||||||
|
len = maxlen;
|
||||||
|
|
||||||
|
if (dev_flags & IORESOURCE_IO) {
|
||||||
|
if (!request_region(start, len, name))
|
||||||
|
return -EBUSY;
|
||||||
|
} else if (dev_flags & IORESOURCE_MEM) {
|
||||||
|
if (!__request_mem_region(start, len, name, req_flags))
|
||||||
|
return -EBUSY;
|
||||||
|
} else {
|
||||||
|
/* That's not a device we can request anything on. */
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __pcim_release_region_range(struct pci_dev *pdev, int bar,
|
||||||
|
unsigned long offset,
|
||||||
|
unsigned long maxlen)
|
||||||
|
{
|
||||||
|
resource_size_t start = pci_resource_start(pdev, bar);
|
||||||
|
resource_size_t len = pci_resource_len(pdev, bar);
|
||||||
|
unsigned long flags = pci_resource_flags(pdev, bar);
|
||||||
|
|
||||||
|
if (len <= offset || start == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (len == 0 || maxlen == 0) /* This an unused BAR. Do nothing. */
|
||||||
|
return;
|
||||||
|
|
||||||
|
start += offset;
|
||||||
|
len -= offset;
|
||||||
|
|
||||||
|
if (len > maxlen)
|
||||||
|
len = maxlen;
|
||||||
|
|
||||||
|
if (flags & IORESOURCE_IO)
|
||||||
|
release_region(start, len);
|
||||||
|
else if (flags & IORESOURCE_MEM)
|
||||||
|
release_mem_region(start, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __pcim_request_region(struct pci_dev *pdev, int bar,
|
||||||
|
const char *name, int flags)
|
||||||
|
{
|
||||||
|
unsigned long offset = 0;
|
||||||
|
unsigned long len = pci_resource_len(pdev, bar);
|
||||||
|
|
||||||
|
return __pcim_request_region_range(pdev, bar, offset, len, name, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __pcim_release_region(struct pci_dev *pdev, int bar)
|
||||||
|
{
|
||||||
|
unsigned long offset = 0;
|
||||||
|
unsigned long len = pci_resource_len(pdev, bar);
|
||||||
|
|
||||||
|
__pcim_release_region_range(pdev, bar, offset, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pcim_addr_resource_release(struct device *dev, void *resource_raw)
|
||||||
|
{
|
||||||
|
struct pci_dev *pdev = to_pci_dev(dev);
|
||||||
|
struct pcim_addr_devres *res = resource_raw;
|
||||||
|
|
||||||
|
switch (res->type) {
|
||||||
|
case PCIM_ADDR_DEVRES_TYPE_REGION:
|
||||||
|
__pcim_release_region(pdev, res->bar);
|
||||||
|
break;
|
||||||
|
case PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING:
|
||||||
|
pci_iounmap(pdev, res->baseaddr);
|
||||||
|
__pcim_release_region(pdev, res->bar);
|
||||||
|
break;
|
||||||
|
case PCIM_ADDR_DEVRES_TYPE_MAPPING:
|
||||||
|
pci_iounmap(pdev, res->baseaddr);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct pcim_addr_devres *pcim_addr_devres_alloc(struct pci_dev *pdev)
|
||||||
|
{
|
||||||
|
struct pcim_addr_devres *res;
|
||||||
|
|
||||||
|
res = devres_alloc_node(pcim_addr_resource_release, sizeof(*res),
|
||||||
|
GFP_KERNEL, dev_to_node(&pdev->dev));
|
||||||
|
if (res)
|
||||||
|
pcim_addr_devres_clear(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Just for consistency and readability. */
|
||||||
|
static inline void pcim_addr_devres_free(struct pcim_addr_devres *res)
|
||||||
|
{
|
||||||
|
devres_free(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used by devres to identify a pcim_addr_devres.
|
||||||
|
*/
|
||||||
|
static int pcim_addr_resources_match(struct device *dev,
|
||||||
|
void *a_raw, void *b_raw)
|
||||||
|
{
|
||||||
|
struct pcim_addr_devres *a, *b;
|
||||||
|
|
||||||
|
a = a_raw;
|
||||||
|
b = b_raw;
|
||||||
|
|
||||||
|
if (a->type != b->type)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
switch (a->type) {
|
||||||
|
case PCIM_ADDR_DEVRES_TYPE_REGION:
|
||||||
|
case PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING:
|
||||||
|
return a->bar == b->bar;
|
||||||
|
case PCIM_ADDR_DEVRES_TYPE_MAPPING:
|
||||||
|
return a->baseaddr == b->baseaddr;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void devm_pci_unmap_iospace(struct device *dev, void *ptr)
|
static void devm_pci_unmap_iospace(struct device *dev, void *ptr)
|
||||||
{
|
{
|
||||||
@ -92,8 +324,8 @@ EXPORT_SYMBOL(devm_pci_remap_cfgspace);
|
|||||||
*
|
*
|
||||||
* All operations are managed and will be undone on driver detach.
|
* All operations are managed and will be undone on driver detach.
|
||||||
*
|
*
|
||||||
* Returns a pointer to the remapped memory or an ERR_PTR() encoded error code
|
* Returns a pointer to the remapped memory or an IOMEM_ERR_PTR() encoded error
|
||||||
* on failure. Usage example::
|
* code on failure. Usage example::
|
||||||
*
|
*
|
||||||
* res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
* res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
* base = devm_pci_remap_cfg_resource(&pdev->dev, res);
|
* base = devm_pci_remap_cfg_resource(&pdev->dev, res);
|
||||||
@ -172,6 +404,17 @@ static void pcim_release(struct device *gendev, void *res)
|
|||||||
struct pci_devres *this = res;
|
struct pci_devres *this = res;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is legacy code.
|
||||||
|
*
|
||||||
|
* All regions requested by a pcim_ function do get released through
|
||||||
|
* pcim_addr_resource_release(). Thanks to the hybrid nature of the pci_
|
||||||
|
* region-request functions, this for-loop has to release the regions
|
||||||
|
* if they have been requested by such a function.
|
||||||
|
*
|
||||||
|
* TODO: Remove this once all users of pcim_enable_device() PLUS
|
||||||
|
* pci-region-request-functions have been ported to pcim_ functions.
|
||||||
|
*/
|
||||||
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++)
|
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++)
|
||||||
if (mask_contains_bar(this->region_mask, i))
|
if (mask_contains_bar(this->region_mask, i))
|
||||||
pci_release_region(dev, i);
|
pci_release_region(dev, i);
|
||||||
@ -258,19 +501,21 @@ EXPORT_SYMBOL(pcim_pin_device);
|
|||||||
|
|
||||||
static void pcim_iomap_release(struct device *gendev, void *res)
|
static void pcim_iomap_release(struct device *gendev, void *res)
|
||||||
{
|
{
|
||||||
struct pci_dev *dev = to_pci_dev(gendev);
|
/*
|
||||||
struct pcim_iomap_devres *this = res;
|
* Do nothing. This is legacy code.
|
||||||
int i;
|
*
|
||||||
|
* Cleanup of the mappings is now done directly through the callbacks
|
||||||
for (i = 0; i < PCIM_IOMAP_MAX; i++)
|
* registered when creating them.
|
||||||
if (this->table[i])
|
*/
|
||||||
pci_iounmap(dev, this->table[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pcim_iomap_table - access iomap allocation table
|
* pcim_iomap_table - access iomap allocation table
|
||||||
* @pdev: PCI device to access iomap table for
|
* @pdev: PCI device to access iomap table for
|
||||||
*
|
*
|
||||||
|
* Returns:
|
||||||
|
* Const pointer to array of __iomem pointers on success, NULL on failure.
|
||||||
|
*
|
||||||
* Access iomap allocation table for @dev. If iomap table doesn't
|
* Access iomap allocation table for @dev. If iomap table doesn't
|
||||||
* exist and @pdev is managed, it will be allocated. All iomaps
|
* exist and @pdev is managed, it will be allocated. All iomaps
|
||||||
* recorded in the iomap table are automatically unmapped on driver
|
* recorded in the iomap table are automatically unmapped on driver
|
||||||
@ -343,30 +588,67 @@ static void pcim_remove_mapping_from_legacy_table(struct pci_dev *pdev,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The same as pcim_remove_mapping_from_legacy_table(), but identifies the
|
||||||
|
* mapping by its BAR index.
|
||||||
|
*/
|
||||||
|
static void pcim_remove_bar_from_legacy_table(struct pci_dev *pdev, int bar)
|
||||||
|
{
|
||||||
|
void __iomem **legacy_iomap_table;
|
||||||
|
|
||||||
|
if (bar >= PCI_STD_NUM_BARS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
legacy_iomap_table = (void __iomem **)pcim_iomap_table(pdev);
|
||||||
|
if (!legacy_iomap_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
legacy_iomap_table[bar] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pcim_iomap - Managed pcim_iomap()
|
* pcim_iomap - Managed pcim_iomap()
|
||||||
* @pdev: PCI device to iomap for
|
* @pdev: PCI device to iomap for
|
||||||
* @bar: BAR to iomap
|
* @bar: BAR to iomap
|
||||||
* @maxlen: Maximum length of iomap
|
* @maxlen: Maximum length of iomap
|
||||||
*
|
*
|
||||||
* Managed pci_iomap(). Map is automatically unmapped on driver
|
* Returns: __iomem pointer on success, NULL on failure.
|
||||||
* detach.
|
*
|
||||||
|
* Managed pci_iomap(). Map is automatically unmapped on driver detach. If
|
||||||
|
* desired, unmap manually only with pcim_iounmap().
|
||||||
|
*
|
||||||
|
* This SHOULD only be used once per BAR.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* Contrary to the other pcim_* functions, this function does not return an
|
||||||
|
* IOMEM_ERR_PTR() on failure, but a simple NULL. This is done for backwards
|
||||||
|
* compatibility.
|
||||||
*/
|
*/
|
||||||
void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen)
|
void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen)
|
||||||
{
|
{
|
||||||
void __iomem *mapping;
|
void __iomem *mapping;
|
||||||
|
struct pcim_addr_devres *res;
|
||||||
|
|
||||||
|
res = pcim_addr_devres_alloc(pdev);
|
||||||
|
if (!res)
|
||||||
|
return NULL;
|
||||||
|
res->type = PCIM_ADDR_DEVRES_TYPE_MAPPING;
|
||||||
|
|
||||||
mapping = pci_iomap(pdev, bar, maxlen);
|
mapping = pci_iomap(pdev, bar, maxlen);
|
||||||
if (!mapping)
|
if (!mapping)
|
||||||
return NULL;
|
goto err_iomap;
|
||||||
|
res->baseaddr = mapping;
|
||||||
|
|
||||||
if (pcim_add_mapping_to_legacy_table(pdev, mapping, bar) != 0)
|
if (pcim_add_mapping_to_legacy_table(pdev, mapping, bar) != 0)
|
||||||
goto err_table;
|
goto err_table;
|
||||||
|
|
||||||
|
devres_add(&pdev->dev, res);
|
||||||
return mapping;
|
return mapping;
|
||||||
|
|
||||||
err_table:
|
err_table:
|
||||||
pci_iounmap(pdev, mapping);
|
pci_iounmap(pdev, mapping);
|
||||||
|
err_iomap:
|
||||||
|
pcim_addr_devres_free(res);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pcim_iomap);
|
EXPORT_SYMBOL(pcim_iomap);
|
||||||
@ -376,91 +658,291 @@ EXPORT_SYMBOL(pcim_iomap);
|
|||||||
* @pdev: PCI device to iounmap for
|
* @pdev: PCI device to iounmap for
|
||||||
* @addr: Address to unmap
|
* @addr: Address to unmap
|
||||||
*
|
*
|
||||||
* Managed pci_iounmap(). @addr must have been mapped using pcim_iomap().
|
* Managed pci_iounmap(). @addr must have been mapped using a pcim_* mapping
|
||||||
|
* function.
|
||||||
*/
|
*/
|
||||||
void pcim_iounmap(struct pci_dev *pdev, void __iomem *addr)
|
void pcim_iounmap(struct pci_dev *pdev, void __iomem *addr)
|
||||||
{
|
{
|
||||||
pci_iounmap(pdev, addr);
|
struct pcim_addr_devres res_searched;
|
||||||
|
|
||||||
|
pcim_addr_devres_clear(&res_searched);
|
||||||
|
res_searched.type = PCIM_ADDR_DEVRES_TYPE_MAPPING;
|
||||||
|
res_searched.baseaddr = addr;
|
||||||
|
|
||||||
|
if (devres_release(&pdev->dev, pcim_addr_resource_release,
|
||||||
|
pcim_addr_resources_match, &res_searched) != 0) {
|
||||||
|
/* Doesn't exist. User passed nonsense. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pcim_remove_mapping_from_legacy_table(pdev, addr);
|
pcim_remove_mapping_from_legacy_table(pdev, addr);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pcim_iounmap);
|
EXPORT_SYMBOL(pcim_iounmap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcim_iomap_region - Request and iomap a PCI BAR
|
||||||
|
* @pdev: PCI device to map IO resources for
|
||||||
|
* @bar: Index of a BAR to map
|
||||||
|
* @name: Name associated with the request
|
||||||
|
*
|
||||||
|
* Returns: __iomem pointer on success, an IOMEM_ERR_PTR on failure.
|
||||||
|
*
|
||||||
|
* Mapping and region will get automatically released on driver detach. If
|
||||||
|
* desired, release manually only with pcim_iounmap_region().
|
||||||
|
*/
|
||||||
|
static void __iomem *pcim_iomap_region(struct pci_dev *pdev, int bar,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct pcim_addr_devres *res;
|
||||||
|
|
||||||
|
res = pcim_addr_devres_alloc(pdev);
|
||||||
|
if (!res)
|
||||||
|
return IOMEM_ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
res->type = PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING;
|
||||||
|
res->bar = bar;
|
||||||
|
|
||||||
|
ret = __pcim_request_region(pdev, bar, name, 0);
|
||||||
|
if (ret != 0)
|
||||||
|
goto err_region;
|
||||||
|
|
||||||
|
res->baseaddr = pci_iomap(pdev, bar, 0);
|
||||||
|
if (!res->baseaddr) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err_iomap;
|
||||||
|
}
|
||||||
|
|
||||||
|
devres_add(&pdev->dev, res);
|
||||||
|
return res->baseaddr;
|
||||||
|
|
||||||
|
err_iomap:
|
||||||
|
__pcim_release_region(pdev, bar);
|
||||||
|
err_region:
|
||||||
|
pcim_addr_devres_free(res);
|
||||||
|
|
||||||
|
return IOMEM_ERR_PTR(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcim_iounmap_region - Unmap and release a PCI BAR
|
||||||
|
* @pdev: PCI device to operate on
|
||||||
|
* @bar: Index of BAR to unmap and release
|
||||||
|
*
|
||||||
|
* Unmap a BAR and release its region manually. Only pass BARs that were
|
||||||
|
* previously mapped by pcim_iomap_region().
|
||||||
|
*/
|
||||||
|
static void pcim_iounmap_region(struct pci_dev *pdev, int bar)
|
||||||
|
{
|
||||||
|
struct pcim_addr_devres res_searched;
|
||||||
|
|
||||||
|
pcim_addr_devres_clear(&res_searched);
|
||||||
|
res_searched.type = PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING;
|
||||||
|
res_searched.bar = bar;
|
||||||
|
|
||||||
|
devres_release(&pdev->dev, pcim_addr_resource_release,
|
||||||
|
pcim_addr_resources_match, &res_searched);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pcim_iomap_regions - Request and iomap PCI BARs
|
* pcim_iomap_regions - Request and iomap PCI BARs
|
||||||
* @pdev: PCI device to map IO resources for
|
* @pdev: PCI device to map IO resources for
|
||||||
* @mask: Mask of BARs to request and iomap
|
* @mask: Mask of BARs to request and iomap
|
||||||
* @name: Name used when requesting regions
|
* @name: Name associated with the requests
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, negative error code on failure.
|
||||||
*
|
*
|
||||||
* Request and iomap regions specified by @mask.
|
* Request and iomap regions specified by @mask.
|
||||||
*/
|
*/
|
||||||
int pcim_iomap_regions(struct pci_dev *pdev, int mask, const char *name)
|
int pcim_iomap_regions(struct pci_dev *pdev, int mask, const char *name)
|
||||||
{
|
{
|
||||||
void __iomem * const *iomap;
|
int ret;
|
||||||
int i, rc;
|
int bar;
|
||||||
|
void __iomem *mapping;
|
||||||
|
|
||||||
iomap = pcim_iomap_table(pdev);
|
for (bar = 0; bar < DEVICE_COUNT_RESOURCE; bar++) {
|
||||||
if (!iomap)
|
if (!mask_contains_bar(mask, bar))
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
|
|
||||||
unsigned long len;
|
|
||||||
|
|
||||||
if (!mask_contains_bar(mask, i))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
rc = -EINVAL;
|
mapping = pcim_iomap_region(pdev, bar, name);
|
||||||
len = pci_resource_len(pdev, i);
|
if (IS_ERR(mapping)) {
|
||||||
if (!len)
|
ret = PTR_ERR(mapping);
|
||||||
goto err_inval;
|
goto err;
|
||||||
|
}
|
||||||
rc = pci_request_region(pdev, i, name);
|
ret = pcim_add_mapping_to_legacy_table(pdev, mapping, bar);
|
||||||
if (rc)
|
if (ret != 0)
|
||||||
goto err_inval;
|
goto err;
|
||||||
|
|
||||||
rc = -ENOMEM;
|
|
||||||
if (!pcim_iomap(pdev, i, 0))
|
|
||||||
goto err_region;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_region:
|
err:
|
||||||
pci_release_region(pdev, i);
|
while (--bar >= 0) {
|
||||||
err_inval:
|
pcim_iounmap_region(pdev, bar);
|
||||||
while (--i >= 0) {
|
pcim_remove_bar_from_legacy_table(pdev, bar);
|
||||||
if (!mask_contains_bar(mask, i))
|
|
||||||
continue;
|
|
||||||
pcim_iounmap(pdev, iomap[i]);
|
|
||||||
pci_release_region(pdev, i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pcim_iomap_regions);
|
EXPORT_SYMBOL(pcim_iomap_regions);
|
||||||
|
|
||||||
|
static int _pcim_request_region(struct pci_dev *pdev, int bar, const char *name,
|
||||||
|
int request_flags)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct pcim_addr_devres *res;
|
||||||
|
|
||||||
|
res = pcim_addr_devres_alloc(pdev);
|
||||||
|
if (!res)
|
||||||
|
return -ENOMEM;
|
||||||
|
res->type = PCIM_ADDR_DEVRES_TYPE_REGION;
|
||||||
|
res->bar = bar;
|
||||||
|
|
||||||
|
ret = __pcim_request_region(pdev, bar, name, request_flags);
|
||||||
|
if (ret != 0) {
|
||||||
|
pcim_addr_devres_free(res);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
devres_add(&pdev->dev, res);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcim_request_region - Request a PCI BAR
|
||||||
|
* @pdev: PCI device to requestion region for
|
||||||
|
* @bar: Index of BAR to request
|
||||||
|
* @name: Name associated with the request
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, a negative error code on failure.
|
||||||
|
*
|
||||||
|
* Request region specified by @bar.
|
||||||
|
*
|
||||||
|
* The region will automatically be released on driver detach. If desired,
|
||||||
|
* release manually only with pcim_release_region().
|
||||||
|
*/
|
||||||
|
static int pcim_request_region(struct pci_dev *pdev, int bar, const char *name)
|
||||||
|
{
|
||||||
|
return _pcim_request_region(pdev, bar, name, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcim_release_region - Release a PCI BAR
|
||||||
|
* @pdev: PCI device to operate on
|
||||||
|
* @bar: Index of BAR to release
|
||||||
|
*
|
||||||
|
* Release a region manually that was previously requested by
|
||||||
|
* pcim_request_region().
|
||||||
|
*/
|
||||||
|
static void pcim_release_region(struct pci_dev *pdev, int bar)
|
||||||
|
{
|
||||||
|
struct pcim_addr_devres res_searched;
|
||||||
|
|
||||||
|
pcim_addr_devres_clear(&res_searched);
|
||||||
|
res_searched.type = PCIM_ADDR_DEVRES_TYPE_REGION;
|
||||||
|
res_searched.bar = bar;
|
||||||
|
|
||||||
|
devres_release(&pdev->dev, pcim_addr_resource_release,
|
||||||
|
pcim_addr_resources_match, &res_searched);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcim_release_all_regions - Release all regions of a PCI-device
|
||||||
|
* @pdev: the PCI device
|
||||||
|
*
|
||||||
|
* Release all regions previously requested through pcim_request_region()
|
||||||
|
* or pcim_request_all_regions().
|
||||||
|
*
|
||||||
|
* Can be called from any context, i.e., not necessarily as a counterpart to
|
||||||
|
* pcim_request_all_regions().
|
||||||
|
*/
|
||||||
|
static void pcim_release_all_regions(struct pci_dev *pdev)
|
||||||
|
{
|
||||||
|
int bar;
|
||||||
|
|
||||||
|
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)
|
||||||
|
pcim_release_region(pdev, bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcim_request_all_regions - Request all regions
|
||||||
|
* @pdev: PCI device to map IO resources for
|
||||||
|
* @name: name associated with the request
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, negative error code on failure.
|
||||||
|
*
|
||||||
|
* Requested regions will automatically be released at driver detach. If
|
||||||
|
* desired, release individual regions with pcim_release_region() or all of
|
||||||
|
* them at once with pcim_release_all_regions().
|
||||||
|
*/
|
||||||
|
static int pcim_request_all_regions(struct pci_dev *pdev, const char *name)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int bar;
|
||||||
|
|
||||||
|
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
|
||||||
|
ret = pcim_request_region(pdev, bar, name);
|
||||||
|
if (ret != 0)
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
pcim_release_all_regions(pdev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pcim_iomap_regions_request_all - Request all BARs and iomap specified ones
|
* pcim_iomap_regions_request_all - Request all BARs and iomap specified ones
|
||||||
* @pdev: PCI device to map IO resources for
|
* @pdev: PCI device to map IO resources for
|
||||||
* @mask: Mask of BARs to iomap
|
* @mask: Mask of BARs to iomap
|
||||||
* @name: Name used when requesting regions
|
* @name: Name associated with the requests
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, negative error code on failure.
|
||||||
*
|
*
|
||||||
* Request all PCI BARs and iomap regions specified by @mask.
|
* Request all PCI BARs and iomap regions specified by @mask.
|
||||||
|
*
|
||||||
|
* To release these resources manually, call pcim_release_region() for the
|
||||||
|
* regions and pcim_iounmap() for the mappings.
|
||||||
*/
|
*/
|
||||||
int pcim_iomap_regions_request_all(struct pci_dev *pdev, int mask,
|
int pcim_iomap_regions_request_all(struct pci_dev *pdev, int mask,
|
||||||
const char *name)
|
const char *name)
|
||||||
{
|
{
|
||||||
int request_mask = ((1 << 6) - 1) & ~mask;
|
int bar;
|
||||||
int rc;
|
int ret;
|
||||||
|
void __iomem **legacy_iomap_table;
|
||||||
|
|
||||||
rc = pci_request_selected_regions(pdev, request_mask, name);
|
ret = pcim_request_all_regions(pdev, name);
|
||||||
if (rc)
|
if (ret != 0)
|
||||||
return rc;
|
return ret;
|
||||||
|
|
||||||
rc = pcim_iomap_regions(pdev, mask, name);
|
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
|
||||||
if (rc)
|
if (!mask_contains_bar(mask, bar))
|
||||||
pci_release_selected_regions(pdev, request_mask);
|
continue;
|
||||||
return rc;
|
if (!pcim_iomap(pdev, bar, 0))
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
/*
|
||||||
|
* If bar is larger than 0, then pcim_iomap() above has most likely
|
||||||
|
* failed because of -EINVAL. If it is equal 0, most likely the table
|
||||||
|
* couldn't be created, indicating -ENOMEM.
|
||||||
|
*/
|
||||||
|
ret = bar > 0 ? -EINVAL : -ENOMEM;
|
||||||
|
legacy_iomap_table = (void __iomem **)pcim_iomap_table(pdev);
|
||||||
|
|
||||||
|
while (--bar >= 0)
|
||||||
|
pcim_iounmap(pdev, legacy_iomap_table[bar]);
|
||||||
|
|
||||||
|
pcim_release_all_regions(pdev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pcim_iomap_regions_request_all);
|
EXPORT_SYMBOL(pcim_iomap_regions_request_all);
|
||||||
|
|
||||||
@ -473,19 +955,14 @@ EXPORT_SYMBOL(pcim_iomap_regions_request_all);
|
|||||||
*/
|
*/
|
||||||
void pcim_iounmap_regions(struct pci_dev *pdev, int mask)
|
void pcim_iounmap_regions(struct pci_dev *pdev, int mask)
|
||||||
{
|
{
|
||||||
void __iomem * const *iomap;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
iomap = pcim_iomap_table(pdev);
|
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
|
||||||
if (!iomap)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (i = 0; i < PCIM_IOMAP_MAX; i++) {
|
|
||||||
if (!mask_contains_bar(mask, i))
|
if (!mask_contains_bar(mask, i))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
pcim_iounmap(pdev, iomap[i]);
|
pcim_iounmap_region(pdev, i);
|
||||||
pci_release_region(pdev, i);
|
pcim_remove_bar_from_legacy_table(pdev, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pcim_iounmap_regions);
|
EXPORT_SYMBOL(pcim_iounmap_regions);
|
||||||
|
@ -3883,6 +3883,17 @@ void pci_release_region(struct pci_dev *pdev, int bar)
|
|||||||
release_mem_region(pci_resource_start(pdev, bar),
|
release_mem_region(pci_resource_start(pdev, bar),
|
||||||
pci_resource_len(pdev, bar));
|
pci_resource_len(pdev, bar));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This devres utility makes this function sometimes managed
|
||||||
|
* (when pcim_enable_device() has been called before).
|
||||||
|
*
|
||||||
|
* This is bad because it conflicts with the pcim_ functions being
|
||||||
|
* exclusively responsible for managed PCI. Its "sometimes yes,
|
||||||
|
* sometimes no" nature can cause bugs.
|
||||||
|
*
|
||||||
|
* TODO: Remove this once all users that use pcim_enable_device() PLUS
|
||||||
|
* a region request function have been ported to using pcim_ functions.
|
||||||
|
*/
|
||||||
dr = find_pci_dr(pdev);
|
dr = find_pci_dr(pdev);
|
||||||
if (dr)
|
if (dr)
|
||||||
dr->region_mask &= ~(1 << bar);
|
dr->region_mask &= ~(1 << bar);
|
||||||
@ -3927,6 +3938,17 @@ static int __pci_request_region(struct pci_dev *pdev, int bar,
|
|||||||
goto err_out;
|
goto err_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This devres utility makes this function sometimes managed
|
||||||
|
* (when pcim_enable_device() has been called before).
|
||||||
|
*
|
||||||
|
* This is bad because it conflicts with the pcim_ functions being
|
||||||
|
* exclusively responsible for managed pci. Its "sometimes yes,
|
||||||
|
* sometimes no" nature can cause bugs.
|
||||||
|
*
|
||||||
|
* TODO: Remove this once all users that use pcim_enable_device() PLUS
|
||||||
|
* a region request function have been ported to using pcim_ functions.
|
||||||
|
*/
|
||||||
dr = find_pci_dr(pdev);
|
dr = find_pci_dr(pdev);
|
||||||
if (dr)
|
if (dr)
|
||||||
dr->region_mask |= 1 << bar;
|
dr->region_mask |= 1 << bar;
|
||||||
|
@ -826,6 +826,11 @@ struct pci_devres {
|
|||||||
unsigned int orig_intx:1;
|
unsigned int orig_intx:1;
|
||||||
unsigned int restore_intx:1;
|
unsigned int restore_intx:1;
|
||||||
unsigned int mwi:1;
|
unsigned int mwi:1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: remove the region_mask once everyone calling
|
||||||
|
* pcim_enable_device() + pci_*region*() is ported to pcim_ functions.
|
||||||
|
*/
|
||||||
u32 region_mask;
|
u32 region_mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user