commit 354cc0e4e8e323ca667cbf99fdf6724556fa40ff
Author: Kengo NAKAHARA <k-nakahara@iij.ad.jp>
Date:   Thu Jul 24 10:14:31 2014 +0000

    support irq affinity

diff --git a/distrib/sets/lists/base/mi b/distrib/sets/lists/base/mi
index 2e07361..8d3820b 100644
--- a/distrib/sets/lists/base/mi
+++ b/distrib/sets/lists/base/mi
@@ -1455,6 +1455,7 @@
 ./usr/sbin/ifwatchd				base-netutil-bin
 ./usr/sbin/inetd				base-netutil-bin
 ./usr/sbin/installboot				base-sysutil-bin
+./usr/sbin/intrctl				base-sysutil-bin
 ./usr/sbin/iopctl				base-sysutil-bin
 ./usr/sbin/iostat				base-sysutil-bin
 ./usr/sbin/ipfs					base-ipf-bin		ipfilter
diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index d63ee41..b284058 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3192,6 +3192,7 @@
 ./usr/include/sys/gpio.h			comp-c-include
 ./usr/include/sys/hash.h			comp-c-include
 ./usr/include/sys/ieee754.h			comp-c-include
+./usr/include/sys/intrio.h			comp-c-include
 ./usr/include/sys/inttypes.h			comp-c-include
 ./usr/include/sys/ioccom.h			comp-c-include
 ./usr/include/sys/ioctl.h			comp-c-include
diff --git a/distrib/sets/lists/debug/mi b/distrib/sets/lists/debug/mi
index 7cadb2e..7fb355f 100644
--- a/distrib/sets/lists/debug/mi
+++ b/distrib/sets/lists/debug/mi
@@ -1045,6 +1045,7 @@
 ./usr/libdata/debug/usr/sbin/ifwatchd.debug	comp-netutil-debug	debug
 ./usr/libdata/debug/usr/sbin/inetd.debug	comp-netutil-debug	debug
 ./usr/libdata/debug/usr/sbin/installboot.debug	comp-sysutil-debug	debug
+./usr/libdata/debug/usr/sbin/intrctl.debug	comp-sysutil-debug	debug
 ./usr/libdata/debug/usr/sbin/iopctl.debug	comp-sysutil-debug	debug
 ./usr/libdata/debug/usr/sbin/iostat.debug	comp-sysutil-debug	debug
 ./usr/libdata/debug/usr/sbin/ipfs.debug		comp-ipf-debug		ipfilter,debug
diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi
index dcf1f8c..d775a4d 100644
--- a/distrib/sets/lists/man/mi
+++ b/distrib/sets/lists/man/mi
@@ -2530,6 +2530,7 @@
 ./usr/share/man/cat8/inetd.0			man-netutil-catman	.cat
 ./usr/share/man/cat8/init.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/installboot.0		man-sysutil-catman	.cat
+./usr/share/man/cat8/intrctl.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/intro.0			man-sys-catman		.cat
 ./usr/share/man/cat8/iopctl.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/iostat.0			man-sysutil-catman	.cat
@@ -5391,6 +5392,7 @@
 ./usr/share/man/html8/inetd.html		man-netutil-htmlman	html
 ./usr/share/man/html8/init.html			man-sysutil-htmlman	html
 ./usr/share/man/html8/installboot.html		man-sysutil-htmlman	html
+./usr/share/man/html8/intrctl.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/intro.html		man-sys-htmlman		html
 ./usr/share/man/html8/iopctl.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/iostat.html		man-sysutil-htmlman	html
@@ -8396,6 +8398,7 @@
 ./usr/share/man/man8/inetd.8			man-netutil-man		.man
 ./usr/share/man/man8/init.8			man-sysutil-man		.man
 ./usr/share/man/man8/installboot.8		man-sysutil-man		.man
+./usr/share/man/man8/intrctl.8			man-sysutil-man		.man
 ./usr/share/man/man8/intro.8			man-sys-man		.man
 ./usr/share/man/man8/iopctl.8			man-sysutil-man		.man
 ./usr/share/man/man8/iostat.8			man-sysutil-man		.man
diff --git a/etc/MAKEDEV.tmpl b/etc/MAKEDEV.tmpl
index 658cd68..92b665a 100644
--- a/etc/MAKEDEV.tmpl
+++ b/etc/MAKEDEV.tmpl
@@ -1446,6 +1446,10 @@ cpuctl)
 	mkdev cpuctl c %cpuctl_chr% 0 666
 	;;
 
+intrctl)
+	mkdev intrctl c %intr_chr% 0 666
+	;;
+
 audio|audio[0-9]*)
 	unit=${i#audio}
 	audio=audio$unit
diff --git a/etc/etc.amd64/MAKEDEV.conf b/etc/etc.amd64/MAKEDEV.conf
index a4a831c..4e71816 100644
--- a/etc/etc.amd64/MAKEDEV.conf
+++ b/etc/etc.amd64/MAKEDEV.conf
@@ -41,6 +41,7 @@ all_md)
 	makedev kttcp
 	makedev nsmb
 	makedev bio
+	makedev intrctl
 	;;
 
 xen)
diff --git a/etc/etc.i386/MAKEDEV.conf b/etc/etc.i386/MAKEDEV.conf
index ba3e2cc..2d2e84f 100644
--- a/etc/etc.i386/MAKEDEV.conf
+++ b/etc/etc.i386/MAKEDEV.conf
@@ -45,6 +45,7 @@ all_md)
 	makedev io
 	makedev bio
 	makedev cfs
+	makedev intrctl
 	;;
 
 xen)
diff --git a/include/paths.h b/include/paths.h
index 82f2ae5..22d8ecf 100644
--- a/include/paths.h
+++ b/include/paths.h
@@ -73,6 +73,7 @@
 #define	_PATH_GETTYTAB	"/etc/gettytab"
 #define	_PATH_I18NMODULE "/usr/lib/i18n"
 #define	_PATH_ICONV	"/usr/share/i18n/iconv"
+#define	_PATH_INTRCTL	"/dev/intrctl"
 #define	_PATH_KMEM	"/dev/kmem"
 #define	_PATH_KSYMS	"/dev/ksyms"
 #define	_PATH_KVMDB	"/var/db/kvm.db"
diff --git a/sys/arch/amd64/conf/ALL b/sys/arch/amd64/conf/ALL
index 4c85633..9b4a24d 100644
--- a/sys/arch/amd64/conf/ALL
+++ b/sys/arch/amd64/conf/ALL
@@ -1565,6 +1565,7 @@ pseudo-device	lockstat		# lock profiling
 pseudo-device	bcsp			# BlueCore Serial Protocol
 pseudo-device	btuart			# Bluetooth HCI UART (H4)
 pseudo-device	gpiosim		1	# GPIO simulator
+pseudo-device	intrctl			# for intrctl(8)
 
 pseudo-device	filemon			# process monitor for make(1)
 
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 4d2c1bd..0845c58 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -1245,6 +1245,7 @@ pseudo-device	ksyms			# /dev/ksyms
 pseudo-device	lockstat		# lock profiling
 pseudo-device	bcsp			# BlueCore Serial Protocol
 pseudo-device	btuart			# Bluetooth HCI UART (H4)
+pseudo-device	intrctl			# for intrctl(8)
 
 # a pseudo device needed for Coda	# also needs CODA (above)
 pseudo-device	vcoda			# coda minicache <-> venus comm.
diff --git a/sys/arch/amd64/conf/files.amd64 b/sys/arch/amd64/conf/files.amd64
index 89eb6b8..fcc6d9a 100644
--- a/sys/arch/amd64/conf/files.amd64
+++ b/sys/arch/amd64/conf/files.amd64
@@ -19,6 +19,9 @@ defparam opt_realmem.h	REALBASEMEM REALEXTMEM
 # The PHYSMEM_MAX_{SIZE,ADDR} optionms
 defparam opt_physmem.h	PHYSMEM_MAX_ADDR PHYSMEM_MAX_SIZE
 
+# for intrctl(8)
+defpseudo	intrctl
+
 #
 # XXX these are just here at the moment so that we can share files
 # with the i386 (they include the opt_*.h for these)
diff --git a/sys/arch/amd64/conf/majors.amd64 b/sys/arch/amd64/conf/majors.amd64
index 9e6b1ac..4408176 100644
--- a/sys/arch/amd64/conf/majors.amd64
+++ b/sys/arch/amd64/conf/majors.amd64
@@ -90,6 +90,8 @@ device-major	dpt		char 96			dpt
 device-major	twe		char 97			twe
 device-major	nsmb		char 98			nsmb		
 
+device-major	intrctl		char 99			intrctl
+
 #
 # Device majors for Xen. These are assigned here so that:
 # - we are sure there are no conflicts with plain i386 
diff --git a/sys/arch/i386/conf/ALL b/sys/arch/i386/conf/ALL
index 4942ba7..438d3fc 100644
--- a/sys/arch/i386/conf/ALL
+++ b/sys/arch/i386/conf/ALL
@@ -1750,6 +1750,7 @@ pseudo-device	lockstat		# lock profiling
 pseudo-device	bcsp			# BlueCore Serial Protocol
 pseudo-device	btuart			# Bluetooth HCI UART (H4)
 pseudo-device	gpiosim		1	# GPIO simulator
+pseudo-device	intrctl			# for intrctl(8)
 
 pseudo-device	filemon			# process monitor for make(1)
 
diff --git a/sys/arch/i386/conf/GENERIC b/sys/arch/i386/conf/GENERIC
index ad7a8d1..9a68ea0 100644
--- a/sys/arch/i386/conf/GENERIC
+++ b/sys/arch/i386/conf/GENERIC
@@ -1655,6 +1655,7 @@ pseudo-device	lockstat		# lock profiling
 pseudo-device	bcsp			# BlueCore Serial Protocol
 pseudo-device	btuart			# Bluetooth HCI UART (H4)
 #pseudo-device	gpiosim		1	# GPIO simulator
+pseudo-device	intrctl			# for intrctl(8)
 
 # a pseudo device needed for Coda	# also needs CODA (above)
 pseudo-device	vcoda			# coda minicache <-> venus comm.
diff --git a/sys/arch/i386/conf/files.i386 b/sys/arch/i386/conf/files.i386
index 367b145..2c974c2 100644
--- a/sys/arch/i386/conf/files.i386
+++ b/sys/arch/i386/conf/files.i386
@@ -15,6 +15,9 @@ defparam opt_kernbase.h	KERNBASE
 # delay before cpu_reset() for reboot.
 defparam		CPURESET_DELAY
 
+# for intrctl(8)
+defpseudo	intrctl
+
 # Obsolete Xbox support
 obsolete defflag	XBOX
 
diff --git a/sys/arch/i386/conf/majors.i386 b/sys/arch/i386/conf/majors.i386
index 38c043f..869a0bc 100644
--- a/sys/arch/i386/conf/majors.i386
+++ b/sys/arch/i386/conf/majors.i386
@@ -105,6 +105,7 @@ device-major	rd		char 105 block 22	rd
 device-major	ct		char 106 block 23	ct
 device-major	mt		char 107 block 24	mt
 
+device-major	intrctl		char 108		intrctl
 #
 # Device majors for Xen. These are assigned here so that:
 # - we are sure there are no conflicts with plain i386 
diff --git a/sys/arch/x86/include/intr.h b/sys/arch/x86/include/intr.h
index 8484177..5f941db 100644
--- a/sys/arch/x86/include/intr.h
+++ b/sys/arch/x86/include/intr.h
@@ -71,6 +71,11 @@ struct intrstub {
 	void *ist_resume;
 };
 
+struct percpu_evcnt {
+	cpuid_t cpuid;
+	uint64_t count;
+};
+
 struct intrsource {
 	int is_maxlevel;		/* max. IPL for this source */
 	int is_pin;			/* IRQ for legacy; pin for IO APIC,
@@ -80,12 +85,15 @@ struct intrsource {
 	void *is_recurse;		/* entry for spllower */
 	void *is_resume;		/* entry for doreti */
 	lwp_t *is_lwp;			/* for soft interrupts */
-	struct evcnt is_evcnt;		/* interrupt counter */
+	struct evcnt is_evcnt;		/* interrupt counter per cpu */
 	int is_flags;			/* see below */
 	int is_type;			/* level, edge */
 	int is_idtvec;
 	int is_minlevel;
 	char is_evname[32];		/* event counter name */
+	char *is_xname;			/* device name */
+	struct percpu_evcnt *is_saved_evcnt;	/* interrupt count of deactivated cpus */
+	int is_active_cpu;		/* active cpuid */
 };
 
 #define IS_LEGACY	0x0001		/* legacy ISA irq source */
@@ -173,6 +181,7 @@ struct pcibus_attach_args;
 
 void intr_default_setup(void);
 void x86_nmi(void);
+void *intr_establish_xname(int, struct pic *, int, int, int, int (*)(void *), void *, bool, const char *);
 void *intr_establish(int, struct pic *, int, int, int, int (*)(void *), void *, bool);
 void intr_disestablish(struct intrhand *);
 void intr_add_pcibus(struct pcibus_attach_args *);
@@ -181,6 +190,10 @@ void cpu_intr_init(struct cpu_info *);
 int intr_find_mpmapping(int, int, int *);
 struct pic *intr_findpic(int);
 void intr_printconfig(void);
+void intr_kernfs_init(void);
+
+struct intrsource *intr_allocate_io_intrsource(int);
+void intr_free_io_intrsource(int);
 
 int x86_send_ipi(struct cpu_info *, int);
 void x86_broadcast_ipi(int);
diff --git a/sys/arch/x86/include/intrdefs.h b/sys/arch/x86/include/intrdefs.h
index 9c8824f..17245df 100644
--- a/sys/arch/x86/include/intrdefs.h
+++ b/sys/arch/x86/include/intrdefs.h
@@ -56,6 +56,8 @@
 #define IDT_INTR_LOW	(0x20 + NUM_LEGACY_IRQS)
 #define IDT_INTR_HIGH	0xef
 
+#define NUM_IO_INTS	255
+
 #ifndef XEN
 
 #define X86_IPI_HALT			0x00000001
diff --git a/sys/arch/x86/include/pci_machdep_common.h b/sys/arch/x86/include/pci_machdep_common.h
index 0c111d6..535fe98 100644
--- a/sys/arch/x86/include/pci_machdep_common.h
+++ b/sys/arch/x86/include/pci_machdep_common.h
@@ -113,6 +113,8 @@ int		pci_intr_map(const struct pci_attach_args *,
 const char	*pci_intr_string(pci_chipset_tag_t, pci_intr_handle_t,
 		    char *, size_t);
 const struct evcnt *pci_intr_evcnt(pci_chipset_tag_t, pci_intr_handle_t);
+void		*pci_intr_establish_xname(pci_chipset_tag_t, pci_intr_handle_t,
+		    int, int (*)(void *), void *, const char *);
 void		*pci_intr_establish(pci_chipset_tag_t, pci_intr_handle_t,
 		    int, int (*)(void *), void *);
 void		pci_intr_disestablish(pci_chipset_tag_t, void *);
diff --git a/sys/arch/x86/pci/pci_intr_machdep.c b/sys/arch/x86/pci/pci_intr_machdep.c
index 52ead8f..6bbb8ea 100644
--- a/sys/arch/x86/pci/pci_intr_machdep.c
+++ b/sys/arch/x86/pci/pci_intr_machdep.c
@@ -271,8 +271,8 @@ pci_intr_setattr(pci_chipset_tag_t pc, pci_intr_handle_t *ih,
 }
 
 void *
-pci_intr_establish(pci_chipset_tag_t pc, pci_intr_handle_t ih,
-    int level, int (*func)(void *), void *arg)
+pci_intr_establish_xname(pci_chipset_tag_t pc, pci_intr_handle_t ih,
+    int level, int (*func)(void *), void *arg, const char *xname)
 {
 	int pin, irq;
 	struct pic *pic;
@@ -309,8 +309,15 @@ pci_intr_establish(pci_chipset_tag_t pc, pci_intr_handle_t ih,
 	}
 #endif
 
-	return intr_establish(irq, pic, pin, IST_LEVEL, level, func, arg,
-	    mpsafe);
+	return intr_establish_xname(irq, pic, pin, IST_LEVEL, level, func, arg,
+	    mpsafe, xname);
+}
+
+void *
+pci_intr_establish(pci_chipset_tag_t pc, pci_intr_handle_t ih,
+    int level, int (*func)(void *), void *arg)
+{
+	return pci_intr_establish_xname(pc, ih, level, func, arg, "unknown");
 }
 
 void
diff --git a/sys/arch/x86/x86/intr.c b/sys/arch/x86/x86/intr.c
index b29f591..3bee08e 100644
--- a/sys/arch/x86/x86/intr.c
+++ b/sys/arch/x86/x86/intr.c
@@ -151,6 +151,17 @@ __KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.77 2014/05/20 03:24:19 ozaki-r Exp $");
 #include <sys/cpu.h>
 #include <sys/atomic.h>
 #include <sys/xcall.h>
+#include <sys/sysctl.h>
+
+#include <sys/stat.h>
+#include <sys/dirent.h>
+#include <sys/malloc.h>
+#include <sys/vnode.h>
+#include <miscfs/kernfs/kernfs.h>
+
+#include <sys/intrio.h>
+#include <sys/kauth.h>
+#include <sys/conf.h>
 
 #include <uvm/uvm_extern.h>
 
@@ -188,6 +199,9 @@ struct pic softintr_pic = {
 	.pic_lock = __SIMPLELOCK_UNLOCKED,
 };
 
+static struct intrsource *io_interrupt_sources[NUM_IO_INTS];
+static __cpu_simple_lock_t io_interrupt_sources_lock = __SIMPLELOCK_UNLOCKED;
+
 #if NIOAPIC > 0 || NACPICA > 0
 static int intr_scan_bus(int, int, int *);
 #if NPCI > 0
@@ -413,6 +427,70 @@ intr_scan_bus(int bus, int pin, int *handle)
 }
 #endif
 
+static struct intrsource *
+intr_get_io_intrsource(int irq)
+{
+	return io_interrupt_sources[irq];
+}
+
+struct intrsource *
+intr_allocate_io_intrsource(int irq)
+{
+	struct intrsource *isp;
+	struct percpu_evcnt *pep;
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info *ci;
+
+	if (irq > NUM_IO_INTS)
+		return NULL;
+
+	if (io_interrupt_sources[irq] != NULL) {
+		return NULL;
+	}
+
+	isp = kmem_zalloc(sizeof(*isp), KM_NOSLEEP);
+	if (isp == NULL) {
+		return NULL;
+	}
+
+	pep = kmem_zalloc(sizeof(*pep) * ncpuonline, KM_NOSLEEP);
+	if (pep == NULL) {
+		kmem_free(isp, sizeof(*isp));
+		return NULL;
+	}
+	isp->is_saved_evcnt = pep;
+	for (CPU_INFO_FOREACH(cii, ci)) {
+		pep->cpuid = ci->ci_cpuid;
+		pep++;
+	}
+	isp->is_xname = NULL;
+
+	io_interrupt_sources[irq] = isp;
+
+	return isp;
+}
+
+void
+intr_free_io_intrsource(int irq)
+{
+	if (irq > NUM_IO_INTS)
+		return;
+
+	if (io_interrupt_sources[irq] == NULL) {
+		return;
+	}
+
+	kmem_free(io_interrupt_sources[irq]->is_xname,
+		  strlen(io_interrupt_sources[irq]->is_xname) + 1);
+	kmem_free(io_interrupt_sources[irq]->is_saved_evcnt,
+		  sizeof(*io_interrupt_sources[irq]->is_saved_evcnt) * ncpuonline);
+	kmem_free(io_interrupt_sources[irq],
+		  sizeof(*io_interrupt_sources[irq]));
+	io_interrupt_sources[irq] = NULL;
+
+	return;
+}
+
 static int
 intr_allocate_slot_cpu(struct cpu_info *ci, struct pic *pic, int pin,
 		       int *index)
@@ -445,14 +523,15 @@ intr_allocate_slot_cpu(struct cpu_info *ci, struct pic *pic, int pin,
 
 	isp = ci->ci_isources[slot];
 	if (isp == NULL) {
-		isp = kmem_zalloc(sizeof(*isp), KM_SLEEP);
-		if (isp == NULL) {
-			return ENOMEM;
-		}
+		__cpu_simple_lock(&io_interrupt_sources_lock);
+		isp = intr_get_io_intrsource(pin);
+		__cpu_simple_unlock(&io_interrupt_sources_lock);
+		KASSERT(isp != NULL);
 		snprintf(isp->is_evname, sizeof (isp->is_evname),
 		    "pin %d", pin);
 		evcnt_attach_dynamic(&isp->is_evcnt, EVCNT_TYPE_INTR, NULL,
 		    pic->pic_name, isp->is_evname);
+		isp->is_active_cpu = ci->ci_cpuid;
 		ci->ci_isources[slot] = isp;
 	}
 
@@ -559,7 +638,6 @@ intr_allocate_slot(struct pic *pic, int pin, int level,
 	}
 	if (idtvec == 0) {
 		evcnt_detach(&ci->ci_isources[slot]->is_evcnt);
-		kmem_free(ci->ci_isources[slot], sizeof(*(ci->ci_isources[slot])));
 		ci->ci_isources[slot] = NULL;
 		return EBUSY;
 	}
@@ -581,7 +659,6 @@ intr_source_free(struct cpu_info *ci, int slot, struct pic *pic, int idtvec)
 		return;
 	ci->ci_isources[slot] = NULL;
 	evcnt_detach(&isp->is_evcnt);
-	kmem_free(isp, sizeof(*isp));
 	ci->ci_isources[slot] = NULL;
 	if (pic != &i8259_pic)
 		idt_vec_free(idtvec);
@@ -626,6 +703,38 @@ intr_findpic(int num)
 	return NULL;
 }
 
+static int
+intr_append_intrsource_xname(struct intrsource *isp, const char *xname)
+{
+	size_t len;
+	char *new;
+
+	/* 16 is same as device_xname(struct device) */
+	KASSERT(strlen(xname) < 16);
+
+	if (isp->is_xname == NULL) {
+		len = strlen(xname);
+		new = kmem_zalloc(len + 1, KM_SLEEP);
+		if (new == NULL) {
+			return ENOMEM;
+		}
+		strncpy(new, xname, len);
+		isp->is_xname = new;
+		return 0;
+	}
+	else {
+		len = strlen(isp->is_xname) + strlen(xname) + 2;
+		new = kmem_zalloc(len + 1, KM_SLEEP);
+		if (new == NULL) {
+			return ENOMEM;
+		}
+		snprintf(new, len + 1, "%s, %s", isp->is_xname, xname);
+		kmem_free(isp->is_xname, strlen(isp->is_xname) + 1);
+		isp->is_xname = new;
+		return 0;
+	}
+}
+
 /*
  * Handle per-CPU component of interrupt establish.
  *
@@ -680,8 +789,9 @@ intr_establish_xcall(void *arg1, void *arg2)
 }
 
 void *
-intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
-	       int (*handler)(void *), void *arg, bool known_mpsafe)
+intr_establish_xname(int legacy_irq, struct pic *pic, int pin, int type, int level,
+		     int (*handler)(void *), void *arg, bool known_mpsafe,
+		     const char *xname)
 {
 	struct intrhand **p, *q, *ih;
 	struct cpu_info *ci;
@@ -706,10 +816,24 @@ intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
 		return NULL;
 	}
 
+	/* allocate intrsource pool, if not yet. */
+	__cpu_simple_lock(&io_interrupt_sources_lock);
+	if (intr_get_io_intrsource(pin) == NULL) {
+		if (intr_allocate_io_intrsource(pin) == NULL) {
+			__cpu_simple_unlock(&io_interrupt_sources_lock);
+			printf("%s: can't allocate io_intersource\n", __func__);
+			return NULL;
+		}
+	}
+	__cpu_simple_unlock(&io_interrupt_sources_lock);
+
 	mutex_enter(&cpu_lock);
 	error = intr_allocate_slot(pic, pin, level, &ci, &slot, &idt_vec);
 	if (error != 0) {
 		mutex_exit(&cpu_lock);
+		__cpu_simple_lock(&io_interrupt_sources_lock);
+		intr_free_io_intrsource(pin);
+		__cpu_simple_unlock(&io_interrupt_sources_lock);
 		kmem_free(ih, sizeof(*ih));
 		printf("failed to allocate interrupt slot for PIC %s pin %d\n",
 		    pic->pic_name, pin);
@@ -721,6 +845,9 @@ intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
 	if (source->is_handlers != NULL &&
 	    source->is_pic->pic_type != pic->pic_type) {
 		mutex_exit(&cpu_lock);
+		__cpu_simple_lock(&io_interrupt_sources_lock);
+		intr_free_io_intrsource(pin);
+		__cpu_simple_unlock(&io_interrupt_sources_lock);
 		kmem_free(ih, sizeof(*ih));
 		printf("%s: can't share intr source between "
 		       "different PIC types (legacy_irq %d pin %d slot %d)\n",
@@ -730,6 +857,18 @@ intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
 
 	source->is_pin = pin;
 	source->is_pic = pic;
+	error = intr_append_intrsource_xname(source, xname);
+	if (error) {
+		mutex_exit(&cpu_lock);
+		__cpu_simple_lock(&io_interrupt_sources_lock);
+		intr_free_io_intrsource(pin);
+		__cpu_simple_unlock(&io_interrupt_sources_lock);
+		kmem_free(ih, sizeof(*ih));
+		intr_source_free(ci, slot, pic, idt_vec);
+		printf("%s: pic %s pin %d: can't set device name: %s\n",
+		       __func__, pic->pic_name, pin, xname);
+		return NULL;
+	}
 
 	switch (source->is_type) {
 	case IST_NONE:
@@ -743,6 +882,9 @@ intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
 	case IST_PULSE:
 		if (type != IST_NONE) {
 			mutex_exit(&cpu_lock);
+			__cpu_simple_lock(&io_interrupt_sources_lock);
+			intr_free_io_intrsource(pin);
+			__cpu_simple_unlock(&io_interrupt_sources_lock);
 			kmem_free(ih, sizeof(*ih));
 			intr_source_free(ci, slot, pic, idt_vec);
 			printf("%s: pic %s pin %d: can't share "
@@ -818,6 +960,14 @@ intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
 	return (ih);
 }
 
+void *
+intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
+	       int (*handler)(void *), void *arg, bool known_mpsafe)
+{
+	return intr_establish_xname(legacy_irq, pic, pin, type,
+	    level, handler, arg, known_mpsafe, "unknown");
+}
+
 /*
  * Called on bound CPU to handle intr_disestablish().
  *
@@ -883,6 +1033,20 @@ intr_disestablish_xcall(void *arg1, void *arg2)
 #endif
 }
 
+static int
+intr_num_handlers(struct intrsource *isp)
+{
+	int num = 0;
+
+	struct intrhand *ih;
+
+	for (ih = isp->is_handlers; ih != NULL; ih = ih->ih_next) {
+		num++;
+	}
+
+	return num;
+}
+
 /*
  * Deregister an interrupt handler.
  */
@@ -891,6 +1055,8 @@ intr_disestablish(struct intrhand *ih)
 {
 	struct cpu_info *ci;
 	uint64_t where;
+	int irq;
+	struct intrsource *isp;
 
 	/*
 	 * Count the removal for load balancing.
@@ -900,6 +1066,7 @@ intr_disestablish(struct intrhand *ih)
 	mutex_enter(&cpu_lock);
 	ci = ih->ih_cpu;
 	(ci->ci_nintrhand)--;
+	irq = ci->ci_isources[ih->ih_slot]->is_pin;
 	KASSERT(ci->ci_nintrhand >= 0);
 	if (ci == curcpu() || !mp_online) {
 		intr_disestablish_xcall(ih, NULL);
@@ -908,6 +1075,13 @@ intr_disestablish(struct intrhand *ih)
 		xc_wait(where);
 	}	
 	mutex_exit(&cpu_lock);
+
+	__cpu_simple_lock(&io_interrupt_sources_lock);
+	isp = intr_get_io_intrsource(irq);
+	if (intr_num_handlers(isp) < 1) {
+		intr_free_io_intrsource(irq);
+	}
+	__cpu_simple_unlock(&io_interrupt_sources_lock);
 	kmem_free(ih, sizeof(*ih));
 }
 
@@ -1161,6 +1335,40 @@ softint_init_md(lwp_t *l, u_int level, uintptr_t *machdep)
 }
 
 static void
+intr_save_evcnt(struct intrsource *source, cpuid_t cpuid)
+{
+	uint64_t curcnt;
+	struct percpu_evcnt *pep;
+	int i;
+
+	curcnt = source->is_evcnt.ev_count;
+	pep = source->is_saved_evcnt;
+
+	for (i = 0; i < ncpuonline; i++) {
+		if (pep[i].cpuid == cpuid) {
+			pep[i].count = curcnt;
+			break;
+		}
+	}
+}
+
+static void
+intr_restore_evcnt(struct intrsource *source, cpuid_t cpuid)
+{
+	struct percpu_evcnt *pep;
+	int i;
+
+	pep = source->is_saved_evcnt;
+
+	for (i = 0; i < ncpuonline; i++) {
+		if (pep[i].cpuid == cpuid) {
+			source->is_evcnt.ev_count = pep[i].count;
+			break;
+		}
+	}
+}
+
+static void
 intr_redistribute_xc_t(void *arg1, void *arg2)
 {
 	struct cpu_info *ci;
@@ -1333,6 +1541,9 @@ intr_redistribute(struct cpu_info *oci)
 		nci->ci_nintrhand++;
 		ih->ih_cpu = nci;
 	}
+	intr_save_evcnt(isp, oci->ci_cpuid);
+	intr_restore_evcnt(isp, nci->ci_cpuid);
+	isp->is_active_cpu = nci->ci_cpuid;
 
 	return true;
 }
@@ -1367,3 +1578,498 @@ cpu_intr_count(struct cpu_info *ci)
 
 	return ci->ci_nintrhand;
 }
+
+static struct cpu_info *
+intr_find_cpuinfo(cpuid_t cpuid)
+{
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info *ci;
+
+	for (CPU_INFO_FOREACH(cii, ci)) {
+		if (ci->ci_cpuid == cpuid) {
+			return ci;
+		}
+	}
+
+	return NULL;
+}
+
+static int
+intr_find_unused_slot(struct cpu_info *ci, struct pic *pic, int *index)
+{
+	int slot, i;
+
+	KASSERT(mutex_owned(&cpu_lock));
+
+	slot = -1;
+	for (i = 0; i < MAX_INTR_SOURCES ; i++) {
+		if (ci->ci_isources[i] == NULL) {
+			slot = i;
+			break;
+		}
+	}
+	if (slot == -1) {
+		printf("cannot allocate ci_isources\n");
+		return EBUSY;
+	}
+
+	*index = slot;
+	return 0;
+}
+
+/* reuse same idt_vec */
+static void
+intr_activate_xcall(void *arg1, void *arg2)
+{
+	struct intrsource *source;
+	struct intrstub *stubp;
+	struct intrhand *ih;
+	struct cpu_info *ci;
+	int idt_vec;
+	int slot;
+	u_long psl;
+
+	ih = arg1;
+
+	KASSERT(ih->ih_cpu == curcpu() || !mp_online);
+
+	ci = ih->ih_cpu;
+	slot = ih->ih_slot;
+	source = ci->ci_isources[slot];
+	idt_vec = source->is_idtvec;
+
+	psl = x86_read_psl();
+	x86_disable_intr();
+
+	intr_calculatemasks(ci);
+
+	if (source->is_type == IST_LEVEL) {
+		stubp = &source->is_pic->pic_level_stubs[slot];
+	}
+	else {
+		stubp = &source->is_pic->pic_edge_stubs[slot];
+	}
+	source->is_resume = stubp->ist_resume;
+	source->is_recurse = stubp->ist_recurse;
+	setgate(&idt[idt_vec], stubp->ist_entry, 0, SDT_SYS386IGT,
+	    SEL_KPL, GSEL(GCODE_SEL, SEL_KPL));
+
+	x86_write_psl(psl);
+}
+
+static void
+intr_deactivate_xcall(void *arg1, void *arg2)
+{
+	struct intrhand *ih;
+	struct cpu_info *ci;
+	u_long psl;
+
+	ih = arg1;
+
+	KASSERT(ih->ih_cpu == curcpu() || !mp_online);
+
+	ci = ih->ih_cpu;
+
+	psl = x86_read_psl();
+	x86_disable_intr();
+
+	intr_calculatemasks(ci);
+
+	x86_write_psl(psl);
+}
+
+static int
+intr_set_affinity(int irq, cpuid_t cpuid)
+{
+	struct intrsource *isp;
+	struct intrhand *ih, *lih;
+	struct pic *pic;
+	int idt_vec;
+	struct cpu_info *oldci, *newci;
+	int oldslot, newslot;
+	int err;
+
+	isp = intr_get_io_intrsource(irq);
+	if (isp == NULL) {
+		printf("invalid irq: %d\n", irq);
+		return EINVAL;
+	}
+
+	newci = intr_find_cpuinfo(cpuid);
+	if (newci == NULL) {
+		printf("invalid cpuid: %ld\n", cpuid);
+		return EINVAL;
+	}
+	if ((newci->ci_schedstate.spc_flags & SPCF_NOINTR) != 0) {
+		printf("set nointr shield cpuid: %ld\n", cpuid);
+		return EINVAL;
+	}
+
+	pic = isp->is_pic;
+	if (pic == &i8259_pic) {
+		printf("i8259 pic does not support set_affinity\n");
+		return ENOTSUP;
+	}
+
+	ih = isp->is_handlers;
+	if (ih == NULL) {
+		printf("irq %d has no handler\n", irq);
+		return ENOENT;
+	}
+
+	oldci = ih->ih_cpu;
+	if (newci == oldci) {
+		/* nothing to do */
+		return 0;
+	}
+
+	oldslot = ih->ih_slot;
+	idt_vec = isp->is_idtvec;
+
+	mutex_enter(&cpu_lock);
+
+	err = intr_find_unused_slot(newci, pic, &newslot);
+	if (err) {
+		printf("failed to allocate interrupt slot for PIC %s irq %d\n",
+		       isp->is_pic->pic_name, irq);
+		goto out;
+	}
+
+	(*pic->pic_hwmask)(pic, irq); /* for ci_ipending check */
+
+	if (oldci->ci_ipending & ~(1 << oldslot)) {
+		(*pic->pic_hwunmask)(pic, irq);
+		printf("there are pending interrupts to irq on cpuid %ld: %d\n ",
+		       oldci->ci_cpuid, irq);
+		err = EBUSY;
+		goto out;
+	}
+
+	/* deactivate old interrupt setting */
+	oldci->ci_isources[oldslot] = NULL;
+	for (lih = ih; lih != NULL; lih = lih->ih_next) {
+		oldci->ci_nintrhand--;
+	}
+	if (oldci == curcpu() || !mp_online) {
+		intr_deactivate_xcall(ih, NULL);
+	}
+	else {
+		uint64_t where;
+		where = xc_unicast(0, intr_deactivate_xcall, ih,
+				   NULL, oldci);
+		xc_wait(where);
+	}
+	intr_save_evcnt(isp, oldci->ci_cpuid);
+	(*pic->pic_delroute)(pic, oldci, irq, idt_vec, isp->is_type);
+
+	/* activate new interrupt setting */
+	newci->ci_isources[newslot] = isp;
+	for (lih = ih; lih != NULL; lih = lih->ih_next) {
+		newci->ci_nintrhand++;
+		lih->ih_cpu = newci;
+		lih->ih_slot = newslot;
+	}
+	if (newci == curcpu() || !mp_online) {
+		intr_activate_xcall(ih, NULL);
+	}
+	else {
+		uint64_t where;
+		where = xc_unicast(0, intr_activate_xcall, ih,
+				   NULL, newci);
+		xc_wait(where);
+	}
+	intr_restore_evcnt(isp, newci->ci_cpuid);
+	isp->is_active_cpu = newci->ci_cpuid;
+	(*pic->pic_addroute)(pic, newci, irq, idt_vec, isp->is_type);
+
+	(*pic->pic_hwunmask)(pic, irq);
+	err = 0;
+out:
+	mutex_exit(&cpu_lock);
+
+	return err;
+}
+
+static int
+intr_loadcnt(char *buf, int length)
+{
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info *ci;
+
+	int ret;
+	char *buf_end;
+	int i;
+	int irq;
+	struct intrsource *isp;
+	struct percpu_evcnt pep;
+
+	if (buf == NULL) {
+		ret = EINVAL;
+		goto out;
+	}
+	if (length < 0) {
+		ret = EINVAL;
+		goto out;
+	}
+
+	buf_end = buf + length;
+
+#define FILL_BUF(cur, end, fmt, ...) do{				\
+		ret = snprintf(cur, end - cur, fmt, ## __VA_ARGS__);	\
+		if (ret < 0) {						\
+			ret = EIO;					\
+			goto out;					\
+		}							\
+		cur += ret;						\
+		if (cur > end) {					\
+			ret = EINVAL;					\
+			goto out;					\
+		}							\
+	}while(0)
+
+	/* print header */
+	FILL_BUF(buf, buf_end, " IRQ");
+	for (CPU_INFO_FOREACH(cii, ci)) {
+		char intr_enable;
+		if (ci->ci_schedstate.spc_flags & SPCF_NOINTR)
+			intr_enable = '-';
+		else
+			intr_enable = '+';
+
+		FILL_BUF(buf, buf_end, "\t  CPU#%02lu(%c)", ci->ci_cpuid,
+			 intr_enable);
+	}
+	*(buf++) = '\n';
+
+	for (irq = 0; irq < NUM_IO_INTS; irq++) {
+		isp = intr_get_io_intrsource(irq);
+		if (isp == NULL) {
+			continue;
+		}
+
+		FILL_BUF(buf, buf_end, " %3d", irq);
+		for (i = 0; i < ncpuonline; i++) {
+			pep = isp->is_saved_evcnt[i];
+			if (isp->is_active_cpu == pep.cpuid) {
+				FILL_BUF(buf, buf_end, "\t%8" PRIu64 "*", isp->is_evcnt.ev_count);
+			}
+			else {
+				FILL_BUF(buf, buf_end, "\t%8" PRIu64, pep.count);
+			}
+		}
+		FILL_BUF(buf, buf_end, "\t%s", isp->is_xname);
+		*(buf++) = '\n';
+	}
+
+	*(buf++) = '\0';
+
+	ret = 0;
+out:
+	return ret;
+
+#undef FILL_BUF
+}
+
+void	intrctlattach(int);
+
+dev_type_ioctl(intrctl_ioctl);
+
+const struct cdevsw intrctl_cdevsw = {
+	.d_open = nullopen,
+	.d_close = nullclose,
+	.d_read = nullread,
+	.d_write = nullwrite,
+	.d_ioctl = intrctl_ioctl,
+	.d_stop = nullstop,
+	.d_tty = notty,
+	.d_poll = nopoll,
+	.d_mmap = nommap,
+	.d_kqfilter = nokqfilter,
+	.d_discard = nodiscard,
+	.d_flag = D_OTHER | D_MPSAFE
+};
+
+void
+intrctlattach(int dummy)
+{
+}
+
+#define UNSET_NOINTR_SHIELD	0
+#define SET_NOINTR_SHIELD	1
+
+static void
+intr_shield_xcall(void *arg1, void *arg2)
+{
+	struct cpu_info *ci;
+	struct schedstate_percpu *spc;
+	int shield;
+	int s;
+
+	ci = arg1;
+	shield = (int)(intptr_t)arg2;
+
+	spc = &ci->ci_schedstate;
+	s = splsched();
+	if (shield == UNSET_NOINTR_SHIELD)
+		spc->spc_flags &= ~SPCF_NOINTR;
+	else if (shield == SET_NOINTR_SHIELD)
+		spc->spc_flags |= SPCF_NOINTR;
+	splx(s);
+
+	return;
+}
+
+static int
+intr_shield(cpuid_t cpuid, int shield)
+{
+	struct cpu_info *ci;
+	struct schedstate_percpu *spc;
+
+	ci = intr_find_cpuinfo(cpuid);
+	if (ci == NULL) {
+		return EINVAL;
+	}
+	spc = &ci->ci_schedstate;
+
+	if (shield == UNSET_NOINTR_SHIELD) {
+		if ((spc->spc_flags & SPCF_NOINTR) == 0)
+			return 0;
+	}
+	else if (shield == SET_NOINTR_SHIELD) {
+		if ((spc->spc_flags & SPCF_NOINTR) != 0)
+			return 0;
+	}
+
+	if (ci == curcpu() || !mp_online) {
+		intr_shield_xcall(ci, (void *)(intptr_t)shield);
+	}
+	else {
+		uint64_t where;
+		where = xc_unicast(0, intr_shield_xcall, ci,
+			(void *)(intptr_t)shield, ci);
+		xc_wait(where);
+	}
+
+	spc->spc_lastmod = time_second;
+	return 0;
+}
+
+static int
+intr_avert_one_intr(struct cpu_info *oldci, bool *do_next)
+{
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info  *newci, *lci;
+	struct intrsource *isp;
+	int i, error;
+	int irq;
+	cpuid_t cpuid;
+
+	for (i = 0; i < MAX_INTR_SOURCES; i++) {
+		isp = oldci->ci_isources[i];
+		if (isp == NULL) {
+			continue;
+		}
+
+		if (isp->is_pic->pic_type == PIC_IOAPIC) { /* XXXX need MSI */
+			break;
+		}
+	}
+	if (i == MAX_INTR_SOURCES) {
+		/* done averting interrupts, or already averted */
+		*do_next = false;
+		return 0;
+	}
+
+	newci = NULL;
+	for (CPU_INFO_FOREACH(cii, lci)) {
+		if ((lci->ci_schedstate.spc_flags & SPCF_NOINTR) == 0) {
+			newci = lci;
+			break;
+		}
+	}
+	if (newci == NULL) {
+		/* cannot avert interrupts */
+		printf("cannot find the cpu to affinity\n");
+		*do_next = false;
+		return ENOMEM;
+	}
+
+	irq = isp->is_pin;
+	cpuid = newci->ci_cpuid;
+	error = intr_set_affinity(irq, cpuid);
+	if (error) {
+		printf("cannot set affinity\n");
+		*do_next = false;
+		return error;
+	}
+
+	*do_next = true;
+	return 0;
+}
+
+static int
+intr_avert_all_intr(cpuid_t cpuid)
+{
+	struct cpu_info *ci;
+	bool do_next = true;
+	int error;
+
+	ci = intr_find_cpuinfo(cpuid);
+	if (ci == NULL) {
+		return EINVAL;
+	}
+
+	while (do_next) {
+		error = intr_avert_one_intr(ci, &do_next);
+		if (error)
+			break;
+	}
+	return error;
+}
+
+int
+intrctl_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
+{
+	int error = 0;
+	struct intr_set *iset;
+	cpuid_t cpuid;
+
+	switch (cmd) {
+	case IOC_INTR_LIST:
+		intr_loadcnt((char *)data, INTR_LIST_BUFSIZE);
+		break;
+
+	case IOC_INTR_AFFINITY:
+		iset = data;
+		error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_INTR,
+		    KAUTH_REQ_SYSTEM_INTR_AFFINITY, NULL, NULL, NULL);
+		if (error)
+			break;
+		error = intr_set_affinity(iset->irq, iset->cpuid);
+		break;
+	case IOC_INTR_INTR:
+		cpuid = *(cpuid_t *)data;
+		error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_CPU,
+		    KAUTH_REQ_SYSTEM_CPU_SETSTATE, NULL, NULL, NULL);
+		if (error)
+			break;
+		error = intr_shield(cpuid, UNSET_NOINTR_SHIELD);
+		break;
+	case IOC_INTR_NOINTR:
+		cpuid = *(cpuid_t *)data;
+		error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_CPU,
+		    KAUTH_REQ_SYSTEM_CPU_SETSTATE, NULL, NULL, NULL);
+		if (error)
+			break;
+		error = intr_shield(cpuid, SET_NOINTR_SHIELD);
+		if (error)
+			break;
+		error = intr_avert_all_intr(cpuid);
+		break;
+	default:
+		error = ENOTTY;
+		break;
+	}
+
+	return error;
+}
diff --git a/sys/secmodel/suser/secmodel_suser.c b/sys/secmodel/suser/secmodel_suser.c
index cbd6fcf..bbb15a0 100644
--- a/sys/secmodel/suser/secmodel_suser.c
+++ b/sys/secmodel/suser/secmodel_suser.c
@@ -443,6 +443,20 @@ secmodel_suser_system_cb(kauth_cred_t cred, kauth_action_t action,
 
 		break;
 
+	case KAUTH_SYSTEM_INTR:
+		switch (req) {
+		case KAUTH_REQ_SYSTEM_INTR_AFFINITY:
+			if (isroot)
+				result = KAUTH_RESULT_ALLOW;
+
+			break;
+
+		default:
+			break;
+		}
+
+		break;
+
 	default:
 		break;
 	}
diff --git a/sys/sys/intrio.h b/sys/sys/intrio.h
new file mode 100644
index 0000000..c8bcf93
--- /dev/null
+++ b/sys/sys/intrio.h
@@ -0,0 +1,20 @@
+
+#ifndef _SYS_INTRIO_H_
+#define _SYS_INTRIO_H_
+
+#include <sys/ioccom.h>
+#include <sys/types.h>
+
+#define INTR_LIST_BUFSIZE 4096
+
+struct intr_set {
+	int irq;
+	cpuid_t cpuid;
+};
+
+#define	IOC_INTR_LIST		_IOR('c', 0, char[INTR_LIST_BUFSIZE])
+#define	IOC_INTR_AFFINITY	_IOW('c', 1, struct intr_set)
+#define	IOC_INTR_INTR		_IOW('c', 2, cpuid_t)
+#define	IOC_INTR_NOINTR		_IOW('c', 3, cpuid_t)
+
+#endif /* !_SYS_INTRIO_H_ */
diff --git a/sys/sys/kauth.h b/sys/sys/kauth.h
index 6e53424..970afdc 100644
--- a/sys/sys/kauth.h
+++ b/sys/sys/kauth.h
@@ -111,6 +111,7 @@ enum {
 	KAUTH_SYSTEM_LFS,
 	KAUTH_SYSTEM_FS_EXTATTR,
 	KAUTH_SYSTEM_FS_SNAPSHOT,
+	KAUTH_SYSTEM_INTR,
 };
 
 /*
@@ -156,6 +157,7 @@ enum kauth_system_req {
 	KAUTH_REQ_SYSTEM_LFS_FCNTL,
 	KAUTH_REQ_SYSTEM_MOUNT_UMAP,
 	KAUTH_REQ_SYSTEM_MOUNT_DEVICE,
+	KAUTH_REQ_SYSTEM_INTR_AFFINITY,
 };
 
 /*
diff --git a/usr.sbin/intrctl/Makefile b/usr.sbin/intrctl/Makefile
new file mode 100644
index 0000000..b5769f3
--- /dev/null
+++ b/usr.sbin/intrctl/Makefile
@@ -0,0 +1,8 @@
+
+.include <bsd.own.mk>
+
+PROG=	intrctl
+MAN=	intrctl.8
+SRCS=	intrctl.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/intrctl/intrctl.8 b/usr.sbin/intrctl/intrctl.8
new file mode 100644
index 0000000..61bd22c
--- /dev/null
+++ b/usr.sbin/intrctl/intrctl.8
@@ -0,0 +1,46 @@
+.Dd August 19, 2014
+.Dt INTRCTL 8
+.Os
+.Sh NAME
+.Nm intrctl
+.Nd program to control interrupts
+.Sh SYNOPSIS
+.Nm intrctl
+.Ar command
+.Op Ar options
+.Sh DESCRIPTION
+The
+.Nm
+command can be used to control and inspect the state of intrerupts in the system.
+.Pp
+The first argument,
+.Ar command ,
+specifies the action to take.
+Valid commands are:
+.Bl -tag -width XofflineXcpunoX
+.It list
+for each IRQ in the system, display interrupt counts per CPU.
+.It affinity Fl c Ar cpuid Fl i Ar irq
+set affinity
+.Ar irq
+interrupt to
+.Ar cpuid.
+.It intr Fl c Ar cpuid
+enable interrupts to set affinity to
+.Ar cpuid.
+.It nointr Fl c Ar cpuid
+disable interrupts to set affinity to
+.Ar cpuid.
+.El
+.Sh FILES
+.Bl -tag -width /dev/intrctl -compact
+.It Pa /dev/intrctl
+control interrupt
+.Ed
+.Sh SEE ALSO
+.Xr cpuctl 8
+.Sh HISTORY
+The
+.Nm
+command first appeared in
+.Nx X.X .
diff --git a/usr.sbin/intrctl/intrctl.c b/usr.sbin/intrctl/intrctl.c
new file mode 100644
index 0000000..bd1183d
--- /dev/null
+++ b/usr.sbin/intrctl/intrctl.c
@@ -0,0 +1,186 @@
+
+#include <sys/ioctl.h>
+#include <sys/intrio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+__dead static void	usage(void);
+
+static int	fd;
+int		verbose;
+
+static void	intr_list(int, char **);
+static void	intr_affinity(int, char **);
+static void	intr_intr(int, char **);
+static void	intr_nointr(int, char **);
+
+static struct cmdtab {
+	const char	*label;
+	void	(*func)(int, char **);
+} const intr_cmdtab[] = {
+	{ "list", intr_list },
+	{ "affinity", intr_affinity },
+	{ "intr", intr_intr },
+	{ "nointr", intr_nointr },
+	{ NULL, NULL },
+};
+
+int
+main(int argc, char **argv)
+{
+	const struct cmdtab *ct;
+	char *cmdname;
+
+	if (argc < 2)
+		usage();
+
+	cmdname = argv[1];
+	argv += 1;
+	argc -= 1;
+
+	for (ct = intr_cmdtab; ct->label != NULL; ct++) {
+		if (strcmp(cmdname, ct->label) == 0) {
+			break;
+		}
+	}
+
+	if (ct->label == NULL)
+		errx(EXIT_FAILURE, "unknown command ``%s''", cmdname);
+
+	if ((fd = open(_PATH_INTRCTL, O_RDWR)) < 0)
+		err(EXIT_FAILURE, _PATH_INTRCTL);
+	(*ct->func)(argc, argv);
+	close(fd);
+	exit(EXIT_SUCCESS);
+	/* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+	const char *progname = getprogname();
+
+	fprintf(stderr, "usage: %s list\n", progname);
+	fprintf(stderr, "       %s affinity -i irq -c cpuno\n", progname);
+	fprintf(stderr, "       %s intr -c cpuno\n", progname);
+	fprintf(stderr, "       %s nointr -c cpuno\n", progname);
+	exit(EXIT_FAILURE);
+	/* NOTREACHED */
+}
+
+static void
+intr_list(int argc, char **argv)
+{
+	char *buf;
+	int error;
+
+	buf = malloc(INTR_LIST_BUFSIZE);
+	if (buf == NULL) {
+		err(EXIT_FAILURE, "malloc");
+	}
+
+	error = ioctl(fd, IOC_INTR_LIST, buf);
+	if (error < 0) {
+		err(EXIT_FAILURE, "IOC_INTR_LIST");
+	}
+
+	printf("%s", buf);
+	free(buf);
+}
+
+static void
+intr_affinity(int argc, char **argv)
+{
+	struct intr_set iset;
+	int ch;
+	int error;
+
+	iset.irq = -1;
+	iset.cpuid = ULONG_MAX;
+
+	while ((ch = getopt(argc, argv, "c:i:")) != -1) {
+		switch (ch) {
+		case 'c':
+			iset.cpuid = strtoul(optarg, NULL, 10);
+			break;
+		case 'i':
+			iset.irq = atoi(optarg);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (iset.irq == -1 || iset.cpuid == ULONG_MAX)
+		usage();
+
+	error = ioctl(fd, IOC_INTR_AFFINITY, &iset);
+	if (error < 0) {
+		err(EXIT_FAILURE, "IOC_INTR_AFFINITY");
+	}
+}
+
+static void
+intr_intr(int argc, char **argv)
+{
+	cpuid_t cpuid;
+	int ch;
+	int error;
+
+	cpuid = ULONG_MAX;
+
+	while ((ch = getopt(argc, argv, "c:")) != -1) {
+		switch (ch) {
+		case 'c':
+			cpuid = strtoul(optarg, NULL, 10);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (cpuid == ULONG_MAX)
+		usage();
+
+	error = ioctl(fd, IOC_INTR_INTR, &cpuid);
+	if (error < 0) {
+		err(EXIT_FAILURE, "IOC_INTR_INTR");
+	}
+}
+
+static void
+intr_nointr(int argc, char **argv)
+{
+	cpuid_t cpuid;
+	int ch;
+	int error;
+
+	cpuid = ULONG_MAX;
+
+	while ((ch = getopt(argc, argv, "c:")) != -1) {
+		switch (ch) {
+		case 'c':
+			cpuid = strtoul(optarg, NULL, 10);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (cpuid == ULONG_MAX)
+		usage();
+
+	error = ioctl(fd, IOC_INTR_NOINTR, &cpuid);
+	if (error < 0) {
+		err(EXIT_FAILURE, "IOC_INTR_NOINTR");
+	}
+}
+
