mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 00:28:52 +02:00 
			
		
		
		
	genirq: Add generic msi irq domain support
Implement the basic functions for MSI interrupt support with hierarchical interrupt domains. [ tglx: Extracted and combined from several patches ] Signed-off-by: Jiang Liu <jiang.liu@linux.intel.com> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: Grant Likely <grant.likely@linaro.org> Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: Yingjoe Chen <yingjoe.chen@mediatek.com> Cc: Yijing Wang <wangyijing@huawei.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
This commit is contained in:
		
							parent
							
								
									9dde55b72d
								
							
						
					
					
						commit
						f3cf8bb0d6
					
				
					 4 changed files with 197 additions and 0 deletions
				
			
		|  | @ -114,4 +114,49 @@ struct msi_controller { | |||
| 	void (*teardown_irq)(struct msi_controller *chip, unsigned int irq); | ||||
| }; | ||||
| 
 | ||||
| #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN | ||||
| struct irq_domain; | ||||
| struct irq_chip; | ||||
| struct device_node; | ||||
| struct msi_domain_info; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct msi_domain_ops - MSI interrupt domain callbacks | ||||
|  * @get_hwirq:		Retrieve the resulting hw irq number | ||||
|  * @msi_init:		Domain specific init function for MSI interrupts | ||||
|  * @msi_free:		Domain specific function to free a MSI interrupts | ||||
|  */ | ||||
| struct msi_domain_ops { | ||||
| 	irq_hw_number_t	(*get_hwirq)(struct msi_domain_info *info, void *arg); | ||||
| 	int		(*msi_init)(struct irq_domain *domain, | ||||
| 				    struct msi_domain_info *info, | ||||
| 				    unsigned int virq, irq_hw_number_t hwirq, | ||||
| 				    void *arg); | ||||
| 	void		(*msi_free)(struct irq_domain *domain, | ||||
| 				    struct msi_domain_info *info, | ||||
| 				    unsigned int virq); | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct msi_domain_info - MSI interrupt domain data | ||||
|  * @ops:	The callback data structure | ||||
|  * @chip:	The associated interrupt chip | ||||
|  * @data:	Domain specific data | ||||
|  */ | ||||
| struct msi_domain_info { | ||||
| 	struct msi_domain_ops	*ops; | ||||
| 	struct irq_chip		*chip; | ||||
| 	void			*data; | ||||
| }; | ||||
| 
 | ||||
| int msi_domain_set_affinity(struct irq_data *data, const struct cpumask *mask, | ||||
| 			    bool force); | ||||
| 
 | ||||
| struct irq_domain *msi_create_irq_domain(struct device_node *of_node, | ||||
| 					 struct msi_domain_info *info, | ||||
| 					 struct irq_domain *parent); | ||||
| struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain); | ||||
| 
 | ||||
| #endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */ | ||||
| 
 | ||||
| #endif /* LINUX_MSI_H */ | ||||
|  |  | |||
|  | @ -60,6 +60,16 @@ config IRQ_DOMAIN_HIERARCHY | |||
| 	bool | ||||
| 	select IRQ_DOMAIN | ||||
| 
 | ||||
| # Generic MSI interrupt support | ||||
| config GENERIC_MSI_IRQ | ||||
| 	bool | ||||
| 
 | ||||
| # Generic MSI hierarchical interrupt domain support | ||||
| config GENERIC_MSI_IRQ_DOMAIN | ||||
| 	bool | ||||
| 	select IRQ_DOMAIN_HIERARCHY | ||||
| 	select GENERIC_MSI_IRQ | ||||
| 
 | ||||
| config HANDLE_DOMAIN_IRQ | ||||
| 	bool | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,3 +6,4 @@ obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o | |||
| obj-$(CONFIG_PROC_FS) += proc.o | ||||
| obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o | ||||
| obj-$(CONFIG_PM_SLEEP) += pm.o | ||||
| obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o | ||||
|  |  | |||
							
								
								
									
										141
									
								
								kernel/irq/msi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								kernel/irq/msi.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | |||
| /*
 | ||||
|  * linux/kernel/irq/msi.c | ||||
|  * | ||||
|  * Copyright (C) 2014 Intel Corp. | ||||
|  * Author: Jiang Liu <jiang.liu@linux.intel.com> | ||||
|  * | ||||
|  * This file is licensed under GPLv2. | ||||
|  * | ||||
|  * This file contains common code to support Message Signalled Interrupt for | ||||
|  * PCI compatible and non PCI compatible devices. | ||||
|  */ | ||||
| #include <linux/irq.h> | ||||
| #include <linux/irqdomain.h> | ||||
| #include <linux/msi.h> | ||||
| 
 | ||||
| #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN | ||||
| /**
 | ||||
|  * msi_domain_set_affinity - Generic affinity setter function for MSI domains | ||||
|  * @irq_data:	The irq data associated to the interrupt | ||||
|  * @mask:	The affinity mask to set | ||||
|  * @force:	Flag to enforce setting (disable online checks) | ||||
|  * | ||||
|  * Intended to be used by MSI interrupt controllers which are | ||||
|  * implemented with hierarchical domains. | ||||
|  */ | ||||
| int msi_domain_set_affinity(struct irq_data *irq_data, | ||||
| 			    const struct cpumask *mask, bool force) | ||||
| { | ||||
| 	struct irq_data *parent = irq_data->parent_data; | ||||
| 	struct msi_msg msg; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = parent->chip->irq_set_affinity(parent, mask, force); | ||||
| 	if (ret >= 0 && ret != IRQ_SET_MASK_OK_DONE) { | ||||
| 		BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); | ||||
| 		irq_chip_write_msi_msg(irq_data, &msg); | ||||
| 	} | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static void msi_domain_activate(struct irq_domain *domain, | ||||
| 				struct irq_data *irq_data) | ||||
| { | ||||
| 	struct msi_msg msg; | ||||
| 
 | ||||
| 	BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); | ||||
| 	irq_chip_write_msi_msg(irq_data, &msg); | ||||
| } | ||||
| 
 | ||||
| static void msi_domain_deactivate(struct irq_domain *domain, | ||||
| 				  struct irq_data *irq_data) | ||||
| { | ||||
| 	struct msi_msg msg; | ||||
| 
 | ||||
| 	memset(&msg, 0, sizeof(msg)); | ||||
| 	irq_chip_write_msi_msg(irq_data, &msg); | ||||
| } | ||||
| 
 | ||||
| static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq, | ||||
| 			    unsigned int nr_irqs, void *arg) | ||||
| { | ||||
| 	struct msi_domain_info *info = domain->host_data; | ||||
| 	struct msi_domain_ops *ops = info->ops; | ||||
| 	irq_hw_number_t hwirq = ops->get_hwirq(info, arg); | ||||
| 	int i, ret; | ||||
| 
 | ||||
| 	if (irq_find_mapping(domain, hwirq) > 0) | ||||
| 		return -EEXIST; | ||||
| 
 | ||||
| 	ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	for (i = 0; i < nr_irqs; i++) { | ||||
| 		ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg); | ||||
| 		if (ret < 0) { | ||||
| 			if (ops->msi_free) { | ||||
| 				for (i--; i > 0; i--) | ||||
| 					ops->msi_free(domain, info, virq + i); | ||||
| 			} | ||||
| 			irq_domain_free_irqs_top(domain, virq, nr_irqs); | ||||
| 			return ret; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void msi_domain_free(struct irq_domain *domain, unsigned int virq, | ||||
| 			    unsigned int nr_irqs) | ||||
| { | ||||
| 	struct msi_domain_info *info = domain->host_data; | ||||
| 	int i; | ||||
| 
 | ||||
| 	if (info->ops->msi_free) { | ||||
| 		for (i = 0; i < nr_irqs; i++) | ||||
| 			info->ops->msi_free(domain, info, virq + i); | ||||
| 	} | ||||
| 	irq_domain_free_irqs_top(domain, virq, nr_irqs); | ||||
| } | ||||
| 
 | ||||
| static struct irq_domain_ops msi_domain_ops = { | ||||
| 	.alloc		= msi_domain_alloc, | ||||
| 	.free		= msi_domain_free, | ||||
| 	.activate	= msi_domain_activate, | ||||
| 	.deactivate	= msi_domain_deactivate, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * msi_create_irq_domain - Create a MSI interrupt domain | ||||
|  * @of_node:	Optional device-tree node of the interrupt controller | ||||
|  * @info:	MSI domain info | ||||
|  * @parent:	Parent irq domain | ||||
|  */ | ||||
| struct irq_domain *msi_create_irq_domain(struct device_node *of_node, | ||||
| 					 struct msi_domain_info *info, | ||||
| 					 struct irq_domain *parent) | ||||
| { | ||||
| 	struct irq_domain *domain; | ||||
| 
 | ||||
| 	domain = irq_domain_add_tree(of_node, &msi_domain_ops, info); | ||||
| 	if (domain) | ||||
| 		domain->parent = parent; | ||||
| 
 | ||||
| 	return domain; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * msi_get_domain_info - Get the MSI interrupt domain info for @domain | ||||
|  * @domain:	The interrupt domain to retrieve data from | ||||
|  * | ||||
|  * Returns the pointer to the msi_domain_info stored in | ||||
|  * @domain->host_data. | ||||
|  */ | ||||
| struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain) | ||||
| { | ||||
| 	return (struct msi_domain_info *)domain->host_data; | ||||
| } | ||||
| 
 | ||||
| #endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */ | ||||
		Loading…
	
		Reference in a new issue
	
	 Jiang Liu
						Jiang Liu