summaryrefslogtreecommitdiff
path: root/arch/ia64/sn
diff options
context:
space:
mode:
Diffstat (limited to 'arch/ia64/sn')
-rw-r--r--arch/ia64/sn/include/pci/pcibr_provider.h6
-rw-r--r--arch/ia64/sn/include/pci/pcibus_provider_defs.h43
-rw-r--r--arch/ia64/sn/include/pci/pcidev.h54
-rw-r--r--arch/ia64/sn/kernel/Makefile8
-rw-r--r--arch/ia64/sn/kernel/bte.c20
-rw-r--r--arch/ia64/sn/kernel/bte_error.c76
-rw-r--r--arch/ia64/sn/kernel/huberror.c9
-rw-r--r--arch/ia64/sn/kernel/io_init.c88
-rw-r--r--arch/ia64/sn/kernel/irq.c19
-rw-r--r--arch/ia64/sn/kernel/mca.c34
-rw-r--r--arch/ia64/sn/kernel/setup.c49
-rw-r--r--arch/ia64/sn/kernel/sn2/sn_hwperf.c112
-rw-r--r--arch/ia64/sn/kernel/tiocx.c552
-rw-r--r--arch/ia64/sn/kernel/xp_main.c289
-rw-r--r--arch/ia64/sn/kernel/xp_nofault.S31
-rw-r--r--arch/ia64/sn/kernel/xpc.h991
-rw-r--r--arch/ia64/sn/kernel/xpc_channel.c2297
-rw-r--r--arch/ia64/sn/kernel/xpc_main.c1064
-rw-r--r--arch/ia64/sn/kernel/xpc_partition.c984
-rw-r--r--arch/ia64/sn/kernel/xpnet.c715
-rw-r--r--arch/ia64/sn/pci/Makefile2
-rw-r--r--arch/ia64/sn/pci/pci_dma.c39
-rw-r--r--arch/ia64/sn/pci/pcibr/pcibr_ate.c4
-rw-r--r--arch/ia64/sn/pci/pcibr/pcibr_dma.c109
-rw-r--r--arch/ia64/sn/pci/pcibr/pcibr_provider.c24
-rw-r--r--arch/ia64/sn/pci/pcibr/pcibr_reg.c4
-rw-r--r--arch/ia64/sn/pci/tioca_provider.c668
27 files changed, 8005 insertions, 286 deletions
diff --git a/arch/ia64/sn/include/pci/pcibr_provider.h b/arch/ia64/sn/include/pci/pcibr_provider.h
index b1f05ffec70b..1cd291d8badd 100644
--- a/arch/ia64/sn/include/pci/pcibr_provider.h
+++ b/arch/ia64/sn/include/pci/pcibr_provider.h
@@ -123,9 +123,11 @@ pcibr_lock(struct pcibus_info *pcibus_info)
}
#define pcibr_unlock(pcibus_info, flag) spin_unlock_irqrestore(&pcibus_info->pbi_lock, flag)
+extern int pcibr_init_provider(void);
extern void *pcibr_bus_fixup(struct pcibus_bussoft *);
-extern uint64_t pcibr_dma_map(struct pcidev_info *, unsigned long, size_t, unsigned int);
-extern void pcibr_dma_unmap(struct pcidev_info *, dma_addr_t, int);
+extern dma_addr_t pcibr_dma_map(struct pci_dev *, unsigned long, size_t);
+extern dma_addr_t pcibr_dma_map_consistent(struct pci_dev *, unsigned long, size_t);
+extern void pcibr_dma_unmap(struct pci_dev *, dma_addr_t, int);
/*
* prototypes for the bridge asic register access routines in pcibr_reg.c
diff --git a/arch/ia64/sn/include/pci/pcibus_provider_defs.h b/arch/ia64/sn/include/pci/pcibus_provider_defs.h
deleted file mode 100644
index 07065615bbea..000000000000
--- a/arch/ia64/sn/include/pci/pcibus_provider_defs.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * This file is subject to the terms and conditions of the GNU General Public
- * License. See the file "COPYING" in the main directory of this archive
- * for more details.
- *
- * Copyright (C) 1992 - 1997, 2000-2004 Silicon Graphics, Inc. All rights reserved.
- */
-#ifndef _ASM_IA64_SN_PCI_PCIBUS_PROVIDER_H
-#define _ASM_IA64_SN_PCI_PCIBUS_PROVIDER_H
-
-/*
- * SN pci asic types. Do not ever renumber these or reuse values. The
- * values must agree with what prom thinks they are.
- */
-
-#define PCIIO_ASIC_TYPE_UNKNOWN 0
-#define PCIIO_ASIC_TYPE_PPB 1
-#define PCIIO_ASIC_TYPE_PIC 2
-#define PCIIO_ASIC_TYPE_TIOCP 3
-
-/*
- * Common pciio bus provider data. There should be one of these as the
- * first field in any pciio based provider soft structure (e.g. pcibr_soft
- * tioca_soft, etc).
- */
-
-struct pcibus_bussoft {
- uint32_t bs_asic_type; /* chipset type */
- uint32_t bs_xid; /* xwidget id */
- uint64_t bs_persist_busnum; /* Persistent Bus Number */
- uint64_t bs_legacy_io; /* legacy io pio addr */
- uint64_t bs_legacy_mem; /* legacy mem pio addr */
- uint64_t bs_base; /* widget base */
- struct xwidget_info *bs_xwidget_info;
-};
-
-/*
- * DMA mapping flags
- */
-
-#define SN_PCIDMA_CONSISTENT 0x0001
-
-#endif /* _ASM_IA64_SN_PCI_PCIBUS_PROVIDER_H */
diff --git a/arch/ia64/sn/include/pci/pcidev.h b/arch/ia64/sn/include/pci/pcidev.h
deleted file mode 100644
index 81eb95d3bf47..000000000000
--- a/arch/ia64/sn/include/pci/pcidev.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * This file is subject to the terms and conditions of the GNU General Public
- * License. See the file "COPYING" in the main directory of this archive
- * for more details.
- *
- * Copyright (C) 1992 - 1997, 2000-2004 Silicon Graphics, Inc. All rights reserved.
- */
-#ifndef _ASM_IA64_SN_PCI_PCIDEV_H
-#define _ASM_IA64_SN_PCI_PCIDEV_H
-
-#include <linux/pci.h>
-
-extern struct sn_irq_info **sn_irq;
-
-#define SN_PCIDEV_INFO(pci_dev) \
- ((struct pcidev_info *)(pci_dev)->sysdata)
-
-/*
- * Given a pci_bus, return the sn pcibus_bussoft struct. Note that
- * this only works for root busses, not for busses represented by PPB's.
- */
-
-#define SN_PCIBUS_BUSSOFT(pci_bus) \
- ((struct pcibus_bussoft *)(PCI_CONTROLLER((pci_bus))->platform_data))
-
-/*
- * Given a struct pci_dev, return the sn pcibus_bussoft struct. Note
- * that this is not equivalent to SN_PCIBUS_BUSSOFT(pci_dev->bus) due
- * due to possible PPB's in the path.
- */
-
-#define SN_PCIDEV_BUSSOFT(pci_dev) \
- (SN_PCIDEV_INFO(pci_dev)->pdi_host_pcidev_info->pdi_pcibus_info)
-
-#define PCIIO_BUS_NONE 255 /* bus 255 reserved */
-#define PCIIO_SLOT_NONE 255
-#define PCIIO_FUNC_NONE 255
-#define PCIIO_VENDOR_ID_NONE (-1)
-
-struct pcidev_info {
- uint64_t pdi_pio_mapped_addr[7]; /* 6 BARs PLUS 1 ROM */
- uint64_t pdi_slot_host_handle; /* Bus and devfn Host pci_dev */
-
- struct pcibus_bussoft *pdi_pcibus_info; /* Kernel common bus soft */
- struct pcidev_info *pdi_host_pcidev_info; /* Kernel Host pci_dev */
- struct pci_dev *pdi_linux_pcidev; /* Kernel pci_dev */
-
- struct sn_irq_info *pdi_sn_irq_info;
-};
-
-extern void sn_irq_fixup(struct pci_dev *pci_dev,
- struct sn_irq_info *sn_irq_info);
-
-#endif /* _ASM_IA64_SN_PCI_PCIDEV_H */
diff --git a/arch/ia64/sn/kernel/Makefile b/arch/ia64/sn/kernel/Makefile
index 6c7f4d9e8ea0..4351c4ff9845 100644
--- a/arch/ia64/sn/kernel/Makefile
+++ b/arch/ia64/sn/kernel/Makefile
@@ -4,9 +4,15 @@
# License. See the file "COPYING" in the main directory of this archive
# for more details.
#
-# Copyright (C) 1999,2001-2003 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (C) 1999,2001-2005 Silicon Graphics, Inc. All Rights Reserved.
#
obj-y += setup.o bte.o bte_error.o irq.o mca.o idle.o \
huberror.o io_init.o iomv.o klconflib.o sn2/
obj-$(CONFIG_IA64_GENERIC) += machvec.o
+obj-$(CONFIG_SGI_TIOCX) += tiocx.o
+obj-$(CONFIG_IA64_SGI_SN_XP) += xp.o
+xp-y := xp_main.o xp_nofault.o
+obj-$(CONFIG_IA64_SGI_SN_XP) += xpc.o
+xpc-y := xpc_main.o xpc_channel.o xpc_partition.o
+obj-$(CONFIG_IA64_SGI_SN_XP) += xpnet.o
diff --git a/arch/ia64/sn/kernel/bte.c b/arch/ia64/sn/kernel/bte.c
index ce0bc4085eae..647deae9bfcd 100644
--- a/arch/ia64/sn/kernel/bte.c
+++ b/arch/ia64/sn/kernel/bte.c
@@ -3,7 +3,7 @@
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
- * Copyright (c) 2000-2003 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2000-2005 Silicon Graphics, Inc. All Rights Reserved.
*/
#include <linux/config.h>
@@ -170,10 +170,6 @@ retry_bteop:
/* Initialize the notification to a known value. */
*bte->most_rcnt_na = BTE_WORD_BUSY;
- /* Set the status reg busy bit and transfer length */
- BTE_PRINTKV(("IBLS = 0x%lx\n", IBLS_BUSY | transfer_size));
- BTE_LNSTAT_STORE(bte, IBLS_BUSY | transfer_size);
-
/* Set the source and destination registers */
BTE_PRINTKV(("IBSA = 0x%lx)\n", (TO_PHYS(src))));
BTE_SRC_STORE(bte, TO_PHYS(src));
@@ -188,7 +184,7 @@ retry_bteop:
/* Initiate the transfer */
BTE_PRINTK(("IBCT = 0x%lx)\n", BTE_VALID_MODE(mode)));
- BTE_CTRL_STORE(bte, BTE_VALID_MODE(mode));
+ BTE_START_TRANSFER(bte, transfer_size, BTE_VALID_MODE(mode));
itc_end = ia64_get_itc() + (40000000 * local_cpu_data->cyc_per_usec);
@@ -429,10 +425,16 @@ void bte_init_node(nodepda_t * mynodepda, cnodeid_t cnode)
mynodepda->bte_recovery_timer.data = (unsigned long)mynodepda;
for (i = 0; i < BTES_PER_NODE; i++) {
+ u64 *base_addr;
+
/* Which link status register should we use? */
- unsigned long link_status = (i == 0 ? IIO_IBLS0 : IIO_IBLS1);
- mynodepda->bte_if[i].bte_base_addr = (u64 *)
- REMOTE_HUB_ADDR(cnodeid_to_nasid(cnode), link_status);
+ base_addr = (u64 *)
+ REMOTE_HUB_ADDR(cnodeid_to_nasid(cnode), BTE_BASE_ADDR(i));
+ mynodepda->bte_if[i].bte_base_addr = base_addr;
+ mynodepda->bte_if[i].bte_source_addr = BTE_SOURCE_ADDR(base_addr);
+ mynodepda->bte_if[i].bte_destination_addr = BTE_DEST_ADDR(base_addr);
+ mynodepda->bte_if[i].bte_control_addr = BTE_CTRL_ADDR(base_addr);
+ mynodepda->bte_if[i].bte_notify_addr = BTE_NOTIF_ADDR(base_addr);
/*
* Initialize the notification and spinlock
diff --git a/arch/ia64/sn/kernel/bte_error.c b/arch/ia64/sn/kernel/bte_error.c
index fd104312c6bd..fcbc748ae433 100644
--- a/arch/ia64/sn/kernel/bte_error.c
+++ b/arch/ia64/sn/kernel/bte_error.c
@@ -3,7 +3,7 @@
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
- * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2000-2005 Silicon Graphics, Inc. All Rights Reserved.
*/
#include <linux/types.h>
@@ -33,48 +33,28 @@ void bte_error_handler(unsigned long);
* Wait until all BTE related CRBs are completed
* and then reset the interfaces.
*/
-void bte_error_handler(unsigned long _nodepda)
+void shub1_bte_error_handler(unsigned long _nodepda)
{
struct nodepda_s *err_nodepda = (struct nodepda_s *)_nodepda;
- spinlock_t *recovery_lock = &err_nodepda->bte_recovery_lock;
struct timer_list *recovery_timer = &err_nodepda->bte_recovery_timer;
nasid_t nasid;
int i;
int valid_crbs;
- unsigned long irq_flags;
- volatile u64 *notify;
- bte_result_t bh_error;
ii_imem_u_t imem; /* II IMEM Register */
ii_icrb0_d_u_t icrbd; /* II CRB Register D */
ii_ibcr_u_t ibcr;
ii_icmr_u_t icmr;
ii_ieclr_u_t ieclr;
- BTE_PRINTK(("bte_error_handler(%p) - %d\n", err_nodepda,
+ BTE_PRINTK(("shub1_bte_error_handler(%p) - %d\n", err_nodepda,
smp_processor_id()));
- spin_lock_irqsave(recovery_lock, irq_flags);
-
if ((err_nodepda->bte_if[0].bh_error == BTE_SUCCESS) &&
(err_nodepda->bte_if[1].bh_error == BTE_SUCCESS)) {
BTE_PRINTK(("eh:%p:%d Nothing to do.\n", err_nodepda,
smp_processor_id()));
- spin_unlock_irqrestore(recovery_lock, irq_flags);
return;
}
- /*
- * Lock all interfaces on this node to prevent new transfers
- * from being queued.
- */
- for (i = 0; i < BTES_PER_NODE; i++) {
- if (err_nodepda->bte_if[i].cleanup_active) {
- continue;
- }
- spin_lock(&err_nodepda->bte_if[i].spinlock);
- BTE_PRINTK(("eh:%p:%d locked %d\n", err_nodepda,
- smp_processor_id(), i));
- err_nodepda->bte_if[i].cleanup_active = 1;
- }
/* Determine information about our hub */
nasid = cnodeid_to_nasid(err_nodepda->bte_if[0].bte_cnode);
@@ -101,7 +81,6 @@ void bte_error_handler(unsigned long _nodepda)
mod_timer(recovery_timer, HZ * 5);
BTE_PRINTK(("eh:%p:%d Marked Giving up\n", err_nodepda,
smp_processor_id()));
- spin_unlock_irqrestore(recovery_lock, irq_flags);
return;
}
if (icmr.ii_icmr_fld_s.i_crb_vld != 0) {
@@ -120,8 +99,6 @@ void bte_error_handler(unsigned long _nodepda)
BTE_PRINTK(("eh:%p:%d Valid %d, Giving up\n",
err_nodepda, smp_processor_id(),
i));
- spin_unlock_irqrestore(recovery_lock,
- irq_flags);
return;
}
}
@@ -146,6 +123,51 @@ void bte_error_handler(unsigned long _nodepda)
ibcr.ii_ibcr_fld_s.i_soft_reset = 1;
REMOTE_HUB_S(nasid, IIO_IBCR, ibcr.ii_ibcr_regval);
+ del_timer(recovery_timer);
+}
+
+/*
+ * Wait until all BTE related CRBs are completed
+ * and then reset the interfaces.
+ */
+void bte_error_handler(unsigned long _nodepda)
+{
+ struct nodepda_s *err_nodepda = (struct nodepda_s *)_nodepda;
+ spinlock_t *recovery_lock = &err_nodepda->bte_recovery_lock;
+ int i;
+ nasid_t nasid;
+ unsigned long irq_flags;
+ volatile u64 *notify;
+ bte_result_t bh_error;
+
+ BTE_PRINTK(("bte_error_handler(%p) - %d\n", err_nodepda,
+ smp_processor_id()));
+
+ spin_lock_irqsave(recovery_lock, irq_flags);
+
+ /*
+ * Lock all interfaces on this node to prevent new transfers
+ * from being queued.
+ */
+ for (i = 0; i < BTES_PER_NODE; i++) {
+ if (err_nodepda->bte_if[i].cleanup_active) {
+ continue;
+ }
+ spin_lock(&err_nodepda->bte_if[i].spinlock);
+ BTE_PRINTK(("eh:%p:%d locked %d\n", err_nodepda,
+ smp_processor_id(), i));
+ err_nodepda->bte_if[i].cleanup_active = 1;
+ }
+
+ if (is_shub1()) {
+ shub1_bte_error_handler(_nodepda);
+ } else {
+ nasid = cnodeid_to_nasid(err_nodepda->bte_if[0].bte_cnode);
+
+ if (ia64_sn_bte_recovery(nasid))
+ panic("bte_error_handler(): Fatal BTE Error");
+ }
+
for (i = 0; i < BTES_PER_NODE; i++) {
bh_error = err_nodepda->bte_if[i].bh_error;
if (bh_error != BTE_SUCCESS) {
@@ -165,8 +187,6 @@ void bte_error_handler(unsigned long _nodepda)
spin_unlock(&err_nodepda->bte_if[i].spinlock);
}
- del_timer(recovery_timer);
-
spin_unlock_irqrestore(recovery_lock, irq_flags);
}
diff --git a/arch/ia64/sn/kernel/huberror.c b/arch/ia64/sn/kernel/huberror.c
index 2bdf684c5066..5c39b43ba3c0 100644
--- a/arch/ia64/sn/kernel/huberror.c
+++ b/arch/ia64/sn/kernel/huberror.c
@@ -3,7 +3,7 @@
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
- * Copyright (C) 1992 - 1997, 2000,2002-2004 Silicon Graphics, Inc. All rights reserved.
+ * Copyright (C) 1992 - 1997, 2000,2002-2005 Silicon Graphics, Inc. All rights reserved.
*/
#include <linux/types.h>
@@ -38,8 +38,11 @@ static irqreturn_t hub_eint_handler(int irq, void *arg, struct pt_regs *ep)
if ((int)ret_stuff.v0)
panic("hubii_eint_handler(): Fatal TIO Error");
- if (!(nasid & 1)) /* Not a TIO, handle CRB errors */
- (void)hubiio_crb_error_handler(hubdev_info);
+ if (is_shub1()) {
+ if (!(nasid & 1)) /* Not a TIO, handle CRB errors */
+ (void)hubiio_crb_error_handler(hubdev_info);
+ } else
+ bte_error_handler((unsigned long)NODEPDA(nasid_to_cnodeid(nasid)));
return IRQ_HANDLED;
}
diff --git a/arch/ia64/sn/kernel/io_init.c b/arch/ia64/sn/kernel/io_init.c
index 001880812b7c..9e07f5463f21 100644
--- a/arch/ia64/sn/kernel/io_init.c
+++ b/arch/ia64/sn/kernel/io_init.c
@@ -11,14 +11,15 @@
#include <asm/sn/types.h>
#include <asm/sn/sn_sal.h>
#include <asm/sn/addrs.h>
-#include "pci/pcibus_provider_defs.h"
-#include "pci/pcidev.h"
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/pcidev.h>
#include "pci/pcibr_provider.h"
#include "xtalk/xwidgetdev.h"
#include <asm/sn/geo.h>
#include "xtalk/hubdev.h"
#include <asm/sn/io.h>
#include <asm/sn/simulator.h>
+#include <asm/sn/tioca_provider.h>
char master_baseio_wid;
nasid_t master_nasid = INVALID_NASID; /* Partition Master */
@@ -34,6 +35,37 @@ struct brick {
int sn_ioif_inited = 0; /* SN I/O infrastructure initialized? */
+struct sn_pcibus_provider *sn_pci_provider[PCIIO_ASIC_MAX_TYPES]; /* indexed by asic type */
+
+/*
+ * Hooks and struct for unsupported pci providers
+ */
+
+static dma_addr_t
+sn_default_pci_map(struct pci_dev *pdev, unsigned long paddr, size_t size)
+{
+ return 0;
+}
+
+static void
+sn_default_pci_unmap(struct pci_dev *pdev, dma_addr_t addr, int direction)
+{
+ return;
+}
+
+static void *
+sn_default_pci_bus_fixup(struct pcibus_bussoft *soft)
+{
+ return NULL;
+}
+
+static struct sn_pcibus_provider sn_pci_default_provider = {
+ .dma_map = sn_default_pci_map,
+ .dma_map_consistent = sn_default_pci_map,
+ .dma_unmap = sn_default_pci_unmap,
+ .bus_fixup = sn_default_pci_bus_fixup,
+};
+
/*
* Retrieve the DMA Flush List given nasid. This list is needed
* to implement the WAR - Flush DMA data on PIO Reads.
@@ -142,6 +174,12 @@ static void sn_fixup_ionodes(void)
if (status)
continue;
+ /* Attach the error interrupt handlers */
+ if (nasid & 1)
+ ice_error_init(hubdev);
+ else
+ hub_error_init(hubdev);
+
for (widget = 0; widget <= HUB_WIDGET_ID_MAX; widget++)
hubdev->hdi_xwidget_info[widget].xwi_hubinfo = hubdev;
@@ -179,10 +217,6 @@ static void sn_fixup_ionodes(void)
sn_flush_device_list;
}
- if (!(i & 1))
- hub_error_init(hubdev);
- else
- ice_error_init(hubdev);
}
}
@@ -201,6 +235,7 @@ static void sn_pci_fixup_slot(struct pci_dev *dev)
struct sn_irq_info *sn_irq_info;
struct pci_dev *host_pci_dev;
int status = 0;
+ struct pcibus_bussoft *bs;
dev->sysdata = kmalloc(sizeof(struct pcidev_info), GFP_KERNEL);
if (SN_PCIDEV_INFO(dev) <= 0)
@@ -241,6 +276,7 @@ static void sn_pci_fixup_slot(struct pci_dev *dev)
}
/* 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)->
@@ -248,10 +284,16 @@ static void sn_pci_fixup_slot(struct pci_dev *dev)
SN_PCIDEV_INFO(dev)->pdi_host_pcidev_info =
SN_PCIDEV_INFO(host_pci_dev);
SN_PCIDEV_INFO(dev)->pdi_linux_pcidev = dev;
- SN_PCIDEV_INFO(dev)->pdi_pcibus_info = SN_PCIBUS_BUSSOFT(dev->bus);
+ SN_PCIDEV_INFO(dev)->pdi_pcibus_info = bs;
+
+ if (bs && bs->bs_asic_type < PCIIO_ASIC_MAX_TYPES) {
+ SN_PCIDEV_BUSPROVIDER(dev) = sn_pci_provider[bs->bs_asic_type];
+ } else {
+ SN_PCIDEV_BUSPROVIDER(dev) = &sn_pci_default_provider;
+ }
/* Only set up IRQ stuff if this device has a host bus context */
- if (SN_PCIDEV_BUSSOFT(dev) && sn_irq_info->irq_irq) {
+ if (bs && sn_irq_info->irq_irq) {
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);
@@ -271,6 +313,7 @@ static void sn_pci_controller_fixup(int segment, int busnum)
struct pcibus_bussoft *prom_bussoft_ptr;
struct hubdev_info *hubdev_info;
void *provider_soft;
+ struct sn_pcibus_provider *provider;
status =
sal_get_pcibus_info((u64) segment, (u64) busnum,
@@ -291,16 +334,22 @@ static void sn_pci_controller_fixup(int segment, int busnum)
/*
* Per-provider fixup. Copies the contents from prom to local
* area and links SN_PCIBUS_BUSSOFT().
- *
- * Note: Provider is responsible for ensuring that prom_bussoft_ptr
- * represents an asic-type that it can handle.
*/
- if (prom_bussoft_ptr->bs_asic_type == PCIIO_ASIC_TYPE_PPB) {
- return; /* no further fixup necessary */
+ if (prom_bussoft_ptr->bs_asic_type >= PCIIO_ASIC_MAX_TYPES) {
+ return; /* unsupported asic type */
+ }
+
+ provider = sn_pci_provider[prom_bussoft_ptr->bs_asic_type];
+ if (provider == NULL) {
+ return; /* no provider registerd for this asic */
+ }
+
+ provider_soft = NULL;
+ if (provider->bus_fixup) {
+ provider_soft = (*provider->bus_fixup) (prom_bussoft_ptr);
}
- provider_soft = pcibr_bus_fixup(prom_bussoft_ptr);
if (provider_soft == NULL) {
return; /* fixup failed or not applicable */
}
@@ -339,6 +388,17 @@ static int __init sn_pci_init(void)
return 0;
/*
+ * prime sn_pci_provider[]. Individial provider init routines will
+ * override their respective default entries.
+ */
+
+ for (i = 0; i < PCIIO_ASIC_MAX_TYPES; i++)
+ sn_pci_provider[i] = &sn_pci_default_provider;
+
+ pcibr_init_provider();
+ tioca_init_provider();
+
+ /*
* This is needed to avoid bounce limit checks in the blk layer
*/
ia64_max_iommu_merge_mask = ~PAGE_MASK;
diff --git a/arch/ia64/sn/kernel/irq.c b/arch/ia64/sn/kernel/irq.c
index 3be44724f6c8..0f4e8138658f 100644
--- a/arch/ia64/sn/kernel/irq.c
+++ b/arch/ia64/sn/kernel/irq.c
@@ -13,8 +13,8 @@
#include <asm/sn/addrs.h>
#include <asm/sn/arch.h>
#include "xtalk/xwidgetdev.h"
-#include "pci/pcibus_provider_defs.h"
-#include "pci/pcidev.h"
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/pcidev.h>
#include "pci/pcibr_provider.h"
#include <asm/sn/shub_mmr.h>
#include <asm/sn/sn_sal.h>
@@ -82,20 +82,9 @@ static void sn_ack_irq(unsigned int irq)
nasid = get_nasid();
event_occurred =
HUB_L((uint64_t *) GLOBAL_MMR_ADDR(nasid, SH_EVENT_OCCURRED));
- if (event_occurred & SH_EVENT_OCCURRED_UART_INT_MASK) {
- mask |= (1 << SH_EVENT_OCCURRED_UART_INT_SHFT);
- }
- if (event_occurred & SH_EVENT_OCCURRED_IPI_INT_MASK) {
- mask |= (1 << SH_EVENT_OCCURRED_IPI_INT_SHFT);
- }
- if (event_occurred & SH_EVENT_OCCURRED_II_INT0_MASK) {
- mask |= (1 << SH_EVENT_OCCURRED_II_INT0_SHFT);
- }
- if (event_occurred & SH_EVENT_OCCURRED_II_INT1_MASK) {
- mask |= (1 << SH_EVENT_OCCURRED_II_INT1_SHFT);
- }
+ mask = event_occurred & SH_ALL_INT_MASK;
HUB_S((uint64_t *) GLOBAL_MMR_ADDR(nasid, SH_EVENT_OCCURRED_ALIAS),
- mask);
+ mask);
__set_bit(irq, (volatile void *)pda->sn_in_service_ivecs);
move_irq(irq);
diff --git a/arch/ia64/sn/kernel/mca.c b/arch/ia64/sn/kernel/mca.c
index 857774bb2c9a..6546db6abdba 100644
--- a/arch/ia64/sn/kernel/mca.c
+++ b/arch/ia64/sn/kernel/mca.c
@@ -37,6 +37,11 @@ static u64 *sn_oemdata_size, sn_oemdata_bufsize;
* This function is the callback routine that SAL calls to log error
* info for platform errors. buf is appended to sn_oemdata, resizing as
* required.
+ * Note: this is a SAL to OS callback, running under the same rules as the SAL
+ * code. SAL calls are run with preempt disabled so this routine must not
+ * sleep. vmalloc can sleep so print_hook cannot resize the output buffer
+ * itself, instead it must set the required size and return to let the caller
+ * resize the buffer then redrive the SAL call.
*/
static int print_hook(const char *fmt, ...)
{
@@ -47,18 +52,8 @@ static int print_hook(const char *fmt, ...)
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
len = strlen(buf);
- while (*sn_oemdata_size + len + 1 > sn_oemdata_bufsize) {
- u8 *newbuf = vmalloc(sn_oemdata_bufsize += 1000);
- if (!newbuf) {
- printk(KERN_ERR "%s: unable to extend sn_oemdata\n",
- __FUNCTION__);
- return 0;
- }
- memcpy(newbuf, *sn_oemdata, *sn_oemdata_size);
- vfree(*sn_oemdata);
- *sn_oemdata = newbuf;
- }
- memcpy(*sn_oemdata + *sn_oemdata_size, buf, len + 1);
+ if (*sn_oemdata_size + len <= sn_oemdata_bufsize)
+ memcpy(*sn_oemdata + *sn_oemdata_size, buf, len);
*sn_oemdata_size += len;
return 0;
}
@@ -98,7 +93,20 @@ sn_platform_plat_specific_err_print(const u8 * sect_header, u8 ** oemdata,
sn_oemdata = oemdata;
sn_oemdata_size = oemdata_size;
sn_oemdata_bufsize = 0;
- ia64_sn_plat_specific_err_print(print_hook, (char *)sect_header);
+ *sn_oemdata_size = PAGE_SIZE; /* first guess at how much data will be generated */
+ while (*sn_oemdata_size > sn_oemdata_bufsize) {
+ u8 *newbuf = vmalloc(*sn_oemdata_size);
+ if (!newbuf) {
+ printk(KERN_ERR "%s: unable to extend sn_oemdata\n",
+ __FUNCTION__);
+ return 1;
+ }
+ vfree(*sn_oemdata);
+ *sn_oemdata = newbuf;
+ sn_oemdata_bufsize = *sn_oemdata_size;
+ *sn_oemdata_size = 0;
+ ia64_sn_plat_specific_err_print(print_hook, (char *)sect_header);
+ }
up(&sn_oemdata_mutex);
return 0;
}
diff --git a/arch/ia64/sn/kernel/setup.c b/arch/ia64/sn/kernel/setup.c
index f0306b516afb..4fb44984afe6 100644
--- a/arch/ia64/sn/kernel/setup.c
+++ b/arch/ia64/sn/kernel/setup.c
@@ -3,7 +3,7 @@
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
- * Copyright (C) 1999,2001-2004 Silicon Graphics, Inc. All rights reserved.
+ * Copyright (C) 1999,2001-2005 Silicon Graphics, Inc. All rights reserved.
*/
#include <linux/config.h>
@@ -29,6 +29,7 @@
#include <linux/sched.h>
#include <linux/root_dev.h>
#include <linux/nodemask.h>
+#include <linux/pm.h>
#include <asm/io.h>
#include <asm/sal.h>
@@ -72,6 +73,12 @@ EXPORT_SYMBOL(sn_rtc_cycles_per_second);
DEFINE_PER_CPU(struct sn_hub_info_s, __sn_hub_info);
EXPORT_PER_CPU_SYMBOL(__sn_hub_info);
+DEFINE_PER_CPU(short, __sn_cnodeid_to_nasid[MAX_NUMNODES]);
+EXPORT_PER_CPU_SYMBOL(__sn_cnodeid_to_nasid);
+
+DEFINE_PER_CPU(struct nodepda_s *, __sn_nodepda);
+EXPORT_PER_CPU_SYMBOL(__sn_nodepda);
+
partid_t sn_partid = -1;
EXPORT_SYMBOL(sn_partid);
char sn_system_serial_number_string[128];
@@ -353,6 +360,14 @@ void __init sn_setup(char **cmdline_p)
screen_info = sn_screen_info;
sn_timer_init();
+
+ /*
+ * set pm_power_off to a SAL call to allow
+ * sn machines to power off. The SAL call can be replaced
+ * by an ACPI interface call when ACPI is fully implemented
+ * for sn.
+ */
+ pm_power_off = ia64_sn_power_down;
}
/**
@@ -364,11 +379,11 @@ static void __init sn_init_pdas(char **cmdline_p)
{
cnodeid_t cnode;
- memset(pda->cnodeid_to_nasid_table, -1,
- sizeof(pda->cnodeid_to_nasid_table));
+ memset(sn_cnodeid_to_nasid, -1,
+ sizeof(__ia64_per_cpu_var(__sn_cnodeid_to_nasid)));
for_each_online_node(cnode)
- pda->cnodeid_to_nasid_table[cnode] =
- pxm_to_nasid(nid_to_pxm_map[cnode]);
+ sn_cnodeid_to_nasid[cnode] =
+ pxm_to_nasid(nid_to_pxm_map[cnode]);
numionodes = num_online_nodes();
scan_for_ionodes();
@@ -468,7 +483,8 @@ void __init sn_cpu_init(void)
cnode = nasid_to_cnodeid(nasid);
- pda->p_nodepda = nodepdaindr[cnode];
+ sn_nodepda = nodepdaindr[cnode];
+
pda->led_address =
(typeof(pda->led_address)) (LED0 + (slice << LED_CPU_SHIFT));
pda->led_state = LED_ALWAYS_SET;
@@ -477,15 +493,18 @@ void __init sn_cpu_init(void)
pda->idle_flag = 0;
if (cpuid != 0) {
- memcpy(pda->cnodeid_to_nasid_table,
- pdacpu(0)->cnodeid_to_nasid_table,
- sizeof(pda->cnodeid_to_nasid_table));
+ /* copy cpu 0's sn_cnodeid_to_nasid table to this cpu's */
+ memcpy(sn_cnodeid_to_nasid,
+ (&per_cpu(__sn_cnodeid_to_nasid, 0)),
+ sizeof(__ia64_per_cpu_var(__sn_cnodeid_to_nasid)));
}
/*
* Check for WARs.
* Only needs to be done once, on BSP.
- * Has to be done after loop above, because it uses pda.cnodeid_to_nasid_table[i].
+ * Has to be done after loop above, because it uses this cpu's
+ * sn_cnodeid_to_nasid table which was just initialized if this
+ * isn't cpu 0.
* Has to be done before assignment below.
*/
if (!wars_have_been_checked) {
@@ -571,8 +590,7 @@ static void __init scan_for_ionodes(void)
brd = find_lboard_any(brd, KLTYPE_SNIA);
while (brd) {
- pda->cnodeid_to_nasid_table[numionodes] =
- brd->brd_nasid;
+ sn_cnodeid_to_nasid[numionodes] = brd->brd_nasid;
physical_node_map[brd->brd_nasid] = numionodes;
root_lboard[numionodes] = brd;
numionodes++;
@@ -593,8 +611,7 @@ static void __init scan_for_ionodes(void)
root_lboard[nasid_to_cnodeid(nasid)],
KLTYPE_TIO);
while (brd) {
- pda->cnodeid_to_nasid_table[numionodes] =
- brd->brd_nasid;
+ sn_cnodeid_to_nasid[numionodes] = brd->brd_nasid;
physical_node_map[brd->brd_nasid] = numionodes;
root_lboard[numionodes] = brd;
numionodes++;
@@ -605,7 +622,6 @@ static void __init scan_for_ionodes(void)
brd = find_lboard_any(brd, KLTYPE_TIO);
}
}
-
}
int
@@ -614,7 +630,8 @@ nasid_slice_to_cpuid(int nasid, int slice)
long cpu;
for (cpu=0; cpu < NR_CPUS; cpu++)
- if (nodepda->phys_cpuid[cpu].nasid == nasid && nodepda->phys_cpuid[cpu].slice == slice)
+ if (cpuid_to_nasid(cpu) == nasid &&
+ cpuid_to_slice(cpu) == slice)
return cpu;
return -1;
diff --git a/arch/ia64/sn/kernel/sn2/sn_hwperf.c b/arch/ia64/sn/kernel/sn2/sn_hwperf.c
index 197356460ee1..833e700fdac9 100644
--- a/arch/ia64/sn/kernel/sn2/sn_hwperf.c
+++ b/arch/ia64/sn/kernel/sn2/sn_hwperf.c
@@ -28,6 +28,7 @@
#include <linux/vmalloc.h>
#include <linux/seq_file.h>
#include <linux/miscdevice.h>
+#include <linux/utsname.h>
#include <linux/cpumask.h>
#include <linux/smp_lock.h>
#include <linux/nodemask.h>
@@ -43,6 +44,7 @@
#include <asm/sn/module.h>
#include <asm/sn/geo.h>
#include <asm/sn/sn2/sn_hwperf.h>
+#include <asm/sn/addrs.h>
static void *sn_hwperf_salheap = NULL;
static int sn_hwperf_obj_cnt = 0;
@@ -81,26 +83,45 @@ out:
return e;
}
+static int sn_hwperf_location_to_bpos(char *location,
+ int *rack, int *bay, int *slot, int *slab)
+{
+ char type;
+
+ /* first scan for an old style geoid string */
+ if (sscanf(location, "%03d%c%02d#%d",
+ rack, &type, bay, slab) == 4)
+ *slot = 0;
+ else /* scan for a new bladed geoid string */
+ if (sscanf(location, "%03d%c%02d^%02d#%d",
+ rack, &type, bay, slot, slab) != 5)
+ return -1;
+ /* success */
+ return 0;
+}
+
static int sn_hwperf_geoid_to_cnode(char *location)
{
int cnode;
geoid_t geoid;
moduleid_t module_id;
- char type;
- int rack, slot, slab;
- int this_rack, this_slot, this_slab;
+ int rack, bay, slot, slab;
+ int this_rack, this_bay, this_slot, this_slab;
- if (sscanf(location, "%03d%c%02d#%d", &rack, &type, &slot, &slab) != 4)
+ if (sn_hwperf_location_to_bpos(location, &rack, &bay, &slot, &slab))
return -1;
for (cnode = 0; cnode < numionodes; cnode++) {
geoid = cnodeid_get_geoid(cnode);
module_id = geo_module(geoid);
this_rack = MODULE_GET_RACK(module_id);
- this_slot = MODULE_GET_BPOS(module_id);
+ this_bay = MODULE_GET_BPOS(module_id);
+ this_slot = geo_slot(geoid);
this_slab = geo_slab(geoid);
- if (rack == this_rack && slot == this_slot && slab == this_slab)
+ if (rack == this_rack && bay == this_bay &&
+ slot == this_slot && slab == this_slab) {
break;
+ }
}
return cnode < numionodes ? cnode : -1;
@@ -153,11 +174,36 @@ static const char *sn_hwperf_get_slabname(struct sn_hwperf_object_info *obj,
return slabname;
}
+static void print_pci_topology(struct seq_file *s,
+ struct sn_hwperf_object_info *obj, int *ordinal,
+ u64 rack, u64 bay, u64 slot, u64 slab)
+{
+ char *p1;
+ char *p2;
+ char *pg;
+
+ if (!(pg = (char *)get_zeroed_page(GFP_KERNEL)))
+ return; /* ignore */
+ if (ia64_sn_ioif_get_pci_topology(rack, bay, slot, slab,
+ __pa(pg), PAGE_SIZE) == SN_HWPERF_OP_OK) {
+ for (p1=pg; *p1 && p1 < pg + PAGE_SIZE;) {
+ if (!(p2 = strchr(p1, '\n')))
+ break;
+ *p2 = '\0';
+ seq_printf(s, "pcibus %d %s-%s\n",
+ *ordinal, obj->location, p1);
+ (*ordinal)++;
+ p1 = p2 + 1;
+ }
+ }
+ free_page((unsigned long)pg);
+}
+
static int sn_topology_show(struct seq_file *s, void *d)
{
int sz;
int pt;
- int e;
+ int e = 0;
int i;
int j;
const char *slabname;
@@ -169,11 +215,44 @@ static int sn_topology_show(struct seq_file *s, void *d)
struct sn_hwperf_object_info *p;
struct sn_hwperf_object_info *obj = d; /* this object */
struct sn_hwperf_object_info *objs = s->private; /* all objects */
+ int rack, bay, slot, slab;
+ u8 shubtype;
+ u8 system_size;
+ u8 sharing_size;
+ u8 partid;
+ u8 coher;
+ u8 nasid_shift;
+ u8 region_size;
+ u16 nasid_mask;
+ int nasid_msb;
+ int pci_bus_ordinal = 0;
if (obj == objs) {
- seq_printf(s, "# sn_topology version 1\n");
+ seq_printf(s, "# sn_topology version 2\n");
seq_printf(s, "# objtype ordinal location partition"
" [attribute value [, ...]]\n");
+
+ if (ia64_sn_get_sn_info(0,
+ &shubtype, &nasid_mask, &nasid_shift, &system_size,
+ &sharing_size, &partid, &coher, &region_size))
+ BUG();
+ for (nasid_msb=63; nasid_msb > 0; nasid_msb--) {
+ if (((u64)nasid_mask << nasid_shift) & (1ULL << nasid_msb))
+ break;
+ }
+ seq_printf(s, "partition %u %s local "
+ "shubtype %s, "
+ "nasid_mask 0x%016lx, "
+ "nasid_bits %d:%d, "
+ "system_size %d, "
+ "sharing_size %d, "
+ "coherency_domain %d, "
+ "region_size %d\n",
+
+ partid, system_utsname.nodename,
+ shubtype ? "shub2" : "shub1",
+ (u64)nasid_mask << nasid_shift, nasid_msb, nasid_shift,
+ system_size, sharing_size, coher, region_size);
}
if (SN_HWPERF_FOREIGN(obj)) {
@@ -181,7 +260,7 @@ static int sn_topology_show(struct seq_file *s, void *d)
return 0;
}
- for (i = 0; obj->name[i]; i++) {
+ for (i = 0; i < SN_HWPERF_MAXSTRING && obj->name[i]; i++) {
if (obj->name[i] == ' ')
obj->name[i] = '_';
}
@@ -221,6 +300,17 @@ static int sn_topology_show(struct seq_file *s, void *d)
seq_putc(s, '\n');
}
}
+
+ /*
+ * PCI busses attached to this node, if any
+ */
+ if (sn_hwperf_location_to_bpos(obj->location,
+ &rack, &bay, &slot, &slab)) {
+ /* export pci bus info */
+ print_pci_topology(s, obj, &pci_bus_ordinal,
+ rack, bay, slot, slab);
+
+ }
}
if (obj->ports) {
@@ -397,6 +487,9 @@ static int sn_hwperf_map_err(int hwperf_err)
break;
case SN_HWPERF_OP_BUSY:
+ e = -EBUSY;
+ break;
+
case SN_HWPERF_OP_RECONFIGURE:
e = -EAGAIN;
break;
@@ -549,6 +642,7 @@ sn_hwperf_ioctl(struct inode *in, struct file *fp, u32 op, u64 arg)
r = sn_hwperf_op_cpu(&op_info);
if (r) {
r = sn_hwperf_map_err(r);
+ a.v0 = v0;
goto error;
}
break;
diff --git a/arch/ia64/sn/kernel/tiocx.c b/arch/ia64/sn/kernel/tiocx.c
new file mode 100644
index 000000000000..ab9b5f35c2a7
--- /dev/null
+++ b/arch/ia64/sn/kernel/tiocx.c
@@ -0,0 +1,552 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2005 Silicon Graphics, Inc. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/proc_fs.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/io.h>
+#include <asm/sn/types.h>
+#include <asm/sn/shubio.h>
+#include <asm/sn/tiocx.h>
+#include <asm/sn/l1.h>
+#include <asm/sn/module.h>
+#include "tio.h"
+#include "xtalk/xwidgetdev.h"
+#include "xtalk/hubdev.h"
+
+#define CX_DEV_NONE 0
+#define DEVICE_NAME "tiocx"
+#define WIDGET_ID 0
+#define TIOCX_DEBUG 0
+
+#if TIOCX_DEBUG
+#define DBG(fmt...) printk(KERN_ALERT fmt)
+#else
+#define DBG(fmt...)
+#endif
+
+struct device_attribute dev_attr_cxdev_control;
+
+/**
+ * tiocx_match - Try to match driver id list with device.
+ * @dev: device pointer
+ * @drv: driver pointer
+ *
+ * Returns 1 if match, 0 otherwise.
+ */
+static int tiocx_match(struct device *dev, struct device_driver *drv)
+{
+ struct cx_dev *cx_dev = to_cx_dev(dev);
+ struct cx_drv *cx_drv = to_cx_driver(drv);
+ const struct cx_device_id *ids = cx_drv->id_table;
+
+ if (!ids)
+ return 0;
+
+ while (ids->part_num) {
+ if (ids->part_num == cx_dev->cx_id.part_num)
+ return 1;
+ ids++;
+ }
+ return 0;
+
+}
+
+static int tiocx_hotplug(struct device *dev, char **envp, int num_envp,
+ char *buffer, int buffer_size)
+{
+ return -ENODEV;
+}
+
+static void tiocx_bus_release(struct device *dev)
+{
+ kfree(to_cx_dev(dev));
+}
+
+struct bus_type tiocx_bus_type = {
+ .name = "tiocx",
+ .match = tiocx_match,
+ .hotplug = tiocx_hotplug,
+};
+
+/**
+ * cx_device_match - Find cx_device in the id table.
+ * @ids: id table from driver
+ * @cx_device: part/mfg id for the device
+ *
+ */
+static const struct cx_device_id *cx_device_match(const struct cx_device_id
+ *ids,
+ struct cx_dev *cx_device)
+{
+ /*
+ * NOTES: We may want to check for CX_ANY_ID too.
+ * Do we want to match against nasid too?
+ * CX_DEV_NONE == 0, if the driver tries to register for
+ * part/mfg == 0 we should return no-match (NULL) here.
+ */
+ while (ids->part_num && ids->mfg_num) {
+ if (ids->part_num == cx_device->cx_id.part_num &&
+ ids->mfg_num == cx_device->cx_id.mfg_num)
+ return ids;
+ ids++;
+ }
+
+ return NULL;
+}
+
+/**
+ * cx_device_probe - Look for matching device.
+ * Call driver probe routine if found.
+ * @cx_driver: driver table (cx_drv struct) from driver
+ * @cx_device: part/mfg id for the device
+ */
+static int cx_device_probe(struct device *dev)
+{
+ const struct cx_device_id *id;
+ struct cx_drv *cx_drv = to_cx_driver(dev->driver);
+ struct cx_dev *cx_dev = to_cx_dev(dev);
+ int error = 0;
+
+ if (!cx_dev->driver && cx_drv->probe) {
+ id = cx_device_match(cx_drv->id_table, cx_dev);
+ if (id) {
+ if ((error = cx_drv->probe(cx_dev, id)) < 0)
+ return error;
+ else
+ cx_dev->driver = cx_drv;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * cx_driver_remove - Remove driver from device struct.
+ * @dev: device
+ */
+static int cx_driver_remove(struct device *dev)
+{
+ struct cx_dev *cx_dev = to_cx_dev(dev);
+ struct cx_drv *cx_drv = cx_dev->driver;
+ if (cx_drv->remove)
+ cx_drv->remove(cx_dev);
+ cx_dev->driver = NULL;
+ return 0;
+}
+
+/**
+ * cx_driver_register - Register the driver.
+ * @cx_driver: driver table (cx_drv struct) from driver
+ *
+ * Called from the driver init routine to register a driver.
+ * The cx_drv struct contains the driver name, a pointer to
+ * a table of part/mfg numbers and a pointer to the driver's
+ * probe/attach routine.
+ */
+int cx_driver_register(struct cx_drv *cx_driver)
+{
+ cx_driver->driver.name = cx_driver->name;
+ cx_driver->driver.bus = &tiocx_bus_type;
+ cx_driver->driver.probe = cx_device_probe;
+ cx_driver->driver.remove = cx_driver_remove;
+
+ return driver_register(&cx_driver->driver);
+}
+
+/**
+ * cx_driver_unregister - Unregister the driver.
+ * @cx_driver: driver table (cx_drv struct) from driver
+ */
+int cx_driver_unregister(struct cx_drv *cx_driver)
+{
+ driver_unregister(&cx_driver->driver);
+ return 0;
+}
+
+/**
+ * cx_device_register - Register a device.
+ * @nasid: device's nasid
+ * @part_num: device's part number
+ * @mfg_num: device's manufacturer number
+ * @hubdev: hub info associated with this device
+ *
+ */
+int
+cx_device_register(nasid_t nasid, int part_num, int mfg_num,
+ struct hubdev_info *hubdev)
+{
+ struct cx_dev *cx_dev;
+
+ cx_dev = kcalloc(1, sizeof(struct cx_dev), GFP_KERNEL);
+ DBG("cx_dev= 0x%p\n", cx_dev);
+ if (cx_dev == NULL)
+ return -ENOMEM;
+
+ cx_dev->cx_id.part_num = part_num;
+ cx_dev->cx_id.mfg_num = mfg_num;
+ cx_dev->cx_id.nasid = nasid;
+ cx_dev->hubdev = hubdev;
+
+ cx_dev->dev.parent = NULL;
+ cx_dev->dev.bus = &tiocx_bus_type;
+ cx_dev->dev.release = tiocx_bus_release;
+ snprintf(cx_dev->dev.bus_id, BUS_ID_SIZE, "%d.0x%x",
+ cx_dev->cx_id.nasid, cx_dev->cx_id.part_num);
+ device_register(&cx_dev->dev);
+ get_device(&cx_dev->dev);
+
+ device_create_file(&cx_dev->dev, &dev_attr_cxdev_control);
+
+ return 0;
+}
+
+/**
+ * cx_device_unregister - Unregister a device.
+ * @cx_dev: part/mfg id for the device
+ */
+int cx_device_unregister(struct cx_dev *cx_dev)
+{
+ put_device(&cx_dev->dev);
+ device_unregister(&cx_dev->dev);
+ return 0;
+}
+
+/**
+ * cx_device_reload - Reload the device.
+ * @nasid: device's nasid
+ * @part_num: device's part number
+ * @mfg_num: device's manufacturer number
+ *
+ * Remove the device associated with 'nasid' from device list and then
+ * call device-register with the given part/mfg numbers.
+ */
+static int cx_device_reload(struct cx_dev *cx_dev)
+{
+ device_remove_file(&cx_dev->dev, &dev_attr_cxdev_control);
+ cx_device_unregister(cx_dev);
+ return cx_device_register(cx_dev->cx_id.nasid, cx_dev->cx_id.part_num,
+ cx_dev->cx_id.mfg_num, cx_dev->hubdev);
+}
+
+static inline uint64_t tiocx_intr_alloc(nasid_t nasid, int widget,
+ u64 sn_irq_info,
+ int req_irq, nasid_t req_nasid,
+ int req_slice)
+{
+ struct ia64_sal_retval rv;
+ rv.status = 0;
+ rv.v0 = 0;
+
+ ia64_sal_oemcall_nolock(&rv, SN_SAL_IOIF_INTERRUPT,
+ SAL_INTR_ALLOC, nasid,
+ widget, sn_irq_info, req_irq,
+ req_nasid, req_slice);
+ return rv.status;
+}
+
+static inline void tiocx_intr_free(nasid_t nasid, int widget,
+ struct sn_irq_info *sn_irq_info)
+{
+ struct ia64_sal_retval rv;
+ rv.status = 0;
+ rv.v0 = 0;
+
+ ia64_sal_oemcall_nolock(&rv, SN_SAL_IOIF_INTERRUPT,
+ SAL_INTR_FREE, nasid,
+ widget, sn_irq_info->irq_irq,
+ sn_irq_info->irq_cookie, 0, 0);
+}
+
+struct sn_irq_info *tiocx_irq_alloc(nasid_t nasid, int widget, int irq,
+ nasid_t req_nasid, int slice)
+{
+ struct sn_irq_info *sn_irq_info;
+ int status;
+ int sn_irq_size = sizeof(struct sn_irq_info);
+
+ if ((nasid & 1) == 0)
+ return NULL;
+
+ sn_irq_info = kmalloc(sn_irq_size, GFP_KERNEL);
+ if (sn_irq_info == NULL)
+ return NULL;
+
+ memset(sn_irq_info, 0x0, sn_irq_size);
+
+ status = tiocx_intr_alloc(nasid, widget, __pa(sn_irq_info), irq,
+ req_nasid, slice);
+ if (status) {
+ kfree(sn_irq_info);
+ return NULL;
+ } else {
+ return sn_irq_info;
+ }
+}
+
+void tiocx_irq_free(struct sn_irq_info *sn_irq_info)
+{
+ uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge;
+ nasid_t nasid = NASID_GET(bridge);
+ int widget;
+
+ if (nasid & 1) {
+ widget = TIO_SWIN_WIDGETNUM(bridge);
+ tiocx_intr_free(nasid, widget, sn_irq_info);
+ kfree(sn_irq_info);
+ }
+}
+
+uint64_t tiocx_dma_addr(uint64_t addr)
+{
+ return PHYS_TO_TIODMA(addr);
+}
+
+uint64_t tiocx_swin_base(int nasid)
+{
+ return TIO_SWIN_BASE(nasid, TIOCX_CORELET);
+}
+
+EXPORT_SYMBOL(cx_driver_register);
+EXPORT_SYMBOL(cx_driver_unregister);
+EXPORT_SYMBOL(cx_device_register);
+EXPORT_SYMBOL(cx_device_unregister);
+EXPORT_SYMBOL(tiocx_irq_alloc);
+EXPORT_SYMBOL(tiocx_irq_free);
+EXPORT_SYMBOL(tiocx_bus_type);
+EXPORT_SYMBOL(tiocx_dma_addr);
+EXPORT_SYMBOL(tiocx_swin_base);
+
+static void tio_conveyor_set(nasid_t nasid, int enable_flag)
+{
+ uint64_t ice_frz;
+ uint64_t disable_cb = (1ull << 61);
+
+ if (!(nasid & 1))
+ return;
+
+ ice_frz = REMOTE_HUB_L(nasid, TIO_ICE_FRZ_CFG);
+ if (enable_flag) {
+ if (!(ice_frz & disable_cb)) /* already enabled */
+ return;
+ ice_frz &= ~disable_cb;
+ } else {
+ if (ice_frz & disable_cb) /* already disabled */
+ return;
+ ice_frz |= disable_cb;
+ }
+ DBG(KERN_ALERT "TIO_ICE_FRZ_CFG= 0x%lx\n", ice_frz);
+ REMOTE_HUB_S(nasid, TIO_ICE_FRZ_CFG, ice_frz);
+}
+
+#define tio_conveyor_enable(nasid) tio_conveyor_set(nasid, 1)
+#define tio_conveyor_disable(nasid) tio_conveyor_set(nasid, 0)
+
+static void tio_corelet_reset(nasid_t nasid, int corelet)
+{
+ if (!(nasid & 1))
+ return;
+
+ REMOTE_HUB_S(nasid, TIO_ICE_PMI_TX_CFG, 1 << corelet);
+ udelay(2000);
+ REMOTE_HUB_S(nasid, TIO_ICE_PMI_TX_CFG, 0);
+ udelay(2000);
+}
+
+static int tiocx_btchar_get(int nasid)
+{
+ moduleid_t module_id;
+ geoid_t geoid;
+ int cnodeid;
+
+ cnodeid = nasid_to_cnodeid(nasid);
+ geoid = cnodeid_get_geoid(cnodeid);
+ module_id = geo_module(geoid);
+ return MODULE_GET_BTCHAR(module_id);
+}
+
+static int is_fpga_brick(int nasid)
+{
+ switch (tiocx_btchar_get(nasid)) {
+ case L1_BRICKTYPE_SA:
+ case L1_BRICKTYPE_ATHENA:
+ return 1;
+ }
+ return 0;
+}
+
+static int bitstream_loaded(nasid_t nasid)
+{
+ uint64_t cx_credits;
+
+ cx_credits = REMOTE_HUB_L(nasid, TIO_ICE_PMI_TX_DYN_CREDIT_STAT_CB3);
+ cx_credits &= TIO_ICE_PMI_TX_DYN_CREDIT_STAT_CB3_CREDIT_CNT_MASK;
+ DBG("cx_credits= 0x%lx\n", cx_credits);
+
+ return (cx_credits == 0xf) ? 1 : 0;
+}
+
+static int tiocx_reload(struct cx_dev *cx_dev)
+{
+ int part_num = CX_DEV_NONE;
+ int mfg_num = CX_DEV_NONE;
+ nasid_t nasid = cx_dev->cx_id.nasid;
+
+ if (bitstream_loaded(nasid)) {
+ uint64_t cx_id;
+
+ cx_id =
+ *(volatile int32_t *)(TIO_SWIN_BASE(nasid, TIOCX_CORELET) +
+ WIDGET_ID);
+ part_num = XWIDGET_PART_NUM(cx_id);
+ mfg_num = XWIDGET_MFG_NUM(cx_id);
+ DBG("part= 0x%x, mfg= 0x%x\n", part_num, mfg_num);
+ /* just ignore it if it's a CE */
+ if (part_num == TIO_CE_ASIC_PARTNUM)
+ return 0;
+ }
+
+ cx_dev->cx_id.part_num = part_num;
+ cx_dev->cx_id.mfg_num = mfg_num;
+
+ /*
+ * Delete old device and register the new one. It's ok if
+ * part_num/mfg_num == CX_DEV_NONE. We want to register
+ * devices in the table even if a bitstream isn't loaded.
+ * That allows use to see that a bitstream isn't loaded via
+ * TIOCX_IOCTL_DEV_LIST.
+ */
+ return cx_device_reload(cx_dev);
+}
+
+static ssize_t show_cxdev_control(struct device *dev, char *buf)
+{
+ struct cx_dev *cx_dev = to_cx_dev(dev);
+
+ return sprintf(buf, "0x%x 0x%x 0x%x %d\n",
+ cx_dev->cx_id.nasid,
+ cx_dev->cx_id.part_num, cx_dev->cx_id.mfg_num,
+ tiocx_btchar_get(cx_dev->cx_id.nasid));
+}
+
+static ssize_t store_cxdev_control(struct device *dev, const char *buf,
+ size_t count)
+{
+ int n;
+ struct cx_dev *cx_dev = to_cx_dev(dev);
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (count <= 0)
+ return 0;
+
+ n = simple_strtoul(buf, NULL, 0);
+
+ switch (n) {
+ case 1:
+ tiocx_reload(cx_dev);
+ break;
+ case 3:
+ tio_corelet_reset(cx_dev->cx_id.nasid, TIOCX_CORELET);
+ break;
+ default:
+ break;
+ }
+
+ return count;
+}
+
+DEVICE_ATTR(cxdev_control, 0644, show_cxdev_control, store_cxdev_control);
+
+static int __init tiocx_init(void)
+{
+ cnodeid_t cnodeid;
+ int found_tiocx_device = 0;
+
+ bus_register(&tiocx_bus_type);
+
+ for (cnodeid = 0; cnodeid < MAX_COMPACT_NODES; cnodeid++) {
+ nasid_t nasid;
+
+ if ((nasid = cnodeid_to_nasid(cnodeid)) < 0)
+ break; /* No more nasids .. bail out of loop */
+
+ if ((nasid & 0x1) && is_fpga_brick(nasid)) {
+ struct hubdev_info *hubdev;
+ struct xwidget_info *widgetp;
+
+ DBG("Found TIO at nasid 0x%x\n", nasid);
+
+ hubdev =
+ (struct hubdev_info *)(NODEPDA(cnodeid)->pdinfo);
+
+ widgetp = &hubdev->hdi_xwidget_info[TIOCX_CORELET];
+
+ /* The CE hangs off of the CX port but is not an FPGA */
+ if (widgetp->xwi_hwid.part_num == TIO_CE_ASIC_PARTNUM)
+ continue;
+
+ tio_corelet_reset(nasid, TIOCX_CORELET);
+ tio_conveyor_enable(nasid);
+
+ if (cx_device_register
+ (nasid, widgetp->xwi_hwid.part_num,
+ widgetp->xwi_hwid.mfg_num, hubdev) < 0)
+ return -ENXIO;
+ else
+ found_tiocx_device++;
+ }
+ }
+
+ /* It's ok if we find zero devices. */
+ DBG("found_tiocx_device= %d\n", found_tiocx_device);
+
+ return 0;
+}
+
+static void __exit tiocx_exit(void)
+{
+ struct device *dev;
+ struct device *tdev;
+
+ DBG("tiocx_exit\n");
+
+ /*
+ * Unregister devices.
+ */
+ list_for_each_entry_safe(dev, tdev, &tiocx_bus_type.devices.list,
+ bus_list) {
+ if (dev) {
+ struct cx_dev *cx_dev = to_cx_dev(dev);
+ device_remove_file(dev, &dev_attr_cxdev_control);
+ cx_device_unregister(cx_dev);
+ }
+ }
+
+ bus_unregister(&tiocx_bus_type);
+}
+
+module_init(tiocx_init);
+module_exit(tiocx_exit);
+
+/************************************************************************
+ * Module licensing and description
+ ************************************************************************/
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Bruce Losure <blosure@sgi.com>");
+MODULE_DESCRIPTION("TIOCX module");
+MODULE_SUPPORTED_DEVICE(DEVICE_NAME);
diff --git a/arch/ia64/sn/kernel/xp_main.c b/arch/ia64/sn/kernel/xp_main.c
new file mode 100644
index 000000000000..3be52a34c80f
--- /dev/null
+++ b/arch/ia64/sn/kernel/xp_main.c
@@ -0,0 +1,289 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2004-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+
+/*
+ * Cross Partition (XP) base.
+ *
+ * XP provides a base from which its users can interact
+ * with XPC, yet not be dependent on XPC.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <asm/sn/intr.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/sn/xp.h>
+
+
+/*
+ * Target of nofault PIO read.
+ */
+u64 xp_nofault_PIOR_target;
+
+
+/*
+ * xpc_registrations[] keeps track of xpc_connect()'s done by the kernel-level
+ * users of XPC.
+ */
+struct xpc_registration xpc_registrations[XPC_NCHANNELS];
+
+
+/*
+ * Initialize the XPC interface to indicate that XPC isn't loaded.
+ */
+static enum xpc_retval xpc_notloaded(void) { return xpcNotLoaded; }
+
+struct xpc_interface xpc_interface = {
+ (void (*)(int)) xpc_notloaded,
+ (void (*)(int)) xpc_notloaded,
+ (enum xpc_retval (*)(partid_t, int, u32, void **)) xpc_notloaded,
+ (enum xpc_retval (*)(partid_t, int, void *)) xpc_notloaded,
+ (enum xpc_retval (*)(partid_t, int, void *, xpc_notify_func, void *))
+ xpc_notloaded,
+ (void (*)(partid_t, int, void *)) xpc_notloaded,
+ (enum xpc_retval (*)(partid_t, void *)) xpc_notloaded
+};
+
+
+/*
+ * XPC calls this when it (the XPC module) has been loaded.
+ */
+void
+xpc_set_interface(void (*connect)(int),
+ void (*disconnect)(int),
+ enum xpc_retval (*allocate)(partid_t, int, u32, void **),
+ enum xpc_retval (*send)(partid_t, int, void *),
+ enum xpc_retval (*send_notify)(partid_t, int, void *,
+ xpc_notify_func, void *),
+ void (*received)(partid_t, int, void *),
+ enum xpc_retval (*partid_to_nasids)(partid_t, void *))
+{
+ xpc_interface.connect = connect;
+ xpc_interface.disconnect = disconnect;
+ xpc_interface.allocate = allocate;
+ xpc_interface.send = send;
+ xpc_interface.send_notify = send_notify;
+ xpc_interface.received = received;
+ xpc_interface.partid_to_nasids = partid_to_nasids;
+}
+
+
+/*
+ * XPC calls this when it (the XPC module) is being unloaded.
+ */
+void
+xpc_clear_interface(void)
+{
+ xpc_interface.connect = (void (*)(int)) xpc_notloaded;
+ xpc_interface.disconnect = (void (*)(int)) xpc_notloaded;
+ xpc_interface.allocate = (enum xpc_retval (*)(partid_t, int, u32,
+ void **)) xpc_notloaded;
+ xpc_interface.send = (enum xpc_retval (*)(partid_t, int, void *))
+ xpc_notloaded;
+ xpc_interface.send_notify = (enum xpc_retval (*)(partid_t, int, void *,
+ xpc_notify_func, void *)) xpc_notloaded;
+ xpc_interface.received = (void (*)(partid_t, int, void *))
+ xpc_notloaded;
+ xpc_interface.partid_to_nasids = (enum xpc_retval (*)(partid_t, void *))
+ xpc_notloaded;
+}
+
+
+/*
+ * Register for automatic establishment of a channel connection whenever
+ * a partition comes up.
+ *
+ * Arguments:
+ *
+ * ch_number - channel # to register for connection.
+ * func - function to call for asynchronous notification of channel
+ * state changes (i.e., connection, disconnection, error) and
+ * the arrival of incoming messages.
+ * key - pointer to optional user-defined value that gets passed back
+ * to the user on any callouts made to func.
+ * payload_size - size in bytes of the XPC message's payload area which
+ * contains a user-defined message. The user should make
+ * this large enough to hold their largest message.
+ * nentries - max #of XPC message entries a message queue can contain.
+ * The actual number, which is determined when a connection
+ * is established and may be less then requested, will be
+ * passed to the user via the xpcConnected callout.
+ * assigned_limit - max number of kthreads allowed to be processing
+ * messages (per connection) at any given instant.
+ * idle_limit - max number of kthreads allowed to be idle at any given
+ * instant.
+ */
+enum xpc_retval
+xpc_connect(int ch_number, xpc_channel_func func, void *key, u16 payload_size,
+ u16 nentries, u32 assigned_limit, u32 idle_limit)
+{
+ struct xpc_registration *registration;
+
+
+ DBUG_ON(ch_number < 0 || ch_number >= XPC_NCHANNELS);
+ DBUG_ON(payload_size == 0 || nentries == 0);
+ DBUG_ON(func == NULL);
+ DBUG_ON(assigned_limit == 0 || idle_limit > assigned_limit);
+
+ registration = &xpc_registrations[ch_number];
+
+ if (down_interruptible(&registration->sema) != 0) {
+ return xpcInterrupted;
+ }
+
+ /* if XPC_CHANNEL_REGISTERED(ch_number) */
+ if (registration->func != NULL) {
+ up(&registration->sema);
+ return xpcAlreadyRegistered;
+ }
+
+ /* register the channel for connection */
+ registration->msg_size = XPC_MSG_SIZE(payload_size);
+ registration->nentries = nentries;
+ registration->assigned_limit = assigned_limit;
+ registration->idle_limit = idle_limit;
+ registration->key = key;
+ registration->func = func;
+
+ up(&registration->sema);
+
+ xpc_interface.connect(ch_number);
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Remove the registration for automatic connection of the specified channel
+ * when a partition comes up.
+ *
+ * Before returning this xpc_disconnect() will wait for all connections on the
+ * specified channel have been closed/torndown. So the caller can be assured
+ * that they will not be receiving any more callouts from XPC to their
+ * function registered via xpc_connect().
+ *
+ * Arguments:
+ *
+ * ch_number - channel # to unregister.
+ */
+void
+xpc_disconnect(int ch_number)
+{
+ struct xpc_registration *registration;
+
+
+ DBUG_ON(ch_number < 0 || ch_number >= XPC_NCHANNELS);
+
+ registration = &xpc_registrations[ch_number];
+
+ /*
+ * We've decided not to make this a down_interruptible(), since we
+ * figured XPC's users will just turn around and call xpc_disconnect()
+ * again anyways, so we might as well wait, if need be.
+ */
+ down(&registration->sema);
+
+ /* if !XPC_CHANNEL_REGISTERED(ch_number) */
+ if (registration->func == NULL) {
+ up(&registration->sema);
+ return;
+ }
+
+ /* remove the connection registration for the specified channel */
+ registration->func = NULL;
+ registration->key = NULL;
+ registration->nentries = 0;
+ registration->msg_size = 0;
+ registration->assigned_limit = 0;
+ registration->idle_limit = 0;
+
+ xpc_interface.disconnect(ch_number);
+
+ up(&registration->sema);
+
+ return;
+}
+
+
+int __init
+xp_init(void)
+{
+ int ret, ch_number;
+ u64 func_addr = *(u64 *) xp_nofault_PIOR;
+ u64 err_func_addr = *(u64 *) xp_error_PIOR;
+
+
+ if (!ia64_platform_is("sn2")) {
+ return -ENODEV;
+ }
+
+ /*
+ * Register a nofault code region which performs a cross-partition
+ * PIO read. If the PIO read times out, the MCA handler will consume
+ * the error and return to a kernel-provided instruction to indicate
+ * an error. This PIO read exists because it is guaranteed to timeout
+ * if the destination is down (AMO operations do not timeout on at
+ * least some CPUs on Shubs <= v1.2, which unfortunately we have to
+ * work around).
+ */
+ if ((ret = sn_register_nofault_code(func_addr, err_func_addr,
+ err_func_addr, 1, 1)) != 0) {
+ printk(KERN_ERR "XP: can't register nofault code, error=%d\n",
+ ret);
+ }
+ /*
+ * Setup the nofault PIO read target. (There is no special reason why
+ * SH_IPI_ACCESS was selected.)
+ */
+ if (is_shub2()) {
+ xp_nofault_PIOR_target = SH2_IPI_ACCESS0;
+ } else {
+ xp_nofault_PIOR_target = SH1_IPI_ACCESS;
+ }
+
+ /* initialize the connection registration semaphores */
+ for (ch_number = 0; ch_number < XPC_NCHANNELS; ch_number++) {
+ sema_init(&xpc_registrations[ch_number].sema, 1); /* mutex */
+ }
+
+ return 0;
+}
+module_init(xp_init);
+
+
+void __exit
+xp_exit(void)
+{
+ u64 func_addr = *(u64 *) xp_nofault_PIOR;
+ u64 err_func_addr = *(u64 *) xp_error_PIOR;
+
+
+ /* unregister the PIO read nofault code region */
+ (void) sn_register_nofault_code(func_addr, err_func_addr,
+ err_func_addr, 1, 0);
+}
+module_exit(xp_exit);
+
+
+MODULE_AUTHOR("Silicon Graphics, Inc.");
+MODULE_DESCRIPTION("Cross Partition (XP) base");
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(xp_nofault_PIOR);
+EXPORT_SYMBOL(xp_nofault_PIOR_target);
+EXPORT_SYMBOL(xpc_registrations);
+EXPORT_SYMBOL(xpc_interface);
+EXPORT_SYMBOL(xpc_clear_interface);
+EXPORT_SYMBOL(xpc_set_interface);
+EXPORT_SYMBOL(xpc_connect);
+EXPORT_SYMBOL(xpc_disconnect);
+
diff --git a/arch/ia64/sn/kernel/xp_nofault.S b/arch/ia64/sn/kernel/xp_nofault.S
new file mode 100644
index 000000000000..b772543053c9
--- /dev/null
+++ b/arch/ia64/sn/kernel/xp_nofault.S
@@ -0,0 +1,31 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2004-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+
+/*
+ * The xp_nofault_PIOR function takes a pointer to a remote PIO register
+ * and attempts to load and consume a value from it. This function
+ * will be registered as a nofault code block. In the event that the
+ * PIO read fails, the MCA handler will force the error to look
+ * corrected and vector to the xp_error_PIOR which will return an error.
+ *
+ * extern int xp_nofault_PIOR(void *remote_register);
+ */
+
+ .global xp_nofault_PIOR
+xp_nofault_PIOR:
+ mov r8=r0 // Stage a success return value
+ ld8.acq r9=[r32];; // PIO Read the specified register
+ adds r9=1,r9 // Add to force a consume
+ br.ret.sptk.many b0;; // Return success
+
+ .global xp_error_PIOR
+xp_error_PIOR:
+ mov r8=1 // Return value of 1
+ br.ret.sptk.many b0;; // Return failure
+
diff --git a/arch/ia64/sn/kernel/xpc.h b/arch/ia64/sn/kernel/xpc.h
new file mode 100644
index 000000000000..1a0aed8490d1
--- /dev/null
+++ b/arch/ia64/sn/kernel/xpc.h
@@ -0,0 +1,991 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2004-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+
+/*
+ * Cross Partition Communication (XPC) structures and macros.
+ */
+
+#ifndef _IA64_SN_KERNEL_XPC_H
+#define _IA64_SN_KERNEL_XPC_H
+
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/sysctl.h>
+#include <linux/device.h>
+#include <asm/pgtable.h>
+#include <asm/processor.h>
+#include <asm/sn/bte.h>
+#include <asm/sn/clksupport.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/mspec.h>
+#include <asm/sn/shub_mmr.h>
+#include <asm/sn/xp.h>
+
+
+/*
+ * XPC Version numbers consist of a major and minor number. XPC can always
+ * talk to versions with same major #, and never talk to versions with a
+ * different major #.
+ */
+#define _XPC_VERSION(_maj, _min) (((_maj) << 4) | ((_min) & 0xf))
+#define XPC_VERSION_MAJOR(_v) ((_v) >> 4)
+#define XPC_VERSION_MINOR(_v) ((_v) & 0xf)
+
+
+/*
+ * The next macros define word or bit representations for given
+ * C-brick nasid in either the SAL provided bit array representing
+ * nasids in the partition/machine or the AMO_t array used for
+ * inter-partition initiation communications.
+ *
+ * For SN2 machines, C-Bricks are alway even numbered NASIDs. As
+ * such, some space will be saved by insisting that nasid information
+ * passed from SAL always be packed for C-Bricks and the
+ * cross-partition interrupts use the same packing scheme.
+ */
+#define XPC_NASID_W_INDEX(_n) (((_n) / 64) / 2)
+#define XPC_NASID_B_INDEX(_n) (((_n) / 2) & (64 - 1))
+#define XPC_NASID_IN_ARRAY(_n, _p) ((_p)[XPC_NASID_W_INDEX(_n)] & \
+ (1UL << XPC_NASID_B_INDEX(_n)))
+#define XPC_NASID_FROM_W_B(_w, _b) (((_w) * 64 + (_b)) * 2)
+
+#define XPC_HB_DEFAULT_INTERVAL 5 /* incr HB every x secs */
+#define XPC_HB_CHECK_DEFAULT_TIMEOUT 20 /* check HB every x secs */
+
+/* define the process name of HB checker and the CPU it is pinned to */
+#define XPC_HB_CHECK_THREAD_NAME "xpc_hb"
+#define XPC_HB_CHECK_CPU 0
+
+/* define the process name of the discovery thread */
+#define XPC_DISCOVERY_THREAD_NAME "xpc_discovery"
+
+
+#define XPC_HB_ALLOWED(_p, _v) ((_v)->heartbeating_to_mask & (1UL << (_p)))
+#define XPC_ALLOW_HB(_p, _v) (_v)->heartbeating_to_mask |= (1UL << (_p))
+#define XPC_DISALLOW_HB(_p, _v) (_v)->heartbeating_to_mask &= (~(1UL << (_p)))
+
+
+/*
+ * Reserved Page provided by SAL.
+ *
+ * SAL provides one page per partition of reserved memory. When SAL
+ * initialization is complete, SAL_signature, SAL_version, partid,
+ * part_nasids, and mach_nasids are set.
+ *
+ * Note: Until vars_pa is set, the partition XPC code has not been initialized.
+ */
+struct xpc_rsvd_page {
+ u64 SAL_signature; /* SAL unique signature */
+ u64 SAL_version; /* SAL specified version */
+ u8 partid; /* partition ID from SAL */
+ u8 version;
+ u8 pad[6]; /* pad to u64 align */
+ u64 vars_pa;
+ u64 part_nasids[XP_NASID_MASK_WORDS] ____cacheline_aligned;
+ u64 mach_nasids[XP_NASID_MASK_WORDS] ____cacheline_aligned;
+};
+#define XPC_RP_VERSION _XPC_VERSION(1,0) /* version 1.0 of the reserved page */
+
+#define XPC_RSVD_PAGE_ALIGNED_SIZE \
+ (L1_CACHE_ALIGN(sizeof(struct xpc_rsvd_page)))
+
+
+/*
+ * Define the structures by which XPC variables can be exported to other
+ * partitions. (There are two: struct xpc_vars and struct xpc_vars_part)
+ */
+
+/*
+ * The following structure describes the partition generic variables
+ * needed by other partitions in order to properly initialize.
+ *
+ * struct xpc_vars version number also applies to struct xpc_vars_part.
+ * Changes to either structure and/or related functionality should be
+ * reflected by incrementing either the major or minor version numbers
+ * of struct xpc_vars.
+ */
+struct xpc_vars {
+ u8 version;
+ u64 heartbeat;
+ u64 heartbeating_to_mask;
+ u64 kdb_status; /* 0 = machine running */
+ int act_nasid;
+ int act_phys_cpuid;
+ u64 vars_part_pa;
+ u64 amos_page_pa; /* paddr of page of AMOs from MSPEC driver */
+ AMO_t *amos_page; /* vaddr of page of AMOs from MSPEC driver */
+ AMO_t *act_amos; /* pointer to the first activation AMO */
+};
+#define XPC_V_VERSION _XPC_VERSION(3,0) /* version 3.0 of the cross vars */
+
+#define XPC_VARS_ALIGNED_SIZE (L1_CACHE_ALIGN(sizeof(struct xpc_vars)))
+
+/*
+ * The following structure describes the per partition specific variables.
+ *
+ * An array of these structures, one per partition, will be defined. As a
+ * partition becomes active XPC will copy the array entry corresponding to
+ * itself from that partition. It is desirable that the size of this
+ * structure evenly divide into a cacheline, such that none of the entries
+ * in this array crosses a cacheline boundary. As it is now, each entry
+ * occupies half a cacheline.
+ */
+struct xpc_vars_part {
+ u64 magic;
+
+ u64 openclose_args_pa; /* physical address of open and close args */
+ u64 GPs_pa; /* physical address of Get/Put values */
+
+ u64 IPI_amo_pa; /* physical address of IPI AMO_t structure */
+ int IPI_nasid; /* nasid of where to send IPIs */
+ int IPI_phys_cpuid; /* physical CPU ID of where to send IPIs */
+
+ u8 nchannels; /* #of defined channels supported */
+
+ u8 reserved[23]; /* pad to a full 64 bytes */
+};
+
+/*
+ * The vars_part MAGIC numbers play a part in the first contact protocol.
+ *
+ * MAGIC1 indicates that the per partition specific variables for a remote
+ * partition have been initialized by this partition.
+ *
+ * MAGIC2 indicates that this partition has pulled the remote partititions
+ * per partition variables that pertain to this partition.
+ */
+#define XPC_VP_MAGIC1 0x0053524156435058L /* 'XPCVARS\0'L (little endian) */
+#define XPC_VP_MAGIC2 0x0073726176435058L /* 'XPCvars\0'L (little endian) */
+
+
+
+/*
+ * Functions registered by add_timer() or called by kernel_thread() only
+ * allow for a single 64-bit argument. The following macros can be used to
+ * pack and unpack two (32-bit, 16-bit or 8-bit) arguments into or out from
+ * the passed argument.
+ */
+#define XPC_PACK_ARGS(_arg1, _arg2) \
+ ((((u64) _arg1) & 0xffffffff) | \
+ ((((u64) _arg2) & 0xffffffff) << 32))
+
+#define XPC_UNPACK_ARG1(_args) (((u64) _args) & 0xffffffff)
+#define XPC_UNPACK_ARG2(_args) ((((u64) _args) >> 32) & 0xffffffff)
+
+
+
+/*
+ * Define a Get/Put value pair (pointers) used with a message queue.
+ */
+struct xpc_gp {
+ s64 get; /* Get value */
+ s64 put; /* Put value */
+};
+
+#define XPC_GP_SIZE \
+ L1_CACHE_ALIGN(sizeof(struct xpc_gp) * XPC_NCHANNELS)
+
+
+
+/*
+ * Define a structure that contains arguments associated with opening and
+ * closing a channel.
+ */
+struct xpc_openclose_args {
+ u16 reason; /* reason why channel is closing */
+ u16 msg_size; /* sizeof each message entry */
+ u16 remote_nentries; /* #of message entries in remote msg queue */
+ u16 local_nentries; /* #of message entries in local msg queue */
+ u64 local_msgqueue_pa; /* physical address of local message queue */
+};
+
+#define XPC_OPENCLOSE_ARGS_SIZE \
+ L1_CACHE_ALIGN(sizeof(struct xpc_openclose_args) * XPC_NCHANNELS)
+
+
+
+/* struct xpc_msg flags */
+
+#define XPC_M_DONE 0x01 /* msg has been received/consumed */
+#define XPC_M_READY 0x02 /* msg is ready to be sent */
+#define XPC_M_INTERRUPT 0x04 /* send interrupt when msg consumed */
+
+
+#define XPC_MSG_ADDRESS(_payload) \
+ ((struct xpc_msg *)((u8 *)(_payload) - XPC_MSG_PAYLOAD_OFFSET))
+
+
+
+/*
+ * Defines notify entry.
+ *
+ * This is used to notify a message's sender that their message was received
+ * and consumed by the intended recipient.
+ */
+struct xpc_notify {
+ struct semaphore sema; /* notify semaphore */
+ u8 type; /* type of notification */
+
+ /* the following two fields are only used if type == XPC_N_CALL */
+ xpc_notify_func func; /* user's notify function */
+ void *key; /* pointer to user's key */
+};
+
+/* struct xpc_notify type of notification */
+
+#define XPC_N_CALL 0x01 /* notify function provided by user */
+
+
+
+/*
+ * Define the structure that manages all the stuff required by a channel. In
+ * particular, they are used to manage the messages sent across the channel.
+ *
+ * This structure is private to a partition, and is NOT shared across the
+ * partition boundary.
+ *
+ * There is an array of these structures for each remote partition. It is
+ * allocated at the time a partition becomes active. The array contains one
+ * of these structures for each potential channel connection to that partition.
+ *
+ * Each of these structures manages two message queues (circular buffers).
+ * They are allocated at the time a channel connection is made. One of
+ * these message queues (local_msgqueue) holds the locally created messages
+ * that are destined for the remote partition. The other of these message
+ * queues (remote_msgqueue) is a locally cached copy of the remote partition's
+ * own local_msgqueue.
+ *
+ * The following is a description of the Get/Put pointers used to manage these
+ * two message queues. Consider the local_msgqueue to be on one partition
+ * and the remote_msgqueue to be its cached copy on another partition. A
+ * description of what each of the lettered areas contains is included.
+ *
+ *
+ * local_msgqueue remote_msgqueue
+ *
+ * |/////////| |/////////|
+ * w_remote_GP.get --> +---------+ |/////////|
+ * | F | |/////////|
+ * remote_GP.get --> +---------+ +---------+ <-- local_GP->get
+ * | | | |
+ * | | | E |
+ * | | | |
+ * | | +---------+ <-- w_local_GP.get
+ * | B | |/////////|
+ * | | |////D////|
+ * | | |/////////|
+ * | | +---------+ <-- w_remote_GP.put
+ * | | |////C////|
+ * local_GP->put --> +---------+ +---------+ <-- remote_GP.put
+ * | | |/////////|
+ * | A | |/////////|
+ * | | |/////////|
+ * w_local_GP.put --> +---------+ |/////////|
+ * |/////////| |/////////|
+ *
+ *
+ * ( remote_GP.[get|put] are cached copies of the remote
+ * partition's local_GP->[get|put], and thus their values can
+ * lag behind their counterparts on the remote partition. )
+ *
+ *
+ * A - Messages that have been allocated, but have not yet been sent to the
+ * remote partition.
+ *
+ * B - Messages that have been sent, but have not yet been acknowledged by the
+ * remote partition as having been received.
+ *
+ * C - Area that needs to be prepared for the copying of sent messages, by
+ * the clearing of the message flags of any previously received messages.
+ *
+ * D - Area into which sent messages are to be copied from the remote
+ * partition's local_msgqueue and then delivered to their intended
+ * recipients. [ To allow for a multi-message copy, another pointer
+ * (next_msg_to_pull) has been added to keep track of the next message
+ * number needing to be copied (pulled). It chases after w_remote_GP.put.
+ * Any messages lying between w_local_GP.get and next_msg_to_pull have
+ * been copied and are ready to be delivered. ]
+ *
+ * E - Messages that have been copied and delivered, but have not yet been
+ * acknowledged by the recipient as having been received.
+ *
+ * F - Messages that have been acknowledged, but XPC has not yet notified the
+ * sender that the message was received by its intended recipient.
+ * This is also an area that needs to be prepared for the allocating of
+ * new messages, by the clearing of the message flags of the acknowledged
+ * messages.
+ */
+struct xpc_channel {
+ partid_t partid; /* ID of remote partition connected */
+ spinlock_t lock; /* lock for updating this structure */
+ u32 flags; /* general flags */
+
+ enum xpc_retval reason; /* reason why channel is disconnect'g */
+ int reason_line; /* line# disconnect initiated from */
+
+ u16 number; /* channel # */
+
+ u16 msg_size; /* sizeof each msg entry */
+ u16 local_nentries; /* #of msg entries in local msg queue */
+ u16 remote_nentries; /* #of msg entries in remote msg queue*/
+
+ void *local_msgqueue_base; /* base address of kmalloc'd space */
+ struct xpc_msg *local_msgqueue; /* local message queue */
+ void *remote_msgqueue_base; /* base address of kmalloc'd space */
+ struct xpc_msg *remote_msgqueue;/* cached copy of remote partition's */
+ /* local message queue */
+ u64 remote_msgqueue_pa; /* phys addr of remote partition's */
+ /* local message queue */
+
+ atomic_t references; /* #of external references to queues */
+
+ atomic_t n_on_msg_allocate_wq; /* #on msg allocation wait queue */
+ wait_queue_head_t msg_allocate_wq; /* msg allocation wait queue */
+
+ /* queue of msg senders who want to be notified when msg received */
+
+ atomic_t n_to_notify; /* #of msg senders to notify */
+ struct xpc_notify *notify_queue;/* notify queue for messages sent */
+
+ xpc_channel_func func; /* user's channel function */
+ void *key; /* pointer to user's key */
+
+ struct semaphore msg_to_pull_sema; /* next msg to pull serialization */
+ struct semaphore teardown_sema; /* wait for teardown completion */
+
+ struct xpc_openclose_args *local_openclose_args; /* args passed on */
+ /* opening or closing of channel */
+
+ /* various flavors of local and remote Get/Put values */
+
+ struct xpc_gp *local_GP; /* local Get/Put values */
+ struct xpc_gp remote_GP; /* remote Get/Put values */
+ struct xpc_gp w_local_GP; /* working local Get/Put values */
+ struct xpc_gp w_remote_GP; /* working remote Get/Put values */
+ s64 next_msg_to_pull; /* Put value of next msg to pull */
+
+ /* kthread management related fields */
+
+// >>> rethink having kthreads_assigned_limit and kthreads_idle_limit; perhaps
+// >>> allow the assigned limit be unbounded and let the idle limit be dynamic
+// >>> dependent on activity over the last interval of time
+ atomic_t kthreads_assigned; /* #of kthreads assigned to channel */
+ u32 kthreads_assigned_limit; /* limit on #of kthreads assigned */
+ atomic_t kthreads_idle; /* #of kthreads idle waiting for work */
+ u32 kthreads_idle_limit; /* limit on #of kthreads idle */
+ atomic_t kthreads_active; /* #of kthreads actively working */
+ // >>> following field is temporary
+ u32 kthreads_created; /* total #of kthreads created */
+
+ wait_queue_head_t idle_wq; /* idle kthread wait queue */
+
+} ____cacheline_aligned;
+
+
+/* struct xpc_channel flags */
+
+#define XPC_C_WASCONNECTED 0x00000001 /* channel was connected */
+
+#define XPC_C_ROPENREPLY 0x00000002 /* remote open channel reply */
+#define XPC_C_OPENREPLY 0x00000004 /* local open channel reply */
+#define XPC_C_ROPENREQUEST 0x00000008 /* remote open channel request */
+#define XPC_C_OPENREQUEST 0x00000010 /* local open channel request */
+
+#define XPC_C_SETUP 0x00000020 /* channel's msgqueues are alloc'd */
+#define XPC_C_CONNECTCALLOUT 0x00000040 /* channel connected callout made */
+#define XPC_C_CONNECTED 0x00000080 /* local channel is connected */
+#define XPC_C_CONNECTING 0x00000100 /* channel is being connected */
+
+#define XPC_C_RCLOSEREPLY 0x00000200 /* remote close channel reply */
+#define XPC_C_CLOSEREPLY 0x00000400 /* local close channel reply */
+#define XPC_C_RCLOSEREQUEST 0x00000800 /* remote close channel request */
+#define XPC_C_CLOSEREQUEST 0x00001000 /* local close channel request */
+
+#define XPC_C_DISCONNECTED 0x00002000 /* channel is disconnected */
+#define XPC_C_DISCONNECTING 0x00004000 /* channel is being disconnected */
+
+
+
+/*
+ * Manages channels on a partition basis. There is one of these structures
+ * for each partition (a partition will never utilize the structure that
+ * represents itself).
+ */
+struct xpc_partition {
+
+ /* XPC HB infrastructure */
+
+ u64 remote_rp_pa; /* phys addr of partition's rsvd pg */
+ u64 remote_vars_pa; /* phys addr of partition's vars */
+ u64 remote_vars_part_pa; /* phys addr of partition's vars part */
+ u64 last_heartbeat; /* HB at last read */
+ u64 remote_amos_page_pa; /* phys addr of partition's amos page */
+ int remote_act_nasid; /* active part's act/deact nasid */
+ int remote_act_phys_cpuid; /* active part's act/deact phys cpuid */
+ u32 act_IRQ_rcvd; /* IRQs since activation */
+ spinlock_t act_lock; /* protect updating of act_state */
+ u8 act_state; /* from XPC HB viewpoint */
+ enum xpc_retval reason; /* reason partition is deactivating */
+ int reason_line; /* line# deactivation initiated from */
+ int reactivate_nasid; /* nasid in partition to reactivate */
+
+
+ /* XPC infrastructure referencing and teardown control */
+
+ u8 setup_state; /* infrastructure setup state */
+ wait_queue_head_t teardown_wq; /* kthread waiting to teardown infra */
+ atomic_t references; /* #of references to infrastructure */
+
+
+ /*
+ * NONE OF THE PRECEDING FIELDS OF THIS STRUCTURE WILL BE CLEARED WHEN
+ * XPC SETS UP THE NECESSARY INFRASTRUCTURE TO SUPPORT CROSS PARTITION
+ * COMMUNICATION. ALL OF THE FOLLOWING FIELDS WILL BE CLEARED. (THE
+ * 'nchannels' FIELD MUST BE THE FIRST OF THE FIELDS TO BE CLEARED.)
+ */
+
+
+ u8 nchannels; /* #of defined channels supported */
+ atomic_t nchannels_active; /* #of channels that are not DISCONNECTED */
+ struct xpc_channel *channels;/* array of channel structures */
+
+ void *local_GPs_base; /* base address of kmalloc'd space */
+ struct xpc_gp *local_GPs; /* local Get/Put values */
+ void *remote_GPs_base; /* base address of kmalloc'd space */
+ struct xpc_gp *remote_GPs;/* copy of remote partition's local Get/Put */
+ /* values */
+ u64 remote_GPs_pa; /* phys address of remote partition's local */
+ /* Get/Put values */
+
+
+ /* fields used to pass args when opening or closing a channel */
+
+ void *local_openclose_args_base; /* base address of kmalloc'd space */
+ struct xpc_openclose_args *local_openclose_args; /* local's args */
+ void *remote_openclose_args_base; /* base address of kmalloc'd space */
+ struct xpc_openclose_args *remote_openclose_args; /* copy of remote's */
+ /* args */
+ u64 remote_openclose_args_pa; /* phys addr of remote's args */
+
+
+ /* IPI sending, receiving and handling related fields */
+
+ int remote_IPI_nasid; /* nasid of where to send IPIs */
+ int remote_IPI_phys_cpuid; /* phys CPU ID of where to send IPIs */
+ AMO_t *remote_IPI_amo_va; /* address of remote IPI AMO_t structure */
+
+ AMO_t *local_IPI_amo_va; /* address of IPI AMO_t structure */
+ u64 local_IPI_amo; /* IPI amo flags yet to be handled */
+ char IPI_owner[8]; /* IPI owner's name */
+ struct timer_list dropped_IPI_timer; /* dropped IPI timer */
+
+ spinlock_t IPI_lock; /* IPI handler lock */
+
+
+ /* channel manager related fields */
+
+ atomic_t channel_mgr_requests; /* #of requests to activate chan mgr */
+ wait_queue_head_t channel_mgr_wq; /* channel mgr's wait queue */
+
+} ____cacheline_aligned;
+
+
+/* struct xpc_partition act_state values (for XPC HB) */
+
+#define XPC_P_INACTIVE 0x00 /* partition is not active */
+#define XPC_P_ACTIVATION_REQ 0x01 /* created thread to activate */
+#define XPC_P_ACTIVATING 0x02 /* activation thread started */
+#define XPC_P_ACTIVE 0x03 /* xpc_partition_up() was called */
+#define XPC_P_DEACTIVATING 0x04 /* partition deactivation initiated */
+
+
+#define XPC_DEACTIVATE_PARTITION(_p, _reason) \
+ xpc_deactivate_partition(__LINE__, (_p), (_reason))
+
+
+/* struct xpc_partition setup_state values */
+
+#define XPC_P_UNSET 0x00 /* infrastructure was never setup */
+#define XPC_P_SETUP 0x01 /* infrastructure is setup */
+#define XPC_P_WTEARDOWN 0x02 /* waiting to teardown infrastructure */
+#define XPC_P_TORNDOWN 0x03 /* infrastructure is torndown */
+
+
+/*
+ * struct xpc_partition IPI_timer #of seconds to wait before checking for
+ * dropped IPIs. These occur whenever an IPI amo write doesn't complete until
+ * after the IPI was received.
+ */
+#define XPC_P_DROPPED_IPI_WAIT (0.25 * HZ)
+
+
+#define XPC_PARTID(_p) ((partid_t) ((_p) - &xpc_partitions[0]))
+
+
+
+/* found in xp_main.c */
+extern struct xpc_registration xpc_registrations[];
+
+
+/* >>> found in xpc_main.c only */
+extern struct device *xpc_part;
+extern struct device *xpc_chan;
+extern irqreturn_t xpc_notify_IRQ_handler(int, void *, struct pt_regs *);
+extern void xpc_dropped_IPI_check(struct xpc_partition *);
+extern void xpc_activate_kthreads(struct xpc_channel *, int);
+extern void xpc_create_kthreads(struct xpc_channel *, int);
+extern void xpc_disconnect_wait(int);
+
+
+/* found in xpc_main.c and efi-xpc.c */
+extern void xpc_activate_partition(struct xpc_partition *);
+
+
+/* found in xpc_partition.c */
+extern int xpc_exiting;
+extern int xpc_hb_interval;
+extern int xpc_hb_check_interval;
+extern struct xpc_vars *xpc_vars;
+extern struct xpc_rsvd_page *xpc_rsvd_page;
+extern struct xpc_vars_part *xpc_vars_part;
+extern struct xpc_partition xpc_partitions[XP_MAX_PARTITIONS + 1];
+extern char xpc_remote_copy_buffer[];
+extern struct xpc_rsvd_page *xpc_rsvd_page_init(void);
+extern void xpc_allow_IPI_ops(void);
+extern void xpc_restrict_IPI_ops(void);
+extern int xpc_identify_act_IRQ_sender(void);
+extern enum xpc_retval xpc_mark_partition_active(struct xpc_partition *);
+extern void xpc_mark_partition_inactive(struct xpc_partition *);
+extern void xpc_discovery(void);
+extern void xpc_check_remote_hb(void);
+extern void xpc_deactivate_partition(const int, struct xpc_partition *,
+ enum xpc_retval);
+extern enum xpc_retval xpc_initiate_partid_to_nasids(partid_t, void *);
+
+
+/* found in xpc_channel.c */
+extern void xpc_initiate_connect(int);
+extern void xpc_initiate_disconnect(int);
+extern enum xpc_retval xpc_initiate_allocate(partid_t, int, u32, void **);
+extern enum xpc_retval xpc_initiate_send(partid_t, int, void *);
+extern enum xpc_retval xpc_initiate_send_notify(partid_t, int, void *,
+ xpc_notify_func, void *);
+extern void xpc_initiate_received(partid_t, int, void *);
+extern enum xpc_retval xpc_setup_infrastructure(struct xpc_partition *);
+extern enum xpc_retval xpc_pull_remote_vars_part(struct xpc_partition *);
+extern void xpc_process_channel_activity(struct xpc_partition *);
+extern void xpc_connected_callout(struct xpc_channel *);
+extern void xpc_deliver_msg(struct xpc_channel *);
+extern void xpc_disconnect_channel(const int, struct xpc_channel *,
+ enum xpc_retval, unsigned long *);
+extern void xpc_disconnected_callout(struct xpc_channel *);
+extern void xpc_partition_down(struct xpc_partition *, enum xpc_retval);
+extern void xpc_teardown_infrastructure(struct xpc_partition *);
+
+
+
+static inline void
+xpc_wakeup_channel_mgr(struct xpc_partition *part)
+{
+ if (atomic_inc_return(&part->channel_mgr_requests) == 1) {
+ wake_up(&part->channel_mgr_wq);
+ }
+}
+
+
+
+/*
+ * These next two inlines are used to keep us from tearing down a channel's
+ * msg queues while a thread may be referencing them.
+ */
+static inline void
+xpc_msgqueue_ref(struct xpc_channel *ch)
+{
+ atomic_inc(&ch->references);
+}
+
+static inline void
+xpc_msgqueue_deref(struct xpc_channel *ch)
+{
+ s32 refs = atomic_dec_return(&ch->references);
+
+ DBUG_ON(refs < 0);
+ if (refs == 0) {
+ xpc_wakeup_channel_mgr(&xpc_partitions[ch->partid]);
+ }
+}
+
+
+
+#define XPC_DISCONNECT_CHANNEL(_ch, _reason, _irqflgs) \
+ xpc_disconnect_channel(__LINE__, _ch, _reason, _irqflgs)
+
+
+/*
+ * These two inlines are used to keep us from tearing down a partition's
+ * setup infrastructure while a thread may be referencing it.
+ */
+static inline void
+xpc_part_deref(struct xpc_partition *part)
+{
+ s32 refs = atomic_dec_return(&part->references);
+
+
+ DBUG_ON(refs < 0);
+ if (refs == 0 && part->setup_state == XPC_P_WTEARDOWN) {
+ wake_up(&part->teardown_wq);
+ }
+}
+
+static inline int
+xpc_part_ref(struct xpc_partition *part)
+{
+ int setup;
+
+
+ atomic_inc(&part->references);
+ setup = (part->setup_state == XPC_P_SETUP);
+ if (!setup) {
+ xpc_part_deref(part);
+ }
+ return setup;
+}
+
+
+
+/*
+ * The following macro is to be used for the setting of the reason and
+ * reason_line fields in both the struct xpc_channel and struct xpc_partition
+ * structures.
+ */
+#define XPC_SET_REASON(_p, _reason, _line) \
+ { \
+ (_p)->reason = _reason; \
+ (_p)->reason_line = _line; \
+ }
+
+
+
+/*
+ * The following set of macros and inlines are used for the sending and
+ * receiving of IPIs (also known as IRQs). There are two flavors of IPIs,
+ * one that is associated with partition activity (SGI_XPC_ACTIVATE) and
+ * the other that is associated with channel activity (SGI_XPC_NOTIFY).
+ */
+
+static inline u64
+xpc_IPI_receive(AMO_t *amo)
+{
+ return FETCHOP_LOAD_OP(TO_AMO((u64) &amo->variable), FETCHOP_CLEAR);
+}
+
+
+static inline enum xpc_retval
+xpc_IPI_send(AMO_t *amo, u64 flag, int nasid, int phys_cpuid, int vector)
+{
+ int ret = 0;
+ unsigned long irq_flags;
+
+
+ local_irq_save(irq_flags);
+
+ FETCHOP_STORE_OP(TO_AMO((u64) &amo->variable), FETCHOP_OR, flag);
+ sn_send_IPI_phys(nasid, phys_cpuid, vector, 0);
+
+ /*
+ * We must always use the nofault function regardless of whether we
+ * are on a Shub 1.1 system or a Shub 1.2 slice 0xc processor. If we
+ * didn't, we'd never know that the other partition is down and would
+ * keep sending IPIs and AMOs to it until the heartbeat times out.
+ */
+ ret = xp_nofault_PIOR((u64 *) GLOBAL_MMR_ADDR(NASID_GET(&amo->variable),
+ xp_nofault_PIOR_target));
+
+ local_irq_restore(irq_flags);
+
+ return ((ret == 0) ? xpcSuccess : xpcPioReadError);
+}
+
+
+/*
+ * IPIs associated with SGI_XPC_ACTIVATE IRQ.
+ */
+
+/*
+ * Flag the appropriate AMO variable and send an IPI to the specified node.
+ */
+static inline void
+xpc_activate_IRQ_send(u64 amos_page, int from_nasid, int to_nasid,
+ int to_phys_cpuid)
+{
+ int w_index = XPC_NASID_W_INDEX(from_nasid);
+ int b_index = XPC_NASID_B_INDEX(from_nasid);
+ AMO_t *amos = (AMO_t *) __va(amos_page +
+ (XP_MAX_PARTITIONS * sizeof(AMO_t)));
+
+
+ (void) xpc_IPI_send(&amos[w_index], (1UL << b_index), to_nasid,
+ to_phys_cpuid, SGI_XPC_ACTIVATE);
+}
+
+static inline void
+xpc_IPI_send_activate(struct xpc_vars *vars)
+{
+ xpc_activate_IRQ_send(vars->amos_page_pa, cnodeid_to_nasid(0),
+ vars->act_nasid, vars->act_phys_cpuid);
+}
+
+static inline void
+xpc_IPI_send_activated(struct xpc_partition *part)
+{
+ xpc_activate_IRQ_send(part->remote_amos_page_pa, cnodeid_to_nasid(0),
+ part->remote_act_nasid, part->remote_act_phys_cpuid);
+}
+
+static inline void
+xpc_IPI_send_reactivate(struct xpc_partition *part)
+{
+ xpc_activate_IRQ_send(xpc_vars->amos_page_pa, part->reactivate_nasid,
+ xpc_vars->act_nasid, xpc_vars->act_phys_cpuid);
+}
+
+
+/*
+ * IPIs associated with SGI_XPC_NOTIFY IRQ.
+ */
+
+/*
+ * Send an IPI to the remote partition that is associated with the
+ * specified channel.
+ */
+#define XPC_NOTIFY_IRQ_SEND(_ch, _ipi_f, _irq_f) \
+ xpc_notify_IRQ_send(_ch, _ipi_f, #_ipi_f, _irq_f)
+
+static inline void
+xpc_notify_IRQ_send(struct xpc_channel *ch, u8 ipi_flag, char *ipi_flag_string,
+ unsigned long *irq_flags)
+{
+ struct xpc_partition *part = &xpc_partitions[ch->partid];
+ enum xpc_retval ret;
+
+
+ if (likely(part->act_state != XPC_P_DEACTIVATING)) {
+ ret = xpc_IPI_send(part->remote_IPI_amo_va,
+ (u64) ipi_flag << (ch->number * 8),
+ part->remote_IPI_nasid,
+ part->remote_IPI_phys_cpuid,
+ SGI_XPC_NOTIFY);
+ dev_dbg(xpc_chan, "%s sent to partid=%d, channel=%d, ret=%d\n",
+ ipi_flag_string, ch->partid, ch->number, ret);
+ if (unlikely(ret != xpcSuccess)) {
+ if (irq_flags != NULL) {
+ spin_unlock_irqrestore(&ch->lock, *irq_flags);
+ }
+ XPC_DEACTIVATE_PARTITION(part, ret);
+ if (irq_flags != NULL) {
+ spin_lock_irqsave(&ch->lock, *irq_flags);
+ }
+ }
+ }
+}
+
+
+/*
+ * Make it look like the remote partition, which is associated with the
+ * specified channel, sent us an IPI. This faked IPI will be handled
+ * by xpc_dropped_IPI_check().
+ */
+#define XPC_NOTIFY_IRQ_SEND_LOCAL(_ch, _ipi_f) \
+ xpc_notify_IRQ_send_local(_ch, _ipi_f, #_ipi_f)
+
+static inline void
+xpc_notify_IRQ_send_local(struct xpc_channel *ch, u8 ipi_flag,
+ char *ipi_flag_string)
+{
+ struct xpc_partition *part = &xpc_partitions[ch->partid];
+
+
+ FETCHOP_STORE_OP(TO_AMO((u64) &part->local_IPI_amo_va->variable),
+ FETCHOP_OR, ((u64) ipi_flag << (ch->number * 8)));
+ dev_dbg(xpc_chan, "%s sent local from partid=%d, channel=%d\n",
+ ipi_flag_string, ch->partid, ch->number);
+}
+
+
+/*
+ * The sending and receiving of IPIs includes the setting of an AMO variable
+ * to indicate the reason the IPI was sent. The 64-bit variable is divided
+ * up into eight bytes, ordered from right to left. Byte zero pertains to
+ * channel 0, byte one to channel 1, and so on. Each byte is described by
+ * the following IPI flags.
+ */
+
+#define XPC_IPI_CLOSEREQUEST 0x01
+#define XPC_IPI_CLOSEREPLY 0x02
+#define XPC_IPI_OPENREQUEST 0x04
+#define XPC_IPI_OPENREPLY 0x08
+#define XPC_IPI_MSGREQUEST 0x10
+
+
+/* given an AMO variable and a channel#, get its associated IPI flags */
+#define XPC_GET_IPI_FLAGS(_amo, _c) ((u8) (((_amo) >> ((_c) * 8)) & 0xff))
+
+#define XPC_ANY_OPENCLOSE_IPI_FLAGS_SET(_amo) ((_amo) & 0x0f0f0f0f0f0f0f0f)
+#define XPC_ANY_MSG_IPI_FLAGS_SET(_amo) ((_amo) & 0x1010101010101010)
+
+
+static inline void
+xpc_IPI_send_closerequest(struct xpc_channel *ch, unsigned long *irq_flags)
+{
+ struct xpc_openclose_args *args = ch->local_openclose_args;
+
+
+ args->reason = ch->reason;
+
+ XPC_NOTIFY_IRQ_SEND(ch, XPC_IPI_CLOSEREQUEST, irq_flags);
+}
+
+static inline void
+xpc_IPI_send_closereply(struct xpc_channel *ch, unsigned long *irq_flags)
+{
+ XPC_NOTIFY_IRQ_SEND(ch, XPC_IPI_CLOSEREPLY, irq_flags);
+}
+
+static inline void
+xpc_IPI_send_openrequest(struct xpc_channel *ch, unsigned long *irq_flags)
+{
+ struct xpc_openclose_args *args = ch->local_openclose_args;
+
+
+ args->msg_size = ch->msg_size;
+ args->local_nentries = ch->local_nentries;
+
+ XPC_NOTIFY_IRQ_SEND(ch, XPC_IPI_OPENREQUEST, irq_flags);
+}
+
+static inline void
+xpc_IPI_send_openreply(struct xpc_channel *ch, unsigned long *irq_flags)
+{
+ struct xpc_openclose_args *args = ch->local_openclose_args;
+
+
+ args->remote_nentries = ch->remote_nentries;
+ args->local_nentries = ch->local_nentries;
+ args->local_msgqueue_pa = __pa(ch->local_msgqueue);
+
+ XPC_NOTIFY_IRQ_SEND(ch, XPC_IPI_OPENREPLY, irq_flags);
+}
+
+static inline void
+xpc_IPI_send_msgrequest(struct xpc_channel *ch)
+{
+ XPC_NOTIFY_IRQ_SEND(ch, XPC_IPI_MSGREQUEST, NULL);
+}
+
+static inline void
+xpc_IPI_send_local_msgrequest(struct xpc_channel *ch)
+{
+ XPC_NOTIFY_IRQ_SEND_LOCAL(ch, XPC_IPI_MSGREQUEST);
+}
+
+
+/*
+ * Memory for XPC's AMO variables is allocated by the MSPEC driver. These
+ * pages are located in the lowest granule. The lowest granule uses 4k pages
+ * for cached references and an alternate TLB handler to never provide a
+ * cacheable mapping for the entire region. This will prevent speculative
+ * reading of cached copies of our lines from being issued which will cause
+ * a PI FSB Protocol error to be generated by the SHUB. For XPC, we need 64
+ * (XP_MAX_PARTITIONS) AMO variables for message notification (xpc_main.c)
+ * and an additional 16 AMO variables for partition activation (xpc_hb.c).
+ */
+static inline AMO_t *
+xpc_IPI_init(partid_t partid)
+{
+ AMO_t *part_amo = xpc_vars->amos_page + partid;
+
+
+ xpc_IPI_receive(part_amo);
+ return part_amo;
+}
+
+
+
+static inline enum xpc_retval
+xpc_map_bte_errors(bte_result_t error)
+{
+ switch (error) {
+ case BTE_SUCCESS: return xpcSuccess;
+ case BTEFAIL_DIR: return xpcBteDirectoryError;
+ case BTEFAIL_POISON: return xpcBtePoisonError;
+ case BTEFAIL_WERR: return xpcBteWriteError;
+ case BTEFAIL_ACCESS: return xpcBteAccessError;
+ case BTEFAIL_PWERR: return xpcBtePWriteError;
+ case BTEFAIL_PRERR: return xpcBtePReadError;
+ case BTEFAIL_TOUT: return xpcBteTimeOutError;
+ case BTEFAIL_XTERR: return xpcBteXtalkError;
+ case BTEFAIL_NOTAVAIL: return xpcBteNotAvailable;
+ default: return xpcBteUnmappedError;
+ }
+}
+
+
+
+static inline void *
+xpc_kmalloc_cacheline_aligned(size_t size, int flags, void **base)
+{
+ /* see if kmalloc will give us cachline aligned memory by default */
+ *base = kmalloc(size, flags);
+ if (*base == NULL) {
+ return NULL;
+ }
+ if ((u64) *base == L1_CACHE_ALIGN((u64) *base)) {
+ return *base;
+ }
+ kfree(*base);
+
+ /* nope, we'll have to do it ourselves */
+ *base = kmalloc(size + L1_CACHE_BYTES, flags);
+ if (*base == NULL) {
+ return NULL;
+ }
+ return (void *) L1_CACHE_ALIGN((u64) *base);
+}
+
+
+/*
+ * Check to see if there is any channel activity to/from the specified
+ * partition.
+ */
+static inline void
+xpc_check_for_channel_activity(struct xpc_partition *part)
+{
+ u64 IPI_amo;
+ unsigned long irq_flags;
+
+
+ IPI_amo = xpc_IPI_receive(part->local_IPI_amo_va);
+ if (IPI_amo == 0) {
+ return;
+ }
+
+ spin_lock_irqsave(&part->IPI_lock, irq_flags);
+ part->local_IPI_amo |= IPI_amo;
+ spin_unlock_irqrestore(&part->IPI_lock, irq_flags);
+
+ dev_dbg(xpc_chan, "received IPI from partid=%d, IPI_amo=0x%lx\n",
+ XPC_PARTID(part), IPI_amo);
+
+ xpc_wakeup_channel_mgr(part);
+}
+
+
+#endif /* _IA64_SN_KERNEL_XPC_H */
+
diff --git a/arch/ia64/sn/kernel/xpc_channel.c b/arch/ia64/sn/kernel/xpc_channel.c
new file mode 100644
index 000000000000..0bf6fbcc46d2
--- /dev/null
+++ b/arch/ia64/sn/kernel/xpc_channel.c
@@ -0,0 +1,2297 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2004-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+
+/*
+ * Cross Partition Communication (XPC) channel support.
+ *
+ * This is the part of XPC that manages the channels and
+ * sends/receives messages across them to/from other partitions.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/cache.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <asm/sn/bte.h>
+#include <asm/sn/sn_sal.h>
+#include "xpc.h"
+
+
+/*
+ * Set up the initial values for the XPartition Communication channels.
+ */
+static void
+xpc_initialize_channels(struct xpc_partition *part, partid_t partid)
+{
+ int ch_number;
+ struct xpc_channel *ch;
+
+
+ for (ch_number = 0; ch_number < part->nchannels; ch_number++) {
+ ch = &part->channels[ch_number];
+
+ ch->partid = partid;
+ ch->number = ch_number;
+ ch->flags = XPC_C_DISCONNECTED;
+
+ ch->local_GP = &part->local_GPs[ch_number];
+ ch->local_openclose_args =
+ &part->local_openclose_args[ch_number];
+
+ atomic_set(&ch->kthreads_assigned, 0);
+ atomic_set(&ch->kthreads_idle, 0);
+ atomic_set(&ch->kthreads_active, 0);
+
+ atomic_set(&ch->references, 0);
+ atomic_set(&ch->n_to_notify, 0);
+
+ spin_lock_init(&ch->lock);
+ sema_init(&ch->msg_to_pull_sema, 1); /* mutex */
+
+ atomic_set(&ch->n_on_msg_allocate_wq, 0);
+ init_waitqueue_head(&ch->msg_allocate_wq);
+ init_waitqueue_head(&ch->idle_wq);
+ }
+}
+
+
+/*
+ * Setup the infrastructure necessary to support XPartition Communication
+ * between the specified remote partition and the local one.
+ */
+enum xpc_retval
+xpc_setup_infrastructure(struct xpc_partition *part)
+{
+ int ret;
+ struct timer_list *timer;
+ partid_t partid = XPC_PARTID(part);
+
+
+ /*
+ * Zero out MOST of the entry for this partition. Only the fields
+ * starting with `nchannels' will be zeroed. The preceding fields must
+ * remain `viable' across partition ups and downs, since they may be
+ * referenced during this memset() operation.
+ */
+ memset(&part->nchannels, 0, sizeof(struct xpc_partition) -
+ offsetof(struct xpc_partition, nchannels));
+
+ /*
+ * Allocate all of the channel structures as a contiguous chunk of
+ * memory.
+ */
+ part->channels = kmalloc(sizeof(struct xpc_channel) * XPC_NCHANNELS,
+ GFP_KERNEL);
+ if (part->channels == NULL) {
+ dev_err(xpc_chan, "can't get memory for channels\n");
+ return xpcNoMemory;
+ }
+ memset(part->channels, 0, sizeof(struct xpc_channel) * XPC_NCHANNELS);
+
+ part->nchannels = XPC_NCHANNELS;
+
+
+ /* allocate all the required GET/PUT values */
+
+ part->local_GPs = xpc_kmalloc_cacheline_aligned(XPC_GP_SIZE,
+ GFP_KERNEL, &part->local_GPs_base);
+ if (part->local_GPs == NULL) {
+ kfree(part->channels);
+ part->channels = NULL;
+ dev_err(xpc_chan, "can't get memory for local get/put "
+ "values\n");
+ return xpcNoMemory;
+ }
+ memset(part->local_GPs, 0, XPC_GP_SIZE);
+
+ part->remote_GPs = xpc_kmalloc_cacheline_aligned(XPC_GP_SIZE,
+ GFP_KERNEL, &part->remote_GPs_base);
+ if (part->remote_GPs == NULL) {
+ kfree(part->channels);
+ part->channels = NULL;
+ kfree(part->local_GPs_base);
+ part->local_GPs = NULL;
+ dev_err(xpc_chan, "can't get memory for remote get/put "
+ "values\n");
+ return xpcNoMemory;
+ }
+ memset(part->remote_GPs, 0, XPC_GP_SIZE);
+
+
+ /* allocate all the required open and close args */
+
+ part->local_openclose_args = xpc_kmalloc_cacheline_aligned(
+ XPC_OPENCLOSE_ARGS_SIZE, GFP_KERNEL,
+ &part->local_openclose_args_base);
+ if (part->local_openclose_args == NULL) {
+ kfree(part->channels);
+ part->channels = NULL;
+ kfree(part->local_GPs_base);
+ part->local_GPs = NULL;
+ kfree(part->remote_GPs_base);
+ part->remote_GPs = NULL;
+ dev_err(xpc_chan, "can't get memory for local connect args\n");
+ return xpcNoMemory;
+ }
+ memset(part->local_openclose_args, 0, XPC_OPENCLOSE_ARGS_SIZE);
+
+ part->remote_openclose_args = xpc_kmalloc_cacheline_aligned(
+ XPC_OPENCLOSE_ARGS_SIZE, GFP_KERNEL,
+ &part->remote_openclose_args_base);
+ if (part->remote_openclose_args == NULL) {
+ kfree(part->channels);
+ part->channels = NULL;
+ kfree(part->local_GPs_base);
+ part->local_GPs = NULL;
+ kfree(part->remote_GPs_base);
+ part->remote_GPs = NULL;
+ kfree(part->local_openclose_args_base);
+ part->local_openclose_args = NULL;
+ dev_err(xpc_chan, "can't get memory for remote connect args\n");
+ return xpcNoMemory;
+ }
+ memset(part->remote_openclose_args, 0, XPC_OPENCLOSE_ARGS_SIZE);
+
+
+ xpc_initialize_channels(part, partid);
+
+ atomic_set(&part->nchannels_active, 0);
+
+
+ /* local_IPI_amo were set to 0 by an earlier memset() */
+
+ /* Initialize this partitions AMO_t structure */
+ part->local_IPI_amo_va = xpc_IPI_init(partid);
+
+ spin_lock_init(&part->IPI_lock);
+
+ atomic_set(&part->channel_mgr_requests, 1);
+ init_waitqueue_head(&part->channel_mgr_wq);
+
+ sprintf(part->IPI_owner, "xpc%02d", partid);
+ ret = request_irq(SGI_XPC_NOTIFY, xpc_notify_IRQ_handler, SA_SHIRQ,
+ part->IPI_owner, (void *) (u64) partid);
+ if (ret != 0) {
+ kfree(part->channels);
+ part->channels = NULL;
+ kfree(part->local_GPs_base);
+ part->local_GPs = NULL;
+ kfree(part->remote_GPs_base);
+ part->remote_GPs = NULL;
+ kfree(part->local_openclose_args_base);
+ part->local_openclose_args = NULL;
+ kfree(part->remote_openclose_args_base);
+ part->remote_openclose_args = NULL;
+ dev_err(xpc_chan, "can't register NOTIFY IRQ handler, "
+ "errno=%d\n", -ret);
+ return xpcLackOfResources;
+ }
+
+ /* Setup a timer to check for dropped IPIs */
+ timer = &part->dropped_IPI_timer;
+ init_timer(timer);
+ timer->function = (void (*)(unsigned long)) xpc_dropped_IPI_check;
+ timer->data = (unsigned long) part;
+ timer->expires = jiffies + XPC_P_DROPPED_IPI_WAIT;
+ add_timer(timer);
+
+ /*
+ * With the setting of the partition setup_state to XPC_P_SETUP, we're
+ * declaring that this partition is ready to go.
+ */
+ (volatile u8) part->setup_state = XPC_P_SETUP;
+
+
+ /*
+ * Setup the per partition specific variables required by the
+ * remote partition to establish channel connections with us.
+ *
+ * The setting of the magic # indicates that these per partition
+ * specific variables are ready to be used.
+ */
+ xpc_vars_part[partid].GPs_pa = __pa(part->local_GPs);
+ xpc_vars_part[partid].openclose_args_pa =
+ __pa(part->local_openclose_args);
+ xpc_vars_part[partid].IPI_amo_pa = __pa(part->local_IPI_amo_va);
+ xpc_vars_part[partid].IPI_nasid = cpuid_to_nasid(smp_processor_id());
+ xpc_vars_part[partid].IPI_phys_cpuid =
+ cpu_physical_id(smp_processor_id());
+ xpc_vars_part[partid].nchannels = part->nchannels;
+ (volatile u64) xpc_vars_part[partid].magic = XPC_VP_MAGIC1;
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Create a wrapper that hides the underlying mechanism for pulling a cacheline
+ * (or multiple cachelines) from a remote partition.
+ *
+ * src must be a cacheline aligned physical address on the remote partition.
+ * dst must be a cacheline aligned virtual address on this partition.
+ * cnt must be an cacheline sized
+ */
+static enum xpc_retval
+xpc_pull_remote_cachelines(struct xpc_partition *part, void *dst,
+ const void *src, size_t cnt)
+{
+ bte_result_t bte_ret;
+
+
+ DBUG_ON((u64) src != L1_CACHE_ALIGN((u64) src));
+ DBUG_ON((u64) dst != L1_CACHE_ALIGN((u64) dst));
+ DBUG_ON(cnt != L1_CACHE_ALIGN(cnt));
+
+ if (part->act_state == XPC_P_DEACTIVATING) {
+ return part->reason;
+ }
+
+ bte_ret = xp_bte_copy((u64) src, (u64) ia64_tpa((u64) dst),
+ (u64) cnt, (BTE_NORMAL | BTE_WACQUIRE), NULL);
+ if (bte_ret == BTE_SUCCESS) {
+ return xpcSuccess;
+ }
+
+ dev_dbg(xpc_chan, "xp_bte_copy() from partition %d failed, ret=%d\n",
+ XPC_PARTID(part), bte_ret);
+
+ return xpc_map_bte_errors(bte_ret);
+}
+
+
+/*
+ * Pull the remote per partititon specific variables from the specified
+ * partition.
+ */
+enum xpc_retval
+xpc_pull_remote_vars_part(struct xpc_partition *part)
+{
+ u8 buffer[L1_CACHE_BYTES * 2];
+ struct xpc_vars_part *pulled_entry_cacheline =
+ (struct xpc_vars_part *) L1_CACHE_ALIGN((u64) buffer);
+ struct xpc_vars_part *pulled_entry;
+ u64 remote_entry_cacheline_pa, remote_entry_pa;
+ partid_t partid = XPC_PARTID(part);
+ enum xpc_retval ret;
+
+
+ /* pull the cacheline that contains the variables we're interested in */
+
+ DBUG_ON(part->remote_vars_part_pa !=
+ L1_CACHE_ALIGN(part->remote_vars_part_pa));
+ DBUG_ON(sizeof(struct xpc_vars_part) != L1_CACHE_BYTES / 2);
+
+ remote_entry_pa = part->remote_vars_part_pa +
+ sn_partition_id * sizeof(struct xpc_vars_part);
+
+ remote_entry_cacheline_pa = (remote_entry_pa & ~(L1_CACHE_BYTES - 1));
+
+ pulled_entry = (struct xpc_vars_part *) ((u64) pulled_entry_cacheline +
+ (remote_entry_pa & (L1_CACHE_BYTES - 1)));
+
+ ret = xpc_pull_remote_cachelines(part, pulled_entry_cacheline,
+ (void *) remote_entry_cacheline_pa,
+ L1_CACHE_BYTES);
+ if (ret != xpcSuccess) {
+ dev_dbg(xpc_chan, "failed to pull XPC vars_part from "
+ "partition %d, ret=%d\n", partid, ret);
+ return ret;
+ }
+
+
+ /* see if they've been set up yet */
+
+ if (pulled_entry->magic != XPC_VP_MAGIC1 &&
+ pulled_entry->magic != XPC_VP_MAGIC2) {
+
+ if (pulled_entry->magic != 0) {
+ dev_dbg(xpc_chan, "partition %d's XPC vars_part for "
+ "partition %d has bad magic value (=0x%lx)\n",
+ partid, sn_partition_id, pulled_entry->magic);
+ return xpcBadMagic;
+ }
+
+ /* they've not been initialized yet */
+ return xpcRetry;
+ }
+
+ if (xpc_vars_part[partid].magic == XPC_VP_MAGIC1) {
+
+ /* validate the variables */
+
+ if (pulled_entry->GPs_pa == 0 ||
+ pulled_entry->openclose_args_pa == 0 ||
+ pulled_entry->IPI_amo_pa == 0) {
+
+ dev_err(xpc_chan, "partition %d's XPC vars_part for "
+ "partition %d are not valid\n", partid,
+ sn_partition_id);
+ return xpcInvalidAddress;
+ }
+
+ /* the variables we imported look to be valid */
+
+ part->remote_GPs_pa = pulled_entry->GPs_pa;
+ part->remote_openclose_args_pa =
+ pulled_entry->openclose_args_pa;
+ part->remote_IPI_amo_va =
+ (AMO_t *) __va(pulled_entry->IPI_amo_pa);
+ part->remote_IPI_nasid = pulled_entry->IPI_nasid;
+ part->remote_IPI_phys_cpuid = pulled_entry->IPI_phys_cpuid;
+
+ if (part->nchannels > pulled_entry->nchannels) {
+ part->nchannels = pulled_entry->nchannels;
+ }
+
+ /* let the other side know that we've pulled their variables */
+
+ (volatile u64) xpc_vars_part[partid].magic = XPC_VP_MAGIC2;
+ }
+
+ if (pulled_entry->magic == XPC_VP_MAGIC1) {
+ return xpcRetry;
+ }
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Get the IPI flags and pull the openclose args and/or remote GPs as needed.
+ */
+static u64
+xpc_get_IPI_flags(struct xpc_partition *part)
+{
+ unsigned long irq_flags;
+ u64 IPI_amo;
+ enum xpc_retval ret;
+
+
+ /*
+ * See if there are any IPI flags to be handled.
+ */
+
+ spin_lock_irqsave(&part->IPI_lock, irq_flags);
+ if ((IPI_amo = part->local_IPI_amo) != 0) {
+ part->local_IPI_amo = 0;
+ }
+ spin_unlock_irqrestore(&part->IPI_lock, irq_flags);
+
+
+ if (XPC_ANY_OPENCLOSE_IPI_FLAGS_SET(IPI_amo)) {
+ ret = xpc_pull_remote_cachelines(part,
+ part->remote_openclose_args,
+ (void *) part->remote_openclose_args_pa,
+ XPC_OPENCLOSE_ARGS_SIZE);
+ if (ret != xpcSuccess) {
+ XPC_DEACTIVATE_PARTITION(part, ret);
+
+ dev_dbg(xpc_chan, "failed to pull openclose args from "
+ "partition %d, ret=%d\n", XPC_PARTID(part),
+ ret);
+
+ /* don't bother processing IPIs anymore */
+ IPI_amo = 0;
+ }
+ }
+
+ if (XPC_ANY_MSG_IPI_FLAGS_SET(IPI_amo)) {
+ ret = xpc_pull_remote_cachelines(part, part->remote_GPs,
+ (void *) part->remote_GPs_pa,
+ XPC_GP_SIZE);
+ if (ret != xpcSuccess) {
+ XPC_DEACTIVATE_PARTITION(part, ret);
+
+ dev_dbg(xpc_chan, "failed to pull GPs from partition "
+ "%d, ret=%d\n", XPC_PARTID(part), ret);
+
+ /* don't bother processing IPIs anymore */
+ IPI_amo = 0;
+ }
+ }
+
+ return IPI_amo;
+}
+
+
+/*
+ * Allocate the local message queue and the notify queue.
+ */
+static enum xpc_retval
+xpc_allocate_local_msgqueue(struct xpc_channel *ch)
+{
+ unsigned long irq_flags;
+ int nentries;
+ size_t nbytes;
+
+
+ // >>> may want to check for ch->flags & XPC_C_DISCONNECTING between
+ // >>> iterations of the for-loop, bail if set?
+
+ // >>> should we impose a minumum #of entries? like 4 or 8?
+ for (nentries = ch->local_nentries; nentries > 0; nentries--) {
+
+ nbytes = nentries * ch->msg_size;
+ ch->local_msgqueue = xpc_kmalloc_cacheline_aligned(nbytes,
+ (GFP_KERNEL | GFP_DMA),
+ &ch->local_msgqueue_base);
+ if (ch->local_msgqueue == NULL) {
+ continue;
+ }
+ memset(ch->local_msgqueue, 0, nbytes);
+
+ nbytes = nentries * sizeof(struct xpc_notify);
+ ch->notify_queue = kmalloc(nbytes, (GFP_KERNEL | GFP_DMA));
+ if (ch->notify_queue == NULL) {
+ kfree(ch->local_msgqueue_base);
+ ch->local_msgqueue = NULL;
+ continue;
+ }
+ memset(ch->notify_queue, 0, nbytes);
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+ if (nentries < ch->local_nentries) {
+ dev_dbg(xpc_chan, "nentries=%d local_nentries=%d, "
+ "partid=%d, channel=%d\n", nentries,
+ ch->local_nentries, ch->partid, ch->number);
+
+ ch->local_nentries = nentries;
+ }
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return xpcSuccess;
+ }
+
+ dev_dbg(xpc_chan, "can't get memory for local message queue and notify "
+ "queue, partid=%d, channel=%d\n", ch->partid, ch->number);
+ return xpcNoMemory;
+}
+
+
+/*
+ * Allocate the cached remote message queue.
+ */
+static enum xpc_retval
+xpc_allocate_remote_msgqueue(struct xpc_channel *ch)
+{
+ unsigned long irq_flags;
+ int nentries;
+ size_t nbytes;
+
+
+ DBUG_ON(ch->remote_nentries <= 0);
+
+ // >>> may want to check for ch->flags & XPC_C_DISCONNECTING between
+ // >>> iterations of the for-loop, bail if set?
+
+ // >>> should we impose a minumum #of entries? like 4 or 8?
+ for (nentries = ch->remote_nentries; nentries > 0; nentries--) {
+
+ nbytes = nentries * ch->msg_size;
+ ch->remote_msgqueue = xpc_kmalloc_cacheline_aligned(nbytes,
+ (GFP_KERNEL | GFP_DMA),
+ &ch->remote_msgqueue_base);
+ if (ch->remote_msgqueue == NULL) {
+ continue;
+ }
+ memset(ch->remote_msgqueue, 0, nbytes);
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+ if (nentries < ch->remote_nentries) {
+ dev_dbg(xpc_chan, "nentries=%d remote_nentries=%d, "
+ "partid=%d, channel=%d\n", nentries,
+ ch->remote_nentries, ch->partid, ch->number);
+
+ ch->remote_nentries = nentries;
+ }
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return xpcSuccess;
+ }
+
+ dev_dbg(xpc_chan, "can't get memory for cached remote message queue, "
+ "partid=%d, channel=%d\n", ch->partid, ch->number);
+ return xpcNoMemory;
+}
+
+
+/*
+ * Allocate message queues and other stuff associated with a channel.
+ *
+ * Note: Assumes all of the channel sizes are filled in.
+ */
+static enum xpc_retval
+xpc_allocate_msgqueues(struct xpc_channel *ch)
+{
+ unsigned long irq_flags;
+ int i;
+ enum xpc_retval ret;
+
+
+ DBUG_ON(ch->flags & XPC_C_SETUP);
+
+ if ((ret = xpc_allocate_local_msgqueue(ch)) != xpcSuccess) {
+ return ret;
+ }
+
+ if ((ret = xpc_allocate_remote_msgqueue(ch)) != xpcSuccess) {
+ kfree(ch->local_msgqueue_base);
+ ch->local_msgqueue = NULL;
+ kfree(ch->notify_queue);
+ ch->notify_queue = NULL;
+ return ret;
+ }
+
+ for (i = 0; i < ch->local_nentries; i++) {
+ /* use a semaphore as an event wait queue */
+ sema_init(&ch->notify_queue[i].sema, 0);
+ }
+
+ sema_init(&ch->teardown_sema, 0); /* event wait */
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+ ch->flags |= XPC_C_SETUP;
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Process a connect message from a remote partition.
+ *
+ * Note: xpc_process_connect() is expecting to be called with the
+ * spin_lock_irqsave held and will leave it locked upon return.
+ */
+static void
+xpc_process_connect(struct xpc_channel *ch, unsigned long *irq_flags)
+{
+ enum xpc_retval ret;
+
+
+ DBUG_ON(!spin_is_locked(&ch->lock));
+
+ if (!(ch->flags & XPC_C_OPENREQUEST) ||
+ !(ch->flags & XPC_C_ROPENREQUEST)) {
+ /* nothing more to do for now */
+ return;
+ }
+ DBUG_ON(!(ch->flags & XPC_C_CONNECTING));
+
+ if (!(ch->flags & XPC_C_SETUP)) {
+ spin_unlock_irqrestore(&ch->lock, *irq_flags);
+ ret = xpc_allocate_msgqueues(ch);
+ spin_lock_irqsave(&ch->lock, *irq_flags);
+
+ if (ret != xpcSuccess) {
+ XPC_DISCONNECT_CHANNEL(ch, ret, irq_flags);
+ }
+ if (ch->flags & (XPC_C_CONNECTED | XPC_C_DISCONNECTING)) {
+ return;
+ }
+
+ DBUG_ON(!(ch->flags & XPC_C_SETUP));
+ DBUG_ON(ch->local_msgqueue == NULL);
+ DBUG_ON(ch->remote_msgqueue == NULL);
+ }
+
+ if (!(ch->flags & XPC_C_OPENREPLY)) {
+ ch->flags |= XPC_C_OPENREPLY;
+ xpc_IPI_send_openreply(ch, irq_flags);
+ }
+
+ if (!(ch->flags & XPC_C_ROPENREPLY)) {
+ return;
+ }
+
+ DBUG_ON(ch->remote_msgqueue_pa == 0);
+
+ ch->flags = (XPC_C_CONNECTED | XPC_C_SETUP); /* clear all else */
+
+ dev_info(xpc_chan, "channel %d to partition %d connected\n",
+ ch->number, ch->partid);
+
+ spin_unlock_irqrestore(&ch->lock, *irq_flags);
+ xpc_create_kthreads(ch, 1);
+ spin_lock_irqsave(&ch->lock, *irq_flags);
+}
+
+
+/*
+ * Free up message queues and other stuff that were allocated for the specified
+ * channel.
+ *
+ * Note: ch->reason and ch->reason_line are left set for debugging purposes,
+ * they're cleared when XPC_C_DISCONNECTED is cleared.
+ */
+static void
+xpc_free_msgqueues(struct xpc_channel *ch)
+{
+ DBUG_ON(!spin_is_locked(&ch->lock));
+ DBUG_ON(atomic_read(&ch->n_to_notify) != 0);
+
+ ch->remote_msgqueue_pa = 0;
+ ch->func = NULL;
+ ch->key = NULL;
+ ch->msg_size = 0;
+ ch->local_nentries = 0;
+ ch->remote_nentries = 0;
+ ch->kthreads_assigned_limit = 0;
+ ch->kthreads_idle_limit = 0;
+
+ ch->local_GP->get = 0;
+ ch->local_GP->put = 0;
+ ch->remote_GP.get = 0;
+ ch->remote_GP.put = 0;
+ ch->w_local_GP.get = 0;
+ ch->w_local_GP.put = 0;
+ ch->w_remote_GP.get = 0;
+ ch->w_remote_GP.put = 0;
+ ch->next_msg_to_pull = 0;
+
+ if (ch->flags & XPC_C_SETUP) {
+ ch->flags &= ~XPC_C_SETUP;
+
+ dev_dbg(xpc_chan, "ch->flags=0x%x, partid=%d, channel=%d\n",
+ ch->flags, ch->partid, ch->number);
+
+ kfree(ch->local_msgqueue_base);
+ ch->local_msgqueue = NULL;
+ kfree(ch->remote_msgqueue_base);
+ ch->remote_msgqueue = NULL;
+ kfree(ch->notify_queue);
+ ch->notify_queue = NULL;
+
+ /* in case someone is waiting for the teardown to complete */
+ up(&ch->teardown_sema);
+ }
+}
+
+
+/*
+ * spin_lock_irqsave() is expected to be held on entry.
+ */
+static void
+xpc_process_disconnect(struct xpc_channel *ch, unsigned long *irq_flags)
+{
+ struct xpc_partition *part = &xpc_partitions[ch->partid];
+ u32 ch_flags = ch->flags;
+
+
+ DBUG_ON(!spin_is_locked(&ch->lock));
+
+ if (!(ch->flags & XPC_C_DISCONNECTING)) {
+ return;
+ }
+
+ DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST));
+
+ /* make sure all activity has settled down first */
+
+ if (atomic_read(&ch->references) > 0) {
+ return;
+ }
+ DBUG_ON(atomic_read(&ch->kthreads_assigned) != 0);
+
+ /* it's now safe to free the channel's message queues */
+
+ xpc_free_msgqueues(ch);
+ DBUG_ON(ch->flags & XPC_C_SETUP);
+
+ if (part->act_state != XPC_P_DEACTIVATING) {
+
+ /* as long as the other side is up do the full protocol */
+
+ if (!(ch->flags & XPC_C_RCLOSEREQUEST)) {
+ return;
+ }
+
+ if (!(ch->flags & XPC_C_CLOSEREPLY)) {
+ ch->flags |= XPC_C_CLOSEREPLY;
+ xpc_IPI_send_closereply(ch, irq_flags);
+ }
+
+ if (!(ch->flags & XPC_C_RCLOSEREPLY)) {
+ return;
+ }
+ }
+
+ /* both sides are disconnected now */
+
+ ch->flags = XPC_C_DISCONNECTED; /* clear all flags, but this one */
+
+ atomic_dec(&part->nchannels_active);
+
+ if (ch_flags & XPC_C_WASCONNECTED) {
+ dev_info(xpc_chan, "channel %d to partition %d disconnected, "
+ "reason=%d\n", ch->number, ch->partid, ch->reason);
+ }
+}
+
+
+/*
+ * Process a change in the channel's remote connection state.
+ */
+static void
+xpc_process_openclose_IPI(struct xpc_partition *part, int ch_number,
+ u8 IPI_flags)
+{
+ unsigned long irq_flags;
+ struct xpc_openclose_args *args =
+ &part->remote_openclose_args[ch_number];
+ struct xpc_channel *ch = &part->channels[ch_number];
+ enum xpc_retval reason;
+
+
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+
+
+ if (IPI_flags & XPC_IPI_CLOSEREQUEST) {
+
+ dev_dbg(xpc_chan, "XPC_IPI_CLOSEREQUEST (reason=%d) received "
+ "from partid=%d, channel=%d\n", args->reason,
+ ch->partid, ch->number);
+
+ /*
+ * If RCLOSEREQUEST is set, we're probably waiting for
+ * RCLOSEREPLY. We should find it and a ROPENREQUEST packed
+ * with this RCLOSEQREUQEST in the IPI_flags.
+ */
+
+ if (ch->flags & XPC_C_RCLOSEREQUEST) {
+ DBUG_ON(!(ch->flags & XPC_C_DISCONNECTING));
+ DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST));
+ DBUG_ON(!(ch->flags & XPC_C_CLOSEREPLY));
+ DBUG_ON(ch->flags & XPC_C_RCLOSEREPLY);
+
+ DBUG_ON(!(IPI_flags & XPC_IPI_CLOSEREPLY));
+ IPI_flags &= ~XPC_IPI_CLOSEREPLY;
+ ch->flags |= XPC_C_RCLOSEREPLY;
+
+ /* both sides have finished disconnecting */
+ xpc_process_disconnect(ch, &irq_flags);
+ }
+
+ if (ch->flags & XPC_C_DISCONNECTED) {
+ // >>> explain this section
+
+ if (!(IPI_flags & XPC_IPI_OPENREQUEST)) {
+ DBUG_ON(part->act_state !=
+ XPC_P_DEACTIVATING);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return;
+ }
+
+ XPC_SET_REASON(ch, 0, 0);
+ ch->flags &= ~XPC_C_DISCONNECTED;
+
+ atomic_inc(&part->nchannels_active);
+ ch->flags |= (XPC_C_CONNECTING | XPC_C_ROPENREQUEST);
+ }
+
+ IPI_flags &= ~(XPC_IPI_OPENREQUEST | XPC_IPI_OPENREPLY);
+
+ /*
+ * The meaningful CLOSEREQUEST connection state fields are:
+ * reason = reason connection is to be closed
+ */
+
+ ch->flags |= XPC_C_RCLOSEREQUEST;
+
+ if (!(ch->flags & XPC_C_DISCONNECTING)) {
+ reason = args->reason;
+ if (reason <= xpcSuccess || reason > xpcUnknownReason) {
+ reason = xpcUnknownReason;
+ } else if (reason == xpcUnregistering) {
+ reason = xpcOtherUnregistering;
+ }
+
+ XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags);
+ } else {
+ xpc_process_disconnect(ch, &irq_flags);
+ }
+ }
+
+
+ if (IPI_flags & XPC_IPI_CLOSEREPLY) {
+
+ dev_dbg(xpc_chan, "XPC_IPI_CLOSEREPLY received from partid=%d,"
+ " channel=%d\n", ch->partid, ch->number);
+
+ if (ch->flags & XPC_C_DISCONNECTED) {
+ DBUG_ON(part->act_state != XPC_P_DEACTIVATING);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return;
+ }
+
+ DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST));
+ DBUG_ON(!(ch->flags & XPC_C_RCLOSEREQUEST));
+
+ ch->flags |= XPC_C_RCLOSEREPLY;
+
+ if (ch->flags & XPC_C_CLOSEREPLY) {
+ /* both sides have finished disconnecting */
+ xpc_process_disconnect(ch, &irq_flags);
+ }
+ }
+
+
+ if (IPI_flags & XPC_IPI_OPENREQUEST) {
+
+ dev_dbg(xpc_chan, "XPC_IPI_OPENREQUEST (msg_size=%d, "
+ "local_nentries=%d) received from partid=%d, "
+ "channel=%d\n", args->msg_size, args->local_nentries,
+ ch->partid, ch->number);
+
+ if ((ch->flags & XPC_C_DISCONNECTING) ||
+ part->act_state == XPC_P_DEACTIVATING) {
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return;
+ }
+ DBUG_ON(!(ch->flags & (XPC_C_DISCONNECTED |
+ XPC_C_OPENREQUEST)));
+ DBUG_ON(ch->flags & (XPC_C_ROPENREQUEST | XPC_C_ROPENREPLY |
+ XPC_C_OPENREPLY | XPC_C_CONNECTED));
+
+ /*
+ * The meaningful OPENREQUEST connection state fields are:
+ * msg_size = size of channel's messages in bytes
+ * local_nentries = remote partition's local_nentries
+ */
+ DBUG_ON(args->msg_size == 0);
+ DBUG_ON(args->local_nentries == 0);
+
+ ch->flags |= (XPC_C_ROPENREQUEST | XPC_C_CONNECTING);
+ ch->remote_nentries = args->local_nentries;
+
+
+ if (ch->flags & XPC_C_OPENREQUEST) {
+ if (args->msg_size != ch->msg_size) {
+ XPC_DISCONNECT_CHANNEL(ch, xpcUnequalMsgSizes,
+ &irq_flags);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return;
+ }
+ } else {
+ ch->msg_size = args->msg_size;
+
+ XPC_SET_REASON(ch, 0, 0);
+ ch->flags &= ~XPC_C_DISCONNECTED;
+
+ atomic_inc(&part->nchannels_active);
+ }
+
+ xpc_process_connect(ch, &irq_flags);
+ }
+
+
+ if (IPI_flags & XPC_IPI_OPENREPLY) {
+
+ dev_dbg(xpc_chan, "XPC_IPI_OPENREPLY (local_msgqueue_pa=0x%lx, "
+ "local_nentries=%d, remote_nentries=%d) received from "
+ "partid=%d, channel=%d\n", args->local_msgqueue_pa,
+ args->local_nentries, args->remote_nentries,
+ ch->partid, ch->number);
+
+ if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED)) {
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return;
+ }
+ DBUG_ON(!(ch->flags & XPC_C_OPENREQUEST));
+ DBUG_ON(!(ch->flags & XPC_C_ROPENREQUEST));
+ DBUG_ON(ch->flags & XPC_C_CONNECTED);
+
+ /*
+ * The meaningful OPENREPLY connection state fields are:
+ * local_msgqueue_pa = physical address of remote
+ * partition's local_msgqueue
+ * local_nentries = remote partition's local_nentries
+ * remote_nentries = remote partition's remote_nentries
+ */
+ DBUG_ON(args->local_msgqueue_pa == 0);
+ DBUG_ON(args->local_nentries == 0);
+ DBUG_ON(args->remote_nentries == 0);
+
+ ch->flags |= XPC_C_ROPENREPLY;
+ ch->remote_msgqueue_pa = args->local_msgqueue_pa;
+
+ if (args->local_nentries < ch->remote_nentries) {
+ dev_dbg(xpc_chan, "XPC_IPI_OPENREPLY: new "
+ "remote_nentries=%d, old remote_nentries=%d, "
+ "partid=%d, channel=%d\n",
+ args->local_nentries, ch->remote_nentries,
+ ch->partid, ch->number);
+
+ ch->remote_nentries = args->local_nentries;
+ }
+ if (args->remote_nentries < ch->local_nentries) {
+ dev_dbg(xpc_chan, "XPC_IPI_OPENREPLY: new "
+ "local_nentries=%d, old local_nentries=%d, "
+ "partid=%d, channel=%d\n",
+ args->remote_nentries, ch->local_nentries,
+ ch->partid, ch->number);
+
+ ch->local_nentries = args->remote_nentries;
+ }
+
+ xpc_process_connect(ch, &irq_flags);
+ }
+
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+}
+
+
+/*
+ * Attempt to establish a channel connection to a remote partition.
+ */
+static enum xpc_retval
+xpc_connect_channel(struct xpc_channel *ch)
+{
+ unsigned long irq_flags;
+ struct xpc_registration *registration = &xpc_registrations[ch->number];
+
+
+ if (down_interruptible(&registration->sema) != 0) {
+ return xpcInterrupted;
+ }
+
+ if (!XPC_CHANNEL_REGISTERED(ch->number)) {
+ up(&registration->sema);
+ return xpcUnregistered;
+ }
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+
+ DBUG_ON(ch->flags & XPC_C_CONNECTED);
+ DBUG_ON(ch->flags & XPC_C_OPENREQUEST);
+
+ if (ch->flags & XPC_C_DISCONNECTING) {
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ up(&registration->sema);
+ return ch->reason;
+ }
+
+
+ /* add info from the channel connect registration to the channel */
+
+ ch->kthreads_assigned_limit = registration->assigned_limit;
+ ch->kthreads_idle_limit = registration->idle_limit;
+ DBUG_ON(atomic_read(&ch->kthreads_assigned) != 0);
+ DBUG_ON(atomic_read(&ch->kthreads_idle) != 0);
+ DBUG_ON(atomic_read(&ch->kthreads_active) != 0);
+
+ ch->func = registration->func;
+ DBUG_ON(registration->func == NULL);
+ ch->key = registration->key;
+
+ ch->local_nentries = registration->nentries;
+
+ if (ch->flags & XPC_C_ROPENREQUEST) {
+ if (registration->msg_size != ch->msg_size) {
+ /* the local and remote sides aren't the same */
+
+ /*
+ * Because XPC_DISCONNECT_CHANNEL() can block we're
+ * forced to up the registration sema before we unlock
+ * the channel lock. But that's okay here because we're
+ * done with the part that required the registration
+ * sema. XPC_DISCONNECT_CHANNEL() requires that the
+ * channel lock be locked and will unlock and relock
+ * the channel lock as needed.
+ */
+ up(&registration->sema);
+ XPC_DISCONNECT_CHANNEL(ch, xpcUnequalMsgSizes,
+ &irq_flags);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return xpcUnequalMsgSizes;
+ }
+ } else {
+ ch->msg_size = registration->msg_size;
+
+ XPC_SET_REASON(ch, 0, 0);
+ ch->flags &= ~XPC_C_DISCONNECTED;
+
+ atomic_inc(&xpc_partitions[ch->partid].nchannels_active);
+ }
+
+ up(&registration->sema);
+
+
+ /* initiate the connection */
+
+ ch->flags |= (XPC_C_OPENREQUEST | XPC_C_CONNECTING);
+ xpc_IPI_send_openrequest(ch, &irq_flags);
+
+ xpc_process_connect(ch, &irq_flags);
+
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Notify those who wanted to be notified upon delivery of their message.
+ */
+static void
+xpc_notify_senders(struct xpc_channel *ch, enum xpc_retval reason, s64 put)
+{
+ struct xpc_notify *notify;
+ u8 notify_type;
+ s64 get = ch->w_remote_GP.get - 1;
+
+
+ while (++get < put && atomic_read(&ch->n_to_notify) > 0) {
+
+ notify = &ch->notify_queue[get % ch->local_nentries];
+
+ /*
+ * See if the notify entry indicates it was associated with
+ * a message who's sender wants to be notified. It is possible
+ * that it is, but someone else is doing or has done the
+ * notification.
+ */
+ notify_type = notify->type;
+ if (notify_type == 0 ||
+ cmpxchg(&notify->type, notify_type, 0) !=
+ notify_type) {
+ continue;
+ }
+
+ DBUG_ON(notify_type != XPC_N_CALL);
+
+ atomic_dec(&ch->n_to_notify);
+
+ if (notify->func != NULL) {
+ dev_dbg(xpc_chan, "notify->func() called, notify=0x%p, "
+ "msg_number=%ld, partid=%d, channel=%d\n",
+ (void *) notify, get, ch->partid, ch->number);
+
+ notify->func(reason, ch->partid, ch->number,
+ notify->key);
+
+ dev_dbg(xpc_chan, "notify->func() returned, "
+ "notify=0x%p, msg_number=%ld, partid=%d, "
+ "channel=%d\n", (void *) notify, get,
+ ch->partid, ch->number);
+ }
+ }
+}
+
+
+/*
+ * Clear some of the msg flags in the local message queue.
+ */
+static inline void
+xpc_clear_local_msgqueue_flags(struct xpc_channel *ch)
+{
+ struct xpc_msg *msg;
+ s64 get;
+
+
+ get = ch->w_remote_GP.get;
+ do {
+ msg = (struct xpc_msg *) ((u64) ch->local_msgqueue +
+ (get % ch->local_nentries) * ch->msg_size);
+ msg->flags = 0;
+ } while (++get < (volatile s64) ch->remote_GP.get);
+}
+
+
+/*
+ * Clear some of the msg flags in the remote message queue.
+ */
+static inline void
+xpc_clear_remote_msgqueue_flags(struct xpc_channel *ch)
+{
+ struct xpc_msg *msg;
+ s64 put;
+
+
+ put = ch->w_remote_GP.put;
+ do {
+ msg = (struct xpc_msg *) ((u64) ch->remote_msgqueue +
+ (put % ch->remote_nentries) * ch->msg_size);
+ msg->flags = 0;
+ } while (++put < (volatile s64) ch->remote_GP.put);
+}
+
+
+static void
+xpc_process_msg_IPI(struct xpc_partition *part, int ch_number)
+{
+ struct xpc_channel *ch = &part->channels[ch_number];
+ int nmsgs_sent;
+
+
+ ch->remote_GP = part->remote_GPs[ch_number];
+
+
+ /* See what, if anything, has changed for each connected channel */
+
+ xpc_msgqueue_ref(ch);
+
+ if (ch->w_remote_GP.get == ch->remote_GP.get &&
+ ch->w_remote_GP.put == ch->remote_GP.put) {
+ /* nothing changed since GPs were last pulled */
+ xpc_msgqueue_deref(ch);
+ return;
+ }
+
+ if (!(ch->flags & XPC_C_CONNECTED)){
+ xpc_msgqueue_deref(ch);
+ return;
+ }
+
+
+ /*
+ * First check to see if messages recently sent by us have been
+ * received by the other side. (The remote GET value will have
+ * changed since we last looked at it.)
+ */
+
+ if (ch->w_remote_GP.get != ch->remote_GP.get) {
+
+ /*
+ * We need to notify any senders that want to be notified
+ * that their sent messages have been received by their
+ * intended recipients. We need to do this before updating
+ * w_remote_GP.get so that we don't allocate the same message
+ * queue entries prematurely (see xpc_allocate_msg()).
+ */
+ if (atomic_read(&ch->n_to_notify) > 0) {
+ /*
+ * Notify senders that messages sent have been
+ * received and delivered by the other side.
+ */
+ xpc_notify_senders(ch, xpcMsgDelivered,
+ ch->remote_GP.get);
+ }
+
+ /*
+ * Clear msg->flags in previously sent messages, so that
+ * they're ready for xpc_allocate_msg().
+ */
+ xpc_clear_local_msgqueue_flags(ch);
+
+ (volatile s64) ch->w_remote_GP.get = ch->remote_GP.get;
+
+ dev_dbg(xpc_chan, "w_remote_GP.get changed to %ld, partid=%d, "
+ "channel=%d\n", ch->w_remote_GP.get, ch->partid,
+ ch->number);
+
+ /*
+ * If anyone was waiting for message queue entries to become
+ * available, wake them up.
+ */
+ if (atomic_read(&ch->n_on_msg_allocate_wq) > 0) {
+ wake_up(&ch->msg_allocate_wq);
+ }
+ }
+
+
+ /*
+ * Now check for newly sent messages by the other side. (The remote
+ * PUT value will have changed since we last looked at it.)
+ */
+
+ if (ch->w_remote_GP.put != ch->remote_GP.put) {
+ /*
+ * Clear msg->flags in previously received messages, so that
+ * they're ready for xpc_get_deliverable_msg().
+ */
+ xpc_clear_remote_msgqueue_flags(ch);
+
+ (volatile s64) ch->w_remote_GP.put = ch->remote_GP.put;
+
+ dev_dbg(xpc_chan, "w_remote_GP.put changed to %ld, partid=%d, "
+ "channel=%d\n", ch->w_remote_GP.put, ch->partid,
+ ch->number);
+
+ nmsgs_sent = ch->w_remote_GP.put - ch->w_local_GP.get;
+ if (nmsgs_sent > 0) {
+ dev_dbg(xpc_chan, "msgs waiting to be copied and "
+ "delivered=%d, partid=%d, channel=%d\n",
+ nmsgs_sent, ch->partid, ch->number);
+
+ if (ch->flags & XPC_C_CONNECTCALLOUT) {
+ xpc_activate_kthreads(ch, nmsgs_sent);
+ }
+ }
+ }
+
+ xpc_msgqueue_deref(ch);
+}
+
+
+void
+xpc_process_channel_activity(struct xpc_partition *part)
+{
+ unsigned long irq_flags;
+ u64 IPI_amo, IPI_flags;
+ struct xpc_channel *ch;
+ int ch_number;
+
+
+ IPI_amo = xpc_get_IPI_flags(part);
+
+ /*
+ * Initiate channel connections for registered channels.
+ *
+ * For each connected channel that has pending messages activate idle
+ * kthreads and/or create new kthreads as needed.
+ */
+
+ for (ch_number = 0; ch_number < part->nchannels; ch_number++) {
+ ch = &part->channels[ch_number];
+
+
+ /*
+ * Process any open or close related IPI flags, and then deal
+ * with connecting or disconnecting the channel as required.
+ */
+
+ IPI_flags = XPC_GET_IPI_FLAGS(IPI_amo, ch_number);
+
+ if (XPC_ANY_OPENCLOSE_IPI_FLAGS_SET(IPI_flags)) {
+ xpc_process_openclose_IPI(part, ch_number, IPI_flags);
+ }
+
+
+ if (ch->flags & XPC_C_DISCONNECTING) {
+ spin_lock_irqsave(&ch->lock, irq_flags);
+ xpc_process_disconnect(ch, &irq_flags);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ continue;
+ }
+
+ if (part->act_state == XPC_P_DEACTIVATING) {
+ continue;
+ }
+
+ if (!(ch->flags & XPC_C_CONNECTED)) {
+ if (!(ch->flags & XPC_C_OPENREQUEST)) {
+ DBUG_ON(ch->flags & XPC_C_SETUP);
+ (void) xpc_connect_channel(ch);
+ } else {
+ spin_lock_irqsave(&ch->lock, irq_flags);
+ xpc_process_connect(ch, &irq_flags);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ }
+ continue;
+ }
+
+
+ /*
+ * Process any message related IPI flags, this may involve the
+ * activation of kthreads to deliver any pending messages sent
+ * from the other partition.
+ */
+
+ if (XPC_ANY_MSG_IPI_FLAGS_SET(IPI_flags)) {
+ xpc_process_msg_IPI(part, ch_number);
+ }
+ }
+}
+
+
+/*
+ * XPC's heartbeat code calls this function to inform XPC that a partition has
+ * gone down. XPC responds by tearing down the XPartition Communication
+ * infrastructure used for the just downed partition.
+ *
+ * XPC's heartbeat code will never call this function and xpc_partition_up()
+ * at the same time. Nor will it ever make multiple calls to either function
+ * at the same time.
+ */
+void
+xpc_partition_down(struct xpc_partition *part, enum xpc_retval reason)
+{
+ unsigned long irq_flags;
+ int ch_number;
+ struct xpc_channel *ch;
+
+
+ dev_dbg(xpc_chan, "deactivating partition %d, reason=%d\n",
+ XPC_PARTID(part), reason);
+
+ if (!xpc_part_ref(part)) {
+ /* infrastructure for this partition isn't currently set up */
+ return;
+ }
+
+
+ /* disconnect all channels associated with the downed partition */
+
+ for (ch_number = 0; ch_number < part->nchannels; ch_number++) {
+ ch = &part->channels[ch_number];
+
+
+ xpc_msgqueue_ref(ch);
+ spin_lock_irqsave(&ch->lock, irq_flags);
+
+ XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags);
+
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ xpc_msgqueue_deref(ch);
+ }
+
+ xpc_wakeup_channel_mgr(part);
+
+ xpc_part_deref(part);
+}
+
+
+/*
+ * Teardown the infrastructure necessary to support XPartition Communication
+ * between the specified remote partition and the local one.
+ */
+void
+xpc_teardown_infrastructure(struct xpc_partition *part)
+{
+ partid_t partid = XPC_PARTID(part);
+
+
+ /*
+ * We start off by making this partition inaccessible to local
+ * processes by marking it as no longer setup. Then we make it
+ * inaccessible to remote processes by clearing the XPC per partition
+ * specific variable's magic # (which indicates that these variables
+ * are no longer valid) and by ignoring all XPC notify IPIs sent to
+ * this partition.
+ */
+
+ DBUG_ON(atomic_read(&part->nchannels_active) != 0);
+ DBUG_ON(part->setup_state != XPC_P_SETUP);
+ part->setup_state = XPC_P_WTEARDOWN;
+
+ xpc_vars_part[partid].magic = 0;
+
+
+ free_irq(SGI_XPC_NOTIFY, (void *) (u64) partid);
+
+
+ /*
+ * Before proceding with the teardown we have to wait until all
+ * existing references cease.
+ */
+ wait_event(part->teardown_wq, (atomic_read(&part->references) == 0));
+
+
+ /* now we can begin tearing down the infrastructure */
+
+ part->setup_state = XPC_P_TORNDOWN;
+
+ /* in case we've still got outstanding timers registered... */
+ del_timer_sync(&part->dropped_IPI_timer);
+
+ kfree(part->remote_openclose_args_base);
+ part->remote_openclose_args = NULL;
+ kfree(part->local_openclose_args_base);
+ part->local_openclose_args = NULL;
+ kfree(part->remote_GPs_base);
+ part->remote_GPs = NULL;
+ kfree(part->local_GPs_base);
+ part->local_GPs = NULL;
+ kfree(part->channels);
+ part->channels = NULL;
+ part->local_IPI_amo_va = NULL;
+}
+
+
+/*
+ * Called by XP at the time of channel connection registration to cause
+ * XPC to establish connections to all currently active partitions.
+ */
+void
+xpc_initiate_connect(int ch_number)
+{
+ partid_t partid;
+ struct xpc_partition *part;
+ struct xpc_channel *ch;
+
+
+ DBUG_ON(ch_number < 0 || ch_number >= XPC_NCHANNELS);
+
+ for (partid = 1; partid < XP_MAX_PARTITIONS; partid++) {
+ part = &xpc_partitions[partid];
+
+ if (xpc_part_ref(part)) {
+ ch = &part->channels[ch_number];
+
+ if (!(ch->flags & XPC_C_DISCONNECTING)) {
+ DBUG_ON(ch->flags & XPC_C_OPENREQUEST);
+ DBUG_ON(ch->flags & XPC_C_CONNECTED);
+ DBUG_ON(ch->flags & XPC_C_SETUP);
+
+ /*
+ * Initiate the establishment of a connection
+ * on the newly registered channel to the
+ * remote partition.
+ */
+ xpc_wakeup_channel_mgr(part);
+ }
+
+ xpc_part_deref(part);
+ }
+ }
+}
+
+
+void
+xpc_connected_callout(struct xpc_channel *ch)
+{
+ unsigned long irq_flags;
+
+
+ /* let the registerer know that a connection has been established */
+
+ if (ch->func != NULL) {
+ dev_dbg(xpc_chan, "ch->func() called, reason=xpcConnected, "
+ "partid=%d, channel=%d\n", ch->partid, ch->number);
+
+ ch->func(xpcConnected, ch->partid, ch->number,
+ (void *) (u64) ch->local_nentries, ch->key);
+
+ dev_dbg(xpc_chan, "ch->func() returned, reason=xpcConnected, "
+ "partid=%d, channel=%d\n", ch->partid, ch->number);
+ }
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+ ch->flags |= XPC_C_CONNECTCALLOUT;
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+}
+
+
+/*
+ * Called by XP at the time of channel connection unregistration to cause
+ * XPC to teardown all current connections for the specified channel.
+ *
+ * Before returning xpc_initiate_disconnect() will wait until all connections
+ * on the specified channel have been closed/torndown. So the caller can be
+ * assured that they will not be receiving any more callouts from XPC to the
+ * function they registered via xpc_connect().
+ *
+ * Arguments:
+ *
+ * ch_number - channel # to unregister.
+ */
+void
+xpc_initiate_disconnect(int ch_number)
+{
+ unsigned long irq_flags;
+ partid_t partid;
+ struct xpc_partition *part;
+ struct xpc_channel *ch;
+
+
+ DBUG_ON(ch_number < 0 || ch_number >= XPC_NCHANNELS);
+
+ /* initiate the channel disconnect for every active partition */
+ for (partid = 1; partid < XP_MAX_PARTITIONS; partid++) {
+ part = &xpc_partitions[partid];
+
+ if (xpc_part_ref(part)) {
+ ch = &part->channels[ch_number];
+ xpc_msgqueue_ref(ch);
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+
+ XPC_DISCONNECT_CHANNEL(ch, xpcUnregistering,
+ &irq_flags);
+
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+
+ xpc_msgqueue_deref(ch);
+ xpc_part_deref(part);
+ }
+ }
+
+ xpc_disconnect_wait(ch_number);
+}
+
+
+/*
+ * To disconnect a channel, and reflect it back to all who may be waiting.
+ *
+ * >>> An OPEN is not allowed until XPC_C_DISCONNECTING is cleared by
+ * >>> xpc_free_msgqueues().
+ *
+ * THE CHANNEL IS TO BE LOCKED BY THE CALLER AND WILL REMAIN LOCKED UPON RETURN.
+ */
+void
+xpc_disconnect_channel(const int line, struct xpc_channel *ch,
+ enum xpc_retval reason, unsigned long *irq_flags)
+{
+ u32 flags;
+
+
+ DBUG_ON(!spin_is_locked(&ch->lock));
+
+ if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED)) {
+ return;
+ }
+ DBUG_ON(!(ch->flags & (XPC_C_CONNECTING | XPC_C_CONNECTED)));
+
+ dev_dbg(xpc_chan, "reason=%d, line=%d, partid=%d, channel=%d\n",
+ reason, line, ch->partid, ch->number);
+
+ XPC_SET_REASON(ch, reason, line);
+
+ flags = ch->flags;
+ /* some of these may not have been set */
+ ch->flags &= ~(XPC_C_OPENREQUEST | XPC_C_OPENREPLY |
+ XPC_C_ROPENREQUEST | XPC_C_ROPENREPLY |
+ XPC_C_CONNECTING | XPC_C_CONNECTED);
+
+ ch->flags |= (XPC_C_CLOSEREQUEST | XPC_C_DISCONNECTING);
+ xpc_IPI_send_closerequest(ch, irq_flags);
+
+ if (flags & XPC_C_CONNECTED) {
+ ch->flags |= XPC_C_WASCONNECTED;
+ }
+
+ if (atomic_read(&ch->kthreads_idle) > 0) {
+ /* wake all idle kthreads so they can exit */
+ wake_up_all(&ch->idle_wq);
+ }
+
+ spin_unlock_irqrestore(&ch->lock, *irq_flags);
+
+
+ /* wake those waiting to allocate an entry from the local msg queue */
+
+ if (atomic_read(&ch->n_on_msg_allocate_wq) > 0) {
+ wake_up(&ch->msg_allocate_wq);
+ }
+
+ /* wake those waiting for notify completion */
+
+ if (atomic_read(&ch->n_to_notify) > 0) {
+ xpc_notify_senders(ch, reason, ch->w_local_GP.put);
+ }
+
+ spin_lock_irqsave(&ch->lock, *irq_flags);
+}
+
+
+void
+xpc_disconnected_callout(struct xpc_channel *ch)
+{
+ /*
+ * Let the channel's registerer know that the channel is now
+ * disconnected. We don't want to do this if the registerer was never
+ * informed of a connection being made, unless the disconnect was for
+ * abnormal reasons.
+ */
+
+ if (ch->func != NULL) {
+ dev_dbg(xpc_chan, "ch->func() called, reason=%d, partid=%d, "
+ "channel=%d\n", ch->reason, ch->partid, ch->number);
+
+ ch->func(ch->reason, ch->partid, ch->number, NULL, ch->key);
+
+ dev_dbg(xpc_chan, "ch->func() returned, reason=%d, partid=%d, "
+ "channel=%d\n", ch->reason, ch->partid, ch->number);
+ }
+}
+
+
+/*
+ * Wait for a message entry to become available for the specified channel,
+ * but don't wait any longer than 1 jiffy.
+ */
+static enum xpc_retval
+xpc_allocate_msg_wait(struct xpc_channel *ch)
+{
+ enum xpc_retval ret;
+
+
+ if (ch->flags & XPC_C_DISCONNECTING) {
+ DBUG_ON(ch->reason == xpcInterrupted); // >>> Is this true?
+ return ch->reason;
+ }
+
+ atomic_inc(&ch->n_on_msg_allocate_wq);
+ ret = interruptible_sleep_on_timeout(&ch->msg_allocate_wq, 1);
+ atomic_dec(&ch->n_on_msg_allocate_wq);
+
+ if (ch->flags & XPC_C_DISCONNECTING) {
+ ret = ch->reason;
+ DBUG_ON(ch->reason == xpcInterrupted); // >>> Is this true?
+ } else if (ret == 0) {
+ ret = xpcTimeout;
+ } else {
+ ret = xpcInterrupted;
+ }
+
+ return ret;
+}
+
+
+/*
+ * Allocate an entry for a message from the message queue associated with the
+ * specified channel.
+ */
+static enum xpc_retval
+xpc_allocate_msg(struct xpc_channel *ch, u32 flags,
+ struct xpc_msg **address_of_msg)
+{
+ struct xpc_msg *msg;
+ enum xpc_retval ret;
+ s64 put;
+
+
+ /* this reference will be dropped in xpc_send_msg() */
+ xpc_msgqueue_ref(ch);
+
+ if (ch->flags & XPC_C_DISCONNECTING) {
+ xpc_msgqueue_deref(ch);
+ return ch->reason;
+ }
+ if (!(ch->flags & XPC_C_CONNECTED)) {
+ xpc_msgqueue_deref(ch);
+ return xpcNotConnected;
+ }
+
+
+ /*
+ * Get the next available message entry from the local message queue.
+ * If none are available, we'll make sure that we grab the latest
+ * GP values.
+ */
+ ret = xpcTimeout;
+
+ while (1) {
+
+ put = (volatile s64) ch->w_local_GP.put;
+ if (put - (volatile s64) ch->w_remote_GP.get <
+ ch->local_nentries) {
+
+ /* There are available message entries. We need to try
+ * to secure one for ourselves. We'll do this by trying
+ * to increment w_local_GP.put as long as someone else
+ * doesn't beat us to it. If they do, we'll have to
+ * try again.
+ */
+ if (cmpxchg(&ch->w_local_GP.put, put, put + 1) ==
+ put) {
+ /* we got the entry referenced by put */
+ break;
+ }
+ continue; /* try again */
+ }
+
+
+ /*
+ * There aren't any available msg entries at this time.
+ *
+ * In waiting for a message entry to become available,
+ * we set a timeout in case the other side is not
+ * sending completion IPIs. This lets us fake an IPI
+ * that will cause the IPI handler to fetch the latest
+ * GP values as if an IPI was sent by the other side.
+ */
+ if (ret == xpcTimeout) {
+ xpc_IPI_send_local_msgrequest(ch);
+ }
+
+ if (flags & XPC_NOWAIT) {
+ xpc_msgqueue_deref(ch);
+ return xpcNoWait;
+ }
+
+ ret = xpc_allocate_msg_wait(ch);
+ if (ret != xpcInterrupted && ret != xpcTimeout) {
+ xpc_msgqueue_deref(ch);
+ return ret;
+ }
+ }
+
+
+ /* get the message's address and initialize it */
+ msg = (struct xpc_msg *) ((u64) ch->local_msgqueue +
+ (put % ch->local_nentries) * ch->msg_size);
+
+
+ DBUG_ON(msg->flags != 0);
+ msg->number = put;
+
+ dev_dbg(xpc_chan, "w_local_GP.put changed to %ld; msg=0x%p, "
+ "msg_number=%ld, partid=%d, channel=%d\n", put + 1,
+ (void *) msg, msg->number, ch->partid, ch->number);
+
+ *address_of_msg = msg;
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Allocate an entry for a message from the message queue associated with the
+ * specified channel. NOTE that this routine can sleep waiting for a message
+ * entry to become available. To not sleep, pass in the XPC_NOWAIT flag.
+ *
+ * Arguments:
+ *
+ * partid - ID of partition to which the channel is connected.
+ * ch_number - channel #.
+ * flags - see xpc.h for valid flags.
+ * payload - address of the allocated payload area pointer (filled in on
+ * return) in which the user-defined message is constructed.
+ */
+enum xpc_retval
+xpc_initiate_allocate(partid_t partid, int ch_number, u32 flags, void **payload)
+{
+ struct xpc_partition *part = &xpc_partitions[partid];
+ enum xpc_retval ret = xpcUnknownReason;
+ struct xpc_msg *msg;
+
+
+ DBUG_ON(partid <= 0 || partid >= XP_MAX_PARTITIONS);
+ DBUG_ON(ch_number < 0 || ch_number >= part->nchannels);
+
+ *payload = NULL;
+
+ if (xpc_part_ref(part)) {
+ ret = xpc_allocate_msg(&part->channels[ch_number], flags, &msg);
+ xpc_part_deref(part);
+
+ if (msg != NULL) {
+ *payload = &msg->payload;
+ }
+ }
+
+ return ret;
+}
+
+
+/*
+ * Now we actually send the messages that are ready to be sent by advancing
+ * the local message queue's Put value and then send an IPI to the recipient
+ * partition.
+ */
+static void
+xpc_send_msgs(struct xpc_channel *ch, s64 initial_put)
+{
+ struct xpc_msg *msg;
+ s64 put = initial_put + 1;
+ int send_IPI = 0;
+
+
+ while (1) {
+
+ while (1) {
+ if (put == (volatile s64) ch->w_local_GP.put) {
+ break;
+ }
+
+ msg = (struct xpc_msg *) ((u64) ch->local_msgqueue +
+ (put % ch->local_nentries) * ch->msg_size);
+
+ if (!(msg->flags & XPC_M_READY)) {
+ break;
+ }
+
+ put++;
+ }
+
+ if (put == initial_put) {
+ /* nothing's changed */
+ break;
+ }
+
+ if (cmpxchg_rel(&ch->local_GP->put, initial_put, put) !=
+ initial_put) {
+ /* someone else beat us to it */
+ DBUG_ON((volatile s64) ch->local_GP->put < initial_put);
+ break;
+ }
+
+ /* we just set the new value of local_GP->put */
+
+ dev_dbg(xpc_chan, "local_GP->put changed to %ld, partid=%d, "
+ "channel=%d\n", put, ch->partid, ch->number);
+
+ send_IPI = 1;
+
+ /*
+ * We need to ensure that the message referenced by
+ * local_GP->put is not XPC_M_READY or that local_GP->put
+ * equals w_local_GP.put, so we'll go have a look.
+ */
+ initial_put = put;
+ }
+
+ if (send_IPI) {
+ xpc_IPI_send_msgrequest(ch);
+ }
+}
+
+
+/*
+ * Common code that does the actual sending of the message by advancing the
+ * local message queue's Put value and sends an IPI to the partition the
+ * message is being sent to.
+ */
+static enum xpc_retval
+xpc_send_msg(struct xpc_channel *ch, struct xpc_msg *msg, u8 notify_type,
+ xpc_notify_func func, void *key)
+{
+ enum xpc_retval ret = xpcSuccess;
+ struct xpc_notify *notify = NULL; // >>> to keep the compiler happy!!
+ s64 put, msg_number = msg->number;
+
+
+ DBUG_ON(notify_type == XPC_N_CALL && func == NULL);
+ DBUG_ON((((u64) msg - (u64) ch->local_msgqueue) / ch->msg_size) !=
+ msg_number % ch->local_nentries);
+ DBUG_ON(msg->flags & XPC_M_READY);
+
+ if (ch->flags & XPC_C_DISCONNECTING) {
+ /* drop the reference grabbed in xpc_allocate_msg() */
+ xpc_msgqueue_deref(ch);
+ return ch->reason;
+ }
+
+ if (notify_type != 0) {
+ /*
+ * Tell the remote side to send an ACK interrupt when the
+ * message has been delivered.
+ */
+ msg->flags |= XPC_M_INTERRUPT;
+
+ atomic_inc(&ch->n_to_notify);
+
+ notify = &ch->notify_queue[msg_number % ch->local_nentries];
+ notify->func = func;
+ notify->key = key;
+ (volatile u8) notify->type = notify_type;
+
+ // >>> is a mb() needed here?
+
+ if (ch->flags & XPC_C_DISCONNECTING) {
+ /*
+ * An error occurred between our last error check and
+ * this one. We will try to clear the type field from
+ * the notify entry. If we succeed then
+ * xpc_disconnect_channel() didn't already process
+ * the notify entry.
+ */
+ if (cmpxchg(&notify->type, notify_type, 0) ==
+ notify_type) {
+ atomic_dec(&ch->n_to_notify);
+ ret = ch->reason;
+ }
+
+ /* drop the reference grabbed in xpc_allocate_msg() */
+ xpc_msgqueue_deref(ch);
+ return ret;
+ }
+ }
+
+ msg->flags |= XPC_M_READY;
+
+ /*
+ * The preceding store of msg->flags must occur before the following
+ * load of ch->local_GP->put.
+ */
+ mb();
+
+ /* see if the message is next in line to be sent, if so send it */
+
+ put = ch->local_GP->put;
+ if (put == msg_number) {
+ xpc_send_msgs(ch, put);
+ }
+
+ /* drop the reference grabbed in xpc_allocate_msg() */
+ xpc_msgqueue_deref(ch);
+ return ret;
+}
+
+
+/*
+ * Send a message previously allocated using xpc_initiate_allocate() on the
+ * specified channel connected to the specified partition.
+ *
+ * This routine will not wait for the message to be received, nor will
+ * notification be given when it does happen. Once this routine has returned
+ * the message entry allocated via xpc_initiate_allocate() is no longer
+ * accessable to the caller.
+ *
+ * This routine, although called by users, does not call xpc_part_ref() to
+ * ensure that the partition infrastructure is in place. It relies on the
+ * fact that we called xpc_msgqueue_ref() in xpc_allocate_msg().
+ *
+ * Arguments:
+ *
+ * partid - ID of partition to which the channel is connected.
+ * ch_number - channel # to send message on.
+ * payload - pointer to the payload area allocated via
+ * xpc_initiate_allocate().
+ */
+enum xpc_retval
+xpc_initiate_send(partid_t partid, int ch_number, void *payload)
+{
+ struct xpc_partition *part = &xpc_partitions[partid];
+ struct xpc_msg *msg = XPC_MSG_ADDRESS(payload);
+ enum xpc_retval ret;
+
+
+ dev_dbg(xpc_chan, "msg=0x%p, partid=%d, channel=%d\n", (void *) msg,
+ partid, ch_number);
+
+ DBUG_ON(partid <= 0 || partid >= XP_MAX_PARTITIONS);
+ DBUG_ON(ch_number < 0 || ch_number >= part->nchannels);
+ DBUG_ON(msg == NULL);
+
+ ret = xpc_send_msg(&part->channels[ch_number], msg, 0, NULL, NULL);
+
+ return ret;
+}
+
+
+/*
+ * Send a message previously allocated using xpc_initiate_allocate on the
+ * specified channel connected to the specified partition.
+ *
+ * This routine will not wait for the message to be sent. Once this routine
+ * has returned the message entry allocated via xpc_initiate_allocate() is no
+ * longer accessable to the caller.
+ *
+ * Once the remote end of the channel has received the message, the function
+ * passed as an argument to xpc_initiate_send_notify() will be called. This
+ * allows the sender to free up or re-use any buffers referenced by the
+ * message, but does NOT mean the message has been processed at the remote
+ * end by a receiver.
+ *
+ * If this routine returns an error, the caller's function will NOT be called.
+ *
+ * This routine, although called by users, does not call xpc_part_ref() to
+ * ensure that the partition infrastructure is in place. It relies on the
+ * fact that we called xpc_msgqueue_ref() in xpc_allocate_msg().
+ *
+ * Arguments:
+ *
+ * partid - ID of partition to which the channel is connected.
+ * ch_number - channel # to send message on.
+ * payload - pointer to the payload area allocated via
+ * xpc_initiate_allocate().
+ * func - function to call with asynchronous notification of message
+ * receipt. THIS FUNCTION MUST BE NON-BLOCKING.
+ * key - user-defined key to be passed to the function when it's called.
+ */
+enum xpc_retval
+xpc_initiate_send_notify(partid_t partid, int ch_number, void *payload,
+ xpc_notify_func func, void *key)
+{
+ struct xpc_partition *part = &xpc_partitions[partid];
+ struct xpc_msg *msg = XPC_MSG_ADDRESS(payload);
+ enum xpc_retval ret;
+
+
+ dev_dbg(xpc_chan, "msg=0x%p, partid=%d, channel=%d\n", (void *) msg,
+ partid, ch_number);
+
+ DBUG_ON(partid <= 0 || partid >= XP_MAX_PARTITIONS);
+ DBUG_ON(ch_number < 0 || ch_number >= part->nchannels);
+ DBUG_ON(msg == NULL);
+ DBUG_ON(func == NULL);
+
+ ret = xpc_send_msg(&part->channels[ch_number], msg, XPC_N_CALL,
+ func, key);
+ return ret;
+}
+
+
+static struct xpc_msg *
+xpc_pull_remote_msg(struct xpc_channel *ch, s64 get)
+{
+ struct xpc_partition *part = &xpc_partitions[ch->partid];
+ struct xpc_msg *remote_msg, *msg;
+ u32 msg_index, nmsgs;
+ u64 msg_offset;
+ enum xpc_retval ret;
+
+
+ if (down_interruptible(&ch->msg_to_pull_sema) != 0) {
+ /* we were interrupted by a signal */
+ return NULL;
+ }
+
+ while (get >= ch->next_msg_to_pull) {
+
+ /* pull as many messages as are ready and able to be pulled */
+
+ msg_index = ch->next_msg_to_pull % ch->remote_nentries;
+
+ DBUG_ON(ch->next_msg_to_pull >=
+ (volatile s64) ch->w_remote_GP.put);
+ nmsgs = (volatile s64) ch->w_remote_GP.put -
+ ch->next_msg_to_pull;
+ if (msg_index + nmsgs > ch->remote_nentries) {
+ /* ignore the ones that wrap the msg queue for now */
+ nmsgs = ch->remote_nentries - msg_index;
+ }
+
+ msg_offset = msg_index * ch->msg_size;
+ msg = (struct xpc_msg *) ((u64) ch->remote_msgqueue +
+ msg_offset);
+ remote_msg = (struct xpc_msg *) (ch->remote_msgqueue_pa +
+ msg_offset);
+
+ if ((ret = xpc_pull_remote_cachelines(part, msg, remote_msg,
+ nmsgs * ch->msg_size)) != xpcSuccess) {
+
+ dev_dbg(xpc_chan, "failed to pull %d msgs starting with"
+ " msg %ld from partition %d, channel=%d, "
+ "ret=%d\n", nmsgs, ch->next_msg_to_pull,
+ ch->partid, ch->number, ret);
+
+ XPC_DEACTIVATE_PARTITION(part, ret);
+
+ up(&ch->msg_to_pull_sema);
+ return NULL;
+ }
+
+ mb(); /* >>> this may not be needed, we're not sure */
+
+ ch->next_msg_to_pull += nmsgs;
+ }
+
+ up(&ch->msg_to_pull_sema);
+
+ /* return the message we were looking for */
+ msg_offset = (get % ch->remote_nentries) * ch->msg_size;
+ msg = (struct xpc_msg *) ((u64) ch->remote_msgqueue + msg_offset);
+
+ return msg;
+}
+
+
+/*
+ * Get a message to be delivered.
+ */
+static struct xpc_msg *
+xpc_get_deliverable_msg(struct xpc_channel *ch)
+{
+ struct xpc_msg *msg = NULL;
+ s64 get;
+
+
+ do {
+ if ((volatile u32) ch->flags & XPC_C_DISCONNECTING) {
+ break;
+ }
+
+ get = (volatile s64) ch->w_local_GP.get;
+ if (get == (volatile s64) ch->w_remote_GP.put) {
+ break;
+ }
+
+ /* There are messages waiting to be pulled and delivered.
+ * We need to try to secure one for ourselves. We'll do this
+ * by trying to increment w_local_GP.get and hope that no one
+ * else beats us to it. If they do, we'll we'll simply have
+ * to try again for the next one.
+ */
+
+ if (cmpxchg(&ch->w_local_GP.get, get, get + 1) == get) {
+ /* we got the entry referenced by get */
+
+ dev_dbg(xpc_chan, "w_local_GP.get changed to %ld, "
+ "partid=%d, channel=%d\n", get + 1,
+ ch->partid, ch->number);
+
+ /* pull the message from the remote partition */
+
+ msg = xpc_pull_remote_msg(ch, get);
+
+ DBUG_ON(msg != NULL && msg->number != get);
+ DBUG_ON(msg != NULL && (msg->flags & XPC_M_DONE));
+ DBUG_ON(msg != NULL && !(msg->flags & XPC_M_READY));
+
+ break;
+ }
+
+ } while (1);
+
+ return msg;
+}
+
+
+/*
+ * Deliver a message to its intended recipient.
+ */
+void
+xpc_deliver_msg(struct xpc_channel *ch)
+{
+ struct xpc_msg *msg;
+
+
+ if ((msg = xpc_get_deliverable_msg(ch)) != NULL) {
+
+ /*
+ * This ref is taken to protect the payload itself from being
+ * freed before the user is finished with it, which the user
+ * indicates by calling xpc_initiate_received().
+ */
+ xpc_msgqueue_ref(ch);
+
+ atomic_inc(&ch->kthreads_active);
+
+ if (ch->func != NULL) {
+ dev_dbg(xpc_chan, "ch->func() called, msg=0x%p, "
+ "msg_number=%ld, partid=%d, channel=%d\n",
+ (void *) msg, msg->number, ch->partid,
+ ch->number);
+
+ /* deliver the message to its intended recipient */
+ ch->func(xpcMsgReceived, ch->partid, ch->number,
+ &msg->payload, ch->key);
+
+ dev_dbg(xpc_chan, "ch->func() returned, msg=0x%p, "
+ "msg_number=%ld, partid=%d, channel=%d\n",
+ (void *) msg, msg->number, ch->partid,
+ ch->number);
+ }
+
+ atomic_dec(&ch->kthreads_active);
+ }
+}
+
+
+/*
+ * Now we actually acknowledge the messages that have been delivered and ack'd
+ * by advancing the cached remote message queue's Get value and if requested
+ * send an IPI to the message sender's partition.
+ */
+static void
+xpc_acknowledge_msgs(struct xpc_channel *ch, s64 initial_get, u8 msg_flags)
+{
+ struct xpc_msg *msg;
+ s64 get = initial_get + 1;
+ int send_IPI = 0;
+
+
+ while (1) {
+
+ while (1) {
+ if (get == (volatile s64) ch->w_local_GP.get) {
+ break;
+ }
+
+ msg = (struct xpc_msg *) ((u64) ch->remote_msgqueue +
+ (get % ch->remote_nentries) * ch->msg_size);
+
+ if (!(msg->flags & XPC_M_DONE)) {
+ break;
+ }
+
+ msg_flags |= msg->flags;
+ get++;
+ }
+
+ if (get == initial_get) {
+ /* nothing's changed */
+ break;
+ }
+
+ if (cmpxchg_rel(&ch->local_GP->get, initial_get, get) !=
+ initial_get) {
+ /* someone else beat us to it */
+ DBUG_ON((volatile s64) ch->local_GP->get <=
+ initial_get);
+ break;
+ }
+
+ /* we just set the new value of local_GP->get */
+
+ dev_dbg(xpc_chan, "local_GP->get changed to %ld, partid=%d, "
+ "channel=%d\n", get, ch->partid, ch->number);
+
+ send_IPI = (msg_flags & XPC_M_INTERRUPT);
+
+ /*
+ * We need to ensure that the message referenced by
+ * local_GP->get is not XPC_M_DONE or that local_GP->get
+ * equals w_local_GP.get, so we'll go have a look.
+ */
+ initial_get = get;
+ }
+
+ if (send_IPI) {
+ xpc_IPI_send_msgrequest(ch);
+ }
+}
+
+
+/*
+ * Acknowledge receipt of a delivered message.
+ *
+ * If a message has XPC_M_INTERRUPT set, send an interrupt to the partition
+ * that sent the message.
+ *
+ * This function, although called by users, does not call xpc_part_ref() to
+ * ensure that the partition infrastructure is in place. It relies on the
+ * fact that we called xpc_msgqueue_ref() in xpc_deliver_msg().
+ *
+ * Arguments:
+ *
+ * partid - ID of partition to which the channel is connected.
+ * ch_number - channel # message received on.
+ * payload - pointer to the payload area allocated via
+ * xpc_initiate_allocate().
+ */
+void
+xpc_initiate_received(partid_t partid, int ch_number, void *payload)
+{
+ struct xpc_partition *part = &xpc_partitions[partid];
+ struct xpc_channel *ch;
+ struct xpc_msg *msg = XPC_MSG_ADDRESS(payload);
+ s64 get, msg_number = msg->number;
+
+
+ DBUG_ON(partid <= 0 || partid >= XP_MAX_PARTITIONS);
+ DBUG_ON(ch_number < 0 || ch_number >= part->nchannels);
+
+ ch = &part->channels[ch_number];
+
+ dev_dbg(xpc_chan, "msg=0x%p, msg_number=%ld, partid=%d, channel=%d\n",
+ (void *) msg, msg_number, ch->partid, ch->number);
+
+ DBUG_ON((((u64) msg - (u64) ch->remote_msgqueue) / ch->msg_size) !=
+ msg_number % ch->remote_nentries);
+ DBUG_ON(msg->flags & XPC_M_DONE);
+
+ msg->flags |= XPC_M_DONE;
+
+ /*
+ * The preceding store of msg->flags must occur before the following
+ * load of ch->local_GP->get.
+ */
+ mb();
+
+ /*
+ * See if this message is next in line to be acknowledged as having
+ * been delivered.
+ */
+ get = ch->local_GP->get;
+ if (get == msg_number) {
+ xpc_acknowledge_msgs(ch, get, msg->flags);
+ }
+
+ /* the call to xpc_msgqueue_ref() was done by xpc_deliver_msg() */
+ xpc_msgqueue_deref(ch);
+}
+
diff --git a/arch/ia64/sn/kernel/xpc_main.c b/arch/ia64/sn/kernel/xpc_main.c
new file mode 100644
index 000000000000..177ddb748ebe
--- /dev/null
+++ b/arch/ia64/sn/kernel/xpc_main.c
@@ -0,0 +1,1064 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2004-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+
+/*
+ * Cross Partition Communication (XPC) support - standard version.
+ *
+ * XPC provides a message passing capability that crosses partition
+ * boundaries. This module is made up of two parts:
+ *
+ * partition This part detects the presence/absence of other
+ * partitions. It provides a heartbeat and monitors
+ * the heartbeats of other partitions.
+ *
+ * channel This part manages the channels and sends/receives
+ * messages across them to/from other partitions.
+ *
+ * There are a couple of additional functions residing in XP, which
+ * provide an interface to XPC for its users.
+ *
+ *
+ * Caveats:
+ *
+ * . We currently have no way to determine which nasid an IPI came
+ * from. Thus, xpc_IPI_send() does a remote AMO write followed by
+ * an IPI. The AMO indicates where data is to be pulled from, so
+ * after the IPI arrives, the remote partition checks the AMO word.
+ * The IPI can actually arrive before the AMO however, so other code
+ * must periodically check for this case. Also, remote AMO operations
+ * do not reliably time out. Thus we do a remote PIO read solely to
+ * know whether the remote partition is down and whether we should
+ * stop sending IPIs to it. This remote PIO read operation is set up
+ * in a special nofault region so SAL knows to ignore (and cleanup)
+ * any errors due to the remote AMO write, PIO read, and/or PIO
+ * write operations.
+ *
+ * If/when new hardware solves this IPI problem, we should abandon
+ * the current approach.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/syscalls.h>
+#include <linux/cache.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <asm/sn/intr.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/uaccess.h>
+#include "xpc.h"
+
+
+/* define two XPC debug device structures to be used with dev_dbg() et al */
+
+struct device_driver xpc_dbg_name = {
+ .name = "xpc"
+};
+
+struct device xpc_part_dbg_subname = {
+ .bus_id = {0}, /* set to "part" at xpc_init() time */
+ .driver = &xpc_dbg_name
+};
+
+struct device xpc_chan_dbg_subname = {
+ .bus_id = {0}, /* set to "chan" at xpc_init() time */
+ .driver = &xpc_dbg_name
+};
+
+struct device *xpc_part = &xpc_part_dbg_subname;
+struct device *xpc_chan = &xpc_chan_dbg_subname;
+
+
+/* systune related variables for /proc/sys directories */
+
+static int xpc_hb_min = 1;
+static int xpc_hb_max = 10;
+
+static int xpc_hb_check_min = 10;
+static int xpc_hb_check_max = 120;
+
+static ctl_table xpc_sys_xpc_hb_dir[] = {
+ {
+ 1,
+ "hb_interval",
+ &xpc_hb_interval,
+ sizeof(int),
+ 0644,
+ NULL,
+ &proc_dointvec_minmax,
+ &sysctl_intvec,
+ NULL,
+ &xpc_hb_min, &xpc_hb_max
+ },
+ {
+ 2,
+ "hb_check_interval",
+ &xpc_hb_check_interval,
+ sizeof(int),
+ 0644,
+ NULL,
+ &proc_dointvec_minmax,
+ &sysctl_intvec,
+ NULL,
+ &xpc_hb_check_min, &xpc_hb_check_max
+ },
+ {0}
+};
+static ctl_table xpc_sys_xpc_dir[] = {
+ {
+ 1,
+ "hb",
+ NULL,
+ 0,
+ 0555,
+ xpc_sys_xpc_hb_dir
+ },
+ {0}
+};
+static ctl_table xpc_sys_dir[] = {
+ {
+ 1,
+ "xpc",
+ NULL,
+ 0,
+ 0555,
+ xpc_sys_xpc_dir
+ },
+ {0}
+};
+static struct ctl_table_header *xpc_sysctl;
+
+
+/* #of IRQs received */
+static atomic_t xpc_act_IRQ_rcvd;
+
+/* IRQ handler notifies this wait queue on receipt of an IRQ */
+static DECLARE_WAIT_QUEUE_HEAD(xpc_act_IRQ_wq);
+
+static unsigned long xpc_hb_check_timeout;
+
+/* xpc_hb_checker thread exited notification */
+static DECLARE_MUTEX_LOCKED(xpc_hb_checker_exited);
+
+/* xpc_discovery thread exited notification */
+static DECLARE_MUTEX_LOCKED(xpc_discovery_exited);
+
+
+static struct timer_list xpc_hb_timer;
+
+
+static void xpc_kthread_waitmsgs(struct xpc_partition *, struct xpc_channel *);
+
+
+/*
+ * Notify the heartbeat check thread that an IRQ has been received.
+ */
+static irqreturn_t
+xpc_act_IRQ_handler(int irq, void *dev_id, struct pt_regs *regs)
+{
+ atomic_inc(&xpc_act_IRQ_rcvd);
+ wake_up_interruptible(&xpc_act_IRQ_wq);
+ return IRQ_HANDLED;
+}
+
+
+/*
+ * Timer to produce the heartbeat. The timer structures function is
+ * already set when this is initially called. A tunable is used to
+ * specify when the next timeout should occur.
+ */
+static void
+xpc_hb_beater(unsigned long dummy)
+{
+ xpc_vars->heartbeat++;
+
+ if (jiffies >= xpc_hb_check_timeout) {
+ wake_up_interruptible(&xpc_act_IRQ_wq);
+ }
+
+ xpc_hb_timer.expires = jiffies + (xpc_hb_interval * HZ);
+ add_timer(&xpc_hb_timer);
+}
+
+
+/*
+ * This thread is responsible for nearly all of the partition
+ * activation/deactivation.
+ */
+static int
+xpc_hb_checker(void *ignore)
+{
+ int last_IRQ_count = 0;
+ int new_IRQ_count;
+ int force_IRQ=0;
+
+
+ /* this thread was marked active by xpc_hb_init() */
+
+ daemonize(XPC_HB_CHECK_THREAD_NAME);
+
+ set_cpus_allowed(current, cpumask_of_cpu(XPC_HB_CHECK_CPU));
+
+ xpc_hb_check_timeout = jiffies + (xpc_hb_check_interval * HZ);
+
+ while (!(volatile int) xpc_exiting) {
+
+ /* wait for IRQ or timeout */
+ (void) wait_event_interruptible(xpc_act_IRQ_wq,
+ (last_IRQ_count < atomic_read(&xpc_act_IRQ_rcvd) ||
+ jiffies >= xpc_hb_check_timeout ||
+ (volatile int) xpc_exiting));
+
+ dev_dbg(xpc_part, "woke up with %d ticks rem; %d IRQs have "
+ "been received\n",
+ (int) (xpc_hb_check_timeout - jiffies),
+ atomic_read(&xpc_act_IRQ_rcvd) - last_IRQ_count);
+
+
+ /* checking of remote heartbeats is skewed by IRQ handling */
+ if (jiffies >= xpc_hb_check_timeout) {
+ dev_dbg(xpc_part, "checking remote heartbeats\n");
+ xpc_check_remote_hb();
+
+ /*
+ * We need to periodically recheck to ensure no
+ * IPI/AMO pairs have been missed. That check
+ * must always reset xpc_hb_check_timeout.
+ */
+ force_IRQ = 1;
+ }
+
+
+ new_IRQ_count = atomic_read(&xpc_act_IRQ_rcvd);
+ if (last_IRQ_count < new_IRQ_count || force_IRQ != 0) {
+ force_IRQ = 0;
+
+ dev_dbg(xpc_part, "found an IRQ to process; will be "
+ "resetting xpc_hb_check_timeout\n");
+
+ last_IRQ_count += xpc_identify_act_IRQ_sender();
+ if (last_IRQ_count < new_IRQ_count) {
+ /* retry once to help avoid missing AMO */
+ (void) xpc_identify_act_IRQ_sender();
+ }
+ last_IRQ_count = new_IRQ_count;
+
+ xpc_hb_check_timeout = jiffies +
+ (xpc_hb_check_interval * HZ);
+ }
+ }
+
+ dev_dbg(xpc_part, "heartbeat checker is exiting\n");
+
+
+ /* mark this thread as inactive */
+ up(&xpc_hb_checker_exited);
+ return 0;
+}
+
+
+/*
+ * This thread will attempt to discover other partitions to activate
+ * based on info provided by SAL. This new thread is short lived and
+ * will exit once discovery is complete.
+ */
+static int
+xpc_initiate_discovery(void *ignore)
+{
+ daemonize(XPC_DISCOVERY_THREAD_NAME);
+
+ xpc_discovery();
+
+ dev_dbg(xpc_part, "discovery thread is exiting\n");
+
+ /* mark this thread as inactive */
+ up(&xpc_discovery_exited);
+ return 0;
+}
+
+
+/*
+ * Establish first contact with the remote partititon. This involves pulling
+ * the XPC per partition variables from the remote partition and waiting for
+ * the remote partition to pull ours.
+ */
+static enum xpc_retval
+xpc_make_first_contact(struct xpc_partition *part)
+{
+ enum xpc_retval ret;
+
+
+ while ((ret = xpc_pull_remote_vars_part(part)) != xpcSuccess) {
+ if (ret != xpcRetry) {
+ XPC_DEACTIVATE_PARTITION(part, ret);
+ return ret;
+ }
+
+ dev_dbg(xpc_chan, "waiting to make first contact with "
+ "partition %d\n", XPC_PARTID(part));
+
+ /* wait a 1/4 of a second or so */
+ set_current_state(TASK_INTERRUPTIBLE);
+ (void) schedule_timeout(0.25 * HZ);
+
+ if (part->act_state == XPC_P_DEACTIVATING) {
+ return part->reason;
+ }
+ }
+
+ return xpc_mark_partition_active(part);
+}
+
+
+/*
+ * The first kthread assigned to a newly activated partition is the one
+ * created by XPC HB with which it calls xpc_partition_up(). XPC hangs on to
+ * that kthread until the partition is brought down, at which time that kthread
+ * returns back to XPC HB. (The return of that kthread will signify to XPC HB
+ * that XPC has dismantled all communication infrastructure for the associated
+ * partition.) This kthread becomes the channel manager for that partition.
+ *
+ * Each active partition has a channel manager, who, besides connecting and
+ * disconnecting channels, will ensure that each of the partition's connected
+ * channels has the required number of assigned kthreads to get the work done.
+ */
+static void
+xpc_channel_mgr(struct xpc_partition *part)
+{
+ while (part->act_state != XPC_P_DEACTIVATING ||
+ atomic_read(&part->nchannels_active) > 0) {
+
+ xpc_process_channel_activity(part);
+
+
+ /*
+ * Wait until we've been requested to activate kthreads or
+ * all of the channel's message queues have been torn down or
+ * a signal is pending.
+ *
+ * The channel_mgr_requests is set to 1 after being awakened,
+ * This is done to prevent the channel mgr from making one pass
+ * through the loop for each request, since he will
+ * be servicing all the requests in one pass. The reason it's
+ * set to 1 instead of 0 is so that other kthreads will know
+ * that the channel mgr is running and won't bother trying to
+ * wake him up.
+ */
+ atomic_dec(&part->channel_mgr_requests);
+ (void) wait_event_interruptible(part->channel_mgr_wq,
+ (atomic_read(&part->channel_mgr_requests) > 0 ||
+ (volatile u64) part->local_IPI_amo != 0 ||
+ ((volatile u8) part->act_state ==
+ XPC_P_DEACTIVATING &&
+ atomic_read(&part->nchannels_active) == 0)));
+ atomic_set(&part->channel_mgr_requests, 1);
+
+ // >>> Does it need to wakeup periodically as well? In case we
+ // >>> miscalculated the #of kthreads to wakeup or create?
+ }
+}
+
+
+/*
+ * When XPC HB determines that a partition has come up, it will create a new
+ * kthread and that kthread will call this function to attempt to set up the
+ * basic infrastructure used for Cross Partition Communication with the newly
+ * upped partition.
+ *
+ * The kthread that was created by XPC HB and which setup the XPC
+ * infrastructure will remain assigned to the partition until the partition
+ * goes down. At which time the kthread will teardown the XPC infrastructure
+ * and then exit.
+ *
+ * XPC HB will put the remote partition's XPC per partition specific variables
+ * physical address into xpc_partitions[partid].remote_vars_part_pa prior to
+ * calling xpc_partition_up().
+ */
+static void
+xpc_partition_up(struct xpc_partition *part)
+{
+ DBUG_ON(part->channels != NULL);
+
+ dev_dbg(xpc_chan, "activating partition %d\n", XPC_PARTID(part));
+
+ if (xpc_setup_infrastructure(part) != xpcSuccess) {
+ return;
+ }
+
+ /*
+ * The kthread that XPC HB called us with will become the
+ * channel manager for this partition. It will not return
+ * back to XPC HB until the partition's XPC infrastructure
+ * has been dismantled.
+ */
+
+ (void) xpc_part_ref(part); /* this will always succeed */
+
+ if (xpc_make_first_contact(part) == xpcSuccess) {
+ xpc_channel_mgr(part);
+ }
+
+ xpc_part_deref(part);
+
+ xpc_teardown_infrastructure(part);
+}
+
+
+static int
+xpc_activating(void *__partid)
+{
+ partid_t partid = (u64) __partid;
+ struct xpc_partition *part = &xpc_partitions[partid];
+ unsigned long irq_flags;
+ struct sched_param param = { sched_priority: MAX_USER_RT_PRIO - 1 };
+ int ret;
+
+
+ DBUG_ON(partid <= 0 || partid >= XP_MAX_PARTITIONS);
+
+ spin_lock_irqsave(&part->act_lock, irq_flags);
+
+ if (part->act_state == XPC_P_DEACTIVATING) {
+ part->act_state = XPC_P_INACTIVE;
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+ part->remote_rp_pa = 0;
+ return 0;
+ }
+
+ /* indicate the thread is activating */
+ DBUG_ON(part->act_state != XPC_P_ACTIVATION_REQ);
+ part->act_state = XPC_P_ACTIVATING;
+
+ XPC_SET_REASON(part, 0, 0);
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+
+ dev_dbg(xpc_part, "bringing partition %d up\n", partid);
+
+ daemonize("xpc%02d", partid);
+
+ /*
+ * This thread needs to run at a realtime priority to prevent a
+ * significant performance degradation.
+ */
+ ret = sched_setscheduler(current, SCHED_FIFO, &param);
+ if (ret != 0) {
+ dev_warn(xpc_part, "unable to set pid %d to a realtime "
+ "priority, ret=%d\n", current->pid, ret);
+ }
+
+ /* allow this thread and its children to run on any CPU */
+ set_cpus_allowed(current, CPU_MASK_ALL);
+
+ /*
+ * Register the remote partition's AMOs with SAL so it can handle
+ * and cleanup errors within that address range should the remote
+ * partition go down. We don't unregister this range because it is
+ * difficult to tell when outstanding writes to the remote partition
+ * are finished and thus when it is safe to unregister. This should
+ * not result in wasted space in the SAL xp_addr_region table because
+ * we should get the same page for remote_amos_page_pa after module
+ * reloads and system reboots.
+ */
+ if (sn_register_xp_addr_region(part->remote_amos_page_pa,
+ PAGE_SIZE, 1) < 0) {
+ dev_warn(xpc_part, "xpc_partition_up(%d) failed to register "
+ "xp_addr region\n", partid);
+
+ spin_lock_irqsave(&part->act_lock, irq_flags);
+ part->act_state = XPC_P_INACTIVE;
+ XPC_SET_REASON(part, xpcPhysAddrRegFailed, __LINE__);
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+ part->remote_rp_pa = 0;
+ return 0;
+ }
+
+ XPC_ALLOW_HB(partid, xpc_vars);
+ xpc_IPI_send_activated(part);
+
+
+ /*
+ * xpc_partition_up() holds this thread and marks this partition as
+ * XPC_P_ACTIVE by calling xpc_hb_mark_active().
+ */
+ (void) xpc_partition_up(part);
+
+ xpc_mark_partition_inactive(part);
+
+ if (part->reason == xpcReactivating) {
+ /* interrupting ourselves results in activating partition */
+ xpc_IPI_send_reactivate(part);
+ }
+
+ return 0;
+}
+
+
+void
+xpc_activate_partition(struct xpc_partition *part)
+{
+ partid_t partid = XPC_PARTID(part);
+ unsigned long irq_flags;
+ pid_t pid;
+
+
+ spin_lock_irqsave(&part->act_lock, irq_flags);
+
+ pid = kernel_thread(xpc_activating, (void *) ((u64) partid), 0);
+
+ DBUG_ON(part->act_state != XPC_P_INACTIVE);
+
+ if (pid > 0) {
+ part->act_state = XPC_P_ACTIVATION_REQ;
+ XPC_SET_REASON(part, xpcCloneKThread, __LINE__);
+ } else {
+ XPC_SET_REASON(part, xpcCloneKThreadFailed, __LINE__);
+ }
+
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+}
+
+
+/*
+ * Handle the receipt of a SGI_XPC_NOTIFY IRQ by seeing whether the specified
+ * partition actually sent it. Since SGI_XPC_NOTIFY IRQs may be shared by more
+ * than one partition, we use an AMO_t structure per partition to indicate
+ * whether a partition has sent an IPI or not. >>> If it has, then wake up the
+ * associated kthread to handle it.
+ *
+ * All SGI_XPC_NOTIFY IRQs received by XPC are the result of IPIs sent by XPC
+ * running on other partitions.
+ *
+ * Noteworthy Arguments:
+ *
+ * irq - Interrupt ReQuest number. NOT USED.
+ *
+ * dev_id - partid of IPI's potential sender.
+ *
+ * regs - processor's context before the processor entered
+ * interrupt code. NOT USED.
+ */
+irqreturn_t
+xpc_notify_IRQ_handler(int irq, void *dev_id, struct pt_regs *regs)
+{
+ partid_t partid = (partid_t) (u64) dev_id;
+ struct xpc_partition *part = &xpc_partitions[partid];
+
+
+ DBUG_ON(partid <= 0 || partid >= XP_MAX_PARTITIONS);
+
+ if (xpc_part_ref(part)) {
+ xpc_check_for_channel_activity(part);
+
+ xpc_part_deref(part);
+ }
+ return IRQ_HANDLED;
+}
+
+
+/*
+ * Check to see if xpc_notify_IRQ_handler() dropped any IPIs on the floor
+ * because the write to their associated IPI amo completed after the IRQ/IPI
+ * was received.
+ */
+void
+xpc_dropped_IPI_check(struct xpc_partition *part)
+{
+ if (xpc_part_ref(part)) {
+ xpc_check_for_channel_activity(part);
+
+ part->dropped_IPI_timer.expires = jiffies +
+ XPC_P_DROPPED_IPI_WAIT;
+ add_timer(&part->dropped_IPI_timer);
+ xpc_part_deref(part);
+ }
+}
+
+
+void
+xpc_activate_kthreads(struct xpc_channel *ch, int needed)
+{
+ int idle = atomic_read(&ch->kthreads_idle);
+ int assigned = atomic_read(&ch->kthreads_assigned);
+ int wakeup;
+
+
+ DBUG_ON(needed <= 0);
+
+ if (idle > 0) {
+ wakeup = (needed > idle) ? idle : needed;
+ needed -= wakeup;
+
+ dev_dbg(xpc_chan, "wakeup %d idle kthreads, partid=%d, "
+ "channel=%d\n", wakeup, ch->partid, ch->number);
+
+ /* only wakeup the requested number of kthreads */
+ wake_up_nr(&ch->idle_wq, wakeup);
+ }
+
+ if (needed <= 0) {
+ return;
+ }
+
+ if (needed + assigned > ch->kthreads_assigned_limit) {
+ needed = ch->kthreads_assigned_limit - assigned;
+ // >>>should never be less than 0
+ if (needed <= 0) {
+ return;
+ }
+ }
+
+ dev_dbg(xpc_chan, "create %d new kthreads, partid=%d, channel=%d\n",
+ needed, ch->partid, ch->number);
+
+ xpc_create_kthreads(ch, needed);
+}
+
+
+/*
+ * This function is where XPC's kthreads wait for messages to deliver.
+ */
+static void
+xpc_kthread_waitmsgs(struct xpc_partition *part, struct xpc_channel *ch)
+{
+ do {
+ /* deliver messages to their intended recipients */
+
+ while ((volatile s64) ch->w_local_GP.get <
+ (volatile s64) ch->w_remote_GP.put &&
+ !((volatile u32) ch->flags &
+ XPC_C_DISCONNECTING)) {
+ xpc_deliver_msg(ch);
+ }
+
+ if (atomic_inc_return(&ch->kthreads_idle) >
+ ch->kthreads_idle_limit) {
+ /* too many idle kthreads on this channel */
+ atomic_dec(&ch->kthreads_idle);
+ break;
+ }
+
+ dev_dbg(xpc_chan, "idle kthread calling "
+ "wait_event_interruptible_exclusive()\n");
+
+ (void) wait_event_interruptible_exclusive(ch->idle_wq,
+ ((volatile s64) ch->w_local_GP.get <
+ (volatile s64) ch->w_remote_GP.put ||
+ ((volatile u32) ch->flags &
+ XPC_C_DISCONNECTING)));
+
+ atomic_dec(&ch->kthreads_idle);
+
+ } while (!((volatile u32) ch->flags & XPC_C_DISCONNECTING));
+}
+
+
+static int
+xpc_daemonize_kthread(void *args)
+{
+ partid_t partid = XPC_UNPACK_ARG1(args);
+ u16 ch_number = XPC_UNPACK_ARG2(args);
+ struct xpc_partition *part = &xpc_partitions[partid];
+ struct xpc_channel *ch;
+ int n_needed;
+
+
+ daemonize("xpc%02dc%d", partid, ch_number);
+
+ dev_dbg(xpc_chan, "kthread starting, partid=%d, channel=%d\n",
+ partid, ch_number);
+
+ ch = &part->channels[ch_number];
+
+ if (!(ch->flags & XPC_C_DISCONNECTING)) {
+ DBUG_ON(!(ch->flags & XPC_C_CONNECTED));
+
+ /* let registerer know that connection has been established */
+
+ if (atomic_read(&ch->kthreads_assigned) == 1) {
+ xpc_connected_callout(ch);
+
+ /*
+ * It is possible that while the callout was being
+ * made that the remote partition sent some messages.
+ * If that is the case, we may need to activate
+ * additional kthreads to help deliver them. We only
+ * need one less than total #of messages to deliver.
+ */
+ n_needed = ch->w_remote_GP.put - ch->w_local_GP.get - 1;
+ if (n_needed > 0 &&
+ !(ch->flags & XPC_C_DISCONNECTING)) {
+ xpc_activate_kthreads(ch, n_needed);
+ }
+ }
+
+ xpc_kthread_waitmsgs(part, ch);
+ }
+
+ if (atomic_dec_return(&ch->kthreads_assigned) == 0 &&
+ ((ch->flags & XPC_C_CONNECTCALLOUT) ||
+ (ch->reason != xpcUnregistering &&
+ ch->reason != xpcOtherUnregistering))) {
+ xpc_disconnected_callout(ch);
+ }
+
+
+ xpc_msgqueue_deref(ch);
+
+ dev_dbg(xpc_chan, "kthread exiting, partid=%d, channel=%d\n",
+ partid, ch_number);
+
+ xpc_part_deref(part);
+ return 0;
+}
+
+
+/*
+ * For each partition that XPC has established communications with, there is
+ * a minimum of one kernel thread assigned to perform any operation that
+ * may potentially sleep or block (basically the callouts to the asynchronous
+ * functions registered via xpc_connect()).
+ *
+ * Additional kthreads are created and destroyed by XPC as the workload
+ * demands.
+ *
+ * A kthread is assigned to one of the active channels that exists for a given
+ * partition.
+ */
+void
+xpc_create_kthreads(struct xpc_channel *ch, int needed)
+{
+ unsigned long irq_flags;
+ pid_t pid;
+ u64 args = XPC_PACK_ARGS(ch->partid, ch->number);
+
+
+ while (needed-- > 0) {
+ pid = kernel_thread(xpc_daemonize_kthread, (void *) args, 0);
+ if (pid < 0) {
+ /* the fork failed */
+
+ if (atomic_read(&ch->kthreads_assigned) <
+ ch->kthreads_idle_limit) {
+ /*
+ * Flag this as an error only if we have an
+ * insufficient #of kthreads for the channel
+ * to function.
+ *
+ * No xpc_msgqueue_ref() is needed here since
+ * the channel mgr is doing this.
+ */
+ spin_lock_irqsave(&ch->lock, irq_flags);
+ XPC_DISCONNECT_CHANNEL(ch, xpcLackOfResources,
+ &irq_flags);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ }
+ break;
+ }
+
+ /*
+ * The following is done on behalf of the newly created
+ * kthread. That kthread is responsible for doing the
+ * counterpart to the following before it exits.
+ */
+ (void) xpc_part_ref(&xpc_partitions[ch->partid]);
+ xpc_msgqueue_ref(ch);
+ atomic_inc(&ch->kthreads_assigned);
+ ch->kthreads_created++; // >>> temporary debug only!!!
+ }
+}
+
+
+void
+xpc_disconnect_wait(int ch_number)
+{
+ partid_t partid;
+ struct xpc_partition *part;
+ struct xpc_channel *ch;
+
+
+ /* now wait for all callouts to the caller's function to cease */
+ for (partid = 1; partid < XP_MAX_PARTITIONS; partid++) {
+ part = &xpc_partitions[partid];
+
+ if (xpc_part_ref(part)) {
+ ch = &part->channels[ch_number];
+
+// >>> how do we keep from falling into the window between our check and going
+// >>> down and coming back up where sema is re-inited?
+ if (ch->flags & XPC_C_SETUP) {
+ (void) down(&ch->teardown_sema);
+ }
+
+ xpc_part_deref(part);
+ }
+ }
+}
+
+
+static void
+xpc_do_exit(void)
+{
+ partid_t partid;
+ int active_part_count;
+ struct xpc_partition *part;
+
+
+ /* now it's time to eliminate our heartbeat */
+ del_timer_sync(&xpc_hb_timer);
+ xpc_vars->heartbeating_to_mask = 0;
+
+ /* indicate to others that our reserved page is uninitialized */
+ xpc_rsvd_page->vars_pa = 0;
+
+ /*
+ * Ignore all incoming interrupts. Without interupts the heartbeat
+ * checker won't activate any new partitions that may come up.
+ */
+ free_irq(SGI_XPC_ACTIVATE, NULL);
+
+ /*
+ * Cause the heartbeat checker and the discovery threads to exit.
+ * We don't want them attempting to activate new partitions as we
+ * try to deactivate the existing ones.
+ */
+ xpc_exiting = 1;
+ wake_up_interruptible(&xpc_act_IRQ_wq);
+
+ /* wait for the heartbeat checker thread to mark itself inactive */
+ down(&xpc_hb_checker_exited);
+
+ /* wait for the discovery thread to mark itself inactive */
+ down(&xpc_discovery_exited);
+
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(0.3 * HZ);
+ set_current_state(TASK_RUNNING);
+
+
+ /* wait for all partitions to become inactive */
+
+ do {
+ active_part_count = 0;
+
+ for (partid = 1; partid < XP_MAX_PARTITIONS; partid++) {
+ part = &xpc_partitions[partid];
+ if (part->act_state != XPC_P_INACTIVE) {
+ active_part_count++;
+
+ XPC_DEACTIVATE_PARTITION(part, xpcUnloading);
+ }
+ }
+
+ if (active_part_count) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(0.3 * HZ);
+ set_current_state(TASK_RUNNING);
+ }
+
+ } while (active_part_count > 0);
+
+
+ /* close down protections for IPI operations */
+ xpc_restrict_IPI_ops();
+
+
+ /* clear the interface to XPC's functions */
+ xpc_clear_interface();
+
+ if (xpc_sysctl) {
+ unregister_sysctl_table(xpc_sysctl);
+ }
+}
+
+
+int __init
+xpc_init(void)
+{
+ int ret;
+ partid_t partid;
+ struct xpc_partition *part;
+ pid_t pid;
+
+
+ /*
+ * xpc_remote_copy_buffer is used as a temporary buffer for bte_copy'ng
+ * both a partition's reserved page and its XPC variables. Its size was
+ * based on the size of a reserved page. So we need to ensure that the
+ * XPC variables will fit as well.
+ */
+ if (XPC_VARS_ALIGNED_SIZE > XPC_RSVD_PAGE_ALIGNED_SIZE) {
+ dev_err(xpc_part, "xpc_remote_copy_buffer is not big enough\n");
+ return -EPERM;
+ }
+ DBUG_ON((u64) xpc_remote_copy_buffer !=
+ L1_CACHE_ALIGN((u64) xpc_remote_copy_buffer));
+
+ snprintf(xpc_part->bus_id, BUS_ID_SIZE, "part");
+ snprintf(xpc_chan->bus_id, BUS_ID_SIZE, "chan");
+
+ xpc_sysctl = register_sysctl_table(xpc_sys_dir, 1);
+
+ /*
+ * The first few fields of each entry of xpc_partitions[] need to
+ * be initialized now so that calls to xpc_connect() and
+ * xpc_disconnect() can be made prior to the activation of any remote
+ * partition. NOTE THAT NONE OF THE OTHER FIELDS BELONGING TO THESE
+ * ENTRIES ARE MEANINGFUL UNTIL AFTER AN ENTRY'S CORRESPONDING
+ * PARTITION HAS BEEN ACTIVATED.
+ */
+ for (partid = 1; partid < XP_MAX_PARTITIONS; partid++) {
+ part = &xpc_partitions[partid];
+
+ DBUG_ON((u64) part != L1_CACHE_ALIGN((u64) part));
+
+ part->act_IRQ_rcvd = 0;
+ spin_lock_init(&part->act_lock);
+ part->act_state = XPC_P_INACTIVE;
+ XPC_SET_REASON(part, 0, 0);
+ part->setup_state = XPC_P_UNSET;
+ init_waitqueue_head(&part->teardown_wq);
+ atomic_set(&part->references, 0);
+ }
+
+ /*
+ * Open up protections for IPI operations (and AMO operations on
+ * Shub 1.1 systems).
+ */
+ xpc_allow_IPI_ops();
+
+ /*
+ * Interrupts being processed will increment this atomic variable and
+ * awaken the heartbeat thread which will process the interrupts.
+ */
+ atomic_set(&xpc_act_IRQ_rcvd, 0);
+
+ /*
+ * This is safe to do before the xpc_hb_checker thread has started
+ * because the handler releases a wait queue. If an interrupt is
+ * received before the thread is waiting, it will not go to sleep,
+ * but rather immediately process the interrupt.
+ */
+ ret = request_irq(SGI_XPC_ACTIVATE, xpc_act_IRQ_handler, 0,
+ "xpc hb", NULL);
+ if (ret != 0) {
+ dev_err(xpc_part, "can't register ACTIVATE IRQ handler, "
+ "errno=%d\n", -ret);
+
+ xpc_restrict_IPI_ops();
+
+ if (xpc_sysctl) {
+ unregister_sysctl_table(xpc_sysctl);
+ }
+ return -EBUSY;
+ }
+
+ /*
+ * Fill the partition reserved page with the information needed by
+ * other partitions to discover we are alive and establish initial
+ * communications.
+ */
+ xpc_rsvd_page = xpc_rsvd_page_init();
+ if (xpc_rsvd_page == NULL) {
+ dev_err(xpc_part, "could not setup our reserved page\n");
+
+ free_irq(SGI_XPC_ACTIVATE, NULL);
+ xpc_restrict_IPI_ops();
+
+ if (xpc_sysctl) {
+ unregister_sysctl_table(xpc_sysctl);
+ }
+ return -EBUSY;
+ }
+
+
+ /*
+ * Set the beating to other partitions into motion. This is
+ * the last requirement for other partitions' discovery to
+ * initiate communications with us.
+ */
+ init_timer(&xpc_hb_timer);
+ xpc_hb_timer.function = xpc_hb_beater;
+ xpc_hb_beater(0);
+
+
+ /*
+ * The real work-horse behind xpc. This processes incoming
+ * interrupts and monitors remote heartbeats.
+ */
+ pid = kernel_thread(xpc_hb_checker, NULL, 0);
+ if (pid < 0) {
+ dev_err(xpc_part, "failed while forking hb check thread\n");
+
+ /* indicate to others that our reserved page is uninitialized */
+ xpc_rsvd_page->vars_pa = 0;
+
+ del_timer_sync(&xpc_hb_timer);
+ free_irq(SGI_XPC_ACTIVATE, NULL);
+ xpc_restrict_IPI_ops();
+
+ if (xpc_sysctl) {
+ unregister_sysctl_table(xpc_sysctl);
+ }
+ return -EBUSY;
+ }
+
+
+ /*
+ * Startup a thread that will attempt to discover other partitions to
+ * activate based on info provided by SAL. This new thread is short
+ * lived and will exit once discovery is complete.
+ */
+ pid = kernel_thread(xpc_initiate_discovery, NULL, 0);
+ if (pid < 0) {
+ dev_err(xpc_part, "failed while forking discovery thread\n");
+
+ /* mark this new thread as a non-starter */
+ up(&xpc_discovery_exited);
+
+ xpc_do_exit();
+ return -EBUSY;
+ }
+
+
+ /* set the interface to point at XPC's functions */
+ xpc_set_interface(xpc_initiate_connect, xpc_initiate_disconnect,
+ xpc_initiate_allocate, xpc_initiate_send,
+ xpc_initiate_send_notify, xpc_initiate_received,
+ xpc_initiate_partid_to_nasids);
+
+ return 0;
+}
+module_init(xpc_init);
+
+
+void __exit
+xpc_exit(void)
+{
+ xpc_do_exit();
+}
+module_exit(xpc_exit);
+
+
+MODULE_AUTHOR("Silicon Graphics, Inc.");
+MODULE_DESCRIPTION("Cross Partition Communication (XPC) support");
+MODULE_LICENSE("GPL");
+
+module_param(xpc_hb_interval, int, 0);
+MODULE_PARM_DESC(xpc_hb_interval, "Number of seconds between "
+ "heartbeat increments.");
+
+module_param(xpc_hb_check_interval, int, 0);
+MODULE_PARM_DESC(xpc_hb_check_interval, "Number of seconds between "
+ "heartbeat checks.");
+
diff --git a/arch/ia64/sn/kernel/xpc_partition.c b/arch/ia64/sn/kernel/xpc_partition.c
new file mode 100644
index 000000000000..2c3c4a8af553
--- /dev/null
+++ b/arch/ia64/sn/kernel/xpc_partition.c
@@ -0,0 +1,984 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2004-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+
+/*
+ * Cross Partition Communication (XPC) partition support.
+ *
+ * This is the part of XPC that detects the presence/absence of
+ * other partitions. It provides a heartbeat and monitors the
+ * heartbeats of other partitions.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/sysctl.h>
+#include <linux/cache.h>
+#include <linux/mmzone.h>
+#include <linux/nodemask.h>
+#include <asm/sn/bte.h>
+#include <asm/sn/intr.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/sn/nodepda.h>
+#include <asm/sn/addrs.h>
+#include "xpc.h"
+
+
+/* XPC is exiting flag */
+int xpc_exiting;
+
+
+/* SH_IPI_ACCESS shub register value on startup */
+static u64 xpc_sh1_IPI_access;
+static u64 xpc_sh2_IPI_access0;
+static u64 xpc_sh2_IPI_access1;
+static u64 xpc_sh2_IPI_access2;
+static u64 xpc_sh2_IPI_access3;
+
+
+/* original protection values for each node */
+u64 xpc_prot_vec[MAX_COMPACT_NODES];
+
+
+/* this partition's reserved page */
+struct xpc_rsvd_page *xpc_rsvd_page;
+
+/* this partition's XPC variables (within the reserved page) */
+struct xpc_vars *xpc_vars;
+struct xpc_vars_part *xpc_vars_part;
+
+
+/*
+ * For performance reasons, each entry of xpc_partitions[] is cacheline
+ * aligned. And xpc_partitions[] is padded with an additional entry at the
+ * end so that the last legitimate entry doesn't share its cacheline with
+ * another variable.
+ */
+struct xpc_partition xpc_partitions[XP_MAX_PARTITIONS + 1];
+
+
+/*
+ * Generic buffer used to store a local copy of the remote partitions
+ * reserved page or XPC variables.
+ *
+ * xpc_discovery runs only once and is a seperate thread that is
+ * very likely going to be processing in parallel with receiving
+ * interrupts.
+ */
+char ____cacheline_aligned
+ xpc_remote_copy_buffer[XPC_RSVD_PAGE_ALIGNED_SIZE];
+
+
+/* systune related variables */
+int xpc_hb_interval = XPC_HB_DEFAULT_INTERVAL;
+int xpc_hb_check_interval = XPC_HB_CHECK_DEFAULT_TIMEOUT;
+
+
+/*
+ * Given a nasid, get the physical address of the partition's reserved page
+ * for that nasid. This function returns 0 on any error.
+ */
+static u64
+xpc_get_rsvd_page_pa(int nasid, u64 buf, u64 buf_size)
+{
+ bte_result_t bte_res;
+ s64 status;
+ u64 cookie = 0;
+ u64 rp_pa = nasid; /* seed with nasid */
+ u64 len = 0;
+
+
+ while (1) {
+
+ status = sn_partition_reserved_page_pa(buf, &cookie, &rp_pa,
+ &len);
+
+ dev_dbg(xpc_part, "SAL returned with status=%li, cookie="
+ "0x%016lx, address=0x%016lx, len=0x%016lx\n",
+ status, cookie, rp_pa, len);
+
+ if (status != SALRET_MORE_PASSES) {
+ break;
+ }
+
+ if (len > buf_size) {
+ dev_err(xpc_part, "len (=0x%016lx) > buf_size\n", len);
+ status = SALRET_ERROR;
+ break;
+ }
+
+ bte_res = xp_bte_copy(rp_pa, ia64_tpa(buf), buf_size,
+ (BTE_NOTIFY | BTE_WACQUIRE), NULL);
+ if (bte_res != BTE_SUCCESS) {
+ dev_dbg(xpc_part, "xp_bte_copy failed %i\n", bte_res);
+ status = SALRET_ERROR;
+ break;
+ }
+ }
+
+ if (status != SALRET_OK) {
+ rp_pa = 0;
+ }
+ dev_dbg(xpc_part, "reserved page at phys address 0x%016lx\n", rp_pa);
+ return rp_pa;
+}
+
+
+/*
+ * Fill the partition reserved page with the information needed by
+ * other partitions to discover we are alive and establish initial
+ * communications.
+ */
+struct xpc_rsvd_page *
+xpc_rsvd_page_init(void)
+{
+ struct xpc_rsvd_page *rp;
+ AMO_t *amos_page;
+ u64 rp_pa, next_cl, nasid_array = 0;
+ int i, ret;
+
+
+ /* get the local reserved page's address */
+
+ rp_pa = xpc_get_rsvd_page_pa(cnodeid_to_nasid(0),
+ (u64) xpc_remote_copy_buffer,
+ XPC_RSVD_PAGE_ALIGNED_SIZE);
+ if (rp_pa == 0) {
+ dev_err(xpc_part, "SAL failed to locate the reserved page\n");
+ return NULL;
+ }
+ rp = (struct xpc_rsvd_page *) __va(rp_pa);
+
+ if (rp->partid != sn_partition_id) {
+ dev_err(xpc_part, "the reserved page's partid of %d should be "
+ "%d\n", rp->partid, sn_partition_id);
+ return NULL;
+ }
+
+ rp->version = XPC_RP_VERSION;
+
+ /*
+ * Place the XPC variables on the cache line following the
+ * reserved page structure.
+ */
+ next_cl = (u64) rp + XPC_RSVD_PAGE_ALIGNED_SIZE;
+ xpc_vars = (struct xpc_vars *) next_cl;
+
+ /*
+ * Before clearing xpc_vars, see if a page of AMOs had been previously
+ * allocated. If not we'll need to allocate one and set permissions
+ * so that cross-partition AMOs are allowed.
+ *
+ * The allocated AMO page needs MCA reporting to remain disabled after
+ * XPC has unloaded. To make this work, we keep a copy of the pointer
+ * to this page (i.e., amos_page) in the struct xpc_vars structure,
+ * which is pointed to by the reserved page, and re-use that saved copy
+ * on subsequent loads of XPC. This AMO page is never freed, and its
+ * memory protections are never restricted.
+ */
+ if ((amos_page = xpc_vars->amos_page) == NULL) {
+ amos_page = (AMO_t *) mspec_kalloc_page(0);
+ if (amos_page == NULL) {
+ dev_err(xpc_part, "can't allocate page of AMOs\n");
+ return NULL;
+ }
+
+ /*
+ * Open up AMO-R/W to cpu. This is done for Shub 1.1 systems
+ * when xpc_allow_IPI_ops() is called via xpc_hb_init().
+ */
+ if (!enable_shub_wars_1_1()) {
+ ret = sn_change_memprotect(ia64_tpa((u64) amos_page),
+ PAGE_SIZE, SN_MEMPROT_ACCESS_CLASS_1,
+ &nasid_array);
+ if (ret != 0) {
+ dev_err(xpc_part, "can't change memory "
+ "protections\n");
+ mspec_kfree_page((unsigned long) amos_page);
+ return NULL;
+ }
+ }
+ } else if (!IS_AMO_ADDRESS((u64) amos_page)) {
+ /*
+ * EFI's XPBOOT can also set amos_page in the reserved page,
+ * but it happens to leave it as an uncached physical address
+ * and we need it to be an uncached virtual, so we'll have to
+ * convert it.
+ */
+ if (!IS_AMO_PHYS_ADDRESS((u64) amos_page)) {
+ dev_err(xpc_part, "previously used amos_page address "
+ "is bad = 0x%p\n", (void *) amos_page);
+ return NULL;
+ }
+ amos_page = (AMO_t *) TO_AMO((u64) amos_page);
+ }
+
+ memset(xpc_vars, 0, sizeof(struct xpc_vars));
+
+ /*
+ * Place the XPC per partition specific variables on the cache line
+ * following the XPC variables structure.
+ */
+ next_cl += XPC_VARS_ALIGNED_SIZE;
+ memset((u64 *) next_cl, 0, sizeof(struct xpc_vars_part) *
+ XP_MAX_PARTITIONS);
+ xpc_vars_part = (struct xpc_vars_part *) next_cl;
+ xpc_vars->vars_part_pa = __pa(next_cl);
+
+ xpc_vars->version = XPC_V_VERSION;
+ xpc_vars->act_nasid = cpuid_to_nasid(0);
+ xpc_vars->act_phys_cpuid = cpu_physical_id(0);
+ xpc_vars->amos_page = amos_page; /* save for next load of XPC */
+
+
+ /*
+ * Initialize the activation related AMO variables.
+ */
+ xpc_vars->act_amos = xpc_IPI_init(XP_MAX_PARTITIONS);
+ for (i = 1; i < XP_NASID_MASK_WORDS; i++) {
+ xpc_IPI_init(i + XP_MAX_PARTITIONS);
+ }
+ /* export AMO page's physical address to other partitions */
+ xpc_vars->amos_page_pa = ia64_tpa((u64) xpc_vars->amos_page);
+
+ /*
+ * This signifies to the remote partition that our reserved
+ * page is initialized.
+ */
+ (volatile u64) rp->vars_pa = __pa(xpc_vars);
+
+ return rp;
+}
+
+
+/*
+ * Change protections to allow IPI operations (and AMO operations on
+ * Shub 1.1 systems).
+ */
+void
+xpc_allow_IPI_ops(void)
+{
+ int node;
+ int nasid;
+
+
+ // >>> Change SH_IPI_ACCESS code to use SAL call once it is available.
+
+ if (is_shub2()) {
+ xpc_sh2_IPI_access0 =
+ (u64) HUB_L((u64 *) LOCAL_MMR_ADDR(SH2_IPI_ACCESS0));
+ xpc_sh2_IPI_access1 =
+ (u64) HUB_L((u64 *) LOCAL_MMR_ADDR(SH2_IPI_ACCESS1));
+ xpc_sh2_IPI_access2 =
+ (u64) HUB_L((u64 *) LOCAL_MMR_ADDR(SH2_IPI_ACCESS2));
+ xpc_sh2_IPI_access3 =
+ (u64) HUB_L((u64 *) LOCAL_MMR_ADDR(SH2_IPI_ACCESS3));
+
+ for_each_online_node(node) {
+ nasid = cnodeid_to_nasid(node);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS0),
+ -1UL);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS1),
+ -1UL);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS2),
+ -1UL);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS3),
+ -1UL);
+ }
+
+ } else {
+ xpc_sh1_IPI_access =
+ (u64) HUB_L((u64 *) LOCAL_MMR_ADDR(SH1_IPI_ACCESS));
+
+ for_each_online_node(node) {
+ nasid = cnodeid_to_nasid(node);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH1_IPI_ACCESS),
+ -1UL);
+
+ /*
+ * Since the BIST collides with memory operations on
+ * SHUB 1.1 sn_change_memprotect() cannot be used.
+ */
+ if (enable_shub_wars_1_1()) {
+ /* open up everything */
+ xpc_prot_vec[node] = (u64) HUB_L((u64 *)
+ GLOBAL_MMR_ADDR(nasid,
+ SH1_MD_DQLP_MMR_DIR_PRIVEC0));
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid,
+ SH1_MD_DQLP_MMR_DIR_PRIVEC0),
+ -1UL);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid,
+ SH1_MD_DQRP_MMR_DIR_PRIVEC0),
+ -1UL);
+ }
+ }
+ }
+}
+
+
+/*
+ * Restrict protections to disallow IPI operations (and AMO operations on
+ * Shub 1.1 systems).
+ */
+void
+xpc_restrict_IPI_ops(void)
+{
+ int node;
+ int nasid;
+
+
+ // >>> Change SH_IPI_ACCESS code to use SAL call once it is available.
+
+ if (is_shub2()) {
+
+ for_each_online_node(node) {
+ nasid = cnodeid_to_nasid(node);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS0),
+ xpc_sh2_IPI_access0);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS1),
+ xpc_sh2_IPI_access1);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS2),
+ xpc_sh2_IPI_access2);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH2_IPI_ACCESS3),
+ xpc_sh2_IPI_access3);
+ }
+
+ } else {
+
+ for_each_online_node(node) {
+ nasid = cnodeid_to_nasid(node);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid, SH1_IPI_ACCESS),
+ xpc_sh1_IPI_access);
+
+ if (enable_shub_wars_1_1()) {
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid,
+ SH1_MD_DQLP_MMR_DIR_PRIVEC0),
+ xpc_prot_vec[node]);
+ HUB_S((u64 *) GLOBAL_MMR_ADDR(nasid,
+ SH1_MD_DQRP_MMR_DIR_PRIVEC0),
+ xpc_prot_vec[node]);
+ }
+ }
+ }
+}
+
+
+/*
+ * At periodic intervals, scan through all active partitions and ensure
+ * their heartbeat is still active. If not, the partition is deactivated.
+ */
+void
+xpc_check_remote_hb(void)
+{
+ struct xpc_vars *remote_vars;
+ struct xpc_partition *part;
+ partid_t partid;
+ bte_result_t bres;
+
+
+ remote_vars = (struct xpc_vars *) xpc_remote_copy_buffer;
+
+ for (partid = 1; partid < XP_MAX_PARTITIONS; partid++) {
+ if (partid == sn_partition_id) {
+ continue;
+ }
+
+ part = &xpc_partitions[partid];
+
+ if (part->act_state == XPC_P_INACTIVE ||
+ part->act_state == XPC_P_DEACTIVATING) {
+ continue;
+ }
+
+ /* pull the remote_hb cache line */
+ bres = xp_bte_copy(part->remote_vars_pa,
+ ia64_tpa((u64) remote_vars),
+ XPC_VARS_ALIGNED_SIZE,
+ (BTE_NOTIFY | BTE_WACQUIRE), NULL);
+ if (bres != BTE_SUCCESS) {
+ XPC_DEACTIVATE_PARTITION(part,
+ xpc_map_bte_errors(bres));
+ continue;
+ }
+
+ dev_dbg(xpc_part, "partid = %d, heartbeat = %ld, last_heartbeat"
+ " = %ld, kdb_status = %ld, HB_mask = 0x%lx\n", partid,
+ remote_vars->heartbeat, part->last_heartbeat,
+ remote_vars->kdb_status,
+ remote_vars->heartbeating_to_mask);
+
+ if (((remote_vars->heartbeat == part->last_heartbeat) &&
+ (remote_vars->kdb_status == 0)) ||
+ !XPC_HB_ALLOWED(sn_partition_id, remote_vars)) {
+
+ XPC_DEACTIVATE_PARTITION(part, xpcNoHeartbeat);
+ continue;
+ }
+
+ part->last_heartbeat = remote_vars->heartbeat;
+ }
+}
+
+
+/*
+ * Get a copy of the remote partition's rsvd page.
+ *
+ * remote_rp points to a buffer that is cacheline aligned for BTE copies and
+ * assumed to be of size XPC_RSVD_PAGE_ALIGNED_SIZE.
+ */
+static enum xpc_retval
+xpc_get_remote_rp(int nasid, u64 *discovered_nasids,
+ struct xpc_rsvd_page *remote_rp, u64 *remote_rsvd_page_pa)
+{
+ int bres, i;
+
+
+ /* get the reserved page's physical address */
+
+ *remote_rsvd_page_pa = xpc_get_rsvd_page_pa(nasid, (u64) remote_rp,
+ XPC_RSVD_PAGE_ALIGNED_SIZE);
+ if (*remote_rsvd_page_pa == 0) {
+ return xpcNoRsvdPageAddr;
+ }
+
+
+ /* pull over the reserved page structure */
+
+ bres = xp_bte_copy(*remote_rsvd_page_pa, ia64_tpa((u64) remote_rp),
+ XPC_RSVD_PAGE_ALIGNED_SIZE,
+ (BTE_NOTIFY | BTE_WACQUIRE), NULL);
+ if (bres != BTE_SUCCESS) {
+ return xpc_map_bte_errors(bres);
+ }
+
+
+ if (discovered_nasids != NULL) {
+ for (i = 0; i < XP_NASID_MASK_WORDS; i++) {
+ discovered_nasids[i] |= remote_rp->part_nasids[i];
+ }
+ }
+
+
+ /* check that the partid is for another partition */
+
+ if (remote_rp->partid < 1 ||
+ remote_rp->partid > (XP_MAX_PARTITIONS - 1)) {
+ return xpcInvalidPartid;
+ }
+
+ if (remote_rp->partid == sn_partition_id) {
+ return xpcLocalPartid;
+ }
+
+
+ if (XPC_VERSION_MAJOR(remote_rp->version) !=
+ XPC_VERSION_MAJOR(XPC_RP_VERSION)) {
+ return xpcBadVersion;
+ }
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Get a copy of the remote partition's XPC variables.
+ *
+ * remote_vars points to a buffer that is cacheline aligned for BTE copies and
+ * assumed to be of size XPC_VARS_ALIGNED_SIZE.
+ */
+static enum xpc_retval
+xpc_get_remote_vars(u64 remote_vars_pa, struct xpc_vars *remote_vars)
+{
+ int bres;
+
+
+ if (remote_vars_pa == 0) {
+ return xpcVarsNotSet;
+ }
+
+
+ /* pull over the cross partition variables */
+
+ bres = xp_bte_copy(remote_vars_pa, ia64_tpa((u64) remote_vars),
+ XPC_VARS_ALIGNED_SIZE,
+ (BTE_NOTIFY | BTE_WACQUIRE), NULL);
+ if (bres != BTE_SUCCESS) {
+ return xpc_map_bte_errors(bres);
+ }
+
+ if (XPC_VERSION_MAJOR(remote_vars->version) !=
+ XPC_VERSION_MAJOR(XPC_V_VERSION)) {
+ return xpcBadVersion;
+ }
+
+ return xpcSuccess;
+}
+
+
+/*
+ * Prior code has determine the nasid which generated an IPI. Inspect
+ * that nasid to determine if its partition needs to be activated or
+ * deactivated.
+ *
+ * A partition is consider "awaiting activation" if our partition
+ * flags indicate it is not active and it has a heartbeat. A
+ * partition is considered "awaiting deactivation" if our partition
+ * flags indicate it is active but it has no heartbeat or it is not
+ * sending its heartbeat to us.
+ *
+ * To determine the heartbeat, the remote nasid must have a properly
+ * initialized reserved page.
+ */
+static void
+xpc_identify_act_IRQ_req(int nasid)
+{
+ struct xpc_rsvd_page *remote_rp;
+ struct xpc_vars *remote_vars;
+ u64 remote_rsvd_page_pa;
+ u64 remote_vars_pa;
+ partid_t partid;
+ struct xpc_partition *part;
+ enum xpc_retval ret;
+
+
+ /* pull over the reserved page structure */
+
+ remote_rp = (struct xpc_rsvd_page *) xpc_remote_copy_buffer;
+
+ ret = xpc_get_remote_rp(nasid, NULL, remote_rp, &remote_rsvd_page_pa);
+ if (ret != xpcSuccess) {
+ dev_warn(xpc_part, "unable to get reserved page from nasid %d, "
+ "which sent interrupt, reason=%d\n", nasid, ret);
+ return;
+ }
+
+ remote_vars_pa = remote_rp->vars_pa;
+ partid = remote_rp->partid;
+ part = &xpc_partitions[partid];
+
+
+ /* pull over the cross partition variables */
+
+ remote_vars = (struct xpc_vars *) xpc_remote_copy_buffer;
+
+ ret = xpc_get_remote_vars(remote_vars_pa, remote_vars);
+ if (ret != xpcSuccess) {
+
+ dev_warn(xpc_part, "unable to get XPC variables from nasid %d, "
+ "which sent interrupt, reason=%d\n", nasid, ret);
+
+ XPC_DEACTIVATE_PARTITION(part, ret);
+ return;
+ }
+
+
+ part->act_IRQ_rcvd++;
+
+ dev_dbg(xpc_part, "partid for nasid %d is %d; IRQs = %d; HB = "
+ "%ld:0x%lx\n", (int) nasid, (int) partid, part->act_IRQ_rcvd,
+ remote_vars->heartbeat, remote_vars->heartbeating_to_mask);
+
+
+ if (part->act_state == XPC_P_INACTIVE) {
+
+ part->remote_rp_pa = remote_rsvd_page_pa;
+ dev_dbg(xpc_part, " remote_rp_pa = 0x%016lx\n",
+ part->remote_rp_pa);
+
+ part->remote_vars_pa = remote_vars_pa;
+ dev_dbg(xpc_part, " remote_vars_pa = 0x%016lx\n",
+ part->remote_vars_pa);
+
+ part->last_heartbeat = remote_vars->heartbeat;
+ dev_dbg(xpc_part, " last_heartbeat = 0x%016lx\n",
+ part->last_heartbeat);
+
+ part->remote_vars_part_pa = remote_vars->vars_part_pa;
+ dev_dbg(xpc_part, " remote_vars_part_pa = 0x%016lx\n",
+ part->remote_vars_part_pa);
+
+ part->remote_act_nasid = remote_vars->act_nasid;
+ dev_dbg(xpc_part, " remote_act_nasid = 0x%x\n",
+ part->remote_act_nasid);
+
+ part->remote_act_phys_cpuid = remote_vars->act_phys_cpuid;
+ dev_dbg(xpc_part, " remote_act_phys_cpuid = 0x%x\n",
+ part->remote_act_phys_cpuid);
+
+ part->remote_amos_page_pa = remote_vars->amos_page_pa;
+ dev_dbg(xpc_part, " remote_amos_page_pa = 0x%lx\n",
+ part->remote_amos_page_pa);
+
+ xpc_activate_partition(part);
+
+ } else if (part->remote_amos_page_pa != remote_vars->amos_page_pa ||
+ !XPC_HB_ALLOWED(sn_partition_id, remote_vars)) {
+
+ part->reactivate_nasid = nasid;
+ XPC_DEACTIVATE_PARTITION(part, xpcReactivating);
+ }
+}
+
+
+/*
+ * Loop through the activation AMO variables and process any bits
+ * which are set. Each bit indicates a nasid sending a partition
+ * activation or deactivation request.
+ *
+ * Return #of IRQs detected.
+ */
+int
+xpc_identify_act_IRQ_sender(void)
+{
+ int word, bit;
+ u64 nasid_mask;
+ u64 nasid; /* remote nasid */
+ int n_IRQs_detected = 0;
+ AMO_t *act_amos;
+ struct xpc_rsvd_page *rp = (struct xpc_rsvd_page *) xpc_rsvd_page;
+
+
+ act_amos = xpc_vars->act_amos;
+
+
+ /* scan through act AMO variable looking for non-zero entries */
+ for (word = 0; word < XP_NASID_MASK_WORDS; word++) {
+
+ nasid_mask = xpc_IPI_receive(&act_amos[word]);
+ if (nasid_mask == 0) {
+ /* no IRQs from nasids in this variable */
+ continue;
+ }
+
+ dev_dbg(xpc_part, "AMO[%d] gave back 0x%lx\n", word,
+ nasid_mask);
+
+
+ /*
+ * If this nasid has been added to the machine since
+ * our partition was reset, this will retain the
+ * remote nasid in our reserved pages machine mask.
+ * This is used in the event of module reload.
+ */
+ rp->mach_nasids[word] |= nasid_mask;
+
+
+ /* locate the nasid(s) which sent interrupts */
+
+ for (bit = 0; bit < (8 * sizeof(u64)); bit++) {
+ if (nasid_mask & (1UL << bit)) {
+ n_IRQs_detected++;
+ nasid = XPC_NASID_FROM_W_B(word, bit);
+ dev_dbg(xpc_part, "interrupt from nasid %ld\n",
+ nasid);
+ xpc_identify_act_IRQ_req(nasid);
+ }
+ }
+ }
+ return n_IRQs_detected;
+}
+
+
+/*
+ * Mark specified partition as active.
+ */
+enum xpc_retval
+xpc_mark_partition_active(struct xpc_partition *part)
+{
+ unsigned long irq_flags;
+ enum xpc_retval ret;
+
+
+ dev_dbg(xpc_part, "setting partition %d to ACTIVE\n", XPC_PARTID(part));
+
+ spin_lock_irqsave(&part->act_lock, irq_flags);
+ if (part->act_state == XPC_P_ACTIVATING) {
+ part->act_state = XPC_P_ACTIVE;
+ ret = xpcSuccess;
+ } else {
+ DBUG_ON(part->reason == xpcSuccess);
+ ret = part->reason;
+ }
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+
+ return ret;
+}
+
+
+/*
+ * Notify XPC that the partition is down.
+ */
+void
+xpc_deactivate_partition(const int line, struct xpc_partition *part,
+ enum xpc_retval reason)
+{
+ unsigned long irq_flags;
+ partid_t partid = XPC_PARTID(part);
+
+
+ spin_lock_irqsave(&part->act_lock, irq_flags);
+
+ if (part->act_state == XPC_P_INACTIVE) {
+ XPC_SET_REASON(part, reason, line);
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+ if (reason == xpcReactivating) {
+ /* we interrupt ourselves to reactivate partition */
+ xpc_IPI_send_reactivate(part);
+ }
+ return;
+ }
+ if (part->act_state == XPC_P_DEACTIVATING) {
+ if ((part->reason == xpcUnloading && reason != xpcUnloading) ||
+ reason == xpcReactivating) {
+ XPC_SET_REASON(part, reason, line);
+ }
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+ return;
+ }
+
+ part->act_state = XPC_P_DEACTIVATING;
+ XPC_SET_REASON(part, reason, line);
+
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+
+ XPC_DISALLOW_HB(partid, xpc_vars);
+
+ dev_dbg(xpc_part, "bringing partition %d down, reason = %d\n", partid,
+ reason);
+
+ xpc_partition_down(part, reason);
+}
+
+
+/*
+ * Mark specified partition as active.
+ */
+void
+xpc_mark_partition_inactive(struct xpc_partition *part)
+{
+ unsigned long irq_flags;
+
+
+ dev_dbg(xpc_part, "setting partition %d to INACTIVE\n",
+ XPC_PARTID(part));
+
+ spin_lock_irqsave(&part->act_lock, irq_flags);
+ part->act_state = XPC_P_INACTIVE;
+ spin_unlock_irqrestore(&part->act_lock, irq_flags);
+ part->remote_rp_pa = 0;
+}
+
+
+/*
+ * SAL has provided a partition and machine mask. The partition mask
+ * contains a bit for each even nasid in our partition. The machine
+ * mask contains a bit for each even nasid in the entire machine.
+ *
+ * Using those two bit arrays, we can determine which nasids are
+ * known in the machine. Each should also have a reserved page
+ * initialized if they are available for partitioning.
+ */
+void
+xpc_discovery(void)
+{
+ void *remote_rp_base;
+ struct xpc_rsvd_page *remote_rp;
+ struct xpc_vars *remote_vars;
+ u64 remote_rsvd_page_pa;
+ u64 remote_vars_pa;
+ int region;
+ int max_regions;
+ int nasid;
+ struct xpc_rsvd_page *rp;
+ partid_t partid;
+ struct xpc_partition *part;
+ u64 *discovered_nasids;
+ enum xpc_retval ret;
+
+
+ remote_rp = xpc_kmalloc_cacheline_aligned(XPC_RSVD_PAGE_ALIGNED_SIZE,
+ GFP_KERNEL, &remote_rp_base);
+ if (remote_rp == NULL) {
+ return;
+ }
+ remote_vars = (struct xpc_vars *) remote_rp;
+
+
+ discovered_nasids = kmalloc(sizeof(u64) * XP_NASID_MASK_WORDS,
+ GFP_KERNEL);
+ if (discovered_nasids == NULL) {
+ kfree(remote_rp_base);
+ return;
+ }
+ memset(discovered_nasids, 0, sizeof(u64) * XP_NASID_MASK_WORDS);
+
+ rp = (struct xpc_rsvd_page *) xpc_rsvd_page;
+
+ /*
+ * The term 'region' in this context refers to the minimum number of
+ * nodes that can comprise an access protection grouping. The access
+ * protection is in regards to memory, IOI and IPI.
+ */
+//>>> move the next two #defines into either include/asm-ia64/sn/arch.h or
+//>>> include/asm-ia64/sn/addrs.h
+#define SH1_MAX_REGIONS 64
+#define SH2_MAX_REGIONS 256
+ max_regions = is_shub2() ? SH2_MAX_REGIONS : SH1_MAX_REGIONS;
+
+ for (region = 0; region < max_regions; region++) {
+
+ if ((volatile int) xpc_exiting) {
+ break;
+ }
+
+ dev_dbg(xpc_part, "searching region %d\n", region);
+
+ for (nasid = (region * sn_region_size * 2);
+ nasid < ((region + 1) * sn_region_size * 2);
+ nasid += 2) {
+
+ if ((volatile int) xpc_exiting) {
+ break;
+ }
+
+ dev_dbg(xpc_part, "checking nasid %d\n", nasid);
+
+
+ if (XPC_NASID_IN_ARRAY(nasid, rp->part_nasids)) {
+ dev_dbg(xpc_part, "PROM indicates Nasid %d is "
+ "part of the local partition; skipping "
+ "region\n", nasid);
+ break;
+ }
+
+ if (!(XPC_NASID_IN_ARRAY(nasid, rp->mach_nasids))) {
+ dev_dbg(xpc_part, "PROM indicates Nasid %d was "
+ "not on Numa-Link network at reset\n",
+ nasid);
+ continue;
+ }
+
+ if (XPC_NASID_IN_ARRAY(nasid, discovered_nasids)) {
+ dev_dbg(xpc_part, "Nasid %d is part of a "
+ "partition which was previously "
+ "discovered\n", nasid);
+ continue;
+ }
+
+
+ /* pull over the reserved page structure */
+
+ ret = xpc_get_remote_rp(nasid, discovered_nasids,
+ remote_rp, &remote_rsvd_page_pa);
+ if (ret != xpcSuccess) {
+ dev_dbg(xpc_part, "unable to get reserved page "
+ "from nasid %d, reason=%d\n", nasid,
+ ret);
+
+ if (ret == xpcLocalPartid) {
+ break;
+ }
+ continue;
+ }
+
+ remote_vars_pa = remote_rp->vars_pa;
+
+ partid = remote_rp->partid;
+ part = &xpc_partitions[partid];
+
+
+ /* pull over the cross partition variables */
+
+ ret = xpc_get_remote_vars(remote_vars_pa, remote_vars);
+ if (ret != xpcSuccess) {
+ dev_dbg(xpc_part, "unable to get XPC variables "
+ "from nasid %d, reason=%d\n", nasid,
+ ret);
+
+ XPC_DEACTIVATE_PARTITION(part, ret);
+ continue;
+ }
+
+ if (part->act_state != XPC_P_INACTIVE) {
+ dev_dbg(xpc_part, "partition %d on nasid %d is "
+ "already activating\n", partid, nasid);
+ break;
+ }
+
+ /*
+ * Register the remote partition's AMOs with SAL so it
+ * can handle and cleanup errors within that address
+ * range should the remote partition go down. We don't
+ * unregister this range because it is difficult to
+ * tell when outstanding writes to the remote partition
+ * are finished and thus when it is thus safe to
+ * unregister. This should not result in wasted space
+ * in the SAL xp_addr_region table because we should
+ * get the same page for remote_act_amos_pa after
+ * module reloads and system reboots.
+ */
+ if (sn_register_xp_addr_region(
+ remote_vars->amos_page_pa,
+ PAGE_SIZE, 1) < 0) {
+ dev_dbg(xpc_part, "partition %d failed to "
+ "register xp_addr region 0x%016lx\n",
+ partid, remote_vars->amos_page_pa);
+
+ XPC_SET_REASON(part, xpcPhysAddrRegFailed,
+ __LINE__);
+ break;
+ }
+
+ /*
+ * The remote nasid is valid and available.
+ * Send an interrupt to that nasid to notify
+ * it that we are ready to begin activation.
+ */
+ dev_dbg(xpc_part, "sending an interrupt to AMO 0x%lx, "
+ "nasid %d, phys_cpuid 0x%x\n",
+ remote_vars->amos_page_pa,
+ remote_vars->act_nasid,
+ remote_vars->act_phys_cpuid);
+
+ xpc_IPI_send_activate(remote_vars);
+ }
+ }
+
+ kfree(discovered_nasids);
+ kfree(remote_rp_base);
+}
+
+
+/*
+ * Given a partid, get the nasids owned by that partition from the
+ * remote partition's reserved page.
+ */
+enum xpc_retval
+xpc_initiate_partid_to_nasids(partid_t partid, void *nasid_mask)
+{
+ struct xpc_partition *part;
+ u64 part_nasid_pa;
+ int bte_res;
+
+
+ part = &xpc_partitions[partid];
+ if (part->remote_rp_pa == 0) {
+ return xpcPartitionDown;
+ }
+
+ part_nasid_pa = part->remote_rp_pa +
+ (u64) &((struct xpc_rsvd_page *) 0)->part_nasids;
+
+ bte_res = xp_bte_copy(part_nasid_pa, ia64_tpa((u64) nasid_mask),
+ L1_CACHE_ALIGN(XP_NASID_MASK_BYTES),
+ (BTE_NOTIFY | BTE_WACQUIRE), NULL);
+
+ return xpc_map_bte_errors(bte_res);
+}
+
diff --git a/arch/ia64/sn/kernel/xpnet.c b/arch/ia64/sn/kernel/xpnet.c
new file mode 100644
index 000000000000..78c13d676fa6
--- /dev/null
+++ b/arch/ia64/sn/kernel/xpnet.c
@@ -0,0 +1,715 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 1999,2001-2005 Silicon Graphics, Inc. All rights reserved.
+ */
+
+
+/*
+ * Cross Partition Network Interface (XPNET) support
+ *
+ * XPNET provides a virtual network layered on top of the Cross
+ * Partition communication layer.
+ *
+ * XPNET provides direct point-to-point and broadcast-like support
+ * for an ethernet-like device. The ethernet broadcast medium is
+ * replaced with a point-to-point message structure which passes
+ * pointers to a DMA-capable block that a remote partition should
+ * retrieve and pass to the upper level networking layer.
+ *
+ */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/smp.h>
+#include <linux/string.h>
+#include <asm/sn/bte.h>
+#include <asm/sn/io.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/types.h>
+#include <asm/atomic.h>
+#include <asm/sn/xp.h>
+
+
+/*
+ * The message payload transferred by XPC.
+ *
+ * buf_pa is the physical address where the DMA should pull from.
+ *
+ * NOTE: for performance reasons, buf_pa should _ALWAYS_ begin on a
+ * cacheline boundary. To accomplish this, we record the number of
+ * bytes from the beginning of the first cacheline to the first useful
+ * byte of the skb (leadin_ignore) and the number of bytes from the
+ * last useful byte of the skb to the end of the last cacheline
+ * (tailout_ignore).
+ *
+ * size is the number of bytes to transfer which includes the skb->len
+ * (useful bytes of the senders skb) plus the leadin and tailout
+ */
+struct xpnet_message {
+ u16 version; /* Version for this message */
+ u16 embedded_bytes; /* #of bytes embedded in XPC message */
+ u32 magic; /* Special number indicating this is xpnet */
+ u64 buf_pa; /* phys address of buffer to retrieve */
+ u32 size; /* #of bytes in buffer */
+ u8 leadin_ignore; /* #of bytes to ignore at the beginning */
+ u8 tailout_ignore; /* #of bytes to ignore at the end */
+ unsigned char data; /* body of small packets */
+};
+
+/*
+ * Determine the size of our message, the cacheline aligned size,
+ * and then the number of message will request from XPC.
+ *
+ * XPC expects each message to exist in an individual cacheline.
+ */
+#define XPNET_MSG_SIZE (L1_CACHE_BYTES - XPC_MSG_PAYLOAD_OFFSET)
+#define XPNET_MSG_DATA_MAX \
+ (XPNET_MSG_SIZE - (u64)(&((struct xpnet_message *)0)->data))
+#define XPNET_MSG_ALIGNED_SIZE (L1_CACHE_ALIGN(XPNET_MSG_SIZE))
+#define XPNET_MSG_NENTRIES (PAGE_SIZE / XPNET_MSG_ALIGNED_SIZE)
+
+
+#define XPNET_MAX_KTHREADS (XPNET_MSG_NENTRIES + 1)
+#define XPNET_MAX_IDLE_KTHREADS (XPNET_MSG_NENTRIES + 1)
+
+/*
+ * Version number of XPNET implementation. XPNET can always talk to versions
+ * with same major #, and never talk to versions with a different version.
+ */
+#define _XPNET_VERSION(_major, _minor) (((_major) << 4) | (_minor))
+#define XPNET_VERSION_MAJOR(_v) ((_v) >> 4)
+#define XPNET_VERSION_MINOR(_v) ((_v) & 0xf)
+
+#define XPNET_VERSION _XPNET_VERSION(1,0) /* version 1.0 */
+#define XPNET_VERSION_EMBED _XPNET_VERSION(1,1) /* version 1.1 */
+#define XPNET_MAGIC 0x88786984 /* "XNET" */
+
+#define XPNET_VALID_MSG(_m) \
+ ((XPNET_VERSION_MAJOR(_m->version) == XPNET_VERSION_MAJOR(XPNET_VERSION)) \
+ && (msg->magic == XPNET_MAGIC))
+
+#define XPNET_DEVICE_NAME "xp0"
+
+
+/*
+ * When messages are queued with xpc_send_notify, a kmalloc'd buffer
+ * of the following type is passed as a notification cookie. When the
+ * notification function is called, we use the cookie to decide
+ * whether all outstanding message sends have completed. The skb can
+ * then be released.
+ */
+struct xpnet_pending_msg {
+ struct list_head free_list;
+ struct sk_buff *skb;
+ atomic_t use_count;
+};
+
+/* driver specific structure pointed to by the device structure */
+struct xpnet_dev_private {
+ struct net_device_stats stats;
+};
+
+struct net_device *xpnet_device;
+
+/*
+ * When we are notified of other partitions activating, we add them to
+ * our bitmask of partitions to which we broadcast.
+ */
+static u64 xpnet_broadcast_partitions;
+/* protect above */
+static spinlock_t xpnet_broadcast_lock = SPIN_LOCK_UNLOCKED;
+
+/*
+ * Since the Block Transfer Engine (BTE) is being used for the transfer
+ * and it relies upon cache-line size transfers, we need to reserve at
+ * least one cache-line for head and tail alignment. The BTE is
+ * limited to 8MB transfers.
+ *
+ * Testing has shown that changing MTU to greater than 64KB has no effect
+ * on TCP as the two sides negotiate a Max Segment Size that is limited
+ * to 64K. Other protocols May use packets greater than this, but for
+ * now, the default is 64KB.
+ */
+#define XPNET_MAX_MTU (0x800000UL - L1_CACHE_BYTES)
+/* 32KB has been determined to be the ideal */
+#define XPNET_DEF_MTU (0x8000UL)
+
+
+/*
+ * The partition id is encapsulated in the MAC address. The following
+ * define locates the octet the partid is in.
+ */
+#define XPNET_PARTID_OCTET 1
+#define XPNET_LICENSE_OCTET 2
+
+
+/*
+ * Define the XPNET debug device structure that is to be used with dev_dbg(),
+ * dev_err(), dev_warn(), and dev_info().
+ */
+struct device_driver xpnet_dbg_name = {
+ .name = "xpnet"
+};
+
+struct device xpnet_dbg_subname = {
+ .bus_id = {0}, /* set to "" */
+ .driver = &xpnet_dbg_name
+};
+
+struct device *xpnet = &xpnet_dbg_subname;
+
+/*
+ * Packet was recevied by XPC and forwarded to us.
+ */
+static void
+xpnet_receive(partid_t partid, int channel, struct xpnet_message *msg)
+{
+ struct sk_buff *skb;
+ bte_result_t bret;
+ struct xpnet_dev_private *priv =
+ (struct xpnet_dev_private *) xpnet_device->priv;
+
+
+ if (!XPNET_VALID_MSG(msg)) {
+ /*
+ * Packet with a different XPC version. Ignore.
+ */
+ xpc_received(partid, channel, (void *) msg);
+
+ priv->stats.rx_errors++;
+
+ return;
+ }
+ dev_dbg(xpnet, "received 0x%lx, %d, %d, %d\n", msg->buf_pa, msg->size,
+ msg->leadin_ignore, msg->tailout_ignore);
+
+
+ /* reserve an extra cache line */
+ skb = dev_alloc_skb(msg->size + L1_CACHE_BYTES);
+ if (!skb) {
+ dev_err(xpnet, "failed on dev_alloc_skb(%d)\n",
+ msg->size + L1_CACHE_BYTES);
+
+ xpc_received(partid, channel, (void *) msg);
+
+ priv->stats.rx_errors++;
+
+ return;
+ }
+
+ /*
+ * The allocated skb has some reserved space.
+ * In order to use bte_copy, we need to get the
+ * skb->data pointer moved forward.
+ */
+ skb_reserve(skb, (L1_CACHE_BYTES - ((u64)skb->data &
+ (L1_CACHE_BYTES - 1)) +
+ msg->leadin_ignore));
+
+ /*
+ * Update the tail pointer to indicate data actually
+ * transferred.
+ */
+ skb_put(skb, (msg->size - msg->leadin_ignore - msg->tailout_ignore));
+
+ /*
+ * Move the data over from the the other side.
+ */
+ if ((XPNET_VERSION_MINOR(msg->version) == 1) &&
+ (msg->embedded_bytes != 0)) {
+ dev_dbg(xpnet, "copying embedded message. memcpy(0x%p, 0x%p, "
+ "%lu)\n", skb->data, &msg->data,
+ (size_t) msg->embedded_bytes);
+
+ memcpy(skb->data, &msg->data, (size_t) msg->embedded_bytes);
+ } else {
+ dev_dbg(xpnet, "transferring buffer to the skb->data area;\n\t"
+ "bte_copy(0x%p, 0x%p, %hu)\n", (void *)msg->buf_pa,
+ (void *)__pa((u64)skb->data & ~(L1_CACHE_BYTES - 1)),
+ msg->size);
+
+ bret = bte_copy(msg->buf_pa,
+ __pa((u64)skb->data & ~(L1_CACHE_BYTES - 1)),
+ msg->size, (BTE_NOTIFY | BTE_WACQUIRE), NULL);
+
+ if (bret != BTE_SUCCESS) {
+ // >>> Need better way of cleaning skb. Currently skb
+ // >>> appears in_use and we can't just call
+ // >>> dev_kfree_skb.
+ dev_err(xpnet, "bte_copy(0x%p, 0x%p, 0x%hx) returned "
+ "error=0x%x\n", (void *)msg->buf_pa,
+ (void *)__pa((u64)skb->data &
+ ~(L1_CACHE_BYTES - 1)),
+ msg->size, bret);
+
+ xpc_received(partid, channel, (void *) msg);
+
+ priv->stats.rx_errors++;
+
+ return;
+ }
+ }
+
+ dev_dbg(xpnet, "<skb->head=0x%p skb->data=0x%p skb->tail=0x%p "
+ "skb->end=0x%p skb->len=%d\n", (void *) skb->head,
+ (void *) skb->data, (void *) skb->tail, (void *) skb->end,
+ skb->len);
+
+ skb->dev = xpnet_device;
+ skb->protocol = eth_type_trans(skb, xpnet_device);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ dev_dbg(xpnet, "passing skb to network layer; \n\tskb->head=0x%p "
+ "skb->data=0x%p skb->tail=0x%p skb->end=0x%p skb->len=%d\n",
+ (void *) skb->head, (void *) skb->data, (void *) skb->tail,
+ (void *) skb->end, skb->len);
+
+
+ xpnet_device->last_rx = jiffies;
+ priv->stats.rx_packets++;
+ priv->stats.rx_bytes += skb->len + ETH_HLEN;
+
+ netif_rx_ni(skb);
+ xpc_received(partid, channel, (void *) msg);
+}
+
+
+/*
+ * This is the handler which XPC calls during any sort of change in
+ * state or message reception on a connection.
+ */
+static void
+xpnet_connection_activity(enum xpc_retval reason, partid_t partid, int channel,
+ void *data, void *key)
+{
+ long bp;
+
+
+ DBUG_ON(partid <= 0 || partid >= XP_MAX_PARTITIONS);
+ DBUG_ON(channel != XPC_NET_CHANNEL);
+
+ switch(reason) {
+ case xpcMsgReceived: /* message received */
+ DBUG_ON(data == NULL);
+
+ xpnet_receive(partid, channel, (struct xpnet_message *) data);
+ break;
+
+ case xpcConnected: /* connection completed to a partition */
+ spin_lock_bh(&xpnet_broadcast_lock);
+ xpnet_broadcast_partitions |= 1UL << (partid -1 );
+ bp = xpnet_broadcast_partitions;
+ spin_unlock_bh(&xpnet_broadcast_lock);
+
+ netif_carrier_on(xpnet_device);
+
+ dev_dbg(xpnet, "%s connection created to partition %d; "
+ "xpnet_broadcast_partitions=0x%lx\n",
+ xpnet_device->name, partid, bp);
+ break;
+
+ default:
+ spin_lock_bh(&xpnet_broadcast_lock);
+ xpnet_broadcast_partitions &= ~(1UL << (partid -1 ));
+ bp = xpnet_broadcast_partitions;
+ spin_unlock_bh(&xpnet_broadcast_lock);
+
+ if (bp == 0) {
+ netif_carrier_off(xpnet_device);
+ }
+
+ dev_dbg(xpnet, "%s disconnected from partition %d; "
+ "xpnet_broadcast_partitions=0x%lx\n",
+ xpnet_device->name, partid, bp);
+ break;
+
+ }
+}
+
+
+static int
+xpnet_dev_open(struct net_device *dev)
+{
+ enum xpc_retval ret;
+
+
+ dev_dbg(xpnet, "calling xpc_connect(%d, 0x%p, NULL, %ld, %ld, %d, "
+ "%d)\n", XPC_NET_CHANNEL, xpnet_connection_activity,
+ XPNET_MSG_SIZE, XPNET_MSG_NENTRIES, XPNET_MAX_KTHREADS,
+ XPNET_MAX_IDLE_KTHREADS);
+
+ ret = xpc_connect(XPC_NET_CHANNEL, xpnet_connection_activity, NULL,
+ XPNET_MSG_SIZE, XPNET_MSG_NENTRIES,
+ XPNET_MAX_KTHREADS, XPNET_MAX_IDLE_KTHREADS);
+ if (ret != xpcSuccess) {
+ dev_err(xpnet, "ifconfig up of %s failed on XPC connect, "
+ "ret=%d\n", dev->name, ret);
+
+ return -ENOMEM;
+ }
+
+ dev_dbg(xpnet, "ifconfig up of %s; XPC connected\n", dev->name);
+
+ return 0;
+}
+
+
+static int
+xpnet_dev_stop(struct net_device *dev)
+{
+ xpc_disconnect(XPC_NET_CHANNEL);
+
+ dev_dbg(xpnet, "ifconfig down of %s; XPC disconnected\n", dev->name);
+
+ return 0;
+}
+
+
+static int
+xpnet_dev_change_mtu(struct net_device *dev, int new_mtu)
+{
+ /* 68 comes from min TCP+IP+MAC header */
+ if ((new_mtu < 68) || (new_mtu > XPNET_MAX_MTU)) {
+ dev_err(xpnet, "ifconfig %s mtu %d failed; value must be "
+ "between 68 and %ld\n", dev->name, new_mtu,
+ XPNET_MAX_MTU);
+ return -EINVAL;
+ }
+
+ dev->mtu = new_mtu;
+ dev_dbg(xpnet, "ifconfig %s mtu set to %d\n", dev->name, new_mtu);
+ return 0;
+}
+
+
+/*
+ * Required for the net_device structure.
+ */
+static int
+xpnet_dev_set_config(struct net_device *dev, struct ifmap *new_map)
+{
+ return 0;
+}
+
+
+/*
+ * Return statistics to the caller.
+ */
+static struct net_device_stats *
+xpnet_dev_get_stats(struct net_device *dev)
+{
+ struct xpnet_dev_private *priv;
+
+
+ priv = (struct xpnet_dev_private *) dev->priv;
+
+ return &priv->stats;
+}
+
+
+/*
+ * Notification that the other end has received the message and
+ * DMA'd the skb information. At this point, they are done with
+ * our side. When all recipients are done processing, we
+ * release the skb and then release our pending message structure.
+ */
+static void
+xpnet_send_completed(enum xpc_retval reason, partid_t partid, int channel,
+ void *__qm)
+{
+ struct xpnet_pending_msg *queued_msg =
+ (struct xpnet_pending_msg *) __qm;
+
+
+ DBUG_ON(queued_msg == NULL);
+
+ dev_dbg(xpnet, "message to %d notified with reason %d\n",
+ partid, reason);
+
+ if (atomic_dec_return(&queued_msg->use_count) == 0) {
+ dev_dbg(xpnet, "all acks for skb->head=-x%p\n",
+ (void *) queued_msg->skb->head);
+
+ dev_kfree_skb_any(queued_msg->skb);
+ kfree(queued_msg);
+ }
+}
+
+
+/*
+ * Network layer has formatted a packet (skb) and is ready to place it
+ * "on the wire". Prepare and send an xpnet_message to all partitions
+ * which have connected with us and are targets of this packet.
+ *
+ * MAC-NOTE: For the XPNET driver, the MAC address contains the
+ * destination partition_id. If the destination partition id word
+ * is 0xff, this packet is to broadcast to all partitions.
+ */
+static int
+xpnet_dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct xpnet_pending_msg *queued_msg;
+ enum xpc_retval ret;
+ struct xpnet_message *msg;
+ u64 start_addr, end_addr;
+ long dp;
+ u8 second_mac_octet;
+ partid_t dest_partid;
+ struct xpnet_dev_private *priv;
+ u16 embedded_bytes;
+
+
+ priv = (struct xpnet_dev_private *) dev->priv;
+
+
+ dev_dbg(xpnet, ">skb->head=0x%p skb->data=0x%p skb->tail=0x%p "
+ "skb->end=0x%p skb->len=%d\n", (void *) skb->head,
+ (void *) skb->data, (void *) skb->tail, (void *) skb->end,
+ skb->len);
+
+
+ /*
+ * The xpnet_pending_msg tracks how many outstanding
+ * xpc_send_notifies are relying on this skb. When none
+ * remain, release the skb.
+ */
+ queued_msg = kmalloc(sizeof(struct xpnet_pending_msg), GFP_ATOMIC);
+ if (queued_msg == NULL) {
+ dev_warn(xpnet, "failed to kmalloc %ld bytes; dropping "
+ "packet\n", sizeof(struct xpnet_pending_msg));
+
+ priv->stats.tx_errors++;
+
+ return -ENOMEM;
+ }
+
+
+ /* get the beginning of the first cacheline and end of last */
+ start_addr = ((u64) skb->data & ~(L1_CACHE_BYTES - 1));
+ end_addr = L1_CACHE_ALIGN((u64) skb->tail);
+
+ /* calculate how many bytes to embed in the XPC message */
+ embedded_bytes = 0;
+ if (unlikely(skb->len <= XPNET_MSG_DATA_MAX)) {
+ /* skb->data does fit so embed */
+ embedded_bytes = skb->len;
+ }
+
+
+ /*
+ * Since the send occurs asynchronously, we set the count to one
+ * and begin sending. Any sends that happen to complete before
+ * we are done sending will not free the skb. We will be left
+ * with that task during exit. This also handles the case of
+ * a packet destined for a partition which is no longer up.
+ */
+ atomic_set(&queued_msg->use_count, 1);
+ queued_msg->skb = skb;
+
+
+ second_mac_octet = skb->data[XPNET_PARTID_OCTET];
+ if (second_mac_octet == 0xff) {
+ /* we are being asked to broadcast to all partitions */
+ dp = xpnet_broadcast_partitions;
+ } else if (second_mac_octet != 0) {
+ dp = xpnet_broadcast_partitions &
+ (1UL << (second_mac_octet - 1));
+ } else {
+ /* 0 is an invalid partid. Ignore */
+ dp = 0;
+ }
+ dev_dbg(xpnet, "destination Partitions mask (dp) = 0x%lx\n", dp);
+
+ /*
+ * If we wanted to allow promiscous mode to work like an
+ * unswitched network, this would be a good point to OR in a
+ * mask of partitions which should be receiving all packets.
+ */
+
+ /*
+ * Main send loop.
+ */
+ for (dest_partid = 1; dp && dest_partid < XP_MAX_PARTITIONS;
+ dest_partid++) {
+
+
+ if (!(dp & (1UL << (dest_partid - 1)))) {
+ /* not destined for this partition */
+ continue;
+ }
+
+ /* remove this partition from the destinations mask */
+ dp &= ~(1UL << (dest_partid - 1));
+
+
+ /* found a partition to send to */
+
+ ret = xpc_allocate(dest_partid, XPC_NET_CHANNEL,
+ XPC_NOWAIT, (void **)&msg);
+ if (unlikely(ret != xpcSuccess)) {
+ continue;
+ }
+
+ msg->embedded_bytes = embedded_bytes;
+ if (unlikely(embedded_bytes != 0)) {
+ msg->version = XPNET_VERSION_EMBED;
+ dev_dbg(xpnet, "calling memcpy(0x%p, 0x%p, 0x%lx)\n",
+ &msg->data, skb->data, (size_t) embedded_bytes);
+ memcpy(&msg->data, skb->data, (size_t) embedded_bytes);
+ } else {
+ msg->version = XPNET_VERSION;
+ }
+ msg->magic = XPNET_MAGIC;
+ msg->size = end_addr - start_addr;
+ msg->leadin_ignore = (u64) skb->data - start_addr;
+ msg->tailout_ignore = end_addr - (u64) skb->tail;
+ msg->buf_pa = __pa(start_addr);
+
+ dev_dbg(xpnet, "sending XPC message to %d:%d\nmsg->buf_pa="
+ "0x%lx, msg->size=%u, msg->leadin_ignore=%u, "
+ "msg->tailout_ignore=%u\n", dest_partid,
+ XPC_NET_CHANNEL, msg->buf_pa, msg->size,
+ msg->leadin_ignore, msg->tailout_ignore);
+
+
+ atomic_inc(&queued_msg->use_count);
+
+ ret = xpc_send_notify(dest_partid, XPC_NET_CHANNEL, msg,
+ xpnet_send_completed, queued_msg);
+ if (unlikely(ret != xpcSuccess)) {
+ atomic_dec(&queued_msg->use_count);
+ continue;
+ }
+
+ }
+
+ if (atomic_dec_return(&queued_msg->use_count) == 0) {
+ dev_dbg(xpnet, "no partitions to receive packet destined for "
+ "%d\n", dest_partid);
+
+
+ dev_kfree_skb(skb);
+ kfree(queued_msg);
+ }
+
+ priv->stats.tx_packets++;
+ priv->stats.tx_bytes += skb->len;
+
+ return 0;
+}
+
+
+/*
+ * Deal with transmit timeouts coming from the network layer.
+ */
+static void
+xpnet_dev_tx_timeout (struct net_device *dev)
+{
+ struct xpnet_dev_private *priv;
+
+
+ priv = (struct xpnet_dev_private *) dev->priv;
+
+ priv->stats.tx_errors++;
+ return;
+}
+
+
+static int __init
+xpnet_init(void)
+{
+ int i;
+ u32 license_num;
+ int result = -ENOMEM;
+
+
+ dev_info(xpnet, "registering network device %s\n", XPNET_DEVICE_NAME);
+
+ /*
+ * use ether_setup() to init the majority of our device
+ * structure and then override the necessary pieces.
+ */
+ xpnet_device = alloc_netdev(sizeof(struct xpnet_dev_private),
+ XPNET_DEVICE_NAME, ether_setup);
+ if (xpnet_device == NULL) {
+ return -ENOMEM;
+ }
+
+ netif_carrier_off(xpnet_device);
+
+ xpnet_device->mtu = XPNET_DEF_MTU;
+ xpnet_device->change_mtu = xpnet_dev_change_mtu;
+ xpnet_device->open = xpnet_dev_open;
+ xpnet_device->get_stats = xpnet_dev_get_stats;
+ xpnet_device->stop = xpnet_dev_stop;
+ xpnet_device->hard_start_xmit = xpnet_dev_hard_start_xmit;
+ xpnet_device->tx_timeout = xpnet_dev_tx_timeout;
+ xpnet_device->set_config = xpnet_dev_set_config;
+
+ /*
+ * Multicast assumes the LSB of the first octet is set for multicast
+ * MAC addresses. We chose the first octet of the MAC to be unlikely
+ * to collide with any vendor's officially issued MAC.
+ */
+ xpnet_device->dev_addr[0] = 0xfe;
+ xpnet_device->dev_addr[XPNET_PARTID_OCTET] = sn_partition_id;
+ license_num = sn_partition_serial_number_val();
+ for (i = 3; i >= 0; i--) {
+ xpnet_device->dev_addr[XPNET_LICENSE_OCTET + i] =
+ license_num & 0xff;
+ license_num = license_num >> 8;
+ }
+
+ /*
+ * ether_setup() sets this to a multicast device. We are
+ * really not supporting multicast at this time.
+ */
+ xpnet_device->flags &= ~IFF_MULTICAST;
+
+ /*
+ * No need to checksum as it is a DMA transfer. The BTE will
+ * report an error if the data is not retrievable and the
+ * packet will be dropped.
+ */
+ xpnet_device->features = NETIF_F_NO_CSUM;
+
+ result = register_netdev(xpnet_device);
+ if (result != 0) {
+ free_netdev(xpnet_device);
+ }
+
+ return result;
+}
+module_init(xpnet_init);
+
+
+static void __exit
+xpnet_exit(void)
+{
+ dev_info(xpnet, "unregistering network device %s\n",
+ xpnet_device[0].name);
+
+ unregister_netdev(xpnet_device);
+
+ free_netdev(xpnet_device);
+}
+module_exit(xpnet_exit);
+
+
+MODULE_AUTHOR("Silicon Graphics, Inc.");
+MODULE_DESCRIPTION("Cross Partition Network adapter (XPNET)");
+MODULE_LICENSE("GPL");
+
diff --git a/arch/ia64/sn/pci/Makefile b/arch/ia64/sn/pci/Makefile
index b5dca0097a8e..2f915bce25f9 100644
--- a/arch/ia64/sn/pci/Makefile
+++ b/arch/ia64/sn/pci/Makefile
@@ -7,4 +7,4 @@
#
# Makefile for the sn pci general routines.
-obj-y := pci_dma.o pcibr/
+obj-y := pci_dma.o tioca_provider.o pcibr/
diff --git a/arch/ia64/sn/pci/pci_dma.c b/arch/ia64/sn/pci/pci_dma.c
index f680824f819d..5da9bdbde7cb 100644
--- a/arch/ia64/sn/pci/pci_dma.c
+++ b/arch/ia64/sn/pci/pci_dma.c
@@ -12,9 +12,8 @@
#include <linux/module.h>
#include <asm/dma.h>
#include <asm/sn/sn_sal.h>
-#include "pci/pcibus_provider_defs.h"
-#include "pci/pcidev.h"
-#include "pci/pcibr_provider.h"
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/pcidev.h>
#define SG_ENT_VIRT_ADDRESS(sg) (page_address((sg)->page) + (sg)->offset)
#define SG_ENT_PHYS_ADDRESS(SG) virt_to_phys(SG_ENT_VIRT_ADDRESS(SG))
@@ -79,7 +78,8 @@ void *sn_dma_alloc_coherent(struct device *dev, size_t size,
{
void *cpuaddr;
unsigned long phys_addr;
- struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev));
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct sn_pcibus_provider *provider = SN_PCIDEV_BUSPROVIDER(pdev);
BUG_ON(dev->bus != &pci_bus_type);
@@ -102,8 +102,7 @@ void *sn_dma_alloc_coherent(struct device *dev, size_t size,
* resources.
*/
- *dma_handle = pcibr_dma_map(pcidev_info, phys_addr, size,
- SN_PCIDMA_CONSISTENT);
+ *dma_handle = provider->dma_map_consistent(pdev, phys_addr, size);
if (!*dma_handle) {
printk(KERN_ERR "%s: out of ATEs\n", __FUNCTION__);
free_pages((unsigned long)cpuaddr, get_order(size));
@@ -127,11 +126,12 @@ EXPORT_SYMBOL(sn_dma_alloc_coherent);
void sn_dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
dma_addr_t dma_handle)
{
- struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev));
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct sn_pcibus_provider *provider = SN_PCIDEV_BUSPROVIDER(pdev);
BUG_ON(dev->bus != &pci_bus_type);
- pcibr_dma_unmap(pcidev_info, dma_handle, 0);
+ provider->dma_unmap(pdev, dma_handle, 0);
free_pages((unsigned long)cpu_addr, get_order(size));
}
EXPORT_SYMBOL(sn_dma_free_coherent);
@@ -159,12 +159,13 @@ dma_addr_t sn_dma_map_single(struct device *dev, void *cpu_addr, size_t size,
{
dma_addr_t dma_addr;
unsigned long phys_addr;
- struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev));
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct sn_pcibus_provider *provider = SN_PCIDEV_BUSPROVIDER(pdev);
BUG_ON(dev->bus != &pci_bus_type);
phys_addr = __pa(cpu_addr);
- dma_addr = pcibr_dma_map(pcidev_info, phys_addr, size, 0);
+ dma_addr = provider->dma_map(pdev, phys_addr, size);
if (!dma_addr) {
printk(KERN_ERR "%s: out of ATEs\n", __FUNCTION__);
return 0;
@@ -187,10 +188,12 @@ EXPORT_SYMBOL(sn_dma_map_single);
void sn_dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
int direction)
{
- struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev));
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct sn_pcibus_provider *provider = SN_PCIDEV_BUSPROVIDER(pdev);
BUG_ON(dev->bus != &pci_bus_type);
- pcibr_dma_unmap(pcidev_info, dma_addr, direction);
+
+ provider->dma_unmap(pdev, dma_addr, direction);
}
EXPORT_SYMBOL(sn_dma_unmap_single);
@@ -207,12 +210,13 @@ void sn_dma_unmap_sg(struct device *dev, struct scatterlist *sg,
int nhwentries, int direction)
{
int i;
- struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev));
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct sn_pcibus_provider *provider = SN_PCIDEV_BUSPROVIDER(pdev);
BUG_ON(dev->bus != &pci_bus_type);
for (i = 0; i < nhwentries; i++, sg++) {
- pcibr_dma_unmap(pcidev_info, sg->dma_address, direction);
+ provider->dma_unmap(pdev, sg->dma_address, direction);
sg->dma_address = (dma_addr_t) NULL;
sg->dma_length = 0;
}
@@ -233,7 +237,8 @@ int sn_dma_map_sg(struct device *dev, struct scatterlist *sg, int nhwentries,
{
unsigned long phys_addr;
struct scatterlist *saved_sg = sg;
- struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(to_pci_dev(dev));
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct sn_pcibus_provider *provider = SN_PCIDEV_BUSPROVIDER(pdev);
int i;
BUG_ON(dev->bus != &pci_bus_type);
@@ -243,8 +248,8 @@ int sn_dma_map_sg(struct device *dev, struct scatterlist *sg, int nhwentries,
*/
for (i = 0; i < nhwentries; i++, sg++) {
phys_addr = SG_ENT_PHYS_ADDRESS(sg);
- sg->dma_address = pcibr_dma_map(pcidev_info, phys_addr,
- sg->length, 0);
+ sg->dma_address = provider->dma_map(pdev,
+ phys_addr, sg->length);
if (!sg->dma_address) {
printk(KERN_ERR "%s: out of ATEs\n", __FUNCTION__);
diff --git a/arch/ia64/sn/pci/pcibr/pcibr_ate.c b/arch/ia64/sn/pci/pcibr/pcibr_ate.c
index 9d6854666f9b..0e47bce85f2d 100644
--- a/arch/ia64/sn/pci/pcibr/pcibr_ate.c
+++ b/arch/ia64/sn/pci/pcibr/pcibr_ate.c
@@ -8,8 +8,8 @@
#include <linux/types.h>
#include <asm/sn/sn_sal.h>
-#include "pci/pcibus_provider_defs.h"
-#include "pci/pcidev.h"
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/pcidev.h>
#include "pci/pcibr_provider.h"
int pcibr_invalidate_ate = 0; /* by default don't invalidate ATE on free */
diff --git a/arch/ia64/sn/pci/pcibr/pcibr_dma.c b/arch/ia64/sn/pci/pcibr/pcibr_dma.c
index b1d66ac065c8..64af2b2c1787 100644
--- a/arch/ia64/sn/pci/pcibr/pcibr_dma.c
+++ b/arch/ia64/sn/pci/pcibr/pcibr_dma.c
@@ -12,8 +12,8 @@
#include <asm/sn/geo.h>
#include "xtalk/xwidgetdev.h"
#include "xtalk/hubdev.h"
-#include "pci/pcibus_provider_defs.h"
-#include "pci/pcidev.h"
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/pcidev.h>
#include "pci/tiocp.h"
#include "pci/pic.h"
#include "pci/pcibr_provider.h"
@@ -40,7 +40,7 @@ extern int sn_ioif_inited;
* we do not have to allocate entries in the PMU.
*/
-static uint64_t
+static dma_addr_t
pcibr_dmamap_ate32(struct pcidev_info *info,
uint64_t paddr, size_t req_size, uint64_t flags)
{
@@ -109,7 +109,7 @@ pcibr_dmamap_ate32(struct pcidev_info *info,
return pci_addr;
}
-static uint64_t
+static dma_addr_t
pcibr_dmatrans_direct64(struct pcidev_info * info, uint64_t paddr,
uint64_t dma_attributes)
{
@@ -141,7 +141,7 @@ pcibr_dmatrans_direct64(struct pcidev_info * info, uint64_t paddr,
}
-static uint64_t
+static dma_addr_t
pcibr_dmatrans_direct32(struct pcidev_info * info,
uint64_t paddr, size_t req_size, uint64_t flags)
{
@@ -180,11 +180,11 @@ pcibr_dmatrans_direct32(struct pcidev_info * info,
* DMA mappings for Direct 64 and 32 do not have any DMA maps.
*/
void
-pcibr_dma_unmap(struct pcidev_info *pcidev_info, dma_addr_t dma_handle,
- int direction)
+pcibr_dma_unmap(struct pci_dev *hwdev, dma_addr_t dma_handle, int direction)
{
- struct pcibus_info *pcibus_info = (struct pcibus_info *)pcidev_info->
- pdi_pcibus_info;
+ struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(hwdev);
+ struct pcibus_info *pcibus_info =
+ (struct pcibus_info *)pcidev_info->pdi_pcibus_info;
if (IS_PCI32_MAPPED(dma_handle)) {
int ate_index;
@@ -301,7 +301,7 @@ void sn_dma_flush(uint64_t addr)
spin_lock_irqsave(&((struct sn_flush_device_list *)p)->
sfdl_flush_lock, flags);
- p->sfdl_flush_value = 0;
+ *p->sfdl_flush_addr = 0;
/* force an interrupt. */
*(volatile uint32_t *)(p->sfdl_force_int_addr) = 1;
@@ -316,64 +316,63 @@ void sn_dma_flush(uint64_t addr)
}
/*
- * Wrapper DMA interface. Called from pci_dma.c routines.
+ * DMA interfaces. Called from pci_dma.c routines.
*/
-uint64_t
-pcibr_dma_map(struct pcidev_info * pcidev_info, unsigned long phys_addr,
- size_t size, unsigned int flags)
+dma_addr_t
+pcibr_dma_map(struct pci_dev * hwdev, unsigned long phys_addr, size_t size)
{
dma_addr_t dma_handle;
- struct pci_dev *pcidev = pcidev_info->pdi_linux_pcidev;
-
- if (flags & SN_PCIDMA_CONSISTENT) {
- /* sn_pci_alloc_consistent interfaces */
- if (pcidev->dev.coherent_dma_mask == ~0UL) {
- dma_handle =
- pcibr_dmatrans_direct64(pcidev_info, phys_addr,
- PCI64_ATTR_BAR);
- } else {
- dma_handle =
- (dma_addr_t) pcibr_dmamap_ate32(pcidev_info,
- phys_addr, size,
- PCI32_ATE_BAR);
- }
- } else {
- /* map_sg/map_single interfaces */
+ struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(hwdev);
- /* SN cannot support DMA addresses smaller than 32 bits. */
- if (pcidev->dma_mask < 0x7fffffff) {
- return 0;
- }
+ /* SN cannot support DMA addresses smaller than 32 bits. */
+ if (hwdev->dma_mask < 0x7fffffff) {
+ return 0;
+ }
- if (pcidev->dma_mask == ~0UL) {
+ if (hwdev->dma_mask == ~0UL) {
+ /*
+ * Handle the most common case: 64 bit cards. This
+ * call should always succeed.
+ */
+
+ dma_handle = pcibr_dmatrans_direct64(pcidev_info, phys_addr,
+ PCI64_ATTR_PREF);
+ } else {
+ /* Handle 32-63 bit cards via direct mapping */
+ dma_handle = pcibr_dmatrans_direct32(pcidev_info, phys_addr,
+ size, 0);
+ if (!dma_handle) {
/*
- * Handle the most common case: 64 bit cards. This
- * call should always succeed.
+ * It is a 32 bit card and we cannot do direct mapping,
+ * so we use an ATE.
*/
- dma_handle =
- pcibr_dmatrans_direct64(pcidev_info, phys_addr,
- PCI64_ATTR_PREF);
- } else {
- /* Handle 32-63 bit cards via direct mapping */
- dma_handle =
- pcibr_dmatrans_direct32(pcidev_info, phys_addr,
- size, 0);
- if (!dma_handle) {
- /*
- * It is a 32 bit card and we cannot do direct mapping,
- * so we use an ATE.
- */
-
- dma_handle =
- pcibr_dmamap_ate32(pcidev_info, phys_addr,
- size, PCI32_ATE_PREF);
- }
+ dma_handle = pcibr_dmamap_ate32(pcidev_info, phys_addr,
+ size, PCI32_ATE_PREF);
}
}
return dma_handle;
}
+dma_addr_t
+pcibr_dma_map_consistent(struct pci_dev * hwdev, unsigned long phys_addr,
+ size_t size)
+{
+ dma_addr_t dma_handle;
+ struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(hwdev);
+
+ if (hwdev->dev.coherent_dma_mask == ~0UL) {
+ dma_handle = pcibr_dmatrans_direct64(pcidev_info, phys_addr,
+ PCI64_ATTR_BAR);
+ } else {
+ dma_handle = (dma_addr_t) pcibr_dmamap_ate32(pcidev_info,
+ phys_addr, size,
+ PCI32_ATE_BAR);
+ }
+
+ return dma_handle;
+}
+
EXPORT_SYMBOL(sn_dma_flush);
diff --git a/arch/ia64/sn/pci/pcibr/pcibr_provider.c b/arch/ia64/sn/pci/pcibr/pcibr_provider.c
index 92bd278cf7ff..3893999d23d8 100644
--- a/arch/ia64/sn/pci/pcibr/pcibr_provider.c
+++ b/arch/ia64/sn/pci/pcibr/pcibr_provider.c
@@ -13,8 +13,8 @@
#include "xtalk/xwidgetdev.h"
#include <asm/sn/geo.h>
#include "xtalk/hubdev.h"
-#include "pci/pcibus_provider_defs.h"
-#include "pci/pcidev.h"
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/pcidev.h>
#include "pci/pcibr_provider.h"
#include <asm/sn/addrs.h>
@@ -168,3 +168,23 @@ void pcibr_change_devices_irq(struct sn_irq_info *sn_irq_info)
pcibr_force_interrupt(sn_irq_info);
}
}
+
+/*
+ * Provider entries for PIC/CP
+ */
+
+struct sn_pcibus_provider pcibr_provider = {
+ .dma_map = pcibr_dma_map,
+ .dma_map_consistent = pcibr_dma_map_consistent,
+ .dma_unmap = pcibr_dma_unmap,
+ .bus_fixup = pcibr_bus_fixup,
+};
+
+int
+pcibr_init_provider(void)
+{
+ sn_pci_provider[PCIIO_ASIC_TYPE_PIC] = &pcibr_provider;
+ sn_pci_provider[PCIIO_ASIC_TYPE_TIOCP] = &pcibr_provider;
+
+ return 0;
+}
diff --git a/arch/ia64/sn/pci/pcibr/pcibr_reg.c b/arch/ia64/sn/pci/pcibr/pcibr_reg.c
index 74a74a7d2a13..865c11c3b50a 100644
--- a/arch/ia64/sn/pci/pcibr/pcibr_reg.c
+++ b/arch/ia64/sn/pci/pcibr/pcibr_reg.c
@@ -8,8 +8,8 @@
#include <linux/types.h>
#include <linux/interrupt.h>
-#include "pci/pcibus_provider_defs.h"
-#include "pci/pcidev.h"
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/pcidev.h>
#include "pci/tiocp.h"
#include "pci/pic.h"
#include "pci/pcibr_provider.h"
diff --git a/arch/ia64/sn/pci/tioca_provider.c b/arch/ia64/sn/pci/tioca_provider.c
new file mode 100644
index 000000000000..8dae9eb45456
--- /dev/null
+++ b/arch/ia64/sn/pci/tioca_provider.c
@@ -0,0 +1,668 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2003-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/pcidev.h>
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/tioca_provider.h>
+
+uint32_t tioca_gart_found;
+EXPORT_SYMBOL(tioca_gart_found); /* used by agp-sgi */
+
+LIST_HEAD(tioca_list);
+EXPORT_SYMBOL(tioca_list); /* used by agp-sgi */
+
+static int tioca_gart_init(struct tioca_kernel *);
+
+/**
+ * tioca_gart_init - Initialize SGI TIOCA GART
+ * @tioca_common: ptr to common prom/kernel struct identifying the
+ *
+ * If the indicated tioca has devices present, initialize its associated
+ * GART MMR's and kernel memory.
+ */
+static int
+tioca_gart_init(struct tioca_kernel *tioca_kern)
+{
+ uint64_t ap_reg;
+ uint64_t offset;
+ struct page *tmp;
+ struct tioca_common *tioca_common;
+ volatile struct tioca *ca_base;
+
+ tioca_common = tioca_kern->ca_common;
+ ca_base = (struct tioca *)tioca_common->ca_common.bs_base;
+
+ if (list_empty(tioca_kern->ca_devices))
+ return 0;
+
+ ap_reg = 0;
+
+ /*
+ * Validate aperature size
+ */
+
+ switch (CA_APERATURE_SIZE >> 20) {
+ case 4:
+ ap_reg |= (0x3ff << CA_GART_AP_SIZE_SHFT); /* 4MB */
+ break;
+ case 8:
+ ap_reg |= (0x3fe << CA_GART_AP_SIZE_SHFT); /* 8MB */
+ break;
+ case 16:
+ ap_reg |= (0x3fc << CA_GART_AP_SIZE_SHFT); /* 16MB */
+ break;
+ case 32:
+ ap_reg |= (0x3f8 << CA_GART_AP_SIZE_SHFT); /* 32 MB */
+ break;
+ case 64:
+ ap_reg |= (0x3f0 << CA_GART_AP_SIZE_SHFT); /* 64 MB */
+ break;
+ case 128:
+ ap_reg |= (0x3e0 << CA_GART_AP_SIZE_SHFT); /* 128 MB */
+ break;
+ case 256:
+ ap_reg |= (0x3c0 << CA_GART_AP_SIZE_SHFT); /* 256 MB */
+ break;
+ case 512:
+ ap_reg |= (0x380 << CA_GART_AP_SIZE_SHFT); /* 512 MB */
+ break;
+ case 1024:
+ ap_reg |= (0x300 << CA_GART_AP_SIZE_SHFT); /* 1GB */
+ break;
+ case 2048:
+ ap_reg |= (0x200 << CA_GART_AP_SIZE_SHFT); /* 2GB */
+ break;
+ case 4096:
+ ap_reg |= (0x000 << CA_GART_AP_SIZE_SHFT); /* 4 GB */
+ break;
+ default:
+ printk(KERN_ERR "%s: Invalid CA_APERATURE_SIZE "
+ "0x%lx\n", __FUNCTION__, (ulong) CA_APERATURE_SIZE);
+ return -1;
+ }
+
+ /*
+ * Set up other aperature parameters
+ */
+
+ if (PAGE_SIZE >= 16384) {
+ tioca_kern->ca_ap_pagesize = 16384;
+ ap_reg |= CA_GART_PAGE_SIZE;
+ } else {
+ tioca_kern->ca_ap_pagesize = 4096;
+ }
+
+ tioca_kern->ca_ap_size = CA_APERATURE_SIZE;
+ tioca_kern->ca_ap_bus_base = CA_APERATURE_BASE;
+ tioca_kern->ca_gart_entries =
+ tioca_kern->ca_ap_size / tioca_kern->ca_ap_pagesize;
+
+ ap_reg |= (CA_GART_AP_ENB_AGP | CA_GART_AP_ENB_PCI);
+ ap_reg |= tioca_kern->ca_ap_bus_base;
+
+ /*
+ * Allocate and set up the GART
+ */
+
+ tioca_kern->ca_gart_size = tioca_kern->ca_gart_entries * sizeof(u64);
+ tmp =
+ alloc_pages_node(tioca_kern->ca_closest_node,
+ GFP_KERNEL | __GFP_ZERO,
+ get_order(tioca_kern->ca_gart_size));
+
+ if (!tmp) {
+ printk(KERN_ERR "%s: Could not allocate "
+ "%lu bytes (order %d) for GART\n",
+ __FUNCTION__,
+ tioca_kern->ca_gart_size,
+ get_order(tioca_kern->ca_gart_size));
+ return -ENOMEM;
+ }
+
+ tioca_kern->ca_gart = page_address(tmp);
+ tioca_kern->ca_gart_coretalk_addr =
+ PHYS_TO_TIODMA(virt_to_phys(tioca_kern->ca_gart));
+
+ /*
+ * Compute PCI/AGP convenience fields
+ */
+
+ offset = CA_PCI32_MAPPED_BASE - CA_APERATURE_BASE;
+ tioca_kern->ca_pciap_base = CA_PCI32_MAPPED_BASE;
+ tioca_kern->ca_pciap_size = CA_PCI32_MAPPED_SIZE;
+ tioca_kern->ca_pcigart_start = offset / tioca_kern->ca_ap_pagesize;
+ tioca_kern->ca_pcigart_base =
+ tioca_kern->ca_gart_coretalk_addr + offset;
+ tioca_kern->ca_pcigart =
+ &tioca_kern->ca_gart[tioca_kern->ca_pcigart_start];
+ tioca_kern->ca_pcigart_entries =
+ tioca_kern->ca_pciap_size / tioca_kern->ca_ap_pagesize;
+ tioca_kern->ca_pcigart_pagemap =
+ kcalloc(1, tioca_kern->ca_pcigart_entries / 8, GFP_KERNEL);
+ if (!tioca_kern->ca_pcigart_pagemap) {
+ free_pages((unsigned long)tioca_kern->ca_gart,
+ get_order(tioca_kern->ca_gart_size));
+ return -1;
+ }
+
+ offset = CA_AGP_MAPPED_BASE - CA_APERATURE_BASE;
+ tioca_kern->ca_gfxap_base = CA_AGP_MAPPED_BASE;
+ tioca_kern->ca_gfxap_size = CA_AGP_MAPPED_SIZE;
+ tioca_kern->ca_gfxgart_start = offset / tioca_kern->ca_ap_pagesize;
+ tioca_kern->ca_gfxgart_base =
+ tioca_kern->ca_gart_coretalk_addr + offset;
+ tioca_kern->ca_gfxgart =
+ &tioca_kern->ca_gart[tioca_kern->ca_gfxgart_start];
+ tioca_kern->ca_gfxgart_entries =
+ tioca_kern->ca_gfxap_size / tioca_kern->ca_ap_pagesize;
+
+ /*
+ * various control settings:
+ * use agp op-combining
+ * use GET semantics to fetch memory
+ * participate in coherency domain
+ * DISABLE GART PREFETCHING due to hw bug tracked in SGI PV930029
+ */
+
+ ca_base->ca_control1 |= CA_AGPDMA_OP_ENB_COMBDELAY; /* PV895469 ? */
+ ca_base->ca_control2 &= ~(CA_GART_MEM_PARAM);
+ ca_base->ca_control2 |= (0x2ull << CA_GART_MEM_PARAM_SHFT);
+ tioca_kern->ca_gart_iscoherent = 1;
+ ca_base->ca_control2 &=
+ ~(CA_GART_WR_PREFETCH_ENB | CA_GART_RD_PREFETCH_ENB);
+
+ /*
+ * Unmask GART fetch error interrupts. Clear residual errors first.
+ */
+
+ ca_base->ca_int_status_alias = CA_GART_FETCH_ERR;
+ ca_base->ca_mult_error_alias = CA_GART_FETCH_ERR;
+ ca_base->ca_int_mask &= ~CA_GART_FETCH_ERR;
+
+ /*
+ * Program the aperature and gart registers in TIOCA
+ */
+
+ ca_base->ca_gart_aperature = ap_reg;
+ ca_base->ca_gart_ptr_table = tioca_kern->ca_gart_coretalk_addr | 1;
+
+ return 0;
+}
+
+/**
+ * tioca_fastwrite_enable - enable AGP FW for a tioca and its functions
+ * @tioca_kernel: structure representing the CA
+ *
+ * Given a CA, scan all attached functions making sure they all support
+ * FastWrite. If so, enable FastWrite for all functions and the CA itself.
+ */
+
+void
+tioca_fastwrite_enable(struct tioca_kernel *tioca_kern)
+{
+ int cap_ptr;
+ uint64_t ca_control1;
+ uint32_t reg;
+ struct tioca *tioca_base;
+ struct pci_dev *pdev;
+ struct tioca_common *common;
+
+ common = tioca_kern->ca_common;
+
+ /*
+ * Scan all vga controllers on this bus making sure they all
+ * suport FW. If not, return.
+ */
+
+ list_for_each_entry(pdev, tioca_kern->ca_devices, bus_list) {
+ if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8))
+ continue;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return; /* no AGP CAP means no FW */
+
+ pci_read_config_dword(pdev, cap_ptr + PCI_AGP_STATUS, &reg);
+ if (!(reg & PCI_AGP_STATUS_FW))
+ return; /* function doesn't support FW */
+ }
+
+ /*
+ * Set fw for all vga fn's
+ */
+
+ list_for_each_entry(pdev, tioca_kern->ca_devices, bus_list) {
+ if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8))
+ continue;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ pci_read_config_dword(pdev, cap_ptr + PCI_AGP_COMMAND, &reg);
+ reg |= PCI_AGP_COMMAND_FW;
+ pci_write_config_dword(pdev, cap_ptr + PCI_AGP_COMMAND, reg);
+ }
+
+ /*
+ * Set ca's fw to match
+ */
+
+ tioca_base = (struct tioca *)common->ca_common.bs_base;
+ ca_control1 = tioca_base->ca_control1;
+ ca_control1 |= CA_AGP_FW_ENABLE;
+ tioca_base->ca_control1 = ca_control1;
+}
+
+EXPORT_SYMBOL(tioca_fastwrite_enable); /* used by agp-sgi */
+
+/**
+ * tioca_dma_d64 - create a DMA mapping using 64-bit direct mode
+ * @paddr: system physical address
+ *
+ * Map @paddr into 64-bit CA bus space. No device context is necessary.
+ * Bits 53:0 come from the coretalk address. We just need to mask in the
+ * following optional bits of the 64-bit pci address:
+ *
+ * 63:60 - Coretalk Packet Type - 0x1 for Mem Get/Put (coherent)
+ * 0x2 for PIO (non-coherent)
+ * We will always use 0x1
+ * 55:55 - Swap bytes Currently unused
+ */
+static uint64_t
+tioca_dma_d64(unsigned long paddr)
+{
+ dma_addr_t bus_addr;
+
+ bus_addr = PHYS_TO_TIODMA(paddr);
+
+ BUG_ON(!bus_addr);
+ BUG_ON(bus_addr >> 54);
+
+ /* Set upper nibble to Cache Coherent Memory op */
+ bus_addr |= (1UL << 60);
+
+ return bus_addr;
+}
+
+/**
+ * tioca_dma_d48 - create a DMA mapping using 48-bit direct mode
+ * @pdev: linux pci_dev representing the function
+ * @paddr: system physical address
+ *
+ * Map @paddr into 64-bit bus space of the CA associated with @pcidev_info.
+ *
+ * The CA agp 48 bit direct address falls out as follows:
+ *
+ * When direct mapping AGP addresses, the 48 bit AGP address is
+ * constructed as follows:
+ *
+ * [47:40] - Low 8 bits of the page Node ID extracted from coretalk
+ * address [47:40]. The upper 8 node bits are fixed
+ * and come from the xxx register bits [5:0]
+ * [39:38] - Chiplet ID extracted from coretalk address [39:38]
+ * [37:00] - node offset extracted from coretalk address [37:00]
+ *
+ * Since the node id in general will be non-zero, and the chiplet id
+ * will always be non-zero, it follows that the device must support
+ * a dma mask of at least 0xffffffffff (40 bits) to target node 0
+ * and in general should be 0xffffffffffff (48 bits) to target nodes
+ * up to 255. Nodes above 255 need the support of the xxx register,
+ * and so a given CA can only directly target nodes in the range
+ * xxx - xxx+255.
+ */
+static uint64_t
+tioca_dma_d48(struct pci_dev *pdev, uint64_t paddr)
+{
+ struct tioca_common *tioca_common;
+ struct tioca *ca_base;
+ uint64_t ct_addr;
+ dma_addr_t bus_addr;
+ uint32_t node_upper;
+ uint64_t agp_dma_extn;
+ struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(pdev);
+
+ tioca_common = (struct tioca_common *)pcidev_info->pdi_pcibus_info;
+ ca_base = (struct tioca *)tioca_common->ca_common.bs_base;
+
+ ct_addr = PHYS_TO_TIODMA(paddr);
+ if (!ct_addr)
+ return 0;
+
+ bus_addr = (dma_addr_t) (ct_addr & 0xffffffffffff);
+ node_upper = ct_addr >> 48;
+
+ if (node_upper > 64) {
+ printk(KERN_ERR "%s: coretalk addr 0x%p node id out "
+ "of range\n", __FUNCTION__, (void *)ct_addr);
+ return 0;
+ }
+
+ agp_dma_extn = ca_base->ca_agp_dma_addr_extn;
+ if (node_upper != (agp_dma_extn >> CA_AGP_DMA_NODE_ID_SHFT)) {
+ printk(KERN_ERR "%s: coretalk upper node (%u) "
+ "mismatch with ca_agp_dma_addr_extn (%lu)\n",
+ __FUNCTION__,
+ node_upper, (agp_dma_extn >> CA_AGP_DMA_NODE_ID_SHFT));
+ return 0;
+ }
+
+ return bus_addr;
+}
+
+/**
+ * tioca_dma_mapped - create a DMA mapping using a CA GART
+ * @pdev: linux pci_dev representing the function
+ * @paddr: host physical address to map
+ * @req_size: len (bytes) to map
+ *
+ * Map @paddr into CA address space using the GART mechanism. The mapped
+ * dma_addr_t is guarenteed to be contiguous in CA bus space.
+ */
+static dma_addr_t
+tioca_dma_mapped(struct pci_dev *pdev, uint64_t paddr, size_t req_size)
+{
+ int i, ps, ps_shift, entry, entries, mapsize, last_entry;
+ uint64_t xio_addr, end_xio_addr;
+ struct tioca_common *tioca_common;
+ struct tioca_kernel *tioca_kern;
+ dma_addr_t bus_addr = 0;
+ struct tioca_dmamap *ca_dmamap;
+ void *map;
+ unsigned long flags;
+ struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(pdev);;
+
+ tioca_common = (struct tioca_common *)pcidev_info->pdi_pcibus_info;
+ tioca_kern = (struct tioca_kernel *)tioca_common->ca_kernel_private;
+
+ xio_addr = PHYS_TO_TIODMA(paddr);
+ if (!xio_addr)
+ return 0;
+
+ spin_lock_irqsave(&tioca_kern->ca_lock, flags);
+
+ /*
+ * allocate a map struct
+ */
+
+ ca_dmamap = kcalloc(1, sizeof(struct tioca_dmamap), GFP_ATOMIC);
+ if (!ca_dmamap)
+ goto map_return;
+
+ /*
+ * Locate free entries that can hold req_size. Account for
+ * unaligned start/length when allocating.
+ */
+
+ ps = tioca_kern->ca_ap_pagesize; /* will be power of 2 */
+ ps_shift = ffs(ps) - 1;
+ end_xio_addr = xio_addr + req_size - 1;
+
+ entries = (end_xio_addr >> ps_shift) - (xio_addr >> ps_shift) + 1;
+
+ map = tioca_kern->ca_pcigart_pagemap;
+ mapsize = tioca_kern->ca_pcigart_entries;
+
+ entry = find_first_zero_bit(map, mapsize);
+ while (entry < mapsize) {
+ last_entry = find_next_bit(map, mapsize, entry);
+
+ if (last_entry - entry >= entries)
+ break;
+
+ entry = find_next_zero_bit(map, mapsize, last_entry);
+ }
+
+ if (entry > mapsize)
+ goto map_return;
+
+ for (i = 0; i < entries; i++)
+ set_bit(entry + i, map);
+
+ bus_addr = tioca_kern->ca_pciap_base + (entry * ps);
+
+ ca_dmamap->cad_dma_addr = bus_addr;
+ ca_dmamap->cad_gart_size = entries;
+ ca_dmamap->cad_gart_entry = entry;
+ list_add(&ca_dmamap->cad_list, &tioca_kern->ca_dmamaps);
+
+ if (xio_addr % ps) {
+ tioca_kern->ca_pcigart[entry] = tioca_paddr_to_gart(xio_addr);
+ bus_addr += xio_addr & (ps - 1);
+ xio_addr &= ~(ps - 1);
+ xio_addr += ps;
+ entry++;
+ }
+
+ while (xio_addr < end_xio_addr) {
+ tioca_kern->ca_pcigart[entry] = tioca_paddr_to_gart(xio_addr);
+ xio_addr += ps;
+ entry++;
+ }
+
+ tioca_tlbflush(tioca_kern);
+
+map_return:
+ spin_unlock_irqrestore(&tioca_kern->ca_lock, flags);
+ return bus_addr;
+}
+
+/**
+ * tioca_dma_unmap - release CA mapping resources
+ * @pdev: linux pci_dev representing the function
+ * @bus_addr: bus address returned by an earlier tioca_dma_map
+ * @dir: mapping direction (unused)
+ *
+ * Locate mapping resources associated with @bus_addr and release them.
+ * For mappings created using the direct modes (64 or 48) there are no
+ * resources to release.
+ */
+void
+tioca_dma_unmap(struct pci_dev *pdev, dma_addr_t bus_addr, int dir)
+{
+ int i, entry;
+ struct tioca_common *tioca_common;
+ struct tioca_kernel *tioca_kern;
+ struct tioca_dmamap *map;
+ struct pcidev_info *pcidev_info = SN_PCIDEV_INFO(pdev);
+ unsigned long flags;
+
+ tioca_common = (struct tioca_common *)pcidev_info->pdi_pcibus_info;
+ tioca_kern = (struct tioca_kernel *)tioca_common->ca_kernel_private;
+
+ /* return straight away if this isn't be a mapped address */
+
+ if (bus_addr < tioca_kern->ca_pciap_base ||
+ bus_addr >= (tioca_kern->ca_pciap_base + tioca_kern->ca_pciap_size))
+ return;
+
+ spin_lock_irqsave(&tioca_kern->ca_lock, flags);
+
+ list_for_each_entry(map, &tioca_kern->ca_dmamaps, cad_list)
+ if (map->cad_dma_addr == bus_addr)
+ break;
+
+ BUG_ON(map == NULL);
+
+ entry = map->cad_gart_entry;
+
+ for (i = 0; i < map->cad_gart_size; i++, entry++) {
+ clear_bit(entry, tioca_kern->ca_pcigart_pagemap);
+ tioca_kern->ca_pcigart[entry] = 0;
+ }
+ tioca_tlbflush(tioca_kern);
+
+ list_del(&map->cad_list);
+ spin_unlock_irqrestore(&tioca_kern->ca_lock, flags);
+ kfree(map);
+}
+
+/**
+ * tioca_dma_map - map pages for PCI DMA
+ * @pdev: linux pci_dev representing the function
+ * @paddr: host physical address to map
+ * @byte_count: bytes to map
+ *
+ * This is the main wrapper for mapping host physical pages to CA PCI space.
+ * The mapping mode used is based on the devices dma_mask. As a last resort
+ * use the GART mapped mode.
+ */
+uint64_t
+tioca_dma_map(struct pci_dev *pdev, uint64_t paddr, size_t byte_count)
+{
+ uint64_t mapaddr;
+
+ /*
+ * If card is 64 or 48 bit addresable, use a direct mapping. 32
+ * bit direct is so restrictive w.r.t. where the memory resides that
+ * we don't use it even though CA has some support.
+ */
+
+ if (pdev->dma_mask == ~0UL)
+ mapaddr = tioca_dma_d64(paddr);
+ else if (pdev->dma_mask == 0xffffffffffffUL)
+ mapaddr = tioca_dma_d48(pdev, paddr);
+ else
+ mapaddr = 0;
+
+ /* Last resort ... use PCI portion of CA GART */
+
+ if (mapaddr == 0)
+ mapaddr = tioca_dma_mapped(pdev, paddr, byte_count);
+
+ return mapaddr;
+}
+
+/**
+ * tioca_error_intr_handler - SGI TIO CA error interrupt handler
+ * @irq: unused
+ * @arg: pointer to tioca_common struct for the given CA
+ * @pt: unused
+ *
+ * Handle a CA error interrupt. Simply a wrapper around a SAL call which
+ * defers processing to the SGI prom.
+ */
+static irqreturn_t
+tioca_error_intr_handler(int irq, void *arg, struct pt_regs *pt)
+{
+ struct tioca_common *soft = arg;
+ struct ia64_sal_retval ret_stuff;
+ uint64_t segment;
+ uint64_t busnum;
+ ret_stuff.status = 0;
+ ret_stuff.v0 = 0;
+
+ segment = 0;
+ busnum = soft->ca_common.bs_persist_busnum;
+
+ SAL_CALL_NOLOCK(ret_stuff,
+ (u64) SN_SAL_IOIF_ERROR_INTERRUPT,
+ segment, busnum, 0, 0, 0, 0, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tioca_bus_fixup - perform final PCI fixup for a TIO CA bus
+ * @prom_bussoft: Common prom/kernel struct representing the bus
+ *
+ * Replicates the tioca_common pointed to by @prom_bussoft in kernel
+ * space. Allocates and initializes a kernel-only area for a given CA,
+ * and sets up an irq for handling CA error interrupts.
+ *
+ * On successful setup, returns the kernel version of tioca_common back to
+ * the caller.
+ */
+void *
+tioca_bus_fixup(struct pcibus_bussoft *prom_bussoft)
+{
+ struct tioca_common *tioca_common;
+ struct tioca_kernel *tioca_kern;
+ struct pci_bus *bus;
+
+ /* sanity check prom rev */
+
+ if (sn_sal_rev_major() < 4 ||
+ (sn_sal_rev_major() == 4 && sn_sal_rev_minor() < 6)) {
+ printk
+ (KERN_ERR "%s: SGI prom rev 4.06 or greater required "
+ "for tioca support\n", __FUNCTION__);
+ return NULL;
+ }
+
+ /*
+ * Allocate kernel bus soft and copy from prom.
+ */
+
+ tioca_common = kcalloc(1, sizeof(struct tioca_common), GFP_KERNEL);
+ if (!tioca_common)
+ return NULL;
+
+ memcpy(tioca_common, prom_bussoft, sizeof(struct tioca_common));
+ tioca_common->ca_common.bs_base |= __IA64_UNCACHED_OFFSET;
+
+ /* init kernel-private area */
+
+ tioca_kern = kcalloc(1, sizeof(struct tioca_kernel), GFP_KERNEL);
+ if (!tioca_kern) {
+ kfree(tioca_common);
+ return NULL;
+ }
+
+ tioca_kern->ca_common = tioca_common;
+ spin_lock_init(&tioca_kern->ca_lock);
+ INIT_LIST_HEAD(&tioca_kern->ca_dmamaps);
+ tioca_kern->ca_closest_node =
+ nasid_to_cnodeid(tioca_common->ca_closest_nasid);
+ tioca_common->ca_kernel_private = (uint64_t) tioca_kern;
+
+ bus = pci_find_bus(0, tioca_common->ca_common.bs_persist_busnum);
+ BUG_ON(!bus);
+ tioca_kern->ca_devices = &bus->devices;
+
+ /* init GART */
+
+ if (tioca_gart_init(tioca_kern) < 0) {
+ kfree(tioca_kern);
+ kfree(tioca_common);
+ return NULL;
+ }
+
+ tioca_gart_found++;
+ list_add(&tioca_kern->ca_list, &tioca_list);
+
+ if (request_irq(SGI_TIOCA_ERROR,
+ tioca_error_intr_handler,
+ SA_SHIRQ, "TIOCA error", (void *)tioca_common))
+ printk(KERN_WARNING
+ "%s: Unable to get irq %d. "
+ "Error interrupts won't be routed for TIOCA bus %d\n",
+ __FUNCTION__, SGI_TIOCA_ERROR,
+ (int)tioca_common->ca_common.bs_persist_busnum);
+
+ return tioca_common;
+}
+
+static struct sn_pcibus_provider tioca_pci_interfaces = {
+ .dma_map = tioca_dma_map,
+ .dma_map_consistent = tioca_dma_map,
+ .dma_unmap = tioca_dma_unmap,
+ .bus_fixup = tioca_bus_fixup,
+};
+
+/**
+ * tioca_init_provider - init SN PCI provider ops for TIO CA
+ */
+int
+tioca_init_provider(void)
+{
+ sn_pci_provider[PCIIO_ASIC_TYPE_TIOCA] = &tioca_pci_interfaces;
+ return 0;
+}