2014-11-25 21:47:22 +03:00
/*
* ARM GIC v2m MSI ( - X ) support
* Support for Message Signaled Interrupts for systems that
* implement ARM Generic Interrupt Controller : GICv2m .
*
* Copyright ( C ) 2014 Advanced Micro Devices , Inc .
* Authors : Suravee Suthikulpanit < suravee . suthikulpanit @ amd . com >
* Harish Kasiviswanathan < harish . kasiviswanathan @ amd . com >
* Brandon Anderson < brandon . anderson @ amd . com >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation .
*/
# define pr_fmt(fmt) "GICv2m: " fmt
2015-12-10 19:55:30 +03:00
# include <linux/acpi.h>
2014-11-25 21:47:22 +03:00
# include <linux/irq.h>
# include <linux/irqdomain.h>
# include <linux/kernel.h>
2015-12-10 19:55:30 +03:00
# include <linux/msi.h>
2014-11-25 21:47:22 +03:00
# include <linux/of_address.h>
# include <linux/of_pci.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
/*
* MSI_TYPER :
* [ 31 : 26 ] Reserved
* [ 25 : 16 ] lowest SPI assigned to MSI
* [ 15 : 10 ] Reserved
* [ 9 : 0 ] Numer of SPIs assigned to MSI
*/
# define V2M_MSI_TYPER 0x008
# define V2M_MSI_TYPER_BASE_SHIFT 16
# define V2M_MSI_TYPER_BASE_MASK 0x3FF
# define V2M_MSI_TYPER_NUM_MASK 0x3FF
# define V2M_MSI_SETSPI_NS 0x040
# define V2M_MIN_SPI 32
# define V2M_MAX_SPI 1019
2015-10-07 01:32:38 +03:00
# define V2M_MSI_IIDR 0xFCC
2014-11-25 21:47:22 +03:00
# define V2M_MSI_TYPER_BASE_SPI(x) \
( ( ( x ) > > V2M_MSI_TYPER_BASE_SHIFT ) & V2M_MSI_TYPER_BASE_MASK )
# define V2M_MSI_TYPER_NUM_SPI(x) ((x) & V2M_MSI_TYPER_NUM_MASK)
2015-10-07 01:32:38 +03:00
/* APM X-Gene with GICv2m MSI_IIDR register value */
# define XGENE_GICV2M_MSI_IIDR 0x06000170
/* List of flags for specific v2m implementation */
# define GICV2M_NEEDS_SPI_OFFSET 0x00000001
2015-10-14 14:27:17 +03:00
static LIST_HEAD ( v2m_nodes ) ;
static DEFINE_SPINLOCK ( v2m_lock ) ;
2014-11-25 21:47:22 +03:00
struct v2m_data {
2015-10-14 14:27:17 +03:00
struct list_head entry ;
2015-12-10 19:55:29 +03:00
struct fwnode_handle * fwnode ;
2014-11-25 21:47:22 +03:00
struct resource res ; /* GICv2m resource */
void __iomem * base ; /* GICv2m virt address */
u32 spi_start ; /* The SPI number that MSIs start */
u32 nr_spis ; /* The number of SPIs for MSIs */
unsigned long * bm ; /* MSI vector bitmap */
2015-10-07 01:32:38 +03:00
u32 flags ; /* v2m flags for specific implementation */
2014-11-25 21:47:22 +03:00
} ;
static void gicv2m_mask_msi_irq ( struct irq_data * d )
{
pci_msi_mask_irq ( d ) ;
irq_chip_mask_parent ( d ) ;
}
static void gicv2m_unmask_msi_irq ( struct irq_data * d )
{
pci_msi_unmask_irq ( d ) ;
irq_chip_unmask_parent ( d ) ;
}
static struct irq_chip gicv2m_msi_irq_chip = {
. name = " MSI " ,
. irq_mask = gicv2m_mask_msi_irq ,
. irq_unmask = gicv2m_unmask_msi_irq ,
. irq_eoi = irq_chip_eoi_parent ,
. irq_write_msi_msg = pci_msi_domain_write_msg ,
} ;
static struct msi_domain_info gicv2m_msi_domain_info = {
. flags = ( MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
MSI_FLAG_PCI_MSIX ) ,
. chip = & gicv2m_msi_irq_chip ,
} ;
static int gicv2m_set_affinity ( struct irq_data * irq_data ,
const struct cpumask * mask , bool force )
{
int ret ;
ret = irq_chip_set_affinity_parent ( irq_data , mask , force ) ;
if ( ret = = IRQ_SET_MASK_OK )
ret = IRQ_SET_MASK_OK_DONE ;
return ret ;
}
static void gicv2m_compose_msi_msg ( struct irq_data * data , struct msi_msg * msg )
{
struct v2m_data * v2m = irq_data_get_irq_chip_data ( data ) ;
phys_addr_t addr = v2m - > res . start + V2M_MSI_SETSPI_NS ;
2015-09-13 14:14:33 +03:00
msg - > address_hi = upper_32_bits ( addr ) ;
msg - > address_lo = lower_32_bits ( addr ) ;
2014-11-25 21:47:22 +03:00
msg - > data = data - > hwirq ;
2015-10-07 01:32:38 +03:00
if ( v2m - > flags & GICV2M_NEEDS_SPI_OFFSET )
msg - > data - = v2m - > spi_start ;
2014-11-25 21:47:22 +03:00
}
static struct irq_chip gicv2m_irq_chip = {
. name = " GICv2m " ,
. irq_mask = irq_chip_mask_parent ,
. irq_unmask = irq_chip_unmask_parent ,
. irq_eoi = irq_chip_eoi_parent ,
. irq_set_affinity = gicv2m_set_affinity ,
. irq_compose_msi_msg = gicv2m_compose_msi_msg ,
} ;
static int gicv2m_irq_gic_domain_alloc ( struct irq_domain * domain ,
unsigned int virq ,
irq_hw_number_t hwirq )
{
2015-10-13 14:51:33 +03:00
struct irq_fwspec fwspec ;
2014-11-25 21:47:22 +03:00
struct irq_data * d ;
int err ;
2015-10-13 14:51:33 +03:00
if ( is_of_node ( domain - > parent - > fwnode ) ) {
fwspec . fwnode = domain - > parent - > fwnode ;
fwspec . param_count = 3 ;
fwspec . param [ 0 ] = 0 ;
fwspec . param [ 1 ] = hwirq - 32 ;
fwspec . param [ 2 ] = IRQ_TYPE_EDGE_RISING ;
2015-12-10 19:55:30 +03:00
} else if ( is_fwnode_irqchip ( domain - > parent - > fwnode ) ) {
fwspec . fwnode = domain - > parent - > fwnode ;
fwspec . param_count = 2 ;
fwspec . param [ 0 ] = hwirq ;
fwspec . param [ 1 ] = IRQ_TYPE_EDGE_RISING ;
2015-10-13 14:51:33 +03:00
} else {
return - EINVAL ;
}
2014-11-25 21:47:22 +03:00
2015-10-13 14:51:33 +03:00
err = irq_domain_alloc_irqs_parent ( domain , virq , 1 , & fwspec ) ;
2014-11-25 21:47:22 +03:00
if ( err )
return err ;
/* Configure the interrupt line to be edge */
d = irq_domain_get_irq_data ( domain - > parent , virq ) ;
d - > chip - > irq_set_type ( d , IRQ_TYPE_EDGE_RISING ) ;
return 0 ;
}
static void gicv2m_unalloc_msi ( struct v2m_data * v2m , unsigned int hwirq )
{
int pos ;
pos = hwirq - v2m - > spi_start ;
if ( pos < 0 | | pos > = v2m - > nr_spis ) {
pr_err ( " Failed to teardown msi. Invalid hwirq %d \n " , hwirq ) ;
return ;
}
2015-10-14 14:27:17 +03:00
spin_lock ( & v2m_lock ) ;
2014-11-25 21:47:22 +03:00
__clear_bit ( pos , v2m - > bm ) ;
2015-10-14 14:27:17 +03:00
spin_unlock ( & v2m_lock ) ;
2014-11-25 21:47:22 +03:00
}
static int gicv2m_irq_domain_alloc ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs , void * args )
{
2015-10-14 14:27:17 +03:00
struct v2m_data * v2m = NULL , * tmp ;
2014-11-25 21:47:22 +03:00
int hwirq , offset , err = 0 ;
2015-10-14 14:27:17 +03:00
spin_lock ( & v2m_lock ) ;
list_for_each_entry ( tmp , & v2m_nodes , entry ) {
offset = find_first_zero_bit ( tmp - > bm , tmp - > nr_spis ) ;
if ( offset < tmp - > nr_spis ) {
__set_bit ( offset , tmp - > bm ) ;
v2m = tmp ;
break ;
}
}
spin_unlock ( & v2m_lock ) ;
2014-11-25 21:47:22 +03:00
2015-10-14 14:27:17 +03:00
if ( ! v2m )
return - ENOSPC ;
2014-11-25 21:47:22 +03:00
hwirq = v2m - > spi_start + offset ;
err = gicv2m_irq_gic_domain_alloc ( domain , virq , hwirq ) ;
if ( err ) {
gicv2m_unalloc_msi ( v2m , hwirq ) ;
return err ;
}
irq_domain_set_hwirq_and_chip ( domain , virq , hwirq ,
& gicv2m_irq_chip , v2m ) ;
return 0 ;
}
static void gicv2m_irq_domain_free ( struct irq_domain * domain ,
unsigned int virq , unsigned int nr_irqs )
{
struct irq_data * d = irq_domain_get_irq_data ( domain , virq ) ;
struct v2m_data * v2m = irq_data_get_irq_chip_data ( d ) ;
BUG_ON ( nr_irqs ! = 1 ) ;
gicv2m_unalloc_msi ( v2m , d - > hwirq ) ;
irq_domain_free_irqs_parent ( domain , virq , nr_irqs ) ;
}
static const struct irq_domain_ops gicv2m_domain_ops = {
. alloc = gicv2m_irq_domain_alloc ,
. free = gicv2m_irq_domain_free ,
} ;
static bool is_msi_spi_valid ( u32 base , u32 num )
{
if ( base < V2M_MIN_SPI ) {
pr_err ( " Invalid MSI base SPI (base:%u) \n " , base ) ;
return false ;
}
if ( ( num = = 0 ) | | ( base + num > V2M_MAX_SPI ) ) {
pr_err ( " Number of SPIs (%u) exceed maximum (%u) \n " ,
num , V2M_MAX_SPI - V2M_MIN_SPI + 1 ) ;
return false ;
}
return true ;
}
2015-07-28 16:46:24 +03:00
static struct irq_chip gicv2m_pmsi_irq_chip = {
. name = " pMSI " ,
} ;
static struct msi_domain_ops gicv2m_pmsi_ops = {
} ;
static struct msi_domain_info gicv2m_pmsi_domain_info = {
. flags = ( MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS ) ,
. ops = & gicv2m_pmsi_ops ,
. chip = & gicv2m_pmsi_irq_chip ,
} ;
2015-10-14 14:27:17 +03:00
static void gicv2m_teardown ( void )
{
struct v2m_data * v2m , * tmp ;
list_for_each_entry_safe ( v2m , tmp , & v2m_nodes , entry ) {
list_del ( & v2m - > entry ) ;
kfree ( v2m - > bm ) ;
iounmap ( v2m - > base ) ;
2015-12-10 19:55:29 +03:00
of_node_put ( to_of_node ( v2m - > fwnode ) ) ;
2015-12-10 19:55:30 +03:00
if ( is_fwnode_irqchip ( v2m - > fwnode ) )
irq_domain_free_fwnode ( v2m - > fwnode ) ;
2015-10-14 14:27:17 +03:00
kfree ( v2m ) ;
}
}
static int gicv2m_allocate_domains ( struct irq_domain * parent )
{
struct irq_domain * inner_domain , * pci_domain , * plat_domain ;
struct v2m_data * v2m ;
v2m = list_first_entry_or_null ( & v2m_nodes , struct v2m_data , entry ) ;
if ( ! v2m )
return 0 ;
2015-12-10 19:55:29 +03:00
inner_domain = irq_domain_create_tree ( v2m - > fwnode ,
2015-10-14 14:27:17 +03:00
& gicv2m_domain_ops , v2m ) ;
if ( ! inner_domain ) {
pr_err ( " Failed to create GICv2m domain \n " ) ;
return - ENOMEM ;
}
inner_domain - > bus_token = DOMAIN_BUS_NEXUS ;
inner_domain - > parent = parent ;
2015-12-10 19:55:29 +03:00
pci_domain = pci_msi_create_irq_domain ( v2m - > fwnode ,
2015-10-14 14:27:17 +03:00
& gicv2m_msi_domain_info ,
inner_domain ) ;
2015-12-10 19:55:29 +03:00
plat_domain = platform_msi_create_irq_domain ( v2m - > fwnode ,
2015-10-14 14:27:17 +03:00
& gicv2m_pmsi_domain_info ,
inner_domain ) ;
if ( ! pci_domain | | ! plat_domain ) {
pr_err ( " Failed to create MSI domains \n " ) ;
if ( plat_domain )
irq_domain_remove ( plat_domain ) ;
if ( pci_domain )
irq_domain_remove ( pci_domain ) ;
irq_domain_remove ( inner_domain ) ;
return - ENOMEM ;
}
return 0 ;
}
2015-12-10 19:55:29 +03:00
static int __init gicv2m_init_one ( struct fwnode_handle * fwnode ,
u32 spi_start , u32 nr_spis ,
struct resource * res )
2014-11-25 21:47:22 +03:00
{
int ret ;
struct v2m_data * v2m ;
v2m = kzalloc ( sizeof ( struct v2m_data ) , GFP_KERNEL ) ;
if ( ! v2m ) {
pr_err ( " Failed to allocate struct v2m_data. \n " ) ;
return - ENOMEM ;
}
2015-10-14 14:27:17 +03:00
INIT_LIST_HEAD ( & v2m - > entry ) ;
2015-12-10 19:55:29 +03:00
v2m - > fwnode = fwnode ;
2015-10-14 14:27:17 +03:00
2015-12-10 19:55:29 +03:00
memcpy ( & v2m - > res , res , sizeof ( struct resource ) ) ;
2014-11-25 21:47:22 +03:00
v2m - > base = ioremap ( v2m - > res . start , resource_size ( & v2m - > res ) ) ;
if ( ! v2m - > base ) {
pr_err ( " Failed to map GICv2m resource \n " ) ;
ret = - ENOMEM ;
goto err_free_v2m ;
}
2015-12-10 19:55:29 +03:00
if ( spi_start & & nr_spis ) {
v2m - > spi_start = spi_start ;
v2m - > nr_spis = nr_spis ;
2014-11-25 21:47:22 +03:00
} else {
u32 typer = readl_relaxed ( v2m - > base + V2M_MSI_TYPER ) ;
v2m - > spi_start = V2M_MSI_TYPER_BASE_SPI ( typer ) ;
v2m - > nr_spis = V2M_MSI_TYPER_NUM_SPI ( typer ) ;
}
if ( ! is_msi_spi_valid ( v2m - > spi_start , v2m - > nr_spis ) ) {
ret = - EINVAL ;
goto err_iounmap ;
}
2015-10-07 01:32:38 +03:00
/*
* APM X - Gene GICv2m implementation has an erratum where
* the MSI data needs to be the offset from the spi_start
* in order to trigger the correct MSI interrupt . This is
* different from the standard GICv2m implementation where
* the MSI data is the absolute value within the range from
* spi_start to ( spi_start + num_spis ) .
*/
if ( readl_relaxed ( v2m - > base + V2M_MSI_IIDR ) = = XGENE_GICV2M_MSI_IIDR )
v2m - > flags | = GICV2M_NEEDS_SPI_OFFSET ;
2014-11-25 21:47:22 +03:00
v2m - > bm = kzalloc ( sizeof ( long ) * BITS_TO_LONGS ( v2m - > nr_spis ) ,
GFP_KERNEL ) ;
if ( ! v2m - > bm ) {
ret = - ENOMEM ;
goto err_iounmap ;
}
2015-10-14 14:27:17 +03:00
list_add_tail ( & v2m - > entry , & v2m_nodes ) ;
2014-11-25 21:47:22 +03:00
2015-12-10 19:55:29 +03:00
pr_info ( " range[%#lx:%#lx], SPI[%d:%d] \n " ,
( unsigned long ) res - > start , ( unsigned long ) res - > end ,
v2m - > spi_start , ( v2m - > spi_start + v2m - > nr_spis ) ) ;
2014-11-25 21:47:22 +03:00
return 0 ;
err_iounmap :
iounmap ( v2m - > base ) ;
err_free_v2m :
kfree ( v2m ) ;
return ret ;
}
static struct of_device_id gicv2m_device_id [ ] = {
{ . compatible = " arm,gic-v2m-frame " , } ,
{ } ,
} ;
2015-12-10 19:55:30 +03:00
static int __init gicv2m_of_init ( struct fwnode_handle * parent_handle ,
struct irq_domain * parent )
2014-11-25 21:47:22 +03:00
{
int ret = 0 ;
2015-12-10 19:55:30 +03:00
struct device_node * node = to_of_node ( parent_handle ) ;
2014-11-25 21:47:22 +03:00
struct device_node * child ;
for ( child = of_find_matching_node ( node , gicv2m_device_id ) ; child ;
child = of_find_matching_node ( child , gicv2m_device_id ) ) {
2015-12-10 19:55:29 +03:00
u32 spi_start = 0 , nr_spis = 0 ;
struct resource res ;
2014-11-25 21:47:22 +03:00
if ( ! of_find_property ( child , " msi-controller " , NULL ) )
continue ;
2015-12-10 19:55:29 +03:00
ret = of_address_to_resource ( child , 0 , & res ) ;
if ( ret ) {
pr_err ( " Failed to allocate v2m resource. \n " ) ;
break ;
}
if ( ! of_property_read_u32 ( child , " arm,msi-base-spi " ,
& spi_start ) & &
! of_property_read_u32 ( child , " arm,msi-num-spis " , & nr_spis ) )
pr_info ( " DT overriding V2M MSI_TYPER (base:%u, num:%u) \n " ,
spi_start , nr_spis ) ;
ret = gicv2m_init_one ( & child - > fwnode , spi_start , nr_spis , & res ) ;
2014-11-25 21:47:22 +03:00
if ( ret ) {
2015-12-16 14:03:24 +03:00
of_node_put ( child ) ;
2014-11-25 21:47:22 +03:00
break ;
}
}
2015-10-14 14:27:17 +03:00
if ( ! ret )
ret = gicv2m_allocate_domains ( parent ) ;
if ( ret )
gicv2m_teardown ( ) ;
2014-11-25 21:47:22 +03:00
return ret ;
}
2015-12-10 19:55:30 +03:00
# ifdef CONFIG_ACPI
static int acpi_num_msi ;
static struct fwnode_handle * gicv2m_get_fwnode ( struct device * dev )
{
struct v2m_data * data ;
if ( WARN_ON ( acpi_num_msi < = 0 ) )
return NULL ;
/* We only return the fwnode of the first MSI frame. */
data = list_first_entry_or_null ( & v2m_nodes , struct v2m_data , entry ) ;
if ( ! data )
return NULL ;
return data - > fwnode ;
}
static int __init
acpi_parse_madt_msi ( struct acpi_subtable_header * header ,
const unsigned long end )
{
int ret ;
struct resource res ;
u32 spi_start = 0 , nr_spis = 0 ;
struct acpi_madt_generic_msi_frame * m ;
struct fwnode_handle * fwnode ;
m = ( struct acpi_madt_generic_msi_frame * ) header ;
if ( BAD_MADT_ENTRY ( m , end ) )
return - EINVAL ;
res . start = m - > base_address ;
res . end = m - > base_address + SZ_4K ;
if ( m - > flags & ACPI_MADT_OVERRIDE_SPI_VALUES ) {
spi_start = m - > spi_base ;
nr_spis = m - > spi_count ;
pr_info ( " ACPI overriding V2M MSI_TYPER (base:%u, num:%u) \n " ,
spi_start , nr_spis ) ;
}
fwnode = irq_domain_alloc_fwnode ( ( void * ) m - > base_address ) ;
if ( ! fwnode ) {
pr_err ( " Unable to allocate GICv2m domain token \n " ) ;
return - EINVAL ;
}
ret = gicv2m_init_one ( fwnode , spi_start , nr_spis , & res ) ;
if ( ret )
irq_domain_free_fwnode ( fwnode ) ;
return ret ;
}
static int __init gicv2m_acpi_init ( struct irq_domain * parent )
{
int ret ;
if ( acpi_num_msi > 0 )
return 0 ;
acpi_num_msi = acpi_table_parse_madt ( ACPI_MADT_TYPE_GENERIC_MSI_FRAME ,
acpi_parse_madt_msi , 0 ) ;
if ( acpi_num_msi < = 0 )
goto err_out ;
ret = gicv2m_allocate_domains ( parent ) ;
if ( ret )
goto err_out ;
pci_msi_register_fwnode_provider ( & gicv2m_get_fwnode ) ;
return 0 ;
err_out :
gicv2m_teardown ( ) ;
return - EINVAL ;
}
# else /* CONFIG_ACPI */
static int __init gicv2m_acpi_init ( struct irq_domain * parent )
{
return - EINVAL ;
}
# endif /* CONFIG_ACPI */
int __init gicv2m_init ( struct fwnode_handle * parent_handle ,
struct irq_domain * parent )
{
if ( is_of_node ( parent_handle ) )
return gicv2m_of_init ( parent_handle , parent ) ;
return gicv2m_acpi_init ( parent ) ;
}