/*
* 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 <linux/delay.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)