// SPDX-License-Identifier: GPL-2.0
#include <linux/device.h>
#include <linux/pci.h>
#include "pci.h"
/*
* 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().
*
* Users of pcim_enable_device() + pci_*region*() are redirected in
* pci.c to the managed functions here in this file. This isn't exactly
* perfect, but the only alternative way would be to port ALL drivers
* using said combination to pcim_ functions.
*
* TODO:
* Remove the legacy table entirely once all calls to pcim_iomap_table() in
* the kernel have been removed.
*/
/*
* Legacy struct storing addresses to whole mapped BARs.
*/
struct pcim_iomap_devres {
void __iomem *table[PCI_STD_NUM_BARS];
};
/* Used to restore the old INTx state on driver detach. */
struct pcim_intx_devres {
int orig_intx;
};
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 *