Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cb4cb2cb authored by Prarit Bhargava's avatar Prarit Bhargava Committed by Tony Luck
Browse files

[IA64] hotplug/ia64: SN Hotplug Driver: SN IRQ Fixes



This patch  fixes the SN IRQ code such that cpu affinity and
Hotplug can modify IRQ values.  The sn_irq_info structures are now locked
using a RCU lock mechanism to avoid lock contention in the lost interrupt
WAR code.

Signed-off-by: default avatarPrarit Bhargava <prarit@sgi.com>
Signed-off-by: default avatarTony Luck <tony.luck@intel.com>
parent bd53d127
Loading
Loading
Loading
Loading
+22 −17
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@
#include <asm/sn/simulator.h>
#include <asm/sn/tioca_provider.h>

char master_baseio_wid;
nasid_t master_nasid = INVALID_NASID;	/* Partition Master */

struct slab_info {
@@ -231,11 +230,13 @@ static void sn_pci_fixup_slot(struct pci_dev *dev)
{
	int idx;
	int segment = 0;
	uint64_t size;
	struct sn_irq_info *sn_irq_info;
	struct pci_dev *host_pci_dev;
	int status = 0;
	struct pcibus_bussoft *bs;
 	struct pci_bus *host_pci_bus;
 	struct pci_dev *host_pci_dev;
 	struct sn_irq_info *sn_irq_info;
 	unsigned long size;
 	unsigned int bus_no, devfn;

	dev->sysdata = kmalloc(sizeof(struct pcidev_info), GFP_KERNEL);
	if (SN_PCIDEV_INFO(dev) <= 0)
@@ -253,7 +254,7 @@ static void sn_pci_fixup_slot(struct pci_dev *dev)
				     (u64) __pa(SN_PCIDEV_INFO(dev)),
				     (u64) __pa(sn_irq_info));
	if (status)
		BUG();		/* Cannot get platform pci device information information */
		BUG(); /* Cannot get platform pci device information */

	/* Copy over PIO Mapped Addresses */
	for (idx = 0; idx <= PCI_ROM_RESOURCE; idx++) {
@@ -275,15 +276,20 @@ static void sn_pci_fixup_slot(struct pci_dev *dev)
			dev->resource[idx].parent = &iomem_resource;
	}

	/* set up host bus linkages */
	bs = SN_PCIBUS_BUSSOFT(dev->bus);
	host_pci_dev =
	    pci_find_slot(SN_PCIDEV_INFO(dev)->pdi_slot_host_handle >> 32,
			  SN_PCIDEV_INFO(dev)->
			  pdi_slot_host_handle & 0xffffffff);
 	/* Using the PROMs values for the PCI host bus, get the Linux
 	 * PCI host_pci_dev struct and set up host bus linkages
 	 */

 	bus_no = SN_PCIDEV_INFO(dev)->pdi_slot_host_handle >> 32;
 	devfn = SN_PCIDEV_INFO(dev)->pdi_slot_host_handle & 0xffffffff;
 	host_pci_bus = pci_find_bus(pci_domain_nr(dev->bus), bus_no);
 	host_pci_dev = pci_get_slot(host_pci_bus, devfn);

	SN_PCIDEV_INFO(dev)->host_pci_dev = host_pci_dev;
	SN_PCIDEV_INFO(dev)->pdi_host_pcidev_info =
	    					SN_PCIDEV_INFO(host_pci_dev);
	SN_PCIDEV_INFO(dev)->pdi_linux_pcidev = dev;
	bs = SN_PCIBUS_BUSSOFT(dev->bus);
	SN_PCIDEV_INFO(dev)->pdi_pcibus_info = bs;

	if (bs && bs->bs_asic_type < PCIIO_ASIC_MAX_TYPES) {
@@ -297,6 +303,9 @@ static void sn_pci_fixup_slot(struct pci_dev *dev)
		SN_PCIDEV_INFO(dev)->pdi_sn_irq_info = sn_irq_info;
		dev->irq = SN_PCIDEV_INFO(dev)->pdi_sn_irq_info->irq_irq;
		sn_irq_fixup(dev, sn_irq_info);
	} else {
		SN_PCIDEV_INFO(dev)->pdi_sn_irq_info = NULL;
		kfree(sn_irq_info);
	}
}

@@ -403,11 +412,7 @@ static int __init sn_pci_init(void)
	 */
	ia64_max_iommu_merge_mask = ~PAGE_MASK;
	sn_fixup_ionodes();
	sn_irq = kmalloc(sizeof(struct sn_irq_info *) * NR_IRQS, GFP_KERNEL);
	if (sn_irq <= 0)
		BUG();		/* Canno afford to run out of memory. */
	memset(sn_irq, 0, sizeof(struct sn_irq_info *) * NR_IRQS);

	sn_irq_lh_init();
	sn_init_cpei_timer();

#ifdef CONFIG_PROC_FS
+133 −117
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
 */

#include <linux/irq.h>
#include <linux/spinlock.h>
#include <asm/sn/intr.h>
#include <asm/sn/addrs.h>
#include <asm/sn/arch.h>
@@ -25,7 +26,8 @@ static void unregister_intr_pda(struct sn_irq_info *sn_irq_info);

extern int sn_force_interrupt_flag;
extern int sn_ioif_inited;
struct sn_irq_info **sn_irq;
static struct list_head **sn_irq_lh;
static spinlock_t sn_irq_info_lock = SPIN_LOCK_UNLOCKED; /* non-IRQ lock */

static inline uint64_t sn_intr_alloc(nasid_t local_nasid, int local_widget,
				     u64 sn_irq_info,
@@ -115,82 +117,84 @@ static void sn_end_irq(unsigned int irq)
		force_interrupt(irq);
}

static void sn_irq_info_free(struct rcu_head *head);

static void sn_set_affinity_irq(unsigned int irq, cpumask_t mask)
{
	struct sn_irq_info *sn_irq_info = sn_irq[irq];
	struct sn_irq_info *tmp_sn_irq_info;
	struct sn_irq_info *sn_irq_info, *sn_irq_info_safe;
	int cpuid, cpuphys;
	nasid_t t_nasid;	/* nasid to target */
	int t_slice;		/* slice to target */

	/* allocate a temp sn_irq_info struct to get new target info */
	tmp_sn_irq_info = kmalloc(sizeof(*tmp_sn_irq_info), GFP_KERNEL);
	if (!tmp_sn_irq_info)
		return;

	cpuid = first_cpu(mask);
	cpuphys = cpu_physical_id(cpuid);
	t_nasid = cpuid_to_nasid(cpuid);
	t_slice = cpuid_to_slice(cpuid);

	while (sn_irq_info) {
		int status;
		int local_widget;
		uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge;
		nasid_t local_nasid = NASID_GET(bridge);
	list_for_each_entry_safe(sn_irq_info, sn_irq_info_safe,
				 sn_irq_lh[irq], list) {
		uint64_t bridge;
		int local_widget, status;
		nasid_t local_nasid;
		struct sn_irq_info *new_irq_info;

		new_irq_info = kmalloc(sizeof(struct sn_irq_info), GFP_ATOMIC);
		if (new_irq_info == NULL)
			break;
		memcpy(new_irq_info, sn_irq_info, sizeof(struct sn_irq_info));

		if (!bridge)
		bridge = (uint64_t) new_irq_info->irq_bridge;
		if (!bridge) {
			kfree(new_irq_info);
			break; /* irq is not a device interrupt */
		}

		local_nasid = NASID_GET(bridge);

		if (local_nasid & 1)
			local_widget = TIO_SWIN_WIDGETNUM(bridge);
		else
			local_widget = SWIN_WIDGETNUM(bridge);

		/* Free the old PROM sn_irq_info structure */
		sn_intr_free(local_nasid, local_widget, sn_irq_info);
		/* Free the old PROM new_irq_info structure */
		sn_intr_free(local_nasid, local_widget, new_irq_info);
		/* Update kernels new_irq_info with new target info */
		unregister_intr_pda(new_irq_info);

		/* allocate a new PROM sn_irq_info struct */
		/* allocate a new PROM new_irq_info struct */
		status = sn_intr_alloc(local_nasid, local_widget,
				       __pa(tmp_sn_irq_info), irq, t_nasid,
				       t_slice);
				       __pa(new_irq_info), irq,
				       cpuid_to_nasid(cpuid),
				       cpuid_to_slice(cpuid));

		if (status == 0) {
			/* Update kernels sn_irq_info with new target info */
			unregister_intr_pda(sn_irq_info);
			sn_irq_info->irq_cpuid = cpuid;
			sn_irq_info->irq_nasid = t_nasid;
			sn_irq_info->irq_slice = t_slice;
			sn_irq_info->irq_xtalkaddr =
			    tmp_sn_irq_info->irq_xtalkaddr;
			sn_irq_info->irq_cookie = tmp_sn_irq_info->irq_cookie;
			register_intr_pda(sn_irq_info);

			if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type)) {
				pcibr_change_devices_irq(sn_irq_info);
		/* SAL call failed */
		if (status) {
			kfree(new_irq_info);
			break;
		}

			sn_irq_info = sn_irq_info->irq_next;
		new_irq_info->irq_cpuid = cpuid;
		register_intr_pda(new_irq_info);

		if (IS_PCI_BRIDGE_ASIC(new_irq_info->irq_bridge_type))
			pcibr_change_devices_irq(new_irq_info);

		spin_lock(&sn_irq_info_lock);
		list_replace_rcu(&sn_irq_info->list, &new_irq_info->list);
		spin_unlock(&sn_irq_info_lock);
		call_rcu(&sn_irq_info->rcu, sn_irq_info_free);

#ifdef CONFIG_SMP
		set_irq_affinity_info((irq & 0xff), cpuphys, 0);
#endif
		} else {
			break;	/* snp_affinity failed the intr_alloc */
		}
	}
	kfree(tmp_sn_irq_info);
}

struct hw_interrupt_type irq_type_sn = {
	"SN hub",
	sn_startup_irq,
	sn_shutdown_irq,
	sn_enable_irq,
	sn_disable_irq,
	sn_ack_irq,
	sn_end_irq,
	sn_set_affinity_irq
	.typename	= "SN hub",
	.startup	= sn_startup_irq,
	.shutdown	= sn_shutdown_irq,
	.enable		= sn_enable_irq,
	.disable	= sn_disable_irq,
	.ack		= sn_ack_irq,
	.end		= sn_end_irq,
	.set_affinity	= sn_set_affinity_irq
};

unsigned int sn_local_vector_to_irq(u8 vector)
@@ -231,19 +235,18 @@ static void unregister_intr_pda(struct sn_irq_info *sn_irq_info)
	struct sn_irq_info *tmp_irq_info;
	int i, foundmatch;

	rcu_read_lock();
	if (pdacpu(cpu)->sn_last_irq == irq) {
		foundmatch = 0;
		for (i = pdacpu(cpu)->sn_last_irq - 1; i; i--) {
			tmp_irq_info = sn_irq[i];
			while (tmp_irq_info) {
		for (i = pdacpu(cpu)->sn_last_irq - 1;
		     i && !foundmatch; i--) {
			list_for_each_entry_rcu(tmp_irq_info,
						sn_irq_lh[i],
						list) {
				if (tmp_irq_info->irq_cpuid == cpu) {
					foundmatch++;
					foundmatch = 1;
					break;
				}
				tmp_irq_info = tmp_irq_info->irq_next;
			}
			if (foundmatch) {
				break;
			}
		}
		pdacpu(cpu)->sn_last_irq = i;
@@ -251,60 +254,27 @@ static void unregister_intr_pda(struct sn_irq_info *sn_irq_info)

	if (pdacpu(cpu)->sn_first_irq == irq) {
		foundmatch = 0;
		for (i = pdacpu(cpu)->sn_first_irq + 1; i < NR_IRQS; i++) {
			tmp_irq_info = sn_irq[i];
			while (tmp_irq_info) {
		for (i = pdacpu(cpu)->sn_first_irq + 1;
		     i < NR_IRQS && !foundmatch; i++) {
			list_for_each_entry_rcu(tmp_irq_info,
						sn_irq_lh[i],
						list) {
				if (tmp_irq_info->irq_cpuid == cpu) {
					foundmatch++;
					foundmatch = 1;
					break;
				}
				tmp_irq_info = tmp_irq_info->irq_next;
			}
			if (foundmatch) {
				break;
			}
		}
		pdacpu(cpu)->sn_first_irq = ((i == NR_IRQS) ? 0 : i);
	}
	rcu_read_unlock();
}

struct sn_irq_info *sn_irq_alloc(nasid_t local_nasid, int local_widget, int irq,
				 nasid_t nasid, int slice)
static void sn_irq_info_free(struct rcu_head *head)
{
	struct sn_irq_info *sn_irq_info;
	int status;

	sn_irq_info = kmalloc(sizeof(*sn_irq_info), GFP_KERNEL);
	if (sn_irq_info == NULL)
		return NULL;

	memset(sn_irq_info, 0x0, sizeof(*sn_irq_info));

	status =
	    sn_intr_alloc(local_nasid, local_widget, __pa(sn_irq_info), irq,
			  nasid, slice);

	if (status) {
		kfree(sn_irq_info);
		return NULL;
	} else {
		return sn_irq_info;
	}
}

void sn_irq_free(struct sn_irq_info *sn_irq_info)
{
	uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge;
	nasid_t local_nasid = NASID_GET(bridge);
	int local_widget;

	if (local_nasid & 1)	/* tio check */
		local_widget = TIO_SWIN_WIDGETNUM(bridge);
	else
		local_widget = SWIN_WIDGETNUM(bridge);

	sn_intr_free(local_nasid, local_widget, sn_irq_info);

	sn_irq_info = container_of(head, struct sn_irq_info, rcu);
	kfree(sn_irq_info);
}

@@ -314,30 +284,54 @@ void sn_irq_fixup(struct pci_dev *pci_dev, struct sn_irq_info *sn_irq_info)
	int slice = sn_irq_info->irq_slice;
	int cpu = nasid_slice_to_cpuid(nasid, slice);

	pci_dev_get(pci_dev);

	sn_irq_info->irq_cpuid = cpu;
	sn_irq_info->irq_pciioinfo = SN_PCIDEV_INFO(pci_dev);

	/* link it into the sn_irq[irq] list */
	sn_irq_info->irq_next = sn_irq[sn_irq_info->irq_irq];
	sn_irq[sn_irq_info->irq_irq] = sn_irq_info;
	spin_lock(&sn_irq_info_lock);
	list_add_rcu(&sn_irq_info->list, sn_irq_lh[sn_irq_info->irq_irq]);
	spin_unlock(&sn_irq_info_lock);

	(void)register_intr_pda(sn_irq_info);
}

void sn_irq_unfixup(struct pci_dev *pci_dev)
{
	struct sn_irq_info *sn_irq_info;

	/* Only cleanup IRQ stuff if this device has a host bus context */
	if (!SN_PCIDEV_BUSSOFT(pci_dev))
		return;

	sn_irq_info = SN_PCIDEV_INFO(pci_dev)->pdi_sn_irq_info;
	if (!sn_irq_info || !sn_irq_info->irq_irq)
		return;

	unregister_intr_pda(sn_irq_info);
	spin_lock(&sn_irq_info_lock);
	list_del_rcu(&sn_irq_info->list);
	spin_unlock(&sn_irq_info_lock);
	call_rcu(&sn_irq_info->rcu, sn_irq_info_free);

	pci_dev_put(pci_dev);
}

static void force_interrupt(int irq)
{
	struct sn_irq_info *sn_irq_info;

	if (!sn_ioif_inited)
		return;
	sn_irq_info = sn_irq[irq];
	while (sn_irq_info) {

	rcu_read_lock();
	list_for_each_entry_rcu(sn_irq_info, sn_irq_lh[irq], list) {
		if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type) &&
		    (sn_irq_info->irq_bridge != NULL)) {
		    (sn_irq_info->irq_bridge != NULL))
			pcibr_force_interrupt(sn_irq_info);
	}
		sn_irq_info = sn_irq_info->irq_next;
	}
	rcu_read_unlock();
}

/*
@@ -402,19 +396,41 @@ static void sn_check_intr(int irq, struct sn_irq_info *sn_irq_info)

void sn_lb_int_war_check(void)
{
	struct sn_irq_info *sn_irq_info;
	int i;

	if (!sn_ioif_inited || pda->sn_first_irq == 0)
		return;

	rcu_read_lock();
	for (i = pda->sn_first_irq; i <= pda->sn_last_irq; i++) {
		struct sn_irq_info *sn_irq_info = sn_irq[i];
		while (sn_irq_info) {
			/* Only call for PCI bridges that are fully initialized. */
		list_for_each_entry_rcu(sn_irq_info, sn_irq_lh[i], list) {
			/*
			 * Only call for PCI bridges that are fully
			 * initialized.
			 */
			if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type) &&
			    (sn_irq_info->irq_bridge != NULL)) {
			    (sn_irq_info->irq_bridge != NULL))
				sn_check_intr(i, sn_irq_info);
		}
			sn_irq_info = sn_irq_info->irq_next;
	}
	rcu_read_unlock();
}

void sn_irq_lh_init(void)
{
	int i;

	sn_irq_lh = kmalloc(sizeof(struct list_head *) * NR_IRQS, GFP_KERNEL);
	if (!sn_irq_lh)
		panic("SN PCI INIT: Failed to allocate memory for PCI init\n");

	for (i = 0; i < NR_IRQS; i++) {
		sn_irq_lh[i] = kmalloc(sizeof(struct list_head), GFP_KERNEL);
		if (!sn_irq_lh[i])
			panic("SN PCI INIT: Failed IRQ memory allocation\n");

		INIT_LIST_HEAD(sn_irq_lh[i]);
	}

}
+5 −1
Original line number Diff line number Diff line
@@ -9,6 +9,8 @@
#ifndef _ASM_IA64_SN_INTR_H
#define _ASM_IA64_SN_INTR_H

#include <linux/rcupdate.h>

#define SGI_UART_VECTOR		(0xe9)
#define SGI_PCIBR_ERROR		(0x33)

@@ -33,7 +35,7 @@

// The SN PROM irq struct
struct sn_irq_info {
	struct sn_irq_info *irq_next;	/* sharing irq list	     */
	struct sn_irq_info *irq_next;	/* deprecated DO NOT USE     */
	short		irq_nasid;	/* Nasid IRQ is assigned to  */
	int		irq_slice;	/* slice IRQ is assigned to  */
	int		irq_cpuid;	/* kernel logical cpuid	     */
@@ -47,6 +49,8 @@ struct sn_irq_info {
	int		irq_cookie;	/* unique cookie 	     */
	int		irq_flags;	/* flags */
	int		irq_share_cnt;	/* num devices sharing IRQ   */
	struct list_head	list;	/* list of sn_irq_info structs */
	struct rcu_head		rcu;	/* rcu callback list */
};

extern void sn_send_IPI_phys(int, long, int, int);
+2 −2
Original line number Diff line number Diff line
@@ -10,8 +10,6 @@

#include <linux/pci.h>

extern struct sn_irq_info **sn_irq;

#define SN_PCIDEV_INFO(pci_dev) \
        ((struct pcidev_info *)(pci_dev)->sysdata)

@@ -50,9 +48,11 @@ struct pcidev_info {

	struct sn_irq_info	*pdi_sn_irq_info;
	struct sn_pcibus_provider *pdi_provider;	/* sn pci ops */
	struct pci_dev 		*host_pci_dev;		/* host bus link */
};

extern void sn_irq_fixup(struct pci_dev *pci_dev,
			 struct sn_irq_info *sn_irq_info);

extern void sn_irq_lh_init(void);
#endif				/* _ASM_IA64_SN_PCI_PCIDEV_H */