Skip to content

Commit 27a6dea

Browse files
committed
genirq/msi: Provide msi_create/free_device_irq_domain()
Now that all prerequsites are in place, provide the actual interfaces for creating and removing per device interrupt domains. MSI device interrupt domains are created from the provided msi_domain_template which is duplicated so that it can be modified for the particular device. The name of the domain and the name of the interrupt chip are composed by "$(PREFIX)$(CHIPNAME)-$(DEVNAME)" $PREFIX: The optional prefix provided by the underlying MSI parent domain via msi_parent_ops::prefix. $CHIPNAME: The name of the irq_chip in the template $DEVNAME: The name of the device The domain is further initialized through a MSI parent domain callback which fills in the required functionality for the parent domain or domains further down the hierarchy. This initialization can fail, e.g. when the requested feature or MSI domain type cannot be supported. The domain pointer is stored in the pointer array inside of msi_device_data which is attached to the domain. The domain can be removed via the API or left for disposal via devres when the device is torn down. The API removal is useful e.g. for PCI to have seperate domains for MSI and MSI-X, which are mutually exclusive and always occupy the default domain id slot. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Kevin Tian <kevin.tian@intel.com> Acked-by: Marc Zyngier <maz@kernel.org> Link: https://lore.kernel.org/r/20221124232325.678838546@linutronix.de
1 parent 4443664 commit 27a6dea

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

include/linux/msi.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,12 @@ struct irq_domain *msi_create_irq_domain(struct fwnode_handle *fwnode,
547547
struct msi_domain_info *info,
548548
struct irq_domain *parent);
549549

550+
bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
551+
const struct msi_domain_template *template,
552+
unsigned int hwsize, void *domain_data,
553+
void *chip_data);
554+
void msi_remove_device_irq_domain(struct device *dev, unsigned int domid);
555+
550556
int msi_domain_alloc_irqs_range_locked(struct device *dev, unsigned int domid,
551557
unsigned int first, unsigned int last);
552558
int msi_domain_alloc_irqs_range(struct device *dev, unsigned int domid,

kernel/irq/msi.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ static void msi_device_data_release(struct device *dev, void *res)
240240
int i;
241241

242242
for (i = 0; i < MSI_MAX_DEVICE_IRQDOMAINS; i++) {
243+
msi_remove_device_irq_domain(dev, i);
243244
WARN_ON_ONCE(!xa_empty(&md->__domains[i].store));
244245
xa_destroy(&md->__domains[i].store);
245246
}
@@ -848,6 +849,143 @@ bool msi_parent_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
848849
msi_child_info);
849850
}
850851

852+
/**
853+
* msi_create_device_irq_domain - Create a device MSI interrupt domain
854+
* @dev: Pointer to the device
855+
* @domid: Domain id
856+
* @template: MSI domain info bundle used as template
857+
* @hwsize: Maximum number of MSI table entries (0 if unknown or unlimited)
858+
* @domain_data: Optional pointer to domain specific data which is set in
859+
* msi_domain_info::data
860+
* @chip_data: Optional pointer to chip specific data which is set in
861+
* msi_domain_info::chip_data
862+
*
863+
* Return: True on success, false otherwise
864+
*
865+
* There is no firmware node required for this interface because the per
866+
* device domains are software constructs which are actually closer to the
867+
* hardware reality than any firmware can describe them.
868+
*
869+
* The domain name and the irq chip name for a MSI device domain are
870+
* composed by: "$(PREFIX)$(CHIPNAME)-$(DEVNAME)"
871+
*
872+
* $PREFIX: Optional prefix provided by the underlying MSI parent domain
873+
* via msi_parent_ops::prefix. If that pointer is NULL the prefix
874+
* is empty.
875+
* $CHIPNAME: The name of the irq_chip in @template
876+
* $DEVNAME: The name of the device
877+
*
878+
* This results in understandable chip names and hardware interrupt numbers
879+
* in e.g. /proc/interrupts
880+
*
881+
* PCI-MSI-0000:00:1c.0 0-edge Parent domain has no prefix
882+
* IR-PCI-MSI-0000:00:1c.4 0-edge Same with interrupt remapping prefix 'IR-'
883+
*
884+
* IR-PCI-MSIX-0000:3d:00.0 0-edge Hardware interrupt numbers reflect
885+
* IR-PCI-MSIX-0000:3d:00.0 1-edge the real MSI-X index on that device
886+
* IR-PCI-MSIX-0000:3d:00.0 2-edge
887+
*
888+
* On IMS domains the hardware interrupt number is either a table entry
889+
* index or a purely software managed index but it is guaranteed to be
890+
* unique.
891+
*
892+
* The domain pointer is stored in @dev::msi::data::__irqdomains[]. All
893+
* subsequent operations on the domain depend on the domain id.
894+
*
895+
* The domain is automatically freed when the device is removed via devres
896+
* in the context of @dev::msi::data freeing, but it can also be
897+
* independently removed via @msi_remove_device_irq_domain().
898+
*/
899+
bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
900+
const struct msi_domain_template *template,
901+
unsigned int hwsize, void *domain_data,
902+
void *chip_data)
903+
{
904+
struct irq_domain *domain, *parent = dev->msi.domain;
905+
const struct msi_parent_ops *pops;
906+
struct msi_domain_template *bundle;
907+
struct fwnode_handle *fwnode;
908+
909+
if (!irq_domain_is_msi_parent(parent))
910+
return false;
911+
912+
if (domid >= MSI_MAX_DEVICE_IRQDOMAINS)
913+
return false;
914+
915+
bundle = kmemdup(template, sizeof(*bundle), GFP_KERNEL);
916+
if (!bundle)
917+
return false;
918+
919+
bundle->info.hwsize = hwsize;
920+
bundle->info.chip = &bundle->chip;
921+
bundle->info.ops = &bundle->ops;
922+
bundle->info.data = domain_data;
923+
bundle->info.chip_data = chip_data;
924+
925+
pops = parent->msi_parent_ops;
926+
snprintf(bundle->name, sizeof(bundle->name), "%s%s-%s",
927+
pops->prefix ? : "", bundle->chip.name, dev_name(dev));
928+
bundle->chip.name = bundle->name;
929+
930+
fwnode = irq_domain_alloc_named_fwnode(bundle->name);
931+
if (!fwnode)
932+
goto free_bundle;
933+
934+
if (msi_setup_device_data(dev))
935+
goto free_fwnode;
936+
937+
msi_lock_descs(dev);
938+
939+
if (WARN_ON_ONCE(msi_get_device_domain(dev, domid)))
940+
goto fail;
941+
942+
if (!pops->init_dev_msi_info(dev, parent, parent, &bundle->info))
943+
goto fail;
944+
945+
domain = __msi_create_irq_domain(fwnode, &bundle->info, IRQ_DOMAIN_FLAG_MSI_DEVICE, parent);
946+
if (!domain)
947+
goto fail;
948+
949+
domain->dev = dev;
950+
dev->msi.data->__domains[domid].domain = domain;
951+
msi_unlock_descs(dev);
952+
return true;
953+
954+
fail:
955+
msi_unlock_descs(dev);
956+
free_fwnode:
957+
kfree(fwnode);
958+
free_bundle:
959+
kfree(bundle);
960+
return false;
961+
}
962+
963+
/**
964+
* msi_remove_device_irq_domain - Free a device MSI interrupt domain
965+
* @dev: Pointer to the device
966+
* @domid: Domain id
967+
*/
968+
void msi_remove_device_irq_domain(struct device *dev, unsigned int domid)
969+
{
970+
struct msi_domain_info *info;
971+
struct irq_domain *domain;
972+
973+
msi_lock_descs(dev);
974+
975+
domain = msi_get_device_domain(dev, domid);
976+
977+
if (!domain || !irq_domain_is_msi_device(domain))
978+
goto unlock;
979+
980+
dev->msi.data->__domains[domid].domain = NULL;
981+
info = domain->host_data;
982+
irq_domain_remove(domain);
983+
kfree(container_of(info, struct msi_domain_template, info));
984+
985+
unlock:
986+
msi_unlock_descs(dev);
987+
}
988+
851989
int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
852990
int nvec, msi_alloc_info_t *arg)
853991
{

0 commit comments

Comments
 (0)