// SPDX-License-Identifier: GPL-2.0-only /* Copyright (C) 2025 Intel Corporation */ #include /** * 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");