LCOV - code coverage report
Current view: top level - drivers/base - platform-msi.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 110 0.0 %
Date: 2022-12-09 01:23:36 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             : 
     142           0 : static int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,
     143             :                                         irq_write_msi_msg_t write_msi_msg)
     144             : {
     145             :         struct platform_msi_priv_data *datap;
     146             :         int err;
     147             : 
     148             :         /*
     149             :          * Limit the number of interrupts to 2048 per device. Should we
     150             :          * need to bump this up, DEV_ID_SHIFT should be adjusted
     151             :          * accordingly (which would impact the max number of MSI
     152             :          * capable devices).
     153             :          */
     154           0 :         if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS)
     155             :                 return -EINVAL;
     156             : 
     157           0 :         if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
     158           0 :                 dev_err(dev, "Incompatible msi_domain, giving up\n");
     159           0 :                 return -EINVAL;
     160             :         }
     161             : 
     162           0 :         err = msi_setup_device_data(dev);
     163           0 :         if (err)
     164             :                 return err;
     165             : 
     166             :         /* Already initialized? */
     167           0 :         if (dev->msi.data->platform_data)
     168             :                 return -EBUSY;
     169             : 
     170           0 :         datap = kzalloc(sizeof(*datap), GFP_KERNEL);
     171           0 :         if (!datap)
     172             :                 return -ENOMEM;
     173             : 
     174           0 :         datap->devid = ida_simple_get(&platform_msi_devid_ida,
     175             :                                       0, 1 << DEV_ID_SHIFT, GFP_KERNEL);
     176           0 :         if (datap->devid < 0) {
     177           0 :                 err = datap->devid;
     178           0 :                 kfree(datap);
     179           0 :                 return err;
     180             :         }
     181             : 
     182           0 :         datap->write_msg = write_msi_msg;
     183           0 :         datap->dev = dev;
     184           0 :         dev->msi.data->platform_data = datap;
     185           0 :         return 0;
     186             : }
     187             : 
     188           0 : static void platform_msi_free_priv_data(struct device *dev)
     189             : {
     190           0 :         struct platform_msi_priv_data *data = dev->msi.data->platform_data;
     191             : 
     192           0 :         dev->msi.data->platform_data = NULL;
     193           0 :         ida_simple_remove(&platform_msi_devid_ida, data->devid);
     194           0 :         kfree(data);
     195           0 : }
     196             : 
     197             : /**
     198             :  * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
     199             :  * @dev:                The device for which to allocate interrupts
     200             :  * @nvec:               The number of interrupts to allocate
     201             :  * @write_msi_msg:      Callback to write an interrupt message for @dev
     202             :  *
     203             :  * Returns:
     204             :  * Zero for success, or an error code in case of failure
     205             :  */
     206           0 : int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
     207             :                                    irq_write_msi_msg_t write_msi_msg)
     208             : {
     209             :         int err;
     210             : 
     211           0 :         err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
     212           0 :         if (err)
     213             :                 return err;
     214             : 
     215           0 :         err = msi_domain_alloc_irqs(dev->msi.domain, dev, nvec);
     216           0 :         if (err)
     217           0 :                 platform_msi_free_priv_data(dev);
     218             : 
     219             :         return err;
     220             : }
     221             : EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs);
     222             : 
     223             : /**
     224             :  * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
     225             :  * @dev:        The device for which to free interrupts
     226             :  */
     227           0 : void platform_msi_domain_free_irqs(struct device *dev)
     228             : {
     229           0 :         msi_domain_free_irqs(dev->msi.domain, dev);
     230           0 :         platform_msi_free_priv_data(dev);
     231           0 : }
     232             : EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs);
     233             : 
     234             : /**
     235             :  * platform_msi_get_host_data - Query the private data associated with
     236             :  *                              a platform-msi domain
     237             :  * @domain:     The platform-msi domain
     238             :  *
     239             :  * Return: The private data provided when calling
     240             :  * platform_msi_create_device_domain().
     241             :  */
     242           0 : void *platform_msi_get_host_data(struct irq_domain *domain)
     243             : {
     244           0 :         struct platform_msi_priv_data *data = domain->host_data;
     245             : 
     246           0 :         return data->host_data;
     247             : }
     248             : 
     249             : static struct lock_class_key platform_device_msi_lock_class;
     250             : 
     251             : /**
     252             :  * __platform_msi_create_device_domain - Create a platform-msi device domain
     253             :  *
     254             :  * @dev:                The device generating the MSIs
     255             :  * @nvec:               The number of MSIs that need to be allocated
     256             :  * @is_tree:            flag to indicate tree hierarchy
     257             :  * @write_msi_msg:      Callback to write an interrupt message for @dev
     258             :  * @ops:                The hierarchy domain operations to use
     259             :  * @host_data:          Private data associated to this domain
     260             :  *
     261             :  * Return: An irqdomain for @nvec interrupts on success, NULL in case of error.
     262             :  *
     263             :  * This is for interrupt domains which stack on a platform-msi domain
     264             :  * created by platform_msi_create_irq_domain(). @dev->msi.domain points to
     265             :  * that platform-msi domain which is the parent for the new domain.
     266             :  */
     267             : struct irq_domain *
     268           0 : __platform_msi_create_device_domain(struct device *dev,
     269             :                                     unsigned int nvec,
     270             :                                     bool is_tree,
     271             :                                     irq_write_msi_msg_t write_msi_msg,
     272             :                                     const struct irq_domain_ops *ops,
     273             :                                     void *host_data)
     274             : {
     275             :         struct platform_msi_priv_data *data;
     276             :         struct irq_domain *domain;
     277             :         int err;
     278             : 
     279           0 :         err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
     280           0 :         if (err)
     281             :                 return NULL;
     282             : 
     283             :         /*
     284             :          * Use a separate lock class for the MSI descriptor mutex on
     285             :          * platform MSI device domains because the descriptor mutex nests
     286             :          * into the domain mutex. See alloc/free below.
     287             :          */
     288             :         lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class);
     289             : 
     290           0 :         data = dev->msi.data->platform_data;
     291           0 :         data->host_data = host_data;
     292           0 :         domain = irq_domain_create_hierarchy(dev->msi.domain, 0,
     293             :                                              is_tree ? 0 : nvec,
     294             :                                              dev->fwnode, ops, data);
     295           0 :         if (!domain)
     296             :                 goto free_priv;
     297             : 
     298           0 :         platform_msi_set_proxy_dev(&data->arg);
     299           0 :         err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg);
     300           0 :         if (err)
     301             :                 goto free_domain;
     302             : 
     303             :         return domain;
     304             : 
     305             : free_domain:
     306           0 :         irq_domain_remove(domain);
     307             : free_priv:
     308           0 :         platform_msi_free_priv_data(dev);
     309           0 :         return NULL;
     310             : }
     311             : 
     312             : /**
     313             :  * platform_msi_device_domain_free - Free interrupts associated with a platform-msi
     314             :  *                                   device domain
     315             :  *
     316             :  * @domain:     The platform-msi device domain
     317             :  * @virq:       The base irq from which to perform the free operation
     318             :  * @nr_irqs:    How many interrupts to free from @virq
     319             :  */
     320           0 : void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq,
     321             :                                      unsigned int nr_irqs)
     322             : {
     323           0 :         struct platform_msi_priv_data *data = domain->host_data;
     324             : 
     325           0 :         msi_lock_descs(data->dev);
     326           0 :         irq_domain_free_irqs_common(domain, virq, nr_irqs);
     327           0 :         msi_free_msi_descs_range(data->dev, MSI_DESC_ALL, virq, virq + nr_irqs - 1);
     328           0 :         msi_unlock_descs(data->dev);
     329           0 : }
     330             : 
     331             : /**
     332             :  * platform_msi_device_domain_alloc - Allocate interrupts associated with
     333             :  *                                    a platform-msi device domain
     334             :  *
     335             :  * @domain:     The platform-msi device domain
     336             :  * @virq:       The base irq from which to perform the allocate operation
     337             :  * @nr_irqs:    How many interrupts to allocate from @virq
     338             :  *
     339             :  * Return 0 on success, or an error code on failure. Must be called
     340             :  * with irq_domain_mutex held (which can only be done as part of a
     341             :  * top-level interrupt allocation).
     342             :  */
     343           0 : int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq,
     344             :                                      unsigned int nr_irqs)
     345             : {
     346           0 :         struct platform_msi_priv_data *data = domain->host_data;
     347           0 :         struct device *dev = data->dev;
     348             : 
     349           0 :         return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg);
     350             : }

Generated by: LCOV version 1.14