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