LCOV - code coverage report
Current view: top level - drivers/base - platform-msi.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 111 0.0 %
Date: 2023-08-24 13:40:31 Functions: 0 14 0.0 %

          Line data    Source code
       1             : // SPDX-License-Identifier: GPL-2.0
       2             : /*
       3             :  * MSI framework for platform devices
       4             :  *
       5             :  * Copyright (C) 2015 ARM Limited, All Rights Reserved.
       6             :  * Author: Marc Zyngier <marc.zyngier@arm.com>
       7             :  */
       8             : 
       9             : #include <linux/device.h>
      10             : #include <linux/idr.h>
      11             : #include <linux/irq.h>
      12             : #include <linux/irqdomain.h>
      13             : #include <linux/msi.h>
      14             : #include <linux/slab.h>
      15             : 
      16             : #define DEV_ID_SHIFT    21
      17             : #define MAX_DEV_MSIS    (1 << (32 - DEV_ID_SHIFT))
      18             : 
      19             : /*
      20             :  * Internal data structure containing a (made up, but unique) devid
      21             :  * and the callback to write the MSI message.
      22             :  */
      23             : struct platform_msi_priv_data {
      24             :         struct device                   *dev;
      25             :         void                            *host_data;
      26             :         msi_alloc_info_t                arg;
      27             :         irq_write_msi_msg_t             write_msg;
      28             :         int                             devid;
      29             : };
      30             : 
      31             : /* The devid allocator */
      32             : static DEFINE_IDA(platform_msi_devid_ida);
      33             : 
      34             : #ifdef GENERIC_MSI_DOMAIN_OPS
      35             : /*
      36             :  * Convert an msi_desc to a globaly unique identifier (per-device
      37             :  * devid + msi_desc position in the msi_list).
      38             :  */
      39             : static irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc)
      40             : {
      41           0 :         u32 devid = desc->dev->msi.data->platform_data->devid;
      42             : 
      43           0 :         return (devid << (32 - DEV_ID_SHIFT)) | desc->msi_index;
      44             : }
      45             : 
      46           0 : static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
      47             : {
      48           0 :         arg->desc = desc;
      49           0 :         arg->hwirq = platform_msi_calc_hwirq(desc);
      50           0 : }
      51             : 
      52           0 : static int platform_msi_init(struct irq_domain *domain,
      53             :                              struct msi_domain_info *info,
      54             :                              unsigned int virq, irq_hw_number_t hwirq,
      55             :                              msi_alloc_info_t *arg)
      56             : {
      57           0 :         return irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
      58           0 :                                              info->chip, info->chip_data);
      59             : }
      60             : 
      61             : static void platform_msi_set_proxy_dev(msi_alloc_info_t *arg)
      62             : {
      63           0 :         arg->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE;
      64             : }
      65             : #else
      66             : #define platform_msi_set_desc           NULL
      67             : #define platform_msi_init               NULL
      68             : #define platform_msi_set_proxy_dev(x)   do {} while(0)
      69             : #endif
      70             : 
      71           0 : static void platform_msi_update_dom_ops(struct msi_domain_info *info)
      72             : {
      73           0 :         struct msi_domain_ops *ops = info->ops;
      74             : 
      75           0 :         BUG_ON(!ops);
      76             : 
      77           0 :         if (ops->msi_init == NULL)
      78           0 :                 ops->msi_init = platform_msi_init;
      79           0 :         if (ops->set_desc == NULL)
      80           0 :                 ops->set_desc = platform_msi_set_desc;
      81           0 : }
      82             : 
      83           0 : static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg)
      84             : {
      85           0 :         struct msi_desc *desc = irq_data_get_msi_desc(data);
      86             : 
      87           0 :         desc->dev->msi.data->platform_data->write_msg(desc, msg);
      88           0 : }
      89             : 
      90           0 : static void platform_msi_update_chip_ops(struct msi_domain_info *info)
      91             : {
      92           0 :         struct irq_chip *chip = info->chip;
      93             : 
      94           0 :         BUG_ON(!chip);
      95           0 :         if (!chip->irq_mask)
      96           0 :                 chip->irq_mask = irq_chip_mask_parent;
      97           0 :         if (!chip->irq_unmask)
      98           0 :                 chip->irq_unmask = irq_chip_unmask_parent;
      99           0 :         if (!chip->irq_eoi)
     100           0 :                 chip->irq_eoi = irq_chip_eoi_parent;
     101           0 :         if (!chip->irq_set_affinity)
     102           0 :                 chip->irq_set_affinity = msi_domain_set_affinity;
     103           0 :         if (!chip->irq_write_msi_msg)
     104           0 :                 chip->irq_write_msi_msg = platform_msi_write_msg;
     105           0 :         if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) &&
     106             :                     !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI)))
     107           0 :                 info->flags &= ~MSI_FLAG_LEVEL_CAPABLE;
     108           0 : }
     109             : 
     110             : /**
     111             :  * platform_msi_create_irq_domain - Create a platform MSI interrupt domain
     112             :  * @fwnode:             Optional fwnode of the interrupt controller
     113             :  * @info:       MSI domain info
     114             :  * @parent:     Parent irq domain
     115             :  *
     116             :  * Updates the domain and chip ops and creates a platform MSI
     117             :  * interrupt domain.
     118             :  *
     119             :  * Returns:
     120             :  * A domain pointer or NULL in case of failure.
     121             :  */
     122           0 : struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
     123             :                                                   struct msi_domain_info *info,
     124             :                                                   struct irq_domain *parent)
     125             : {
     126             :         struct irq_domain *domain;
     127             : 
     128           0 :         if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
     129           0 :                 platform_msi_update_dom_ops(info);
     130           0 :         if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
     131           0 :                 platform_msi_update_chip_ops(info);
     132           0 :         info->flags |= MSI_FLAG_DEV_SYSFS | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS |
     133             :                        MSI_FLAG_FREE_MSI_DESCS;
     134             : 
     135           0 :         domain = msi_create_irq_domain(fwnode, info, parent);
     136           0 :         if (domain)
     137           0 :                 irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI);
     138             : 
     139           0 :         return domain;
     140             : }
     141             : EXPORT_SYMBOL_GPL(platform_msi_create_irq_domain);
     142             : 
     143           0 : static int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,
     144             :                                         irq_write_msi_msg_t write_msi_msg)
     145             : {
     146             :         struct platform_msi_priv_data *datap;
     147             :         int err;
     148             : 
     149             :         /*
     150             :          * Limit the number of interrupts to 2048 per device. Should we
     151             :          * need to bump this up, DEV_ID_SHIFT should be adjusted
     152             :          * accordingly (which would impact the max number of MSI
     153             :          * capable devices).
     154             :          */
     155           0 :         if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS)
     156             :                 return -EINVAL;
     157             : 
     158           0 :         if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
     159           0 :                 dev_err(dev, "Incompatible msi_domain, giving up\n");
     160           0 :                 return -EINVAL;
     161             :         }
     162             : 
     163           0 :         err = msi_setup_device_data(dev);
     164           0 :         if (err)
     165             :                 return err;
     166             : 
     167             :         /* Already initialized? */
     168           0 :         if (dev->msi.data->platform_data)
     169             :                 return -EBUSY;
     170             : 
     171           0 :         datap = kzalloc(sizeof(*datap), GFP_KERNEL);
     172           0 :         if (!datap)
     173             :                 return -ENOMEM;
     174             : 
     175           0 :         datap->devid = ida_simple_get(&platform_msi_devid_ida,
     176             :                                       0, 1 << DEV_ID_SHIFT, GFP_KERNEL);
     177           0 :         if (datap->devid < 0) {
     178           0 :                 err = datap->devid;
     179           0 :                 kfree(datap);
     180           0 :                 return err;
     181             :         }
     182             : 
     183           0 :         datap->write_msg = write_msi_msg;
     184           0 :         datap->dev = dev;
     185           0 :         dev->msi.data->platform_data = datap;
     186           0 :         return 0;
     187             : }
     188             : 
     189           0 : static void platform_msi_free_priv_data(struct device *dev)
     190             : {
     191           0 :         struct platform_msi_priv_data *data = dev->msi.data->platform_data;
     192             : 
     193           0 :         dev->msi.data->platform_data = NULL;
     194           0 :         ida_simple_remove(&platform_msi_devid_ida, data->devid);
     195           0 :         kfree(data);
     196           0 : }
     197             : 
     198             : /**
     199             :  * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
     200             :  * @dev:                The device for which to allocate interrupts
     201             :  * @nvec:               The number of interrupts to allocate
     202             :  * @write_msi_msg:      Callback to write an interrupt message for @dev
     203             :  *
     204             :  * Returns:
     205             :  * Zero for success, or an error code in case of failure
     206             :  */
     207           0 : int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
     208             :                                    irq_write_msi_msg_t write_msi_msg)
     209             : {
     210             :         int err;
     211             : 
     212           0 :         err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
     213           0 :         if (err)
     214             :                 return err;
     215             : 
     216           0 :         err = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, nvec - 1);
     217           0 :         if (err)
     218           0 :                 platform_msi_free_priv_data(dev);
     219             : 
     220             :         return err;
     221             : }
     222             : EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs);
     223             : 
     224             : /**
     225             :  * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
     226             :  * @dev:        The device for which to free interrupts
     227             :  */
     228           0 : void platform_msi_domain_free_irqs(struct device *dev)
     229             : {
     230           0 :         msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN);
     231           0 :         platform_msi_free_priv_data(dev);
     232           0 : }
     233             : EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs);
     234             : 
     235             : /**
     236             :  * platform_msi_get_host_data - Query the private data associated with
     237             :  *                              a platform-msi domain
     238             :  * @domain:     The platform-msi domain
     239             :  *
     240             :  * Return: The private data provided when calling
     241             :  * platform_msi_create_device_domain().
     242             :  */
     243           0 : void *platform_msi_get_host_data(struct irq_domain *domain)
     244             : {
     245           0 :         struct platform_msi_priv_data *data = domain->host_data;
     246             : 
     247           0 :         return data->host_data;
     248             : }
     249             : 
     250             : static struct lock_class_key platform_device_msi_lock_class;
     251             : 
     252             : /**
     253             :  * __platform_msi_create_device_domain - Create a platform-msi device domain
     254             :  *
     255             :  * @dev:                The device generating the MSIs
     256             :  * @nvec:               The number of MSIs that need to be allocated
     257             :  * @is_tree:            flag to indicate tree hierarchy
     258             :  * @write_msi_msg:      Callback to write an interrupt message for @dev
     259             :  * @ops:                The hierarchy domain operations to use
     260             :  * @host_data:          Private data associated to this domain
     261             :  *
     262             :  * Return: An irqdomain for @nvec interrupts on success, NULL in case of error.
     263             :  *
     264             :  * This is for interrupt domains which stack on a platform-msi domain
     265             :  * created by platform_msi_create_irq_domain(). @dev->msi.domain points to
     266             :  * that platform-msi domain which is the parent for the new domain.
     267             :  */
     268             : struct irq_domain *
     269           0 : __platform_msi_create_device_domain(struct device *dev,
     270             :                                     unsigned int nvec,
     271             :                                     bool is_tree,
     272             :                                     irq_write_msi_msg_t write_msi_msg,
     273             :                                     const struct irq_domain_ops *ops,
     274             :                                     void *host_data)
     275             : {
     276             :         struct platform_msi_priv_data *data;
     277             :         struct irq_domain *domain;
     278             :         int err;
     279             : 
     280           0 :         err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
     281           0 :         if (err)
     282             :                 return NULL;
     283             : 
     284             :         /*
     285             :          * Use a separate lock class for the MSI descriptor mutex on
     286             :          * platform MSI device domains because the descriptor mutex nests
     287             :          * into the domain mutex. See alloc/free below.
     288             :          */
     289             :         lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class);
     290             : 
     291           0 :         data = dev->msi.data->platform_data;
     292           0 :         data->host_data = host_data;
     293           0 :         domain = irq_domain_create_hierarchy(dev->msi.domain, 0,
     294             :                                              is_tree ? 0 : nvec,
     295             :                                              dev->fwnode, ops, data);
     296           0 :         if (!domain)
     297             :                 goto free_priv;
     298             : 
     299           0 :         platform_msi_set_proxy_dev(&data->arg);
     300           0 :         err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg);
     301           0 :         if (err)
     302             :                 goto free_domain;
     303             : 
     304             :         return domain;
     305             : 
     306             : free_domain:
     307           0 :         irq_domain_remove(domain);
     308             : free_priv:
     309           0 :         platform_msi_free_priv_data(dev);
     310           0 :         return NULL;
     311             : }
     312             : 
     313             : /**
     314             :  * platform_msi_device_domain_free - Free interrupts associated with a platform-msi
     315             :  *                                   device domain
     316             :  *
     317             :  * @domain:     The platform-msi device domain
     318             :  * @virq:       The base irq from which to perform the free operation
     319             :  * @nr_irqs:    How many interrupts to free from @virq
     320             :  */
     321           0 : void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq,
     322             :                                      unsigned int nr_irqs)
     323             : {
     324           0 :         struct platform_msi_priv_data *data = domain->host_data;
     325             : 
     326           0 :         msi_lock_descs(data->dev);
     327           0 :         msi_domain_depopulate_descs(data->dev, virq, nr_irqs);
     328           0 :         irq_domain_free_irqs_common(domain, virq, nr_irqs);
     329           0 :         msi_free_msi_descs_range(data->dev, virq, virq + nr_irqs - 1);
     330           0 :         msi_unlock_descs(data->dev);
     331           0 : }
     332             : 
     333             : /**
     334             :  * platform_msi_device_domain_alloc - Allocate interrupts associated with
     335             :  *                                    a platform-msi device domain
     336             :  *
     337             :  * @domain:     The platform-msi device domain
     338             :  * @virq:       The base irq from which to perform the allocate operation
     339             :  * @nr_irqs:    How many interrupts to allocate from @virq
     340             :  *
     341             :  * Return 0 on success, or an error code on failure. Must be called
     342             :  * with irq_domain_mutex held (which can only be done as part of a
     343             :  * top-level interrupt allocation).
     344             :  */
     345           0 : int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq,
     346             :                                      unsigned int nr_irqs)
     347             : {
     348           0 :         struct platform_msi_priv_data *data = domain->host_data;
     349           0 :         struct device *dev = data->dev;
     350             : 
     351           0 :         return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg);
     352             : }

Generated by: LCOV version 1.14