2021-12-06 23:27:52 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Message Signaled Interrupt ( MSI ) - irqdomain support
*/
# include <linux/acpi_iort.h>
# include <linux/irqdomain.h>
# include <linux/of_irq.h>
# include "msi.h"
int pci_msi_setup_msi_irqs ( struct pci_dev * dev , int nvec , int type )
{
struct irq_domain * domain ;
domain = dev_get_msi_domain ( & dev - > dev ) ;
if ( domain & & irq_domain_is_hierarchy ( domain ) )
2021-12-06 23:51:13 +01:00
return msi_domain_alloc_irqs_descs_locked ( domain , & dev - > dev , nvec ) ;
2021-12-06 23:27:52 +01:00
return pci_msi_legacy_setup_msi_irqs ( dev , nvec , type ) ;
}
void pci_msi_teardown_msi_irqs ( struct pci_dev * dev )
{
struct irq_domain * domain ;
domain = dev_get_msi_domain ( & dev - > dev ) ;
if ( domain & & irq_domain_is_hierarchy ( domain ) )
2021-12-06 23:51:13 +01:00
msi_domain_free_irqs_descs_locked ( domain , & dev - > dev ) ;
2021-12-06 23:27:52 +01:00
else
pci_msi_legacy_teardown_msi_irqs ( dev ) ;
2022-01-19 18:54:52 +01:00
msi_free_msi_descs ( & dev - > dev ) ;
2021-12-06 23:27:52 +01:00
}
/**
* pci_msi_domain_write_msg - Helper to write MSI message to PCI config space
* @ irq_data : Pointer to interrupt data of the MSI interrupt
* @ msg : Pointer to the message
*/
static void pci_msi_domain_write_msg ( struct irq_data * irq_data , struct msi_msg * msg )
{
struct msi_desc * desc = irq_data_get_msi_desc ( irq_data ) ;
/*
* For MSI - X desc - > irq is always equal to irq_data - > irq . For
* MSI only the first interrupt of MULTI MSI passes the test .
*/
if ( desc - > irq = = irq_data - > irq )
__pci_write_msi_msg ( desc , msg ) ;
}
/**
* pci_msi_domain_calc_hwirq - Generate a unique ID for an MSI source
* @ desc : Pointer to the MSI descriptor
*
* The ID number is only used within the irqdomain .
*/
static irq_hw_number_t pci_msi_domain_calc_hwirq ( struct msi_desc * desc )
{
struct pci_dev * dev = msi_desc_to_pci_dev ( desc ) ;
2021-12-10 23:19:18 +01:00
return ( irq_hw_number_t ) desc - > msi_index |
2021-12-06 23:27:52 +01:00
pci_dev_id ( dev ) < < 11 |
( pci_domain_nr ( dev - > bus ) & 0xFFFFFFFF ) < < 27 ;
}
static inline bool pci_msi_desc_is_multi_msi ( struct msi_desc * desc )
{
return ! desc - > pci . msi_attrib . is_msix & & desc - > nvec_used > 1 ;
}
/**
* pci_msi_domain_check_cap - Verify that @ domain supports the capabilities
* for @ dev
* @ domain : The interrupt domain to check
* @ info : The domain info for verification
* @ dev : The device to check
*
* Returns :
* 0 if the functionality is supported
* 1 if Multi MSI is requested , but the domain does not support it
* - ENOTSUPP otherwise
*/
2021-12-06 23:27:57 +01:00
static int pci_msi_domain_check_cap ( struct irq_domain * domain ,
struct msi_domain_info * info ,
struct device * dev )
2021-12-06 23:27:52 +01:00
{
2021-12-06 23:51:18 +01:00
struct msi_desc * desc = msi_first_desc ( dev , MSI_DESC_ALL ) ;
2021-12-06 23:27:52 +01:00
/* Special handling to support __pci_enable_msi_range() */
if ( pci_msi_desc_is_multi_msi ( desc ) & &
! ( info - > flags & MSI_FLAG_MULTI_PCI_MSI ) )
return 1 ;
2021-12-10 23:19:20 +01:00
if ( desc - > pci . msi_attrib . is_msix ) {
if ( ! ( info - > flags & MSI_FLAG_PCI_MSIX ) )
return - ENOTSUPP ;
if ( info - > flags & MSI_FLAG_MSIX_CONTIGUOUS ) {
unsigned int idx = 0 ;
/* Check for gaps in the entry indices */
2021-12-06 23:51:18 +01:00
msi_for_each_desc ( desc , dev , MSI_DESC_ALL ) {
2021-12-10 23:19:20 +01:00
if ( desc - > msi_index ! = idx + + )
return - ENOTSUPP ;
}
}
}
2021-12-06 23:27:52 +01:00
return 0 ;
}
static void pci_msi_domain_set_desc ( msi_alloc_info_t * arg ,
struct msi_desc * desc )
{
arg - > desc = desc ;
arg - > hwirq = pci_msi_domain_calc_hwirq ( desc ) ;
}
static struct msi_domain_ops pci_msi_domain_ops_default = {
. set_desc = pci_msi_domain_set_desc ,
. msi_check = pci_msi_domain_check_cap ,
} ;
static void pci_msi_domain_update_dom_ops ( struct msi_domain_info * info )
{
struct msi_domain_ops * ops = info - > ops ;
if ( ops = = NULL ) {
info - > ops = & pci_msi_domain_ops_default ;
} else {
if ( ops - > set_desc = = NULL )
ops - > set_desc = pci_msi_domain_set_desc ;
if ( ops - > msi_check = = NULL )
ops - > msi_check = pci_msi_domain_check_cap ;
}
}
static void pci_msi_domain_update_chip_ops ( struct msi_domain_info * info )
{
struct irq_chip * chip = info - > chip ;
BUG_ON ( ! chip ) ;
if ( ! chip - > irq_write_msi_msg )
chip - > irq_write_msi_msg = pci_msi_domain_write_msg ;
if ( ! chip - > irq_mask )
chip - > irq_mask = pci_msi_mask_irq ;
if ( ! chip - > irq_unmask )
chip - > irq_unmask = pci_msi_unmask_irq ;
}
/**
* pci_msi_create_irq_domain - Create a MSI interrupt domain
* @ fwnode : Optional fwnode of the interrupt controller
* @ info : MSI domain info
* @ parent : Parent irq domain
*
* Updates the domain and chip ops and creates a MSI interrupt domain .
*
* Returns :
* A domain pointer or NULL in case of failure .
*/
struct irq_domain * pci_msi_create_irq_domain ( struct fwnode_handle * fwnode ,
struct msi_domain_info * info ,
struct irq_domain * parent )
{
struct irq_domain * domain ;
if ( WARN_ON ( info - > flags & MSI_FLAG_LEVEL_CAPABLE ) )
info - > flags & = ~ MSI_FLAG_LEVEL_CAPABLE ;
if ( info - > flags & MSI_FLAG_USE_DEF_DOM_OPS )
pci_msi_domain_update_dom_ops ( info ) ;
if ( info - > flags & MSI_FLAG_USE_DEF_CHIP_OPS )
pci_msi_domain_update_chip_ops ( info ) ;
2022-01-19 18:54:52 +01:00
info - > flags | = MSI_FLAG_ACTIVATE_EARLY | MSI_FLAG_DEV_SYSFS ;
2021-12-06 23:27:52 +01:00
if ( IS_ENABLED ( CONFIG_GENERIC_IRQ_RESERVATION_MODE ) )
info - > flags | = MSI_FLAG_MUST_REACTIVATE ;
/* PCI-MSI is oneshot-safe */
info - > chip - > flags | = IRQCHIP_ONESHOT_SAFE ;
domain = msi_create_irq_domain ( fwnode , info , parent ) ;
if ( ! domain )
return NULL ;
irq_domain_update_bus_token ( domain , DOMAIN_BUS_PCI_MSI ) ;
return domain ;
}
EXPORT_SYMBOL_GPL ( pci_msi_create_irq_domain ) ;
/*
* Users of the generic MSI infrastructure expect a device to have a single ID ,
* so with DMA aliases we have to pick the least - worst compromise . Devices with
* DMA phantom functions tend to still emit MSIs from the real function number ,
* so we ignore those and only consider topological aliases where either the
* alias device or RID appears on a different bus number . We also make the
* reasonable assumption that bridges are walked in an upstream direction ( so
* the last one seen wins ) , and the much braver assumption that the most likely
* case is that of PCI - > PCIe so we should always use the alias RID . This echoes
* the logic from intel_irq_remapping ' s set_msi_sid ( ) , which presumably works
* well enough in practice ; in the face of the horrible PCIe < - > PCI - X conditions
* for taking ownership all we can really do is close our eyes and hope . . .
*/
static int get_msi_id_cb ( struct pci_dev * pdev , u16 alias , void * data )
{
u32 * pa = data ;
u8 bus = PCI_BUS_NUM ( * pa ) ;
if ( pdev - > bus - > number ! = bus | | PCI_BUS_NUM ( alias ) ! = bus )
* pa = alias ;
return 0 ;
}
/**
* pci_msi_domain_get_msi_rid - Get the MSI requester id ( RID )
* @ domain : The interrupt domain
* @ pdev : The PCI device .
*
* The RID for a device is formed from the alias , with a firmware
* supplied mapping applied
*
* Returns : The RID .
*/
u32 pci_msi_domain_get_msi_rid ( struct irq_domain * domain , struct pci_dev * pdev )
{
struct device_node * of_node ;
u32 rid = pci_dev_id ( pdev ) ;
pci_for_each_dma_alias ( pdev , get_msi_id_cb , & rid ) ;
of_node = irq_domain_get_of_node ( domain ) ;
rid = of_node ? of_msi_map_id ( & pdev - > dev , of_node , rid ) :
iort_msi_map_id ( & pdev - > dev , rid ) ;
return rid ;
}
/**
* pci_msi_get_device_domain - Get the MSI domain for a given PCI device
* @ pdev : The PCI device
*
* Use the firmware data to find a device - specific MSI domain
* ( i . e . not one that is set as a default ) .
*
* Returns : The corresponding MSI domain or NULL if none has been found .
*/
struct irq_domain * pci_msi_get_device_domain ( struct pci_dev * pdev )
{
struct irq_domain * dom ;
u32 rid = pci_dev_id ( pdev ) ;
pci_for_each_dma_alias ( pdev , get_msi_id_cb , & rid ) ;
dom = of_msi_map_get_device_domain ( & pdev - > dev , rid , DOMAIN_BUS_PCI_MSI ) ;
if ( ! dom )
dom = iort_get_device_domain ( & pdev - > dev , rid ,
DOMAIN_BUS_PCI_MSI ) ;
return dom ;
}
/**
* pci_dev_has_special_msi_domain - Check whether the device is handled by
* a non - standard PCI - MSI domain
* @ pdev : The PCI device to check .
*
* Returns : True if the device irqdomain or the bus irqdomain is
* non - standard PCI / MSI .
*/
bool pci_dev_has_special_msi_domain ( struct pci_dev * pdev )
{
struct irq_domain * dom = dev_get_msi_domain ( & pdev - > dev ) ;
if ( ! dom )
dom = dev_get_msi_domain ( & pdev - > bus - > dev ) ;
if ( ! dom )
return true ;
return dom - > bus_token ! = DOMAIN_BUS_PCI_MSI ;
}