Commit 49e07d8f authored by Jiang Liu's avatar Jiang Liu Committed by Thomas Gleixner

x86/htirq: Use hierarchical irqdomain to manage Hypertransport interrupts

We have slightly changed the architecture interfaces to support htirq
PCI driver. It's safe because currently Hypertransport interrupt is
only enabled on x86 platforms.
Signed-off-by: default avatarJiang Liu <jiang.liu@linux.intel.com>
Cc: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Cc: David Cohen <david.a.cohen@linux.intel.com>
Cc: Sander Eikelenboom <linux@eikelenboom.it>
Cc: David Vrabel <david.vrabel@citrix.com>
Cc: Tony Luck <tony.luck@intel.com>
Cc: Joerg Roedel <joro@8bytes.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Rafael J. Wysocki <rjw@rjwysocki.net>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Yinghai Lu <yinghai@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Dimitri Sivanich <sivanich@sgi.com>
Link: http://lkml.kernel.org/r/1428905519-23704-22-git-send-email-jiang.liu@linux.intel.comSigned-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
parent 0921f1da
...@@ -160,6 +160,14 @@ struct irq_alloc_info { ...@@ -160,6 +160,14 @@ struct irq_alloc_info {
int dmar_id; int dmar_id;
void *dmar_data; void *dmar_data;
}; };
#endif
#ifdef CONFIG_HT_IRQ
struct {
int ht_pos;
int ht_idx;
struct pci_dev *ht_dev;
void *ht_update;
};
#endif #endif
}; };
}; };
...@@ -227,6 +235,11 @@ extern void arch_init_msi_domain(struct irq_domain *domain); ...@@ -227,6 +235,11 @@ extern void arch_init_msi_domain(struct irq_domain *domain);
#else #else
static inline void arch_init_msi_domain(struct irq_domain *domain) { } static inline void arch_init_msi_domain(struct irq_domain *domain) { }
#endif #endif
#ifdef CONFIG_HT_IRQ
extern void arch_init_htirq_domain(struct irq_domain *domain);
#else
static inline void arch_init_htirq_domain(struct irq_domain *domain) { }
#endif
/* Statistics */ /* Statistics */
extern atomic_t irq_err_count; extern atomic_t irq_err_count;
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
* *
* Copyright (C) 1997, 1998, 1999, 2000, 2009 Ingo Molnar, Hajnalka Szabo * Copyright (C) 1997, 1998, 1999, 2000, 2009 Ingo Molnar, Hajnalka Szabo
* Moved from arch/x86/kernel/apic/io_apic.c. * Moved from arch/x86/kernel/apic/io_apic.c.
* Jiang Liu <jiang.liu@linux.intel.com>
* Add support of hierarchical irqdomain
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License version 2 as
...@@ -19,70 +21,104 @@ ...@@ -19,70 +21,104 @@
#include <asm/apic.h> #include <asm/apic.h>
#include <asm/hypertransport.h> #include <asm/hypertransport.h>
static struct irq_domain *htirq_domain;
/* /*
* Hypertransport interrupt support * Hypertransport interrupt support
*/ */
static void target_ht_irq(unsigned int irq, unsigned int dest, u8 vector)
{
struct ht_irq_msg msg;
fetch_ht_irq_msg(irq, &msg);
msg.address_lo &= ~(HT_IRQ_LOW_VECTOR_MASK | HT_IRQ_LOW_DEST_ID_MASK);
msg.address_hi &= ~(HT_IRQ_HIGH_DEST_ID_MASK);
msg.address_lo |= HT_IRQ_LOW_VECTOR(vector) | HT_IRQ_LOW_DEST_ID(dest);
msg.address_hi |= HT_IRQ_HIGH_DEST_ID(dest);
write_ht_irq_msg(irq, &msg);
}
static int static int
ht_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force) ht_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force)
{ {
struct irq_cfg *cfg = irqd_cfg(data); struct irq_data *parent = data->parent_data;
unsigned int dest;
int ret; int ret;
ret = apic_set_affinity(data, mask, &dest); ret = parent->chip->irq_set_affinity(parent, mask, force);
if (ret) if (ret >= 0) {
return ret; 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);
}
target_ht_irq(data->irq, dest, cfg->vector); return ret;
return IRQ_SET_MASK_OK_NOCOPY;
} }
static struct irq_chip ht_irq_chip = { static struct irq_chip ht_irq_chip = {
.name = "PCI-HT", .name = "PCI-HT",
.irq_mask = mask_ht_irq, .irq_mask = mask_ht_irq,
.irq_unmask = unmask_ht_irq, .irq_unmask = unmask_ht_irq,
.irq_ack = apic_ack_edge, .irq_ack = irq_chip_ack_parent,
.irq_set_affinity = ht_set_affinity, .irq_set_affinity = ht_set_affinity,
.irq_retrigger = apic_retrigger_irq, .irq_retrigger = irq_chip_retrigger_hierarchy,
.flags = IRQCHIP_SKIP_SET_WAKE, .flags = IRQCHIP_SKIP_SET_WAKE,
}; };
int arch_alloc_ht_irq(struct pci_dev *dev) static int htirq_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{ {
return irq_domain_alloc_irqs(NULL, 1, dev_to_node(&dev->dev), NULL); 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;
} }
void arch_free_ht_irq(int irq) static void htirq_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs)
{ {
irq_domain_free_irqs(irq, 1); 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);
} }
int arch_setup_ht_irq(unsigned int irq, struct pci_dev *dev) static void htirq_domain_activate(struct irq_domain *domain,
struct irq_data *irq_data)
{ {
struct irq_cfg *cfg;
struct ht_irq_msg msg; struct ht_irq_msg msg;
struct irq_cfg *cfg = irqd_cfg(irq_data);
if (disable_apic)
return -ENXIO;
cfg = irq_cfg(irq);
msg.address_hi = HT_IRQ_HIGH_DEST_ID(cfg->dest_apicid); msg.address_hi = HT_IRQ_HIGH_DEST_ID(cfg->dest_apicid);
msg.address_lo = msg.address_lo =
HT_IRQ_LOW_BASE | HT_IRQ_LOW_BASE |
HT_IRQ_LOW_DEST_ID(cfg->dest_apicid) | HT_IRQ_LOW_DEST_ID(cfg->dest_apicid) |
...@@ -95,13 +131,56 @@ int arch_setup_ht_irq(unsigned int irq, struct pci_dev *dev) ...@@ -95,13 +131,56 @@ int arch_setup_ht_irq(unsigned int irq, struct pci_dev *dev)
HT_IRQ_LOW_MT_FIXED : HT_IRQ_LOW_MT_FIXED :
HT_IRQ_LOW_MT_ARBITRATED) | HT_IRQ_LOW_MT_ARBITRATED) |
HT_IRQ_LOW_IRQ_MASKED; HT_IRQ_LOW_IRQ_MASKED;
write_ht_irq_msg(irq_data->irq, &msg);
}
write_ht_irq_msg(irq, &msg); static void htirq_domain_deactivate(struct irq_domain *domain,
struct irq_data *irq_data)
{
struct ht_irq_msg msg;
irq_set_chip_and_handler_name(irq, &ht_irq_chip, memset(&msg, 0, sizeof(msg));
handle_edge_irq, "edge"); write_ht_irq_msg(irq_data->irq, &msg);
}
dev_dbg(&dev->dev, "irq %d for HT\n", irq); static struct irq_domain_ops htirq_domain_ops = {
.alloc = htirq_domain_alloc,
.free = htirq_domain_free,
.activate = htirq_domain_activate,
.deactivate = htirq_domain_deactivate,
};
return 0; void arch_init_htirq_domain(struct irq_domain *parent)
{
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);
} }
...@@ -365,6 +365,7 @@ int __init arch_early_irq_init(void) ...@@ -365,6 +365,7 @@ int __init arch_early_irq_init(void)
irq_set_default_host(x86_vector_domain); irq_set_default_host(x86_vector_domain);
arch_init_msi_domain(x86_vector_domain); arch_init_msi_domain(x86_vector_domain);
arch_init_htirq_domain(x86_vector_domain);
return arch_early_ioapic_init(); return arch_early_ioapic_init();
} }
......
...@@ -23,20 +23,11 @@ ...@@ -23,20 +23,11 @@
*/ */
static DEFINE_SPINLOCK(ht_irq_lock); static DEFINE_SPINLOCK(ht_irq_lock);
struct ht_irq_cfg {
struct pci_dev *dev;
/* Update callback used to cope with buggy hardware */
ht_irq_update_t *update;
unsigned pos;
unsigned idx;
struct ht_irq_msg msg;
};
void write_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg) void write_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg)
{ {
struct ht_irq_cfg *cfg = irq_get_handler_data(irq); struct ht_irq_cfg *cfg = irq_get_handler_data(irq);
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&ht_irq_lock, flags); spin_lock_irqsave(&ht_irq_lock, flags);
if (cfg->msg.address_lo != msg->address_lo) { if (cfg->msg.address_lo != msg->address_lo) {
pci_write_config_byte(cfg->dev, cfg->pos + 2, cfg->idx); pci_write_config_byte(cfg->dev, cfg->pos + 2, cfg->idx);
...@@ -55,6 +46,7 @@ void write_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg) ...@@ -55,6 +46,7 @@ void write_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg)
void fetch_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg) void fetch_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg)
{ {
struct ht_irq_cfg *cfg = irq_get_handler_data(irq); struct ht_irq_cfg *cfg = irq_get_handler_data(irq);
*msg = cfg->msg; *msg = cfg->msg;
} }
...@@ -86,7 +78,6 @@ void unmask_ht_irq(struct irq_data *data) ...@@ -86,7 +78,6 @@ void unmask_ht_irq(struct irq_data *data)
*/ */
int __ht_create_irq(struct pci_dev *dev, int idx, ht_irq_update_t *update) int __ht_create_irq(struct pci_dev *dev, int idx, ht_irq_update_t *update)
{ {
struct ht_irq_cfg *cfg;
int max_irq, pos, irq; int max_irq, pos, irq;
unsigned long flags; unsigned long flags;
u32 data; u32 data;
...@@ -105,29 +96,9 @@ int __ht_create_irq(struct pci_dev *dev, int idx, ht_irq_update_t *update) ...@@ -105,29 +96,9 @@ int __ht_create_irq(struct pci_dev *dev, int idx, ht_irq_update_t *update)
if (idx > max_irq) if (idx > max_irq)
return -EINVAL; return -EINVAL;
cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); irq = arch_setup_ht_irq(idx, pos, dev, update);
if (!cfg) if (irq > 0)
return -ENOMEM; dev_dbg(&dev->dev, "irq %d for HT\n", irq);
cfg->dev = dev;
cfg->update = update;
cfg->pos = pos;
cfg->idx = 0x10 + (idx * 2);
/* Initialize msg to a value that will never match the first write. */
cfg->msg.address_lo = 0xffffffff;
cfg->msg.address_hi = 0xffffffff;
irq = arch_alloc_ht_irq(dev);
if (irq <= 0) {
kfree(cfg);
return -EBUSY;
}
irq_set_handler_data(irq, cfg);
if (arch_setup_ht_irq(irq, dev) < 0) {
ht_destroy_irq(irq);
return -EBUSY;
}
return irq; return irq;
} }
...@@ -158,12 +129,6 @@ EXPORT_SYMBOL(ht_create_irq); ...@@ -158,12 +129,6 @@ EXPORT_SYMBOL(ht_create_irq);
*/ */
void ht_destroy_irq(unsigned int irq) void ht_destroy_irq(unsigned int irq)
{ {
struct ht_irq_cfg *cfg; arch_teardown_ht_irq(irq);
cfg = irq_get_handler_data(irq);
irq_set_chip(irq, NULL);
irq_set_handler_data(irq, NULL);
arch_free_ht_irq(irq);
kfree(cfg);
} }
EXPORT_SYMBOL(ht_destroy_irq); EXPORT_SYMBOL(ht_destroy_irq);
#ifndef LINUX_HTIRQ_H #ifndef LINUX_HTIRQ_H
#define LINUX_HTIRQ_H #define LINUX_HTIRQ_H
struct pci_dev;
struct irq_data;
struct ht_irq_msg { struct ht_irq_msg {
u32 address_lo; /* low 32 bits of the ht irq message */ u32 address_lo; /* low 32 bits of the ht irq message */
u32 address_hi; /* high 32 bits of the it irq message */ u32 address_hi; /* high 32 bits of the it irq message */
}; };
typedef void (ht_irq_update_t)(struct pci_dev *dev, int irq,
struct ht_irq_msg *msg);
struct ht_irq_cfg {
struct pci_dev *dev;
/* Update callback used to cope with buggy hardware */
ht_irq_update_t *update;
unsigned pos;
unsigned idx;
struct ht_irq_msg msg;
};
/* Helper functions.. */ /* Helper functions.. */
void fetch_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg); void fetch_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg);
void write_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg); void write_ht_irq_msg(unsigned int irq, struct ht_irq_msg *msg);
struct irq_data;
void mask_ht_irq(struct irq_data *data); void mask_ht_irq(struct irq_data *data);
void unmask_ht_irq(struct irq_data *data); void unmask_ht_irq(struct irq_data *data);
/* The arch hook for getting things started */ /* The arch hook for getting things started */
int arch_setup_ht_irq(unsigned int irq, struct pci_dev *dev); int arch_setup_ht_irq(int idx, int pos, struct pci_dev *dev,
int arch_alloc_ht_irq(struct pci_dev *dev); ht_irq_update_t *update);
void arch_free_ht_irq(int irq); void arch_teardown_ht_irq(unsigned int irq);
/* For drivers of buggy hardware */ /* For drivers of buggy hardware */
typedef void (ht_irq_update_t)(struct pci_dev *dev, int irq,
struct ht_irq_msg *msg);
int __ht_create_irq(struct pci_dev *dev, int idx, ht_irq_update_t *update); int __ht_create_irq(struct pci_dev *dev, int idx, ht_irq_update_t *update);
#endif /* LINUX_HTIRQ_H */ #endif /* LINUX_HTIRQ_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment