Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 58 additions & 69 deletions drivers/pci/controller/dwc/pcie-designware-host.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,27 @@ static struct pci_ops dw_pcie_ops;
static struct pci_ops dw_pcie_ecam_ops;
static struct pci_ops dw_child_pcie_ops;

#ifdef CONFIG_SMP
static void dw_irq_noop(struct irq_data *d) { }
#endif

static bool dw_pcie_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
struct irq_domain *real_parent, struct msi_domain_info *info)
{
if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
return false;

#ifdef CONFIG_SMP
info->chip->irq_ack = dw_irq_noop;
info->chip->irq_pre_redirect = irq_chip_pre_redirect_parent;
#else
info->chip->irq_ack = irq_chip_ack_parent;
#endif
return true;
}

#define DW_PCIE_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
MSI_FLAG_USE_DEF_CHIP_OPS | \
MSI_FLAG_NO_AFFINITY | \
MSI_FLAG_PCI_MSI_MASK_PARENT)
#define DW_PCIE_MSI_FLAGS_SUPPORTED (MSI_FLAG_MULTI_PCI_MSI | \
MSI_FLAG_PCI_MSIX | \
Expand All @@ -42,41 +60,30 @@ static const struct msi_parent_ops dw_pcie_msi_parent_ops = {
.required_flags = DW_PCIE_MSI_FLAGS_REQUIRED,
.supported_flags = DW_PCIE_MSI_FLAGS_SUPPORTED,
.bus_select_token = DOMAIN_BUS_PCI_MSI,
.chip_flags = MSI_CHIP_FLAG_SET_ACK,
.prefix = "DW-",
.init_dev_msi_info = msi_lib_init_dev_msi_info,
.init_dev_msi_info = dw_pcie_init_dev_msi_info,
};

/* MSI int handler */
irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
void dw_handle_msi_irq(struct dw_pcie_rp *pp)
{
int i, pos;
unsigned long val;
u32 status, num_ctrls;
irqreturn_t ret = IRQ_NONE;
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
unsigned int i, num_ctrls;

num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;

for (i = 0; i < num_ctrls; i++) {
status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
(i * MSI_REG_CTRL_BLOCK_SIZE));
unsigned int reg_off = i * MSI_REG_CTRL_BLOCK_SIZE;
unsigned int irq_off = i * MAX_MSI_IRQS_PER_CTRL;
unsigned long status, pos;

status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS + reg_off);
if (!status)
continue;

ret = IRQ_HANDLED;
val = status;
pos = 0;
while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
pos)) != MAX_MSI_IRQS_PER_CTRL) {
generic_handle_domain_irq(pp->irq_domain,
(i * MAX_MSI_IRQS_PER_CTRL) +
pos);
pos++;
}
for_each_set_bit(pos, &status, MAX_MSI_IRQS_PER_CTRL)
generic_handle_demux_domain_irq(pp->irq_domain, irq_off + pos);
}

return ret;
}

/* Chained MSI interrupt service routine */
Expand All @@ -97,13 +104,10 @@ static void dw_pci_setup_msi_msg(struct irq_data *d, struct msi_msg *msg)
{
struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
u64 msi_target;

msi_target = (u64)pp->msi_data;
u64 msi_target = (u64)pp->msi_data;

msg->address_lo = lower_32_bits(msi_target);
msg->address_hi = upper_32_bits(msi_target);

msg->data = d->hwirq;

dev_dbg(pci->dev, "msi#%d address_hi %#x address_lo %#x\n",
Expand All @@ -115,37 +119,29 @@ static void dw_pci_bottom_mask(struct irq_data *d)
struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
unsigned int res, bit, ctrl;
unsigned long flags;

raw_spin_lock_irqsave(&pp->lock, flags);

guard(raw_spinlock)(&pp->lock);
ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;

pp->irq_mask[ctrl] |= BIT(bit);
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);

raw_spin_unlock_irqrestore(&pp->lock, flags);
}

static void dw_pci_bottom_unmask(struct irq_data *d)
{
struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
unsigned int res, bit, ctrl;
unsigned long flags;

raw_spin_lock_irqsave(&pp->lock, flags);

guard(raw_spinlock)(&pp->lock);
ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;

pp->irq_mask[ctrl] &= ~BIT(bit);
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);

raw_spin_unlock_irqrestore(&pp->lock, flags);
}

static void dw_pci_bottom_ack(struct irq_data *d)
Expand All @@ -162,54 +158,48 @@ static void dw_pci_bottom_ack(struct irq_data *d)
}

static struct irq_chip dw_pci_msi_bottom_irq_chip = {
.name = "DWPCI-MSI",
.irq_ack = dw_pci_bottom_ack,
.irq_compose_msi_msg = dw_pci_setup_msi_msg,
.irq_mask = dw_pci_bottom_mask,
.irq_unmask = dw_pci_bottom_unmask,
.name = "DWPCI-MSI",
.irq_compose_msi_msg = dw_pci_setup_msi_msg,
.irq_mask = dw_pci_bottom_mask,
.irq_unmask = dw_pci_bottom_unmask,
#ifdef CONFIG_SMP
.irq_ack = dw_irq_noop,
.irq_pre_redirect = dw_pci_bottom_ack,
.irq_set_affinity = irq_chip_redirect_set_affinity,
#else
.irq_ack = dw_pci_bottom_ack,
#endif
};

static int dw_pcie_irq_domain_alloc(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs,
void *args)
static int dw_pcie_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *args)
{
struct dw_pcie_rp *pp = domain->host_data;
unsigned long flags;
u32 i;
int bit;

raw_spin_lock_irqsave(&pp->lock, flags);

bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
order_base_2(nr_irqs));

raw_spin_unlock_irqrestore(&pp->lock, flags);
scoped_guard (raw_spinlock_irq, &pp->lock) {
bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
order_base_2(nr_irqs));
}

if (bit < 0)
return -ENOSPC;

for (i = 0; i < nr_irqs; i++)
irq_domain_set_info(domain, virq + i, bit + i,
pp->msi_irq_chip,
pp, handle_edge_irq,
NULL, NULL);

for (unsigned int i = 0; i < nr_irqs; i++) {
irq_domain_set_info(domain, virq + i, bit + i, pp->msi_irq_chip,
pp, handle_edge_irq, NULL, NULL);
}
return 0;
}

static void dw_pcie_irq_domain_free(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs)
static void dw_pcie_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 dw_pcie_rp *pp = domain->host_data;
unsigned long flags;

raw_spin_lock_irqsave(&pp->lock, flags);

bitmap_release_region(pp->msi_irq_in_use, d->hwirq,
order_base_2(nr_irqs));

raw_spin_unlock_irqrestore(&pp->lock, flags);
guard(raw_spinlock_irq)(&pp->lock);
bitmap_release_region(pp->msi_irq_in_use, d->hwirq, order_base_2(nr_irqs));
}

static const struct irq_domain_ops dw_pcie_msi_domain_ops = {
Expand Down Expand Up @@ -242,8 +232,7 @@ void dw_pcie_free_msi(struct dw_pcie_rp *pp)

for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
if (pp->msi_irq[ctrl] > 0)
irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
NULL, NULL);
irq_set_chained_handler_and_data(pp->msi_irq[ctrl], NULL, NULL);
}

irq_domain_remove(pp->irq_domain);
Expand Down
7 changes: 2 additions & 5 deletions drivers/pci/controller/dwc/pcie-designware.h
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ const char *dw_pcie_ltssm_status_string(enum dw_pcie_ltssm ltssm);
#ifdef CONFIG_PCIE_DW_HOST
int dw_pcie_suspend_noirq(struct dw_pcie *pci);
int dw_pcie_resume_noirq(struct dw_pcie *pci);
irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp);
void dw_handle_msi_irq(struct dw_pcie_rp *pp);
void dw_pcie_msi_init(struct dw_pcie_rp *pp);
int dw_pcie_msi_host_init(struct dw_pcie_rp *pp);
void dw_pcie_free_msi(struct dw_pcie_rp *pp);
Expand All @@ -837,10 +837,7 @@ static inline int dw_pcie_resume_noirq(struct dw_pcie *pci)
return 0;
}

static inline irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
{
return IRQ_NONE;
}
static inline void dw_handle_msi_irq(struct dw_pcie_rp *pp) { }

static inline void dw_pcie_msi_init(struct dw_pcie_rp *pp)
{ }
Expand Down
10 changes: 10 additions & 0 deletions include/linux/irq.h
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ static inline irq_hw_number_t irqd_to_hwirq(struct irq_data *d)
* checks against the supplied affinity mask are not
* required. This is used for CPU hotplug where the
* target CPU is not yet set in the cpu_online_mask.
* @irq_pre_redirect: Optional function to be invoked before redirecting
* an interrupt via irq_work. Called only on CONFIG_SMP.
* @irq_retrigger: resend an IRQ to the CPU
* @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @irq_set_wake: enable/disable power-management wake-on of an IRQ
Expand Down Expand Up @@ -503,6 +505,7 @@ struct irq_chip {
void (*irq_eoi)(struct irq_data *data);

int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
void (*irq_pre_redirect)(struct irq_data *data);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
Expand Down Expand Up @@ -688,6 +691,13 @@ extern int irq_chip_set_vcpu_affinity_parent(struct irq_data *data,
extern int irq_chip_set_type_parent(struct irq_data *data, unsigned int type);
extern int irq_chip_request_resources_parent(struct irq_data *data);
extern void irq_chip_release_resources_parent(struct irq_data *data);
#ifdef CONFIG_SMP
void irq_chip_pre_redirect_parent(struct irq_data *data);
#endif
#endif

#ifdef CONFIG_SMP
int irq_chip_redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force);
#endif

/* Disable or mask interrupts during a kernel kexec */
Expand Down
17 changes: 16 additions & 1 deletion include/linux/irqdesc.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
#ifndef _LINUX_IRQDESC_H
#define _LINUX_IRQDESC_H

#include <linux/rcupdate.h>
#include <linux/irq_work.h>
#include <linux/kobject.h>
#include <linux/mutex.h>
#include <linux/rcupdate.h>

/*
* Core internal functions to deal with irq descriptors
Expand All @@ -29,6 +30,17 @@ struct irqstat {
#endif
};

/**
* struct irq_redirect - interrupt redirection metadata
* @work: Harg irq_work item for handler execution on a different CPU
* @target_cpu: CPU to run irq handler on in case the current CPU is not part
* of the irq affinity mask
*/
struct irq_redirect {
struct irq_work work;
unsigned int target_cpu;
};

/**
* struct irq_desc - interrupt descriptor
* @irq_common_data: per irq and chip data passed down to chip functions
Expand All @@ -46,6 +58,7 @@ struct irqstat {
* @threads_handled: stats field for deferred spurious detection of threaded handlers
* @threads_handled_last: comparator field for deferred spurious detection of threaded handlers
* @lock: locking for SMP
* @redirect: Facility for redirecting interrupts via irq_work
* @affinity_hint: hint to user space for preferred irq affinity
* @affinity_notify: context for notification of affinity changes
* @pending_mask: pending rebalanced interrupts
Expand Down Expand Up @@ -84,6 +97,7 @@ struct irq_desc {
struct cpumask *percpu_enabled;
const struct cpumask *percpu_affinity;
#ifdef CONFIG_SMP
struct irq_redirect redirect;
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
Expand Down Expand Up @@ -186,6 +200,7 @@ int generic_handle_irq_safe(unsigned int irq);
int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq);
int generic_handle_domain_irq_safe(struct irq_domain *domain, unsigned int hwirq);
int generic_handle_domain_nmi(struct irq_domain *domain, unsigned int hwirq);
bool generic_handle_demux_domain_irq(struct irq_domain *domain, irq_hw_number_t hwirq);
#endif

/* Test to see if a driver has successfully requested an irq */
Expand Down
24 changes: 23 additions & 1 deletion kernel/irq/chip.c
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,7 @@ void irq_cpu_offline(void)
}
#endif

#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY

#ifdef CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS
/**
Expand Down Expand Up @@ -1215,6 +1215,15 @@ EXPORT_SYMBOL_GPL(handle_fasteoi_mask_irq);

#endif /* CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS */

#ifdef CONFIG_SMP
void irq_chip_pre_redirect_parent(struct irq_data *data)
{
data = data->parent_data;
data->chip->irq_pre_redirect(data);
}
EXPORT_SYMBOL_GPL(irq_chip_pre_redirect_parent);
#endif

/**
* irq_chip_set_parent_state - set the state of a parent interrupt.
*
Expand Down Expand Up @@ -1497,6 +1506,19 @@ void irq_chip_release_resources_parent(struct irq_data *data)
data->chip->irq_release_resources(data);
}
EXPORT_SYMBOL_GPL(irq_chip_release_resources_parent);
#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */

#ifdef CONFIG_SMP
int irq_chip_redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force)
{
struct irq_redirect *redir = &irq_data_to_desc(data)->redirect;

WRITE_ONCE(redir->target_cpu, cpumask_first(dest));
irq_data_update_effective_affinity(data, dest);

return IRQ_SET_MASK_OK;
}
EXPORT_SYMBOL_GPL(irq_chip_redirect_set_affinity);
#endif

/**
Expand Down
Loading
Loading