#include <linux/device.h>
#include <linux/pci.h>
#include "pci.h"
struct pcim_iomap_devres {
void __iomem *table[PCI_NUM_RESOURCES];
};
struct pcim_intx_devres {
int orig_intx;
};
enum pcim_addr_devres_type {
PCIM_ADDR_DEVRES_TYPE_INVALID,
PCIM_ADDR_DEVRES_TYPE_REGION,
PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING,
PCIM_ADDR_DEVRES_TYPE_MAPPING,
};
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;
}
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:
pci_release_region(pdev, res->bar);
break;
case PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING:
pci_iounmap(pdev, res->baseaddr);
pci_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;
}
static inline void pcim_addr_devres_free(struct pcim_addr_devres *res)
{
devres_free(res);
}
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)
{
struct resource **res = ptr;
pci_unmap_iospace(*res);
}
int devm_pci_remap_iospace(struct device *dev, const struct resource *res,
phys_addr_t phys_addr)
{
const struct resource **ptr;
int error;
ptr = devres_alloc(devm_pci_unmap_iospace, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
error = pci_remap_iospace(res, phys_addr);
if (error) {
devres_free(ptr);
} else {
*ptr = res;
devres_add(dev, ptr);
}
return error;
}
EXPORT_SYMBOL(devm_pci_remap_iospace);
void __iomem *devm_pci_remap_cfgspace(struct device *dev,
resource_size_t offset,
resource_size_t size)
{
void __iomem **ptr, *addr;
ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return NULL;
addr = pci_remap_cfgspace(offset, size);
if (addr) {
*ptr = addr;
devres_add(dev, ptr);
} else
devres_free(ptr);
return addr;
}
EXPORT_SYMBOL(devm_pci_remap_cfgspace);
void __iomem *devm_pci_remap_cfg_resource(struct device *dev,
struct resource *res)
{
resource_size_t size;
const char *name;
void __iomem *dest_ptr;
BUG_ON(!dev);
if (!res || resource_type(res) != IORESOURCE_MEM) {
dev_err(dev, "invalid resource\n");
return IOMEM_ERR_PTR(-EINVAL);
}
size = resource_size(res);
if (res->name)
name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", dev_name(dev),
res->name);
else
name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
if (!name)
return IOMEM_ERR_PTR(-ENOMEM);
if (!devm_request_mem_region(dev, res->start, size, name)) {
dev_err(dev, "can't request region for resource %pR\n", res);
return IOMEM_ERR_PTR(-EBUSY);
}
dest_ptr = devm_pci_remap_cfgspace(dev, res->start, size);
if (!dest_ptr) {
dev_err(dev, "ioremap failed for resource %pR\n", res);
devm_release_mem_region(dev, res->start, size);
dest_ptr = IOMEM_ERR_PTR(-ENOMEM);
}
return dest_ptr;
}
EXPORT_SYMBOL(devm_pci_remap_cfg_resource);
static void __pcim_clear_mwi(void *pdev_raw)
{
struct pci_dev *pdev = pdev_raw;
pci_clear_mwi(pdev);
}
int pcim_set_mwi(struct pci_dev *pdev)
{
int ret;
ret = devm_add_action(&pdev->dev, __pcim_clear_mwi, pdev);
if (ret != 0)
return ret;
ret = pci_set_mwi(pdev);
if (ret != 0)
devm_remove_action(&pdev->dev, __pcim_clear_mwi, pdev);
return ret;
}
EXPORT_SYMBOL(pcim_set_mwi);
static inline bool mask_contains_bar(int mask, int bar)
{
return mask & BIT(bar);
}
static void pcim_intx_restore(struct device *dev, void *data)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pcim_intx_devres *res = data;
pci_intx(pdev, res->orig_intx);
}
static void save_orig_intx(struct pci_dev *pdev, struct pcim_intx_devres *res)
{
u16 pci_command;
pci_read_config_word(pdev, PCI_COMMAND, &pci_command);
res->orig_intx = !(pci_command & PCI_COMMAND_INTX_DISABLE);
}
int pcim_intx(struct pci_dev *pdev, int enable)
{
struct pcim_intx_devres *res;
struct device *dev = &pdev->dev;
res = devres_find(dev, pcim_intx_restore, NULL, NULL);
if (!res) {
res = devres_alloc(pcim_intx_restore, sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
save_orig_intx(pdev, res);
devres_add(dev, res);
}
pci_intx(pdev, enable);
return 0;
}
EXPORT_SYMBOL_GPL(pcim_intx);
static void pcim_disable_device(void *pdev_raw)
{
struct pci_dev *pdev = pdev_raw;
if (!pdev->pinned)
pci_disable_device(pdev);
pdev->is_managed = false;
}
int pcim_enable_device(struct pci_dev *pdev)
{
int ret;
ret = devm_add_action(&pdev->dev, pcim_disable_device, pdev);
if (ret != 0)
return ret;
ret = pci_enable_device(pdev);
if (ret != 0) {
devm_remove_action(&pdev->dev, pcim_disable_device, pdev);
return ret;
}
pdev->is_managed = true;
return ret;
}
EXPORT_SYMBOL(pcim_enable_device);
void pcim_pin_device(struct pci_dev *pdev)
{
pdev->pinned = true;
}
EXPORT_SYMBOL(pcim_pin_device);
static void pcim_iomap_release(struct device *gendev, void *res)
{
}
void __iomem * const *pcim_iomap_table(struct pci_dev *pdev)
{
struct pcim_iomap_devres *dr, *new_dr;
dr = devres_find(&pdev->dev, pcim_iomap_release, NULL, NULL);
if (dr)
return dr->table;
new_dr = devres_alloc_node(pcim_iomap_release, sizeof(*new_dr), GFP_KERNEL,
dev_to_node(&pdev->dev));
if (!new_dr)
return NULL;
dr = devres_get(&pdev->dev, new_dr, NULL, NULL);
return dr->table;
}
EXPORT_SYMBOL(pcim_iomap_table);
static int pcim_add_mapping_to_legacy_table(struct pci_dev *pdev,
void __iomem *mapping, int bar)
{
void __iomem **legacy_iomap_table;
if (!pci_bar_index_is_valid(bar))
return -EINVAL;
legacy_iomap_table = (void __iomem **)pcim_iomap_table(pdev);
if (!legacy_iomap_table)
return -ENOMEM;
legacy_iomap_table[bar] = mapping;
return 0;
}
static void pcim_remove_mapping_from_legacy_table(struct pci_dev *pdev,
void __iomem *addr)
{
int bar;
void __iomem **legacy_iomap_table;
legacy_iomap_table = (void __iomem **)pcim_iomap_table(pdev);
if (!legacy_iomap_table)
return;
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
if (legacy_iomap_table[bar] == addr) {
legacy_iomap_table[bar] = NULL;
return;
}
}
}
static void pcim_remove_bar_from_legacy_table(struct pci_dev *pdev, int bar)
{
void __iomem **legacy_iomap_table;
if (!pci_bar_index_is_valid(bar))
return;
legacy_iomap_table = (void __iomem **)pcim_iomap_table(pdev);
if (!legacy_iomap_table)
return;
legacy_iomap_table[bar] = NULL;
}
void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen)
{
void __iomem *mapping;
struct pcim_addr_devres *res;
if (!pci_bar_index_is_valid(bar))
return NULL;
res = pcim_addr_devres_alloc(pdev);
if (!res)
return NULL;
res->type = PCIM_ADDR_DEVRES_TYPE_MAPPING;
mapping = pci_iomap(pdev, bar, maxlen);
if (!mapping)
goto err_iomap;
res->baseaddr = mapping;
if (pcim_add_mapping_to_legacy_table(pdev, mapping, bar) != 0)
goto err_table;
devres_add(&pdev->dev, res);
return mapping;
err_table:
pci_iounmap(pdev, mapping);
err_iomap:
pcim_addr_devres_free(res);
return NULL;
}
EXPORT_SYMBOL(pcim_iomap);
void pcim_iounmap(struct pci_dev *pdev, void __iomem *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) {
return;
}
pcim_remove_mapping_from_legacy_table(pdev, addr);
}
EXPORT_SYMBOL(pcim_iounmap);
void __iomem *pcim_iomap_region(struct pci_dev *pdev, int bar,
const char *name)
{
int ret;
struct pcim_addr_devres *res;
if (!pci_bar_index_is_valid(bar))
return IOMEM_ERR_PTR(-EINVAL);
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 = pci_request_region(pdev, bar, name);
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:
pci_release_region(pdev, bar);
err_region:
pcim_addr_devres_free(res);
return IOMEM_ERR_PTR(ret);
}
EXPORT_SYMBOL(pcim_iomap_region);
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);
}
EXPORT_SYMBOL(pcim_iounmap_region);
int pcim_iomap_regions(struct pci_dev *pdev, int mask, const char *name)
{
int ret;
int bar;
void __iomem *mapping;
for (bar = 0; bar < DEVICE_COUNT_RESOURCE; bar++) {
if (!mask_contains_bar(mask, bar))
continue;
mapping = pcim_iomap_region(pdev, bar, name);
if (IS_ERR(mapping)) {
ret = PTR_ERR(mapping);
goto err;
}
ret = pcim_add_mapping_to_legacy_table(pdev, mapping, bar);
if (ret != 0)
goto err;
}
return 0;
err:
while (--bar >= 0) {
pcim_iounmap_region(pdev, bar);
pcim_remove_bar_from_legacy_table(pdev, bar);
}
return ret;
}
EXPORT_SYMBOL(pcim_iomap_regions);
int pcim_request_region(struct pci_dev *pdev, int bar, const char *name)
{
int ret;
struct pcim_addr_devres *res;
if (!pci_bar_index_is_valid(bar))
return -EINVAL;
res = pcim_addr_devres_alloc(pdev);
if (!res)
return -ENOMEM;
res->type = PCIM_ADDR_DEVRES_TYPE_REGION;
res->bar = bar;
ret = pci_request_region(pdev, bar, name);
if (ret != 0) {
pcim_addr_devres_free(res);
return ret;
}
devres_add(&pdev->dev, res);
return 0;
}
EXPORT_SYMBOL(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);
}
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);
}
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;
}
EXPORT_SYMBOL(pcim_request_all_regions);
void __iomem *pcim_iomap_range(struct pci_dev *pdev, int bar,
unsigned long offset, unsigned long len)
{
void __iomem *mapping;
struct pcim_addr_devres *res;
if (!pci_bar_index_is_valid(bar))
return IOMEM_ERR_PTR(-EINVAL);
res = pcim_addr_devres_alloc(pdev);
if (!res)
return IOMEM_ERR_PTR(-ENOMEM);
mapping = pci_iomap_range(pdev, bar, offset, len);
if (!mapping) {
pcim_addr_devres_free(res);
return IOMEM_ERR_PTR(-EINVAL);
}
res->type = PCIM_ADDR_DEVRES_TYPE_MAPPING;
res->baseaddr = mapping;
devres_add(&pdev->dev, res);
return mapping;
}
EXPORT_SYMBOL(pcim_iomap_range);