Skip to content

Commit 15c72f8

Browse files
committed
PCI/MSI: Add support for per device MSI[X] domains
Provide a template and the necessary callbacks to create PCI/MSI and PCI/MSI-X domains. The domains are created when MSI or MSI-X is enabled. The domain's lifetime is either the device lifetime or in case that e.g. MSI-X was tried first and failed, then the MSI-X domain is removed and a MSI domain is created as both are mutually exclusive and reside in the default domain ID slot of the per device domain pointer array. Also expand pci_msi_domain_supports() to handle feature checks correctly even in the case that the per device domain was not yet created by checking the features supported by the MSI parent. Add the necessary setup calls into the MSI and MSI-X enable code path. These setup calls are backwards compatible. They return success when there is no parent domain found, which means the existing global domains or the legacy allocation path keep just working. Co-developed-by: Ahmed S. Darwish <[email protected]> Signed-off-by: Ahmed S. Darwish <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Reviewed-by: Kevin Tian <[email protected]> Acked-by: Bjorn Helgaas <[email protected]> Acked-by: Marc Zyngier <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent bd141a3 commit 15c72f8

File tree

3 files changed

+201
-5
lines changed

3 files changed

+201
-5
lines changed

drivers/pci/msi/irqdomain.c

Lines changed: 186 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,170 @@ struct irq_domain *pci_msi_create_irq_domain(struct fwnode_handle *fwnode,
139139
}
140140
EXPORT_SYMBOL_GPL(pci_msi_create_irq_domain);
141141

142+
/*
143+
* Per device MSI[-X] domain functionality
144+
*/
145+
static void pci_device_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
146+
{
147+
arg->desc = desc;
148+
arg->hwirq = desc->msi_index;
149+
}
150+
151+
static void pci_irq_mask_msi(struct irq_data *data)
152+
{
153+
struct msi_desc *desc = irq_data_get_msi_desc(data);
154+
155+
pci_msi_mask(desc, BIT(data->irq - desc->irq));
156+
}
157+
158+
static void pci_irq_unmask_msi(struct irq_data *data)
159+
{
160+
struct msi_desc *desc = irq_data_get_msi_desc(data);
161+
162+
pci_msi_unmask(desc, BIT(data->irq - desc->irq));
163+
}
164+
165+
#ifdef CONFIG_GENERIC_IRQ_RESERVATION_MODE
166+
# define MSI_REACTIVATE MSI_FLAG_MUST_REACTIVATE
167+
#else
168+
# define MSI_REACTIVATE 0
169+
#endif
170+
171+
#define MSI_COMMON_FLAGS (MSI_FLAG_FREE_MSI_DESCS | \
172+
MSI_FLAG_ACTIVATE_EARLY | \
173+
MSI_FLAG_DEV_SYSFS | \
174+
MSI_REACTIVATE)
175+
176+
static const struct msi_domain_template pci_msi_template = {
177+
.chip = {
178+
.name = "PCI-MSI",
179+
.irq_mask = pci_irq_mask_msi,
180+
.irq_unmask = pci_irq_unmask_msi,
181+
.irq_write_msi_msg = pci_msi_domain_write_msg,
182+
.flags = IRQCHIP_ONESHOT_SAFE,
183+
},
184+
185+
.ops = {
186+
.set_desc = pci_device_domain_set_desc,
187+
},
188+
189+
.info = {
190+
.flags = MSI_COMMON_FLAGS | MSI_FLAG_MULTI_PCI_MSI,
191+
.bus_token = DOMAIN_BUS_PCI_DEVICE_MSI,
192+
},
193+
};
194+
195+
static void pci_irq_mask_msix(struct irq_data *data)
196+
{
197+
pci_msix_mask(irq_data_get_msi_desc(data));
198+
}
199+
200+
static void pci_irq_unmask_msix(struct irq_data *data)
201+
{
202+
pci_msix_unmask(irq_data_get_msi_desc(data));
203+
}
204+
205+
static const struct msi_domain_template pci_msix_template = {
206+
.chip = {
207+
.name = "PCI-MSIX",
208+
.irq_mask = pci_irq_mask_msix,
209+
.irq_unmask = pci_irq_unmask_msix,
210+
.irq_write_msi_msg = pci_msi_domain_write_msg,
211+
.flags = IRQCHIP_ONESHOT_SAFE,
212+
},
213+
214+
.ops = {
215+
.set_desc = pci_device_domain_set_desc,
216+
},
217+
218+
.info = {
219+
.flags = MSI_COMMON_FLAGS | MSI_FLAG_PCI_MSIX,
220+
.bus_token = DOMAIN_BUS_PCI_DEVICE_MSIX,
221+
},
222+
};
223+
224+
static bool pci_match_device_domain(struct pci_dev *pdev, enum irq_domain_bus_token bus_token)
225+
{
226+
return msi_match_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN, bus_token);
227+
}
228+
229+
static bool pci_create_device_domain(struct pci_dev *pdev, const struct msi_domain_template *tmpl,
230+
unsigned int hwsize)
231+
{
232+
struct irq_domain *domain = dev_get_msi_domain(&pdev->dev);
233+
234+
if (!domain || !irq_domain_is_msi_parent(domain))
235+
return true;
236+
237+
return msi_create_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN, tmpl,
238+
hwsize, NULL, NULL);
239+
}
240+
241+
/**
242+
* pci_setup_msi_device_domain - Setup a device MSI interrupt domain
243+
* @pdev: The PCI device to create the domain on
244+
*
245+
* Return:
246+
* True when:
247+
* - The device does not have a MSI parent irq domain associated,
248+
* which keeps the legacy architecture specific and the global
249+
* PCI/MSI domain models working
250+
* - The MSI domain exists already
251+
* - The MSI domain was successfully allocated
252+
* False when:
253+
* - MSI-X is enabled
254+
* - The domain creation fails.
255+
*
256+
* The created MSI domain is preserved until:
257+
* - The device is removed
258+
* - MSI is disabled and a MSI-X domain is created
259+
*/
260+
bool pci_setup_msi_device_domain(struct pci_dev *pdev)
261+
{
262+
if (WARN_ON_ONCE(pdev->msix_enabled))
263+
return false;
264+
265+
if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSI))
266+
return true;
267+
if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSIX))
268+
msi_remove_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN);
269+
270+
return pci_create_device_domain(pdev, &pci_msi_template, 1);
271+
}
272+
273+
/**
274+
* pci_setup_msix_device_domain - Setup a device MSI-X interrupt domain
275+
* @pdev: The PCI device to create the domain on
276+
* @hwsize: The size of the MSI-X vector table
277+
*
278+
* Return:
279+
* True when:
280+
* - The device does not have a MSI parent irq domain associated,
281+
* which keeps the legacy architecture specific and the global
282+
* PCI/MSI domain models working
283+
* - The MSI-X domain exists already
284+
* - The MSI-X domain was successfully allocated
285+
* False when:
286+
* - MSI is enabled
287+
* - The domain creation fails.
288+
*
289+
* The created MSI-X domain is preserved until:
290+
* - The device is removed
291+
* - MSI-X is disabled and a MSI domain is created
292+
*/
293+
bool pci_setup_msix_device_domain(struct pci_dev *pdev, unsigned int hwsize)
294+
{
295+
if (WARN_ON_ONCE(pdev->msi_enabled))
296+
return false;
297+
298+
if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSIX))
299+
return true;
300+
if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSI))
301+
msi_remove_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN);
302+
303+
return pci_create_device_domain(pdev, &pci_msix_template, hwsize);
304+
}
305+
142306
/**
143307
* pci_msi_domain_supports - Check for support of a particular feature flag
144308
* @pdev: The PCI device to operate on
@@ -152,13 +316,33 @@ bool pci_msi_domain_supports(struct pci_dev *pdev, unsigned int feature_mask,
152316
{
153317
struct msi_domain_info *info;
154318
struct irq_domain *domain;
319+
unsigned int supported;
155320

156321
domain = dev_get_msi_domain(&pdev->dev);
157322

158323
if (!domain || !irq_domain_is_hierarchy(domain))
159324
return mode == ALLOW_LEGACY;
160-
info = domain->host_data;
161-
return (info->flags & feature_mask) == feature_mask;
325+
326+
if (!irq_domain_is_msi_parent(domain)) {
327+
/*
328+
* For "global" PCI/MSI interrupt domains the associated
329+
* msi_domain_info::flags is the authoritive source of
330+
* information.
331+
*/
332+
info = domain->host_data;
333+
supported = info->flags;
334+
} else {
335+
/*
336+
* For MSI parent domains the supported feature set
337+
* is avaliable in the parent ops. This makes checks
338+
* possible before actually instantiating the
339+
* per device domain because the parent is never
340+
* expanding the PCI/MSI functionality.
341+
*/
342+
supported = domain->msi_parent_ops->supported_flags;
343+
}
344+
345+
return (supported & feature_mask) == feature_mask;
162346
}
163347

164348
/*

drivers/pci/msi/msi.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,9 @@ int __pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec,
436436
if (rc)
437437
return rc;
438438

439+
if (!pci_setup_msi_device_domain(dev))
440+
return -ENODEV;
441+
439442
for (;;) {
440443
if (affd) {
441444
nvec = irq_calc_affinity_vectors(minvec, nvec, affd);
@@ -787,9 +790,13 @@ int __pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries, int
787790
if (!pci_msix_validate_entries(dev, entries, nvec, hwsize))
788791
return -EINVAL;
789792

790-
/* PCI_IRQ_VIRTUAL is a horrible hack! */
791-
if (nvec > hwsize && !(flags & PCI_IRQ_VIRTUAL))
792-
nvec = hwsize;
793+
if (hwsize < nvec) {
794+
/* Keep the IRQ virtual hackery working */
795+
if (flags & PCI_IRQ_VIRTUAL)
796+
hwsize = nvec;
797+
else
798+
nvec = hwsize;
799+
}
793800

794801
if (nvec < minvec)
795802
return -ENOSPC;
@@ -798,6 +805,9 @@ int __pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries, int
798805
if (rc)
799806
return rc;
800807

808+
if (!pci_setup_msix_device_domain(dev, hwsize))
809+
return -ENODEV;
810+
801811
for (;;) {
802812
if (affd) {
803813
nvec = irq_calc_affinity_vectors(minvec, nvec, affd);

drivers/pci/msi/msi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ enum support_mode {
105105
};
106106

107107
bool pci_msi_domain_supports(struct pci_dev *dev, unsigned int feature_mask, enum support_mode mode);
108+
bool pci_setup_msi_device_domain(struct pci_dev *pdev);
109+
bool pci_setup_msix_device_domain(struct pci_dev *pdev, unsigned int hwsize);
108110

109111
/* Legacy (!IRQDOMAIN) fallbacks */
110112

0 commit comments

Comments
 (0)