--- old/usr/src/cmd/intrd/Makefile	2013-09-25 09:16:13.046172707 +0000
+++ new/usr/src/cmd/intrd/Makefile	2013-09-25 09:16:13.058444151 +0000
@@ -26,7 +26,6 @@
 # Makefile for interrupt distribution daemon
 #
 
-PROG=		intrd
 MANIFEST=	intrd.xml
 SVCMETHOD=	svc-intrd
 
@@ -38,14 +37,12 @@
 
 # install macros and rule
 #
-ROOTPROG=	$(ROOTLIB)/$(PROG)
-$(ROOTPROG)	:= FILEMODE= 555
 
 .KEEP_STATE:
 
 all: $(PROG)
 
-install: all .WAIT $(ROOTPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD)
+install: all .WAIT $(ROOTMANIFEST) $(ROOTSVCMETHOD)
 
 clean:
 
--- old/usr/src/cmd/intrd/intrd.pl	2013-09-25 09:16:13.726248226 +0000
+++ /dev/null	2013-09-25 09:16:13.000000000 +0000
@@ -1,1407 +0,0 @@
-#!/usr/perl5/bin/perl
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-
-#
-# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-require 5.8.4;
-use strict;
-use warnings;
-use POSIX;
-use File::Basename("basename");
-
-my $cmdname = basename($0);
-
-my $using_scengen = 0;	# 1 if using scenario simulator
-my $debug = 0;
-
-my $normal_sleeptime = 10;		# time to sleep between samples
-my $idle_sleeptime = 45;		# time to sleep when idle
-my $onecpu_sleeptime = (60 * 15);	# used if only 1 CPU on system
-my $sleeptime = $normal_sleeptime;	# either normal_ or idle_ or onecpu_
-
-my $idle_intrload = .1;			# idle if interrupt load < 10%
-
-my $timerange_toohi    = .01;
-my $statslen = 60;	# time period (in secs) to keep in @deltas
-
-
-# Parse arguments. intrd does not accept any public arguments; the two
-# arguments below are meant for testing purposes. -D generates a significant
-# amount of syslog output. -S <filename> loads the filename as a perl
-# script. That file is expected to implement a kstat "simulator" which
-# can be used to feed information to intrd and verify intrd's responses.
-
-while ($_ = shift @ARGV) {
-	if ($_ eq "-S" && $#ARGV != -1) {
-		$using_scengen = 1;
-		do $ARGV[0];	# load simulator
-		shift @ARGV;
-	} elsif ($_ eq "-D") {
-		$debug = 1;
-	}
-}
-
-if ($using_scengen == 0) {
-	require Sun::Solaris::Kstat;
-	require Sun::Solaris::Intrs;
-	import Sun::Solaris::Intrs(qw(intrmove is_apic));
-	require Sys::Syslog;
-	import Sys::Syslog;
-	openlog($cmdname, 'pid', 'daemon');
-	setlogmask(Sys::Syslog::LOG_UPTO($debug > 0 ? &Sys::Syslog::LOG_DEBUG :
-	    &Sys::Syslog::LOG_INFO));
-}
-
-my $asserted = 0;
-my $assert_level = 'debug';	# syslog level for assertion failures
-sub VERIFY($@)
-{
-	my $bad = (shift() == 0);	# $_[0] == 0 means assert failed
-	if ($bad) {
-		my $msg = shift();
-		syslog($assert_level, "VERIFY: $msg", @_);
-		$asserted++;
-	}
-	return ($bad);
-}
-
-
-
-
-sub getstat($$);
-sub generate_delta($$);
-sub compress_deltas($);
-sub dumpdelta($);
-
-sub goodness($);
-sub imbalanced($$);
-sub do_reconfig($);
-
-sub goodness_cpu($$);		# private function
-sub move_intr($$$$);		# private function
-sub ivecs_to_string(@);		# private function
-sub do_find_goal($$$$);		# private function
-sub find_goal($$);		# private function
-sub do_reconfig_cpu2cpu($$$$);	# private function
-sub do_reconfig_cpu($$$);	# private function
-
-
-#
-# What follow are the basic data structures routines of intrd.
-#
-# getstat() is responsible for reading the kstats and generating a "stat" hash.
-#
-# generate_delta() is responsible for taking two "stat" hashes and creating
-# a new "delta" hash that represents what has changed over time.
-#
-# compress_deltas() is responsible for taking a list of deltas and generating
-# a single delta hash that encompasses all the time periods described by the
-# deltas.
-
-
-#
-# getstat() is handed a reference to a kstat and generates a hash, returned
-# by reference, containing all the fields from the kstats which we need.
-# If it returns the scalar 0, it failed to gather the kstats, and the caller
-# should react accordingly.
-#
-# getstat() is also responsible for maintaining a reasonable $sleeptime.
-#
-# {"snaptime"}          kstat's snaptime
-# {<cpuid>}             one hash reference per online cpu
-#  ->{"tot"}            == cpu:<cpuid>:sys:cpu_nsec_{user + kernel + idle}
-#  ->{"crtime"}         == cpu:<cpuid>:sys:crtime
-#  ->{"ivecs"}
-#     ->{<cookie#>}     iterates over pci_intrs::<nexus>:cookie
-#        ->{"time"}     == pci_intrs:<ivec#>:<nexus>:time (in nsec)
-#        ->{"pil"}      == pci_intrs:<ivec#>:<nexus>:pil
-#        ->{"crtime"}   == pci_intrs:<ivec#>:<nexus>:crtime
-#        ->{"ino"}      == pci_intrs:<ivec#>:<nexus>:ino
-#        ->{"num_ino"}  == num inos of single device instance sharing this entry
-#				Will be > 1 on pcplusmp X86 systems for devices
-#				with multiple MSI interrupts.
-#        ->{"buspath"}  == pci_intrs:<ivec#>:<nexus>:buspath
-#        ->{"name"}     == pci_intrs:<ivec#>:<nexus>:name
-#        ->{"ihs"}      == pci_intrs:<ivec#>:<nexus>:ihs
-#
-
-sub getstat($$)
-{
-	my ($ks, $pcplusmp_sys) = @_;
-
-	my $cpucnt = 0;
-	my %stat = ();
-	my ($minsnap, $maxsnap);
-
-	# Hash of hash which matches (MSI device, ino) combos to kstats.
-	my %msidevs = ();
-
-	# kstats are not generated atomically. Each kstat hierarchy will
-	# have been generated within the kernel at a different time. On a
-	# thrashing system, we may not run quickly enough in order to get
-	# coherent kstat timing information across all the kstats. To
-	# determine if this is occurring, $minsnap/$maxsnap are used to
-	# find the breadth between the first and last snaptime of all the
-	# kstats we access. $maxsnap - $minsnap roughly represents the
-	# total time taken up in getstat(). If this time approaches the
-	# time between snapshots, our results may not be useful.
-
-	$minsnap = -1;		# snaptime is always a positive number
-	$maxsnap = $minsnap;
-
-	# Iterate over the cpus in cpu:<cpuid>::. Check
-	# cpu_info:<cpuid>:cpu_info<cpuid>:state to make sure the
-	# processor is "on-line". If not, it isn't accepting interrupts
-	# and doesn't concern us.
-	#
-	# Record cpu:<cpuid>:sys:snaptime, and check $minsnap/$maxsnap.
-
-	while (my ($cpu, $cpst) = each %{$ks->{cpu}}) {
-		next if !exists($ks->{cpu_info}{$cpu}{"cpu_info$cpu"}{state});
-		#"state" fld of kstat w/
-		#		  modname    inst name-"cpuinfo0"
-		my $state = $ks->{cpu_info}{$cpu}{"cpu_info$cpu"}{state};
-		next if ($state !~ /^on-line\0/);
-		my $cpu_sys = $cpst->{sys};
-
-		$stat{$cpu}{tot} = ($cpu_sys->{cpu_nsec_idle} +
-				    $cpu_sys->{cpu_nsec_user} +
-				    $cpu_sys->{cpu_nsec_kernel});
-		$stat{$cpu}{crtime} = $cpu_sys->{crtime};
-		$stat{$cpu}{ivecs} = {};
-
-		if ($minsnap == -1 || $cpu_sys->{snaptime} < $minsnap) {
-			$minsnap = $cpu_sys->{snaptime};
-		}
-		if ($cpu_sys->{snaptime} > $maxsnap) {
-			$maxsnap = $cpu_sys->{snaptime};
-		}
-		$cpucnt++;
-	}
-
-	if ($cpucnt <= 1) {
-		$sleeptime = $onecpu_sleeptime;
-		return (0);	# nothing to do with 1 CPU
-	}
-
-	# Iterate over the ivecs. If the cpu is not on-line, ignore the
-	# ivecs mapped to it, if any.
-	#
-	# Record pci_intrs:{inum}:<nexus>:time, snaptime, crtime, pil,
-	# ino, name, and buspath. Check $minsnap/$maxsnap.
-
-	foreach my $inst (values(%{$ks->{pci_intrs}})) {
-		my $intrcfg = (values(%$inst))[0]; 
-		my $cpu = $intrcfg->{cpu};
-
-		next unless exists $stat{$cpu};
-		next if ($intrcfg->{type} =~ /^disabled\0/);
-
-		# Perl looks beyond NULL chars in pattern matching.
-		# Truncate name field at the first NULL
-		$intrcfg->{name} =~ s/\0.*$//;
-
-		if ($intrcfg->{snaptime} < $minsnap) {
-			$minsnap = $intrcfg->{snaptime};
-		} elsif ($intrcfg->{snaptime} > $maxsnap) {
-			$maxsnap = $intrcfg->{snaptime};
-		}
-
-		my $cookie = "$intrcfg->{buspath} $intrcfg->{ino}";
-		if (exists $stat{$cpu}{ivecs}{$cookie}) {
-			my $cookiestats = $stat{$cpu}{ivecs}{$cookie};
-
-			$cookiestats->{time} += $intrcfg->{time};
-			$cookiestats->{name} .= "/$intrcfg->{name}";
-
-			# If this new interrupt sharing $cookie represents a
-			# change from an earlier getstat, make sure that
-			# generate_delta will see the change by setting
-			# crtime to the most recent crtime of its components.
-
-			if ($intrcfg->{crtime} > $cookiestats->{crtime}) {
-				$cookiestats->{crtime} = $intrcfg->{crtime};
-			}
-			$cookiestats->{ihs}++;
-			next;
-		}
-		$stat{$cpu}{ivecs}{$cookie}{time} = $intrcfg->{time};
-		$stat{$cpu}{ivecs}{$cookie}{crtime} = $intrcfg->{crtime};
-		$stat{$cpu}{ivecs}{$cookie}{pil} = $intrcfg->{pil};
-		$stat{$cpu}{ivecs}{$cookie}{ino} = $intrcfg->{ino};
-		$stat{$cpu}{ivecs}{$cookie}{num_ino} = 1;
-		$stat{$cpu}{ivecs}{$cookie}{buspath} = $intrcfg->{buspath};
-		$stat{$cpu}{ivecs}{$cookie}{name} = $intrcfg->{name};
-		$stat{$cpu}{ivecs}{$cookie}{ihs} = 1;
-
-		if ($pcplusmp_sys && ($intrcfg->{type} =~ /^msi\0/)) {
-			if (!(exists($msidevs{$intrcfg->{name}}))) {
-				$msidevs{$intrcfg->{name}} = {};
-			}
-			$msidevs{$intrcfg->{name}}{$intrcfg->{ino}} =
-			    \$stat{$cpu}{ivecs}{$cookie};
-		}
-	}
-
-	# All MSI interrupts of a device instance share a single MSI address.
-	# On X86 systems with an APIC, this MSI address is interpreted as CPU
-	# routing info by the APIC.  For this reason, on these platforms, all
-	# interrupts for MSI devices must be moved to the same CPU at the same
-	# time.
-	#
-	# Since all interrupts will be on the same CPU on these platforms, all
-	# interrupts can be consolidated into one ivec entry.  For such devices,
-	# num_ino will be > 1 to denote that a group move is needed.  
-
-	# Loop thru all MSI devices on X86 pcplusmp systems.
-	# Nop on other systems.
-	foreach my $msidevkey (sort keys %msidevs) {
-
-		# Loop thru inos of the device, sorted by lowest value first
-		# For each cookie found for a device, incr num_ino for the
-		# lowest cookie and remove other cookies.
-
-		# Assumes PIL is the same for first and current cookies
-
-		my $first_ino = -1;
-		my $first_cookiep;
-		my $curr_cookiep;
-		foreach my $inokey (sort keys %{$msidevs{$msidevkey}}) {
-			$curr_cookiep = $msidevs{$msidevkey}{$inokey};
-			if ($first_ino == -1) {
-				$first_ino = $inokey;
-				$first_cookiep = $curr_cookiep;
-			} else {
-				$$first_cookiep->{num_ino}++;
-				$$first_cookiep->{time} +=
-				    $$curr_cookiep->{time};
-				if ($$curr_cookiep->{crtime} >
-				    $$first_cookiep->{crtime}) {
-					$$first_cookiep->{crtime} =
-					    $$curr_cookiep->{crtime};
-				}
-				# Invalidate this cookie, less complicated and
-				# more efficient than deleting it.
-				$$curr_cookiep->{num_ino} = 0;
-			}
-		}
-	}
-
-	# We define the timerange as the amount of time spent gathering the
-	# various kstats, divided by our sleeptime. If we take a lot of time
-	# to access the kstats, and then we create a delta comparing these
-	# kstats with a prior set of kstats, that delta will cover
-	# substaintially different amount of time depending upon which
-	# interrupt or CPU is being examined.
-	#
-	# By checking the timerange here, we guarantee that any deltas
-	# created from these kstats will contain self-consistent data,
-	# in that all CPUs and interrupts cover a similar span of time.
-	#
-	# $timerange_toohi is the upper bound. Any timerange above
-	# this is thrown out as garbage. If the stat is safely within this
-	# bound, we treat the stat as representing an instant in time, rather
-	# than the time range it actually spans. We arbitrarily choose minsnap
-	# as the snaptime of the stat.
-
-	$stat{snaptime} = $minsnap;
-	my $timerange = ($maxsnap - $minsnap) / $sleeptime;
-	return (0) if ($timerange > $timerange_toohi);	# i.e. failure
-	return (\%stat);
-}
-
-#
-# dumpdelta takes a reference to our "delta" structure:
-# {"missing"}           "1" if the delta's component stats had inconsistencies
-# {"minsnap"}           time of the first kstat snaptime used in this delta
-# {"maxsnap"}           time of the last kstat snaptime used in this delta
-# {"goodness"}          cost function applied to this delta
-# {"avgintrload"}       avg of interrupt load across cpus, as a percentage
-# {"avgintrnsec"}       avg number of nsec spent in interrupts, per cpu
-# {<cpuid>}             iterates over on-line cpus
-#  ->{"intrs"}          cpu's movable intr time (sum of "time" for each ivec)
-#  ->{"tot"}            CPU load from all sources in nsec
-#  ->{"bigintr"}        largest value of {ivecs}{<ivec#>}{time} from below
-#  ->{"intrload"}       intrs / tot
-#  ->{"ivecs"}          
-#     ->{<ivec#>}       iterates over ivecs for this cpu
-#        ->{"time"}     time used by this interrupt (in nsec)
-#        ->{"pil"}      pil level of this interrupt
-#        ->{"ino"}      interrupt number (or base vector if MSI group)
-#        ->{"buspath"}  filename of the directory of the device's bus
-#        ->{"name"}     device name
-#        ->{"ihs"}      number of different handlers sharing this ino
-#        ->{"num_ino"}  number of interrupt vectors in MSI group
-#
-# It prints out the delta structure in a nice, human readable display.
-#
-
-sub dumpdelta($)
-{
-	my ($delta) = @_;
-
-	# print global info
-
-	syslog('debug', "dumpdelta:");
-	syslog('debug', " RECONFIGURATION IN DELTA") if $delta->{missing} > 0;
-	syslog('debug', " avgintrload: %5.2f%%  avgintrnsec: %d",
-	       $delta->{avgintrload} * 100, $delta->{avgintrnsec});
-	syslog('debug', "    goodness: %5.2f%%", $delta->{goodness} * 100)
-	    if exists($delta->{goodness});
-
-	# iterate over cpus
-
-	while (my ($cpu, $cpst) = each %$delta) {
-		next if !ref($cpst);		# skip non-cpuid entries
-		my $tot = $cpst->{tot};
-		syslog('debug', "    cpu %3d intr %7.3f%%  (bigintr %7.3f%%)",
-		       $cpu, $cpst->{intrload}*100, $cpst->{bigintr}*100/$tot);
-		syslog('debug', "        intrs %d, bigintr %d",
-		       $cpst->{intrs}, $cpst->{bigintr});
-
-		# iterate over ivecs on this cpu
-
-		while (my ($ivec, $ivst) = each %{$cpst->{ivecs}}) {
-			syslog('debug', "    %15s:\"%s\": %7.3f%%  %d",
-			    ($ivst->{ihs} > 1 ? "$ivst->{name}($ivst->{ihs})" :
-			    $ivst->{name}), $ivec,
-			    $ivst->{time}*100 / $tot, $ivst->{time});
-		}
-	}
-}
-
-#
-# generate_delta($stat, $newstat) takes two stat references, returned from
-# getstat(), and creates a %delta. %delta (not surprisingly) contains the
-# same basic info as stat and newstat, but with the timestamps as deltas
-# instead of absolute times. We return a reference to the delta.
-#
-
-sub generate_delta($$)
-{
-	my ($stat, $newstat) = @_;
-
-	my %delta = ();
-	my $intrload;
-	my $intrnsec;
-	my $cpus;
-
-	# Take the worstcase timerange
-	$delta{minsnap} = $stat->{snaptime};
-	$delta{maxsnap} = $newstat->{snaptime};
-	if (VERIFY($delta{maxsnap} > $delta{minsnap},
-	    "generate_delta: stats aren't ascending")) {
-		$delta{missing} = 1;
-		return (\%delta);
-	}
-
-	# if there are a different number of cpus in the stats, set missing
-
-	$delta{missing} = (keys(%$stat) != keys(%$newstat));
-	if (VERIFY($delta{missing} == 0,
-	    "generate_delta: number of CPUs changed")) {
-		return (\%delta);
-	}
-
-	# scan through every cpu in %newstat and compare against %stat
-
-	while (my ($cpu, $newcpst) = each %$newstat) {
-		next if !ref($newcpst);		# skip non-cpuid fields
-
-		# If %stat is missing a cpu from %newstat, then it was just
-		# onlined. Mark missing.
-
-		if (VERIFY(exists $stat->{$cpu} &&
-		    $stat->{$cpu}{crtime} == $newcpst->{crtime},
-		    "generate_delta: cpu $cpu changed")) {
-			$delta{missing} = 1;
-			return (\%delta);
-		}
-		my $cpst = $stat->{$cpu};
-		$delta{$cpu}{tot} = $newcpst->{tot} - $cpst->{tot};
-		if (VERIFY($delta{$cpu}{tot} >= 0,
-		    "generate_delta: deltas are not ascending?")) {
-			$delta{missing} = 1;
-			delete($delta{$cpu});
-			return (\%delta);
-		}
-		# Avoid remote chance of division by zero
-		$delta{$cpu}{tot} = 1 if $delta{$cpu}{tot} == 0;
-		$delta{$cpu}{intrs} = 0;
-		$delta{$cpu}{bigintr} = 0;
-
-		my %ivecs = ();
-		$delta{$cpu}{ivecs} = \%ivecs;
-
-		# if the number of ivecs differs, set missing
-
-		if (VERIFY(keys(%{$cpst->{ivecs}}) ==
-			   keys(%{$newcpst->{ivecs}}),
-			   "generate_delta: cpu $cpu has more/less".
-			   " interrupts")) {
-			$delta{missing} = 1;
-			return (\%delta);
-		}
-
-		while (my ($inum, $newivec) = each %{$newcpst->{ivecs}}) {
-
-			# Unused cookie, corresponding to an MSI vector which
-			# is part of a group.  The whole group is accounted for
-			# by a different cookie.
-			next if ($newivec->{num_ino} == 0);
-
-			# If this ivec doesn't exist in $stat, or if $stat
-			# shows a different crtime, set missing.
-			if (VERIFY(exists $cpst->{ivecs}{$inum} &&
-				   $cpst->{ivecs}{$inum}{crtime} ==
-				   $newivec->{crtime},
-				   "generate_delta: cpu $cpu inum $inum".
-				   " has changed")) {
-				$delta{missing} = 1;
-				return (\%delta);
-			}
-			my $ivec = $cpst->{ivecs}{$inum};
-
-			# Create $delta{$cpu}{ivecs}{$inum}.
-
-			my %dltivec = ();
-			$delta{$cpu}{ivecs}{$inum} = \%dltivec;
-
-			# calculate time used by this interrupt
-
-			my $time = $newivec->{time} - $ivec->{time};
-			if (VERIFY($time >= 0,
-				   "generate_delta: ivec went backwards?")) {
-				$delta{missing} = 1;
-				delete($delta{$cpu}{ivecs}{$inum});
-				return (\%delta);
-			}
-			$delta{$cpu}{intrs} += $time;
-			$dltivec{time} = $time;
-			if ($time > $delta{$cpu}{bigintr}) {
-				$delta{$cpu}{bigintr} = $time;
-			}
-
-			# Transfer over basic info about the kstat. We
-			# don't have to worry about discrepancies between
-			# ivec and newivec because we verified that both
-			# have the same crtime.
-
-			$dltivec{pil} = $newivec->{pil};
-			$dltivec{ino} = $newivec->{ino};
-			$dltivec{buspath} = $newivec->{buspath};
-			$dltivec{name} = $newivec->{name};
-			$dltivec{ihs} = $newivec->{ihs};
-			$dltivec{num_ino} = $newivec->{num_ino};
-		}
-		if ($delta{$cpu}{tot} < $delta{$cpu}{intrs}) {
-			# Ewww! Hopefully just a rounding error.
-			# Make something up.
-			$delta{$cpu}{tot} = $delta{$cpu}{intrs};
-		}
-		$delta{$cpu}{intrload} =
-		       $delta{$cpu}{intrs} / $delta{$cpu}{tot};
-		$intrload += $delta{$cpu}{intrload};
-		$intrnsec += $delta{$cpu}{intrs};
-		$cpus++;
-	}
-	if ($cpus > 0) {
-		$delta{avgintrload} = $intrload / $cpus;
-		$delta{avgintrnsec} = $intrnsec / $cpus;
-	} else {
-		$delta{avgintrload} = 0;
-		$delta{avgintrnsec} = 0;
-	}
-	return (\%delta);
-}
-
-
-# compress_delta takes a list of deltas, and returns a single new delta
-# which represents the combined information from all the deltas. The deltas
-# provided are assumed to be sequential in time. The resulting compressed
-# delta looks just like any other delta. This new delta is also more accurate
-# since its statistics are averaged over a longer period than any of the
-# original deltas.
-
-sub compress_deltas ($)
-{
-	my ($deltas) = @_;
-
-	my %newdelta = ();
-	my ($intrs, $tot);
-	my $cpus = 0;
-	my ($high_intrload) = 0;
-
-	if (VERIFY($#$deltas != -1,
-		   "compress_deltas: list of delta is empty?")) {
-		return (0);
-	}
-	$newdelta{minsnap} = $deltas->[0]{minsnap};
-	$newdelta{maxsnap} = $deltas->[$#$deltas]{maxsnap};
-	$newdelta{missing} = 0;
-
-	foreach my $delta (@$deltas) {
-		if (VERIFY($delta->{missing} == 0,
-		    "compressing bad deltas?")) {
-			return (0);
-		}
-		while (my ($cpuid, $cpu) = each %$delta) {
-			next if !ref($cpu);
-
-			$intrs += $cpu->{intrs};
-			$tot += $cpu->{tot};
-			$newdelta{$cpuid}{intrs} += $cpu->{intrs};
-			$newdelta{$cpuid}{tot} += $cpu->{tot};
-			if (!exists $newdelta{$cpuid}{ivecs}) {
-				my %ivecs = ();
-				$newdelta{$cpuid}{ivecs} = \%ivecs;
-			}
-			while (my ($inum, $ivec) = each %{$cpu->{ivecs}}) {
-				my $newivecs = $newdelta{$cpuid}{ivecs};
-				$newivecs->{$inum}{time} += $ivec->{time};
-				$newivecs->{$inum}{pil} = $ivec->{pil};
-				$newivecs->{$inum}{ino} = $ivec->{ino};
-				$newivecs->{$inum}{buspath} = $ivec->{buspath};
-				$newivecs->{$inum}{name} = $ivec->{name};
-				$newivecs->{$inum}{ihs} = $ivec->{ihs};
-				$newivecs->{$inum}{num_ino} = $ivec->{num_ino};
-			}
-		}
-	}
-	foreach my $cpu (values(%newdelta)) {
-		next if !ref($cpu); # ignore non-cpu fields
-		$cpus++;
-
-		my $bigintr = 0;
-		foreach my $ivec (values(%{$cpu->{ivecs}})) {
-			if ($ivec->{time} > $bigintr) {
-				$bigintr = $ivec->{time};
-			}
-		}
-		$cpu->{bigintr} = $bigintr;
-		$cpu->{intrload} = $cpu->{intrs} / $cpu->{tot};
-		if ($high_intrload < $cpu->{intrload}) {
-			$high_intrload = $cpu->{intrload};
-		}
-		$cpu->{tot} = 1 if $cpu->{tot} <= 0;
-	}
-	if ($cpus == 0) {
-		$newdelta{avgintrnsec} = 0;
-		$newdelta{avgintrload} = 0;
-	} else {
-		$newdelta{avgintrnsec} = $intrs / $cpus;
-		$newdelta{avgintrload} = $intrs / $tot;
-	}
-	$sleeptime = ($high_intrload < $idle_intrload) ? $idle_sleeptime :
-	    $normal_sleeptime;
-	return (\%newdelta);
-}
-
-
-
-
-
-# What follow are the core functions responsible for examining the deltas
-# generated above and deciding what to do about them.
-#
-# goodness() and its helper goodness_cpu() return a heuristic which describe
-# how good (or bad) the current interrupt balance is. The value returned will
-# be between 0 and 1, with 0 representing maximum goodness, and 1 representing
-# maximum badness.
-#
-# imbalanced() compares a current and historical value of goodness, and
-# determines if there has been enough change to warrant evaluating a
-# reconfiguration of the interrupts
-#
-# do_reconfig(), and its helpers, do_reconfig_cpu(), do_reconfig_cpu2cpu(),
-# find_goal(), do_find_goal(), and move_intr(), are responsible for examining
-# a delta and determining the best possible assignment of interrupts to CPUs.
-#
-# It is important that do_reconfig() be in alignment with goodness(). If
-# do_reconfig were to generate a new interrupt distribution that worsened
-# goodness, we could get into a pathological loop with intrd fighting itself,
-# constantly deciding that things are imbalanced, and then changing things
-# only to make them worse.
-
-
-
-# any goodness over $goodness_unsafe_load is considered really bad
-# goodness must drop by at least $goodness_mindelta for a reconfig
-
-my $goodness_unsafe_load = .9;
-my $goodness_mindelta = .1;
-
-# goodness(%delta) examines a delta and return its "goodness". goodness will
-# be between 0 (best) and 1 (major bad). goodness is determined by evaluating
-# the goodness of each individual cpu, and returning the worst case. This
-# helps on systems with many CPUs, where otherwise a single pathological CPU
-# might otherwise be ignored because the average was OK.
-#
-# To calculate the goodness of an individual CPU, we start by looking at its
-# load due to interrupts. If the load is above a certain high threshold and
-# there is more than one interrupt assigned to this CPU, we set goodness
-# to worst-case. If the load is below the average interrupt load of all CPUs,
-# then we return best-case, since what's to complain about?
-#
-# Otherwise we look at how much the load is above the average, and return
-# that as the goodness, with one caveat: we never return more than the CPU's
-# interrupt load ignoring its largest single interrupt source. This is
-# because a CPU with one high-load interrupt, and no other interrupts, is
-# perfectly balanced. Nothing can be done to improve the situation, and thus
-# it is perfectly balanced even if the interrupt's load is 100%.
-
-sub goodness($)
-{
-	my ($delta) = @_;
-
-	return (1) if $delta->{missing} > 0;
-
-	my $high_goodness = 0;
-	my $goodness;
-
-	foreach my $cpu (values(%$delta)) {
-		next if !ref($cpu);		# skip non-cpuid fields
-
-		$goodness = goodness_cpu($cpu, $delta->{avgintrload});
-		if (VERIFY($goodness >= 0 && $goodness <= 1,
-			   "goodness: cpu goodness out of range?")) {
-			dumpdelta($delta);
-			return (1);
-		}
-		if ($goodness == 1) {
-			return (1);	# worst case, no need to continue
-		}
-		if ($goodness > $high_goodness) {
-			$high_goodness = $goodness;
-		}
-	}
-	return ($high_goodness);
-}
-
-sub goodness_cpu($$)		# private function
-{
-	my ($cpu, $avgintrload) = @_;
-
-	my $goodness;
-	my $load = $cpu->{intrs} / $cpu->{tot};
-
-	return (0) if ($load < $avgintrload);	# low loads are perfectly good
-
-	# Calculate $load_no_bigintr, which represents the load
-	# due to interrupts, excluding the one biggest interrupt.
-	# This is the most gain we can get on this CPU from
-	# offloading interrupts.
-
-	my $load_no_bigintr = ($cpu->{intrs} - $cpu->{bigintr}) / $cpu->{tot};
-
-	# A major imbalance is indicated if a CPU is saturated
-	# with interrupt handling, and it has more than one
-	# source of interrupts. Those other interrupts could be
-	# starved if of a lower pil. Return a goodness of 1,
-	# which is the worst possible return value,
-	# which will effectively contaminate this entire delta.
-
-	my $cnt = keys(%{$cpu->{ivecs}});
-
-	if ($load > $goodness_unsafe_load && $cnt > 1) {
-		return (1);
-	}
-	$goodness = $load - $avgintrload;
-	if ($goodness > $load_no_bigintr) {
-		$goodness = $load_no_bigintr;
-	}
-	return ($goodness);
-}
-
-
-# imbalanced() is used by the main routine to determine if the goodness
-# has shifted far enough from our last baseline to warrant a reassignment
-# of interrupts. A very high goodness indicates that a CPU is way out of
-# whack. If the goodness has varied too much since the baseline, then
-# perhaps a reconfiguration is worth considering.
-
-sub imbalanced ($$)
-{
-	my ($goodness, $baseline) = @_;
-
-	# Return 1 if we are pathological, or creeping away from the baseline
-
-	return (1) if $goodness > .50;
-	return (1) if abs($goodness - $baseline) > $goodness_mindelta;
-	return (0);
-}
-
-# do_reconfig(), do_reconfig_cpu(), and do_reconfig_cpu2cpu(), are the
-# decision-making functions responsible for generating a new interrupt
-# distribution. They are designed with the definition of goodness() in
-# mind, i.e. they use the same definition of "good distribution" as does
-# goodness().
-#
-# do_reconfig() is responsible for deciding whether a redistribution is
-# actually warranted. If the goodness is already pretty good, it doesn't
-# waste the CPU time to generate a new distribution. If it
-# calculates a new distribution and finds that it is not sufficiently
-# improved from the prior distirbution, it will not do the redistribution,
-# mainly to avoid the disruption to system performance caused by
-# rejuggling interrupts.
-#
-# Its main loop works by going through a list of cpus sorted from
-# highest to lowest interrupt load. It removes the highest-load cpus
-# one at a time and hands them off to do_reconfig_cpu(). This function
-# then re-sorts the remaining CPUs from lowest to highest interrupt load,
-# and one at a time attempts to rejuggle interrupts between the original
-# high-load CPU and the low-load CPU. Rejuggling on a high-load CPU is
-# considered finished as soon as its interrupt load is within
-# $goodness_mindelta of the average interrupt load. Such a CPU will have
-# a goodness of below the $goodness_mindelta threshold.
-
-#
-# move_intr(\%delta, $inum, $oldcpu, $newcpu)
-# used by reconfiguration code to move an interrupt between cpus within
-# a delta. This manipulates data structures, and does not actually move
-# the interrupt on the running system.
-#
-sub move_intr($$$$)		# private function
-{
-	my ($delta, $inum, $oldcpuid, $newcpuid) = @_;
-
-	my $ivec = $delta->{$oldcpuid}{ivecs}{$inum};
-
-	# Remove ivec from old cpu
-
-	my $oldcpu = $delta->{$oldcpuid};
-	$oldcpu->{intrs} -= $ivec->{time};
-	$oldcpu->{intrload} = $oldcpu->{intrs} / $oldcpu->{tot};
-	delete($oldcpu->{ivecs}{$inum});
-
-	VERIFY($oldcpu->{intrs} >= 0, "move_intr: intr's time > total time?");
-	VERIFY($ivec->{time} <= $oldcpu->{bigintr},
-	       "move_intr: intr's time > bigintr?");
-
-	if ($ivec->{time} >= $oldcpu->{bigintr}) {
-		my $bigtime = 0;
-
-		foreach my $ivec (values(%{$oldcpu->{ivecs}})) {
-			$bigtime = $ivec->{time} if $ivec->{time} > $bigtime;
-		}
-		$oldcpu->{bigintr} = $bigtime;
-	}
-
-	# Add ivec onto new cpu
-
-	my $newcpu = $delta->{$newcpuid};
-
-	$ivec->{nowcpu} = $newcpuid;
-	$newcpu->{intrs} += $ivec->{time};
-	$newcpu->{intrload} = $newcpu->{intrs} / $newcpu->{tot};
-	$newcpu->{ivecs}{$inum} = $ivec;
-
-	$newcpu->{bigintr} = $ivec->{time}
-		if $ivec->{time} > $newcpu->{bigintr};
-}
-
-sub move_intr_check($$$)	# private function
-{
-	my ($delta, $oldcpuid, $newcpuid) = @_;
-
-	VERIFY($delta->{$oldcpuid}{tot} >= $delta->{$oldcpuid}{intrs},
-	       "Moved interrupts left 100+%% load on src cpu");
-	VERIFY($delta->{$newcpuid}{tot} >= $delta->{$newcpuid}{intrs},
-	       "Moved interrupts left 100+%% load on tgt cpu");
-}
-
-sub ivecs_to_string(@)		# private function
-{
-	my $str = "";
-	foreach my $ivec (@_) {
-		$str = "$str $ivec->{inum}";
-	}
-	return ($str);
-}
-
-
-sub do_reconfig($)
-{
-	my ($delta) = @_;
-
-	my $goodness = $delta->{goodness};
-
-	# We can't improve goodness to better than 0. We should stop here
-	# if, even if we achieve a goodness of 0, the improvement is still
-	# too small to merit the action.
-
-	if ($goodness - 0 < $goodness_mindelta) {
-		syslog('debug', "goodness good enough, don't reconfig");
-		return (0);
-	}
-
-	syslog('notice', "Optimizing interrupt assignments");
-
-	if (VERIFY ($delta->{missing} == 0, "RECONFIG Aborted: should not ".
-	    "have a delta with missing")) {
-		return (-1);
-	}
-
-	# Make a list of all cpuids, and also add some extra information
-	# to the ivec structures.
-
-	my @cpusortlist = ();
-
-	while (my ($cpuid, $cpu) = each %$delta) {
-		next if !ref($cpu);	# skip non-cpu entries
-
-		push(@cpusortlist, $cpuid);
-		while (my ($inum, $ivec) = each %{$cpu->{ivecs}}) {
-			$ivec->{origcpu} = $cpuid;
-			$ivec->{nowcpu} = $cpuid;
-			$ivec->{inum} = $inum;
-		}
-	}
-
-	# Sort the list of CPUs from highest to lowest interrupt load.
-	# Remove the top CPU from that list and attempt to redistribute
-	# its interrupts. If the CPU has a goodness below a threshold,
-	# just ignore the CPU and move to the next one. If the CPU's
-	# load falls below the average load plus that same threshold,
-	# then there are no CPUs left worth reconfiguring, and we're done.
-
-	while (@cpusortlist) {
-		# Re-sort cpusortlist each time, since do_reconfig_cpu can
-		# move interrupts around.
-
-		@cpusortlist =
-		    sort({$delta->{$b}{intrload} <=> $delta->{$a}{intrload}}
-		    @cpusortlist);
-
-		my $cpu = shift(@cpusortlist);
-		if (($delta->{$cpu}{intrload} <= $goodness_unsafe_load) &&
-		    ($delta->{$cpu}{intrload} <=
-		    $delta->{avgintrload} + $goodness_mindelta)) {
-			syslog('debug', "finished reconfig: cpu $cpu load ".
-			    "$delta->{$cpu}{intrload} avgload ".
-			    "$delta->{avgintrload}");
-			last;
-		}
-		if (goodness_cpu($delta->{$cpu}, $delta->{avgintrload}) <
-		    $goodness_mindelta) {
-			next;
-		}
-		do_reconfig_cpu($delta, \@cpusortlist, $cpu);
-	}
-
-	# How good a job did we do? If the improvement was minimal, and
-	# our goodness wasn't pathological (and thus needing any help it
-	# can get), then don't bother moving the interrupts.
-
-	my $newgoodness = goodness($delta);
-	VERIFY($newgoodness <= $goodness,
-	       "reconfig: result has worse goodness?");
-
-	if (($goodness != 1 || $newgoodness == 1) &&
-	    $goodness - $newgoodness < $goodness_mindelta) {
-		syslog('debug', "goodness already near optimum, ".
-		       "don't reconfig");
-		return (0);
-	}
-	syslog('debug', "goodness %5.2f%% --> %5.2f%%", $goodness*100,
-	       $newgoodness*100);
-
-	# Time to move those interrupts!
-
-	my $ret = 1;
-	my $warned = 0;
-	while (my ($cpuid, $cpu) = each %$delta) {
-		next if $cpuid =~ /\D/;
-		while (my ($inum, $ivec) = each %{$cpu->{ivecs}}) {
-			next if ($ivec->{origcpu} == $cpuid);
-
-			if (!intrmove($ivec->{buspath}, $ivec->{origcpu},
-			    $ivec->{ino}, $cpuid, $ivec->{num_ino})) {
-				syslog('warning', "Unable to move interrupts")
-				    if $warned++ == 0;
-				syslog('debug', "Unable to move buspath ".
-				    "$ivec->{buspath} ino $ivec->{ino} to ".
-				    "cpu $cpuid");
-				$ret = -1;
-			}
-		}
-	}
-
-	syslog('notice', "Interrupt assignments optimized");
-	return ($ret);
-}
-
-sub do_reconfig_cpu($$$)	# private function
-{
-	my ($delta, $cpusortlist, $oldcpuid) = @_;
-
-	# We have been asked to rejuggle interrupts between $oldcpuid and
-	# other CPUs found on $cpusortlist so as to improve the load on
-	# $oldcpuid. We reverse $cpusortlist to get our own copy of the
-	# list, sorted from lowest to highest interrupt load. One at a
-	# time, shift a CPU off of this list of CPUs, and attempt to
-	# rejuggle interrupts between the two CPUs. Don't do this if the
-	# other CPU has a higher load than oldcpuid. We're done rejuggling
-	# once $oldcpuid's goodness falls below a threshold.
-
-	syslog('debug', "reconfiguring $oldcpuid");
-
-	my $cpu = $delta->{$oldcpuid};
-	my $avgintrload = $delta->{avgintrload};
-
-	my @cputargetlist = reverse(@$cpusortlist); # make a copy of the list
-	while ($#cputargetlist != -1) {
- 		last if goodness_cpu($cpu, $avgintrload) < $goodness_mindelta;
-
-		my $tgtcpuid = shift(@cputargetlist);
-		my $tgt = $delta->{$tgtcpuid};
-		my $load = $cpu->{intrload};
-		my $tgtload = $tgt->{intrload};
-		last if $tgtload > $load;
-		do_reconfig_cpu2cpu($delta, $oldcpuid, $tgtcpuid, $load);
-	}
-}
-
-sub do_reconfig_cpu2cpu($$$$)	# private function
-{
-	my ($delta, $srccpuid, $tgtcpuid, $srcload) = @_;
-
-	# We've been asked to consider interrupt juggling between srccpuid
-	# (with a high interrupt load) and tgtcpuid (with a lower interrupt
-	# load). First, make a single list with all of the ivecs from both
-	# CPUs, and sort the list from highest to lowest load.
-
-	syslog('debug', "exchanging intrs between $srccpuid and $tgtcpuid");
-
-	# Gather together all the ivecs and sort by load
-
-	my @ivecs = (values(%{$delta->{$srccpuid}{ivecs}}),
-	    values(%{$delta->{$tgtcpuid}{ivecs}}));
-	return if $#ivecs == -1;
-
-	@ivecs = sort({$b->{time} <=> $a->{time}} @ivecs);
-
-	# Our "goal" load for srccpuid is the average load across all CPUs.
-	# find_goal() will find determine the optimum selection of the
-	# available interrupts which comes closest to this goal without
-	# falling below the goal.
-
-	my $goal = $delta->{avgintrnsec};
-
-	# We know that the interrupt load on tgtcpuid is less than that on
-	# srccpuid, but its load could still be above avgintrnsec. Don't
-	# choose a goal which would bring srccpuid below the load on tgtcpuid.
-
-	my $avgnsec =
-	    ($delta->{$srccpuid}{intrs} + $delta->{$tgtcpuid}{intrs}) / 2;
-	if ($goal < $avgnsec) {
-		$goal = $avgnsec;
-	}
-
-	# If the largest of the interrupts is on srccpuid, leave it there.
-	# This can help minimize the disruption caused by moving interrupts.
-
-	if ($ivecs[0]->{origcpu} == $srccpuid) {
-		syslog('debug', "Keeping $ivecs[0]->{inum} on $srccpuid");
-		$goal -= $ivecs[0]->{time};
-		shift(@ivecs);
-	}
-
-	syslog('debug', "GOAL: inums should total $goal");
-	find_goal(\@ivecs, $goal);
-
-	# find_goal() returned its results to us by setting $ivec->{goal} if
-	# the ivec should be on srccpuid, or clearing it for tgtcpuid.
-	# Call move_intr() to update our $delta with the new results.
-
-	foreach my $ivec (@ivecs) {
-		syslog('debug', "ivec $ivec->{inum} goal $ivec->{goal}");
-		VERIFY($ivec->{nowcpu} == $srccpuid ||
-		    $ivec->{nowcpu} == $tgtcpuid, "cpu2cpu found an ".
-		    "interrupt not currently on src or tgt cpu");
-
-		if ($ivec->{goal} && $ivec->{nowcpu} != $srccpuid) {
-			move_intr($delta, $ivec->{inum}, $ivec->{nowcpu},
-			    $srccpuid);
-		} elsif ($ivec->{goal} == 0 && $ivec->{nowcpu} != $tgtcpuid) {
-			move_intr($delta, $ivec->{inum}, $ivec->{nowcpu},
-			    $tgtcpuid);
-		}
-	}
-	move_intr_check($delta, $srccpuid, $tgtcpuid); # asserts
-
-	my $newload = $delta->{$srccpuid}{intrs} / $delta->{$srccpuid}{tot};
-	VERIFY($newload <= $srcload && $newload > $delta->{avgintrload},
-	    "cpu2cpu: new load didn't end up in expected range");
-}
-
-
-# find_goal() and its helper do_find_goal() are used to find the best
-# combination of interrupts in order to generate a load that is as close
-# as possible to a goal load without falling below that goal. Before returning
-# to its caller, find_goal() sets a new value in the hash of each interrupt,
-# {goal}, which if set signifies that this interrupt is one of the interrupts
-# identified as part of the set of interrupts which best meet the goal.
-#
-# The arguments to find_goal are a list of ivecs (hash references), sorted
-# by descending {time}, and the goal load. The goal is relative to {time}.
-# The best fit is determined by performing a depth-first search. do_find_goal
-# is the recursive subroutine which carries out the search.
-#
-# It is passed an index as an argument, originally 0. On a given invocation,
-# it is only to consider interrupts in the ivecs array starting at that index.
-# It then considers two possibilities:
-#   1) What is the best goal-fit if I include ivecs[index]?
-#   2) What is the best goal-fit if I exclude ivecs[index]?
-# To determine case 1, it subtracts the load of ivecs[index] from the goal,
-# and calls itself recursively with that new goal and index++.
-# To determine case 2, it calls itself recursively with the same goal and
-# index++.
-#
-# It then compares the two results, decide which one best meets the goals,
-# and returns the result. The return value is the best-fit's interrupt load,
-# followed by a list of all the interrupts which make up that best-fit.
-#
-# As an optimization, a second array loads[] is created which mirrors ivecs[].
-# loads[i] will equal the total loads of all ivecs[i..$#ivecs]. This is used
-# by do_find_goal to avoid recursing all the way to the end of the ivecs
-# array if including all remaining interrupts will still leave the best-fit
-# at below goal load. If so, it then includes all remaining interrupts on
-# the goal list and returns.
-#
-sub find_goal($$)		# private function
-{
-	my ($ivecs, $goal) = @_;
-
-	my @goals;
-	my $load;
-	my $ivec;
-
-	if ($goal <= 0) {
-		@goals = ();	# the empty set will best meet the goal
-	} else {
-		syslog('debug', "finding goal from intrs %s",
-		    ivecs_to_string(@$ivecs));
-
-		# Generate @loads array
-
-		my $tot = 0;
-		foreach $ivec (@$ivecs) {
-			$tot += $ivec->{time};
-		}
-		my @loads = ();
-		foreach $ivec (@$ivecs) {
-			push(@loads, $tot);
-			$tot -= $ivec->{time};
-		}
-		($load, @goals) = do_find_goal($ivecs, \@loads, $goal, 0);
-		VERIFY($load >= $goal, "find_goal didn't meet goals");
-	}
-	syslog('debug', "goals found: %s", ivecs_to_string(@goals));
-
-	# Set or clear $ivec->{goal} for each ivec, based on returned @goals
-
-	foreach $ivec (@$ivecs) {
-		if ($#goals > -1 && $ivec == $goals[0]) {
-			syslog('debug', "inum $ivec->{inum} on source cpu");
-			$ivec->{goal} = 1;
-			shift(@goals);
-		} else {
-			syslog('debug', "inum $ivec->{inum} on target cpu");
-			$ivec->{goal} = 0;
-		}
-	}
-}
-
-
-sub do_find_goal($$$$)		# private function
-{
-	my ($ivecs, $loads, $goal, $idx) = @_;
-
-	if ($idx > $#{$ivecs}) {
-		return (0);
-	}
-	syslog('debug', "$idx: finding goal $goal inum $ivecs->[$idx]{inum}");
-
-	my $load = $ivecs->[$idx]{time};
-	my @goals_with = ();
-	my @goals_without = ();
-	my ($with, $without);
-
-	# If we include all remaining items and we're still below goal,
-	# stop here. We can just return a result that includes $idx and all
-	# subsequent ivecs. Since this will still be below goal, there's
-	# nothing better to be done.
-
-	if ($loads->[$idx] <= $goal) {
-		syslog('debug',
-		    "$idx: including all remaining intrs %s with load %d",
-		    ivecs_to_string(@$ivecs[$idx .. $#{$ivecs}]),
-		    $loads->[$idx]);
-		return ($loads->[$idx], @$ivecs[$idx .. $#{$ivecs}]);
-	}
-
-	# Evaluate the "with" option, i.e. the best matching goal which
-	# includes $ivecs->[$idx]. If idx's load is more than our goal load,
-	# stop here. Once we're above the goal, there is no need to consider
-	# further interrupts since they'll only take us further from the goal.
-
-	if ($goal <= $load) {
-		$with = $load;	# stop here
-	} else {
-		($with, @goals_with) =
-		    do_find_goal($ivecs, $loads, $goal - $load, $idx + 1);
-		$with += $load;
-	}
-	syslog('debug', "$idx: with-load $with intrs %s",
-	       ivecs_to_string($ivecs->[$idx], @goals_with));
-
-	# Evaluate the "without" option, i.e. the best matching goal which
-	# excludes $ivecs->[$idx].
-
-	($without, @goals_without) =
-	    &do_find_goal($ivecs, $loads, $goal, $idx + 1);
-	syslog('debug', "$idx: without-load $without intrs %s",
-	       ivecs_to_string(@goals_without));
-
-	# We now have our "with" and "without" options, and we choose which
-	# best fits the goal. If one is greater than goal and the other is
-	# below goal, we choose the one that is greater. If they are both 
-	# below goal, then we choose the one that is greater. If they are
-	# both above goal, then we choose the smaller.
-
-	my $which;		# 0 == with, 1 == without
-	if ($with >= $goal && $without < $goal) {
-		$which = 0;
-	} elsif ($with < $goal && $without >= $goal) {
-		$which = 1;
-	} elsif ($with >= $goal && $without >= $goal) {
-		$which = ($without < $with);
-	} else {
-		$which = ($without > $with);
-	}
-
-	# Return the load of our best case scenario, followed by all the ivecs
-	# which compose that goal.
-
-	if ($which == 1) {	# without
-		syslog('debug', "$idx: going without");
-		return ($without, @goals_without);
-	} else {
-		syslog('debug', "$idx: going with");
-		return ($with, $ivecs->[$idx], @goals_with);
-	}
-	# Not reached
-}
-
-
-
-
-syslog('debug', "intrd is starting".($debug ? " (debug)" : ""));
-
-my @deltas = ();
-my $deltas_tottime = 0;		# sum of maxsnap-minsnap across @deltas
-my $avggoodness;
-my $baseline_goodness = 0;
-my $compdelta;
-
-my $do_reconfig;
-
-# temp variables
-my $goodness;
-my $deltatime;
-my $olddelta;
-my $olddeltatime;
-my $delta;
-my $newstat;
-my $below_statslen;
-my $newtime;
-my $ret;
-
-
-my $gotsig = 0;
-$SIG{INT} = sub { $gotsig = 1; };     # don't die in the middle of retargeting
-$SIG{HUP} = $SIG{INT};
-$SIG{TERM} = $SIG{INT};
-
-my $ks;
-if ($using_scengen == 0) {
-	$ks = Sun::Solaris::Kstat->new();
-} else {
-	$ks = myks_update();	# supplied by the simulator
-}
-
-# If no pci_intrs kstats were found, we need to exit, but we can't because
-# SMF will restart us and/or report an error to the administrator. But
-# there's nothing an administrator can do. So print out a message for SMF
-# logs and silently pause forever.
-
-if (!exists($ks->{pci_intrs})) {
-	print STDERR "$cmdname: no interrupts were found; ".
-	    "your PCI bus may not yet be supported\n";
-	pause() while $gotsig == 0;
-	exit 0;
-}
-
-# See if this is a system with a pcplusmp APIC.
-# Such systems will get special handling.
-# Assume that if one bus has a pcplusmp APIC that they all do.
-
-# Get a list of pci_intrs kstats.
-my @elem = values(%{$ks->{pci_intrs}});
-my $elem0 = $elem[0];
-my $elemval = (values(%$elem0))[0];
-
-# Use its buspath to query the system.  It is assumed that either all or none
-# of the busses on a system are hosted by the pcplusmp APIC or APIX.
-my $pcplusmp_sys = is_apic($elemval->{buspath});
-
-my $stat = getstat($ks, $pcplusmp_sys);
-
-for (;;) {
-	sub clear_deltas {
-		@deltas = ();
-		$deltas_tottime = 0;
-		$stat = 0;   # prevent next gen_delta() from setting {missing}
-	}
-
-	# 1. Sleep, update the kstats, and save the new stats in $newstat.
-
-	exit 0 if $gotsig;		# if we got ^C / SIGTERM, exit
-	if ($using_scengen == 0) {
-		sleep($sleeptime);
-		exit 0 if $gotsig;	# if we got ^C / SIGTERM, exit
-		$ks->update();
-	} else {
-		$ks = myks_update();
-	}
-	$newstat = getstat($ks, $pcplusmp_sys);
-
-	# $stat or $newstat could be zero if they're uninitialized, or if
-	# getstat() failed. If $stat is zero, move $newstat to $stat, sleep
-	# and try again. If $newstat is zero, then we also sleep and try
-	# again, hoping the problem will clear up.
-
-	next if (!ref $newstat);
-	if (!ref $stat) {
-		$stat = $newstat;
-		next;
-	}
-
-	# 2. Compare $newstat with the prior set of values, result in %$delta.
-
-	$delta = generate_delta($stat, $newstat);
-	dumpdelta($delta) if $debug;	# Dump most recent stats to stdout.
-	$stat = $newstat;	# The new stats now become the old stats.
-
-
-	# 3. If $delta->{missing}, then there has been a reconfiguration of
-	# either cpus or interrupts (probably both). We need to toss out our
-	# old set of statistics and start from scratch.
-	#
-	# Also, if the delta covers a very long range of time, then we've
-	# been experiencing a system overload that has resulted in intrd
-	# not being allowed to run effectively for a while now. As above,
-	# toss our old statistics and start from scratch.
-
-	$deltatime = $delta->{maxsnap} - $delta->{minsnap};
-	if ($delta->{missing} > 0 || $deltatime > $statslen) {
-		clear_deltas();
-		syslog('debug', "evaluating interrupt assignments");
-		next;
-	}
-
-
-	# 4. Incorporate new delta into the list of deltas, and associated
-	# statistics. If we've just now received $statslen deltas, then it's
-	# time to evaluate a reconfiguration.
-
-	$below_statslen = ($deltas_tottime < $statslen);
-	$deltas_tottime += $deltatime;
-	$do_reconfig = ($below_statslen && $deltas_tottime >= $statslen);
-	push(@deltas, $delta);
-
-	# 5. Remove old deltas if total time is more than $statslen. We use
-	# @deltas as a moving average of the last $statslen seconds. Shift
-	# off the olders deltas, but only if that doesn't cause us to fall
-	# below $statslen seconds.
-
-	while (@deltas > 1) {
-		$olddelta = $deltas[0];
-		$olddeltatime = $olddelta->{maxsnap} - $olddelta->{minsnap};
-		$newtime = $deltas_tottime - $olddeltatime;
-		last if ($newtime < $statslen);
-
-		shift(@deltas);
-		$deltas_tottime = $newtime;
-	}
-
-	# 6. The brains of the operation are here. First, check if we're
-	# imbalanced, and if so set $do_reconfig. If $do_reconfig is set,
-	# either because of imbalance or above in step 4, we evaluate a
-	# new configuration.
-	#
-	# First, take @deltas and generate a single "compressed" delta
-	# which summarizes them all. Pass that to do_reconfig and see
-	# what it does with it:
-	#
-	# $ret == -1 : failure
-	# $ret ==  0 : current config is optimal (or close enough)
-	# $ret ==  1 : reconfiguration has occurred
-	#
-	# If $ret is -1 or 1, dump all our deltas and start from scratch.
-	# Step 4 above will set do_reconfig soon thereafter.
-	#
-	# If $ret is 0, then nothing has happened because we're already
-	# good enough. Set baseline_goodness to current goodness.
-
-	$compdelta = compress_deltas(\@deltas);
-	if (VERIFY(ref($compdelta) eq "HASH", "couldn't compress deltas")) {
-		clear_deltas();
-		next;
-	}
-	$compdelta->{goodness} = goodness($compdelta);
-	dumpdelta($compdelta) if $debug;
-
-	$goodness = $compdelta->{goodness};
-	syslog('debug', "GOODNESS: %5.2f%%", $goodness * 100);
-
-	if ($deltas_tottime >= $statslen &&
-	    imbalanced($goodness, $baseline_goodness)) {
-		$do_reconfig = 1;
-	}
-
-	if ($do_reconfig) {
-		$ret = do_reconfig($compdelta);
-
-		if ($ret != 0) {
-			clear_deltas();
-			syslog('debug', "do_reconfig FAILED!") if $ret == -1;
-		} else {
-			syslog('debug', "setting new baseline of $goodness");
-			$baseline_goodness = $goodness;
-		}
-	}
-	syslog('debug', "---------------------------------------");
-}		
--- old/usr/src/cmd/intrd/intrd.xml	2013-09-25 09:16:13.860163213 +0000
+++ new/usr/src/cmd/intrd/intrd.xml	2013-09-25 09:16:13.869835904 +0000
@@ -24,8 +24,6 @@
 	Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 	Use is subject to license terms.
 
-	ident	"%Z%%M%	%I%	%E% SMI"
-
 	NOTE:  This service manifest is not editable; its contents will
 	be overwritten by package or patch operations, including
 	operating system upgrade.  Make customizations in a different
@@ -54,15 +52,19 @@
 	<exec_method
 		type='method'
 		name='start'
-		exec='/lib/svc/method/svc-intrd'
+		exec='/lib/svc/method/svc-intrd start'
 		timeout_seconds='60' />
 
 	<exec_method
 		type='method'
 		name='stop'
-		exec=':kill'
+		exec='/lib/svc/method/svc-intrd stop'
 		timeout_seconds='10' />
 
+	<property_group name='startd' type='framework'>
+		<propval name='duration' type='astring' value='transient' />
+	</property_group>
+
 	<stability value='Unstable' />
 
 	<template>
--- old/usr/src/cmd/intrd/svc-intrd	2013-09-25 09:16:14.265640096 +0000
+++ new/usr/src/cmd/intrd/svc-intrd	2013-09-25 09:16:14.275132625 +0000
@@ -25,20 +25,19 @@
 # Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# ident	"%Z%%M%	%I%	%E% SMI"
 
 . /lib/svc/share/smf_include.sh
 
-# Don't start intrd if intrd can't be found or if intrd is already running.
-
-if [ ! -f /usr/lib/intrd ]; then
-	echo "$0: intrd daemon not installed"
-	exit $SMF_EXIT_ERR_CONFIG
-fi
-
-if /usr/bin/pgrep -x -z global intrd >/dev/null; then
-	echo "$0: intrd is already running"
-	exit $SMF_EXIT_ERR_FATAL
-fi
+#
+# Main
+#
+case "$1" in
+'start')
+	#/sbin/uadmin 24 1 >/dev/null 2>&1
+	;;
+'stop')
+	#/sbin/uadmin 24 0 >/dev/null 2>&1
+	;;
+esac;
 
-/usr/lib/intrd &
+exit $SMF_EXIT_OK
--- old/usr/src/cmd/uadmin/uadmin.c	2013-09-25 09:16:14.663938386 +0000
+++ new/usr/src/cmd/uadmin/uadmin.c	2013-09-25 09:16:14.676818361 +0000
@@ -119,6 +119,9 @@
 	case A_SWAPCTL:
 		event_id = ADT_uadmin_swapctl;
 		break;
+	case A_INTRD:
+		event_id = ADT_uadmin_intrd;
+		break;
 	default:
 		event_id = 0;
 	}
@@ -252,6 +255,9 @@
 			case A_SWAPCTL:
 				event->adt_uadmin_swapctl.fcn = fcn_id;
 				break;
+			case A_INTRD:
+				event->adt_uadmin_intrd.fcn = fcn_id;
+				break;
 			}
 
 			if (adt_put_event(event, ADT_SUCCESS, 0) != 0) {
--- old/usr/src/lib/libbsm/audit_event.txt	2013-09-25 09:16:15.035567657 +0000
+++ new/usr/src/lib/libbsm/audit_event.txt	2013-09-25 09:16:15.044993422 +0000
@@ -673,3 +673,4 @@
 9199:AUE_ForceScreenSaver:XForceScreenSaver(3X11):xp
 9200:AUE_SetPointerMapping:XSetPointerMapping(3X11):xs
 9201:AUE_SetModifierMapping:XSetModifierMapping(3X11):xs
+9202:AUE_uadmin_intrd:uadmin(1m) - intrd:ss
--- old/usr/src/lib/libbsm/common/adt.xml	2013-09-25 09:16:15.468435626 +0000
+++ new/usr/src/lib/libbsm/common/adt.xml	2013-09-25 09:16:15.479061265 +0000
@@ -2572,6 +2572,13 @@
 	    <external opt="none"/>
 	</entry>
     </event>
+    <event id="AUE_uadmin_intrd" instance_of="AUE_uadmin_generic_fcn"
+	header="0" idNo="136" omit="JNI">
+	<title>uadmin intrd</title>
+	<program>/sbin/uadmin</program>
+	<program>/usr/sbin/uadmin</program>
+	<see>uadmin(1M)</see>
+    </event>
 
 <!-- add new events here with the next higher idNo -->
 <!-- Highest idNo is 135, so next is 136, then fix this comment -->
--- old/usr/src/pkg/manifests/SUNWcs.mf	2013-09-25 09:16:15.987323083 +0000
+++ new/usr/src/pkg/manifests/SUNWcs.mf	2013-09-25 09:16:15.995914695 +0000
@@ -1219,7 +1219,6 @@
 file path=usr/lib/inet/certlocal mode=0555
 file path=usr/lib/inet/certrldb mode=0555
 file path=usr/lib/inet/inetd mode=0555
-file path=usr/lib/intrd mode=0555
 file path=usr/lib/isaexec mode=0555
 file path=usr/lib/kssladm mode=0555
 $(sparc_ONLY)file path=usr/lib/ld.so
@@ -1880,7 +1879,7 @@
 #
 depend fmri=release/name type=require
 #
-# intrd and others use the illumos-defaulted perl interpreter
+# Illumos-defaulted perl interpreter
 #
 depend fmri=runtime/perl-510 type=require
 #
--- old/usr/src/uts/common/Makefile.files	2013-09-25 09:16:16.426613487 +0000
+++ new/usr/src/uts/common/Makefile.files	2013-09-25 09:16:16.434907918 +0000
@@ -194,6 +194,7 @@
 		groups.o	\
 		grow.o		\
 		hat_refmod.o	\
+		interrupt_balancer.o	\
 		id32.o		\
 		id_space.o	\
 		inet_ntop.o	\
--- /dev/null	2013-09-25 09:16:16.000000000 +0000
+++ new/usr/src/uts/common/os/interrupt_balancer.c	2013-09-25 09:16:16.841055808 +0000
@@ -0,0 +1,1512 @@
+/*
+ * 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 interrupt load balancer reassigns interrupts from one cpu
+ * to another, if the interrupt load
+ */
+
+#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>
+
+extern	proc_t	*proc_intrd;
+
+#define	IB_NAME			"intrd"
+
+/*
+ * Various sleeptimes.
+ */
+#define	IB_NORMAL_SLEEPTIME	10
+#define	IB_IDLE_SLEEPTIME	45
+#define IB_ONECPU_SLEEPTIME	(60 * 15)
+
+static kmutex_t		ib_lock;
+static kcondvar_t	ib_cv;
+
+/*
+ * System tuneable.
+ *
+ * Exclude interrupts in this list.
+ */
+static char *ib_exclude = NULL;
+
+typedef struct _ib_exclude {
+	list_node_t	*ix_next;
+	processorid_t	ix_cpu_id;
+} ib_exclude_list_t;
+
+static list_t	ib_exclude_list;
+
+
+
+/*
+ * CPU structure.
+ */
+typedef struct _ib_cpu {
+	list_node_t	ic_next;	/* link */
+	processorid_t	ic_cpu_id;	/* processor id */
+	boolean_t	ic_offline;	/* CPU is offline */
+	list_t		ic_ivec_list;	/* list of interrupt vectors */
+	uint32_t	ic_num_ivecs;	/* number of interrupt vectors */
+	hrtime_t	ic_tot;		/* total time */
+	int64_t		ic_intrs;	/* number of interrupts */
+	int		ic_intr_load;	/* interrupts / total time */
+	int64_t		ic_big_intrs;
+	int64_t		ic_bigintr;	/* largest interrupt on cpu */
+	lgrp_t		*ic_lgrp;	/* locality group of this cpu */
+} ib_cpu_t;
+
+/*
+ * Interrupt vector.
+ */
+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;	/* current CPU */
+	processorid_t	ii_now_cpu;	/* new to be assigned CPU */
+	uint64_t	ii_inum;
+	boolean_t	ii_goal;
+} 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 OS CPUs */
+
+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	boolean_t	ib_cpu_exclude(processorid_t);
+static	ib_cpu_t	*ib_cpu_create(void);
+static	ib_cpu_t	*ib_cpu_find(list_t, processorid_t);
+static	void	ib_cpu_destroy(ib_cpu_t *);
+
+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	void	ib_interrupt_move_check(ib_delta_t *, processorid_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	IS_CPU(cpu_id)		(cpu[cpu_id] != NULL)
+
+#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));
+
+	/* Initialize interrupt exclude list */
+	list_create(&ib_exclude_list, sizeof (ib_exclude_list_t),
+	   offsetof(ib_exclude_list_t, ix_next));
+
+	/*
+	 * Parse list of interrupts to exclude.
+	 *
+	 * XXX: move interrupts to active processors.
+	 */
+	if (ib_exclude != NULL) {
+		processorid_t	rval;
+
+		IB_LOG((CE_CONT, "XXX %s XXX", ib_exclude));
+	}
+
+	/*
+	 * 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];
+		if (lgrp->lgrp_cpu != NULL) {
+			cpu_t	*lgrp_cpu;
+
+			for (lgrp_cpu = lgrp->lgrp_cpu; lgrp_cpu != NULL;
+			    lgrp_cpu =lgrp_cpu->cpu_next_lgrp) {
+				ib_cpu_t	*icpu;
+
+				icpu = ib_cpu_find(ib_cpu_list, lgrp_cpu->cpu_id);
+
+				/*
+				 * Assign locality group if we found a CPU.
+				 */
+				if (icpu != NULL)
+					icpu->ic_lgrp = lgrp;
+			}
+		}
+	}
+
+	/*
+	 * 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);
+
+	list_destroy(&ib_exclude_list);
+	list_destroy(&ib_delta_list);
+	list_destroy(&ib_cpu_list);
+
+}
+
+/*
+ * 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;
+
+	/*
+	 * Is this CPU baned from interrupt handling?
+	 */
+	if (ib_cpu_exclude(cpu_id))
+		return;
+
+	new_cpu = ib_cpu_create();
+	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;
+	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) {
+			/* Remove CPU from global list */
+			list_remove(&ib_cpu_list, icpu);
+
+			/* Free CPU structure */
+			ib_cpu_destroy(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) {
+	case CPU_UNCONFIG:
+	case CPU_CPUPART_OUT:
+	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	*new_cpu;
+
+	new_cpu = kmem_alloc(sizeof (ib_cpu_t), KM_SLEEP);
+	new_cpu->ic_offline = B_FALSE;
+
+	return (new_cpu);
+}
+
+static void
+ib_cpu_destroy(ib_cpu_t *old_cpu)
+{
+	ib_ivec_t	*ivec;
+
+	FOREACH_IVEC(ivec, old_cpu->ic_ivec_list) {
+		kmem_free(ivec, sizeof (ib_ivec_t));
+	}
+
+	kmem_free(old_cpu, sizeof (ib_cpu_t));
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Search exclude lists.
+ */
+static boolean_t
+ib_cpu_exclude(processorid_t cpu_id)
+{
+	ib_exclude_list_t	*excluded_cpu;
+
+	/*
+	 * Search global list of CPUs excluded from interrupt handling.
+	 */
+	for (excluded_cpu = list_head(&ib_exclude_list); excluded_cpu != NULL;
+	    excluded_cpu = list_next(&ib_exclude_list, excluded_cpu)) {
+		if (excluded_cpu->ix_cpu_id == cpu_id)
+			return (B_TRUE);
+	}
+
+	return (B_FALSE);
+}
+
+/*
+ * 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
+ib_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);
+}
+
+/*
+ * Destroy a snapshot.
+ */
+static void
+ib_snapshot_destroy(ib_snapshot_t *snapshot)
+{
+	ib_cpu_t	*icpu;
+
+	FOREACH_CPU(icpu, snapshot->is_cpu_list) {
+		ib_cpu_destroy(icpu);
+	}
+
+	kmem_free(snapshot, sizeof (ib_snapshot_t));
+}
+
+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, new_load;
+	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);
+
+	FOREACH_IVEC(ivec, ivecs) {
+		if (!(ivec->ii_now_cpu == src_cpuid ||
+		    ivec->ii_now_cpu == tgt_cpuid)) {
+			IB_LOG((CE_CONT, "ib_do_reconfig_cpu2cpu: "));
+		}
+
+		if (ivec->ii_goal && ivec->ii_now_cpu != src_cpuid) {
+			ib_interrupt_do_move(ivec, src_cpuid);
+		} else if (ivec->ii_goal == B_FALSE &&
+			    ivec->ii_now_cpu != tgt_cpuid) {
+			ib_interrupt_do_move(ivec, tgt_cpuid);
+		}
+	}
+
+	ib_interrupt_move_check(delta, src_cpuid, tgt_cpuid);
+
+	new_load = src_cpu->ic_intrs / src_cpu->ic_tot;
+
+	if (!(new_load <= src_load && new_load > delta->id_avgintrload)) {
+		IB_LOG((CE_CONT, "ib_reconfig_cpu2cpu: %d", new_load));
+	}
+}
+
+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 move 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);
+}
--- old/usr/src/uts/common/os/main.c	2013-09-25 09:16:17.042312340 +0000
+++ new/usr/src/uts/common/os/main.c	2013-09-25 09:16:17.052012977 +0000
@@ -87,6 +87,7 @@
 proc_t *proc_init;		/* init */
 proc_t *proc_pageout;		/* pageout daemon */
 proc_t *proc_fsflush;		/* fsflush daemon */
+proc_t *proc_intrd;		/* interrupt daemon */
 
 pgcnt_t	maxmem;		/* Maximum available memory in pages.	*/
 pgcnt_t	freemem;	/* Current available memory in pages.	*/
@@ -363,6 +364,7 @@
 	int		(**initptr)();
 	extern void	sched();
 	extern void	fsflush();
+	extern void	interrupt_balancer();
 	extern int	(*init_tbl[])();
 	extern int	(*mp_init_tbl[])();
 	extern id_t	syscid, defaultcid;
@@ -622,6 +624,10 @@
 	    FAMOUS_PID_FSFLUSH))
 		panic("main: unable to fork fsflush()");
 
+	/* create interrupt balancer process */
+	if (newproc(interrupt_balancer, NULL, syscid, minclsyspri, NULL, 0))
+		panic("main: unable to fork interrupt_balancer()");
+
 	/* create cluster process if we're a member of one */
 	if (cluster_bootflags & CLUSTER_BOOTED) {
 		if (newproc(cluster_wrapper, NULL, syscid, minclsyspri,
--- old/usr/src/uts/common/sys/uadmin.h	2013-09-25 09:16:17.406413154 +0000
+++ new/usr/src/uts/common/sys/uadmin.h	2013-09-25 09:16:17.423706298 +0000
@@ -50,6 +50,7 @@
 /*			17-21	   reserved for obsolete interface */
 #define	A_SDTTEST	22	/* DTrace sdt:::test */
 #define	A_CONFIG	23	/* For system configuration */
+#define	A_INTRD		24	/* Interrupt balancer */
 
 #define	AD_UNKNOWN	-1	/* no method */
 #define	AD_HALT		0	/* halt the processor */
