diff options
Diffstat (limited to 'drivers/base/platform-msi.c')
| -rw-r--r-- | drivers/base/platform-msi.c | 254 | 
1 files changed, 200 insertions, 54 deletions
diff --git a/drivers/base/platform-msi.c b/drivers/base/platform-msi.c index 5df4575b5ba7..47c43386786b 100644 --- a/drivers/base/platform-msi.c +++ b/drivers/base/platform-msi.c @@ -24,13 +24,17 @@  #include <linux/msi.h>  #include <linux/slab.h> -#define DEV_ID_SHIFT	24 +#define DEV_ID_SHIFT	21 +#define MAX_DEV_MSIS	(1 << (32 - DEV_ID_SHIFT))  /*   * Internal data structure containing a (made up, but unique) devid   * and the callback to write the MSI message.   */  struct platform_msi_priv_data { +	struct device		*dev; +	void 			*host_data; +	msi_alloc_info_t	arg;  	irq_write_msi_msg_t	write_msg;  	int			devid;  }; @@ -110,39 +114,49 @@ static void platform_msi_update_chip_ops(struct msi_domain_info *info)  		chip->irq_write_msi_msg = platform_msi_write_msg;  } -static void platform_msi_free_descs(struct device *dev) +static void platform_msi_free_descs(struct device *dev, int base, int nvec)  {  	struct msi_desc *desc, *tmp;  	list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { -		list_del(&desc->list); -		free_msi_entry(desc); +		if (desc->platform.msi_index >= base && +		    desc->platform.msi_index < (base + nvec)) { +			list_del(&desc->list); +			free_msi_entry(desc); +		}  	}  } -static int platform_msi_alloc_descs(struct device *dev, int nvec, -				    struct platform_msi_priv_data *data) +static int platform_msi_alloc_descs_with_irq(struct device *dev, int virq, +					     int nvec, +					     struct platform_msi_priv_data *data)  { -	int i; +	struct msi_desc *desc; +	int i, base = 0; -	for (i = 0; i < nvec; i++) { -		struct msi_desc *desc; +	if (!list_empty(dev_to_msi_list(dev))) { +		desc = list_last_entry(dev_to_msi_list(dev), +				       struct msi_desc, list); +		base = desc->platform.msi_index + 1; +	} +	for (i = 0; i < nvec; i++) {  		desc = alloc_msi_entry(dev);  		if (!desc)  			break;  		desc->platform.msi_priv_data = data; -		desc->platform.msi_index = i; +		desc->platform.msi_index = base + i;  		desc->nvec_used = 1; +		desc->irq = virq ? virq + i : 0;  		list_add_tail(&desc->list, dev_to_msi_list(dev));  	}  	if (i != nvec) {  		/* Clean up the mess */ -		platform_msi_free_descs(dev); +		platform_msi_free_descs(dev, base, nvec);  		return -ENOMEM;  	} @@ -150,6 +164,13 @@ static int platform_msi_alloc_descs(struct device *dev, int nvec,  	return 0;  } +static int platform_msi_alloc_descs(struct device *dev, int nvec, +				    struct platform_msi_priv_data *data) + +{ +	return platform_msi_alloc_descs_with_irq(dev, 0, nvec, data); +} +  /**   * platform_msi_create_irq_domain - Create a platform MSI interrupt domain   * @fwnode:		Optional fwnode of the interrupt controller @@ -180,56 +201,75 @@ struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,  	return domain;  } -/** - * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev - * @dev:		The device for which to allocate interrupts - * @nvec:		The number of interrupts to allocate - * @write_msi_msg:	Callback to write an interrupt message for @dev - * - * Returns: - * Zero for success, or an error code in case of failure - */ -int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, -				   irq_write_msi_msg_t write_msi_msg) +static struct platform_msi_priv_data * +platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, +			     irq_write_msi_msg_t write_msi_msg)  { -	struct platform_msi_priv_data *priv_data; -	int err; - +	struct platform_msi_priv_data *datap;  	/*  	 * Limit the number of interrupts to 256 per device. Should we  	 * need to bump this up, DEV_ID_SHIFT should be adjusted  	 * accordingly (which would impact the max number of MSI  	 * capable devices).  	 */ -	if (!dev->msi_domain || !write_msi_msg || !nvec || -	    nvec > (1 << (32 - DEV_ID_SHIFT))) -		return -EINVAL; +	if (!dev->msi_domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) +		return ERR_PTR(-EINVAL);  	if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {  		dev_err(dev, "Incompatible msi_domain, giving up\n"); -		return -EINVAL; +		return ERR_PTR(-EINVAL);  	}  	/* Already had a helping of MSI? Greed... */  	if (!list_empty(dev_to_msi_list(dev))) -		return -EBUSY; +		return ERR_PTR(-EBUSY); -	priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); -	if (!priv_data) -		return -ENOMEM; +	datap = kzalloc(sizeof(*datap), GFP_KERNEL); +	if (!datap) +		return ERR_PTR(-ENOMEM); -	priv_data->devid = ida_simple_get(&platform_msi_devid_ida, -					  0, 1 << DEV_ID_SHIFT, GFP_KERNEL); -	if (priv_data->devid < 0) { -		err = priv_data->devid; -		goto out_free_data; +	datap->devid = ida_simple_get(&platform_msi_devid_ida, +				      0, 1 << DEV_ID_SHIFT, GFP_KERNEL); +	if (datap->devid < 0) { +		int err = datap->devid; +		kfree(datap); +		return ERR_PTR(err);  	} -	priv_data->write_msg = write_msi_msg; +	datap->write_msg = write_msi_msg; +	datap->dev = dev; + +	return datap; +} + +static void platform_msi_free_priv_data(struct platform_msi_priv_data *data) +{ +	ida_simple_remove(&platform_msi_devid_ida, data->devid); +	kfree(data); +} + +/** + * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev + * @dev:		The device for which to allocate interrupts + * @nvec:		The number of interrupts to allocate + * @write_msi_msg:	Callback to write an interrupt message for @dev + * + * Returns: + * Zero for success, or an error code in case of failure + */ +int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, +				   irq_write_msi_msg_t write_msi_msg) +{ +	struct platform_msi_priv_data *priv_data; +	int err; + +	priv_data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); +	if (IS_ERR(priv_data)) +		return PTR_ERR(priv_data);  	err = platform_msi_alloc_descs(dev, nvec, priv_data);  	if (err) -		goto out_free_id; +		goto out_free_priv_data;  	err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec);  	if (err) @@ -238,11 +278,9 @@ int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,  	return 0;  out_free_desc: -	platform_msi_free_descs(dev); -out_free_id: -	ida_simple_remove(&platform_msi_devid_ida, priv_data->devid); -out_free_data: -	kfree(priv_data); +	platform_msi_free_descs(dev, 0, nvec); +out_free_priv_data: +	platform_msi_free_priv_data(priv_data);  	return err;  } @@ -253,18 +291,126 @@ out_free_data:   */  void platform_msi_domain_free_irqs(struct device *dev)  { -	struct msi_desc *desc; +	if (!list_empty(dev_to_msi_list(dev))) { +		struct msi_desc *desc; + +		desc = first_msi_entry(dev); +		platform_msi_free_priv_data(desc->platform.msi_priv_data); +	} + +	msi_domain_free_irqs(dev->msi_domain, dev); +	platform_msi_free_descs(dev, 0, MAX_DEV_MSIS); +} + +/** + * platform_msi_get_host_data - Query the private data associated with + *                              a platform-msi domain + * @domain:	The platform-msi domain + * + * Returns the private data provided when calling + * platform_msi_create_device_domain. + */ +void *platform_msi_get_host_data(struct irq_domain *domain) +{ +	struct platform_msi_priv_data *data = domain->host_data; +	return data->host_data; +} + +/** + * platform_msi_create_device_domain - Create a platform-msi domain + * + * @dev:		The device generating the MSIs + * @nvec:		The number of MSIs that need to be allocated + * @write_msi_msg:	Callback to write an interrupt message for @dev + * @ops:		The hierarchy domain operations to use + * @host_data:		Private data associated to this domain + * + * Returns an irqdomain for @nvec interrupts + */ +struct irq_domain * +platform_msi_create_device_domain(struct device *dev, +				  unsigned int nvec, +				  irq_write_msi_msg_t write_msi_msg, +				  const struct irq_domain_ops *ops, +				  void *host_data) +{ +	struct platform_msi_priv_data *data; +	struct irq_domain *domain; +	int err; + +	data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); +	if (IS_ERR(data)) +		return NULL; + +	data->host_data = host_data; +	domain = irq_domain_create_hierarchy(dev->msi_domain, 0, nvec, +					     of_node_to_fwnode(dev->of_node), +					     ops, data); +	if (!domain) +		goto free_priv; -	desc = first_msi_entry(dev); -	if (desc) { -		struct platform_msi_priv_data *data; +	err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg); +	if (err) +		goto free_domain; + +	return domain; -		data = desc->platform.msi_priv_data; +free_domain: +	irq_domain_remove(domain); +free_priv: +	platform_msi_free_priv_data(data); +	return NULL; +} + +/** + * platform_msi_domain_free - Free interrupts associated with a platform-msi + *                            domain + * + * @domain:	The platform-msi domain + * @virq:	The base irq from which to perform the free operation + * @nvec:	How many interrupts to free from @virq + */ +void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq, +			      unsigned int nvec) +{ +	struct platform_msi_priv_data *data = domain->host_data; +	struct msi_desc *desc; +	for_each_msi_entry(desc, data->dev) { +		if (WARN_ON(!desc->irq || desc->nvec_used != 1)) +			return; +		if (!(desc->irq >= virq && desc->irq < (virq + nvec))) +			continue; -		ida_simple_remove(&platform_msi_devid_ida, data->devid); -		kfree(data); +		irq_domain_free_irqs_common(domain, desc->irq, 1);  	} +} -	msi_domain_free_irqs(dev->msi_domain, dev); -	platform_msi_free_descs(dev); +/** + * platform_msi_domain_alloc - Allocate interrupts associated with + *			       a platform-msi domain + * + * @domain:	The platform-msi domain + * @virq:	The base irq from which to perform the allocate operation + * @nvec:	How many interrupts to free from @virq + * + * Return 0 on success, or an error code on failure. Must be called + * with irq_domain_mutex held (which can only be done as part of a + * top-level interrupt allocation). + */ +int platform_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, +			      unsigned int nr_irqs) +{ +	struct platform_msi_priv_data *data = domain->host_data; +	int err; + +	err = platform_msi_alloc_descs_with_irq(data->dev, virq, nr_irqs, data); +	if (err) +		return err; + +	err = msi_domain_populate_irqs(domain->parent, data->dev, +				       virq, nr_irqs, &data->arg); +	if (err) +		platform_msi_domain_free(domain, virq, nr_irqs); + +	return err;  }  | 
