162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * MSI framework for platform devices 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2015 ARM Limited, All Rights Reserved. 662306a36Sopenharmony_ci * Author: Marc Zyngier <marc.zyngier@arm.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/device.h> 1062306a36Sopenharmony_ci#include <linux/idr.h> 1162306a36Sopenharmony_ci#include <linux/irq.h> 1262306a36Sopenharmony_ci#include <linux/irqdomain.h> 1362306a36Sopenharmony_ci#include <linux/msi.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define DEV_ID_SHIFT 21 1762306a36Sopenharmony_ci#define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT)) 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* 2062306a36Sopenharmony_ci * Internal data structure containing a (made up, but unique) devid 2162306a36Sopenharmony_ci * and the callback to write the MSI message. 2262306a36Sopenharmony_ci */ 2362306a36Sopenharmony_cistruct platform_msi_priv_data { 2462306a36Sopenharmony_ci struct device *dev; 2562306a36Sopenharmony_ci void *host_data; 2662306a36Sopenharmony_ci msi_alloc_info_t arg; 2762306a36Sopenharmony_ci irq_write_msi_msg_t write_msg; 2862306a36Sopenharmony_ci int devid; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* The devid allocator */ 3262306a36Sopenharmony_cistatic DEFINE_IDA(platform_msi_devid_ida); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#ifdef GENERIC_MSI_DOMAIN_OPS 3562306a36Sopenharmony_ci/* 3662306a36Sopenharmony_ci * Convert an msi_desc to a globaly unique identifier (per-device 3762306a36Sopenharmony_ci * devid + msi_desc position in the msi_list). 3862306a36Sopenharmony_ci */ 3962306a36Sopenharmony_cistatic irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci u32 devid = desc->dev->msi.data->platform_data->devid; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci return (devid << (32 - DEV_ID_SHIFT)) | desc->msi_index; 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci arg->desc = desc; 4962306a36Sopenharmony_ci arg->hwirq = platform_msi_calc_hwirq(desc); 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int platform_msi_init(struct irq_domain *domain, 5362306a36Sopenharmony_ci struct msi_domain_info *info, 5462306a36Sopenharmony_ci unsigned int virq, irq_hw_number_t hwirq, 5562306a36Sopenharmony_ci msi_alloc_info_t *arg) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, 5862306a36Sopenharmony_ci info->chip, info->chip_data); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic void platform_msi_set_proxy_dev(msi_alloc_info_t *arg) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci arg->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci#else 6662306a36Sopenharmony_ci#define platform_msi_set_desc NULL 6762306a36Sopenharmony_ci#define platform_msi_init NULL 6862306a36Sopenharmony_ci#define platform_msi_set_proxy_dev(x) do {} while(0) 6962306a36Sopenharmony_ci#endif 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void platform_msi_update_dom_ops(struct msi_domain_info *info) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct msi_domain_ops *ops = info->ops; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci BUG_ON(!ops); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (ops->msi_init == NULL) 7862306a36Sopenharmony_ci ops->msi_init = platform_msi_init; 7962306a36Sopenharmony_ci if (ops->set_desc == NULL) 8062306a36Sopenharmony_ci ops->set_desc = platform_msi_set_desc; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci struct msi_desc *desc = irq_data_get_msi_desc(data); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci desc->dev->msi.data->platform_data->write_msg(desc, msg); 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic void platform_msi_update_chip_ops(struct msi_domain_info *info) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct irq_chip *chip = info->chip; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci BUG_ON(!chip); 9562306a36Sopenharmony_ci if (!chip->irq_mask) 9662306a36Sopenharmony_ci chip->irq_mask = irq_chip_mask_parent; 9762306a36Sopenharmony_ci if (!chip->irq_unmask) 9862306a36Sopenharmony_ci chip->irq_unmask = irq_chip_unmask_parent; 9962306a36Sopenharmony_ci if (!chip->irq_eoi) 10062306a36Sopenharmony_ci chip->irq_eoi = irq_chip_eoi_parent; 10162306a36Sopenharmony_ci if (!chip->irq_set_affinity) 10262306a36Sopenharmony_ci chip->irq_set_affinity = msi_domain_set_affinity; 10362306a36Sopenharmony_ci if (!chip->irq_write_msi_msg) 10462306a36Sopenharmony_ci chip->irq_write_msi_msg = platform_msi_write_msg; 10562306a36Sopenharmony_ci if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) && 10662306a36Sopenharmony_ci !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI))) 10762306a36Sopenharmony_ci info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci/** 11162306a36Sopenharmony_ci * platform_msi_create_irq_domain - Create a platform MSI interrupt domain 11262306a36Sopenharmony_ci * @fwnode: Optional fwnode of the interrupt controller 11362306a36Sopenharmony_ci * @info: MSI domain info 11462306a36Sopenharmony_ci * @parent: Parent irq domain 11562306a36Sopenharmony_ci * 11662306a36Sopenharmony_ci * Updates the domain and chip ops and creates a platform MSI 11762306a36Sopenharmony_ci * interrupt domain. 11862306a36Sopenharmony_ci * 11962306a36Sopenharmony_ci * Returns: 12062306a36Sopenharmony_ci * A domain pointer or NULL in case of failure. 12162306a36Sopenharmony_ci */ 12262306a36Sopenharmony_cistruct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, 12362306a36Sopenharmony_ci struct msi_domain_info *info, 12462306a36Sopenharmony_ci struct irq_domain *parent) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct irq_domain *domain; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) 12962306a36Sopenharmony_ci platform_msi_update_dom_ops(info); 13062306a36Sopenharmony_ci if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) 13162306a36Sopenharmony_ci platform_msi_update_chip_ops(info); 13262306a36Sopenharmony_ci info->flags |= MSI_FLAG_DEV_SYSFS | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | 13362306a36Sopenharmony_ci MSI_FLAG_FREE_MSI_DESCS; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci domain = msi_create_irq_domain(fwnode, info, parent); 13662306a36Sopenharmony_ci if (domain) 13762306a36Sopenharmony_ci irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return domain; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(platform_msi_create_irq_domain); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, 14462306a36Sopenharmony_ci irq_write_msi_msg_t write_msi_msg) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct platform_msi_priv_data *datap; 14762306a36Sopenharmony_ci int err; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci /* 15062306a36Sopenharmony_ci * Limit the number of interrupts to 2048 per device. Should we 15162306a36Sopenharmony_ci * need to bump this up, DEV_ID_SHIFT should be adjusted 15262306a36Sopenharmony_ci * accordingly (which would impact the max number of MSI 15362306a36Sopenharmony_ci * capable devices). 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_ci if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) 15662306a36Sopenharmony_ci return -EINVAL; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { 15962306a36Sopenharmony_ci dev_err(dev, "Incompatible msi_domain, giving up\n"); 16062306a36Sopenharmony_ci return -EINVAL; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci err = msi_setup_device_data(dev); 16462306a36Sopenharmony_ci if (err) 16562306a36Sopenharmony_ci return err; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci /* Already initialized? */ 16862306a36Sopenharmony_ci if (dev->msi.data->platform_data) 16962306a36Sopenharmony_ci return -EBUSY; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci datap = kzalloc(sizeof(*datap), GFP_KERNEL); 17262306a36Sopenharmony_ci if (!datap) 17362306a36Sopenharmony_ci return -ENOMEM; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci datap->devid = ida_simple_get(&platform_msi_devid_ida, 17662306a36Sopenharmony_ci 0, 1 << DEV_ID_SHIFT, GFP_KERNEL); 17762306a36Sopenharmony_ci if (datap->devid < 0) { 17862306a36Sopenharmony_ci err = datap->devid; 17962306a36Sopenharmony_ci kfree(datap); 18062306a36Sopenharmony_ci return err; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci datap->write_msg = write_msi_msg; 18462306a36Sopenharmony_ci datap->dev = dev; 18562306a36Sopenharmony_ci dev->msi.data->platform_data = datap; 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic void platform_msi_free_priv_data(struct device *dev) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci struct platform_msi_priv_data *data = dev->msi.data->platform_data; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci dev->msi.data->platform_data = NULL; 19462306a36Sopenharmony_ci ida_simple_remove(&platform_msi_devid_ida, data->devid); 19562306a36Sopenharmony_ci kfree(data); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci/** 19962306a36Sopenharmony_ci * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev 20062306a36Sopenharmony_ci * @dev: The device for which to allocate interrupts 20162306a36Sopenharmony_ci * @nvec: The number of interrupts to allocate 20262306a36Sopenharmony_ci * @write_msi_msg: Callback to write an interrupt message for @dev 20362306a36Sopenharmony_ci * 20462306a36Sopenharmony_ci * Returns: 20562306a36Sopenharmony_ci * Zero for success, or an error code in case of failure 20662306a36Sopenharmony_ci */ 20762306a36Sopenharmony_ciint platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, 20862306a36Sopenharmony_ci irq_write_msi_msg_t write_msi_msg) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci int err; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); 21362306a36Sopenharmony_ci if (err) 21462306a36Sopenharmony_ci return err; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci err = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, nvec - 1); 21762306a36Sopenharmony_ci if (err) 21862306a36Sopenharmony_ci platform_msi_free_priv_data(dev); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci return err; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci/** 22562306a36Sopenharmony_ci * platform_msi_domain_free_irqs - Free MSI interrupts for @dev 22662306a36Sopenharmony_ci * @dev: The device for which to free interrupts 22762306a36Sopenharmony_ci */ 22862306a36Sopenharmony_civoid platform_msi_domain_free_irqs(struct device *dev) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN); 23162306a36Sopenharmony_ci platform_msi_free_priv_data(dev); 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci/** 23662306a36Sopenharmony_ci * platform_msi_get_host_data - Query the private data associated with 23762306a36Sopenharmony_ci * a platform-msi domain 23862306a36Sopenharmony_ci * @domain: The platform-msi domain 23962306a36Sopenharmony_ci * 24062306a36Sopenharmony_ci * Return: The private data provided when calling 24162306a36Sopenharmony_ci * platform_msi_create_device_domain(). 24262306a36Sopenharmony_ci */ 24362306a36Sopenharmony_civoid *platform_msi_get_host_data(struct irq_domain *domain) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci struct platform_msi_priv_data *data = domain->host_data; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return data->host_data; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic struct lock_class_key platform_device_msi_lock_class; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci/** 25362306a36Sopenharmony_ci * __platform_msi_create_device_domain - Create a platform-msi device domain 25462306a36Sopenharmony_ci * 25562306a36Sopenharmony_ci * @dev: The device generating the MSIs 25662306a36Sopenharmony_ci * @nvec: The number of MSIs that need to be allocated 25762306a36Sopenharmony_ci * @is_tree: flag to indicate tree hierarchy 25862306a36Sopenharmony_ci * @write_msi_msg: Callback to write an interrupt message for @dev 25962306a36Sopenharmony_ci * @ops: The hierarchy domain operations to use 26062306a36Sopenharmony_ci * @host_data: Private data associated to this domain 26162306a36Sopenharmony_ci * 26262306a36Sopenharmony_ci * Return: An irqdomain for @nvec interrupts on success, NULL in case of error. 26362306a36Sopenharmony_ci * 26462306a36Sopenharmony_ci * This is for interrupt domains which stack on a platform-msi domain 26562306a36Sopenharmony_ci * created by platform_msi_create_irq_domain(). @dev->msi.domain points to 26662306a36Sopenharmony_ci * that platform-msi domain which is the parent for the new domain. 26762306a36Sopenharmony_ci */ 26862306a36Sopenharmony_cistruct irq_domain * 26962306a36Sopenharmony_ci__platform_msi_create_device_domain(struct device *dev, 27062306a36Sopenharmony_ci unsigned int nvec, 27162306a36Sopenharmony_ci bool is_tree, 27262306a36Sopenharmony_ci irq_write_msi_msg_t write_msi_msg, 27362306a36Sopenharmony_ci const struct irq_domain_ops *ops, 27462306a36Sopenharmony_ci void *host_data) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci struct platform_msi_priv_data *data; 27762306a36Sopenharmony_ci struct irq_domain *domain; 27862306a36Sopenharmony_ci int err; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); 28162306a36Sopenharmony_ci if (err) 28262306a36Sopenharmony_ci return NULL; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci /* 28562306a36Sopenharmony_ci * Use a separate lock class for the MSI descriptor mutex on 28662306a36Sopenharmony_ci * platform MSI device domains because the descriptor mutex nests 28762306a36Sopenharmony_ci * into the domain mutex. See alloc/free below. 28862306a36Sopenharmony_ci */ 28962306a36Sopenharmony_ci lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci data = dev->msi.data->platform_data; 29262306a36Sopenharmony_ci data->host_data = host_data; 29362306a36Sopenharmony_ci domain = irq_domain_create_hierarchy(dev->msi.domain, 0, 29462306a36Sopenharmony_ci is_tree ? 0 : nvec, 29562306a36Sopenharmony_ci dev->fwnode, ops, data); 29662306a36Sopenharmony_ci if (!domain) 29762306a36Sopenharmony_ci goto free_priv; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci platform_msi_set_proxy_dev(&data->arg); 30062306a36Sopenharmony_ci err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg); 30162306a36Sopenharmony_ci if (err) 30262306a36Sopenharmony_ci goto free_domain; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci return domain; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cifree_domain: 30762306a36Sopenharmony_ci irq_domain_remove(domain); 30862306a36Sopenharmony_cifree_priv: 30962306a36Sopenharmony_ci platform_msi_free_priv_data(dev); 31062306a36Sopenharmony_ci return NULL; 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci/** 31462306a36Sopenharmony_ci * platform_msi_device_domain_free - Free interrupts associated with a platform-msi 31562306a36Sopenharmony_ci * device domain 31662306a36Sopenharmony_ci * 31762306a36Sopenharmony_ci * @domain: The platform-msi device domain 31862306a36Sopenharmony_ci * @virq: The base irq from which to perform the free operation 31962306a36Sopenharmony_ci * @nr_irqs: How many interrupts to free from @virq 32062306a36Sopenharmony_ci */ 32162306a36Sopenharmony_civoid platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq, 32262306a36Sopenharmony_ci unsigned int nr_irqs) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci struct platform_msi_priv_data *data = domain->host_data; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci msi_lock_descs(data->dev); 32762306a36Sopenharmony_ci msi_domain_depopulate_descs(data->dev, virq, nr_irqs); 32862306a36Sopenharmony_ci irq_domain_free_irqs_common(domain, virq, nr_irqs); 32962306a36Sopenharmony_ci msi_free_msi_descs_range(data->dev, virq, virq + nr_irqs - 1); 33062306a36Sopenharmony_ci msi_unlock_descs(data->dev); 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci/** 33462306a36Sopenharmony_ci * platform_msi_device_domain_alloc - Allocate interrupts associated with 33562306a36Sopenharmony_ci * a platform-msi device domain 33662306a36Sopenharmony_ci * 33762306a36Sopenharmony_ci * @domain: The platform-msi device domain 33862306a36Sopenharmony_ci * @virq: The base irq from which to perform the allocate operation 33962306a36Sopenharmony_ci * @nr_irqs: How many interrupts to allocate from @virq 34062306a36Sopenharmony_ci * 34162306a36Sopenharmony_ci * Return 0 on success, or an error code on failure. Must be called 34262306a36Sopenharmony_ci * with irq_domain_mutex held (which can only be done as part of a 34362306a36Sopenharmony_ci * top-level interrupt allocation). 34462306a36Sopenharmony_ci */ 34562306a36Sopenharmony_ciint platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq, 34662306a36Sopenharmony_ci unsigned int nr_irqs) 34762306a36Sopenharmony_ci{ 34862306a36Sopenharmony_ci struct platform_msi_priv_data *data = domain->host_data; 34962306a36Sopenharmony_ci struct device *dev = data->dev; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg); 35262306a36Sopenharmony_ci} 353