152a65ff5SThomas Gleixner // SPDX-License-Identifier: GPL-2.0 2f3cf8bb0SJiang Liu /* 3f3cf8bb0SJiang Liu * Copyright (C) 2014 Intel Corp. 4f3cf8bb0SJiang Liu * Author: Jiang Liu <jiang.liu@linux.intel.com> 5f3cf8bb0SJiang Liu * 6f3cf8bb0SJiang Liu * This file is licensed under GPLv2. 7f3cf8bb0SJiang Liu * 8a359f757SIngo Molnar * This file contains common code to support Message Signaled Interrupts for 9f3cf8bb0SJiang Liu * PCI compatible and non PCI compatible devices. 10f3cf8bb0SJiang Liu */ 11aeeb5965SJiang Liu #include <linux/types.h> 12aeeb5965SJiang Liu #include <linux/device.h> 13f3cf8bb0SJiang Liu #include <linux/irq.h> 14f3cf8bb0SJiang Liu #include <linux/irqdomain.h> 15f3cf8bb0SJiang Liu #include <linux/msi.h> 164e201566SMarc Zyngier #include <linux/slab.h> 173ba1f050SThomas Gleixner #include <linux/sysfs.h> 182f170814SBarry Song #include <linux/pci.h> 19d9109698SJiang Liu 2007557ccbSThomas Gleixner #include "internals.h" 2107557ccbSThomas Gleixner 22377712c5SThomas Gleixner /** 23377712c5SThomas Gleixner * struct msi_ctrl - MSI internal management control structure 24377712c5SThomas Gleixner * @domid: ID of the domain on which management operations should be done 25377712c5SThomas Gleixner * @first: First (hardware) slot index to operate on 26377712c5SThomas Gleixner * @last: Last (hardware) slot index to operate on 27f2480e7dSThomas Gleixner * @nirqs: The number of Linux interrupts to allocate. Can be larger 28f2480e7dSThomas Gleixner * than the range due to PCI/multi-MSI. 29377712c5SThomas Gleixner */ 30377712c5SThomas Gleixner struct msi_ctrl { 31377712c5SThomas Gleixner unsigned int domid; 32377712c5SThomas Gleixner unsigned int first; 33377712c5SThomas Gleixner unsigned int last; 34f2480e7dSThomas Gleixner unsigned int nirqs; 35377712c5SThomas Gleixner }; 36377712c5SThomas Gleixner 3794ff94cfSThomas Gleixner /* Invalid Xarray index which is outside of any searchable range */ 3894ff94cfSThomas Gleixner #define MSI_XA_MAX_INDEX (ULONG_MAX - 1) 3994ff94cfSThomas Gleixner /* The maximum domain size */ 4094ff94cfSThomas Gleixner #define MSI_XA_DOMAIN_SIZE (MSI_MAX_INDEX + 1) 4194ff94cfSThomas Gleixner 42f2480e7dSThomas Gleixner static void msi_domain_free_locked(struct device *dev, struct msi_ctrl *ctrl); 43*36db3d90SThomas Gleixner static unsigned int msi_domain_get_hwsize(struct device *dev, unsigned int domid); 44bf5e758fSThomas Gleixner static inline int msi_sysfs_create_group(struct device *dev); 45cc9a246dSThomas Gleixner 4694ff94cfSThomas Gleixner 4728f4b041SThomas Gleixner /** 48cc9a246dSThomas Gleixner * msi_alloc_desc - Allocate an initialized msi_desc 4928f4b041SThomas Gleixner * @dev: Pointer to the device for which this is allocated 5028f4b041SThomas Gleixner * @nvec: The number of vectors used in this entry 5128f4b041SThomas Gleixner * @affinity: Optional pointer to an affinity mask array size of @nvec 5228f4b041SThomas Gleixner * 533b35e7e6SRandy Dunlap * If @affinity is not %NULL then an affinity array[@nvec] is allocated 54bec04037SDou Liyang * and the affinity masks and flags from @affinity are copied. 553b35e7e6SRandy Dunlap * 563b35e7e6SRandy Dunlap * Return: pointer to allocated &msi_desc on success or %NULL on failure 5728f4b041SThomas Gleixner */ 58cc9a246dSThomas Gleixner static struct msi_desc *msi_alloc_desc(struct device *dev, int nvec, 59bec04037SDou Liyang const struct irq_affinity_desc *affinity) 60aa48b6f7SJiang Liu { 61cc9a246dSThomas Gleixner struct msi_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL); 6228f4b041SThomas Gleixner 63aa48b6f7SJiang Liu if (!desc) 64aa48b6f7SJiang Liu return NULL; 65aa48b6f7SJiang Liu 66aa48b6f7SJiang Liu desc->dev = dev; 6728f4b041SThomas Gleixner desc->nvec_used = nvec; 6828f4b041SThomas Gleixner if (affinity) { 69cc9a246dSThomas Gleixner desc->affinity = kmemdup(affinity, nvec * sizeof(*desc->affinity), GFP_KERNEL); 7028f4b041SThomas Gleixner if (!desc->affinity) { 7128f4b041SThomas Gleixner kfree(desc); 7228f4b041SThomas Gleixner return NULL; 7328f4b041SThomas Gleixner } 7428f4b041SThomas Gleixner } 75aa48b6f7SJiang Liu return desc; 76aa48b6f7SJiang Liu } 77aa48b6f7SJiang Liu 78cc9a246dSThomas Gleixner static void msi_free_desc(struct msi_desc *desc) 79aa48b6f7SJiang Liu { 80cc9a246dSThomas Gleixner kfree(desc->affinity); 81cc9a246dSThomas Gleixner kfree(desc); 82aa48b6f7SJiang Liu } 83aa48b6f7SJiang Liu 84*36db3d90SThomas Gleixner static int msi_insert_desc(struct device *dev, struct msi_desc *desc, 85fc8ab388SThomas Gleixner unsigned int domid, unsigned int index) 86cd6cf065SThomas Gleixner { 87*36db3d90SThomas Gleixner struct msi_device_data *md = dev->msi.data; 88fc8ab388SThomas Gleixner struct xarray *xa = &md->__domains[domid].store; 89*36db3d90SThomas Gleixner unsigned int hwsize; 90cd6cf065SThomas Gleixner int ret; 91cd6cf065SThomas Gleixner 92*36db3d90SThomas Gleixner hwsize = msi_domain_get_hwsize(dev, domid); 93*36db3d90SThomas Gleixner if (index >= hwsize) { 94*36db3d90SThomas Gleixner ret = -ERANGE; 95*36db3d90SThomas Gleixner goto fail; 96*36db3d90SThomas Gleixner } 97*36db3d90SThomas Gleixner 98cd6cf065SThomas Gleixner desc->msi_index = index; 99f1139f90SThomas Gleixner ret = xa_insert(xa, index, desc, GFP_KERNEL); 100cd6cf065SThomas Gleixner if (ret) 101*36db3d90SThomas Gleixner goto fail; 102*36db3d90SThomas Gleixner return 0; 103*36db3d90SThomas Gleixner 104*36db3d90SThomas Gleixner fail: 105cd6cf065SThomas Gleixner msi_free_desc(desc); 106cd6cf065SThomas Gleixner return ret; 107cd6cf065SThomas Gleixner } 108cd6cf065SThomas Gleixner 10960290525SThomas Gleixner /** 110fc8ab388SThomas Gleixner * msi_domain_insert_msi_desc - Allocate and initialize a MSI descriptor and 1111c893963SThomas Gleixner * insert it at @init_desc->msi_index 1121c893963SThomas Gleixner * 11360290525SThomas Gleixner * @dev: Pointer to the device for which the descriptor is allocated 114fc8ab388SThomas Gleixner * @domid: The id of the interrupt domain to which the desriptor is added 11560290525SThomas Gleixner * @init_desc: Pointer to an MSI descriptor to initialize the new descriptor 11660290525SThomas Gleixner * 11760290525SThomas Gleixner * Return: 0 on success or an appropriate failure code. 11860290525SThomas Gleixner */ 119fc8ab388SThomas Gleixner int msi_domain_insert_msi_desc(struct device *dev, unsigned int domid, 120fc8ab388SThomas Gleixner struct msi_desc *init_desc) 12160290525SThomas Gleixner { 12260290525SThomas Gleixner struct msi_desc *desc; 12360290525SThomas Gleixner 12460290525SThomas Gleixner lockdep_assert_held(&dev->msi.data->mutex); 12560290525SThomas Gleixner 126cc9a246dSThomas Gleixner desc = msi_alloc_desc(dev, init_desc->nvec_used, init_desc->affinity); 12760290525SThomas Gleixner if (!desc) 12860290525SThomas Gleixner return -ENOMEM; 12960290525SThomas Gleixner 130cd6cf065SThomas Gleixner /* Copy type specific data to the new descriptor. */ 13160290525SThomas Gleixner desc->pci = init_desc->pci; 132fc8ab388SThomas Gleixner 133*36db3d90SThomas Gleixner return msi_insert_desc(dev, desc, domid, init_desc->msi_index); 13460290525SThomas Gleixner } 13560290525SThomas Gleixner 136cd6cf065SThomas Gleixner static bool msi_desc_match(struct msi_desc *desc, enum msi_desc_filter filter) 137cd6cf065SThomas Gleixner { 138cd6cf065SThomas Gleixner switch (filter) { 139cd6cf065SThomas Gleixner case MSI_DESC_ALL: 140cd6cf065SThomas Gleixner return true; 141cd6cf065SThomas Gleixner case MSI_DESC_NOTASSOCIATED: 142cd6cf065SThomas Gleixner return !desc->irq; 143cd6cf065SThomas Gleixner case MSI_DESC_ASSOCIATED: 144cd6cf065SThomas Gleixner return !!desc->irq; 145cd6cf065SThomas Gleixner } 146cd6cf065SThomas Gleixner WARN_ON_ONCE(1); 147cd6cf065SThomas Gleixner return false; 14860290525SThomas Gleixner } 14960290525SThomas Gleixner 150377712c5SThomas Gleixner static bool msi_ctrl_valid(struct device *dev, struct msi_ctrl *ctrl) 151645474e2SThomas Gleixner { 152*36db3d90SThomas Gleixner unsigned int hwsize; 153*36db3d90SThomas Gleixner 154377712c5SThomas Gleixner if (WARN_ON_ONCE(ctrl->domid >= MSI_MAX_DEVICE_IRQDOMAINS || 155*36db3d90SThomas Gleixner !dev->msi.data->__domains[ctrl->domid].domain)) 156*36db3d90SThomas Gleixner return false; 157*36db3d90SThomas Gleixner 158*36db3d90SThomas Gleixner hwsize = msi_domain_get_hwsize(dev, ctrl->domid); 159*36db3d90SThomas Gleixner if (WARN_ON_ONCE(ctrl->first > ctrl->last || 160*36db3d90SThomas Gleixner ctrl->first >= hwsize || 161*36db3d90SThomas Gleixner ctrl->last >= hwsize)) 162377712c5SThomas Gleixner return false; 163377712c5SThomas Gleixner return true; 164377712c5SThomas Gleixner } 165377712c5SThomas Gleixner 166377712c5SThomas Gleixner static void msi_domain_free_descs(struct device *dev, struct msi_ctrl *ctrl) 167377712c5SThomas Gleixner { 168645474e2SThomas Gleixner struct msi_desc *desc; 169377712c5SThomas Gleixner struct xarray *xa; 170cd6cf065SThomas Gleixner unsigned long idx; 171645474e2SThomas Gleixner 172645474e2SThomas Gleixner lockdep_assert_held(&dev->msi.data->mutex); 173645474e2SThomas Gleixner 174377712c5SThomas Gleixner if (!msi_ctrl_valid(dev, ctrl)) 175377712c5SThomas Gleixner return; 176377712c5SThomas Gleixner 177377712c5SThomas Gleixner xa = &dev->msi.data->__domains[ctrl->domid].store; 178377712c5SThomas Gleixner xa_for_each_range(xa, idx, desc, ctrl->first, ctrl->last) { 179cd6cf065SThomas Gleixner xa_erase(xa, idx); 1802f2940d1SThomas Gleixner 1812f2940d1SThomas Gleixner /* Leak the descriptor when it is still referenced */ 1822f2940d1SThomas Gleixner if (WARN_ON_ONCE(msi_desc_match(desc, MSI_DESC_ASSOCIATED))) 1832f2940d1SThomas Gleixner continue; 184cc9a246dSThomas Gleixner msi_free_desc(desc); 185645474e2SThomas Gleixner } 186645474e2SThomas Gleixner } 187645474e2SThomas Gleixner 188377712c5SThomas Gleixner /** 189377712c5SThomas Gleixner * msi_domain_free_msi_descs_range - Free a range of MSI descriptors of a device in an irqdomain 190377712c5SThomas Gleixner * @dev: Device for which to free the descriptors 191377712c5SThomas Gleixner * @domid: Id of the domain to operate on 192377712c5SThomas Gleixner * @first: Index to start freeing from (inclusive) 193377712c5SThomas Gleixner * @last: Last index to be freed (inclusive) 194377712c5SThomas Gleixner */ 195377712c5SThomas Gleixner void msi_domain_free_msi_descs_range(struct device *dev, unsigned int domid, 196377712c5SThomas Gleixner unsigned int first, unsigned int last) 197377712c5SThomas Gleixner { 198377712c5SThomas Gleixner struct msi_ctrl ctrl = { 199377712c5SThomas Gleixner .domid = domid, 200377712c5SThomas Gleixner .first = first, 201377712c5SThomas Gleixner .last = last, 202377712c5SThomas Gleixner }; 203377712c5SThomas Gleixner 204377712c5SThomas Gleixner msi_domain_free_descs(dev, &ctrl); 205377712c5SThomas Gleixner } 206377712c5SThomas Gleixner 20740742716SThomas Gleixner /** 20840742716SThomas Gleixner * msi_domain_add_simple_msi_descs - Allocate and initialize MSI descriptors 20940742716SThomas Gleixner * @dev: Pointer to the device for which the descriptors are allocated 21040742716SThomas Gleixner * @ctrl: Allocation control struct 21140742716SThomas Gleixner * 21240742716SThomas Gleixner * Return: 0 on success or an appropriate failure code. 21340742716SThomas Gleixner */ 21440742716SThomas Gleixner static int msi_domain_add_simple_msi_descs(struct device *dev, struct msi_ctrl *ctrl) 21540742716SThomas Gleixner { 21640742716SThomas Gleixner struct msi_desc *desc; 21740742716SThomas Gleixner unsigned int idx; 21840742716SThomas Gleixner int ret; 21940742716SThomas Gleixner 22040742716SThomas Gleixner lockdep_assert_held(&dev->msi.data->mutex); 22140742716SThomas Gleixner 22240742716SThomas Gleixner if (!msi_ctrl_valid(dev, ctrl)) 22340742716SThomas Gleixner return -EINVAL; 22440742716SThomas Gleixner 22540742716SThomas Gleixner for (idx = ctrl->first; idx <= ctrl->last; idx++) { 22640742716SThomas Gleixner desc = msi_alloc_desc(dev, 1, NULL); 22740742716SThomas Gleixner if (!desc) 22840742716SThomas Gleixner goto fail_mem; 229*36db3d90SThomas Gleixner ret = msi_insert_desc(dev, desc, ctrl->domid, idx); 23040742716SThomas Gleixner if (ret) 23140742716SThomas Gleixner goto fail; 23240742716SThomas Gleixner } 23340742716SThomas Gleixner return 0; 23440742716SThomas Gleixner 23540742716SThomas Gleixner fail_mem: 23640742716SThomas Gleixner ret = -ENOMEM; 23740742716SThomas Gleixner fail: 23840742716SThomas Gleixner msi_domain_free_descs(dev, ctrl); 23940742716SThomas Gleixner return ret; 24040742716SThomas Gleixner } 24140742716SThomas Gleixner 24238b6a1cfSJiang Liu void __get_cached_msi_msg(struct msi_desc *entry, struct msi_msg *msg) 24338b6a1cfSJiang Liu { 24438b6a1cfSJiang Liu *msg = entry->msg; 24538b6a1cfSJiang Liu } 24638b6a1cfSJiang Liu 24738b6a1cfSJiang Liu void get_cached_msi_msg(unsigned int irq, struct msi_msg *msg) 24838b6a1cfSJiang Liu { 24938b6a1cfSJiang Liu struct msi_desc *entry = irq_get_msi_desc(irq); 25038b6a1cfSJiang Liu 25138b6a1cfSJiang Liu __get_cached_msi_msg(entry, msg); 25238b6a1cfSJiang Liu } 25338b6a1cfSJiang Liu EXPORT_SYMBOL_GPL(get_cached_msi_msg); 25438b6a1cfSJiang Liu 255013bd8e5SThomas Gleixner static void msi_device_data_release(struct device *dev, void *res) 256013bd8e5SThomas Gleixner { 257125282cdSThomas Gleixner struct msi_device_data *md = res; 258f1139f90SThomas Gleixner int i; 259125282cdSThomas Gleixner 260f1139f90SThomas Gleixner for (i = 0; i < MSI_MAX_DEVICE_IRQDOMAINS; i++) { 26127a6dea3SThomas Gleixner msi_remove_device_irq_domain(dev, i); 262f1139f90SThomas Gleixner WARN_ON_ONCE(!xa_empty(&md->__domains[i].store)); 263f1139f90SThomas Gleixner xa_destroy(&md->__domains[i].store); 264f1139f90SThomas Gleixner } 265013bd8e5SThomas Gleixner dev->msi.data = NULL; 266013bd8e5SThomas Gleixner } 267013bd8e5SThomas Gleixner 268013bd8e5SThomas Gleixner /** 269013bd8e5SThomas Gleixner * msi_setup_device_data - Setup MSI device data 270013bd8e5SThomas Gleixner * @dev: Device for which MSI device data should be set up 271013bd8e5SThomas Gleixner * 272013bd8e5SThomas Gleixner * Return: 0 on success, appropriate error code otherwise 273013bd8e5SThomas Gleixner * 274013bd8e5SThomas Gleixner * This can be called more than once for @dev. If the MSI device data is 275013bd8e5SThomas Gleixner * already allocated the call succeeds. The allocated memory is 276013bd8e5SThomas Gleixner * automatically released when the device is destroyed. 277013bd8e5SThomas Gleixner */ 278013bd8e5SThomas Gleixner int msi_setup_device_data(struct device *dev) 279013bd8e5SThomas Gleixner { 280013bd8e5SThomas Gleixner struct msi_device_data *md; 281f1139f90SThomas Gleixner int ret, i; 282013bd8e5SThomas Gleixner 283013bd8e5SThomas Gleixner if (dev->msi.data) 284013bd8e5SThomas Gleixner return 0; 285013bd8e5SThomas Gleixner 286013bd8e5SThomas Gleixner md = devres_alloc(msi_device_data_release, sizeof(*md), GFP_KERNEL); 287013bd8e5SThomas Gleixner if (!md) 288013bd8e5SThomas Gleixner return -ENOMEM; 289013bd8e5SThomas Gleixner 290bf5e758fSThomas Gleixner ret = msi_sysfs_create_group(dev); 291bf5e758fSThomas Gleixner if (ret) { 292bf5e758fSThomas Gleixner devres_free(md); 293bf5e758fSThomas Gleixner return ret; 294bf5e758fSThomas Gleixner } 295bf5e758fSThomas Gleixner 296f1139f90SThomas Gleixner for (i = 0; i < MSI_MAX_DEVICE_IRQDOMAINS; i++) 297f1139f90SThomas Gleixner xa_init(&md->__domains[i].store); 298f1139f90SThomas Gleixner 29964258eaaSThomas Gleixner /* 30064258eaaSThomas Gleixner * If @dev::msi::domain is set and is a global MSI domain, copy the 30164258eaaSThomas Gleixner * pointer into the domain array so all code can operate on domain 30264258eaaSThomas Gleixner * ids. The NULL pointer check is required to keep the legacy 30364258eaaSThomas Gleixner * architecture specific PCI/MSI support working. 30464258eaaSThomas Gleixner */ 30564258eaaSThomas Gleixner if (dev->msi.domain && !irq_domain_is_msi_parent(dev->msi.domain)) 30664258eaaSThomas Gleixner md->__domains[MSI_DEFAULT_DOMAIN].domain = dev->msi.domain; 30764258eaaSThomas Gleixner 308b5f687f9SThomas Gleixner mutex_init(&md->mutex); 309013bd8e5SThomas Gleixner dev->msi.data = md; 310013bd8e5SThomas Gleixner devres_add(dev, md); 311013bd8e5SThomas Gleixner return 0; 312013bd8e5SThomas Gleixner } 313013bd8e5SThomas Gleixner 314cf15f43aSThomas Gleixner /** 315b5f687f9SThomas Gleixner * msi_lock_descs - Lock the MSI descriptor storage of a device 316b5f687f9SThomas Gleixner * @dev: Device to operate on 317b5f687f9SThomas Gleixner */ 318b5f687f9SThomas Gleixner void msi_lock_descs(struct device *dev) 319b5f687f9SThomas Gleixner { 320b5f687f9SThomas Gleixner mutex_lock(&dev->msi.data->mutex); 321b5f687f9SThomas Gleixner } 322b5f687f9SThomas Gleixner EXPORT_SYMBOL_GPL(msi_lock_descs); 323b5f687f9SThomas Gleixner 324b5f687f9SThomas Gleixner /** 325b5f687f9SThomas Gleixner * msi_unlock_descs - Unlock the MSI descriptor storage of a device 326b5f687f9SThomas Gleixner * @dev: Device to operate on 327b5f687f9SThomas Gleixner */ 328b5f687f9SThomas Gleixner void msi_unlock_descs(struct device *dev) 329b5f687f9SThomas Gleixner { 330f1139f90SThomas Gleixner /* Invalidate the index which was cached by the iterator */ 33194ff94cfSThomas Gleixner dev->msi.data->__iter_idx = MSI_XA_MAX_INDEX; 332b5f687f9SThomas Gleixner mutex_unlock(&dev->msi.data->mutex); 333b5f687f9SThomas Gleixner } 334b5f687f9SThomas Gleixner EXPORT_SYMBOL_GPL(msi_unlock_descs); 335b5f687f9SThomas Gleixner 33694ff94cfSThomas Gleixner static struct msi_desc *msi_find_desc(struct msi_device_data *md, unsigned int domid, 33794ff94cfSThomas Gleixner enum msi_desc_filter filter) 3381046f71dSThomas Gleixner { 33994ff94cfSThomas Gleixner struct xarray *xa = &md->__domains[domid].store; 3401046f71dSThomas Gleixner struct msi_desc *desc; 3411046f71dSThomas Gleixner 342f1139f90SThomas Gleixner xa_for_each_start(xa, md->__iter_idx, desc, md->__iter_idx) { 3431046f71dSThomas Gleixner if (msi_desc_match(desc, filter)) 3441046f71dSThomas Gleixner return desc; 3451046f71dSThomas Gleixner } 34694ff94cfSThomas Gleixner md->__iter_idx = MSI_XA_MAX_INDEX; 3471046f71dSThomas Gleixner return NULL; 3481046f71dSThomas Gleixner } 3491046f71dSThomas Gleixner 3501046f71dSThomas Gleixner /** 35194ff94cfSThomas Gleixner * msi_domain_first_desc - Get the first MSI descriptor of an irqdomain associated to a device 3521046f71dSThomas Gleixner * @dev: Device to operate on 35394ff94cfSThomas Gleixner * @domid: The id of the interrupt domain which should be walked. 3541046f71dSThomas Gleixner * @filter: Descriptor state filter 3551046f71dSThomas Gleixner * 3561046f71dSThomas Gleixner * Must be called with the MSI descriptor mutex held, i.e. msi_lock_descs() 3571046f71dSThomas Gleixner * must be invoked before the call. 3581046f71dSThomas Gleixner * 3591046f71dSThomas Gleixner * Return: Pointer to the first MSI descriptor matching the search 3601046f71dSThomas Gleixner * criteria, NULL if none found. 3611046f71dSThomas Gleixner */ 36294ff94cfSThomas Gleixner struct msi_desc *msi_domain_first_desc(struct device *dev, unsigned int domid, 36394ff94cfSThomas Gleixner enum msi_desc_filter filter) 3641046f71dSThomas Gleixner { 365cd6cf065SThomas Gleixner struct msi_device_data *md = dev->msi.data; 3661046f71dSThomas Gleixner 36794ff94cfSThomas Gleixner if (WARN_ON_ONCE(!md || domid >= MSI_MAX_DEVICE_IRQDOMAINS)) 3681046f71dSThomas Gleixner return NULL; 3691046f71dSThomas Gleixner 370cd6cf065SThomas Gleixner lockdep_assert_held(&md->mutex); 3711046f71dSThomas Gleixner 372cd6cf065SThomas Gleixner md->__iter_idx = 0; 37394ff94cfSThomas Gleixner return msi_find_desc(md, domid, filter); 3741046f71dSThomas Gleixner } 37594ff94cfSThomas Gleixner EXPORT_SYMBOL_GPL(msi_domain_first_desc); 3761046f71dSThomas Gleixner 3771046f71dSThomas Gleixner /** 3781046f71dSThomas Gleixner * msi_next_desc - Get the next MSI descriptor of a device 3791046f71dSThomas Gleixner * @dev: Device to operate on 38094ff94cfSThomas Gleixner * @domid: The id of the interrupt domain which should be walked. 381fdd53404SThomas Gleixner * @filter: Descriptor state filter 3821046f71dSThomas Gleixner * 3831046f71dSThomas Gleixner * The first invocation of msi_next_desc() has to be preceeded by a 384cd6cf065SThomas Gleixner * successful invocation of __msi_first_desc(). Consecutive invocations are 3851046f71dSThomas Gleixner * only valid if the previous one was successful. All these operations have 3861046f71dSThomas Gleixner * to be done within the same MSI mutex held region. 3871046f71dSThomas Gleixner * 3881046f71dSThomas Gleixner * Return: Pointer to the next MSI descriptor matching the search 3891046f71dSThomas Gleixner * criteria, NULL if none found. 3901046f71dSThomas Gleixner */ 39194ff94cfSThomas Gleixner struct msi_desc *msi_next_desc(struct device *dev, unsigned int domid, 39294ff94cfSThomas Gleixner enum msi_desc_filter filter) 3931046f71dSThomas Gleixner { 394cd6cf065SThomas Gleixner struct msi_device_data *md = dev->msi.data; 3951046f71dSThomas Gleixner 39694ff94cfSThomas Gleixner if (WARN_ON_ONCE(!md || domid >= MSI_MAX_DEVICE_IRQDOMAINS)) 3971046f71dSThomas Gleixner return NULL; 3981046f71dSThomas Gleixner 399cd6cf065SThomas Gleixner lockdep_assert_held(&md->mutex); 4001046f71dSThomas Gleixner 401cd6cf065SThomas Gleixner if (md->__iter_idx >= (unsigned long)MSI_MAX_INDEX) 4021046f71dSThomas Gleixner return NULL; 4031046f71dSThomas Gleixner 404cd6cf065SThomas Gleixner md->__iter_idx++; 40594ff94cfSThomas Gleixner return msi_find_desc(md, domid, filter); 4061046f71dSThomas Gleixner } 4071046f71dSThomas Gleixner EXPORT_SYMBOL_GPL(msi_next_desc); 4081046f71dSThomas Gleixner 409b5f687f9SThomas Gleixner /** 41098043704SAhmed S. Darwish * msi_domain_get_virq - Lookup the Linux interrupt number for a MSI index on a interrupt domain 411cf15f43aSThomas Gleixner * @dev: Device to operate on 41298043704SAhmed S. Darwish * @domid: Domain ID of the interrupt domain associated to the device 413cf15f43aSThomas Gleixner * @index: MSI interrupt index to look for (0-based) 414cf15f43aSThomas Gleixner * 415cf15f43aSThomas Gleixner * Return: The Linux interrupt number on success (> 0), 0 if not found 416cf15f43aSThomas Gleixner */ 41798043704SAhmed S. Darwish unsigned int msi_domain_get_virq(struct device *dev, unsigned int domid, unsigned int index) 418cf15f43aSThomas Gleixner { 419cf15f43aSThomas Gleixner struct msi_desc *desc; 420495c66acSThomas Gleixner unsigned int ret = 0; 42198043704SAhmed S. Darwish bool pcimsi = false; 422f1139f90SThomas Gleixner struct xarray *xa; 423cf15f43aSThomas Gleixner 424cf15f43aSThomas Gleixner if (!dev->msi.data) 425cf15f43aSThomas Gleixner return 0; 426cf15f43aSThomas Gleixner 42798043704SAhmed S. Darwish if (WARN_ON_ONCE(index > MSI_MAX_INDEX || domid >= MSI_MAX_DEVICE_IRQDOMAINS)) 42898043704SAhmed S. Darwish return 0; 42998043704SAhmed S. Darwish 43098043704SAhmed S. Darwish /* This check is only valid for the PCI default MSI domain */ 43198043704SAhmed S. Darwish if (dev_is_pci(dev) && domid == MSI_DEFAULT_DOMAIN) 43298043704SAhmed S. Darwish pcimsi = to_pci_dev(dev)->msi_enabled; 433cf15f43aSThomas Gleixner 434495c66acSThomas Gleixner msi_lock_descs(dev); 43598043704SAhmed S. Darwish xa = &dev->msi.data->__domains[domid].store; 436f1139f90SThomas Gleixner desc = xa_load(xa, pcimsi ? 0 : index); 437cd6cf065SThomas Gleixner if (desc && desc->irq) { 438cf15f43aSThomas Gleixner /* 439cd6cf065SThomas Gleixner * PCI-MSI has only one descriptor for multiple interrupts. 440cf15f43aSThomas Gleixner * PCI-MSIX and platform MSI use a descriptor per 441cf15f43aSThomas Gleixner * interrupt. 442cf15f43aSThomas Gleixner */ 443cd6cf065SThomas Gleixner if (pcimsi) { 444cd6cf065SThomas Gleixner if (index < desc->nvec_used) 445cd6cf065SThomas Gleixner ret = desc->irq + index; 446cd6cf065SThomas Gleixner } else { 447495c66acSThomas Gleixner ret = desc->irq; 448cf15f43aSThomas Gleixner } 449495c66acSThomas Gleixner } 45098043704SAhmed S. Darwish 451495c66acSThomas Gleixner msi_unlock_descs(dev); 452495c66acSThomas Gleixner return ret; 453cf15f43aSThomas Gleixner } 45498043704SAhmed S. Darwish EXPORT_SYMBOL_GPL(msi_domain_get_virq); 455cf15f43aSThomas Gleixner 4561197528aSThomas Gleixner #ifdef CONFIG_SYSFS 457bf5e758fSThomas Gleixner static struct attribute *msi_dev_attrs[] = { 458bf5e758fSThomas Gleixner NULL 459bf5e758fSThomas Gleixner }; 460bf5e758fSThomas Gleixner 461bf5e758fSThomas Gleixner static const struct attribute_group msi_irqs_group = { 462bf5e758fSThomas Gleixner .name = "msi_irqs", 463bf5e758fSThomas Gleixner .attrs = msi_dev_attrs, 464bf5e758fSThomas Gleixner }; 465bf5e758fSThomas Gleixner 466bf5e758fSThomas Gleixner static inline int msi_sysfs_create_group(struct device *dev) 467bf5e758fSThomas Gleixner { 468bf5e758fSThomas Gleixner return devm_device_add_group(dev, &msi_irqs_group); 469bf5e758fSThomas Gleixner } 470bf5e758fSThomas Gleixner 4712f170814SBarry Song static ssize_t msi_mode_show(struct device *dev, struct device_attribute *attr, 4722f170814SBarry Song char *buf) 4732f170814SBarry Song { 4746ef7f771SThomas Gleixner /* MSI vs. MSIX is per device not per interrupt */ 4756ef7f771SThomas Gleixner bool is_msix = dev_is_pci(dev) ? to_pci_dev(dev)->msix_enabled : false; 4762f170814SBarry Song 4772f170814SBarry Song return sysfs_emit(buf, "%s\n", is_msix ? "msix" : "msi"); 4782f170814SBarry Song } 4792f170814SBarry Song 480bf5e758fSThomas Gleixner static void msi_sysfs_remove_desc(struct device *dev, struct msi_desc *desc) 4812f170814SBarry Song { 482bf5e758fSThomas Gleixner struct device_attribute *attrs = desc->sysfs_attrs; 4832f170814SBarry Song int i; 4842f170814SBarry Song 485bf5e758fSThomas Gleixner if (!attrs) 486bf5e758fSThomas Gleixner return; 4872f170814SBarry Song 488bf5e758fSThomas Gleixner desc->sysfs_attrs = NULL; 489bf5e758fSThomas Gleixner for (i = 0; i < desc->nvec_used; i++) { 490bf5e758fSThomas Gleixner if (attrs[i].show) 491bf5e758fSThomas Gleixner sysfs_remove_file_from_group(&dev->kobj, &attrs[i].attr, msi_irqs_group.name); 492bf5e758fSThomas Gleixner kfree(attrs[i].attr.name); 4932f170814SBarry Song } 494bf5e758fSThomas Gleixner kfree(attrs); 4952f170814SBarry Song } 4962f170814SBarry Song 497bf5e758fSThomas Gleixner static int msi_sysfs_populate_desc(struct device *dev, struct msi_desc *desc) 498bf5e758fSThomas Gleixner { 499bf5e758fSThomas Gleixner struct device_attribute *attrs; 500bf5e758fSThomas Gleixner int ret, i; 5012f170814SBarry Song 502bf5e758fSThomas Gleixner attrs = kcalloc(desc->nvec_used, sizeof(*attrs), GFP_KERNEL); 503bf5e758fSThomas Gleixner if (!attrs) 504bf5e758fSThomas Gleixner return -ENOMEM; 5052f170814SBarry Song 506bf5e758fSThomas Gleixner desc->sysfs_attrs = attrs; 507bf5e758fSThomas Gleixner for (i = 0; i < desc->nvec_used; i++) { 508bf5e758fSThomas Gleixner sysfs_attr_init(&attrs[i].attr); 509bf5e758fSThomas Gleixner attrs[i].attr.name = kasprintf(GFP_KERNEL, "%d", desc->irq + i); 510bf5e758fSThomas Gleixner if (!attrs[i].attr.name) { 511bf5e758fSThomas Gleixner ret = -ENOMEM; 512bf5e758fSThomas Gleixner goto fail; 5132f170814SBarry Song } 5142f170814SBarry Song 515bf5e758fSThomas Gleixner attrs[i].attr.mode = 0444; 516bf5e758fSThomas Gleixner attrs[i].show = msi_mode_show; 517bf5e758fSThomas Gleixner 518bf5e758fSThomas Gleixner ret = sysfs_add_file_to_group(&dev->kobj, &attrs[i].attr, msi_irqs_group.name); 519bf5e758fSThomas Gleixner if (ret) { 520bf5e758fSThomas Gleixner attrs[i].show = NULL; 521bf5e758fSThomas Gleixner goto fail; 522bf5e758fSThomas Gleixner } 523bf5e758fSThomas Gleixner } 524bf5e758fSThomas Gleixner return 0; 525bf5e758fSThomas Gleixner 526bf5e758fSThomas Gleixner fail: 527bf5e758fSThomas Gleixner msi_sysfs_remove_desc(dev, desc); 528bf5e758fSThomas Gleixner return ret; 529bf5e758fSThomas Gleixner } 530bf5e758fSThomas Gleixner 531bf5e758fSThomas Gleixner #ifdef CONFIG_PCI_MSI_ARCH_FALLBACKS 5322f170814SBarry Song /** 533bf6e054eSThomas Gleixner * msi_device_populate_sysfs - Populate msi_irqs sysfs entries for a device 534bf6e054eSThomas Gleixner * @dev: The device (PCI, platform etc) which will get sysfs entries 535bf6e054eSThomas Gleixner */ 536bf6e054eSThomas Gleixner int msi_device_populate_sysfs(struct device *dev) 537bf6e054eSThomas Gleixner { 538bf5e758fSThomas Gleixner struct msi_desc *desc; 539bf5e758fSThomas Gleixner int ret; 540bf6e054eSThomas Gleixner 541bf5e758fSThomas Gleixner msi_for_each_desc(desc, dev, MSI_DESC_ASSOCIATED) { 542bf5e758fSThomas Gleixner if (desc->sysfs_attrs) 543bf5e758fSThomas Gleixner continue; 544bf5e758fSThomas Gleixner ret = msi_sysfs_populate_desc(dev, desc); 545bf5e758fSThomas Gleixner if (ret) 546bf5e758fSThomas Gleixner return ret; 547bf5e758fSThomas Gleixner } 548bf6e054eSThomas Gleixner return 0; 549bf6e054eSThomas Gleixner } 550bf6e054eSThomas Gleixner 551bf6e054eSThomas Gleixner /** 55224cff375SThomas Gleixner * msi_device_destroy_sysfs - Destroy msi_irqs sysfs entries for a device 55324cff375SThomas Gleixner * @dev: The device (PCI, platform etc) for which to remove 55424cff375SThomas Gleixner * sysfs entries 5552f170814SBarry Song */ 55624cff375SThomas Gleixner void msi_device_destroy_sysfs(struct device *dev) 5572f170814SBarry Song { 558bf5e758fSThomas Gleixner struct msi_desc *desc; 5592f170814SBarry Song 560bf5e758fSThomas Gleixner msi_for_each_desc(desc, dev, MSI_DESC_ALL) 561bf5e758fSThomas Gleixner msi_sysfs_remove_desc(dev, desc); 5622f170814SBarry Song } 563bf5e758fSThomas Gleixner #endif /* CONFIG_PCI_MSI_ARCH_FALLBACK */ 564bf5e758fSThomas Gleixner #else /* CONFIG_SYSFS */ 565bf5e758fSThomas Gleixner static inline int msi_sysfs_create_group(struct device *dev) { return 0; } 566bf5e758fSThomas Gleixner static inline int msi_sysfs_populate_desc(struct device *dev, struct msi_desc *desc) { return 0; } 567bf5e758fSThomas Gleixner static inline void msi_sysfs_remove_desc(struct device *dev, struct msi_desc *desc) { } 568bf5e758fSThomas Gleixner #endif /* !CONFIG_SYSFS */ 5692f170814SBarry Song 5704cd5f440SThomas Gleixner static struct irq_domain *msi_get_device_domain(struct device *dev, unsigned int domid) 5714cd5f440SThomas Gleixner { 5724cd5f440SThomas Gleixner struct irq_domain *domain; 5734cd5f440SThomas Gleixner 5744cd5f440SThomas Gleixner lockdep_assert_held(&dev->msi.data->mutex); 5754cd5f440SThomas Gleixner 5764cd5f440SThomas Gleixner if (WARN_ON_ONCE(domid >= MSI_MAX_DEVICE_IRQDOMAINS)) 5774cd5f440SThomas Gleixner return NULL; 5784cd5f440SThomas Gleixner 5794cd5f440SThomas Gleixner domain = dev->msi.data->__domains[domid].domain; 5804cd5f440SThomas Gleixner if (!domain) 5814cd5f440SThomas Gleixner return NULL; 5824cd5f440SThomas Gleixner 5834cd5f440SThomas Gleixner if (WARN_ON_ONCE(irq_domain_is_msi_parent(domain))) 5844cd5f440SThomas Gleixner return NULL; 5854cd5f440SThomas Gleixner 5864cd5f440SThomas Gleixner return domain; 5874cd5f440SThomas Gleixner } 588762687ceSThomas Gleixner 589*36db3d90SThomas Gleixner static unsigned int msi_domain_get_hwsize(struct device *dev, unsigned int domid) 590*36db3d90SThomas Gleixner { 591*36db3d90SThomas Gleixner struct msi_domain_info *info; 592*36db3d90SThomas Gleixner struct irq_domain *domain; 593*36db3d90SThomas Gleixner 594*36db3d90SThomas Gleixner domain = msi_get_device_domain(dev, domid); 595*36db3d90SThomas Gleixner if (domain) { 596*36db3d90SThomas Gleixner info = domain->host_data; 597*36db3d90SThomas Gleixner return info->hwsize; 598*36db3d90SThomas Gleixner } 599*36db3d90SThomas Gleixner /* No domain, no size... */ 600*36db3d90SThomas Gleixner return 0; 601*36db3d90SThomas Gleixner } 602*36db3d90SThomas Gleixner 60374faaf7aSThomas Gleixner static inline void irq_chip_write_msi_msg(struct irq_data *data, 60474faaf7aSThomas Gleixner struct msi_msg *msg) 60574faaf7aSThomas Gleixner { 60674faaf7aSThomas Gleixner data->chip->irq_write_msi_msg(data, msg); 60774faaf7aSThomas Gleixner } 60874faaf7aSThomas Gleixner 6090be8153cSMarc Zyngier static void msi_check_level(struct irq_domain *domain, struct msi_msg *msg) 6100be8153cSMarc Zyngier { 6110be8153cSMarc Zyngier struct msi_domain_info *info = domain->host_data; 6120be8153cSMarc Zyngier 6130be8153cSMarc Zyngier /* 6140be8153cSMarc Zyngier * If the MSI provider has messed with the second message and 6150be8153cSMarc Zyngier * not advertized that it is level-capable, signal the breakage. 6160be8153cSMarc Zyngier */ 6170be8153cSMarc Zyngier WARN_ON(!((info->flags & MSI_FLAG_LEVEL_CAPABLE) && 6180be8153cSMarc Zyngier (info->chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI)) && 6190be8153cSMarc Zyngier (msg[1].address_lo || msg[1].address_hi || msg[1].data)); 6200be8153cSMarc Zyngier } 6210be8153cSMarc Zyngier 622f3cf8bb0SJiang Liu /** 623f3cf8bb0SJiang Liu * msi_domain_set_affinity - Generic affinity setter function for MSI domains 624f3cf8bb0SJiang Liu * @irq_data: The irq data associated to the interrupt 625f3cf8bb0SJiang Liu * @mask: The affinity mask to set 626f3cf8bb0SJiang Liu * @force: Flag to enforce setting (disable online checks) 627f3cf8bb0SJiang Liu * 628f3cf8bb0SJiang Liu * Intended to be used by MSI interrupt controllers which are 629f3cf8bb0SJiang Liu * implemented with hierarchical domains. 6303b35e7e6SRandy Dunlap * 6313b35e7e6SRandy Dunlap * Return: IRQ_SET_MASK_* result code 632f3cf8bb0SJiang Liu */ 633f3cf8bb0SJiang Liu int msi_domain_set_affinity(struct irq_data *irq_data, 634f3cf8bb0SJiang Liu const struct cpumask *mask, bool force) 635f3cf8bb0SJiang Liu { 636f3cf8bb0SJiang Liu struct irq_data *parent = irq_data->parent_data; 6370be8153cSMarc Zyngier struct msi_msg msg[2] = { [1] = { }, }; 638f3cf8bb0SJiang Liu int ret; 639f3cf8bb0SJiang Liu 640f3cf8bb0SJiang Liu ret = parent->chip->irq_set_affinity(parent, mask, force); 641f3cf8bb0SJiang Liu if (ret >= 0 && ret != IRQ_SET_MASK_OK_DONE) { 6420be8153cSMarc Zyngier BUG_ON(irq_chip_compose_msi_msg(irq_data, msg)); 6430be8153cSMarc Zyngier msi_check_level(irq_data->domain, msg); 6440be8153cSMarc Zyngier irq_chip_write_msi_msg(irq_data, msg); 645f3cf8bb0SJiang Liu } 646f3cf8bb0SJiang Liu 647f3cf8bb0SJiang Liu return ret; 648f3cf8bb0SJiang Liu } 649f3cf8bb0SJiang Liu 65072491643SThomas Gleixner static int msi_domain_activate(struct irq_domain *domain, 65172491643SThomas Gleixner struct irq_data *irq_data, bool early) 652f3cf8bb0SJiang Liu { 6530be8153cSMarc Zyngier struct msi_msg msg[2] = { [1] = { }, }; 654f3cf8bb0SJiang Liu 6550be8153cSMarc Zyngier BUG_ON(irq_chip_compose_msi_msg(irq_data, msg)); 6560be8153cSMarc Zyngier msi_check_level(irq_data->domain, msg); 6570be8153cSMarc Zyngier irq_chip_write_msi_msg(irq_data, msg); 65872491643SThomas Gleixner return 0; 659f3cf8bb0SJiang Liu } 660f3cf8bb0SJiang Liu 661f3cf8bb0SJiang Liu static void msi_domain_deactivate(struct irq_domain *domain, 662f3cf8bb0SJiang Liu struct irq_data *irq_data) 663f3cf8bb0SJiang Liu { 6640be8153cSMarc Zyngier struct msi_msg msg[2]; 665f3cf8bb0SJiang Liu 6660be8153cSMarc Zyngier memset(msg, 0, sizeof(msg)); 6670be8153cSMarc Zyngier irq_chip_write_msi_msg(irq_data, msg); 668f3cf8bb0SJiang Liu } 669f3cf8bb0SJiang Liu 670f3cf8bb0SJiang Liu static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq, 671f3cf8bb0SJiang Liu unsigned int nr_irqs, void *arg) 672f3cf8bb0SJiang Liu { 673f3cf8bb0SJiang Liu struct msi_domain_info *info = domain->host_data; 674f3cf8bb0SJiang Liu struct msi_domain_ops *ops = info->ops; 675f3cf8bb0SJiang Liu irq_hw_number_t hwirq = ops->get_hwirq(info, arg); 676f3cf8bb0SJiang Liu int i, ret; 677f3cf8bb0SJiang Liu 678f3cf8bb0SJiang Liu if (irq_find_mapping(domain, hwirq) > 0) 679f3cf8bb0SJiang Liu return -EEXIST; 680f3cf8bb0SJiang Liu 681bf6f869fSLiu Jiang if (domain->parent) { 682f3cf8bb0SJiang Liu ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg); 683f3cf8bb0SJiang Liu if (ret < 0) 684f3cf8bb0SJiang Liu return ret; 685bf6f869fSLiu Jiang } 686f3cf8bb0SJiang Liu 687f3cf8bb0SJiang Liu for (i = 0; i < nr_irqs; i++) { 688f3cf8bb0SJiang Liu ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg); 689f3cf8bb0SJiang Liu if (ret < 0) { 690f3cf8bb0SJiang Liu if (ops->msi_free) { 691f3cf8bb0SJiang Liu for (i--; i > 0; i--) 692f3cf8bb0SJiang Liu ops->msi_free(domain, info, virq + i); 693f3cf8bb0SJiang Liu } 694f3cf8bb0SJiang Liu irq_domain_free_irqs_top(domain, virq, nr_irqs); 695f3cf8bb0SJiang Liu return ret; 696f3cf8bb0SJiang Liu } 697f3cf8bb0SJiang Liu } 698f3cf8bb0SJiang Liu 699f3cf8bb0SJiang Liu return 0; 700f3cf8bb0SJiang Liu } 701f3cf8bb0SJiang Liu 702f3cf8bb0SJiang Liu static void msi_domain_free(struct irq_domain *domain, unsigned int virq, 703f3cf8bb0SJiang Liu unsigned int nr_irqs) 704f3cf8bb0SJiang Liu { 705f3cf8bb0SJiang Liu struct msi_domain_info *info = domain->host_data; 706f3cf8bb0SJiang Liu int i; 707f3cf8bb0SJiang Liu 708f3cf8bb0SJiang Liu if (info->ops->msi_free) { 709f3cf8bb0SJiang Liu for (i = 0; i < nr_irqs; i++) 710f3cf8bb0SJiang Liu info->ops->msi_free(domain, info, virq + i); 711f3cf8bb0SJiang Liu } 712f3cf8bb0SJiang Liu irq_domain_free_irqs_top(domain, virq, nr_irqs); 713f3cf8bb0SJiang Liu } 714f3cf8bb0SJiang Liu 71501364028SKrzysztof Kozlowski static const struct irq_domain_ops msi_domain_ops = { 716f3cf8bb0SJiang Liu .alloc = msi_domain_alloc, 717f3cf8bb0SJiang Liu .free = msi_domain_free, 718f3cf8bb0SJiang Liu .activate = msi_domain_activate, 719f3cf8bb0SJiang Liu .deactivate = msi_domain_deactivate, 720f3cf8bb0SJiang Liu }; 721f3cf8bb0SJiang Liu 722aeeb5965SJiang Liu static irq_hw_number_t msi_domain_ops_get_hwirq(struct msi_domain_info *info, 723aeeb5965SJiang Liu msi_alloc_info_t *arg) 724aeeb5965SJiang Liu { 725aeeb5965SJiang Liu return arg->hwirq; 726aeeb5965SJiang Liu } 727aeeb5965SJiang Liu 728aeeb5965SJiang Liu static int msi_domain_ops_prepare(struct irq_domain *domain, struct device *dev, 729aeeb5965SJiang Liu int nvec, msi_alloc_info_t *arg) 730aeeb5965SJiang Liu { 731aeeb5965SJiang Liu memset(arg, 0, sizeof(*arg)); 732aeeb5965SJiang Liu return 0; 733aeeb5965SJiang Liu } 734aeeb5965SJiang Liu 735aeeb5965SJiang Liu static void msi_domain_ops_set_desc(msi_alloc_info_t *arg, 736aeeb5965SJiang Liu struct msi_desc *desc) 737aeeb5965SJiang Liu { 738aeeb5965SJiang Liu arg->desc = desc; 739aeeb5965SJiang Liu } 740aeeb5965SJiang Liu 741aeeb5965SJiang Liu static int msi_domain_ops_init(struct irq_domain *domain, 742aeeb5965SJiang Liu struct msi_domain_info *info, 743aeeb5965SJiang Liu unsigned int virq, irq_hw_number_t hwirq, 744aeeb5965SJiang Liu msi_alloc_info_t *arg) 745aeeb5965SJiang Liu { 746aeeb5965SJiang Liu irq_domain_set_hwirq_and_chip(domain, virq, hwirq, info->chip, 747aeeb5965SJiang Liu info->chip_data); 748aeeb5965SJiang Liu if (info->handler && info->handler_name) { 749aeeb5965SJiang Liu __irq_set_handler(virq, info->handler, 0, info->handler_name); 750aeeb5965SJiang Liu if (info->handler_data) 751aeeb5965SJiang Liu irq_set_handler_data(virq, info->handler_data); 752aeeb5965SJiang Liu } 753aeeb5965SJiang Liu return 0; 754aeeb5965SJiang Liu } 755aeeb5965SJiang Liu 756aeeb5965SJiang Liu static struct msi_domain_ops msi_domain_ops_default = { 757aeeb5965SJiang Liu .get_hwirq = msi_domain_ops_get_hwirq, 758aeeb5965SJiang Liu .msi_init = msi_domain_ops_init, 759aeeb5965SJiang Liu .msi_prepare = msi_domain_ops_prepare, 760aeeb5965SJiang Liu .set_desc = msi_domain_ops_set_desc, 761aeeb5965SJiang Liu }; 762aeeb5965SJiang Liu 763aeeb5965SJiang Liu static void msi_domain_update_dom_ops(struct msi_domain_info *info) 764aeeb5965SJiang Liu { 765aeeb5965SJiang Liu struct msi_domain_ops *ops = info->ops; 766aeeb5965SJiang Liu 767aeeb5965SJiang Liu if (ops == NULL) { 768aeeb5965SJiang Liu info->ops = &msi_domain_ops_default; 769aeeb5965SJiang Liu return; 770aeeb5965SJiang Liu } 771aeeb5965SJiang Liu 77243e9e705SThomas Gleixner if (!(info->flags & MSI_FLAG_USE_DEF_DOM_OPS)) 77343e9e705SThomas Gleixner return; 77443e9e705SThomas Gleixner 775aeeb5965SJiang Liu if (ops->get_hwirq == NULL) 776aeeb5965SJiang Liu ops->get_hwirq = msi_domain_ops_default.get_hwirq; 777aeeb5965SJiang Liu if (ops->msi_init == NULL) 778aeeb5965SJiang Liu ops->msi_init = msi_domain_ops_default.msi_init; 779aeeb5965SJiang Liu if (ops->msi_prepare == NULL) 780aeeb5965SJiang Liu ops->msi_prepare = msi_domain_ops_default.msi_prepare; 781aeeb5965SJiang Liu if (ops->set_desc == NULL) 782aeeb5965SJiang Liu ops->set_desc = msi_domain_ops_default.set_desc; 783aeeb5965SJiang Liu } 784aeeb5965SJiang Liu 785aeeb5965SJiang Liu static void msi_domain_update_chip_ops(struct msi_domain_info *info) 786aeeb5965SJiang Liu { 787aeeb5965SJiang Liu struct irq_chip *chip = info->chip; 788aeeb5965SJiang Liu 7890701c53eSMarc Zyngier BUG_ON(!chip || !chip->irq_mask || !chip->irq_unmask); 790aeeb5965SJiang Liu if (!chip->irq_set_affinity) 791aeeb5965SJiang Liu chip->irq_set_affinity = msi_domain_set_affinity; 792aeeb5965SJiang Liu } 793aeeb5965SJiang Liu 794a80c0aceSThomas Gleixner static struct irq_domain *__msi_create_irq_domain(struct fwnode_handle *fwnode, 795f3cf8bb0SJiang Liu struct msi_domain_info *info, 796a80c0aceSThomas Gleixner unsigned int flags, 797f3cf8bb0SJiang Liu struct irq_domain *parent) 798f3cf8bb0SJiang Liu { 799a97b852bSMarc Zyngier struct irq_domain *domain; 800a97b852bSMarc Zyngier 80161bf992fSThomas Gleixner if (info->hwsize > MSI_XA_DOMAIN_SIZE) 80261bf992fSThomas Gleixner return NULL; 80361bf992fSThomas Gleixner 80461bf992fSThomas Gleixner /* 80561bf992fSThomas Gleixner * Hardware size 0 is valid for backwards compatibility and for 80661bf992fSThomas Gleixner * domains which are not backed by a hardware table. Grant the 80761bf992fSThomas Gleixner * maximum index space. 80861bf992fSThomas Gleixner */ 80961bf992fSThomas Gleixner if (!info->hwsize) 81061bf992fSThomas Gleixner info->hwsize = MSI_XA_DOMAIN_SIZE; 81161bf992fSThomas Gleixner 812aeeb5965SJiang Liu msi_domain_update_dom_ops(info); 813aeeb5965SJiang Liu if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) 814aeeb5965SJiang Liu msi_domain_update_chip_ops(info); 815f3cf8bb0SJiang Liu 816a80c0aceSThomas Gleixner domain = irq_domain_create_hierarchy(parent, flags | IRQ_DOMAIN_FLAG_MSI, 0, 81788156f00SEric Auger fwnode, &msi_domain_ops, info); 8180165308aSThomas Gleixner 81922db089aSAhmed S. Darwish if (domain) { 82022db089aSAhmed S. Darwish if (!domain->name && info->chip) 821a97b852bSMarc Zyngier domain->name = info->chip->name; 82222db089aSAhmed S. Darwish irq_domain_update_bus_token(domain, info->bus_token); 82322db089aSAhmed S. Darwish } 824a97b852bSMarc Zyngier 825a97b852bSMarc Zyngier return domain; 826f3cf8bb0SJiang Liu } 827f3cf8bb0SJiang Liu 828b78780d9SThomas Gleixner /** 829a80c0aceSThomas Gleixner * msi_create_irq_domain - Create an MSI interrupt domain 830a80c0aceSThomas Gleixner * @fwnode: Optional fwnode of the interrupt controller 831a80c0aceSThomas Gleixner * @info: MSI domain info 832a80c0aceSThomas Gleixner * @parent: Parent irq domain 833a80c0aceSThomas Gleixner * 834a80c0aceSThomas Gleixner * Return: pointer to the created &struct irq_domain or %NULL on failure 835a80c0aceSThomas Gleixner */ 836a80c0aceSThomas Gleixner struct irq_domain *msi_create_irq_domain(struct fwnode_handle *fwnode, 837a80c0aceSThomas Gleixner struct msi_domain_info *info, 838a80c0aceSThomas Gleixner struct irq_domain *parent) 839a80c0aceSThomas Gleixner { 840a80c0aceSThomas Gleixner return __msi_create_irq_domain(fwnode, info, 0, parent); 841a80c0aceSThomas Gleixner } 842a80c0aceSThomas Gleixner 843a80c0aceSThomas Gleixner /** 844b78780d9SThomas Gleixner * msi_parent_init_dev_msi_info - Delegate initialization of device MSI info down 845b78780d9SThomas Gleixner * in the domain hierarchy 846b78780d9SThomas Gleixner * @dev: The device for which the domain should be created 847b78780d9SThomas Gleixner * @domain: The domain in the hierarchy this op is being called on 848b78780d9SThomas Gleixner * @msi_parent_domain: The IRQ_DOMAIN_FLAG_MSI_PARENT domain for the child to 849b78780d9SThomas Gleixner * be created 850b78780d9SThomas Gleixner * @msi_child_info: The MSI domain info of the IRQ_DOMAIN_FLAG_MSI_DEVICE 851b78780d9SThomas Gleixner * domain to be created 852b78780d9SThomas Gleixner * 853b78780d9SThomas Gleixner * Return: true on success, false otherwise 854b78780d9SThomas Gleixner * 855b78780d9SThomas Gleixner * This is the most complex problem of per device MSI domains and the 856b78780d9SThomas Gleixner * underlying interrupt domain hierarchy: 857b78780d9SThomas Gleixner * 858b78780d9SThomas Gleixner * The device domain to be initialized requests the broadest feature set 859b78780d9SThomas Gleixner * possible and the underlying domain hierarchy puts restrictions on it. 860b78780d9SThomas Gleixner * 861b78780d9SThomas Gleixner * That's trivial for a simple parent->child relationship, but it gets 862b78780d9SThomas Gleixner * interesting with an intermediate domain: root->parent->child. The 863b78780d9SThomas Gleixner * intermediate 'parent' can expand the capabilities which the 'root' 864b78780d9SThomas Gleixner * domain is providing. So that creates a classic hen and egg problem: 865b78780d9SThomas Gleixner * Which entity is doing the restrictions/expansions? 866b78780d9SThomas Gleixner * 867b78780d9SThomas Gleixner * One solution is to let the root domain handle the initialization that's 868b78780d9SThomas Gleixner * why there is the @domain and the @msi_parent_domain pointer. 869b78780d9SThomas Gleixner */ 870b78780d9SThomas Gleixner bool msi_parent_init_dev_msi_info(struct device *dev, struct irq_domain *domain, 871b78780d9SThomas Gleixner struct irq_domain *msi_parent_domain, 872b78780d9SThomas Gleixner struct msi_domain_info *msi_child_info) 873b78780d9SThomas Gleixner { 874b78780d9SThomas Gleixner struct irq_domain *parent = domain->parent; 875b78780d9SThomas Gleixner 876b78780d9SThomas Gleixner if (WARN_ON_ONCE(!parent || !parent->msi_parent_ops || 877b78780d9SThomas Gleixner !parent->msi_parent_ops->init_dev_msi_info)) 878b78780d9SThomas Gleixner return false; 879b78780d9SThomas Gleixner 880b78780d9SThomas Gleixner return parent->msi_parent_ops->init_dev_msi_info(dev, parent, msi_parent_domain, 881b78780d9SThomas Gleixner msi_child_info); 882b78780d9SThomas Gleixner } 883b78780d9SThomas Gleixner 88427a6dea3SThomas Gleixner /** 88527a6dea3SThomas Gleixner * msi_create_device_irq_domain - Create a device MSI interrupt domain 88627a6dea3SThomas Gleixner * @dev: Pointer to the device 88727a6dea3SThomas Gleixner * @domid: Domain id 88827a6dea3SThomas Gleixner * @template: MSI domain info bundle used as template 88927a6dea3SThomas Gleixner * @hwsize: Maximum number of MSI table entries (0 if unknown or unlimited) 89027a6dea3SThomas Gleixner * @domain_data: Optional pointer to domain specific data which is set in 89127a6dea3SThomas Gleixner * msi_domain_info::data 89227a6dea3SThomas Gleixner * @chip_data: Optional pointer to chip specific data which is set in 89327a6dea3SThomas Gleixner * msi_domain_info::chip_data 89427a6dea3SThomas Gleixner * 89527a6dea3SThomas Gleixner * Return: True on success, false otherwise 89627a6dea3SThomas Gleixner * 89727a6dea3SThomas Gleixner * There is no firmware node required for this interface because the per 89827a6dea3SThomas Gleixner * device domains are software constructs which are actually closer to the 89927a6dea3SThomas Gleixner * hardware reality than any firmware can describe them. 90027a6dea3SThomas Gleixner * 90127a6dea3SThomas Gleixner * The domain name and the irq chip name for a MSI device domain are 90227a6dea3SThomas Gleixner * composed by: "$(PREFIX)$(CHIPNAME)-$(DEVNAME)" 90327a6dea3SThomas Gleixner * 90427a6dea3SThomas Gleixner * $PREFIX: Optional prefix provided by the underlying MSI parent domain 90527a6dea3SThomas Gleixner * via msi_parent_ops::prefix. If that pointer is NULL the prefix 90627a6dea3SThomas Gleixner * is empty. 90727a6dea3SThomas Gleixner * $CHIPNAME: The name of the irq_chip in @template 90827a6dea3SThomas Gleixner * $DEVNAME: The name of the device 90927a6dea3SThomas Gleixner * 91027a6dea3SThomas Gleixner * This results in understandable chip names and hardware interrupt numbers 91127a6dea3SThomas Gleixner * in e.g. /proc/interrupts 91227a6dea3SThomas Gleixner * 91327a6dea3SThomas Gleixner * PCI-MSI-0000:00:1c.0 0-edge Parent domain has no prefix 91427a6dea3SThomas Gleixner * IR-PCI-MSI-0000:00:1c.4 0-edge Same with interrupt remapping prefix 'IR-' 91527a6dea3SThomas Gleixner * 91627a6dea3SThomas Gleixner * IR-PCI-MSIX-0000:3d:00.0 0-edge Hardware interrupt numbers reflect 91727a6dea3SThomas Gleixner * IR-PCI-MSIX-0000:3d:00.0 1-edge the real MSI-X index on that device 91827a6dea3SThomas Gleixner * IR-PCI-MSIX-0000:3d:00.0 2-edge 91927a6dea3SThomas Gleixner * 92027a6dea3SThomas Gleixner * On IMS domains the hardware interrupt number is either a table entry 92127a6dea3SThomas Gleixner * index or a purely software managed index but it is guaranteed to be 92227a6dea3SThomas Gleixner * unique. 92327a6dea3SThomas Gleixner * 92427a6dea3SThomas Gleixner * The domain pointer is stored in @dev::msi::data::__irqdomains[]. All 92527a6dea3SThomas Gleixner * subsequent operations on the domain depend on the domain id. 92627a6dea3SThomas Gleixner * 92727a6dea3SThomas Gleixner * The domain is automatically freed when the device is removed via devres 92827a6dea3SThomas Gleixner * in the context of @dev::msi::data freeing, but it can also be 92927a6dea3SThomas Gleixner * independently removed via @msi_remove_device_irq_domain(). 93027a6dea3SThomas Gleixner */ 93127a6dea3SThomas Gleixner bool msi_create_device_irq_domain(struct device *dev, unsigned int domid, 93227a6dea3SThomas Gleixner const struct msi_domain_template *template, 93327a6dea3SThomas Gleixner unsigned int hwsize, void *domain_data, 93427a6dea3SThomas Gleixner void *chip_data) 93527a6dea3SThomas Gleixner { 93627a6dea3SThomas Gleixner struct irq_domain *domain, *parent = dev->msi.domain; 93727a6dea3SThomas Gleixner const struct msi_parent_ops *pops; 93827a6dea3SThomas Gleixner struct msi_domain_template *bundle; 93927a6dea3SThomas Gleixner struct fwnode_handle *fwnode; 94027a6dea3SThomas Gleixner 94127a6dea3SThomas Gleixner if (!irq_domain_is_msi_parent(parent)) 94227a6dea3SThomas Gleixner return false; 94327a6dea3SThomas Gleixner 94427a6dea3SThomas Gleixner if (domid >= MSI_MAX_DEVICE_IRQDOMAINS) 94527a6dea3SThomas Gleixner return false; 94627a6dea3SThomas Gleixner 94727a6dea3SThomas Gleixner bundle = kmemdup(template, sizeof(*bundle), GFP_KERNEL); 94827a6dea3SThomas Gleixner if (!bundle) 94927a6dea3SThomas Gleixner return false; 95027a6dea3SThomas Gleixner 95127a6dea3SThomas Gleixner bundle->info.hwsize = hwsize; 95227a6dea3SThomas Gleixner bundle->info.chip = &bundle->chip; 95327a6dea3SThomas Gleixner bundle->info.ops = &bundle->ops; 95427a6dea3SThomas Gleixner bundle->info.data = domain_data; 95527a6dea3SThomas Gleixner bundle->info.chip_data = chip_data; 95627a6dea3SThomas Gleixner 95727a6dea3SThomas Gleixner pops = parent->msi_parent_ops; 95827a6dea3SThomas Gleixner snprintf(bundle->name, sizeof(bundle->name), "%s%s-%s", 95927a6dea3SThomas Gleixner pops->prefix ? : "", bundle->chip.name, dev_name(dev)); 96027a6dea3SThomas Gleixner bundle->chip.name = bundle->name; 96127a6dea3SThomas Gleixner 96227a6dea3SThomas Gleixner fwnode = irq_domain_alloc_named_fwnode(bundle->name); 96327a6dea3SThomas Gleixner if (!fwnode) 96427a6dea3SThomas Gleixner goto free_bundle; 96527a6dea3SThomas Gleixner 96627a6dea3SThomas Gleixner if (msi_setup_device_data(dev)) 96727a6dea3SThomas Gleixner goto free_fwnode; 96827a6dea3SThomas Gleixner 96927a6dea3SThomas Gleixner msi_lock_descs(dev); 97027a6dea3SThomas Gleixner 97127a6dea3SThomas Gleixner if (WARN_ON_ONCE(msi_get_device_domain(dev, domid))) 97227a6dea3SThomas Gleixner goto fail; 97327a6dea3SThomas Gleixner 97427a6dea3SThomas Gleixner if (!pops->init_dev_msi_info(dev, parent, parent, &bundle->info)) 97527a6dea3SThomas Gleixner goto fail; 97627a6dea3SThomas Gleixner 97727a6dea3SThomas Gleixner domain = __msi_create_irq_domain(fwnode, &bundle->info, IRQ_DOMAIN_FLAG_MSI_DEVICE, parent); 97827a6dea3SThomas Gleixner if (!domain) 97927a6dea3SThomas Gleixner goto fail; 98027a6dea3SThomas Gleixner 98127a6dea3SThomas Gleixner domain->dev = dev; 98227a6dea3SThomas Gleixner dev->msi.data->__domains[domid].domain = domain; 98327a6dea3SThomas Gleixner msi_unlock_descs(dev); 98427a6dea3SThomas Gleixner return true; 98527a6dea3SThomas Gleixner 98627a6dea3SThomas Gleixner fail: 98727a6dea3SThomas Gleixner msi_unlock_descs(dev); 98827a6dea3SThomas Gleixner free_fwnode: 98927a6dea3SThomas Gleixner kfree(fwnode); 99027a6dea3SThomas Gleixner free_bundle: 99127a6dea3SThomas Gleixner kfree(bundle); 99227a6dea3SThomas Gleixner return false; 99327a6dea3SThomas Gleixner } 99427a6dea3SThomas Gleixner 99527a6dea3SThomas Gleixner /** 99627a6dea3SThomas Gleixner * msi_remove_device_irq_domain - Free a device MSI interrupt domain 99727a6dea3SThomas Gleixner * @dev: Pointer to the device 99827a6dea3SThomas Gleixner * @domid: Domain id 99927a6dea3SThomas Gleixner */ 100027a6dea3SThomas Gleixner void msi_remove_device_irq_domain(struct device *dev, unsigned int domid) 100127a6dea3SThomas Gleixner { 100227a6dea3SThomas Gleixner struct msi_domain_info *info; 100327a6dea3SThomas Gleixner struct irq_domain *domain; 100427a6dea3SThomas Gleixner 100527a6dea3SThomas Gleixner msi_lock_descs(dev); 100627a6dea3SThomas Gleixner 100727a6dea3SThomas Gleixner domain = msi_get_device_domain(dev, domid); 100827a6dea3SThomas Gleixner 100927a6dea3SThomas Gleixner if (!domain || !irq_domain_is_msi_device(domain)) 101027a6dea3SThomas Gleixner goto unlock; 101127a6dea3SThomas Gleixner 101227a6dea3SThomas Gleixner dev->msi.data->__domains[domid].domain = NULL; 101327a6dea3SThomas Gleixner info = domain->host_data; 101427a6dea3SThomas Gleixner irq_domain_remove(domain); 101527a6dea3SThomas Gleixner kfree(container_of(info, struct msi_domain_template, info)); 101627a6dea3SThomas Gleixner 101727a6dea3SThomas Gleixner unlock: 101827a6dea3SThomas Gleixner msi_unlock_descs(dev); 101927a6dea3SThomas Gleixner } 102027a6dea3SThomas Gleixner 102126e91b75SThomas Gleixner /** 102226e91b75SThomas Gleixner * msi_match_device_irq_domain - Match a device irq domain against a bus token 102326e91b75SThomas Gleixner * @dev: Pointer to the device 102426e91b75SThomas Gleixner * @domid: Domain id 102526e91b75SThomas Gleixner * @bus_token: Bus token to match against the domain bus token 102626e91b75SThomas Gleixner * 102726e91b75SThomas Gleixner * Return: True if device domain exists and bus tokens match. 102826e91b75SThomas Gleixner */ 102926e91b75SThomas Gleixner bool msi_match_device_irq_domain(struct device *dev, unsigned int domid, 103026e91b75SThomas Gleixner enum irq_domain_bus_token bus_token) 103126e91b75SThomas Gleixner { 103226e91b75SThomas Gleixner struct msi_domain_info *info; 103326e91b75SThomas Gleixner struct irq_domain *domain; 103426e91b75SThomas Gleixner bool ret = false; 103526e91b75SThomas Gleixner 103626e91b75SThomas Gleixner msi_lock_descs(dev); 103726e91b75SThomas Gleixner domain = msi_get_device_domain(dev, domid); 103826e91b75SThomas Gleixner if (domain && irq_domain_is_msi_device(domain)) { 103926e91b75SThomas Gleixner info = domain->host_data; 104026e91b75SThomas Gleixner ret = info->bus_token == bus_token; 104126e91b75SThomas Gleixner } 104226e91b75SThomas Gleixner msi_unlock_descs(dev); 104326e91b75SThomas Gleixner return ret; 104426e91b75SThomas Gleixner } 104526e91b75SThomas Gleixner 1046b2eba39bSMarc Zyngier int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev, 1047b2eba39bSMarc Zyngier int nvec, msi_alloc_info_t *arg) 1048b2eba39bSMarc Zyngier { 1049b2eba39bSMarc Zyngier struct msi_domain_info *info = domain->host_data; 1050b2eba39bSMarc Zyngier struct msi_domain_ops *ops = info->ops; 1051b2eba39bSMarc Zyngier 10522569f62cSThomas Gleixner return ops->msi_prepare(domain, dev, nvec, arg); 1053b2eba39bSMarc Zyngier } 1054b2eba39bSMarc Zyngier 10552145ac93SMarc Zyngier int msi_domain_populate_irqs(struct irq_domain *domain, struct device *dev, 1056a80713feSThomas Gleixner int virq_base, int nvec, msi_alloc_info_t *arg) 10572145ac93SMarc Zyngier { 10582145ac93SMarc Zyngier struct msi_domain_info *info = domain->host_data; 10592145ac93SMarc Zyngier struct msi_domain_ops *ops = info->ops; 106040742716SThomas Gleixner struct msi_ctrl ctrl = { 106140742716SThomas Gleixner .domid = MSI_DEFAULT_DOMAIN, 106240742716SThomas Gleixner .first = virq_base, 106340742716SThomas Gleixner .last = virq_base + nvec - 1, 106440742716SThomas Gleixner }; 10652145ac93SMarc Zyngier struct msi_desc *desc; 1066f1139f90SThomas Gleixner struct xarray *xa; 1067a80713feSThomas Gleixner int ret, virq; 10682145ac93SMarc Zyngier 106940742716SThomas Gleixner if (!msi_ctrl_valid(dev, &ctrl)) 107040742716SThomas Gleixner return -EINVAL; 107140742716SThomas Gleixner 1072a80713feSThomas Gleixner msi_lock_descs(dev); 107340742716SThomas Gleixner ret = msi_domain_add_simple_msi_descs(dev, &ctrl); 1074cd6cf065SThomas Gleixner if (ret) 1075cd6cf065SThomas Gleixner goto unlock; 10762145ac93SMarc Zyngier 107740742716SThomas Gleixner xa = &dev->msi.data->__domains[ctrl.domid].store; 1078f1139f90SThomas Gleixner 1079cd6cf065SThomas Gleixner for (virq = virq_base; virq < virq_base + nvec; virq++) { 1080f1139f90SThomas Gleixner desc = xa_load(xa, virq); 1081a80713feSThomas Gleixner desc->irq = virq; 10822145ac93SMarc Zyngier 10832145ac93SMarc Zyngier ops->set_desc(arg, desc); 1084a80713feSThomas Gleixner ret = irq_domain_alloc_irqs_hierarchy(domain, virq, 1, arg); 10852145ac93SMarc Zyngier if (ret) 1086a80713feSThomas Gleixner goto fail; 10872145ac93SMarc Zyngier 1088a80713feSThomas Gleixner irq_set_msi_desc(virq, desc); 10892145ac93SMarc Zyngier } 1090a80713feSThomas Gleixner msi_unlock_descs(dev); 1091a80713feSThomas Gleixner return 0; 10922145ac93SMarc Zyngier 1093a80713feSThomas Gleixner fail: 1094a80713feSThomas Gleixner for (--virq; virq >= virq_base; virq--) 1095a80713feSThomas Gleixner irq_domain_free_irqs_common(domain, virq, 1); 109640742716SThomas Gleixner msi_domain_free_descs(dev, &ctrl); 1097cd6cf065SThomas Gleixner unlock: 1098a80713feSThomas Gleixner msi_unlock_descs(dev); 10992145ac93SMarc Zyngier return ret; 11002145ac93SMarc Zyngier } 11012145ac93SMarc Zyngier 1102bc976233SThomas Gleixner /* 1103bc976233SThomas Gleixner * Carefully check whether the device can use reservation mode. If 1104bc976233SThomas Gleixner * reservation mode is enabled then the early activation will assign a 1105bc976233SThomas Gleixner * dummy vector to the device. If the PCI/MSI device does not support 1106bc976233SThomas Gleixner * masking of the entry then this can result in spurious interrupts when 1107bc976233SThomas Gleixner * the device driver is not absolutely careful. But even then a malfunction 1108bc976233SThomas Gleixner * of the hardware could result in a spurious interrupt on the dummy vector 1109bc976233SThomas Gleixner * and render the device unusable. If the entry can be masked then the core 1110bc976233SThomas Gleixner * logic will prevent the spurious interrupt and reservation mode can be 1111bc976233SThomas Gleixner * used. For now reservation mode is restricted to PCI/MSI. 1112bc976233SThomas Gleixner */ 1113bc976233SThomas Gleixner static bool msi_check_reservation_mode(struct irq_domain *domain, 1114bc976233SThomas Gleixner struct msi_domain_info *info, 1115bc976233SThomas Gleixner struct device *dev) 1116da5dd9e8SThomas Gleixner { 1117bc976233SThomas Gleixner struct msi_desc *desc; 1118bc976233SThomas Gleixner 1119c6c9e283SThomas Gleixner switch(domain->bus_token) { 1120c6c9e283SThomas Gleixner case DOMAIN_BUS_PCI_MSI: 1121c6c9e283SThomas Gleixner case DOMAIN_BUS_VMD_MSI: 1122c6c9e283SThomas Gleixner break; 1123c6c9e283SThomas Gleixner default: 1124bc976233SThomas Gleixner return false; 1125c6c9e283SThomas Gleixner } 1126bc976233SThomas Gleixner 1127da5dd9e8SThomas Gleixner if (!(info->flags & MSI_FLAG_MUST_REACTIVATE)) 1128da5dd9e8SThomas Gleixner return false; 1129bc976233SThomas Gleixner 1130bc976233SThomas Gleixner if (IS_ENABLED(CONFIG_PCI_MSI) && pci_msi_ignore_mask) 1131bc976233SThomas Gleixner return false; 1132bc976233SThomas Gleixner 1133bc976233SThomas Gleixner /* 1134bc976233SThomas Gleixner * Checking the first MSI descriptor is sufficient. MSIX supports 11359c8e9c96SThomas Gleixner * masking and MSI does so when the can_mask attribute is set. 1136bc976233SThomas Gleixner */ 1137495c66acSThomas Gleixner desc = msi_first_desc(dev, MSI_DESC_ALL); 1138e58f2259SThomas Gleixner return desc->pci.msi_attrib.is_msix || desc->pci.msi_attrib.can_mask; 1139da5dd9e8SThomas Gleixner } 1140da5dd9e8SThomas Gleixner 114189033762SThomas Gleixner static int msi_handle_pci_fail(struct irq_domain *domain, struct msi_desc *desc, 114289033762SThomas Gleixner int allocated) 114389033762SThomas Gleixner { 114489033762SThomas Gleixner switch(domain->bus_token) { 114589033762SThomas Gleixner case DOMAIN_BUS_PCI_MSI: 114689033762SThomas Gleixner case DOMAIN_BUS_VMD_MSI: 114789033762SThomas Gleixner if (IS_ENABLED(CONFIG_PCI_MSI)) 114889033762SThomas Gleixner break; 114989033762SThomas Gleixner fallthrough; 115089033762SThomas Gleixner default: 115189033762SThomas Gleixner return -ENOSPC; 115289033762SThomas Gleixner } 115389033762SThomas Gleixner 115489033762SThomas Gleixner /* Let a failed PCI multi MSI allocation retry */ 115589033762SThomas Gleixner if (desc->nvec_used > 1) 115689033762SThomas Gleixner return 1; 115789033762SThomas Gleixner 115889033762SThomas Gleixner /* If there was a successful allocation let the caller know */ 115989033762SThomas Gleixner return allocated ? allocated : -ENOSPC; 116089033762SThomas Gleixner } 116189033762SThomas Gleixner 1162ef8dd015SThomas Gleixner #define VIRQ_CAN_RESERVE 0x01 1163ef8dd015SThomas Gleixner #define VIRQ_ACTIVATE 0x02 1164ef8dd015SThomas Gleixner #define VIRQ_NOMASK_QUIRK 0x04 1165ef8dd015SThomas Gleixner 1166ef8dd015SThomas Gleixner static int msi_init_virq(struct irq_domain *domain, int virq, unsigned int vflags) 1167ef8dd015SThomas Gleixner { 1168ef8dd015SThomas Gleixner struct irq_data *irqd = irq_domain_get_irq_data(domain, virq); 1169ef8dd015SThomas Gleixner int ret; 1170ef8dd015SThomas Gleixner 1171ef8dd015SThomas Gleixner if (!(vflags & VIRQ_CAN_RESERVE)) { 1172ef8dd015SThomas Gleixner irqd_clr_can_reserve(irqd); 1173ef8dd015SThomas Gleixner if (vflags & VIRQ_NOMASK_QUIRK) 1174ef8dd015SThomas Gleixner irqd_set_msi_nomask_quirk(irqd); 1175d802057cSMarc Zyngier 1176d802057cSMarc Zyngier /* 1177d802057cSMarc Zyngier * If the interrupt is managed but no CPU is available to 1178d802057cSMarc Zyngier * service it, shut it down until better times. Note that 1179d802057cSMarc Zyngier * we only do this on the !RESERVE path as x86 (the only 1180d802057cSMarc Zyngier * architecture using this flag) deals with this in a 1181d802057cSMarc Zyngier * different way by using a catch-all vector. 1182d802057cSMarc Zyngier */ 1183d802057cSMarc Zyngier if ((vflags & VIRQ_ACTIVATE) && 1184d802057cSMarc Zyngier irqd_affinity_is_managed(irqd) && 1185d802057cSMarc Zyngier !cpumask_intersects(irq_data_get_affinity_mask(irqd), 1186d802057cSMarc Zyngier cpu_online_mask)) { 1187d802057cSMarc Zyngier irqd_set_managed_shutdown(irqd); 1188d802057cSMarc Zyngier return 0; 1189d802057cSMarc Zyngier } 1190ef8dd015SThomas Gleixner } 1191ef8dd015SThomas Gleixner 1192ef8dd015SThomas Gleixner if (!(vflags & VIRQ_ACTIVATE)) 1193ef8dd015SThomas Gleixner return 0; 1194ef8dd015SThomas Gleixner 1195ef8dd015SThomas Gleixner ret = irq_domain_activate_irq(irqd, vflags & VIRQ_CAN_RESERVE); 1196ef8dd015SThomas Gleixner if (ret) 1197ef8dd015SThomas Gleixner return ret; 1198ef8dd015SThomas Gleixner /* 1199ef8dd015SThomas Gleixner * If the interrupt uses reservation mode, clear the activated bit 1200ef8dd015SThomas Gleixner * so request_irq() will assign the final vector. 1201ef8dd015SThomas Gleixner */ 1202ef8dd015SThomas Gleixner if (vflags & VIRQ_CAN_RESERVE) 1203ef8dd015SThomas Gleixner irqd_clr_activated(irqd); 1204ef8dd015SThomas Gleixner return 0; 1205ef8dd015SThomas Gleixner } 1206ef8dd015SThomas Gleixner 1207f2480e7dSThomas Gleixner static int __msi_domain_alloc_irqs(struct device *dev, struct irq_domain *domain, 1208f2480e7dSThomas Gleixner struct msi_ctrl *ctrl) 1209d9109698SJiang Liu { 1210f2480e7dSThomas Gleixner struct xarray *xa = &dev->msi.data->__domains[ctrl->domid].store; 1211d9109698SJiang Liu struct msi_domain_info *info = domain->host_data; 1212d9109698SJiang Liu struct msi_domain_ops *ops = info->ops; 1213f2480e7dSThomas Gleixner unsigned int vflags = 0, allocated = 0; 121406fde695SZenghui Yu msi_alloc_info_t arg = { }; 1215ef8dd015SThomas Gleixner struct msi_desc *desc; 1216f2480e7dSThomas Gleixner unsigned long idx; 1217b6140914SThomas Gleixner int i, ret, virq; 1218d9109698SJiang Liu 1219f2480e7dSThomas Gleixner ret = msi_domain_prepare_irqs(domain, dev, ctrl->nirqs, &arg); 1220d9109698SJiang Liu if (ret) 1221d9109698SJiang Liu return ret; 1222d9109698SJiang Liu 1223ef8dd015SThomas Gleixner /* 1224ef8dd015SThomas Gleixner * This flag is set by the PCI layer as we need to activate 1225ef8dd015SThomas Gleixner * the MSI entries before the PCI layer enables MSI in the 1226ef8dd015SThomas Gleixner * card. Otherwise the card latches a random msi message. 1227ef8dd015SThomas Gleixner */ 1228ef8dd015SThomas Gleixner if (info->flags & MSI_FLAG_ACTIVATE_EARLY) 1229ef8dd015SThomas Gleixner vflags |= VIRQ_ACTIVATE; 1230ef8dd015SThomas Gleixner 1231ef8dd015SThomas Gleixner /* 1232ef8dd015SThomas Gleixner * Interrupt can use a reserved vector and will not occupy 1233ef8dd015SThomas Gleixner * a real device vector until the interrupt is requested. 1234ef8dd015SThomas Gleixner */ 1235ef8dd015SThomas Gleixner if (msi_check_reservation_mode(domain, info, dev)) { 1236ef8dd015SThomas Gleixner vflags |= VIRQ_CAN_RESERVE; 1237ef8dd015SThomas Gleixner /* 1238ef8dd015SThomas Gleixner * MSI affinity setting requires a special quirk (X86) when 1239ef8dd015SThomas Gleixner * reservation mode is active. 1240ef8dd015SThomas Gleixner */ 12413dad5f9aSThomas Gleixner if (info->flags & MSI_FLAG_NOMASK_QUIRK) 1242ef8dd015SThomas Gleixner vflags |= VIRQ_NOMASK_QUIRK; 1243ef8dd015SThomas Gleixner } 1244ef8dd015SThomas Gleixner 1245f2480e7dSThomas Gleixner xa_for_each_range(xa, idx, desc, ctrl->first, ctrl->last) { 1246f2480e7dSThomas Gleixner if (!msi_desc_match(desc, MSI_DESC_NOTASSOCIATED)) 1247f2480e7dSThomas Gleixner continue; 1248f2480e7dSThomas Gleixner 1249f2480e7dSThomas Gleixner /* This should return -ECONFUSED... */ 1250f2480e7dSThomas Gleixner if (WARN_ON_ONCE(allocated >= ctrl->nirqs)) 1251f2480e7dSThomas Gleixner return -EINVAL; 1252f2480e7dSThomas Gleixner 1253d9109698SJiang Liu ops->set_desc(&arg, desc); 1254d9109698SJiang Liu 1255b6140914SThomas Gleixner virq = __irq_domain_alloc_irqs(domain, -1, desc->nvec_used, 125606ee6d57SThomas Gleixner dev_to_node(dev), &arg, false, 12570972fa57SThomas Gleixner desc->affinity); 12580f62d941SThomas Gleixner if (virq < 0) 12590f62d941SThomas Gleixner return msi_handle_pci_fail(domain, desc, allocated); 1260d9109698SJiang Liu 126107557ccbSThomas Gleixner for (i = 0; i < desc->nvec_used; i++) { 1262d9109698SJiang Liu irq_set_msi_desc_off(virq, i, desc); 126307557ccbSThomas Gleixner irq_debugfs_copy_devname(virq + i, dev); 1264ef8dd015SThomas Gleixner ret = msi_init_virq(domain, virq + i, vflags); 1265bb9b428aSThomas Gleixner if (ret) 12660f62d941SThomas Gleixner return ret; 126774a5257aSThomas Gleixner } 1268bf5e758fSThomas Gleixner if (info->flags & MSI_FLAG_DEV_SYSFS) { 1269bf5e758fSThomas Gleixner ret = msi_sysfs_populate_desc(dev, desc); 1270bf5e758fSThomas Gleixner if (ret) 1271bf5e758fSThomas Gleixner return ret; 1272bf5e758fSThomas Gleixner } 1273ef8dd015SThomas Gleixner allocated++; 1274d9109698SJiang Liu } 1275d9109698SJiang Liu return 0; 12760f62d941SThomas Gleixner } 12770f62d941SThomas Gleixner 127840742716SThomas Gleixner static int msi_domain_alloc_simple_msi_descs(struct device *dev, 127940742716SThomas Gleixner struct msi_domain_info *info, 1280f2480e7dSThomas Gleixner struct msi_ctrl *ctrl) 1281645474e2SThomas Gleixner { 1282645474e2SThomas Gleixner if (!(info->flags & MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS)) 1283645474e2SThomas Gleixner return 0; 1284645474e2SThomas Gleixner 1285f2480e7dSThomas Gleixner return msi_domain_add_simple_msi_descs(dev, ctrl); 1286f2480e7dSThomas Gleixner } 1287f2480e7dSThomas Gleixner 1288f2480e7dSThomas Gleixner static int __msi_domain_alloc_locked(struct device *dev, struct msi_ctrl *ctrl) 1289f2480e7dSThomas Gleixner { 1290f2480e7dSThomas Gleixner struct msi_domain_info *info; 1291f2480e7dSThomas Gleixner struct msi_domain_ops *ops; 1292f2480e7dSThomas Gleixner struct irq_domain *domain; 1293f2480e7dSThomas Gleixner int ret; 1294f2480e7dSThomas Gleixner 1295f2480e7dSThomas Gleixner if (!msi_ctrl_valid(dev, ctrl)) 1296f2480e7dSThomas Gleixner return -EINVAL; 1297f2480e7dSThomas Gleixner 1298f2480e7dSThomas Gleixner domain = msi_get_device_domain(dev, ctrl->domid); 1299f2480e7dSThomas Gleixner if (!domain) 1300f2480e7dSThomas Gleixner return -ENODEV; 1301f2480e7dSThomas Gleixner 1302f2480e7dSThomas Gleixner info = domain->host_data; 1303f2480e7dSThomas Gleixner 1304f2480e7dSThomas Gleixner ret = msi_domain_alloc_simple_msi_descs(dev, info, ctrl); 1305f2480e7dSThomas Gleixner if (ret) 1306f2480e7dSThomas Gleixner return ret; 1307f2480e7dSThomas Gleixner 1308f2480e7dSThomas Gleixner ops = info->ops; 1309f2480e7dSThomas Gleixner if (ops->domain_alloc_irqs) 1310f2480e7dSThomas Gleixner return ops->domain_alloc_irqs(domain, dev, ctrl->nirqs); 1311f2480e7dSThomas Gleixner 1312f2480e7dSThomas Gleixner return __msi_domain_alloc_irqs(dev, domain, ctrl); 1313f2480e7dSThomas Gleixner } 1314f2480e7dSThomas Gleixner 1315f2480e7dSThomas Gleixner static int msi_domain_alloc_locked(struct device *dev, struct msi_ctrl *ctrl) 1316f2480e7dSThomas Gleixner { 1317f2480e7dSThomas Gleixner int ret = __msi_domain_alloc_locked(dev, ctrl); 1318f2480e7dSThomas Gleixner 1319f2480e7dSThomas Gleixner if (ret) 1320f2480e7dSThomas Gleixner msi_domain_free_locked(dev, ctrl); 1321f2480e7dSThomas Gleixner return ret; 1322f2480e7dSThomas Gleixner } 1323f2480e7dSThomas Gleixner 1324f2480e7dSThomas Gleixner /** 1325f2480e7dSThomas Gleixner * msi_domain_alloc_irqs_range_locked - Allocate interrupts from a MSI interrupt domain 1326f2480e7dSThomas Gleixner * @dev: Pointer to device struct of the device for which the interrupts 1327f2480e7dSThomas Gleixner * are allocated 1328f2480e7dSThomas Gleixner * @domid: Id of the interrupt domain to operate on 1329f2480e7dSThomas Gleixner * @first: First index to allocate (inclusive) 1330f2480e7dSThomas Gleixner * @last: Last index to allocate (inclusive) 1331f2480e7dSThomas Gleixner * 1332f2480e7dSThomas Gleixner * Must be invoked from within a msi_lock_descs() / msi_unlock_descs() 1333f2480e7dSThomas Gleixner * pair. Use this for MSI irqdomains which implement their own descriptor 1334f2480e7dSThomas Gleixner * allocation/free. 1335f2480e7dSThomas Gleixner * 1336f2480e7dSThomas Gleixner * Return: %0 on success or an error code. 1337f2480e7dSThomas Gleixner */ 1338f2480e7dSThomas Gleixner int msi_domain_alloc_irqs_range_locked(struct device *dev, unsigned int domid, 1339f2480e7dSThomas Gleixner unsigned int first, unsigned int last) 1340f2480e7dSThomas Gleixner { 1341f2480e7dSThomas Gleixner struct msi_ctrl ctrl = { 1342f2480e7dSThomas Gleixner .domid = domid, 1343f2480e7dSThomas Gleixner .first = first, 1344f2480e7dSThomas Gleixner .last = last, 1345f2480e7dSThomas Gleixner .nirqs = last + 1 - first, 1346f2480e7dSThomas Gleixner }; 1347f2480e7dSThomas Gleixner 1348f2480e7dSThomas Gleixner return msi_domain_alloc_locked(dev, &ctrl); 1349f2480e7dSThomas Gleixner } 1350f2480e7dSThomas Gleixner 1351f2480e7dSThomas Gleixner /** 1352f2480e7dSThomas Gleixner * msi_domain_alloc_irqs_range - Allocate interrupts from a MSI interrupt domain 1353f2480e7dSThomas Gleixner * @dev: Pointer to device struct of the device for which the interrupts 1354f2480e7dSThomas Gleixner * are allocated 1355f2480e7dSThomas Gleixner * @domid: Id of the interrupt domain to operate on 1356f2480e7dSThomas Gleixner * @first: First index to allocate (inclusive) 1357f2480e7dSThomas Gleixner * @last: Last index to allocate (inclusive) 1358f2480e7dSThomas Gleixner * 1359f2480e7dSThomas Gleixner * Return: %0 on success or an error code. 1360f2480e7dSThomas Gleixner */ 1361f2480e7dSThomas Gleixner int msi_domain_alloc_irqs_range(struct device *dev, unsigned int domid, 1362f2480e7dSThomas Gleixner unsigned int first, unsigned int last) 1363f2480e7dSThomas Gleixner { 1364f2480e7dSThomas Gleixner int ret; 1365f2480e7dSThomas Gleixner 1366f2480e7dSThomas Gleixner msi_lock_descs(dev); 1367f2480e7dSThomas Gleixner ret = msi_domain_alloc_irqs_range_locked(dev, domid, first, last); 1368f2480e7dSThomas Gleixner msi_unlock_descs(dev); 1369f2480e7dSThomas Gleixner return ret; 1370f2480e7dSThomas Gleixner } 1371f2480e7dSThomas Gleixner 1372f2480e7dSThomas Gleixner /** 1373f2480e7dSThomas Gleixner * msi_domain_alloc_irqs_all_locked - Allocate all interrupts from a MSI interrupt domain 1374f2480e7dSThomas Gleixner * 1375f2480e7dSThomas Gleixner * @dev: Pointer to device struct of the device for which the interrupts 1376f2480e7dSThomas Gleixner * are allocated 1377f2480e7dSThomas Gleixner * @domid: Id of the interrupt domain to operate on 1378f2480e7dSThomas Gleixner * @nirqs: The number of interrupts to allocate 1379f2480e7dSThomas Gleixner * 1380f2480e7dSThomas Gleixner * This function scans all MSI descriptors of the MSI domain and allocates interrupts 1381f2480e7dSThomas Gleixner * for all unassigned ones. That function is to be used for MSI domain usage where 1382f2480e7dSThomas Gleixner * the descriptor allocation is handled at the call site, e.g. PCI/MSI[X]. 1383f2480e7dSThomas Gleixner * 1384f2480e7dSThomas Gleixner * Return: %0 on success or an error code. 1385f2480e7dSThomas Gleixner */ 1386f2480e7dSThomas Gleixner int msi_domain_alloc_irqs_all_locked(struct device *dev, unsigned int domid, int nirqs) 1387f2480e7dSThomas Gleixner { 1388f2480e7dSThomas Gleixner struct msi_ctrl ctrl = { 1389f2480e7dSThomas Gleixner .domid = domid, 1390f2480e7dSThomas Gleixner .first = 0, 1391*36db3d90SThomas Gleixner .last = msi_domain_get_hwsize(dev, domid) - 1, 1392f2480e7dSThomas Gleixner .nirqs = nirqs, 1393f2480e7dSThomas Gleixner }; 1394f2480e7dSThomas Gleixner 1395f2480e7dSThomas Gleixner return msi_domain_alloc_locked(dev, &ctrl); 1396645474e2SThomas Gleixner } 1397645474e2SThomas Gleixner 13984cd5f440SThomas Gleixner static void __msi_domain_free_irqs(struct device *dev, struct irq_domain *domain, 13994cd5f440SThomas Gleixner struct msi_ctrl *ctrl) 1400d9109698SJiang Liu { 14014cd5f440SThomas Gleixner struct xarray *xa = &dev->msi.data->__domains[ctrl->domid].store; 1402bf5e758fSThomas Gleixner struct msi_domain_info *info = domain->host_data; 1403ef8dd015SThomas Gleixner struct irq_data *irqd; 1404d9109698SJiang Liu struct msi_desc *desc; 14054cd5f440SThomas Gleixner unsigned long idx; 1406dbbc9357SBixuan Cui int i; 1407dbbc9357SBixuan Cui 14084cd5f440SThomas Gleixner xa_for_each_range(xa, idx, desc, ctrl->first, ctrl->last) { 1409ef8dd015SThomas Gleixner /* Only handle MSI entries which have an interrupt associated */ 14104cd5f440SThomas Gleixner if (!msi_desc_match(desc, MSI_DESC_ASSOCIATED)) 14114cd5f440SThomas Gleixner continue; 14124cd5f440SThomas Gleixner 1413ef8dd015SThomas Gleixner /* Make sure all interrupts are deactivated */ 1414ef8dd015SThomas Gleixner for (i = 0; i < desc->nvec_used; i++) { 1415ef8dd015SThomas Gleixner irqd = irq_domain_get_irq_data(domain, desc->irq + i); 1416ef8dd015SThomas Gleixner if (irqd && irqd_is_activated(irqd)) 1417ef8dd015SThomas Gleixner irq_domain_deactivate_irq(irqd); 1418dbbc9357SBixuan Cui } 1419d9109698SJiang Liu 1420d9109698SJiang Liu irq_domain_free_irqs(desc->irq, desc->nvec_used); 1421bf5e758fSThomas Gleixner if (info->flags & MSI_FLAG_DEV_SYSFS) 1422bf5e758fSThomas Gleixner msi_sysfs_remove_desc(dev, desc); 1423d9109698SJiang Liu desc->irq = 0; 1424d9109698SJiang Liu } 1425d9109698SJiang Liu } 1426d9109698SJiang Liu 14274cd5f440SThomas Gleixner static void msi_domain_free_locked(struct device *dev, struct msi_ctrl *ctrl) 1428645474e2SThomas Gleixner { 14294cd5f440SThomas Gleixner struct msi_domain_info *info; 14304cd5f440SThomas Gleixner struct msi_domain_ops *ops; 14314cd5f440SThomas Gleixner struct irq_domain *domain; 14324cd5f440SThomas Gleixner 14334cd5f440SThomas Gleixner if (!msi_ctrl_valid(dev, ctrl)) 14344cd5f440SThomas Gleixner return; 14354cd5f440SThomas Gleixner 14364cd5f440SThomas Gleixner domain = msi_get_device_domain(dev, ctrl->domid); 14374cd5f440SThomas Gleixner if (!domain) 14384cd5f440SThomas Gleixner return; 14394cd5f440SThomas Gleixner 14404cd5f440SThomas Gleixner info = domain->host_data; 14414cd5f440SThomas Gleixner ops = info->ops; 14424cd5f440SThomas Gleixner 14434cd5f440SThomas Gleixner if (ops->domain_free_irqs) 14444cd5f440SThomas Gleixner ops->domain_free_irqs(domain, dev); 14454cd5f440SThomas Gleixner else 14464cd5f440SThomas Gleixner __msi_domain_free_irqs(dev, domain, ctrl); 14474cd5f440SThomas Gleixner 14484cd5f440SThomas Gleixner if (ops->msi_post_free) 14494cd5f440SThomas Gleixner ops->msi_post_free(domain, dev); 14504cd5f440SThomas Gleixner 1451645474e2SThomas Gleixner if (info->flags & MSI_FLAG_FREE_MSI_DESCS) 14524cd5f440SThomas Gleixner msi_domain_free_descs(dev, ctrl); 14534cd5f440SThomas Gleixner } 14544cd5f440SThomas Gleixner 14554cd5f440SThomas Gleixner /** 14564cd5f440SThomas Gleixner * msi_domain_free_irqs_range_locked - Free a range of interrupts from a MSI interrupt domain 14574cd5f440SThomas Gleixner * associated to @dev with msi_lock held 14584cd5f440SThomas Gleixner * @dev: Pointer to device struct of the device for which the interrupts 14594cd5f440SThomas Gleixner * are freed 14604cd5f440SThomas Gleixner * @domid: Id of the interrupt domain to operate on 14614cd5f440SThomas Gleixner * @first: First index to free (inclusive) 14624cd5f440SThomas Gleixner * @last: Last index to free (inclusive) 14634cd5f440SThomas Gleixner */ 14644cd5f440SThomas Gleixner void msi_domain_free_irqs_range_locked(struct device *dev, unsigned int domid, 14654cd5f440SThomas Gleixner unsigned int first, unsigned int last) 14664cd5f440SThomas Gleixner { 14674cd5f440SThomas Gleixner struct msi_ctrl ctrl = { 14684cd5f440SThomas Gleixner .domid = domid, 14694cd5f440SThomas Gleixner .first = first, 14704cd5f440SThomas Gleixner .last = last, 14714cd5f440SThomas Gleixner }; 14724cd5f440SThomas Gleixner msi_domain_free_locked(dev, &ctrl); 14734cd5f440SThomas Gleixner } 14744cd5f440SThomas Gleixner 14754cd5f440SThomas Gleixner /** 14764cd5f440SThomas Gleixner * msi_domain_free_irqs_range - Free a range of interrupts from a MSI interrupt domain 14774cd5f440SThomas Gleixner * associated to @dev 14784cd5f440SThomas Gleixner * @dev: Pointer to device struct of the device for which the interrupts 14794cd5f440SThomas Gleixner * are freed 14804cd5f440SThomas Gleixner * @domid: Id of the interrupt domain to operate on 14814cd5f440SThomas Gleixner * @first: First index to free (inclusive) 14824cd5f440SThomas Gleixner * @last: Last index to free (inclusive) 14834cd5f440SThomas Gleixner */ 14844cd5f440SThomas Gleixner void msi_domain_free_irqs_range(struct device *dev, unsigned int domid, 14854cd5f440SThomas Gleixner unsigned int first, unsigned int last) 14864cd5f440SThomas Gleixner { 14874cd5f440SThomas Gleixner msi_lock_descs(dev); 14884cd5f440SThomas Gleixner msi_domain_free_irqs_range_locked(dev, domid, first, last); 14894cd5f440SThomas Gleixner msi_unlock_descs(dev); 14904cd5f440SThomas Gleixner } 14914cd5f440SThomas Gleixner 14924cd5f440SThomas Gleixner /** 14934cd5f440SThomas Gleixner * msi_domain_free_irqs_all_locked - Free all interrupts from a MSI interrupt domain 14944cd5f440SThomas Gleixner * associated to a device 14954cd5f440SThomas Gleixner * @dev: Pointer to device struct of the device for which the interrupts 14964cd5f440SThomas Gleixner * are freed 14974cd5f440SThomas Gleixner * @domid: The id of the domain to operate on 14984cd5f440SThomas Gleixner * 14994cd5f440SThomas Gleixner * Must be invoked from within a msi_lock_descs() / msi_unlock_descs() 15004cd5f440SThomas Gleixner * pair. Use this for MSI irqdomains which implement their own vector 15014cd5f440SThomas Gleixner * allocation. 15024cd5f440SThomas Gleixner */ 15034cd5f440SThomas Gleixner void msi_domain_free_irqs_all_locked(struct device *dev, unsigned int domid) 15044cd5f440SThomas Gleixner { 1505*36db3d90SThomas Gleixner msi_domain_free_irqs_range_locked(dev, domid, 0, 1506*36db3d90SThomas Gleixner msi_domain_get_hwsize(dev, domid) - 1); 15074cd5f440SThomas Gleixner } 15084cd5f440SThomas Gleixner 15094cd5f440SThomas Gleixner /** 15104cd5f440SThomas Gleixner * msi_domain_free_irqs_all - Free all interrupts from a MSI interrupt domain 15114cd5f440SThomas Gleixner * associated to a device 15124cd5f440SThomas Gleixner * @dev: Pointer to device struct of the device for which the interrupts 15134cd5f440SThomas Gleixner * are freed 15144cd5f440SThomas Gleixner * @domid: The id of the domain to operate on 15154cd5f440SThomas Gleixner */ 15164cd5f440SThomas Gleixner void msi_domain_free_irqs_all(struct device *dev, unsigned int domid) 15174cd5f440SThomas Gleixner { 15184cd5f440SThomas Gleixner msi_lock_descs(dev); 15194cd5f440SThomas Gleixner msi_domain_free_irqs_all_locked(dev, domid); 15204cd5f440SThomas Gleixner msi_unlock_descs(dev); 1521645474e2SThomas Gleixner } 1522645474e2SThomas Gleixner 1523d9109698SJiang Liu /** 1524f3cf8bb0SJiang Liu * msi_get_domain_info - Get the MSI interrupt domain info for @domain 1525f3cf8bb0SJiang Liu * @domain: The interrupt domain to retrieve data from 1526f3cf8bb0SJiang Liu * 15273b35e7e6SRandy Dunlap * Return: the pointer to the msi_domain_info stored in @domain->host_data. 1528f3cf8bb0SJiang Liu */ 1529f3cf8bb0SJiang Liu struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain) 1530f3cf8bb0SJiang Liu { 1531f3cf8bb0SJiang Liu return (struct msi_domain_info *)domain->host_data; 1532f3cf8bb0SJiang Liu } 1533