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); | 	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 */ | #endif /* LINUX_MSI_H */ | ||||||
|  |  | ||||||
|  | @ -60,6 +60,16 @@ config IRQ_DOMAIN_HIERARCHY | ||||||
| 	bool | 	bool | ||||||
| 	select IRQ_DOMAIN | 	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 | config HANDLE_DOMAIN_IRQ | ||||||
| 	bool | 	bool | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,3 +6,4 @@ obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o | ||||||
| obj-$(CONFIG_PROC_FS) += proc.o | obj-$(CONFIG_PROC_FS) += proc.o | ||||||
| obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o | obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o | ||||||
| obj-$(CONFIG_PM_SLEEP) += pm.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