blob: b756c1186ce90ef4cf410a239767c51e41dbbd38 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2025 Intel Corporation */
#include <linux/net/intel/libie/pci.h>
/**
* libie_find_mmio_region - find MMIO region containing a range
* @mmio_list: list that contains MMIO region info
* @offset: range start offset
* @size: range size
* @bar_idx: BAR index containing the range to search
*
* MMIO regions mappings are traversed from oldest to newest.
*
* Return: pointer to a MMIO region overlapping with the range in any way or
* NULL if no such region is mapped.
*/
static struct libie_pci_mmio_region *
libie_find_mmio_region(const struct list_head *mmio_list,
resource_size_t offset, resource_size_t size,
int bar_idx)
{
resource_size_t end_offset = offset + size;
struct libie_pci_mmio_region *mr;
list_for_each_entry(mr, mmio_list, list) {
resource_size_t mr_end = mr->offset + mr->size;
resource_size_t mr_start = mr->offset;
if (mr->bar_idx != bar_idx)
continue;
if (offset < mr_end && end_offset > mr_start)
return mr;
}
return NULL;
}
/**
* __libie_pci_get_mmio_addr - get the MMIO virtual address
* @mmio_info: contains list of MMIO regions
* @offset: register offset to find
* @num_args: number of additional arguments present
* @...: optional BAR index (0 by default)
*
* This function finds the virtual address of a register offset by iterating
* through the non-linear MMIO regions that are mapped by the driver.
*
* The list is traversed oldest to newest, so accessing an older mapping
* via this function while deleting a newer one is allowed.
*
* Return: valid MMIO virtual address or NULL.
*/
void __iomem *__libie_pci_get_mmio_addr(struct libie_mmio_info *mmio_info,
resource_size_t offset,
int num_args, ...)
{
struct libie_pci_mmio_region *mr;
int bar_idx = 0;
va_list args;
if (num_args) {
va_start(args, num_args);
bar_idx = va_arg(args, int);
va_end(args);
}
list_for_each_entry(mr, &mmio_info->mmio_list, list)
if (bar_idx == mr->bar_idx && offset >= mr->offset &&
offset < mr->offset + mr->size) {
offset -= mr->offset;
return mr->addr + offset;
}
WARN_ONCE(true, "Access to an unmapped MMIO region (BAR%d, offset %pa)",
bar_idx, &offset);
return NULL;
}
EXPORT_SYMBOL_NS_GPL(__libie_pci_get_mmio_addr, "LIBIE_PCI");
/**
* __libie_pci_map_mmio_region - map PCI device MMIO region
* @mmio_info: struct to store the mapped MMIO region
* @offset: MMIO region start offset
* @size: MMIO region size
* @num_args: number of additional arguments present
* @...: optional BAR index (0 by default)
*
* Return: true if the requested address range is accessible through
* new or existing mapping, false otherwise.
*/
bool __libie_pci_map_mmio_region(struct libie_mmio_info *mmio_info,
resource_size_t offset,
resource_size_t size, int num_args, ...)
{
struct pci_dev *pdev = mmio_info->pdev;
struct libie_pci_mmio_region *mr;
resource_size_t end_offset;
void __iomem *va;
int bar_idx = 0;
va_list args;
if (num_args) {
va_start(args, num_args);
bar_idx = va_arg(args, int);
va_end(args);
}
if (bar_idx >= PCI_STD_NUM_BARS || bar_idx < 0 ||
!pci_resource_is_mem(pdev, bar_idx))
return false;
/* pci_iomap_range() silently maps less
* if the requested length is too big
*/
if (!size || check_add_overflow(offset, size, &end_offset) ||
end_offset > pci_resource_len(pdev, bar_idx))
return false;
mr = libie_find_mmio_region(&mmio_info->mmio_list, offset, size,
bar_idx);
if (mr) {
pci_warn(pdev,
"Mapping of BAR%u (offset=%llu, size=%llu) intersecting region (offset=%llu, size=%llu) already exists\n",
bar_idx, (unsigned long long)mr->offset,
(unsigned long long)mr->size,
(unsigned long long)offset, (unsigned long long)size);
return mr->offset <= offset &&
mr->offset + mr->size >= end_offset;
}
va = pci_iomap_range(mmio_info->pdev, bar_idx, offset, size);
if (!va) {
pci_err(pdev, "Failed to map BAR%u region\n", bar_idx);
return false;
}
mr = kvzalloc_obj(*mr);
if (!mr) {
pci_iounmap(pdev, va);
return false;
}
mr->addr = va;
mr->offset = offset;
mr->size = size;
mr->bar_idx = bar_idx;
list_add_tail(&mr->list, &mmio_info->mmio_list);
return true;
}
EXPORT_SYMBOL_NS_GPL(__libie_pci_map_mmio_region, "LIBIE_PCI");
/**
* libie_pci_unmap_fltr_regs - unmap selected PCI device MMIO regions
* @mmio_info: contains list of MMIO regions to unmap
* @fltr: returns true, if region is to be unmapped
*/
void libie_pci_unmap_fltr_regs(struct libie_mmio_info *mmio_info,
bool (*fltr)(struct libie_mmio_info *mmio_info,
struct libie_pci_mmio_region *reg))
{
struct libie_pci_mmio_region *mr, *tmp;
list_for_each_entry_safe(mr, tmp, &mmio_info->mmio_list, list) {
if (!fltr(mmio_info, mr))
continue;
list_del(&mr->list);
pci_iounmap(mmio_info->pdev, mr->addr);
kvfree(mr);
}
}
EXPORT_SYMBOL_NS_GPL(libie_pci_unmap_fltr_regs, "LIBIE_PCI");
/**
* libie_pci_unmap_all_mmio_regions - unmap all PCI device MMIO regions
* @mmio_info: contains list of MMIO regions to unmap
*/
void libie_pci_unmap_all_mmio_regions(struct libie_mmio_info *mmio_info)
{
struct libie_pci_mmio_region *mr, *tmp;
list_for_each_entry_safe(mr, tmp, &mmio_info->mmio_list, list) {
list_del(&mr->list);
pci_iounmap(mmio_info->pdev, mr->addr);
kvfree(mr);
}
}
EXPORT_SYMBOL_NS_GPL(libie_pci_unmap_all_mmio_regions, "LIBIE_PCI");
/**
* libie_pci_init_dev - enable and configure the device
* @pdev: PCI device information
*
* Enable the device, request memory regions, set 64-bit DMA mask
* and coherent DMA mask, and enable bus-mastering
*
* Return: %0 on success, -%errno on failure.
*/
int libie_pci_init_dev(struct pci_dev *pdev)
{
int err;
err = pcim_enable_device(pdev);
if (err)
return err;
for (int bar = 0; bar < PCI_STD_NUM_BARS; bar++)
if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
err = pcim_request_region(pdev, bar, pci_name(pdev));
if (err)
return err;
}
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err)
return err;
pci_set_master(pdev);
return 0;
}
EXPORT_SYMBOL_NS_GPL(libie_pci_init_dev, "LIBIE_PCI");
MODULE_DESCRIPTION("Common Ethernet PCI library");
MODULE_LICENSE("GPL");