LCOV - code coverage report
Current view: top level - drivers/base - platform-msi.c (source / functions) Hit Total Coverage
Test: combined.info Lines: 0 132 0.0 %
Date: 2022-04-01 14:17:54 Functions: 0 11 0.0 %
Branches: 0 76 0.0 %

           Branch data     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                 :            :         u32 devid;
      42                 :            : 
      43                 :            :         devid = desc->platform.msi_priv_data->devid;
      44                 :            : 
      45                 :            :         return (devid << (32 - DEV_ID_SHIFT)) | desc->platform.msi_index;
      46                 :            : }
      47                 :            : 
      48                 :            : static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
      49                 :            : {
      50                 :            :         arg->desc = desc;
      51                 :            :         arg->hwirq = platform_msi_calc_hwirq(desc);
      52                 :            : }
      53                 :            : 
      54                 :            : static int platform_msi_init(struct irq_domain *domain,
      55                 :            :                              struct msi_domain_info *info,
      56                 :            :                              unsigned int virq, irq_hw_number_t hwirq,
      57                 :            :                              msi_alloc_info_t *arg)
      58                 :            : {
      59                 :            :         return irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
      60                 :            :                                              info->chip, info->chip_data);
      61                 :            : }
      62                 :            : #else
      63                 :            : #define platform_msi_set_desc           NULL
      64                 :            : #define platform_msi_init               NULL
      65                 :            : #endif
      66                 :            : 
      67                 :          0 : static void platform_msi_update_dom_ops(struct msi_domain_info *info)
      68                 :            : {
      69                 :          0 :         struct msi_domain_ops *ops = info->ops;
      70                 :            : 
      71                 :          0 :         BUG_ON(!ops);
      72                 :            : 
      73         [ #  # ]:          0 :         if (ops->msi_init == NULL)
      74                 :          0 :                 ops->msi_init = platform_msi_init;
      75         [ #  # ]:          0 :         if (ops->set_desc == NULL)
      76                 :          0 :                 ops->set_desc = platform_msi_set_desc;
      77                 :            : }
      78                 :            : 
      79                 :          0 : static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg)
      80                 :            : {
      81                 :          0 :         struct msi_desc *desc = irq_data_get_msi_desc(data);
      82                 :          0 :         struct platform_msi_priv_data *priv_data;
      83                 :            : 
      84                 :          0 :         priv_data = desc->platform.msi_priv_data;
      85                 :            : 
      86                 :          0 :         priv_data->write_msg(desc, msg);
      87                 :          0 : }
      88                 :            : 
      89                 :            : static void platform_msi_update_chip_ops(struct msi_domain_info *info)
      90                 :            : {
      91                 :            :         struct irq_chip *chip = info->chip;
      92                 :            : 
      93                 :            :         BUG_ON(!chip);
      94                 :            :         if (!chip->irq_mask)
      95                 :            :                 chip->irq_mask = irq_chip_mask_parent;
      96                 :            :         if (!chip->irq_unmask)
      97                 :            :                 chip->irq_unmask = irq_chip_unmask_parent;
      98                 :            :         if (!chip->irq_eoi)
      99                 :            :                 chip->irq_eoi = irq_chip_eoi_parent;
     100                 :            :         if (!chip->irq_set_affinity)
     101                 :            :                 chip->irq_set_affinity = msi_domain_set_affinity;
     102                 :            :         if (!chip->irq_write_msi_msg)
     103                 :            :                 chip->irq_write_msi_msg = platform_msi_write_msg;
     104                 :            :         if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) &&
     105                 :            :                     !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI)))
     106                 :            :                 info->flags &= ~MSI_FLAG_LEVEL_CAPABLE;
     107                 :            : }
     108                 :            : 
     109                 :          0 : static void platform_msi_free_descs(struct device *dev, int base, int nvec)
     110                 :            : {
     111                 :          0 :         struct msi_desc *desc, *tmp;
     112                 :            : 
     113         [ #  # ]:          0 :         list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) {
     114         [ #  # ]:          0 :                 if (desc->platform.msi_index >= base &&
     115         [ #  # ]:          0 :                     desc->platform.msi_index < (base + nvec)) {
     116                 :          0 :                         list_del(&desc->list);
     117                 :          0 :                         free_msi_entry(desc);
     118                 :            :                 }
     119                 :            :         }
     120                 :          0 : }
     121                 :            : 
     122                 :          0 : static int platform_msi_alloc_descs_with_irq(struct device *dev, int virq,
     123                 :            :                                              int nvec,
     124                 :            :                                              struct platform_msi_priv_data *data)
     125                 :            : 
     126                 :            : {
     127                 :          0 :         struct msi_desc *desc;
     128                 :          0 :         int i, base = 0;
     129                 :            : 
     130         [ #  # ]:          0 :         if (!list_empty(dev_to_msi_list(dev))) {
     131                 :          0 :                 desc = list_last_entry(dev_to_msi_list(dev),
     132                 :            :                                        struct msi_desc, list);
     133                 :          0 :                 base = desc->platform.msi_index + 1;
     134                 :            :         }
     135                 :            : 
     136         [ #  # ]:          0 :         for (i = 0; i < nvec; i++) {
     137                 :          0 :                 desc = alloc_msi_entry(dev, 1, NULL);
     138         [ #  # ]:          0 :                 if (!desc)
     139                 :            :                         break;
     140                 :            : 
     141                 :          0 :                 desc->platform.msi_priv_data = data;
     142                 :          0 :                 desc->platform.msi_index = base + i;
     143         [ #  # ]:          0 :                 desc->irq = virq ? virq + i : 0;
     144                 :            : 
     145                 :          0 :                 list_add_tail(&desc->list, dev_to_msi_list(dev));
     146                 :            :         }
     147                 :            : 
     148         [ #  # ]:          0 :         if (i != nvec) {
     149                 :            :                 /* Clean up the mess */
     150                 :          0 :                 platform_msi_free_descs(dev, base, nvec);
     151                 :            : 
     152                 :          0 :                 return -ENOMEM;
     153                 :            :         }
     154                 :            : 
     155                 :            :         return 0;
     156                 :            : }
     157                 :            : 
     158                 :          0 : static int platform_msi_alloc_descs(struct device *dev, int nvec,
     159                 :            :                                     struct platform_msi_priv_data *data)
     160                 :            : 
     161                 :            : {
     162                 :          0 :         return platform_msi_alloc_descs_with_irq(dev, 0, nvec, data);
     163                 :            : }
     164                 :            : 
     165                 :            : /**
     166                 :            :  * platform_msi_create_irq_domain - Create a platform MSI interrupt domain
     167                 :            :  * @fwnode:             Optional fwnode of the interrupt controller
     168                 :            :  * @info:       MSI domain info
     169                 :            :  * @parent:     Parent irq domain
     170                 :            :  *
     171                 :            :  * Updates the domain and chip ops and creates a platform MSI
     172                 :            :  * interrupt domain.
     173                 :            :  *
     174                 :            :  * Returns:
     175                 :            :  * A domain pointer or NULL in case of failure.
     176                 :            :  */
     177                 :          0 : struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
     178                 :            :                                                   struct msi_domain_info *info,
     179                 :            :                                                   struct irq_domain *parent)
     180                 :            : {
     181                 :          0 :         struct irq_domain *domain;
     182                 :            : 
     183         [ #  # ]:          0 :         if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
     184         [ #  # ]:          0 :                 platform_msi_update_dom_ops(info);
     185         [ #  # ]:          0 :         if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
     186                 :          0 :                 platform_msi_update_chip_ops(info);
     187                 :            : 
     188                 :          0 :         domain = msi_create_irq_domain(fwnode, info, parent);
     189         [ #  # ]:          0 :         if (domain)
     190                 :          0 :                 irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI);
     191                 :            : 
     192                 :          0 :         return domain;
     193                 :            : }
     194                 :            : 
     195                 :            : static struct platform_msi_priv_data *
     196                 :          0 : platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,
     197                 :            :                              irq_write_msi_msg_t write_msi_msg)
     198                 :            : {
     199                 :          0 :         struct platform_msi_priv_data *datap;
     200                 :            :         /*
     201                 :            :          * Limit the number of interrupts to 2048 per device. Should we
     202                 :            :          * need to bump this up, DEV_ID_SHIFT should be adjusted
     203                 :            :          * accordingly (which would impact the max number of MSI
     204                 :            :          * capable devices).
     205                 :            :          */
     206   [ #  #  #  #  :          0 :         if (!dev->msi_domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS)
                   #  # ]
     207                 :            :                 return ERR_PTR(-EINVAL);
     208                 :            : 
     209         [ #  # ]:          0 :         if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
     210                 :          0 :                 dev_err(dev, "Incompatible msi_domain, giving up\n");
     211                 :          0 :                 return ERR_PTR(-EINVAL);
     212                 :            :         }
     213                 :            : 
     214                 :            :         /* Already had a helping of MSI? Greed... */
     215         [ #  # ]:          0 :         if (!list_empty(dev_to_msi_list(dev)))
     216                 :            :                 return ERR_PTR(-EBUSY);
     217                 :            : 
     218                 :          0 :         datap = kzalloc(sizeof(*datap), GFP_KERNEL);
     219         [ #  # ]:          0 :         if (!datap)
     220                 :            :                 return ERR_PTR(-ENOMEM);
     221                 :            : 
     222                 :          0 :         datap->devid = ida_simple_get(&platform_msi_devid_ida,
     223                 :            :                                       0, 1 << DEV_ID_SHIFT, GFP_KERNEL);
     224         [ #  # ]:          0 :         if (datap->devid < 0) {
     225                 :          0 :                 int err = datap->devid;
     226                 :          0 :                 kfree(datap);
     227                 :          0 :                 return ERR_PTR(err);
     228                 :            :         }
     229                 :            : 
     230                 :          0 :         datap->write_msg = write_msi_msg;
     231                 :          0 :         datap->dev = dev;
     232                 :            : 
     233                 :          0 :         return datap;
     234                 :            : }
     235                 :            : 
     236                 :          0 : static void platform_msi_free_priv_data(struct platform_msi_priv_data *data)
     237                 :            : {
     238                 :          0 :         ida_simple_remove(&platform_msi_devid_ida, data->devid);
     239                 :          0 :         kfree(data);
     240                 :          0 : }
     241                 :            : 
     242                 :            : /**
     243                 :            :  * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
     244                 :            :  * @dev:                The device for which to allocate interrupts
     245                 :            :  * @nvec:               The number of interrupts to allocate
     246                 :            :  * @write_msi_msg:      Callback to write an interrupt message for @dev
     247                 :            :  *
     248                 :            :  * Returns:
     249                 :            :  * Zero for success, or an error code in case of failure
     250                 :            :  */
     251                 :          0 : int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
     252                 :            :                                    irq_write_msi_msg_t write_msi_msg)
     253                 :            : {
     254                 :          0 :         struct platform_msi_priv_data *priv_data;
     255                 :          0 :         int err;
     256                 :            : 
     257                 :          0 :         priv_data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
     258         [ #  # ]:          0 :         if (IS_ERR(priv_data))
     259                 :          0 :                 return PTR_ERR(priv_data);
     260                 :            : 
     261                 :          0 :         err = platform_msi_alloc_descs(dev, nvec, priv_data);
     262         [ #  # ]:          0 :         if (err)
     263                 :          0 :                 goto out_free_priv_data;
     264                 :            : 
     265                 :          0 :         err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec);
     266         [ #  # ]:          0 :         if (err)
     267                 :          0 :                 goto out_free_desc;
     268                 :            : 
     269                 :            :         return 0;
     270                 :            : 
     271                 :            : out_free_desc:
     272                 :          0 :         platform_msi_free_descs(dev, 0, nvec);
     273                 :          0 : out_free_priv_data:
     274                 :          0 :         platform_msi_free_priv_data(priv_data);
     275                 :            : 
     276                 :          0 :         return err;
     277                 :            : }
     278                 :            : EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs);
     279                 :            : 
     280                 :            : /**
     281                 :            :  * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
     282                 :            :  * @dev:        The device for which to free interrupts
     283                 :            :  */
     284                 :          0 : void platform_msi_domain_free_irqs(struct device *dev)
     285                 :            : {
     286         [ #  # ]:          0 :         if (!list_empty(dev_to_msi_list(dev))) {
     287                 :          0 :                 struct msi_desc *desc;
     288                 :            : 
     289                 :          0 :                 desc = first_msi_entry(dev);
     290                 :          0 :                 platform_msi_free_priv_data(desc->platform.msi_priv_data);
     291                 :            :         }
     292                 :            : 
     293                 :          0 :         msi_domain_free_irqs(dev->msi_domain, dev);
     294                 :          0 :         platform_msi_free_descs(dev, 0, MAX_DEV_MSIS);
     295                 :          0 : }
     296                 :            : EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs);
     297                 :            : 
     298                 :            : /**
     299                 :            :  * platform_msi_get_host_data - Query the private data associated with
     300                 :            :  *                              a platform-msi domain
     301                 :            :  * @domain:     The platform-msi domain
     302                 :            :  *
     303                 :            :  * Returns the private data provided when calling
     304                 :            :  * platform_msi_create_device_domain.
     305                 :            :  */
     306                 :          0 : void *platform_msi_get_host_data(struct irq_domain *domain)
     307                 :            : {
     308                 :          0 :         struct platform_msi_priv_data *data = domain->host_data;
     309                 :          0 :         return data->host_data;
     310                 :            : }
     311                 :            : 
     312                 :            : /**
     313                 :            :  * platform_msi_create_device_domain - Create a platform-msi domain
     314                 :            :  *
     315                 :            :  * @dev:                The device generating the MSIs
     316                 :            :  * @nvec:               The number of MSIs that need to be allocated
     317                 :            :  * @write_msi_msg:      Callback to write an interrupt message for @dev
     318                 :            :  * @ops:                The hierarchy domain operations to use
     319                 :            :  * @host_data:          Private data associated to this domain
     320                 :            :  *
     321                 :            :  * Returns an irqdomain for @nvec interrupts
     322                 :            :  */
     323                 :            : struct irq_domain *
     324                 :          0 : __platform_msi_create_device_domain(struct device *dev,
     325                 :            :                                     unsigned int nvec,
     326                 :            :                                     bool is_tree,
     327                 :            :                                     irq_write_msi_msg_t write_msi_msg,
     328                 :            :                                     const struct irq_domain_ops *ops,
     329                 :            :                                     void *host_data)
     330                 :            : {
     331                 :          0 :         struct platform_msi_priv_data *data;
     332                 :          0 :         struct irq_domain *domain;
     333                 :          0 :         int err;
     334                 :            : 
     335                 :          0 :         data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
     336         [ #  # ]:          0 :         if (IS_ERR(data))
     337                 :            :                 return NULL;
     338                 :            : 
     339                 :          0 :         data->host_data = host_data;
     340         [ #  # ]:          0 :         domain = irq_domain_create_hierarchy(dev->msi_domain, 0,
     341                 :            :                                              is_tree ? 0 : nvec,
     342                 :            :                                              dev->fwnode, ops, data);
     343         [ #  # ]:          0 :         if (!domain)
     344                 :          0 :                 goto free_priv;
     345                 :            : 
     346                 :          0 :         err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg);
     347         [ #  # ]:          0 :         if (err)
     348                 :          0 :                 goto free_domain;
     349                 :            : 
     350                 :            :         return domain;
     351                 :            : 
     352                 :            : free_domain:
     353                 :          0 :         irq_domain_remove(domain);
     354                 :          0 : free_priv:
     355                 :          0 :         platform_msi_free_priv_data(data);
     356                 :          0 :         return NULL;
     357                 :            : }
     358                 :            : 
     359                 :            : /**
     360                 :            :  * platform_msi_domain_free - Free interrupts associated with a platform-msi
     361                 :            :  *                            domain
     362                 :            :  *
     363                 :            :  * @domain:     The platform-msi domain
     364                 :            :  * @virq:       The base irq from which to perform the free operation
     365                 :            :  * @nvec:       How many interrupts to free from @virq
     366                 :            :  */
     367                 :          0 : void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq,
     368                 :            :                               unsigned int nvec)
     369                 :            : {
     370                 :          0 :         struct platform_msi_priv_data *data = domain->host_data;
     371                 :          0 :         struct msi_desc *desc, *tmp;
     372         [ #  # ]:          0 :         for_each_msi_entry_safe(desc, tmp, data->dev) {
     373   [ #  #  #  #  :          0 :                 if (WARN_ON(!desc->irq || desc->nvec_used != 1))
             #  #  #  # ]
     374                 :            :                         return;
     375   [ #  #  #  # ]:          0 :                 if (!(desc->irq >= virq && desc->irq < (virq + nvec)))
     376                 :          0 :                         continue;
     377                 :            : 
     378                 :          0 :                 irq_domain_free_irqs_common(domain, desc->irq, 1);
     379                 :          0 :                 list_del(&desc->list);
     380                 :          0 :                 free_msi_entry(desc);
     381                 :            :         }
     382                 :            : }
     383                 :            : 
     384                 :            : /**
     385                 :            :  * platform_msi_domain_alloc - Allocate interrupts associated with
     386                 :            :  *                             a platform-msi domain
     387                 :            :  *
     388                 :            :  * @domain:     The platform-msi domain
     389                 :            :  * @virq:       The base irq from which to perform the allocate operation
     390                 :            :  * @nvec:       How many interrupts to free from @virq
     391                 :            :  *
     392                 :            :  * Return 0 on success, or an error code on failure. Must be called
     393                 :            :  * with irq_domain_mutex held (which can only be done as part of a
     394                 :            :  * top-level interrupt allocation).
     395                 :            :  */
     396                 :          0 : int platform_msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
     397                 :            :                               unsigned int nr_irqs)
     398                 :            : {
     399                 :          0 :         struct platform_msi_priv_data *data = domain->host_data;
     400                 :          0 :         int err;
     401                 :            : 
     402                 :          0 :         err = platform_msi_alloc_descs_with_irq(data->dev, virq, nr_irqs, data);
     403         [ #  # ]:          0 :         if (err)
     404                 :            :                 return err;
     405                 :            : 
     406                 :          0 :         err = msi_domain_populate_irqs(domain->parent, data->dev,
     407                 :            :                                        virq, nr_irqs, &data->arg);
     408         [ #  # ]:          0 :         if (err)
     409                 :          0 :                 platform_msi_domain_free(domain, virq, nr_irqs);
     410                 :            : 
     411                 :            :         return err;
     412                 :            : }

Generated by: LCOV version 1.14