2014-10-27 16:12:03 +08:00
/*
* Support Hypertransport IRQ
*
* Copyright ( C ) 1997 , 1998 , 1999 , 2000 , 2009 Ingo Molnar , Hajnalka Szabo
* Moved from arch / x86 / kernel / apic / io_apic . c .
2015-04-13 14:11:43 +08:00
* Jiang Liu < jiang . liu @ linux . intel . com >
* Add support of hierarchical irqdomain
2014-10-27 16:12:03 +08:00
*
* 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 .
*/
# include <linux/mm.h>
# include <linux/interrupt.h>
# include <linux/init.h>
# include <linux/device.h>
# include <linux/pci.h>
# include <linux/htirq.h>
2015-04-14 10:30:09 +08:00
# include <asm/irqdomain.h>
2014-10-27 16:12:03 +08:00
# include <asm/hw_irq.h>
# include <asm/apic.h>
# include <asm/hypertransport.h>
2015-04-13 14:11:43 +08:00
static struct irq_domain * htirq_domain ;
2014-10-27 16:12:03 +08:00
/*
* Hypertransport interrupt support
*/
static int
ht_set_affinity ( struct irq_data * data , const struct cpumask * mask , bool force )
{
2015-04-13 14:11:43 +08:00
struct irq_data * parent = data - > parent_data ;
2014-10-27 16:12:03 +08:00
int ret ;
2015-04-13 14:11:43 +08:00
ret = parent - > chip - > irq_set_affinity ( parent , mask , force ) ;
if ( ret > = 0 ) {
struct ht_irq_msg msg ;
struct irq_cfg * cfg = irqd_cfg ( data ) ;
fetch_ht_irq_msg ( data - > irq , & msg ) ;
msg . address_lo & = ~ ( HT_IRQ_LOW_VECTOR_MASK |
HT_IRQ_LOW_DEST_ID_MASK ) ;
msg . address_lo | = HT_IRQ_LOW_VECTOR ( cfg - > vector ) |
HT_IRQ_LOW_DEST_ID ( cfg - > dest_apicid ) ;
msg . address_hi & = ~ ( HT_IRQ_HIGH_DEST_ID_MASK ) ;
msg . address_hi | = HT_IRQ_HIGH_DEST_ID ( cfg - > dest_apicid ) ;
write_ht_irq_msg ( data - > irq , & msg ) ;
}
return ret ;
2014-10-27 16:12:03 +08:00
}
static struct irq_chip ht_irq_chip = {
. name = " PCI-HT " ,
. irq_mask = mask_ht_irq ,
. irq_unmask = unmask_ht_irq ,
2015-04-13 14:11:43 +08:00
. irq_ack = irq_chip_ack_parent ,
2014-10-27 16:12:03 +08:00
. irq_set_affinity = ht_set_affinity ,
2015-04-13 14:11:43 +08:00
. irq_retrigger = irq_chip_retrigger_hierarchy ,
2014-10-27 16:12:03 +08:00
. flags = IRQCHIP_SKIP_SET_WAKE ,
} ;
2015-04-13 14:11:43 +08:00
static int htirq_domain_alloc ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs , void * arg )
2015-04-13 14:11:28 +08:00
{
2015-04-13 14:11:43 +08:00
struct ht_irq_cfg * ht_cfg ;
struct irq_alloc_info * info = arg ;
struct pci_dev * dev ;
irq_hw_number_t hwirq ;
int ret ;
if ( nr_irqs > 1 | | ! info )
return - EINVAL ;
dev = info - > ht_dev ;
hwirq = ( info - > ht_idx & 0xFF ) |
PCI_DEVID ( dev - > bus - > number , dev - > devfn ) < < 8 |
( pci_domain_nr ( dev - > bus ) & 0xFFFFFFFF ) < < 24 ;
if ( irq_find_mapping ( domain , hwirq ) > 0 )
return - EEXIST ;
ht_cfg = kmalloc ( sizeof ( * ht_cfg ) , GFP_KERNEL ) ;
if ( ! ht_cfg )
return - ENOMEM ;
ret = irq_domain_alloc_irqs_parent ( domain , virq , nr_irqs , info ) ;
if ( ret < 0 ) {
kfree ( ht_cfg ) ;
return ret ;
}
/* Initialize msg to a value that will never match the first write. */
ht_cfg - > msg . address_lo = 0xffffffff ;
ht_cfg - > msg . address_hi = 0xffffffff ;
ht_cfg - > dev = info - > ht_dev ;
ht_cfg - > update = info - > ht_update ;
ht_cfg - > pos = info - > ht_pos ;
ht_cfg - > idx = 0x10 + ( info - > ht_idx * 2 ) ;
irq_domain_set_info ( domain , virq , hwirq , & ht_irq_chip , ht_cfg ,
handle_edge_irq , ht_cfg , " edge " ) ;
return 0 ;
2015-04-13 14:11:28 +08:00
}
2015-04-13 14:11:43 +08:00
static void htirq_domain_free ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs )
2015-04-13 14:11:28 +08:00
{
2015-04-13 14:11:43 +08:00
struct irq_data * irq_data = irq_domain_get_irq_data ( domain , virq ) ;
BUG_ON ( nr_irqs ! = 1 ) ;
kfree ( irq_data - > chip_data ) ;
irq_domain_free_irqs_top ( domain , virq , nr_irqs ) ;
2015-04-13 14:11:28 +08:00
}
2015-04-13 14:11:43 +08:00
static void htirq_domain_activate ( struct irq_domain * domain ,
struct irq_data * irq_data )
2014-10-27 16:12:03 +08:00
{
struct ht_irq_msg msg ;
2015-04-13 14:11:43 +08:00
struct irq_cfg * cfg = irqd_cfg ( irq_data ) ;
2014-10-27 16:12:03 +08:00
2015-04-13 14:11:28 +08:00
msg . address_hi = HT_IRQ_HIGH_DEST_ID ( cfg - > dest_apicid ) ;
2014-10-27 16:12:03 +08:00
msg . address_lo =
HT_IRQ_LOW_BASE |
2015-04-13 14:11:28 +08:00
HT_IRQ_LOW_DEST_ID ( cfg - > dest_apicid ) |
2014-10-27 16:12:03 +08:00
HT_IRQ_LOW_VECTOR ( cfg - > vector ) |
( ( apic - > irq_dest_mode = = 0 ) ?
HT_IRQ_LOW_DM_PHYSICAL :
HT_IRQ_LOW_DM_LOGICAL ) |
HT_IRQ_LOW_RQEOI_EDGE |
( ( apic - > irq_delivery_mode ! = dest_LowestPrio ) ?
HT_IRQ_LOW_MT_FIXED :
HT_IRQ_LOW_MT_ARBITRATED ) |
HT_IRQ_LOW_IRQ_MASKED ;
2015-04-13 14:11:43 +08:00
write_ht_irq_msg ( irq_data - > irq , & msg ) ;
}
2014-10-27 16:12:03 +08:00
2015-04-13 14:11:43 +08:00
static void htirq_domain_deactivate ( struct irq_domain * domain ,
struct irq_data * irq_data )
{
struct ht_irq_msg msg ;
2014-10-27 16:12:03 +08:00
2015-04-13 14:11:43 +08:00
memset ( & msg , 0 , sizeof ( msg ) ) ;
write_ht_irq_msg ( irq_data - > irq , & msg ) ;
}
2014-10-27 16:12:03 +08:00
2015-05-05 11:10:11 +02:00
static const struct irq_domain_ops htirq_domain_ops = {
. alloc = htirq_domain_alloc ,
. free = htirq_domain_free ,
. activate = htirq_domain_activate ,
. deactivate = htirq_domain_deactivate ,
2015-04-13 14:11:43 +08:00
} ;
2014-10-27 16:12:03 +08:00
2017-06-22 11:15:41 +08:00
void __init arch_init_htirq_domain ( struct irq_domain * parent )
2015-04-13 14:11:43 +08:00
{
if ( disable_apic )
return ;
htirq_domain = irq_domain_add_tree ( NULL , & htirq_domain_ops , NULL ) ;
if ( ! htirq_domain )
pr_warn ( " failed to initialize irqdomain for HTIRQ. \n " ) ;
else
htirq_domain - > parent = parent ;
}
int arch_setup_ht_irq ( int idx , int pos , struct pci_dev * dev ,
ht_irq_update_t * update )
{
struct irq_alloc_info info ;
if ( ! htirq_domain )
return - ENOSYS ;
init_irq_alloc_info ( & info , NULL ) ;
info . ht_idx = idx ;
info . ht_pos = pos ;
info . ht_dev = dev ;
info . ht_update = update ;
return irq_domain_alloc_irqs ( htirq_domain , 1 , dev_to_node ( & dev - > dev ) ,
& info ) ;
}
void arch_teardown_ht_irq ( unsigned int irq )
{
irq_domain_free_irqs ( irq , 1 ) ;
2014-10-27 16:12:03 +08:00
}