--- /dev/null	2013-09-14 15:00:33.000000000 +0000
+++ new/usr/src/uts/common/os/interrupt_balancer.c	2013-09-14 15:00:33.555906991 +0000
@@ -0,0 +1,1387 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2013 David Hoeppner.  All rights reserved.
+ */
+
+/*
+ * Interrupt Load Balancer.
+ *
+ * The original balance functions views all CPU as equally.
+ */
+
+/* XXX
+ *
+ * ib_cpu_list::walk list |::print ib_cpu_t
+ *
+ *
+ *
+ */
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/callb.h>
+#include <sys/cpuvar.h>
+#include <sys/proc.h>
+#include <sys/processor.h>
+#include <sys/sdt.h>
+#include <sys/sysmacros.h>
+#include <sys/time.h>
+#include <sys/cmn_err.h>
+#include <sys/zone.h>
+#include <sys/lgrp.h>
+
+#include <sys/pci_tools.h>
+
+extern	proc_t	*proc_intrd;
+
+#define	IB_NAME			"intrd"
+
+#define	IS_CPU(cpu_id)		(cpu[cpu_id] != NULL)
+
+#define	IB_NORMAL_SLEEPTIME	10
+#define	IB_IDLE_SLEEPTIME	45
+#define IB_ONECPU_SLEEPTIME	(60 * 15)
+
+#define	IB_NUM_SAMPLES		6
+
+
+static kmutex_t		ib_lock;
+static kcondvar_t	ib_cv;
+
+/*
+ * Interrupt CPU instance.
+ */
+typedef struct _ib_cpu {
+	list_node_t	ic_next;
+	boolean_t	ic_offline;
+
+	hrtime_t	ic_tot;
+	list_t		ic_ivec_list;
+	uint32_t	ic_num_ivecs;
+	processorid_t	ic_cpu_id;	/* XXX duplicate */
+	int64_t		ic_intrs;
+	int64_t		ic_big_intrs;
+	int64_t		ic_bigintr;	/* XXX bitintrs */
+
+	int		ic_intr_load;	/* intrs / tot */
+} ib_cpu_t;
+
+/*
+ * Interrupt vector instance.
+ */
+typedef struct _ib_ivec {
+	list_node_t	ii_next;	/* link */
+
+	uint64_t	ii_ihs;
+	uint64_t	ii_ino;
+	uint64_t	ii_num_ino;
+	uint64_t	ii_pil;
+	uint64_t	ii_time;
+	char		*ii_buspath;
+	char		*ii_name;
+
+	processorid_t	ii_orig_cpu;
+	processorid_t	ii_now_cpu;
+	uint64_t	ii_inum;
+} ib_ivec_t;
+
+/*
+ * MSI
+ */
+typedef struct _ib_msi {
+	list_node_t	im_next;	/* link */
+	const char	*im_name;
+	list_t		im_ino_list;
+} ib_msi_t;
+
+typedef struct _ib_msi_ino {
+	list_node_t	imi_next;	/* link */
+	uint64_t	imi_ino;
+	ib_ivec_t	*imi_ivec;
+} ib_msi_ino_t;
+
+/*
+ * Snapshot
+ */
+typedef struct _ib_snapshot {
+	list_node_t	is_next;	/* link */
+	list_t		is_cpu_list;
+	processorid_t	is_num_cpus;
+} ib_snapshot_t;
+
+/*
+ * Snapshot delta structure.
+ */
+typedef struct _ib_delta {
+	list_node_t	id_next;	/* link */
+	list_t		id_cpu_list;
+	boolean_t	id_missing;
+	int		id_avgintrload;	/* interrupts / total time */
+	uint64_t	id_avgintrnsec;
+	int		id_goodness;
+} ib_delta_t;
+
+
+static list_t	ib_cpu_list;		/* List of all CPU's */
+
+static uint8_t	ib_cs = 0;		/* Index of current sample  */
+static long	ib_sleeptime = IB_NORMAL_SLEEPTIME;
+static processorid_t	ib_num_cpus;
+
+static int	goodness_unsafe_load = 90;
+static int	goodness_mindelta = 10;
+
+/*
+ * Function prototypes.
+ */
+static	void	ib_cpu_register(processorid_t);
+static	int	ib_cpu_setup(cpu_setup_t, int, void *);
+static	int	ib_goodness(ib_delta_t *);
+static	int	ib_do_reconfig(ib_delta_t *);
+static	int	ib_imbalanced(int, int);
+static 	int	ib_interrupt_do_move(ib_ivec_t *, processorid_t);
+
+static	ib_snapshot_t	*ib_get_statistics(void);
+static	ib_delta_t	*ib_delta_generate(ib_snapshot_t *, ib_snapshot_t *);
+
+/*
+ * Helper macros.
+ */
+#define	FOREACH_CPU(icpu, icpu_list)				\
+	for (icpu = list_head(&icpu_list); icpu != NULL;	\
+	    icpu = list_next(&icpu_list, icpu))
+
+#define	FOREACH_IVEC(ivec, ivec_list)				\
+	for (ivec = list_head(&ivec_list); ivec != NULL;	\
+	    ivec = list_next(&ivec_list, ivec))
+
+#define	DTRACE_INTRD(name)	\
+	DTRACE_PROBE(__intrd_##name)
+
+#define	DEBUG	1
+#ifdef	DEBUG
+#define	IB_APIDBG(args)	cmn_err args
+#define	IB_IMPLDBG(args)	cmn_err args
+#else
+#define	IB_APIDBG(args)
+#define	IB_IMPLDBG(args)
+#endif
+
+#define	IB_LOG(args)	cmn_err args
+
+void
+interrupt_balancer(void)
+{
+	processorid_t	cpu_id;
+	callb_cpr_t	cpr;
+	user_t		*u = PTOU(curproc);
+	int		error;
+
+	boolean_t	do_reconfig = B_FALSE;
+	int		goodness;
+	int		baseline_goodness = 0;
+	list_t		ib_delta_list;
+	hrtime_t	statslen = 60;
+
+	proc_intrd = ttoproc(curthread);
+	proc_intrd->p_cstime = proc_intrd->p_stime = 0;
+	proc_intrd->p_cutime = proc_intrd->p_utime = 0;
+
+	(void) strncpy(u->u_psargs, IB_NAME, sizeof(u->u_psargs));
+	(void) strncpy(u->u_comm, IB_NAME, sizeof(u->u_comm));
+
+	/* Initialize global mutex lock */
+	mutex_init(&ib_lock, NULL, MUTEX_DEFAULT, NULL);
+
+	/* Initialize CPU list */
+	list_create(&ib_cpu_list, sizeof (ib_cpu_t),
+	    offsetof(ib_cpu_t, ic_next));
+
+	/* Initialize delta list */
+	list_create(&ib_delta_list, sizeof (ib_delta_t),
+	    offsetof(ib_delta_t, id_next));
+
+	/*
+	 * Build a list of all CPUs available for interrupt handling.
+	 */
+	for (cpu_id = 0; cpu_id <= max_cpu_seqid_ever; cpu_id++) {
+		if (IS_CPU(cpu_id))
+			ib_cpu_register(cpu_id);
+	}
+
+	/*
+	 * Locality group information.
+	 */
+	int	i;
+	for (i = 0; i < lgrp_plat_max_lgrps(); i++) {
+		lgrp_t	*lgrp;
+
+		lgrp = lgrp_table[i];
+	}
+
+	/*
+	 * Register a callback if a CPU goes offline or comes online.
+	 */
+	mutex_enter(&cpu_lock);
+	register_cpu_setup_func(ib_cpu_setup, NULL);
+	mutex_exit(&cpu_lock);
+
+	CALLB_CPR_INIT(&cpr, &ib_lock, callb_generic_cpr, IB_NAME);
+
+	ib_snapshot_t	*snapshot = NULL;
+	ib_snapshot_t	*new_snapshot = NULL;
+	hrtime_t	delta_time;
+	hrtime_t	deltas_tottime = 0;
+	boolean_t	below_statslen;
+
+	snapshot = ib_get_statistics();
+
+	mutex_enter(&ib_lock);
+	for (;;) {
+		ib_delta_t	*delta;
+
+		DTRACE_INTRD(get_stats);
+		new_snapshot = ib_get_statistics();
+
+		delta = ib_delta_generate(snapshot, new_snapshot);
+
+		below_statslen = (deltas_tottime < statslen);
+		deltas_tottime += delta_time;
+		do_reconfig = (below_statslen && deltas_tottime >= statslen);
+
+		list_insert_tail(&ib_delta_list, delta);
+
+		/*
+		 * Calculate the goodness of the current configuration.
+		 */
+		goodness = ib_goodness(delta);
+
+		if (ib_imbalanced(goodness, baseline_goodness))
+			do_reconfig = B_TRUE;
+
+		/*
+		 * Reconfigure interrupt distribution.
+		 */
+		if (do_reconfig) {
+			error = ib_do_reconfig(delta);
+
+			if (error != 0) {
+				if (error == -1)
+					IB_LOG((CE_CONT, "ib_do_reconfig failed!"));
+			} else {
+				IB_LOG((CE_CONT, "setting new baseline of %d", goodness));
+				baseline_goodness = goodness;
+			}
+		}
+
+		/*
+		 * Wait for timeout or CPU reconfiguration.
+	 	 */
+		CALLB_CPR_SAFE_BEGIN(&cpr);
+		cv_timedwait(&ib_cv, &ib_lock, ddi_get_lbolt() +
+		    SEC_TO_TICK(ib_sleeptime));
+		CALLB_CPR_SAFE_END(&cpr, &ib_lock);
+	}
+
+	CALLB_CPR_EXIT(&cpr);
+
+	/*
+	 * Unregister CPU callback.
+	 */
+	mutex_enter(&cpu_lock);
+	unregister_cpu_setup_func(ib_cpu_setup, NULL);
+	mutex_exit(&cpu_lock);
+}
+
+/*
+ * Register a new CPU in the global list of CPUs.
+ */
+static void
+ib_cpu_register(processorid_t cpu_id)
+{
+	cpu_t		*cp = cpu[cpu_id];
+	ib_cpu_t	*new_cpu;
+
+	new_cpu = kmem_alloc(sizeof (ib_cpu_t), KM_SLEEP);
+	new_cpu->ic_cpu_id = cpu_id;
+
+	/* Initialize list for interrupt vectors */
+	list_create(&new_cpu->ic_ivec_list, sizeof (ib_ivec_t),
+	    offsetof(ib_ivec_t, ii_next));
+
+	list_link_init(&new_cpu->ic_next);
+
+	/* Check if this CPU can handle interrupts */
+	mutex_enter(&cpu_lock);
+	if (cpu_is_nointr(cp))
+		new_cpu->ic_offline = B_TRUE;
+	else
+		new_cpu->ic_offline = B_FALSE;
+	mutex_exit(&cpu_lock);
+
+	/* Add CPU to list of CPUs */
+	list_insert_tail(&ib_cpu_list, new_cpu);
+
+	ib_num_cpus++;
+
+	IB_IMPLDBG((CE_CONT, "ib_cpu_register: cpu=0x%x", cpu_id));
+}
+
+/*
+ * Unregister CPU from the global list of CPUs.
+ */
+static void
+ib_cpu_unregister(processorid_t cpu_id)
+{
+	ib_cpu_t	*icpu;
+
+	mutex_enter(&ib_lock);
+	FOREACH_CPU(icpu, ib_cpu_list) {
+		if (icpu->ic_cpu_id == cpu_id) {
+			list_remove(&ib_cpu_list, icpu);
+			/* XXX or just offline CPU; statistics? */
+			break;
+		}
+	}
+	mutex_exit(&ib_lock);
+
+	ib_num_cpus--;
+
+	IB_IMPLDBG((CE_CONT, "ib_cpu_unregister: cpu=0x%x",
+	    cpu_id));
+}
+
+/*
+ * Hook for CPU changes.
+ */
+static int
+ib_cpu_setup(cpu_setup_t what, int cpu_id, void *arg)
+{
+
+	switch (what) {
+	/* XXX */
+	case CPU_OFF:
+		ib_cpu_unregister(cpu_id);
+		cv_signal(&ib_cv);
+		break;
+
+	case CPU_INTR_ON:
+		ib_cpu_register(cpu_id);
+		cv_signal(&ib_cv);
+		break;
+
+	default:
+		break;
+	}
+
+	return (0);
+}
+
+static ib_cpu_t *
+ib_cpu_create(void)
+{
+	ib_cpu_t	*icpu;
+
+	icpu = kmem_alloc(sizeof (ib_cpu_t), KM_SLEEP);
+
+	return (icpu);
+}
+
+/*
+ * Find a CPU in the global list of CPUs by processor id.
+ */
+static ib_cpu_t *
+ib_cpu_find(list_t cpu_list, processorid_t cpu_id)
+{
+	ib_cpu_t	*icpu;
+
+	IB_APIDBG((CE_CONT, "ib_cpu_find: API cpu = %d", cpu_id));
+
+	FOREACH_CPU(icpu, cpu_list) {
+		if (icpu->ic_cpu_id == cpu_id)
+			return (icpu);
+	}
+
+	return (NULL);
+}
+
+/*
+ * Find a interrupt vector for a specific CPU.
+ */
+static ib_ivec_t *
+ib_cpu_find_ivec(list_t cpu_list, processorid_t cpu_id, char *buspath, uint64_t ino)
+{
+	ib_cpu_t	*icpu;
+	ib_ivec_t	*ivec;
+
+	icpu = ib_cpu_find(cpu_list, cpu_id);
+	if (icpu == NULL)
+		return (NULL);
+
+	for (ivec = list_head(&icpu->ic_ivec_list); ivec != NULL;
+	    ivec = list_next(&icpu->ic_ivec_list, ivec)) {
+		if (ivec->ii_ino == ino)
+			return (ivec);
+	}
+
+	return (NULL);
+}
+
+/*
+ * Total times spend.
+ */
+static void
+ib_cpu_statistics(ib_cpu_t *icpu)
+{
+	cpu_t		*cp;
+	hrtime_t	msnsecs[NCMSTATES];
+	hrtime_t	new_tot;
+
+	cp = cpu[icpu->ic_cpu_id];
+	get_cpu_mstate(cp, msnsecs);
+
+	icpu->ic_tot = msnsecs[CMS_IDLE] + msnsecs[CMS_USER] +
+	    msnsecs[CMS_SYSTEM];
+
+}
+
+/*
+ * Create a new interrupt vector.
+ */
+static ib_ivec_t *
+ib_ivec_create(const char *buspath, uint64_t ino)
+{
+	ib_ivec_t	*ivec;
+
+	ivec = (ib_ivec_t *)kmem_alloc(sizeof (ib_ivec_t), KM_SLEEP);
+	
+	list_link_init(&ivec->ii_next);
+
+	ivec->ii_buspath = (char *)buspath;	/* XXX: strdup */
+	ivec->ii_ino = ino;
+	ivec->ii_ihs = 1;
+
+	return (ivec);
+}
+
+static void
+intrd_ivec_register(ib_cpu_t *icpu)
+{
+}
+
+/*
+ * Find interrupt vector by ino.
+ */
+static ib_ivec_t *
+ib_ivec_find_ino(list_t ivec_list, uint64_t ino)
+{
+	ib_ivec_t	*ivec;
+
+	FOREACH_IVEC(ivec, ivec_list) {
+		if (ivec->ii_inum == ino)
+			return (ivec);
+	}
+
+	return (NULL);
+}
+
+/*
+ * Delete a interrupt vector from a list.
+ */
+static void
+ib_ivec_delete_ino(list_t ivec_list, uint64_t ino)
+{
+	ib_ivec_t	*ivec;
+
+	FOREACH_IVEC(ivec, ivec_list) {
+		if (ivec->ii_inum == ino) {
+			/* XXX: remove from list */
+			;
+		}
+	}
+}
+
+/*
+ * Add a new interrupt vector to a list.
+ */
+static void
+ib_ivec_add_ino(list_t ivec_list, ib_ivec_t *ivec)
+{
+	list_insert_tail(&ivec_list, ivec);
+}
+
+static ib_msi_t *
+ib_msi_create(const char *name)
+{
+	ib_msi_t	*msi;
+
+	msi = (ib_msi_t *)kmem_alloc(sizeof (ib_msi_t), KM_SLEEP);
+
+	msi->im_name = name;
+
+	list_link_init(&msi->im_next);
+	list_create(&msi->im_ino_list, sizeof (ib_msi_ino_t),
+	    offsetof(ib_msi_ino_t, imi_next));
+
+	return (msi);
+}
+
+/*
+ * Allocate and initialize a new snapshot structure.
+ */
+static ib_snapshot_t *
+ib_snapshot_create(void)
+{
+	ib_snapshot_t	*snapshot;
+
+	snapshot = kmem_alloc(sizeof (ib_snapshot_t), KM_SLEEP);
+
+	/* init link */
+
+	/* Initialize CPU list */
+	list_create(&snapshot->is_cpu_list, sizeof (ib_cpu_t),
+	    offsetof(ib_cpu_t, ic_next));
+
+	snapshot->is_num_cpus = 0;
+
+	return (snapshot);
+}
+
+static ib_ivec_t *
+ib_irq_fill_ivec(kstat_t *ksp)
+{
+	kstat_named_t	*knp;
+	ib_ivec_t	*ivec;
+	char		*datap;
+	uint64_t	time;
+	int		i;
+
+	datap = ksp->ks_data;
+	knp = KSTAT_NAMED_PTR(ksp);
+	for (i = 0; i < ksp->ks_ndata; i++, knp++) {
+		IB_IMPLDBG((CE_CONT, "ib_irq_fill_ivec: %s",
+		    knp->name));
+
+		if (strcmp(knp->name, "time") == 0) {
+			cmn_err(CE_CONT, "XXX ib time");
+			time = knp->value.ui64;
+		}
+
+		knp += sizeof (kstat_named_t);
+		datap += sizeof (kstat_named_t);
+	}
+
+	/* Allocate a new interrupt vector */
+	ivec = ib_ivec_create("", 0);
+	ivec->ii_time = time;
+
+	return (ivec);
+}
+
+/*
+ * XXX: icpu not needed, move out of loop
+ */
+static void
+ib_irq_statistics(ib_cpu_t *icpu)
+{
+	kstat_t		*ksp;
+	int		instance = 1;
+
+	/*
+	 * Read pci interrupts.
+	 */
+	ksp = kstat_hold_byname("pci_intrs", instance, "pci", ALL_ZONES);
+	while (ksp != NULL) {
+		KSTAT_ENTER(ksp);
+
+		if (ksp->ks_data != NULL && ksp->ks_type == KSTAT_TYPE_NAMED) {
+			ib_cpu_t	*icpu;
+			ib_ivec_t	*ivec;
+			kstat_named_t	*knp;
+			kstat_named_t	*datap;
+			uint64_t	ino;
+			char		*buspath;
+			char		*namep;
+			processorid_t	cpu_id;
+			int		i;
+			boolean_t	is_enabled = B_TRUE;
+
+			(void) KSTAT_UPDATE(ksp, KSTAT_READ);
+
+			/*
+			 * Find the CPU this interrupt vector is on and
+			 * if the vector itself is enabled.
+			 */
+			datap = ksp->ks_data;
+			namep = KSTAT_NAMED_PTR(ksp)->name;
+			for (i = 0; i < ksp->ks_ndata; i++) {
+				if (strcmp(namep, "cpu") == 0) {
+					cpu_id = datap->value.ui64;
+				} else if (strcmp(namep, "type") == 0) {
+					if (strcmp(datap->value.c, "disabled") == 0) {
+						is_enabled = B_FALSE;
+						break;
+					}
+				}
+
+				namep += sizeof (kstat_named_t);
+				datap += sizeof (kstat_named_t);
+			}
+
+			/*
+			 * Skip this interrupt vector if its disabled.
+			 */
+			if (!is_enabled)
+				continue;
+
+			/*
+			 * Check if CPU is online.
+			 */
+			icpu = ib_cpu_find(ib_cpu_list, cpu_id);
+			if (icpu == NULL || icpu->ic_offline)
+				continue;
+
+			/*
+			 * Fill information.
+			 */
+			ivec = ib_irq_fill_ivec(ksp);
+			if (ivec == NULL)
+				continue;
+
+			list_insert_tail(&icpu->ic_ivec_list, ivec);
+		}
+
+		KSTAT_EXIT(ksp);
+		kstat_rele(ksp);
+
+		instance++;
+		ksp = kstat_hold_byname("pci_intrs", instance, "pci", ALL_ZONES);
+	}
+}
+
+/*
+ * Collect data from CPUs and interrupt vectors.
+ */
+static ib_snapshot_t *
+ib_get_statistics(void)
+{
+	ib_cpu_t	*os_cpu;
+	ib_snapshot_t	*snapshot;
+	ib_cpu_t	*snapshot_cpu;
+
+	/*
+	 * Nothing to balance with one CPU. XXX: right place?
+	 */
+	if (ib_num_cpus <= 1) {
+		ib_sleeptime = IB_ONECPU_SLEEPTIME;
+		return (NULL);
+	}
+
+	/*
+	 * Store all CPUs and ivecs here.
+	 */
+	snapshot = ib_snapshot_create();
+
+	/*
+	 * Loop over all active CPUs
+	 */
+	FOREACH_CPU(os_cpu, ib_cpu_list) {
+
+		snapshot->is_num_cpus++;
+
+		snapshot_cpu = ib_cpu_create();
+		snapshot_cpu->ic_cpu_id = os_cpu->ic_cpu_id;
+
+		list_insert_tail(&snapshot->is_cpu_list, snapshot_cpu);
+
+		ib_cpu_statistics(snapshot_cpu);
+		ib_irq_statistics(os_cpu);
+	}
+
+	return (snapshot);
+}
+
+static ib_delta_t *
+ib_delta_create(void)
+{
+	ib_delta_t	*delta;
+
+	delta = kmem_alloc(sizeof (ib_delta_t), KM_SLEEP);
+	delta->id_missing = B_FALSE;
+
+	list_create(&delta->id_cpu_list, sizeof (ib_cpu_t),
+	    offsetof(ib_cpu_t, ic_next));
+
+	return (delta);
+}
+
+/*
+ * Generate the delta of two snapshots.
+ */
+static ib_delta_t *
+ib_delta_generate(ib_snapshot_t *old_snapshot, ib_snapshot_t *new_snapshot)
+{
+	ib_cpu_t	*old_cpu, *new_cpu;
+	ib_delta_t	*delta;
+	int		intrload = 0;
+	int		intrnsec = 0;
+	processorid_t	cpus = 0;
+
+	/*
+	 * Allocate a new delta structure.
+	 */
+	delta = ib_delta_create();
+
+	/*
+	 * Number of CPUs must be the same.
+	 */
+	delta->id_missing = old_snapshot->is_num_cpus !=
+	    new_snapshot->is_num_cpus;
+
+	if (delta->id_missing != 0) {
+		IB_LOG((CE_CONT, "ib_delta_generate: number of CPUs changed"));
+		return (delta);
+	}
+
+	/*
+	 * Loop over the CPUs in both snapshots.
+	 */
+	for (new_cpu = list_head(&new_snapshot->is_cpu_list),
+	    old_cpu = list_head(&old_snapshot->is_cpu_list);
+	    new_cpu != NULL && old_cpu != NULL;
+	    new_cpu = list_next(&new_snapshot->is_cpu_list, new_cpu),
+	    old_cpu = list_next(&old_snapshot->is_cpu_list, old_cpu)) {
+		ib_cpu_t	*delta_cpu;
+		ib_ivec_t	*new_ivec;
+
+		/* XXX: just onlined CPU? */
+
+		/* Allocate a new CPU structure */
+		delta_cpu = ib_cpu_create();
+
+		/* Difference of total time */
+		delta_cpu->ic_tot = new_cpu->ic_tot - old_cpu->ic_tot;
+		if (!(delta_cpu->ic_tot >= 0)) {
+			delta->id_missing = B_TRUE;
+			kmem_free(delta_cpu, sizeof (ib_cpu_t));
+			return (delta);
+		}
+
+		list_insert_tail(&delta->id_cpu_list, delta_cpu);
+
+		/* Avoid division by zero */
+		if (delta_cpu->ic_tot == 0)
+			delta_cpu->ic_tot = 1;
+
+		delta_cpu->ic_intrs = 0;
+		delta_cpu->ic_big_intrs = 0;
+
+		/*
+		 * Number of interrupt vectors must be the same.
+		 */
+		if (old_cpu->ic_num_ivecs != new_cpu->ic_num_ivecs) {
+			IB_LOG((CE_CONT, "ib_delta_generate: cpu %d has more "
+			    "or less interrupts", old_cpu->ic_cpu_id));
+			delta->id_missing = B_TRUE;
+			return (delta);
+		}
+
+		/*
+		 * Loop over the interrupt vectors of the new CPU.
+		 */
+		for (new_ivec = list_head(&new_cpu->ic_ivec_list);
+		    new_ivec != NULL; new_ivec =
+		    list_next(&new_cpu->ic_ivec_list, new_ivec)) {
+			ib_ivec_t	*ivec;
+			ib_ivec_t	*delta_ivec;
+			hrtime_t	time;
+
+			if (new_ivec->ii_num_ino == 0)
+				continue;
+
+			/*
+			 * If interrupt vector does not exists or XXX crtime
+			 * is different, set missing.
+			 */
+			ivec = ib_ivec_find_ino(old_cpu->ic_ivec_list,
+			    new_ivec->ii_ino);
+			if (ivec == NULL) {
+				delta->id_missing = B_TRUE;
+				return (delta);
+			}
+
+			/* Allocate a new delta interrupt vector */
+			delta_ivec = ib_ivec_create(new_ivec->ii_buspath,
+			    new_ivec->ii_ino);
+
+			/*
+			 * Time used by this interrupt.
+			 */
+			time = new_ivec->ii_time - ivec->ii_time;
+			if (time < 0) {
+				delta->id_missing = B_TRUE;
+				kmem_free(delta_ivec, sizeof (ib_delta_t));
+				return (delta);
+			}
+
+			delta_cpu->ic_intrs += time;
+			delta_ivec->ii_time = time;
+
+			if (time > delta_cpu->ic_bigintr)
+				delta_cpu->ic_bigintr = time;
+
+			/*
+			 * Fill in the rest.
+			 */
+			delta_ivec->ii_ihs = new_ivec->ii_ihs;
+			delta_ivec->ii_pil = new_ivec->ii_pil;
+			delta_ivec->ii_ino = new_ivec->ii_ino;
+			delta_ivec->ii_num_ino = new_ivec->ii_num_ino;
+			/* XXX: buspath, name */
+		}
+
+		/*
+		 * Rounding error
+		 */
+		if (delta_cpu->ic_tot < delta_cpu->ic_intrs)
+			delta_cpu->ic_tot = delta_cpu->ic_intrs;
+
+		delta_cpu->ic_intr_load =
+		    delta_cpu->ic_intrs / delta_cpu->ic_tot;
+		intrload += delta_cpu->ic_intr_load;
+		intrnsec += delta_cpu->ic_intrs;
+
+		cpus++;
+	}
+
+	if (cpus > 0) {
+		delta->id_avgintrload = intrload / cpus;
+		delta->id_avgintrnsec = intrnsec / cpus;
+	} else {
+		delta->id_avgintrload = 0;
+		delta->id_avgintrnsec = 0;
+	}
+
+	return (delta);
+}
+
+/*
+ * Compress deltas.
+ */
+static ib_delta_t *
+ib_delta_compress(list_t *deltas)
+{
+	ib_cpu_t	*icpu;
+	ib_ivec_t	*ivec;
+	ib_delta_t	*new_delta, *delta;
+	processorid_t	cpus = 0;
+	int		high_intrload = 0;
+	int		intrs = 0, tot;
+
+	/* Check if empty list of deltas */
+	if (deltas == NULL || list_is_empty(deltas) != 0) {
+		IB_LOG((CE_CONT, "ib_delta_compress: deltas are empty?"));
+		return (NULL);
+	}
+
+	/* Allocate a new delta structure */
+	new_delta = ib_delta_create();
+
+	/*
+	 * Loop over the deltas in the list.
+	 */
+	for (delta = list_head(deltas); delta != NULL;
+	    delta = list_next(deltas, delta)) {
+
+		/* Compressing bad delta? */
+		if (delta->id_missing) {
+			IB_LOG((CE_CONT,
+			    "ib_delta_compress: compressing bad deltas?"));
+			return (NULL);
+		}
+
+		FOREACH_CPU(icpu, delta->id_cpu_list) {
+			ib_cpu_t	*new_cpu;
+			ib_ivec_t	*new_ivec;
+
+			intrs += icpu->ic_intrs;
+			tot += icpu->ic_tot;
+			new_cpu = ib_cpu_create();
+			new_cpu->ic_cpu_id = icpu->ic_cpu_id;
+			new_cpu->ic_intrs = icpu->ic_intrs;
+			new_cpu->ic_tot = icpu->ic_tot;
+
+			/* XXX: exists ivecs */
+			FOREACH_IVEC(new_ivec, icpu->ic_ivec_list) {
+				ib_ivec_t	*new_delta_ivec;
+
+				new_delta_ivec = ib_ivec_create(
+				    new_ivec->ii_buspath, new_ivec->ii_ino);
+
+			}
+		}
+	}
+
+	FOREACH_CPU(icpu, new_delta->id_cpu_list) {
+		int	bigintr = 0;
+
+		cpus++;
+
+		FOREACH_IVEC(ivec, icpu->ic_ivec_list) {
+			if (ivec->ii_time > bigintr)
+				bigintr = ivec->ii_time;
+		}
+
+		icpu->ic_bigintr = bigintr;
+		icpu->ic_intr_load = icpu->ic_intrs / icpu->ic_tot;
+
+		if (high_intrload < icpu->ic_intr_load)
+			high_intrload = icpu->ic_intr_load;
+
+		if (icpu->ic_tot <= 0)
+			icpu->ic_tot = 100;
+	}
+
+	if (cpus > 0) {
+		new_delta->id_avgintrload = intrs / tot;
+		new_delta->id_avgintrnsec = intrs / cpus;
+	} else {
+		new_delta->id_avgintrload = 0;
+		new_delta->id_avgintrnsec = 0;
+	}
+
+	/* XXX: global sleeptime */
+
+	return (new_delta);
+}
+
+/*
+ * Decide if the load is out of balance.
+ */
+static int
+ib_imbalanced(int goodness, int baseline)
+{
+	if (goodness > 50)
+		return (100);
+
+	/* XXX: abs */
+	if ((goodness - baseline) > goodness_mindelta)
+		return (100);
+
+	return (0);
+}
+
+/*
+ * Calculate goodness of a CPU.
+ */
+static int
+ib_goodness_cpu(ib_cpu_t *icpu, int avg_interrupt_load)
+{
+	int	goodness;
+	int	load, load_no_bigintr;
+
+	load = icpu->ic_intrs / icpu->ic_tot;
+	if (load < avg_interrupt_load)
+		return (0);
+
+	load_no_bigintr = (icpu->ic_intrs - icpu->ic_bigintr) / icpu->ic_tot;
+
+	if ((load > goodness_unsafe_load) && (icpu->ic_num_ivecs > 1))
+		return (1);
+
+	goodness = load - avg_interrupt_load;
+	if (goodness > load_no_bigintr)
+		goodness = load_no_bigintr;
+
+	return (goodness);
+}
+
+/*
+ * Calculate goodness.
+ */
+static int
+ib_goodness(ib_delta_t *delta)
+{
+	ib_cpu_t	*icpu;
+	int		goodness, high_goodness = 0;
+
+	if (delta->id_missing > 0)
+		return (1);
+
+	FOREACH_CPU(icpu, delta->id_cpu_list) {
+		goodness = ib_goodness_cpu(icpu, delta->id_avgintrload);
+		if (!(goodness >= 0 && goodness <= 100)) {
+			IB_LOG((CE_CONT,
+			    "ib_goodness: cpu goodness out of range?"));
+			return (100);
+		}
+
+		if (goodness == 100)
+			return (100);
+
+		if (goodness > high_goodness)
+			high_goodness = goodness;
+	}
+
+	return (high_goodness);
+}
+
+static void
+ib_do_find_goal(list_t ivecs, list_t loads, int goal, int idx)
+{
+	list_t	goals_with;
+	list_t	goals_without;
+	int	with, without;
+	int	which, load;
+
+
+	if (goal <= load) {
+		with = load;
+	} else {
+		/* XXX: do_find_goal */
+		with += load;
+	}
+
+	IB_LOG((CE_CONT, "XXX"));
+
+	if (with >= goal && without < goal) {
+		which = 0;
+	} else if (with < goal && without >= goal) {
+		which = 1;
+	} else if (with >= goal && without >= goal) {
+		which = without < with;
+	} else {
+		which = without > with;
+	}
+
+	if (which == 1) {
+		IB_LOG((CE_CONT, "ib_do_find_goal: going without"));
+		/* XXX */
+	} else {
+		IB_LOG((CE_CONT, "ib_do_find_goal: going with"));
+		/* XXX */
+	}
+}
+
+typedef struct _ib_goal {
+	list_node_t	*ig_link;
+	int		ig_value;
+} ib_goal_t;
+
+typedef struct _ib_goal_load {
+	list_node_t	*igl_link;
+	int		igl_value;
+} ib_goal_load_t;
+
+static void
+ib_find_goal(list_t ivecs, int goal)
+{
+	ib_ivec_t	*ivec;
+	list_t		goals;
+	int		load;
+
+	if (goal <= 0) {
+		list_create(&goals, sizeof (ib_goal_t),
+		   offsetof (ib_goal_t, ig_link));
+	} else {
+		list_t		loads;
+		hrtime_t	tot = 0;
+
+		IB_LOG((CE_CONT, "ib_find_goal: finding goal from intrs XXX"));
+
+		FOREACH_IVEC(ivec, ivecs) {
+			tot += ivec->ii_time;
+		}
+
+		list_create(&loads, sizeof (ib_goal_load_t),
+		    offsetof (ib_goal_load_t, igl_link));
+
+		FOREACH_IVEC(ivec, ivecs) {
+			ib_goal_load_t	*igl = kmem_alloc(sizeof (ib_goal_load_t), KM_SLEEP);
+
+			igl->igl_value = tot;
+			list_insert_tail(&loads, igl);
+
+			tot -= ivec->ii_time;
+		}
+	}
+}
+
+static void
+ib_do_reconfig_cpu2cpu(ib_delta_t *delta, processorid_t src_cpuid,
+    processorid_t tgt_cpuid, int src_load)
+{
+	ib_cpu_t	*src_cpu, *tgt_cpu;
+	ib_ivec_t	*ivec;
+	list_t		ivecs;
+	int		goal;
+	int		avg_nsec;
+
+	if (delta == NULL)
+		return;
+
+	goal = delta->id_avgintrnsec;
+
+	src_cpu = ib_cpu_find(delta->id_cpu_list, src_cpuid);
+	if (src_cpu == NULL)
+		return;
+
+	tgt_cpu = ib_cpu_find(delta->id_cpu_list, tgt_cpuid);
+	if (tgt_cpu == NULL)
+		return;
+
+	avg_nsec = (src_cpu->ic_intrs + tgt_cpu->ic_intrs) / 2;
+	if (goal < avg_nsec)
+		goal = avg_nsec;
+
+
+	/*
+	 * Sort interrupt vectors by time.
+	 */
+	list_create(&ivecs, sizeof (ib_ivec_t),
+	    offsetof (ib_ivec_t, ii_next));
+
+	ivec = list_head(&ivecs);
+	if (ivec->ii_orig_cpu == src_cpuid) {
+		IB_LOG((CE_CONT, "Keeping XXX on %d",
+		    src_cpuid)); /* ivec->ii_inum, */
+		goal -= ivec->ii_time;
+		/* XXX: shift */
+	}
+
+	IB_LOG((CE_CONT, "ib_reconfig_cpu2cpu: inums should total %d", goal));
+
+	ib_find_goal(ivecs, goal);
+}
+
+static void
+ib_do_reconfig_cpu(ib_delta_t *delta, list_t *cpu_sorted_list,
+    processorid_t old_cpu_id)
+{
+	ib_cpu_t	*icpu;
+	int		avgintrload;
+
+	if (delta == NULL)
+		return;
+
+	icpu = ib_cpu_find(delta->id_cpu_list, old_cpu_id);
+	if (icpu == NULL)
+		return;
+
+	avgintrload = delta->id_avgintrload;
+
+}
+
+/*
+ * Reconfigure interrupt distribution among CPUs.
+ */
+static int
+ib_do_reconfig(ib_delta_t *delta)
+{
+	ib_cpu_t	*icpu;
+	ib_ivec_t	*ivec;
+	list_t		cpu_sorted_list;
+	int		goodness, new_goodness;
+	int		warned = 0;
+	int		rval = 1, ret = 1;
+
+	if (delta == NULL)
+		return (-1);
+
+	goodness = delta->id_goodness;
+	if (goodness < goodness_mindelta) {
+		IB_LOG((CE_CONT, "ib_do_reconfig: goodness is good enough"));
+		return (0);
+	}
+
+	IB_LOG((CE_CONT, "ib_do_reconfig: optimizing interrupt assignments"));
+
+	if (delta->id_missing != 0) {
+		IB_LOG((CE_CONT, "ib_do_reconfig: aborted"));
+		return (-1);
+	}
+
+	FOREACH_CPU(icpu, delta->id_cpu_list) {
+		FOREACH_IVEC(ivec, icpu->ic_ivec_list) {
+			ivec->ii_orig_cpu = icpu->ic_cpu_id;
+			ivec->ii_now_cpu = icpu->ic_cpu_id;
+			/* XXX: inum */
+		}
+	}
+
+	list_create(&cpu_sorted_list, sizeof (ib_cpu_t),
+	    offsetof(ib_cpu_t, ic_next));
+
+	/*
+	 * Have we an improvement?
+	 */
+	new_goodness = ib_goodness(delta);
+	if (!(new_goodness <= goodness)) {
+		IB_LOG((CE_CONT,
+		    "ib_do_reconfig: result has worse goodness"));
+	}
+
+	if ((goodness != 100 || new_goodness == 100) &&
+	    goodness - new_goodness < goodness_mindelta) {
+		IB_LOG((CE_CONT,
+		    "ib_do_reconfig: goodness already near optimum"));
+		return (0);
+	}
+
+	/*
+	 * Move interrupts.
+	 */
+
+	FOREACH_CPU(icpu, delta->id_cpu_list) {
+		FOREACH_IVEC(ivec, icpu->ic_ivec_list) {
+			int	error;
+
+			if (ivec->ii_orig_cpu == icpu->ic_cpu_id)
+				continue;
+
+			error = ib_interrupt_do_move(ivec, icpu->ic_cpu_id);
+			if (error != 0) {
+				if (warned++ == 0) {
+					IB_LOG((CE_CONT, "ib_do_reconfig: "
+					    "unable to move interrupt"));
+				}
+
+				IB_LOG((CE_CONT, "ib_do_reconfig: "
+				    "unable to move buspath"));
+
+				ret = -1;
+			}
+		}
+	}
+
+	return (rval);
+}
+
+
+/*
+ * Check if the interrupt load did decrease.
+ */
+static void
+ib_interrupt_move_check(ib_delta_t *delta, processorid_t old_cpuid,
+    processorid_t new_cpuid)
+{
+	ib_cpu_t	*old_cpu, *new_cpu;
+
+	/*
+	 * Check old CPU.
+	 */
+	old_cpu = ib_cpu_find(delta->id_cpu_list, old_cpuid);
+	if (old_cpu == NULL)
+		return;
+	if (!(old_cpu->ic_tot >= old_cpu->ic_intrs)) {
+		IB_LOG((CE_CONT,
+		    "Moved interrupts left 100+%% load on source CPU"));
+	}
+
+	/*
+	 * Check new CPU.
+	 */
+	new_cpu = ib_cpu_find(delta->id_cpu_list, new_cpuid);
+	if (new_cpu == NULL)
+		return;
+	if (!(new_cpu->ic_tot >= new_cpu->ic_intrs)) {
+		IB_LOG((CE_CONT,
+		    "Moved interrupts left 100+%% load on target CPU"));
+	}
+}
+
+/*
+ * Actually moving the interrupt.
+ */
+static int
+ib_interrupt_do_move(ib_ivec_t *ivec, processorid_t cpu_id)
+{
+	int	ret, result;
+
+	struct psm_ops	*pops;
+
+	//pops = mach_set[0];
+
+	//	ret = (*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_SET_CPU,
+	//	    &result);
+
+	return (-1);
+}
+
+/*
+ * Move an interrupt to a different CPU.
+ */
+static int
+ib_interrupt_move(ib_delta_t *delta, uint64_t inum, processorid_t old_cpuid,
+    processorid_t new_cpuid)
+{
+	ib_cpu_t	*old_cpu, *new_cpu;
+	ib_ivec_t	*ivec;
+
+	if (delta == NULL)
+		return (-1);
+
+	/*
+	 * Remove interrupt vector from old CPU.
+	 */
+	old_cpu = ib_cpu_find(delta->id_cpu_list, old_cpuid);
+	if (old_cpu == NULL)
+		return (-1);
+
+	ivec = ib_ivec_find_ino(old_cpu->ic_ivec_list, inum);
+
+	old_cpu->ic_intrs -= ivec->ii_time;
+	old_cpu->ic_intr_load = old_cpu->ic_intrs / old_cpu->ic_tot;
+	ib_ivec_delete_ino(old_cpu->ic_ivec_list, inum);
+
+	/*
+	 * Verify interrupts.
+	 */
+	if (!(old_cpu->ic_intrs >= 0)) {
+		IB_LOG((CE_CONT,
+		    "ib_interrupt_move: interrupt time > total time?"));
+	}
+
+	if (!(ivec->ii_time <= old_cpu->ic_bigintr)) {
+		IB_LOG((CE_CONT,
+		    "ib_interrupt_move: interrupt time > big interrupt?"));
+	}
+
+	if (ivec->ii_time >= old_cpu->ic_bigintr) {
+		ib_ivec_t	*time_ivec;
+		uint64_t	bigtime = 0;
+
+		FOREACH_IVEC(time_ivec, old_cpu->ic_ivec_list) {
+			if (time_ivec->ii_time > bigtime)
+				bigtime = time_ivec->ii_time;
+		}
+	}
+
+
+	/*
+	 * Insert interrupt vector into new CPU.
+	 */
+	new_cpu = ib_cpu_find(delta->id_cpu_list, new_cpuid);
+	if (new_cpu == NULL)
+		return (-1);
+
+	ivec->ii_now_cpu = new_cpuid;
+	new_cpu->ic_intrs += ivec->ii_time;
+	new_cpu->ic_intr_load = new_cpu->ic_intrs / new_cpu->ic_tot;
+	ib_ivec_add_ino(new_cpu->ic_ivec_list, ivec);
+
+	if (ivec->ii_time > new_cpu->ic_bigintr)
+		new_cpu->ic_bigintr = ivec->ii_time;
+
+	return (0);
+}
