Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: GPL-2.0
#
# PCI Endpoint Support
#
menu "PCI Endpoint"
config PCI_ENDPOINT
bool "PCI Endpoint Support"
depends on HAVE_PCI
help
Enable this configuration option to support configurable PCI
endpoint. This should be enabled if the platform has a PCI
controller that can operate in endpoint mode.
Enabling this option will build the endpoint library, which
includes endpoint controller library and endpoint function
library.
If in doubt, say "N" to disable Endpoint support.
config PCI_ENDPOINT_CONFIGFS
bool "PCI Endpoint Configfs Support"
depends on PCI_ENDPOINT
select CONFIGFS_FS
help
This will enable the configfs entry that can be used to
configure the endpoint function and used to bind the
function with an endpoint controller.
config PCI_ENDPOINT_MSI_DOORBELL
bool "PCI Endpoint MSI Doorbell Support"
depends on PCI_ENDPOINT && GENERIC_MSI_IRQ
help
This enables the EP's MSI interrupt controller to function as a
doorbell. The RC can trigger doorbell in EP by writing data to a
dedicated BAR, which the EP maps to the controller's message address.
source "drivers/pci/endpoint/functions/Kconfig"
endmenu
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for PCI Endpoint Support
#
obj-$(CONFIG_PCI_ENDPOINT_CONFIGFS) += pci-ep-cfs.o
obj-$(CONFIG_PCI_ENDPOINT) += pci-epc-core.o pci-epf-core.o\
pci-epc-mem.o functions/
obj-$(CONFIG_PCI_ENDPOINT_MSI_DOORBELL) += pci-ep-msi.o
@@ -0,0 +1,50 @@
# SPDX-License-Identifier: GPL-2.0
#
# PCI Endpoint Functions
#
config PCI_EPF_TEST
tristate "PCI Endpoint Test driver"
depends on PCI_ENDPOINT
select CONFIGFS_FS
select CRC32
help
Enable this configuration option to enable the test driver
for PCI Endpoint.
If in doubt, say "N" to disable Endpoint test driver.
config PCI_EPF_NTB
tristate "PCI Endpoint NTB driver"
depends on PCI_ENDPOINT
select CONFIGFS_FS
help
Select this configuration option to enable the Non-Transparent
Bridge (NTB) driver for PCI Endpoint. NTB driver implements NTB
controller functionality using multiple PCIe endpoint instances.
It can support NTB endpoint function devices created using
device tree.
If in doubt, say "N" to disable Endpoint NTB driver.
config PCI_EPF_VNTB
tristate "PCI Endpoint Virtual NTB driver"
depends on PCI_ENDPOINT
depends on NTB
select CONFIGFS_FS
help
Select this configuration option to enable the Non-Transparent
Bridge (NTB) driver for PCIe Endpoint. NTB driver implements NTB
between PCI Root Port and PCIe Endpoint.
If in doubt, say "N" to disable Endpoint NTB driver.
config PCI_EPF_MHI
tristate "PCI Endpoint driver for MHI bus"
depends on PCI_ENDPOINT && MHI_BUS_EP
help
Enable this configuration option to enable the PCI Endpoint
driver for Modem Host Interface (MHI) bus in Qualcomm Endpoint
devices such as SDX55.
If in doubt, say "N" to disable Endpoint driver for MHI bus.
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for PCI Endpoint Functions
#
obj-$(CONFIG_PCI_EPF_TEST) += pci-epf-test.o
obj-$(CONFIG_PCI_EPF_NTB) += pci-epf-ntb.o
obj-$(CONFIG_PCI_EPF_VNTB) += pci-epf-vntb.o
obj-$(CONFIG_PCI_EPF_MHI) += pci-epf-mhi.o
@@ -0,0 +1,995 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI EPF driver for MHI Endpoint devices
*
* Copyright (C) 2023 Linaro Ltd.
* Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
*/
#include <linux/dmaengine.h>
#include <linux/mhi_ep.h>
#include <linux/module.h>
#include <linux/of_dma.h>
#include <linux/platform_device.h>
#include <linux/pci-epc.h>
#include <linux/pci-epf.h>
#define MHI_VERSION_1_0 0x01000000
#define to_epf_mhi(cntrl) container_of(cntrl, struct pci_epf_mhi, cntrl)
/* Platform specific flags */
#define MHI_EPF_USE_DMA BIT(0)
struct pci_epf_mhi_dma_transfer {
struct pci_epf_mhi *epf_mhi;
struct mhi_ep_buf_info buf_info;
struct list_head node;
dma_addr_t paddr;
enum dma_data_direction dir;
size_t size;
};
struct pci_epf_mhi_ep_info {
const struct mhi_ep_cntrl_config *config;
struct pci_epf_header *epf_header;
enum pci_barno bar_num;
u32 epf_flags;
u32 msi_count;
u32 mru;
u32 flags;
};
#define MHI_EP_CHANNEL_CONFIG(ch_num, ch_name, direction) \
{ \
.num = ch_num, \
.name = ch_name, \
.dir = direction, \
}
#define MHI_EP_CHANNEL_CONFIG_UL(ch_num, ch_name) \
MHI_EP_CHANNEL_CONFIG(ch_num, ch_name, DMA_TO_DEVICE)
#define MHI_EP_CHANNEL_CONFIG_DL(ch_num, ch_name) \
MHI_EP_CHANNEL_CONFIG(ch_num, ch_name, DMA_FROM_DEVICE)
static const struct mhi_ep_channel_config mhi_v1_channels[] = {
MHI_EP_CHANNEL_CONFIG_UL(0, "LOOPBACK"),
MHI_EP_CHANNEL_CONFIG_DL(1, "LOOPBACK"),
MHI_EP_CHANNEL_CONFIG_UL(2, "SAHARA"),
MHI_EP_CHANNEL_CONFIG_DL(3, "SAHARA"),
MHI_EP_CHANNEL_CONFIG_UL(4, "DIAG"),
MHI_EP_CHANNEL_CONFIG_DL(5, "DIAG"),
MHI_EP_CHANNEL_CONFIG_UL(6, "SSR"),
MHI_EP_CHANNEL_CONFIG_DL(7, "SSR"),
MHI_EP_CHANNEL_CONFIG_UL(8, "QDSS"),
MHI_EP_CHANNEL_CONFIG_DL(9, "QDSS"),
MHI_EP_CHANNEL_CONFIG_UL(10, "EFS"),
MHI_EP_CHANNEL_CONFIG_DL(11, "EFS"),
MHI_EP_CHANNEL_CONFIG_UL(12, "MBIM"),
MHI_EP_CHANNEL_CONFIG_DL(13, "MBIM"),
MHI_EP_CHANNEL_CONFIG_UL(14, "QMI"),
MHI_EP_CHANNEL_CONFIG_DL(15, "QMI"),
MHI_EP_CHANNEL_CONFIG_UL(16, "QMI"),
MHI_EP_CHANNEL_CONFIG_DL(17, "QMI"),
MHI_EP_CHANNEL_CONFIG_UL(18, "IP-CTRL-1"),
MHI_EP_CHANNEL_CONFIG_DL(19, "IP-CTRL-1"),
MHI_EP_CHANNEL_CONFIG_UL(20, "IPCR"),
MHI_EP_CHANNEL_CONFIG_DL(21, "IPCR"),
MHI_EP_CHANNEL_CONFIG_UL(32, "DUN"),
MHI_EP_CHANNEL_CONFIG_DL(33, "DUN"),
MHI_EP_CHANNEL_CONFIG_UL(46, "IP_SW0"),
MHI_EP_CHANNEL_CONFIG_DL(47, "IP_SW0"),
};
static const struct mhi_ep_cntrl_config mhi_v1_config = {
.max_channels = 128,
.num_channels = ARRAY_SIZE(mhi_v1_channels),
.ch_cfg = mhi_v1_channels,
.mhi_version = MHI_VERSION_1_0,
};
static struct pci_epf_header sdx55_header = {
.vendorid = PCI_VENDOR_ID_QCOM,
.deviceid = 0x0306,
.baseclass_code = PCI_BASE_CLASS_COMMUNICATION,
.subclass_code = PCI_CLASS_COMMUNICATION_MODEM & 0xff,
.interrupt_pin = PCI_INTERRUPT_INTA,
};
static const struct pci_epf_mhi_ep_info sdx55_info = {
.config = &mhi_v1_config,
.epf_header = &sdx55_header,
.bar_num = BAR_0,
.epf_flags = PCI_BASE_ADDRESS_MEM_TYPE_32,
.msi_count = 32,
.mru = 0x8000,
};
static struct pci_epf_header sm8450_header = {
.vendorid = PCI_VENDOR_ID_QCOM,
.deviceid = 0x0306,
.baseclass_code = PCI_CLASS_OTHERS,
.interrupt_pin = PCI_INTERRUPT_INTA,
};
static const struct pci_epf_mhi_ep_info sm8450_info = {
.config = &mhi_v1_config,
.epf_header = &sm8450_header,
.bar_num = BAR_0,
.epf_flags = PCI_BASE_ADDRESS_MEM_TYPE_32,
.msi_count = 32,
.mru = 0x8000,
.flags = MHI_EPF_USE_DMA,
};
static struct pci_epf_header sa8775p_header = {
.vendorid = PCI_VENDOR_ID_QCOM,
.deviceid = 0x0116,
.baseclass_code = PCI_CLASS_OTHERS,
.interrupt_pin = PCI_INTERRUPT_INTA,
};
static const struct pci_epf_mhi_ep_info sa8775p_info = {
.config = &mhi_v1_config,
.epf_header = &sa8775p_header,
.bar_num = BAR_0,
.epf_flags = PCI_BASE_ADDRESS_MEM_TYPE_32,
.msi_count = 32,
.mru = 0x8000,
.flags = MHI_EPF_USE_DMA,
};
struct pci_epf_mhi {
const struct pci_epc_features *epc_features;
const struct pci_epf_mhi_ep_info *info;
struct mhi_ep_cntrl mhi_cntrl;
struct pci_epf *epf;
struct mutex lock;
void __iomem *mmio;
resource_size_t mmio_phys;
struct dma_chan *dma_chan_tx;
struct dma_chan *dma_chan_rx;
struct workqueue_struct *dma_wq;
struct work_struct dma_work;
struct list_head dma_list;
spinlock_t list_lock;
u32 mmio_size;
int irq;
};
static size_t get_align_offset(struct pci_epf_mhi *epf_mhi, u64 addr)
{
return addr & (epf_mhi->epc_features->align -1);
}
static int __pci_epf_mhi_alloc_map(struct mhi_ep_cntrl *mhi_cntrl, u64 pci_addr,
phys_addr_t *paddr, void __iomem **vaddr,
size_t offset, size_t size)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
struct pci_epf *epf = epf_mhi->epf;
struct pci_epc *epc = epf->epc;
int ret;
*vaddr = pci_epc_mem_alloc_addr(epc, paddr, size + offset);
if (!*vaddr)
return -ENOMEM;
ret = pci_epc_map_addr(epc, epf->func_no, epf->vfunc_no, *paddr,
pci_addr - offset, size + offset);
if (ret) {
pci_epc_mem_free_addr(epc, *paddr, *vaddr, size + offset);
return ret;
}
*paddr = *paddr + offset;
*vaddr = *vaddr + offset;
return 0;
}
static int pci_epf_mhi_alloc_map(struct mhi_ep_cntrl *mhi_cntrl, u64 pci_addr,
phys_addr_t *paddr, void __iomem **vaddr,
size_t size)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
size_t offset = get_align_offset(epf_mhi, pci_addr);
return __pci_epf_mhi_alloc_map(mhi_cntrl, pci_addr, paddr, vaddr,
offset, size);
}
static void __pci_epf_mhi_unmap_free(struct mhi_ep_cntrl *mhi_cntrl,
u64 pci_addr, phys_addr_t paddr,
void __iomem *vaddr, size_t offset,
size_t size)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
struct pci_epf *epf = epf_mhi->epf;
struct pci_epc *epc = epf->epc;
pci_epc_unmap_addr(epc, epf->func_no, epf->vfunc_no, paddr - offset);
pci_epc_mem_free_addr(epc, paddr - offset, vaddr - offset,
size + offset);
}
static void pci_epf_mhi_unmap_free(struct mhi_ep_cntrl *mhi_cntrl, u64 pci_addr,
phys_addr_t paddr, void __iomem *vaddr,
size_t size)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
size_t offset = get_align_offset(epf_mhi, pci_addr);
__pci_epf_mhi_unmap_free(mhi_cntrl, pci_addr, paddr, vaddr, offset,
size);
}
static void pci_epf_mhi_raise_irq(struct mhi_ep_cntrl *mhi_cntrl, u32 vector)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
struct pci_epf *epf = epf_mhi->epf;
struct pci_epc *epc = epf->epc;
/*
* MHI supplies 0 based MSI vectors but the API expects the vector
* number to start from 1, so we need to increment the vector by 1.
*/
pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no, PCI_IRQ_MSI,
vector + 1);
}
static int pci_epf_mhi_iatu_read(struct mhi_ep_cntrl *mhi_cntrl,
struct mhi_ep_buf_info *buf_info)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
size_t offset = get_align_offset(epf_mhi, buf_info->host_addr);
void __iomem *tre_buf;
phys_addr_t tre_phys;
int ret;
mutex_lock(&epf_mhi->lock);
ret = __pci_epf_mhi_alloc_map(mhi_cntrl, buf_info->host_addr, &tre_phys,
&tre_buf, offset, buf_info->size);
if (ret) {
mutex_unlock(&epf_mhi->lock);
return ret;
}
memcpy_fromio(buf_info->dev_addr, tre_buf, buf_info->size);
__pci_epf_mhi_unmap_free(mhi_cntrl, buf_info->host_addr, tre_phys,
tre_buf, offset, buf_info->size);
mutex_unlock(&epf_mhi->lock);
if (buf_info->cb)
buf_info->cb(buf_info);
return 0;
}
static int pci_epf_mhi_iatu_write(struct mhi_ep_cntrl *mhi_cntrl,
struct mhi_ep_buf_info *buf_info)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
size_t offset = get_align_offset(epf_mhi, buf_info->host_addr);
void __iomem *tre_buf;
phys_addr_t tre_phys;
int ret;
mutex_lock(&epf_mhi->lock);
ret = __pci_epf_mhi_alloc_map(mhi_cntrl, buf_info->host_addr, &tre_phys,
&tre_buf, offset, buf_info->size);
if (ret) {
mutex_unlock(&epf_mhi->lock);
return ret;
}
memcpy_toio(tre_buf, buf_info->dev_addr, buf_info->size);
__pci_epf_mhi_unmap_free(mhi_cntrl, buf_info->host_addr, tre_phys,
tre_buf, offset, buf_info->size);
mutex_unlock(&epf_mhi->lock);
if (buf_info->cb)
buf_info->cb(buf_info);
return 0;
}
static void pci_epf_mhi_dma_callback(void *param)
{
complete(param);
}
static int pci_epf_mhi_edma_read(struct mhi_ep_cntrl *mhi_cntrl,
struct mhi_ep_buf_info *buf_info)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
struct device *dma_dev = epf_mhi->epf->epc->dev.parent;
struct dma_chan *chan = epf_mhi->dma_chan_rx;
struct device *dev = &epf_mhi->epf->dev;
DECLARE_COMPLETION_ONSTACK(complete);
struct dma_async_tx_descriptor *desc;
struct dma_slave_config config = {};
dma_cookie_t cookie;
dma_addr_t dst_addr;
int ret;
if (buf_info->size < SZ_4K)
return pci_epf_mhi_iatu_read(mhi_cntrl, buf_info);
mutex_lock(&epf_mhi->lock);
config.direction = DMA_DEV_TO_MEM;
config.src_addr = buf_info->host_addr;
ret = dmaengine_slave_config(chan, &config);
if (ret) {
dev_err(dev, "Failed to configure DMA channel\n");
goto err_unlock;
}
dst_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_FROM_DEVICE);
ret = dma_mapping_error(dma_dev, dst_addr);
if (ret) {
dev_err(dev, "Failed to map remote memory\n");
goto err_unlock;
}
desc = dmaengine_prep_slave_single(chan, dst_addr, buf_info->size,
DMA_DEV_TO_MEM,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
goto err_unmap;
}
desc->callback = pci_epf_mhi_dma_callback;
desc->callback_param = &complete;
cookie = dmaengine_submit(desc);
ret = dma_submit_error(cookie);
if (ret) {
dev_err(dev, "Failed to do DMA submit\n");
goto err_unmap;
}
dma_async_issue_pending(chan);
ret = wait_for_completion_timeout(&complete, msecs_to_jiffies(1000));
if (!ret) {
dev_err(dev, "DMA transfer timeout\n");
dmaengine_terminate_sync(chan);
ret = -ETIMEDOUT;
} else {
ret = 0;
}
err_unmap:
dma_unmap_single(dma_dev, dst_addr, buf_info->size, DMA_FROM_DEVICE);
err_unlock:
mutex_unlock(&epf_mhi->lock);
return ret;
}
static int pci_epf_mhi_edma_write(struct mhi_ep_cntrl *mhi_cntrl,
struct mhi_ep_buf_info *buf_info)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
struct device *dma_dev = epf_mhi->epf->epc->dev.parent;
struct dma_chan *chan = epf_mhi->dma_chan_tx;
struct device *dev = &epf_mhi->epf->dev;
DECLARE_COMPLETION_ONSTACK(complete);
struct dma_async_tx_descriptor *desc;
struct dma_slave_config config = {};
dma_cookie_t cookie;
dma_addr_t src_addr;
int ret;
if (buf_info->size < SZ_4K)
return pci_epf_mhi_iatu_write(mhi_cntrl, buf_info);
mutex_lock(&epf_mhi->lock);
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = buf_info->host_addr;
ret = dmaengine_slave_config(chan, &config);
if (ret) {
dev_err(dev, "Failed to configure DMA channel\n");
goto err_unlock;
}
src_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_TO_DEVICE);
ret = dma_mapping_error(dma_dev, src_addr);
if (ret) {
dev_err(dev, "Failed to map remote memory\n");
goto err_unlock;
}
desc = dmaengine_prep_slave_single(chan, src_addr, buf_info->size,
DMA_MEM_TO_DEV,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
goto err_unmap;
}
desc->callback = pci_epf_mhi_dma_callback;
desc->callback_param = &complete;
cookie = dmaengine_submit(desc);
ret = dma_submit_error(cookie);
if (ret) {
dev_err(dev, "Failed to do DMA submit\n");
goto err_unmap;
}
dma_async_issue_pending(chan);
ret = wait_for_completion_timeout(&complete, msecs_to_jiffies(1000));
if (!ret) {
dev_err(dev, "DMA transfer timeout\n");
dmaengine_terminate_sync(chan);
ret = -ETIMEDOUT;
} else {
ret = 0;
}
err_unmap:
dma_unmap_single(dma_dev, src_addr, buf_info->size, DMA_TO_DEVICE);
err_unlock:
mutex_unlock(&epf_mhi->lock);
return ret;
}
static void pci_epf_mhi_dma_worker(struct work_struct *work)
{
struct pci_epf_mhi *epf_mhi = container_of(work, struct pci_epf_mhi, dma_work);
struct device *dma_dev = epf_mhi->epf->epc->dev.parent;
struct pci_epf_mhi_dma_transfer *itr, *tmp;
struct mhi_ep_buf_info *buf_info;
unsigned long flags;
LIST_HEAD(head);
spin_lock_irqsave(&epf_mhi->list_lock, flags);
list_splice_tail_init(&epf_mhi->dma_list, &head);
spin_unlock_irqrestore(&epf_mhi->list_lock, flags);
list_for_each_entry_safe(itr, tmp, &head, node) {
list_del(&itr->node);
dma_unmap_single(dma_dev, itr->paddr, itr->size, itr->dir);
buf_info = &itr->buf_info;
buf_info->cb(buf_info);
kfree(itr);
}
}
static void pci_epf_mhi_dma_async_callback(void *param)
{
struct pci_epf_mhi_dma_transfer *transfer = param;
struct pci_epf_mhi *epf_mhi = transfer->epf_mhi;
spin_lock(&epf_mhi->list_lock);
list_add_tail(&transfer->node, &epf_mhi->dma_list);
spin_unlock(&epf_mhi->list_lock);
queue_work(epf_mhi->dma_wq, &epf_mhi->dma_work);
}
static int pci_epf_mhi_edma_read_async(struct mhi_ep_cntrl *mhi_cntrl,
struct mhi_ep_buf_info *buf_info)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
struct device *dma_dev = epf_mhi->epf->epc->dev.parent;
struct pci_epf_mhi_dma_transfer *transfer = NULL;
struct dma_chan *chan = epf_mhi->dma_chan_rx;
struct device *dev = &epf_mhi->epf->dev;
DECLARE_COMPLETION_ONSTACK(complete);
struct dma_async_tx_descriptor *desc;
struct dma_slave_config config = {};
dma_cookie_t cookie;
dma_addr_t dst_addr;
int ret;
mutex_lock(&epf_mhi->lock);
config.direction = DMA_DEV_TO_MEM;
config.src_addr = buf_info->host_addr;
ret = dmaengine_slave_config(chan, &config);
if (ret) {
dev_err(dev, "Failed to configure DMA channel\n");
goto err_unlock;
}
dst_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_FROM_DEVICE);
ret = dma_mapping_error(dma_dev, dst_addr);
if (ret) {
dev_err(dev, "Failed to map remote memory\n");
goto err_unlock;
}
desc = dmaengine_prep_slave_single(chan, dst_addr, buf_info->size,
DMA_DEV_TO_MEM,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
goto err_unmap;
}
transfer = kzalloc_obj(*transfer);
if (!transfer) {
ret = -ENOMEM;
goto err_unmap;
}
transfer->epf_mhi = epf_mhi;
transfer->paddr = dst_addr;
transfer->size = buf_info->size;
transfer->dir = DMA_FROM_DEVICE;
memcpy(&transfer->buf_info, buf_info, sizeof(*buf_info));
desc->callback = pci_epf_mhi_dma_async_callback;
desc->callback_param = transfer;
cookie = dmaengine_submit(desc);
ret = dma_submit_error(cookie);
if (ret) {
dev_err(dev, "Failed to do DMA submit\n");
goto err_free_transfer;
}
dma_async_issue_pending(chan);
goto err_unlock;
err_free_transfer:
kfree(transfer);
err_unmap:
dma_unmap_single(dma_dev, dst_addr, buf_info->size, DMA_FROM_DEVICE);
err_unlock:
mutex_unlock(&epf_mhi->lock);
return ret;
}
static int pci_epf_mhi_edma_write_async(struct mhi_ep_cntrl *mhi_cntrl,
struct mhi_ep_buf_info *buf_info)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
struct device *dma_dev = epf_mhi->epf->epc->dev.parent;
struct pci_epf_mhi_dma_transfer *transfer = NULL;
struct dma_chan *chan = epf_mhi->dma_chan_tx;
struct device *dev = &epf_mhi->epf->dev;
DECLARE_COMPLETION_ONSTACK(complete);
struct dma_async_tx_descriptor *desc;
struct dma_slave_config config = {};
dma_cookie_t cookie;
dma_addr_t src_addr;
int ret;
mutex_lock(&epf_mhi->lock);
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = buf_info->host_addr;
ret = dmaengine_slave_config(chan, &config);
if (ret) {
dev_err(dev, "Failed to configure DMA channel\n");
goto err_unlock;
}
src_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_TO_DEVICE);
ret = dma_mapping_error(dma_dev, src_addr);
if (ret) {
dev_err(dev, "Failed to map remote memory\n");
goto err_unlock;
}
desc = dmaengine_prep_slave_single(chan, src_addr, buf_info->size,
DMA_MEM_TO_DEV,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
goto err_unmap;
}
transfer = kzalloc_obj(*transfer);
if (!transfer) {
ret = -ENOMEM;
goto err_unmap;
}
transfer->epf_mhi = epf_mhi;
transfer->paddr = src_addr;
transfer->size = buf_info->size;
transfer->dir = DMA_TO_DEVICE;
memcpy(&transfer->buf_info, buf_info, sizeof(*buf_info));
desc->callback = pci_epf_mhi_dma_async_callback;
desc->callback_param = transfer;
cookie = dmaengine_submit(desc);
ret = dma_submit_error(cookie);
if (ret) {
dev_err(dev, "Failed to do DMA submit\n");
goto err_free_transfer;
}
dma_async_issue_pending(chan);
goto err_unlock;
err_free_transfer:
kfree(transfer);
err_unmap:
dma_unmap_single(dma_dev, src_addr, buf_info->size, DMA_TO_DEVICE);
err_unlock:
mutex_unlock(&epf_mhi->lock);
return ret;
}
struct epf_dma_filter {
struct device *dev;
u32 dma_mask;
};
static bool pci_epf_mhi_filter(struct dma_chan *chan, void *node)
{
struct epf_dma_filter *filter = node;
struct dma_slave_caps caps;
memset(&caps, 0, sizeof(caps));
dma_get_slave_caps(chan, &caps);
return chan->device->dev == filter->dev && filter->dma_mask &
caps.directions;
}
static int pci_epf_mhi_dma_init(struct pci_epf_mhi *epf_mhi)
{
struct device *dma_dev = epf_mhi->epf->epc->dev.parent;
struct device *dev = &epf_mhi->epf->dev;
struct epf_dma_filter filter;
dma_cap_mask_t mask;
int ret;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
filter.dev = dma_dev;
filter.dma_mask = BIT(DMA_MEM_TO_DEV);
epf_mhi->dma_chan_tx = dma_request_channel(mask, pci_epf_mhi_filter,
&filter);
if (IS_ERR_OR_NULL(epf_mhi->dma_chan_tx)) {
dev_err(dev, "Failed to request tx channel\n");
return -ENODEV;
}
filter.dma_mask = BIT(DMA_DEV_TO_MEM);
epf_mhi->dma_chan_rx = dma_request_channel(mask, pci_epf_mhi_filter,
&filter);
if (IS_ERR_OR_NULL(epf_mhi->dma_chan_rx)) {
dev_err(dev, "Failed to request rx channel\n");
ret = -ENODEV;
goto err_release_tx;
}
epf_mhi->dma_wq = alloc_workqueue("pci_epf_mhi_dma_wq", WQ_PERCPU, 0);
if (!epf_mhi->dma_wq) {
ret = -ENOMEM;
goto err_release_rx;
}
INIT_LIST_HEAD(&epf_mhi->dma_list);
INIT_WORK(&epf_mhi->dma_work, pci_epf_mhi_dma_worker);
spin_lock_init(&epf_mhi->list_lock);
return 0;
err_release_rx:
dma_release_channel(epf_mhi->dma_chan_rx);
epf_mhi->dma_chan_rx = NULL;
err_release_tx:
dma_release_channel(epf_mhi->dma_chan_tx);
epf_mhi->dma_chan_tx = NULL;
return ret;
}
static void pci_epf_mhi_dma_deinit(struct pci_epf_mhi *epf_mhi)
{
destroy_workqueue(epf_mhi->dma_wq);
dma_release_channel(epf_mhi->dma_chan_tx);
dma_release_channel(epf_mhi->dma_chan_rx);
epf_mhi->dma_chan_tx = NULL;
epf_mhi->dma_chan_rx = NULL;
}
static int pci_epf_mhi_epc_init(struct pci_epf *epf)
{
struct pci_epf_mhi *epf_mhi = epf_get_drvdata(epf);
const struct pci_epf_mhi_ep_info *info = epf_mhi->info;
struct pci_epf_bar *epf_bar = &epf->bar[info->bar_num];
struct pci_epc *epc = epf->epc;
struct device *dev = &epf->dev;
int ret;
epf_bar->phys_addr = epf_mhi->mmio_phys;
epf_bar->size = epf_mhi->mmio_size;
epf_bar->barno = info->bar_num;
epf_bar->flags = info->epf_flags;
ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, epf_bar);
if (ret) {
dev_err(dev, "Failed to set BAR: %d\n", ret);
return ret;
}
ret = pci_epc_set_msi(epc, epf->func_no, epf->vfunc_no,
order_base_2(info->msi_count));
if (ret) {
dev_err(dev, "Failed to set MSI configuration: %d\n", ret);
return ret;
}
ret = pci_epc_write_header(epc, epf->func_no, epf->vfunc_no,
epf->header);
if (ret) {
dev_err(dev, "Failed to set Configuration header: %d\n", ret);
return ret;
}
epf_mhi->epc_features = pci_epc_get_features(epc, epf->func_no, epf->vfunc_no);
if (!epf_mhi->epc_features)
return -ENODATA;
if (info->flags & MHI_EPF_USE_DMA) {
ret = pci_epf_mhi_dma_init(epf_mhi);
if (ret) {
dev_err(dev, "Failed to initialize DMA: %d\n", ret);
return ret;
}
}
return 0;
}
static void pci_epf_mhi_epc_deinit(struct pci_epf *epf)
{
struct pci_epf_mhi *epf_mhi = epf_get_drvdata(epf);
const struct pci_epf_mhi_ep_info *info = epf_mhi->info;
struct pci_epf_bar *epf_bar = &epf->bar[info->bar_num];
struct mhi_ep_cntrl *mhi_cntrl = &epf_mhi->mhi_cntrl;
struct pci_epc *epc = epf->epc;
if (mhi_cntrl->mhi_dev) {
mhi_ep_power_down(mhi_cntrl);
if (info->flags & MHI_EPF_USE_DMA)
pci_epf_mhi_dma_deinit(epf_mhi);
mhi_ep_unregister_controller(mhi_cntrl);
}
pci_epc_clear_bar(epc, epf->func_no, epf->vfunc_no, epf_bar);
}
static int pci_epf_mhi_link_up(struct pci_epf *epf)
{
struct pci_epf_mhi *epf_mhi = epf_get_drvdata(epf);
const struct pci_epf_mhi_ep_info *info = epf_mhi->info;
struct mhi_ep_cntrl *mhi_cntrl = &epf_mhi->mhi_cntrl;
struct pci_epc *epc = epf->epc;
struct device *dev = &epf->dev;
int ret;
mhi_cntrl->mmio = epf_mhi->mmio;
mhi_cntrl->irq = epf_mhi->irq;
mhi_cntrl->mru = info->mru;
/* Assign the struct dev of PCI EP as MHI controller device */
mhi_cntrl->cntrl_dev = epc->dev.parent;
mhi_cntrl->raise_irq = pci_epf_mhi_raise_irq;
mhi_cntrl->alloc_map = pci_epf_mhi_alloc_map;
mhi_cntrl->unmap_free = pci_epf_mhi_unmap_free;
mhi_cntrl->read_sync = mhi_cntrl->read_async = pci_epf_mhi_iatu_read;
mhi_cntrl->write_sync = mhi_cntrl->write_async = pci_epf_mhi_iatu_write;
if (info->flags & MHI_EPF_USE_DMA) {
mhi_cntrl->read_sync = pci_epf_mhi_edma_read;
mhi_cntrl->write_sync = pci_epf_mhi_edma_write;
mhi_cntrl->read_async = pci_epf_mhi_edma_read_async;
mhi_cntrl->write_async = pci_epf_mhi_edma_write_async;
}
/* Register the MHI EP controller */
ret = mhi_ep_register_controller(mhi_cntrl, info->config);
if (ret) {
dev_err(dev, "Failed to register MHI EP controller: %d\n", ret);
if (info->flags & MHI_EPF_USE_DMA)
pci_epf_mhi_dma_deinit(epf_mhi);
return ret;
}
return 0;
}
static int pci_epf_mhi_link_down(struct pci_epf *epf)
{
struct pci_epf_mhi *epf_mhi = epf_get_drvdata(epf);
const struct pci_epf_mhi_ep_info *info = epf_mhi->info;
struct mhi_ep_cntrl *mhi_cntrl = &epf_mhi->mhi_cntrl;
if (mhi_cntrl->mhi_dev) {
mhi_ep_power_down(mhi_cntrl);
if (info->flags & MHI_EPF_USE_DMA)
pci_epf_mhi_dma_deinit(epf_mhi);
mhi_ep_unregister_controller(mhi_cntrl);
}
return 0;
}
static int pci_epf_mhi_bus_master_enable(struct pci_epf *epf)
{
struct pci_epf_mhi *epf_mhi = epf_get_drvdata(epf);
const struct pci_epf_mhi_ep_info *info = epf_mhi->info;
struct mhi_ep_cntrl *mhi_cntrl = &epf_mhi->mhi_cntrl;
struct device *dev = &epf->dev;
int ret;
/*
* Power up the MHI EP stack if link is up and stack is in power down
* state.
*/
if (!mhi_cntrl->enabled && mhi_cntrl->mhi_dev) {
ret = mhi_ep_power_up(mhi_cntrl);
if (ret) {
dev_err(dev, "Failed to power up MHI EP: %d\n", ret);
if (info->flags & MHI_EPF_USE_DMA)
pci_epf_mhi_dma_deinit(epf_mhi);
mhi_ep_unregister_controller(mhi_cntrl);
}
}
return 0;
}
static int pci_epf_mhi_bind(struct pci_epf *epf)
{
struct pci_epf_mhi *epf_mhi = epf_get_drvdata(epf);
struct pci_epc *epc = epf->epc;
struct device *dev = &epf->dev;
struct platform_device *pdev = to_platform_device(epc->dev.parent);
struct resource *res;
int ret;
/* Get MMIO base address from Endpoint controller */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mmio");
if (!res) {
dev_err(dev, "Failed to get \"mmio\" resource\n");
return -ENODEV;
}
epf_mhi->mmio_phys = res->start;
epf_mhi->mmio_size = resource_size(res);
epf_mhi->mmio = ioremap(epf_mhi->mmio_phys, epf_mhi->mmio_size);
if (!epf_mhi->mmio)
return -ENOMEM;
ret = platform_get_irq_byname(pdev, "doorbell");
if (ret < 0) {
iounmap(epf_mhi->mmio);
return ret;
}
epf_mhi->irq = ret;
return 0;
}
static void pci_epf_mhi_unbind(struct pci_epf *epf)
{
struct pci_epf_mhi *epf_mhi = epf_get_drvdata(epf);
const struct pci_epf_mhi_ep_info *info = epf_mhi->info;
struct pci_epf_bar *epf_bar = &epf->bar[info->bar_num];
struct mhi_ep_cntrl *mhi_cntrl = &epf_mhi->mhi_cntrl;
struct pci_epc *epc = epf->epc;
/*
* Forcefully power down the MHI EP stack. Only way to bring the MHI EP
* stack back to working state after successive bind is by getting Bus
* Master Enable event from host.
*/
if (mhi_cntrl->mhi_dev) {
mhi_ep_power_down(mhi_cntrl);
if (info->flags & MHI_EPF_USE_DMA)
pci_epf_mhi_dma_deinit(epf_mhi);
mhi_ep_unregister_controller(mhi_cntrl);
}
iounmap(epf_mhi->mmio);
pci_epc_clear_bar(epc, epf->func_no, epf->vfunc_no, epf_bar);
}
static const struct pci_epc_event_ops pci_epf_mhi_event_ops = {
.epc_init = pci_epf_mhi_epc_init,
.epc_deinit = pci_epf_mhi_epc_deinit,
.link_up = pci_epf_mhi_link_up,
.link_down = pci_epf_mhi_link_down,
.bus_master_enable = pci_epf_mhi_bus_master_enable,
};
static int pci_epf_mhi_probe(struct pci_epf *epf,
const struct pci_epf_device_id *id)
{
struct pci_epf_mhi_ep_info *info =
(struct pci_epf_mhi_ep_info *)id->driver_data;
struct pci_epf_mhi *epf_mhi;
struct device *dev = &epf->dev;
epf_mhi = devm_kzalloc(dev, sizeof(*epf_mhi), GFP_KERNEL);
if (!epf_mhi)
return -ENOMEM;
epf->header = info->epf_header;
epf_mhi->info = info;
epf_mhi->epf = epf;
epf->event_ops = &pci_epf_mhi_event_ops;
mutex_init(&epf_mhi->lock);
epf_set_drvdata(epf, epf_mhi);
return 0;
}
static const struct pci_epf_device_id pci_epf_mhi_ids[] = {
{ .name = "pci_epf_mhi_sa8775p", .driver_data = (kernel_ulong_t)&sa8775p_info },
{ .name = "pci_epf_mhi_sdx55", .driver_data = (kernel_ulong_t)&sdx55_info },
{ .name = "pci_epf_mhi_sm8450", .driver_data = (kernel_ulong_t)&sm8450_info },
{},
};
static const struct pci_epf_ops pci_epf_mhi_ops = {
.unbind = pci_epf_mhi_unbind,
.bind = pci_epf_mhi_bind,
};
static struct pci_epf_driver pci_epf_mhi_driver = {
.driver.name = "pci_epf_mhi",
.probe = pci_epf_mhi_probe,
.id_table = pci_epf_mhi_ids,
.ops = &pci_epf_mhi_ops,
.owner = THIS_MODULE,
};
static int __init pci_epf_mhi_init(void)
{
return pci_epf_register_driver(&pci_epf_mhi_driver);
}
module_init(pci_epf_mhi_init);
static void __exit pci_epf_mhi_exit(void)
{
pci_epf_unregister_driver(&pci_epf_mhi_driver);
}
module_exit(pci_epf_mhi_exit);
MODULE_DESCRIPTION("PCI EPF driver for MHI Endpoint devices");
MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,777 @@
// SPDX-License-Identifier: GPL-2.0
/*
* configfs to configure the PCI endpoint
*
* Copyright (C) 2017 Texas Instruments
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*/
#include <linux/module.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/pci-epc.h>
#include <linux/pci-epf.h>
#include <linux/pci-ep-cfs.h>
static DEFINE_IDR(functions_idr);
static DEFINE_MUTEX(functions_mutex);
static struct config_group *functions_group;
static struct config_group *controllers_group;
struct pci_epf_group {
struct config_group group;
struct config_group primary_epc_group;
struct config_group secondary_epc_group;
struct pci_epf *epf;
int index;
};
struct pci_epc_group {
struct config_group group;
struct pci_epc *epc;
bool start;
};
static inline struct pci_epf_group *to_pci_epf_group(struct config_item *item)
{
return container_of(to_config_group(item), struct pci_epf_group, group);
}
static inline struct pci_epc_group *to_pci_epc_group(struct config_item *item)
{
return container_of(to_config_group(item), struct pci_epc_group, group);
}
static int pci_secondary_epc_epf_link(struct config_item *epf_item,
struct config_item *epc_item)
{
int ret;
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item->ci_parent);
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
struct pci_epc *epc = epc_group->epc;
struct pci_epf *epf = epf_group->epf;
ret = pci_epc_add_epf(epc, epf, SECONDARY_INTERFACE);
if (ret)
return ret;
ret = pci_epf_bind(epf);
if (ret) {
pci_epc_remove_epf(epc, epf, SECONDARY_INTERFACE);
return ret;
}
/* Send any pending EPC initialization complete to the EPF driver */
pci_epc_notify_pending_init(epc, epf);
return 0;
}
static void pci_secondary_epc_epf_unlink(struct config_item *epf_item,
struct config_item *epc_item)
{
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item->ci_parent);
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
struct pci_epc *epc;
struct pci_epf *epf;
WARN_ON_ONCE(epc_group->start);
epc = epc_group->epc;
epf = epf_group->epf;
pci_epf_unbind(epf);
pci_epc_remove_epf(epc, epf, SECONDARY_INTERFACE);
}
static const struct configfs_item_operations pci_secondary_epc_item_ops = {
.allow_link = pci_secondary_epc_epf_link,
.drop_link = pci_secondary_epc_epf_unlink,
};
static const struct config_item_type pci_secondary_epc_type = {
.ct_item_ops = &pci_secondary_epc_item_ops,
.ct_owner = THIS_MODULE,
};
static struct config_group
*pci_ep_cfs_add_secondary_group(struct pci_epf_group *epf_group)
{
struct config_group *secondary_epc_group;
secondary_epc_group = &epf_group->secondary_epc_group;
config_group_init_type_name(secondary_epc_group, "secondary",
&pci_secondary_epc_type);
configfs_add_default_group(secondary_epc_group, &epf_group->group);
return secondary_epc_group;
}
static int pci_primary_epc_epf_link(struct config_item *epf_item,
struct config_item *epc_item)
{
int ret;
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item->ci_parent);
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
struct pci_epc *epc = epc_group->epc;
struct pci_epf *epf = epf_group->epf;
ret = pci_epc_add_epf(epc, epf, PRIMARY_INTERFACE);
if (ret)
return ret;
ret = pci_epf_bind(epf);
if (ret) {
pci_epc_remove_epf(epc, epf, PRIMARY_INTERFACE);
return ret;
}
/* Send any pending EPC initialization complete to the EPF driver */
pci_epc_notify_pending_init(epc, epf);
return 0;
}
static void pci_primary_epc_epf_unlink(struct config_item *epf_item,
struct config_item *epc_item)
{
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item->ci_parent);
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
struct pci_epc *epc;
struct pci_epf *epf;
WARN_ON_ONCE(epc_group->start);
epc = epc_group->epc;
epf = epf_group->epf;
pci_epf_unbind(epf);
pci_epc_remove_epf(epc, epf, PRIMARY_INTERFACE);
}
static const struct configfs_item_operations pci_primary_epc_item_ops = {
.allow_link = pci_primary_epc_epf_link,
.drop_link = pci_primary_epc_epf_unlink,
};
static const struct config_item_type pci_primary_epc_type = {
.ct_item_ops = &pci_primary_epc_item_ops,
.ct_owner = THIS_MODULE,
};
static struct config_group
*pci_ep_cfs_add_primary_group(struct pci_epf_group *epf_group)
{
struct config_group *primary_epc_group = &epf_group->primary_epc_group;
config_group_init_type_name(primary_epc_group, "primary",
&pci_primary_epc_type);
configfs_add_default_group(primary_epc_group, &epf_group->group);
return primary_epc_group;
}
static ssize_t pci_epc_start_store(struct config_item *item, const char *page,
size_t len)
{
int ret;
bool start;
struct pci_epc *epc;
struct pci_epc_group *epc_group = to_pci_epc_group(item);
epc = epc_group->epc;
if (kstrtobool(page, &start) < 0)
return -EINVAL;
if (start == epc_group->start)
return -EALREADY;
if (!start) {
pci_epc_stop(epc);
epc_group->start = 0;
return len;
}
ret = pci_epc_start(epc);
if (ret) {
dev_err(&epc->dev, "failed to start endpoint controller\n");
return -EINVAL;
}
epc_group->start = start;
return len;
}
static ssize_t pci_epc_start_show(struct config_item *item, char *page)
{
return sysfs_emit(page, "%d\n", to_pci_epc_group(item)->start);
}
CONFIGFS_ATTR(pci_epc_, start);
static struct configfs_attribute *pci_epc_attrs[] = {
&pci_epc_attr_start,
NULL,
};
static int pci_epc_epf_link(struct config_item *epc_item,
struct config_item *epf_item)
{
int ret;
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item);
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
struct pci_epc *epc = epc_group->epc;
struct pci_epf *epf = epf_group->epf;
ret = pci_epc_add_epf(epc, epf, PRIMARY_INTERFACE);
if (ret)
return ret;
ret = pci_epf_bind(epf);
if (ret) {
pci_epc_remove_epf(epc, epf, PRIMARY_INTERFACE);
return ret;
}
/* Send any pending EPC initialization complete to the EPF driver */
pci_epc_notify_pending_init(epc, epf);
return 0;
}
static void pci_epc_epf_unlink(struct config_item *epc_item,
struct config_item *epf_item)
{
struct pci_epc *epc;
struct pci_epf *epf;
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item);
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
WARN_ON_ONCE(epc_group->start);
epc = epc_group->epc;
epf = epf_group->epf;
pci_epf_unbind(epf);
pci_epc_remove_epf(epc, epf, PRIMARY_INTERFACE);
}
static const struct configfs_item_operations pci_epc_item_ops = {
.allow_link = pci_epc_epf_link,
.drop_link = pci_epc_epf_unlink,
};
static const struct config_item_type pci_epc_type = {
.ct_item_ops = &pci_epc_item_ops,
.ct_attrs = pci_epc_attrs,
.ct_owner = THIS_MODULE,
};
struct config_group *pci_ep_cfs_add_epc_group(const char *name)
{
int ret;
struct pci_epc *epc;
struct config_group *group;
struct pci_epc_group *epc_group;
epc_group = kzalloc_obj(*epc_group);
if (!epc_group) {
ret = -ENOMEM;
goto err;
}
group = &epc_group->group;
config_group_init_type_name(group, name, &pci_epc_type);
ret = configfs_register_group(controllers_group, group);
if (ret) {
pr_err("failed to register configfs group for %s\n", name);
goto err_register_group;
}
epc = pci_epc_get(name);
if (IS_ERR(epc)) {
ret = PTR_ERR(epc);
goto err_epc_get;
}
epc_group->epc = epc;
return group;
err_epc_get:
configfs_unregister_group(group);
err_register_group:
kfree(epc_group);
err:
return ERR_PTR(ret);
}
EXPORT_SYMBOL(pci_ep_cfs_add_epc_group);
void pci_ep_cfs_remove_epc_group(struct config_group *group)
{
struct pci_epc_group *epc_group;
if (!group)
return;
epc_group = container_of(group, struct pci_epc_group, group);
pci_epc_put(epc_group->epc);
configfs_unregister_group(&epc_group->group);
kfree(epc_group);
}
EXPORT_SYMBOL(pci_ep_cfs_remove_epc_group);
#define PCI_EPF_HEADER_R(_name) \
static ssize_t pci_epf_##_name##_show(struct config_item *item, char *page) \
{ \
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
if (WARN_ON_ONCE(!epf->header)) \
return -EINVAL; \
return sysfs_emit(page, "0x%04x\n", epf->header->_name); \
}
#define PCI_EPF_HEADER_W_u32(_name) \
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
u32 val; \
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
if (WARN_ON_ONCE(!epf->header)) \
return -EINVAL; \
if (kstrtou32(page, 0, &val) < 0) \
return -EINVAL; \
epf->header->_name = val; \
return len; \
}
#define PCI_EPF_HEADER_W_u16(_name) \
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
u16 val; \
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
if (WARN_ON_ONCE(!epf->header)) \
return -EINVAL; \
if (kstrtou16(page, 0, &val) < 0) \
return -EINVAL; \
epf->header->_name = val; \
return len; \
}
#define PCI_EPF_HEADER_W_u8(_name) \
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
u8 val; \
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
if (WARN_ON_ONCE(!epf->header)) \
return -EINVAL; \
if (kstrtou8(page, 0, &val) < 0) \
return -EINVAL; \
epf->header->_name = val; \
return len; \
}
static ssize_t pci_epf_msi_interrupts_store(struct config_item *item,
const char *page, size_t len)
{
u8 val;
if (kstrtou8(page, 0, &val) < 0)
return -EINVAL;
to_pci_epf_group(item)->epf->msi_interrupts = val;
return len;
}
static ssize_t pci_epf_msi_interrupts_show(struct config_item *item,
char *page)
{
return sysfs_emit(page, "%d\n",
to_pci_epf_group(item)->epf->msi_interrupts);
}
static ssize_t pci_epf_msix_interrupts_store(struct config_item *item,
const char *page, size_t len)
{
u16 val;
if (kstrtou16(page, 0, &val) < 0)
return -EINVAL;
to_pci_epf_group(item)->epf->msix_interrupts = val;
return len;
}
static ssize_t pci_epf_msix_interrupts_show(struct config_item *item,
char *page)
{
return sysfs_emit(page, "%d\n",
to_pci_epf_group(item)->epf->msix_interrupts);
}
PCI_EPF_HEADER_R(vendorid)
PCI_EPF_HEADER_W_u16(vendorid)
PCI_EPF_HEADER_R(deviceid)
PCI_EPF_HEADER_W_u16(deviceid)
PCI_EPF_HEADER_R(revid)
PCI_EPF_HEADER_W_u8(revid)
PCI_EPF_HEADER_R(progif_code)
PCI_EPF_HEADER_W_u8(progif_code)
PCI_EPF_HEADER_R(subclass_code)
PCI_EPF_HEADER_W_u8(subclass_code)
PCI_EPF_HEADER_R(baseclass_code)
PCI_EPF_HEADER_W_u8(baseclass_code)
PCI_EPF_HEADER_R(cache_line_size)
PCI_EPF_HEADER_W_u8(cache_line_size)
PCI_EPF_HEADER_R(subsys_vendor_id)
PCI_EPF_HEADER_W_u16(subsys_vendor_id)
PCI_EPF_HEADER_R(subsys_id)
PCI_EPF_HEADER_W_u16(subsys_id)
PCI_EPF_HEADER_R(interrupt_pin)
PCI_EPF_HEADER_W_u8(interrupt_pin)
CONFIGFS_ATTR(pci_epf_, vendorid);
CONFIGFS_ATTR(pci_epf_, deviceid);
CONFIGFS_ATTR(pci_epf_, revid);
CONFIGFS_ATTR(pci_epf_, progif_code);
CONFIGFS_ATTR(pci_epf_, subclass_code);
CONFIGFS_ATTR(pci_epf_, baseclass_code);
CONFIGFS_ATTR(pci_epf_, cache_line_size);
CONFIGFS_ATTR(pci_epf_, subsys_vendor_id);
CONFIGFS_ATTR(pci_epf_, subsys_id);
CONFIGFS_ATTR(pci_epf_, interrupt_pin);
CONFIGFS_ATTR(pci_epf_, msi_interrupts);
CONFIGFS_ATTR(pci_epf_, msix_interrupts);
static struct configfs_attribute *pci_epf_attrs[] = {
&pci_epf_attr_vendorid,
&pci_epf_attr_deviceid,
&pci_epf_attr_revid,
&pci_epf_attr_progif_code,
&pci_epf_attr_subclass_code,
&pci_epf_attr_baseclass_code,
&pci_epf_attr_cache_line_size,
&pci_epf_attr_subsys_vendor_id,
&pci_epf_attr_subsys_id,
&pci_epf_attr_interrupt_pin,
&pci_epf_attr_msi_interrupts,
&pci_epf_attr_msix_interrupts,
NULL,
};
static int pci_epf_vepf_link(struct config_item *epf_pf_item,
struct config_item *epf_vf_item)
{
struct pci_epf_group *epf_vf_group = to_pci_epf_group(epf_vf_item);
struct pci_epf_group *epf_pf_group = to_pci_epf_group(epf_pf_item);
struct pci_epf *epf_pf = epf_pf_group->epf;
struct pci_epf *epf_vf = epf_vf_group->epf;
return pci_epf_add_vepf(epf_pf, epf_vf);
}
static void pci_epf_vepf_unlink(struct config_item *epf_pf_item,
struct config_item *epf_vf_item)
{
struct pci_epf_group *epf_vf_group = to_pci_epf_group(epf_vf_item);
struct pci_epf_group *epf_pf_group = to_pci_epf_group(epf_pf_item);
struct pci_epf *epf_pf = epf_pf_group->epf;
struct pci_epf *epf_vf = epf_vf_group->epf;
pci_epf_remove_vepf(epf_pf, epf_vf);
}
static void pci_epf_release(struct config_item *item)
{
struct pci_epf_group *epf_group = to_pci_epf_group(item);
mutex_lock(&functions_mutex);
idr_remove(&functions_idr, epf_group->index);
mutex_unlock(&functions_mutex);
pci_epf_destroy(epf_group->epf);
kfree(epf_group);
}
static const struct configfs_item_operations pci_epf_ops = {
.allow_link = pci_epf_vepf_link,
.drop_link = pci_epf_vepf_unlink,
.release = pci_epf_release,
};
static const struct config_item_type pci_epf_type = {
.ct_item_ops = &pci_epf_ops,
.ct_attrs = pci_epf_attrs,
.ct_owner = THIS_MODULE,
};
/**
* pci_epf_type_add_cfs() - Help function drivers to expose function specific
* attributes in configfs
* @epf: the EPF device that has to be configured using configfs
* @group: the parent configfs group (corresponding to entries in
* pci_epf_device_id)
*
* Invoke to expose function specific attributes in configfs.
*
* Return: A pointer to a config_group structure or NULL if the function driver
* does not have anything to expose (attributes configured by user) or if
* the function driver does not implement the add_cfs() method.
*
* Returns an error pointer if this function is called for an unbound EPF device
* or if the EPF driver add_cfs() method fails.
*/
static struct config_group *pci_epf_type_add_cfs(struct pci_epf *epf,
struct config_group *group)
{
struct config_group *epf_type_group;
if (!epf->driver) {
dev_err(&epf->dev, "epf device not bound to driver\n");
return ERR_PTR(-ENODEV);
}
if (!epf->driver->ops->add_cfs)
return NULL;
mutex_lock(&epf->lock);
epf_type_group = epf->driver->ops->add_cfs(epf, group);
mutex_unlock(&epf->lock);
return epf_type_group;
}
static void pci_ep_cfs_add_type_group(struct pci_epf_group *epf_group)
{
struct config_group *group;
group = pci_epf_type_add_cfs(epf_group->epf, &epf_group->group);
if (!group)
return;
if (IS_ERR(group)) {
dev_err(&epf_group->epf->dev,
"failed to create epf type specific attributes: %pe\n",
group);
return;
}
configfs_add_default_group(group, &epf_group->group);
}
static void pci_epf_cfs_add_sub_groups(struct pci_epf_group *epf_group)
{
struct config_group *group;
group = pci_ep_cfs_add_primary_group(epf_group);
if (IS_ERR(group)) {
dev_err(&epf_group->epf->dev,
"failed to create 'primary' EPC interface: %pe\n",
group);
return;
}
group = pci_ep_cfs_add_secondary_group(epf_group);
if (IS_ERR(group)) {
dev_err(&epf_group->epf->dev,
"failed to create 'secondary' EPC interface: %pe\n",
group);
return;
}
pci_ep_cfs_add_type_group(epf_group);
}
static struct config_group *pci_epf_make(struct config_group *group,
const char *name)
{
struct pci_epf_group *epf_group;
struct pci_epf *epf;
char *epf_name;
int index, err;
epf_group = kzalloc_obj(*epf_group);
if (!epf_group)
return ERR_PTR(-ENOMEM);
mutex_lock(&functions_mutex);
index = idr_alloc(&functions_idr, epf_group, 0, 0, GFP_KERNEL);
mutex_unlock(&functions_mutex);
if (index < 0) {
err = index;
goto free_group;
}
epf_group->index = index;
config_group_init_type_name(&epf_group->group, name, &pci_epf_type);
epf_name = kasprintf(GFP_KERNEL, "%s.%d",
group->cg_item.ci_name, epf_group->index);
if (!epf_name) {
err = -ENOMEM;
goto remove_idr;
}
epf = pci_epf_create(epf_name);
if (IS_ERR(epf)) {
err = PTR_ERR(epf);
pr_err("failed to create endpoint function device (%s): %d\n",
epf_name, err);
goto free_name;
}
epf->group = &epf_group->group;
epf_group->epf = epf;
kfree(epf_name);
pci_epf_cfs_add_sub_groups(epf_group);
return &epf_group->group;
free_name:
kfree(epf_name);
remove_idr:
mutex_lock(&functions_mutex);
idr_remove(&functions_idr, epf_group->index);
mutex_unlock(&functions_mutex);
free_group:
kfree(epf_group);
return ERR_PTR(err);
}
static void pci_epf_drop(struct config_group *group, struct config_item *item)
{
config_item_put(item);
}
static const struct configfs_group_operations pci_epf_group_ops = {
.make_group = &pci_epf_make,
.drop_item = &pci_epf_drop,
};
static const struct config_item_type pci_epf_group_type = {
.ct_group_ops = &pci_epf_group_ops,
.ct_owner = THIS_MODULE,
};
struct config_group *pci_ep_cfs_add_epf_group(const char *name)
{
struct config_group *group;
group = configfs_register_default_group(functions_group, name,
&pci_epf_group_type);
if (IS_ERR(group))
pr_err("failed to register configfs group for %s function: %pe\n",
name, group);
return group;
}
EXPORT_SYMBOL(pci_ep_cfs_add_epf_group);
void pci_ep_cfs_remove_epf_group(struct config_group *group)
{
if (IS_ERR_OR_NULL(group))
return;
list_del(&group->group_entry);
configfs_unregister_default_group(group);
}
EXPORT_SYMBOL(pci_ep_cfs_remove_epf_group);
static const struct config_item_type pci_functions_type = {
.ct_owner = THIS_MODULE,
};
static const struct config_item_type pci_controllers_type = {
.ct_owner = THIS_MODULE,
};
static const struct config_item_type pci_ep_type = {
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem pci_ep_cfs_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "pci_ep",
.ci_type = &pci_ep_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(pci_ep_cfs_subsys.su_mutex),
};
static int __init pci_ep_cfs_init(void)
{
int ret;
struct config_group *root = &pci_ep_cfs_subsys.su_group;
config_group_init(root);
ret = configfs_register_subsystem(&pci_ep_cfs_subsys);
if (ret) {
pr_err("Error %d while registering subsystem %s\n",
ret, root->cg_item.ci_namebuf);
goto err;
}
functions_group = configfs_register_default_group(root, "functions",
&pci_functions_type);
if (IS_ERR(functions_group)) {
ret = PTR_ERR(functions_group);
pr_err("Error %d while registering functions group\n",
ret);
goto err_functions_group;
}
controllers_group =
configfs_register_default_group(root, "controllers",
&pci_controllers_type);
if (IS_ERR(controllers_group)) {
ret = PTR_ERR(controllers_group);
pr_err("Error %d while registering controllers group\n",
ret);
goto err_controllers_group;
}
return 0;
err_controllers_group:
configfs_unregister_default_group(functions_group);
err_functions_group:
configfs_unregister_subsystem(&pci_ep_cfs_subsys);
err:
return ret;
}
module_init(pci_ep_cfs_init);
static void __exit pci_ep_cfs_exit(void)
{
configfs_unregister_default_group(controllers_group);
configfs_unregister_default_group(functions_group);
configfs_unregister_subsystem(&pci_ep_cfs_subsys);
}
module_exit(pci_ep_cfs_exit);
MODULE_DESCRIPTION("PCI EP CONFIGFS");
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Endpoint *Controller* (EPC) MSI library
*
* Copyright (C) 2025 NXP
* Author: Frank Li <Frank.Li@nxp.com>
*/
#include <linux/align.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/msi.h>
#include <linux/of_irq.h>
#include <linux/pci-epc.h>
#include <linux/pci-epf.h>
#include <linux/pci-ep-cfs.h>
#include <linux/pci-ep-msi.h>
#include <linux/slab.h>
static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
{
struct pci_epc *epc;
struct pci_epf *epf;
epc = pci_epc_get(dev_name(msi_desc_to_dev(desc)));
if (IS_ERR(epc))
return;
epf = list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list);
if (epf && epf->db_msg && desc->msi_index < epf->num_db)
memcpy(&epf->db_msg[desc->msi_index].msg, msg, sizeof(*msg));
pci_epc_put(epc);
}
static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db)
{
const struct pci_epc_aux_resource *doorbell = NULL;
struct pci_epf_doorbell_msg *msg;
struct pci_epc *epc = epf->epc;
size_t map_size = 0, off = 0;
dma_addr_t iova_base = 0;
phys_addr_t phys_base;
int count, ret, i;
u64 addr;
count = pci_epc_get_aux_resources_count(epc, epf->func_no,
epf->vfunc_no);
if (count < 0)
return count;
if (!count)
return -ENODEV;
struct pci_epc_aux_resource *res __free(kfree) =
kcalloc(count, sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
ret = pci_epc_get_aux_resources(epc, epf->func_no, epf->vfunc_no,
res, count);
if (ret)
return ret;
/* TODO: Support multiple DOORBELL_MMIO resources per EPC. */
for (i = 0; i < count; i++) {
if (res[i].type != PCI_EPC_AUX_DOORBELL_MMIO)
continue;
doorbell = &res[i];
break;
}
if (!doorbell)
return -ENODEV;
addr = doorbell->phys_addr;
if (!IS_ALIGNED(addr, sizeof(u32)))
return -EINVAL;
/*
* Reuse the pre-exposed BAR window if available. Otherwise map the MMIO
* doorbell resource here. Any required IOMMU mapping is handled
* internally, matching the MSI doorbell semantics.
*/
if (doorbell->bar == NO_BAR) {
phys_base = addr & PAGE_MASK;
off = addr - phys_base;
map_size = PAGE_ALIGN(off + sizeof(u32));
iova_base = dma_map_resource(epc->dev.parent, phys_base,
map_size, DMA_FROM_DEVICE, 0);
if (dma_mapping_error(epc->dev.parent, iova_base))
return -EIO;
addr = iova_base + off;
}
msg = kcalloc(num_db, sizeof(*msg), GFP_KERNEL);
if (!msg) {
ret = -ENOMEM;
goto err_unmap;
}
/*
* Embedded doorbell backends (e.g. DesignWare eDMA interrupt emulation)
* typically provide a single IRQ and do not offer per-doorbell
* distinguishable address/data pairs. The EPC aux resource therefore
* exposes one DOORBELL_MMIO entry (u.db_mmio.irq).
*
* Still, pci_epf_alloc_doorbell() allows requesting multiple doorbells.
* For such backends we replicate the same address/data for each entry
* and mark the IRQ as shared (IRQF_SHARED). Consumers must treat them
* as equivalent "kick" doorbells.
*/
for (i = 0; i < num_db; i++)
msg[i] = (struct pci_epf_doorbell_msg) {
.msg.address_lo = (u32)addr,
.msg.address_hi = (u32)(addr >> 32),
.msg.data = doorbell->u.db_mmio.data,
.virq = doorbell->u.db_mmio.irq,
.irq_flags = IRQF_SHARED,
.type = PCI_EPF_DOORBELL_EMBEDDED,
.bar = doorbell->bar,
.offset = (doorbell->bar == NO_BAR) ? 0 :
doorbell->bar_offset,
.iova_base = iova_base,
.iova_size = map_size,
};
epf->num_db = num_db;
epf->db_msg = msg;
return 0;
err_unmap:
if (map_size)
dma_unmap_resource(epc->dev.parent, iova_base, map_size,
DMA_FROM_DEVICE, 0);
return ret;
}
static int pci_epf_alloc_doorbell_msi(struct pci_epf *epf, u16 num_db)
{
struct pci_epf_doorbell_msg *msg;
struct device *dev = &epf->dev;
struct pci_epc *epc = epf->epc;
struct irq_domain *domain;
int ret, i;
domain = of_msi_map_get_device_domain(epc->dev.parent, 0,
DOMAIN_BUS_PLATFORM_MSI);
if (!domain) {
dev_err(dev, "Can't find MSI domain for EPC\n");
return -ENODEV;
}
if (!irq_domain_is_msi_parent(domain))
return -ENODEV;
if (!irq_domain_is_msi_immutable(domain)) {
dev_err(dev, "Mutable MSI controller not supported\n");
return -ENODEV;
}
dev_set_msi_domain(epc->dev.parent, domain);
msg = kzalloc_objs(struct pci_epf_doorbell_msg, num_db);
if (!msg)
return -ENOMEM;
for (i = 0; i < num_db; i++)
msg[i] = (struct pci_epf_doorbell_msg) {
.type = PCI_EPF_DOORBELL_MSI,
.bar = NO_BAR,
};
epf->num_db = num_db;
epf->db_msg = msg;
ret = platform_device_msi_init_and_alloc_irqs(epc->dev.parent, num_db,
pci_epf_write_msi_msg);
if (ret) {
dev_err(dev, "Failed to allocate MSI\n");
kfree(msg);
epf->db_msg = NULL;
epf->num_db = 0;
return ret;
}
for (i = 0; i < num_db; i++)
epf->db_msg[i].virq = msi_get_virq(epc->dev.parent, i);
return 0;
}
int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
{
struct pci_epc *epc = epf->epc;
struct device *dev = &epf->dev;
int ret;
/* TODO: Multi-EPF support */
if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) {
dev_err(dev, "Doorbell doesn't support multiple EPF\n");
return -EINVAL;
}
if (epf->db_msg)
return -EBUSY;
ret = pci_epf_alloc_doorbell_msi(epf, num_db);
if (!ret)
return 0;
/*
* Fall back to embedded doorbell only when platform MSI is unavailable
* for this EPC.
*/
if (ret != -ENODEV)
return ret;
ret = pci_epf_alloc_doorbell_embedded(epf, num_db);
if (ret) {
dev_err(dev, "Failed to allocate doorbell: %d\n", ret);
return ret;
}
dev_info(dev, "Using embedded (DMA) doorbell fallback\n");
return 0;
}
EXPORT_SYMBOL_GPL(pci_epf_alloc_doorbell);
void pci_epf_free_doorbell(struct pci_epf *epf)
{
struct pci_epf_doorbell_msg *msg0;
struct pci_epc *epc = epf->epc;
if (!epf->db_msg)
return;
msg0 = &epf->db_msg[0];
if (msg0->type == PCI_EPF_DOORBELL_MSI)
platform_device_msi_free_irqs_all(epf->epc->dev.parent);
else if (msg0->type == PCI_EPF_DOORBELL_EMBEDDED && msg0->iova_size)
dma_unmap_resource(epc->dev.parent, msg0->iova_base,
msg0->iova_size, DMA_FROM_DEVICE, 0);
kfree(epf->db_msg);
epf->db_msg = NULL;
epf->num_db = 0;
}
EXPORT_SYMBOL_GPL(pci_epf_free_doorbell);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Endpoint *Controller* Address Space Management
*
* Copyright (C) 2017 Texas Instruments
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pci-epc.h>
/**
* pci_epc_mem_get_order() - determine the allocation order of a memory size
* @mem: address space of the endpoint controller
* @size: the size for which to get the order
*
* Reimplement get_order() for mem->page_size since the generic get_order
* always gets order with a constant PAGE_SIZE.
*/
static int pci_epc_mem_get_order(struct pci_epc_mem *mem, size_t size)
{
int order;
unsigned int page_shift = ilog2(mem->window.page_size);
size--;
size >>= page_shift;
#if BITS_PER_LONG == 32
order = fls(size);
#else
order = fls64(size);
#endif
return order;
}
/**
* pci_epc_multi_mem_init() - initialize the pci_epc_mem structure
* @epc: the EPC device that invoked pci_epc_mem_init
* @windows: pointer to windows supported by the device
* @num_windows: number of windows device supports
*
* Invoke to initialize the pci_epc_mem structure used by the
* endpoint functions to allocate mapped PCI address.
*/
int pci_epc_multi_mem_init(struct pci_epc *epc,
struct pci_epc_mem_window *windows,
unsigned int num_windows)
{
struct pci_epc_mem *mem = NULL;
unsigned long *bitmap = NULL;
unsigned int page_shift;
size_t page_size;
int bitmap_size;
int pages;
int ret;
int i;
epc->num_windows = 0;
if (!windows || !num_windows)
return -EINVAL;
epc->windows = kzalloc_objs(*epc->windows, num_windows);
if (!epc->windows)
return -ENOMEM;
for (i = 0; i < num_windows; i++) {
page_size = windows[i].page_size;
if (page_size < PAGE_SIZE)
page_size = PAGE_SIZE;
page_shift = ilog2(page_size);
pages = windows[i].size >> page_shift;
bitmap_size = BITS_TO_LONGS(pages) * sizeof(long);
mem = kzalloc_obj(*mem);
if (!mem) {
ret = -ENOMEM;
i--;
goto err_mem;
}
bitmap = kzalloc(bitmap_size, GFP_KERNEL);
if (!bitmap) {
ret = -ENOMEM;
kfree(mem);
i--;
goto err_mem;
}
mem->window.phys_base = windows[i].phys_base;
mem->window.size = windows[i].size;
mem->window.page_size = page_size;
mem->bitmap = bitmap;
mem->pages = pages;
mutex_init(&mem->lock);
epc->windows[i] = mem;
}
epc->mem = epc->windows[0];
epc->num_windows = num_windows;
return 0;
err_mem:
for (; i >= 0; i--) {
mem = epc->windows[i];
kfree(mem->bitmap);
kfree(mem);
}
kfree(epc->windows);
return ret;
}
EXPORT_SYMBOL_GPL(pci_epc_multi_mem_init);
/**
* pci_epc_mem_init() - Initialize the pci_epc_mem structure
* @epc: the EPC device that invoked pci_epc_mem_init
* @base: Physical address of the window region
* @size: Total Size of the window region
* @page_size: Page size of the window region
*
* Invoke to initialize a single pci_epc_mem structure used by the
* endpoint functions to allocate memory for mapping the PCI host memory
*/
int pci_epc_mem_init(struct pci_epc *epc, phys_addr_t base,
size_t size, size_t page_size)
{
struct pci_epc_mem_window mem_window;
mem_window.phys_base = base;
mem_window.size = size;
mem_window.page_size = page_size;
return pci_epc_multi_mem_init(epc, &mem_window, 1);
}
EXPORT_SYMBOL_GPL(pci_epc_mem_init);
/**
* pci_epc_mem_exit() - cleanup the pci_epc_mem structure
* @epc: the EPC device that invoked pci_epc_mem_exit
*
* Invoke to cleanup the pci_epc_mem structure allocated in
* pci_epc_mem_init().
*/
void pci_epc_mem_exit(struct pci_epc *epc)
{
struct pci_epc_mem *mem;
int i;
if (!epc->num_windows)
return;
for (i = 0; i < epc->num_windows; i++) {
mem = epc->windows[i];
kfree(mem->bitmap);
kfree(mem);
}
kfree(epc->windows);
epc->windows = NULL;
epc->mem = NULL;
epc->num_windows = 0;
}
EXPORT_SYMBOL_GPL(pci_epc_mem_exit);
/**
* pci_epc_mem_alloc_addr() - allocate memory address from EPC addr space
* @epc: the EPC device on which memory has to be allocated
* @phys_addr: populate the allocated physical address here
* @size: the size of the address space that has to be allocated
*
* Invoke to allocate memory address from the EPC address space. This
* is usually done to map the remote RC address into the local system.
*/
void __iomem *pci_epc_mem_alloc_addr(struct pci_epc *epc,
phys_addr_t *phys_addr, size_t size)
{
void __iomem *virt_addr;
struct pci_epc_mem *mem;
unsigned int page_shift;
size_t align_size;
int pageno;
int order;
int i;
for (i = 0; i < epc->num_windows; i++) {
mem = epc->windows[i];
if (size > mem->window.size)
continue;
align_size = ALIGN(size, mem->window.page_size);
order = pci_epc_mem_get_order(mem, align_size);
mutex_lock(&mem->lock);
pageno = bitmap_find_free_region(mem->bitmap, mem->pages,
order);
if (pageno >= 0) {
page_shift = ilog2(mem->window.page_size);
*phys_addr = mem->window.phys_base +
((phys_addr_t)pageno << page_shift);
virt_addr = ioremap(*phys_addr, align_size);
if (!virt_addr) {
bitmap_release_region(mem->bitmap,
pageno, order);
mutex_unlock(&mem->lock);
continue;
}
mutex_unlock(&mem->lock);
return virt_addr;
}
mutex_unlock(&mem->lock);
}
return NULL;
}
EXPORT_SYMBOL_GPL(pci_epc_mem_alloc_addr);
static struct pci_epc_mem *pci_epc_get_matching_window(struct pci_epc *epc,
phys_addr_t phys_addr)
{
struct pci_epc_mem *mem;
int i;
for (i = 0; i < epc->num_windows; i++) {
mem = epc->windows[i];
if (phys_addr >= mem->window.phys_base &&
phys_addr < (mem->window.phys_base + mem->window.size))
return mem;
}
return NULL;
}
/**
* pci_epc_mem_free_addr() - free the allocated memory address
* @epc: the EPC device on which memory was allocated
* @phys_addr: the allocated physical address
* @virt_addr: virtual address of the allocated mem space
* @size: the size of the allocated address space
*
* Invoke to free the memory allocated using pci_epc_mem_alloc_addr.
*/
void pci_epc_mem_free_addr(struct pci_epc *epc, phys_addr_t phys_addr,
void __iomem *virt_addr, size_t size)
{
struct pci_epc_mem *mem;
unsigned int page_shift;
size_t page_size;
int pageno;
int order;
mem = pci_epc_get_matching_window(epc, phys_addr);
if (!mem) {
pr_err("failed to get matching window\n");
return;
}
page_size = mem->window.page_size;
page_shift = ilog2(page_size);
iounmap(virt_addr);
pageno = (phys_addr - mem->window.phys_base) >> page_shift;
size = ALIGN(size, page_size);
order = pci_epc_mem_get_order(mem, size);
mutex_lock(&mem->lock);
bitmap_release_region(mem->bitmap, pageno, order);
mutex_unlock(&mem->lock);
}
EXPORT_SYMBOL_GPL(pci_epc_mem_free_addr);
MODULE_DESCRIPTION("PCI EPC Address Space Management");
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
@@ -0,0 +1,699 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Endpoint *Function* (EPF) library
*
* Copyright (C) 2017 Texas Instruments
* Author: Kishon Vijay Abraham I <kishon@ti.com>
*/
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/pci-epc.h>
#include <linux/pci-epf.h>
#include <linux/pci-ep-cfs.h>
static DEFINE_MUTEX(pci_epf_mutex);
static const struct bus_type pci_epf_bus_type;
static const struct device_type pci_epf_type;
/**
* pci_epf_unbind() - Notify the function driver that the binding between the
* EPF device and EPC device has been lost
* @epf: the EPF device which has lost the binding with the EPC device
*
* Invoke to notify the function driver that the binding between the EPF device
* and EPC device has been lost.
*/
void pci_epf_unbind(struct pci_epf *epf)
{
struct pci_epf *epf_vf;
if (!epf->driver) {
dev_WARN(&epf->dev, "epf device not bound to driver\n");
return;
}
mutex_lock(&epf->lock);
list_for_each_entry(epf_vf, &epf->pci_vepf, list) {
if (epf_vf->is_bound)
epf_vf->driver->ops->unbind(epf_vf);
}
if (epf->is_bound)
epf->driver->ops->unbind(epf);
mutex_unlock(&epf->lock);
module_put(epf->driver->owner);
}
EXPORT_SYMBOL_GPL(pci_epf_unbind);
/**
* pci_epf_bind() - Notify the function driver that the EPF device has been
* bound to a EPC device
* @epf: the EPF device which has been bound to the EPC device
*
* Invoke to notify the function driver that it has been bound to a EPC device
*/
int pci_epf_bind(struct pci_epf *epf)
{
struct device *dev = &epf->dev;
struct pci_epf *epf_vf;
u8 func_no, vfunc_no;
struct pci_epc *epc;
int ret;
if (!epf->driver) {
dev_WARN(dev, "epf device not bound to driver\n");
return -EINVAL;
}
if (!try_module_get(epf->driver->owner))
return -EAGAIN;
mutex_lock(&epf->lock);
list_for_each_entry(epf_vf, &epf->pci_vepf, list) {
vfunc_no = epf_vf->vfunc_no;
if (vfunc_no < 1) {
dev_err(dev, "Invalid virtual function number\n");
ret = -EINVAL;
goto ret;
}
epc = epf->epc;
func_no = epf->func_no;
if (!IS_ERR_OR_NULL(epc)) {
if (!epc->max_vfs) {
dev_err(dev, "No support for virt function\n");
ret = -EINVAL;
goto ret;
}
if (vfunc_no > epc->max_vfs[func_no]) {
dev_err(dev, "PF%d: Exceeds max vfunc number\n",
func_no);
ret = -EINVAL;
goto ret;
}
}
epc = epf->sec_epc;
func_no = epf->sec_epc_func_no;
if (!IS_ERR_OR_NULL(epc)) {
if (!epc->max_vfs) {
dev_err(dev, "No support for virt function\n");
ret = -EINVAL;
goto ret;
}
if (vfunc_no > epc->max_vfs[func_no]) {
dev_err(dev, "PF%d: Exceeds max vfunc number\n",
func_no);
ret = -EINVAL;
goto ret;
}
}
epf_vf->func_no = epf->func_no;
epf_vf->sec_epc_func_no = epf->sec_epc_func_no;
epf_vf->epc = epf->epc;
epf_vf->sec_epc = epf->sec_epc;
ret = epf_vf->driver->ops->bind(epf_vf);
if (ret)
goto ret;
epf_vf->is_bound = true;
}
ret = epf->driver->ops->bind(epf);
if (ret)
goto ret;
epf->is_bound = true;
mutex_unlock(&epf->lock);
return 0;
ret:
mutex_unlock(&epf->lock);
pci_epf_unbind(epf);
return ret;
}
EXPORT_SYMBOL_GPL(pci_epf_bind);
/**
* pci_epf_add_vepf() - associate virtual EP function to physical EP function
* @epf_pf: the physical EP function to which the virtual EP function should be
* associated
* @epf_vf: the virtual EP function to be added
*
* A physical endpoint function can be associated with multiple virtual
* endpoint functions. Invoke pci_epf_add_vepf() to add a virtual PCI endpoint
* function to a physical PCI endpoint function.
*/
int pci_epf_add_vepf(struct pci_epf *epf_pf, struct pci_epf *epf_vf)
{
u32 vfunc_no;
if (IS_ERR_OR_NULL(epf_pf) || IS_ERR_OR_NULL(epf_vf))
return -EINVAL;
if (epf_pf->epc || epf_vf->epc || epf_vf->epf_pf)
return -EBUSY;
if (epf_pf->sec_epc || epf_vf->sec_epc)
return -EBUSY;
mutex_lock(&epf_pf->lock);
vfunc_no = find_first_zero_bit(&epf_pf->vfunction_num_map,
BITS_PER_LONG);
if (vfunc_no >= BITS_PER_LONG) {
mutex_unlock(&epf_pf->lock);
return -EINVAL;
}
set_bit(vfunc_no, &epf_pf->vfunction_num_map);
epf_vf->vfunc_no = vfunc_no;
epf_vf->epf_pf = epf_pf;
epf_vf->is_vf = true;
list_add_tail(&epf_vf->list, &epf_pf->pci_vepf);
mutex_unlock(&epf_pf->lock);
return 0;
}
EXPORT_SYMBOL_GPL(pci_epf_add_vepf);
/**
* pci_epf_remove_vepf() - remove virtual EP function from physical EP function
* @epf_pf: the physical EP function from which the virtual EP function should
* be removed
* @epf_vf: the virtual EP function to be removed
*
* Invoke to remove a virtual endpoint function from the physical endpoint
* function.
*/
void pci_epf_remove_vepf(struct pci_epf *epf_pf, struct pci_epf *epf_vf)
{
if (IS_ERR_OR_NULL(epf_pf) || IS_ERR_OR_NULL(epf_vf))
return;
mutex_lock(&epf_pf->lock);
clear_bit(epf_vf->vfunc_no, &epf_pf->vfunction_num_map);
epf_vf->epf_pf = NULL;
list_del(&epf_vf->list);
mutex_unlock(&epf_pf->lock);
}
EXPORT_SYMBOL_GPL(pci_epf_remove_vepf);
static int pci_epf_get_required_bar_size(struct pci_epf *epf, size_t *bar_size,
size_t *aligned_mem_size,
enum pci_barno bar,
const struct pci_epc_features *epc_features,
enum pci_epc_interface_type type)
{
u64 bar_fixed_size = epc_features->bar[bar].fixed_size;
size_t align = epc_features->align;
size_t size = *bar_size;
if (size < 128)
size = 128;
/* According to PCIe base spec, min size for a resizable BAR is 1 MB. */
if (epc_features->bar[bar].type == BAR_RESIZABLE && size < SZ_1M)
size = SZ_1M;
if (epc_features->bar[bar].type == BAR_FIXED && bar_fixed_size) {
if (size > bar_fixed_size) {
dev_err(&epf->dev,
"requested BAR size is larger than fixed size\n");
return -ENOMEM;
}
size = bar_fixed_size;
} else {
/* BAR size must be power of two */
size = roundup_pow_of_two(size);
}
*bar_size = size;
/*
* The EPC's BAR start address must meet alignment requirements. In most
* cases, the alignment will match the BAR size. However, differences
* can occur—for example, when the fixed BAR size (e.g., 128 bytes) is
* smaller than the required alignment (e.g., 4 KB).
*/
*aligned_mem_size = align ? ALIGN(size, align) : size;
return 0;
}
/**
* pci_epf_free_space() - free the allocated PCI EPF register space
* @epf: the EPF device from whom to free the memory
* @addr: the virtual address of the PCI EPF register space
* @bar: the BAR number corresponding to the register space
* @type: Identifies if the allocated space is for primary EPC or secondary EPC
*
* Invoke to free the allocated PCI EPF register space.
*/
void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar,
enum pci_epc_interface_type type)
{
struct device *dev;
struct pci_epf_bar *epf_bar;
struct pci_epc *epc;
if (!addr)
return;
if (type == PRIMARY_INTERFACE) {
epc = epf->epc;
epf_bar = epf->bar;
} else {
epc = epf->sec_epc;
epf_bar = epf->sec_epc_bar;
}
dev = epc->dev.parent;
dma_free_coherent(dev, epf_bar[bar].mem_size, addr,
epf_bar[bar].phys_addr);
epf_bar[bar].phys_addr = 0;
epf_bar[bar].addr = NULL;
epf_bar[bar].size = 0;
epf_bar[bar].mem_size = 0;
epf_bar[bar].barno = 0;
epf_bar[bar].flags = 0;
}
EXPORT_SYMBOL_GPL(pci_epf_free_space);
/**
* pci_epf_alloc_space() - allocate memory for the PCI EPF register space
* @epf: the EPF device to whom allocate the memory
* @size: the size of the memory that has to be allocated
* @bar: the BAR number corresponding to the allocated register space
* @epc_features: the features provided by the EPC specific to this EPF
* @type: Identifies if the allocation is for primary EPC or secondary EPC
*
* Invoke to allocate memory for the PCI EPF register space.
* Flag PCI_BASE_ADDRESS_MEM_TYPE_64 will automatically get set if the BAR
* can only be a 64-bit BAR, or if the requested size is larger than 2 GB.
*/
void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar,
const struct pci_epc_features *epc_features,
enum pci_epc_interface_type type)
{
struct pci_epf_bar *epf_bar;
dma_addr_t phys_addr;
struct pci_epc *epc;
struct device *dev;
size_t mem_size;
void *space;
if (pci_epf_get_required_bar_size(epf, &size, &mem_size, bar,
epc_features, type))
return NULL;
if (type == PRIMARY_INTERFACE) {
epc = epf->epc;
epf_bar = epf->bar;
} else {
epc = epf->sec_epc;
epf_bar = epf->sec_epc_bar;
}
dev = epc->dev.parent;
space = dma_alloc_coherent(dev, mem_size, &phys_addr, GFP_KERNEL);
if (!space) {
dev_err(dev, "failed to allocate mem space\n");
return NULL;
}
epf_bar[bar].phys_addr = phys_addr;
epf_bar[bar].addr = space;
epf_bar[bar].size = size;
epf_bar[bar].mem_size = mem_size;
epf_bar[bar].barno = bar;
if (upper_32_bits(size) || epc_features->bar[bar].only_64bit)
epf_bar[bar].flags |= PCI_BASE_ADDRESS_MEM_TYPE_64;
else
epf_bar[bar].flags |= PCI_BASE_ADDRESS_MEM_TYPE_32;
return space;
}
EXPORT_SYMBOL_GPL(pci_epf_alloc_space);
/**
* pci_epf_assign_bar_space() - Assign PCI EPF BAR space
* @epf: EPF device to assign the BAR memory
* @size: Size of the memory that has to be assigned
* @bar: BAR number for which the memory is assigned
* @epc_features: Features provided by the EPC specific to this EPF
* @type: Identifies if the assignment is for primary EPC or secondary EPC
* @bar_addr: Address to be assigned for the @bar
*
* Invoke to assign memory for the PCI EPF BAR.
* Flag PCI_BASE_ADDRESS_MEM_TYPE_64 will automatically get set if the BAR
* can only be a 64-bit BAR, or if the requested size is larger than 2 GB.
*/
int pci_epf_assign_bar_space(struct pci_epf *epf, size_t size,
enum pci_barno bar,
const struct pci_epc_features *epc_features,
enum pci_epc_interface_type type,
dma_addr_t bar_addr)
{
size_t bar_size, aligned_mem_size;
struct pci_epf_bar *epf_bar;
dma_addr_t limit;
int pos;
if (!size)
return -EINVAL;
limit = bar_addr + size - 1;
/*
* Bits: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
* bar_addr: U U U U U U 0 X X X X X X X X X
* limit: U U U U U U 1 X X X X X X X X X
*
* bar_addr^limit 0 0 0 0 0 0 1 X X X X X X X X X
*
* U: unchanged address bits in range [bar_addr, limit]
* X: bit 0 or 1
*
* (bar_addr^limit) & BIT_ULL(pos) will find the first set bit from MSB
* (pos). And value of (2 ^ pos) should be able to cover the BAR range.
*/
for (pos = 8 * sizeof(dma_addr_t) - 1; pos > 0; pos--)
if ((limit ^ bar_addr) & BIT_ULL(pos))
break;
if (pos == 8 * sizeof(dma_addr_t) - 1)
return -EINVAL;
bar_size = BIT_ULL(pos + 1);
if (pci_epf_get_required_bar_size(epf, &bar_size, &aligned_mem_size,
bar, epc_features, type))
return -ENOMEM;
if (type == PRIMARY_INTERFACE)
epf_bar = epf->bar;
else
epf_bar = epf->sec_epc_bar;
epf_bar[bar].phys_addr = ALIGN_DOWN(bar_addr, aligned_mem_size);
if (epf_bar[bar].phys_addr + bar_size < limit)
return -ENOMEM;
epf_bar[bar].addr = NULL;
epf_bar[bar].size = bar_size;
epf_bar[bar].mem_size = aligned_mem_size;
epf_bar[bar].barno = bar;
if (upper_32_bits(size) || epc_features->bar[bar].only_64bit)
epf_bar[bar].flags |= PCI_BASE_ADDRESS_MEM_TYPE_64;
else
epf_bar[bar].flags |= PCI_BASE_ADDRESS_MEM_TYPE_32;
return 0;
}
EXPORT_SYMBOL_GPL(pci_epf_assign_bar_space);
static void pci_epf_remove_cfs(struct pci_epf_driver *driver)
{
struct config_group *group, *tmp;
if (!IS_ENABLED(CONFIG_PCI_ENDPOINT_CONFIGFS))
return;
mutex_lock(&pci_epf_mutex);
list_for_each_entry_safe(group, tmp, &driver->epf_group, group_entry)
pci_ep_cfs_remove_epf_group(group);
WARN_ON(!list_empty(&driver->epf_group));
mutex_unlock(&pci_epf_mutex);
}
/**
* pci_epf_unregister_driver() - unregister the PCI EPF driver
* @driver: the PCI EPF driver that has to be unregistered
*
* Invoke to unregister the PCI EPF driver.
*/
void pci_epf_unregister_driver(struct pci_epf_driver *driver)
{
pci_epf_remove_cfs(driver);
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL_GPL(pci_epf_unregister_driver);
static int pci_epf_add_cfs(struct pci_epf_driver *driver)
{
struct config_group *group;
const struct pci_epf_device_id *id;
if (!IS_ENABLED(CONFIG_PCI_ENDPOINT_CONFIGFS))
return 0;
INIT_LIST_HEAD(&driver->epf_group);
id = driver->id_table;
while (id->name[0]) {
group = pci_ep_cfs_add_epf_group(id->name);
if (IS_ERR(group)) {
pci_epf_remove_cfs(driver);
return PTR_ERR(group);
}
mutex_lock(&pci_epf_mutex);
list_add_tail(&group->group_entry, &driver->epf_group);
mutex_unlock(&pci_epf_mutex);
id++;
}
return 0;
}
/**
* __pci_epf_register_driver() - register a new PCI EPF driver
* @driver: structure representing PCI EPF driver
* @owner: the owner of the module that registers the PCI EPF driver
*
* Invoke to register a new PCI EPF driver.
*/
int __pci_epf_register_driver(struct pci_epf_driver *driver,
struct module *owner)
{
int ret;
if (!driver->ops)
return -EINVAL;
if (!driver->ops->bind || !driver->ops->unbind)
return -EINVAL;
driver->driver.bus = &pci_epf_bus_type;
driver->driver.owner = owner;
ret = driver_register(&driver->driver);
if (ret)
return ret;
pci_epf_add_cfs(driver);
return 0;
}
EXPORT_SYMBOL_GPL(__pci_epf_register_driver);
/**
* pci_epf_destroy() - destroy the created PCI EPF device
* @epf: the PCI EPF device that has to be destroyed.
*
* Invoke to destroy the PCI EPF device created by invoking pci_epf_create().
*/
void pci_epf_destroy(struct pci_epf *epf)
{
device_unregister(&epf->dev);
}
EXPORT_SYMBOL_GPL(pci_epf_destroy);
/**
* pci_epf_create() - create a new PCI EPF device
* @name: the name of the PCI EPF device. This name will be used to bind the
* EPF device to a EPF driver
*
* Invoke to create a new PCI EPF device by providing the name of the function
* device.
*/
struct pci_epf *pci_epf_create(const char *name)
{
int ret;
struct pci_epf *epf;
struct device *dev;
int len;
epf = kzalloc_obj(*epf);
if (!epf)
return ERR_PTR(-ENOMEM);
len = strchrnul(name, '.') - name;
epf->name = kstrndup(name, len, GFP_KERNEL);
if (!epf->name) {
kfree(epf);
return ERR_PTR(-ENOMEM);
}
/* VFs are numbered starting with 1. So set BIT(0) by default */
epf->vfunction_num_map = 1;
INIT_LIST_HEAD(&epf->pci_vepf);
dev = &epf->dev;
device_initialize(dev);
dev->bus = &pci_epf_bus_type;
dev->type = &pci_epf_type;
mutex_init(&epf->lock);
ret = dev_set_name(dev, "%s", name);
if (ret) {
put_device(dev);
return ERR_PTR(ret);
}
ret = device_add(dev);
if (ret) {
put_device(dev);
return ERR_PTR(ret);
}
return epf;
}
EXPORT_SYMBOL_GPL(pci_epf_create);
/**
* pci_epf_align_inbound_addr() - Align the given address based on the BAR
* alignment requirement
* @epf: the EPF device
* @addr: inbound address to be aligned
* @bar: the BAR number corresponding to the given addr
* @base: base address matching the @bar alignment requirement
* @off: offset to be added to the @base address
*
* Helper function to align input @addr based on BAR's alignment requirement.
* The aligned base address and offset are returned via @base and @off.
*
* NOTE: The pci_epf_alloc_space() function already accounts for alignment.
* This API is primarily intended for use with other memory regions not
* allocated by pci_epf_alloc_space(), such as peripheral register spaces or
* the message address of a platform MSI controller.
*
* Return: 0 on success, errno otherwise.
*/
int pci_epf_align_inbound_addr(struct pci_epf *epf, enum pci_barno bar,
u64 addr, dma_addr_t *base, size_t *off)
{
/*
* Most EP controllers require the BAR start address to be aligned to
* the BAR size, because they mask off the lower bits.
*
* Alignment to BAR size also works for controllers that support
* unaligned addresses.
*/
u64 align = epf->bar[bar].size;
*base = round_down(addr, align);
*off = addr & (align - 1);
return 0;
}
EXPORT_SYMBOL_GPL(pci_epf_align_inbound_addr);
static void pci_epf_dev_release(struct device *dev)
{
struct pci_epf *epf = to_pci_epf(dev);
kfree(epf->name);
kfree(epf);
}
static const struct device_type pci_epf_type = {
.release = pci_epf_dev_release,
};
static const struct pci_epf_device_id *
pci_epf_match_id(const struct pci_epf_device_id *id, const struct pci_epf *epf)
{
while (id->name[0]) {
if (strcmp(epf->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
static int pci_epf_device_match(struct device *dev, const struct device_driver *drv)
{
struct pci_epf *epf = to_pci_epf(dev);
const struct pci_epf_driver *driver = to_pci_epf_driver(drv);
if (driver->id_table)
return !!pci_epf_match_id(driver->id_table, epf);
return !strcmp(epf->name, drv->name);
}
static int pci_epf_device_probe(struct device *dev)
{
struct pci_epf *epf = to_pci_epf(dev);
struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver);
if (!driver->probe)
return -ENODEV;
epf->driver = driver;
return driver->probe(epf, pci_epf_match_id(driver->id_table, epf));
}
static void pci_epf_device_remove(struct device *dev)
{
struct pci_epf *epf = to_pci_epf(dev);
struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver);
if (driver->remove)
driver->remove(epf);
epf->driver = NULL;
}
static const struct bus_type pci_epf_bus_type = {
.name = "pci-epf",
.match = pci_epf_device_match,
.probe = pci_epf_device_probe,
.remove = pci_epf_device_remove,
};
static int __init pci_epf_init(void)
{
int ret;
ret = bus_register(&pci_epf_bus_type);
if (ret) {
pr_err("failed to register pci epf bus --> %d\n", ret);
return ret;
}
return 0;
}
module_init(pci_epf_init);
static void __exit pci_epf_exit(void)
{
bus_unregister(&pci_epf_bus_type);
}
module_exit(pci_epf_exit);
MODULE_DESCRIPTION("PCI EPF Library");
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");