#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;
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;
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;
}
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;
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;
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);