diff --git a/distrib/sets/lists/base/mi b/distrib/sets/lists/base/mi
index 1311e5c..29af14e 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 a37fd43..1c81ee0 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3194,6 +3194,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 641eb3d..b9648b6 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 3bb9a5e..6764b92 100644
--- a/distrib/sets/lists/man/mi
+++ b/distrib/sets/lists/man/mi
@@ -2531,6 +2531,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
@@ -5393,6 +5394,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
@@ -8399,6 +8401,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..b108598 100644
--- a/etc/MAKEDEV.tmpl
+++ b/etc/MAKEDEV.tmpl
@@ -247,6 +247,7 @@
 #	hil	HP300 HIL input devices
 #	icp	ICP-Vortex/Intel RAID control interface
 #	iic*	IIC bus device
+#	intrctl	interrupt control
 #	io	x86 IOPL access for COMPAT_10, COMPAT_FREEBSD
 #	iop*	I2O IOP control interface
 #	ipl	IP Filter
@@ -825,7 +826,7 @@ all)
 	makedev bpf npf
 	makedev tun0 tun1 tun2 tun3
 	makedev ipl pf crypto random
-	makedev lockstat clockctl cpuctl
+	makedev lockstat clockctl cpuctl intrctl
 	makedev atabus0 atabus1 atabus2 atabus3 atabus4 atabus5 atabus6 atabus7
 	makedev tap tap0 tap1 tap2 tap3
 	makedev gpio gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7
@@ -1446,6 +1447,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/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/x86/include/intr.h b/sys/arch/x86/include/intr.h
index 8484177..a11b6dc 100644
--- a/sys/arch/x86/include/intr.h
+++ b/sys/arch/x86/include/intr.h
@@ -42,6 +42,8 @@
 #endif
 
 #include <sys/evcnt.h>
+#include <sys/intrio.h>
+#include <sys/queue.h>
 #include <machine/intrdefs.h>
 
 #ifndef _LOCORE
@@ -71,6 +73,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 +87,17 @@ 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_intrid[INTRID_LEN + 1];	/* intrid created by create_intrid() */
+	char *is_xname;			/* device name */
+	cpuid_t is_active_cpu;		/* active cpuid */
+	struct percpu_evcnt *is_saved_evcnt;	/* interrupt count of deactivated cpus */
+	LIST_ENTRY(intrsource) is_list;	/* link of intrsources */
 };
 
 #define IS_LEGACY	0x0001		/* legacy ISA irq source */
@@ -173,6 +185,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 *);
@@ -182,6 +195,9 @@ int intr_find_mpmapping(int, int, int *);
 struct pic *intr_findpic(int);
 void intr_printconfig(void);
 
+struct intrsource *intr_allocate_io_intrsource(const char *);
+void intr_free_io_intrsource(const char *);
+
 int x86_send_ipi(struct cpu_info *, int);
 void x86_broadcast_ipi(int);
 void x86_ipi_handler(void);
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..a57b659 100644
--- a/sys/arch/x86/x86/intr.c
+++ b/sys/arch/x86/x86/intr.c
@@ -152,6 +152,10 @@ __KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.77 2014/05/20 03:24:19 ozaki-r Exp $");
 #include <sys/atomic.h>
 #include <sys/xcall.h>
 
+#include <sys/intrio.h>
+#include <sys/kauth.h>
+#include <sys/conf.h>
+
 #include <uvm/uvm_extern.h>
 
 #include <machine/i8259.h>
@@ -188,6 +192,9 @@ struct pic softintr_pic = {
 	.pic_lock = __SIMPLELOCK_UNLOCKED,
 };
 
+static LIST_HEAD(, intrsource) io_interrupt_sources =
+	LIST_HEAD_INITIALIZER(io_interrupt_sources);
+
 #if NIOAPIC > 0 || NACPICA > 0
 static int intr_scan_bus(int, int, int *);
 #if NPCI > 0
@@ -413,9 +420,101 @@ intr_scan_bus(int bus, int pin, int *handle)
 }
 #endif
 
+static const char *
+create_intrid(int pin, struct pic *pic, char *buf, size_t len)
+{
+	int ih;
+
+	ih = ((pic->pic_apicid << APIC_INT_APIC_SHIFT) & APIC_INT_APIC_MASK) |
+		((pin << APIC_INT_PIN_SHIFT) & APIC_INT_PIN_MASK);
+	if (pic->pic_type == PIC_IOAPIC) {
+		ih |= APIC_INT_VIA_APIC;
+	}
+	KASSERT(ih != 0);
+
+	return intr_string(ih, buf, len);
+}
+
+static struct intrsource *
+intr_get_io_intrsource(const char *intrid)
+{
+	struct intrsource *isp;
+
+	LIST_FOREACH(isp, &io_interrupt_sources, is_list) {
+		KASSERT(isp->is_intrid != NULL);
+		if (strncmp(intrid, isp->is_intrid, INTRID_LEN) == 0) {
+			return isp;
+		}
+	}
+	return NULL;
+}
+
+struct intrsource *
+intr_allocate_io_intrsource(const char *intrid)
+{
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info *ci;
+	struct intrsource *isp;
+	struct percpu_evcnt *pep;
+
+	if (intrid == 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;
+	strncpy(isp->is_intrid, intrid, sizeof(isp->is_intrid));
+
+	LIST_INSERT_HEAD(&io_interrupt_sources, isp, is_list);
+
+	return isp;
+}
+
+static void
+intr_free_io_intrsource_direct(struct intrsource *isp)
+{
+	KASSERT(mutex_owned(&cpu_lock));
+
+	LIST_REMOVE(isp, is_list);
+
+	evcnt_detach(&isp->is_evcnt);
+	kmem_free(isp->is_xname, strlen(isp->is_xname) + 1);
+	kmem_free(isp->is_saved_evcnt,
+	    sizeof(*(isp->is_saved_evcnt)) * ncpuonline);
+	kmem_free(isp, sizeof(*isp));
+}
+
+void
+intr_free_io_intrsource(const char *intrid)
+{
+	struct intrsource *isp;
+
+	if (intrid == NULL)
+		return;
+
+	if ((isp = intr_get_io_intrsource(intrid)) == NULL) {
+		return;
+	}
+
+	intr_free_io_intrsource_direct(isp);
+}
+
 static int
 intr_allocate_slot_cpu(struct cpu_info *ci, struct pic *pic, int pin,
-		       int *index)
+		       int *index, struct intrsource *chained)
 {
 	int slot, i;
 	struct intrsource *isp;
@@ -445,14 +544,13 @@ 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;
-		}
+		isp = chained;
+		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;
 	}
 
@@ -465,7 +563,8 @@ intr_allocate_slot_cpu(struct cpu_info *ci, struct pic *pic, int pin,
  */
 static int __noinline
 intr_allocate_slot(struct pic *pic, int pin, int level,
-		   struct cpu_info **cip, int *index, int *idt_slot)
+		   struct cpu_info **cip, int *index, int *idt_slot,
+		   struct intrsource *chained)
 {
 	CPU_INFO_ITERATOR cii;
 	struct cpu_info *ci, *lci;
@@ -504,7 +603,7 @@ intr_allocate_slot(struct pic *pic, int pin, int level,
 		 * Must be directed to BP.
 		 */
 		ci = &cpu_info_primary;
-		error = intr_allocate_slot_cpu(ci, pic, pin, &slot);
+		error = intr_allocate_slot_cpu(ci, pic, pin, &slot, chained);
 	} else {
 		/*
 		 * Find least loaded AP/BP and try to allocate there.
@@ -524,7 +623,7 @@ intr_allocate_slot(struct pic *pic, int pin, int level,
 #endif
 		}
 		KASSERT(ci != NULL);
-		error = intr_allocate_slot_cpu(ci, pic, pin, &slot);
+		error = intr_allocate_slot_cpu(ci, pic, pin, &slot, chained);
 
 		/*
 		 * If that did not work, allocate anywhere.
@@ -536,7 +635,7 @@ intr_allocate_slot(struct pic *pic, int pin, int level,
 					continue;
 				}
 				error = intr_allocate_slot_cpu(ci, pic,
-				    pin, &slot);
+				    pin, &slot, chained);
 				if (error == 0) {
 					break;
 				}
@@ -559,7 +658,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;
 	}
@@ -580,9 +678,6 @@ intr_source_free(struct cpu_info *ci, int slot, struct pic *pic, int idtvec)
 	if (isp->is_handlers != NULL)
 		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 +721,37 @@ intr_findpic(int num)
 	return NULL;
 }
 
+static int
+intr_append_intrsource_xname(struct intrsource *isp, const char *xname)
+{
+	char *new;
+	size_t len;
+
+	/* 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,17 +806,20 @@ 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;
 	int slot, error, idt_vec;
-	struct intrsource *source;
+	struct intrsource *chained, *source;
 #ifdef MULTIPROCESSOR
 	bool mpsafe = (known_mpsafe || level != IPL_VM);
 #endif /* MULTIPROCESSOR */
 	uint64_t where;
+	const char *intrstr;
+	char intrstr_buf[INTRID_LEN + 1];
 
 #ifdef DIAGNOSTIC
 	if (legacy_irq != -1 && (legacy_irq < 0 || legacy_irq > 15))
@@ -706,9 +835,25 @@ intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
 		return NULL;
 	}
 
+	intrstr = create_intrid(pin, pic, intrstr_buf, sizeof(intrstr_buf));
+	KASSERT(intrstr != NULL);
+
 	mutex_enter(&cpu_lock);
-	error = intr_allocate_slot(pic, pin, level, &ci, &slot, &idt_vec);
+
+	/* allocate intrsource pool, if not yet. */
+	chained = intr_get_io_intrsource(intrstr);
+	if (chained == NULL) {
+		chained = intr_allocate_io_intrsource(intrstr);
+		if (chained == NULL) {
+			printf("%s: can't allocate io_intersource\n", __func__);
+			return NULL;
+		}
+	}
+
+	error = intr_allocate_slot(pic, pin, level, &ci, &slot, &idt_vec,
+	    chained);
 	if (error != 0) {
+		intr_free_io_intrsource_direct(chained);
 		mutex_exit(&cpu_lock);
 		kmem_free(ih, sizeof(*ih));
 		printf("failed to allocate interrupt slot for PIC %s pin %d\n",
@@ -720,6 +865,7 @@ 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) {
+		intr_free_io_intrsource_direct(chained);
 		mutex_exit(&cpu_lock);
 		kmem_free(ih, sizeof(*ih));
 		printf("%s: can't share intr source between "
@@ -730,6 +876,16 @@ 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) {
+		intr_source_free(ci, slot, pic, idt_vec);
+		intr_free_io_intrsource_direct(chained);
+		mutex_exit(&cpu_lock);
+		kmem_free(ih, sizeof(*ih));
+		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:
@@ -742,9 +898,10 @@ intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
 		/* FALLTHROUGH */
 	case IST_PULSE:
 		if (type != IST_NONE) {
+			intr_source_free(ci, slot, pic, idt_vec);
+			intr_free_io_intrsource_direct(chained);
 			mutex_exit(&cpu_lock);
 			kmem_free(ih, sizeof(*ih));
-			intr_source_free(ci, slot, pic, idt_vec);
 			printf("%s: pic %s pin %d: can't share "
 			       "type %d with %d\n",
 				__func__, pic->pic_name, pin,
@@ -818,6 +975,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 +1048,19 @@ intr_disestablish_xcall(void *arg1, void *arg2)
 #endif
 }
 
+static int
+intr_num_handlers(struct intrsource *isp)
+{
+	struct intrhand *ih;
+	int num;
+
+	num = 0;
+	for (ih = isp->is_handlers; ih != NULL; ih = ih->ih_next)
+		num++;
+
+	return num;
+}
+
 /*
  * Deregister an interrupt handler.
  */
@@ -890,6 +1068,7 @@ void
 intr_disestablish(struct intrhand *ih)
 {
 	struct cpu_info *ci;
+	struct intrsource *isp;
 	uint64_t where;
 
 	/*
@@ -901,13 +1080,18 @@ intr_disestablish(struct intrhand *ih)
 	ci = ih->ih_cpu;
 	(ci->ci_nintrhand)--;
 	KASSERT(ci->ci_nintrhand >= 0);
+	isp = ci->ci_isources[ih->ih_slot];
 	if (ci == curcpu() || !mp_online) {
 		intr_disestablish_xcall(ih, NULL);
 	} else {
 		where = xc_unicast(0, intr_disestablish_xcall, ih, NULL, ci);
 		xc_wait(where);
 	}	
+	if (intr_num_handlers(isp) < 1) {
+		intr_free_io_intrsource_direct(isp);
+	}
 	mutex_exit(&cpu_lock);
+
 	kmem_free(ih, sizeof(*ih));
 }
 
@@ -1161,6 +1345,40 @@ softint_init_md(lwp_t *l, u_int level, uintptr_t *machdep)
 }
 
 static void
+intr_save_evcnt(struct intrsource *source, cpuid_t cpuid)
+{
+	struct percpu_evcnt *pep;
+	uint64_t curcnt;
+	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 +1551,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 +1588,367 @@ cpu_intr_count(struct cpu_info *ci)
 
 	return ci->ci_nintrhand;
 }
+
+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 cpu_info *ci;
+	struct intrsource *source;
+	struct intrstub *stubp;
+	struct intrhand *ih;
+	u_long psl;
+	int idt_vec;
+	int slot;
+
+	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 cpu_info *ci;
+	struct intrhand *ih, *lih;
+	u_long psl;
+	int slot;
+
+	ih = arg1;
+
+	KASSERT(ih->ih_cpu == curcpu() || !mp_online);
+
+	ci = ih->ih_cpu;
+	slot = ih->ih_slot;
+
+	psl = x86_read_psl();
+	x86_disable_intr();
+
+	ci->ci_isources[slot] = NULL;
+	for (lih = ih; lih != NULL; lih = lih->ih_next) {
+		ci->ci_nintrhand--;
+	}
+
+	intr_calculatemasks(ci);
+
+	x86_write_psl(psl);
+}
+
+static void
+intr_get_affinity(void *ich, kcpuset_t *cpuset)
+{
+	struct cpu_info *ci;
+	struct intrsource *isp;
+
+	if (ich == NULL) {
+		kcpuset_zero(cpuset);
+		return;
+	}
+
+	isp = ich;
+	ci = isp->is_handlers->ih_cpu;
+	if (ci == NULL) {
+		kcpuset_zero(cpuset);
+		return;
+	}
+
+	kcpuset_set(cpuset, cpu_index(ci));
+	return;
+}
+
+static int
+intr_set_affinity(void *ich, const kcpuset_t *cpuset)
+{
+	struct cpu_info *oldci, *newci;
+	struct intrsource *isp;
+	struct intrhand *ih, *lih;
+	struct pic *pic;
+	u_int cpu_idx;
+	int idt_vec;
+	int oldslot, newslot;
+	int err;
+	int pin;
+
+	KASSERT(mutex_owned(&cpu_lock));
+
+	/*
+	 * logical destination mode is not supported, use lowest index cpu.
+	 */
+	cpu_idx = kcpuset_ffs(cpuset) - 1;
+	newci = cpu_lookup(cpu_idx);
+	if (newci == NULL) {
+		printf("invalid cpu index: %u\n", cpu_idx);
+		return EINVAL;
+	}
+	if ((newci->ci_schedstate.spc_flags & SPCF_NOINTR) != 0) {
+		printf("the cpu is set nointr shield. index:%u\n", cpu_idx);
+		return EINVAL;
+	}
+
+	isp = ich;
+	if (isp == NULL) {
+		printf("invalid intrctl handler\n");
+		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;
+	oldci = ih->ih_cpu;
+	if (newci == oldci) /* nothing to do */
+		return 0;
+
+	oldslot = ih->ih_slot;
+	idt_vec = isp->is_idtvec;
+
+	err = intr_find_unused_slot(newci, pic, &newslot);
+	if (err) {
+		printf("failed to allocate interrupt slot for PIC %s intrid %s\n",
+		       isp->is_pic->pic_name, isp->is_intrid);
+		return err;
+	}
+
+	pin = isp->is_pin;
+	(*pic->pic_hwmask)(pic, pin); /* for ci_ipending check */
+	if (oldci->ci_ipending & (1 << oldslot)) {
+		(*pic->pic_hwunmask)(pic, pin);
+		printf("pin %d on cpuid %ld has pending interrupts.\n",
+		    pin, oldci->ci_cpuid);
+		return EBUSY;
+	}
+
+	/* deactivate old interrupt setting */
+	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, pin, 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, pin, idt_vec, isp->is_type);
+
+	(*pic->pic_hwunmask)(pic, pin);
+
+	return err;
+}
+
+static bool
+intr_is_affinity_intrsource(struct intrsource *isp, const kcpuset_t *cpuset)
+{
+	struct cpu_info *ci;
+
+	ci = isp->is_handlers->ih_cpu;
+	KASSERT(ci != NULL);
+
+	return kcpuset_isset(cpuset, cpu_index(ci));
+}
+
+void *
+intr_get_handler(const char *intrid)
+{
+	KASSERT(mutex_owned(&cpu_lock));
+
+	return intr_get_io_intrsource(intrid);
+}
+
+uint64_t
+intr_get_count(void *ich, u_int cpu_idx)
+{
+	struct cpu_info *ci;
+	struct intrsource *isp;
+	struct percpu_evcnt pep;
+	cpuid_t cpuid;
+	int i;
+
+	ci = cpu_lookup(cpu_idx);
+	cpuid = ci->ci_cpuid;
+
+	isp = ich;
+	for (i = 0; i < ncpuonline; i++) {
+		pep = isp->is_saved_evcnt[i];
+		if (cpuid == pep.cpuid) {
+			if (isp->is_active_cpu == pep.cpuid) {
+				return isp->is_evcnt.ev_count;
+			} else {
+				return pep.count;
+			}
+		}
+	}
+	return 0;
+}
+
+void
+intr_get_assigned(void *ich, kcpuset_t *cpuset)
+{
+	struct cpu_info *ci;
+	struct intrsource *isp;
+
+	isp = ich;
+	ci = isp->is_handlers->ih_cpu;
+	KASSERT(ci != NULL);
+
+	kcpuset_zero(cpuset);
+	kcpuset_set(cpuset, cpu_index(ci));
+}
+
+void
+intr_get_available(kcpuset_t *cpuset)
+{
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info *ci;
+
+	kcpuset_zero(cpuset);
+	for (CPU_INFO_FOREACH(cii, ci)) {
+		if ((ci->ci_schedstate.spc_flags & SPCF_NOINTR) == 0) {
+			kcpuset_set(cpuset, cpu_index(ci));
+		}
+	}
+}
+
+char *
+intr_get_devname(void *ich)
+{
+	struct intrsource *isp;
+
+	isp = ich;
+	return isp->is_xname;
+}
+
+int
+intr_distribute(void *ich, const kcpuset_t *newset, kcpuset_t *oldset)
+{
+	if (oldset != NULL) {
+		intr_get_affinity(ich, oldset);
+	}
+
+	return intr_set_affinity(ich, newset);
+}
+
+int
+intr_construct_intrids(const kcpuset_t *cpuset, char ***intrids, int *count)
+{
+	struct intrsource *isp;
+	char **ids;
+	int i;
+
+	if (count == NULL)
+		return EINVAL;
+
+	if (kcpuset_iszero(cpuset))
+		return 0;
+
+	*count = 0;
+	LIST_FOREACH(isp, &io_interrupt_sources, is_list) {
+		if (intr_is_affinity_intrsource(isp, cpuset))
+			(*count)++;
+	}
+	if (*count == 0)
+		return 0;
+
+	ids = kmem_zalloc(sizeof(char*) * (*count), KM_SLEEP);
+	if (ids == NULL)
+		return ENOMEM;
+
+	i = 0;
+	LIST_FOREACH(isp, &io_interrupt_sources, is_list) {
+		if (!intr_is_affinity_intrsource(isp, cpuset))
+			continue;
+
+		ids[i] = kmem_zalloc(INTRID_LEN + 1, KM_NOSLEEP);
+		if (ids[i] == NULL) {
+			int j;
+			for (j = i - 1; j >= 0; i--) {
+				kmem_free(ids[i], INTRID_LEN + 1);
+			}
+			kmem_free(ids, sizeof(char*) * (*count));
+			return ENOMEM;
+		}
+
+		strncpy(ids[i], isp->is_intrid, INTRID_LEN + 1);
+		i++;
+	}
+
+	*intrids = ids;
+	return 0;
+}
+
+void
+intr_destruct_intrids(char **intrids, int count)
+{
+	int i;
+
+	for (i = 0; i < count; i++)
+		kmem_free(intrids[i], INTRID_LEN + 1);
+
+	kmem_free(intrids, sizeof(char*) * count);
+}
diff --git a/sys/conf/files b/sys/conf/files
index ac89cac..a430099 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1431,6 +1431,9 @@ file	kern/kern_drvctl.c		drvctl		needs-flag
 defpseudo cpuctl
 defflag				CPU_UCODE: firmload
 
+# interrupt control
+defpseudo intrctl
+
 # pass-to-userspace transporter
 defpseudo putter
 file	dev/putter/putter.c		putter
@@ -1617,6 +1620,7 @@ file	kern/subr_exec_fd.c
 file	kern/subr_extent.c
 file	kern/subr_hash.c
 file	kern/subr_humanize.c
+file	kern/subr_intr.c
 file	kern/subr_iostat.c
 file	kern/subr_ipi.c
 file	kern/subr_kcpuset.c
diff --git a/sys/conf/majors b/sys/conf/majors
index 1617c56..2307e67 100644
--- a/sys/conf/majors
+++ b/sys/conf/majors
@@ -55,3 +55,4 @@ device-major seeprom   char 206		   seeprom
 device-major dtrace    char 207		   dtrace
 device-major spiflash  char 208 block 208  spiflash
 device-major lua       char 209            lua
+device-major intrctl   char 210				single
diff --git a/sys/conf/std b/sys/conf/std
index 06d4ce5..2540aac 100644
--- a/sys/conf/std
+++ b/sys/conf/std
@@ -28,6 +28,8 @@ options	SCHED_4BSD
 
 pseudo-device	cpuctl
 
+pseudo-device	intrctl
+
 #
 # Kernel entropy pool and random-number generator pseudodevice.
 # The pseudodevice might stop being "std" when the two are torn
diff --git a/sys/kern/kern_stub.c b/sys/kern/kern_stub.c
index 79dbaa6..cf8cd47 100644
--- a/sys/kern/kern_stub.c
+++ b/sys/kern/kern_stub.c
@@ -147,6 +147,15 @@ __weak_alias(userconf_prompt, voidop);
 
 __weak_alias(kobj_renamespace, nullop);
 
+__weak_alias(intr_get_handler, eopnotsupp);
+__weak_alias(intr_get_counts, eopnotsupp);
+__weak_alias(intr_get_assigned, eopnotsupp);
+__weak_alias(intr_get_available, eopnotsupp);
+__weak_alias(intr_get_devname, eopnotsupp);
+__weak_alias(intr_distribute, eopnotsupp);
+__weak_alias(intr_construct_intrids, eopnotsupp);
+__weak_alias(intr_destruct_intrids, eopnotsupp);
+
 /*
  * Scheduler activations system calls.  These need to remain until libc's
  * major version is bumped.
diff --git a/sys/kern/subr_intr.c b/sys/kern/subr_intr.c
new file mode 100644
index 0000000..c87d8cd
--- /dev/null
+++ b/sys/kern/subr_intr.c
@@ -0,0 +1,377 @@
+/*	$NetBSD$	*/
+
+/*
+ * Copyright (c) 2014 Internet Initiative Japan Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/errno.h>
+#include <sys/cpu.h>
+#include <sys/intr.h>
+#include <sys/kcpuset.h>
+#include <sys/proc.h>
+#include <sys/xcall.h>
+
+#include <sys/conf.h>
+#include <sys/intrio.h>
+#include <sys/kauth.h>
+
+#include <machine/limits.h>
+
+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)
+{
+}
+
+static int
+intrctl_list(void *data, int length)
+{
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info *ci;
+	kcpuset_t *avail, *assigned;
+	void *ich;
+	char *buf, *buf_end;
+	char **ids;
+	uint64_t intr_count;
+	u_int cpu_idx;
+	int intr_idx, nids;
+	int ret;
+
+	buf = data;
+	if (buf == NULL)
+		return EINVAL;
+
+	if (length < 0)
+		return EINVAL;
+
+	ret = intr_construct_intrids(kcpuset_running, &ids, &nids);
+	if (ret != 0)
+		return ret;
+
+	kcpuset_create(&avail, true);
+	kcpuset_create(&assigned, true);
+
+	buf_end = buf + length;
+
+#define FILL_BUF(cur, end, fmt, ...) do{				\
+		ret = snprintf(cur, end - cur, fmt, ## __VA_ARGS__);	\
+		if (ret < 0) {						\
+			goto out;					\
+		}							\
+		cur += ret;						\
+		if (cur > end) {					\
+			ret = ENOBUFS;					\
+			goto out;					\
+		}							\
+	}while(0)
+
+	FILL_BUF(buf, buf_end, " interrupt name");
+	intr_get_available(avail);
+	for (CPU_INFO_FOREACH(cii, ci)) {
+		char intr_enable;
+		if (kcpuset_isset(avail, cpu_index(ci)))
+			intr_enable = '+';
+		else
+			intr_enable = '-';
+
+		FILL_BUF(buf, buf_end, "\tCPU#%02u(%c)", cpu_index(ci),
+		    intr_enable);
+	}
+	*(buf++) = '\n';
+
+	for (intr_idx = 0; intr_idx < nids; intr_idx++) {
+		FILL_BUF(buf, buf_end, " %s", ids[intr_idx]);
+
+		mutex_enter(&cpu_lock);
+		ich = intr_get_handler(ids[intr_idx]);
+		KASSERT(ich != NULL);
+		mutex_exit(&cpu_lock);
+
+		intr_get_assigned(ich, assigned);
+		for (cpu_idx = 0; cpu_idx < ncpuonline; cpu_idx++) {
+			intr_count = intr_get_count(ich, cpu_idx);
+			if (kcpuset_isset(assigned, cpu_idx)) {
+				FILL_BUF(buf, buf_end, "\t%8" PRIu64 "*", intr_count);
+			} else {
+				FILL_BUF(buf, buf_end, "\t%8" PRIu64, intr_count);
+			}
+
+		}
+		FILL_BUF(buf, buf_end, "\t%s", intr_get_devname(ich));
+		*(buf++) = '\n';
+	}
+
+	*(buf++) = '\0';
+	ret = 0;
+out:
+	kcpuset_destroy(assigned);
+	kcpuset_destroy(avail);
+	intr_destruct_intrids(ids, nids);
+	return ret;
+
+#undef FILL_BUF
+}
+
+static int
+intrctl_affinity(void *data)
+{
+	cpuset_t *ucpuset;
+	kcpuset_t *kcpuset;
+	struct intr_set *iset;
+	void *ich;
+	int error;
+
+	iset = data;
+	ucpuset = iset->cpuset;
+	kcpuset_create(&kcpuset, true);
+	kcpuset_copyin(ucpuset, kcpuset, iset->cpuset_size);
+	if (kcpuset_iszero(kcpuset)) {
+		kcpuset_destroy(kcpuset);
+		return EINVAL;
+	}
+
+	mutex_enter(&cpu_lock);
+	ich = intr_get_handler(iset->intrid);
+	if (ich == NULL) {
+		mutex_exit(&cpu_lock);
+		kcpuset_destroy(kcpuset);
+		return EINVAL;
+	}
+	error = intr_distribute(ich, kcpuset, NULL);
+	mutex_exit(&cpu_lock);
+
+	kcpuset_destroy(kcpuset);
+	return error;
+}
+
+#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);
+}
+
+static int
+intr_shield(u_int cpu_idx, int shield)
+{
+	struct cpu_info *ci;
+	struct schedstate_percpu *spc;
+
+	ci = cpu_lookup(cpu_idx);
+	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_intr(u_int cpu_idx)
+{
+	kcpuset_t *cpuset;
+	void *ich;
+	char **ids;
+	int nids;
+	int error;
+	int i;
+
+	KASSERT(mutex_owned(&cpu_lock));
+
+	kcpuset_create(&cpuset, true);
+	kcpuset_set(cpuset, cpu_idx);
+
+	error = intr_construct_intrids(cpuset, &ids, &nids);
+	if (error)
+		return error;
+	if (nids == 0)
+		return 0; /* nothing to do */
+
+	intr_get_available(cpuset);
+	kcpuset_clear(cpuset, cpu_idx);
+	if (kcpuset_iszero(cpuset)) {
+		printf("no available cpu\n");
+		return ENOENT;
+	}
+
+	for (i = 0; i < nids; i++) {
+		ich = intr_get_handler(ids[i]);
+		if (ich == NULL) {
+			error = ENOENT;
+			break;
+		}
+		error = intr_distribute(ich, cpuset, NULL);
+		if (error)
+			break;
+	}
+
+	intr_destruct_intrids(ids, nids);
+	kcpuset_destroy(cpuset);
+	return error;
+}
+
+static int
+intrctl_intr(void *data)
+{
+	cpuset_t *ucpuset;
+	kcpuset_t *kcpuset;
+	struct intr_set *iset;
+	u_int cpu_idx;
+	int error;
+
+	iset = data;
+	ucpuset = iset->cpuset;
+	kcpuset_create(&kcpuset, true);
+	kcpuset_copyin(ucpuset, kcpuset, iset->cpuset_size);
+	cpu_idx = kcpuset_ffs(kcpuset) - 1; /* support one CPU only */
+
+	mutex_enter(&cpu_lock);
+	error = intr_shield(cpu_idx, UNSET_NOINTR_SHIELD);
+	mutex_exit(&cpu_lock);
+
+	return error;
+}
+
+static int
+intrctl_nointr(void *data)
+{
+	cpuset_t *ucpuset;
+	kcpuset_t *kcpuset;
+	struct intr_set *iset;
+	u_int cpu_idx;
+	int error;
+
+	iset = data;
+	ucpuset = iset->cpuset;
+	kcpuset_create(&kcpuset, true);
+	kcpuset_copyin(ucpuset, kcpuset, iset->cpuset_size);
+	cpu_idx = kcpuset_ffs(kcpuset) - 1; /* support one CPU only */
+
+	mutex_enter(&cpu_lock);
+	error = intr_shield(cpu_idx, SET_NOINTR_SHIELD);
+	if (error) {
+		mutex_exit(&cpu_lock);
+		return error;
+	}
+	error = intr_avert_intr(cpu_idx);
+	mutex_exit(&cpu_lock);
+
+	return error;
+}
+
+int
+intrctl_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
+{
+	int error;
+
+	switch (cmd) {
+	case IOC_INTR_LIST:
+		error = intrctl_list(data, INTR_LIST_BUFSIZE);
+		break;
+	case IOC_INTR_AFFINITY:
+		error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_INTR,
+		    KAUTH_REQ_SYSTEM_INTR_AFFINITY, NULL, NULL, NULL);
+		if (error)
+			break;
+		error = intrctl_affinity(data);
+		break;
+	case IOC_INTR_INTR:
+		error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_CPU,
+		    KAUTH_REQ_SYSTEM_CPU_SETSTATE, NULL, NULL, NULL);
+		if (error)
+			break;
+		error = intrctl_intr(data);
+		break;
+	case IOC_INTR_NOINTR:
+		error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_CPU,
+		    KAUTH_REQ_SYSTEM_CPU_SETSTATE, NULL, NULL, NULL);
+		if (error)
+			break;
+		error = intrctl_nointr(data);
+		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/intr.h b/sys/sys/intr.h
index 3904fdd..a7e06d2 100644
--- a/sys/sys/intr.h
+++ b/sys/sys/intr.h
@@ -35,6 +35,7 @@
 #ifdef _KERNEL
 
 #include <sys/types.h>
+#include <sys/kcpuset.h>
 
 struct cpu_info;
 
@@ -57,6 +58,15 @@ void	softint_trigger(uintptr_t);
 #endif
 void	softint_dispatch(lwp_t *, int);
 
+void		*intr_get_handler(const char *);
+uint64_t	intr_get_count(void *, u_int);
+void		intr_get_assigned(void *, kcpuset_t *);
+void		intr_get_available(kcpuset_t *);
+char		*intr_get_devname(void *);
+int		intr_distribute(void *, const kcpuset_t *, kcpuset_t *);
+int		intr_construct_intrids(const kcpuset_t *, char ***, int *);
+void		intr_destruct_intrids(char **, int);
+
 /* Flags for softint_establish(). */
 #define	SOFTINT_BIO	0x0000
 #define	SOFTINT_CLOCK	0x0001
diff --git a/sys/sys/intrio.h b/sys/sys/intrio.h
new file mode 100644
index 0000000..1b2e061
--- /dev/null
+++ b/sys/sys/intrio.h
@@ -0,0 +1,60 @@
+/*	$NetBSD$	*/
+
+/*
+ * Copyright (c) 2014 Internet Initiative Japan Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SYS_INTRIO_H_
+#define _SYS_INTRIO_H_
+
+#include <sys/ioccom.h>
+#include <sys/types.h>
+
+#ifndef _KERNEL
+#include <sched.h>
+#endif
+
+#define INTR_LIST_BUFSIZE 4096
+
+#define INTRID_LEN 47 /* should use max size of interrupt name of supporting
+		       * architectures.
+		       *     - x86
+		       *       sizeof(struct device.dv_xname) +
+		       *           sizeof(struct intrsource.is_evname) - 1
+		       *       see intr_string()
+		       */
+
+struct intr_set {
+	char intrid[INTRID_LEN + 1];
+	cpuset_t *cpuset;
+	size_t cpuset_size;
+};
+
+#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, struct intr_set)
+#define	IOC_INTR_NOINTR		_IOW('c', 3, struct intr_set)
+
+#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..641f253
--- /dev/null
+++ b/usr.sbin/intrctl/Makefile
@@ -0,0 +1,9 @@
+# $NetBSD$
+
+.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..16e1372
--- /dev/null
+++ b/usr.sbin/intrctl/intrctl.8
@@ -0,0 +1,72 @@
+.\" $NetBSD$
+.\"
+.\" Copyright (c) 2014 Internet Initiative Japan Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.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 cpu_index Fl i Ar irq
+set affinity
+.Ar irq
+interrupt to
+.Ar cpu_index.
+.It intr Fl c Ar cpu_index
+enable interrupts to set affinity to
+.Ar cpu_index.
+.It nointr Fl c Ar cpu_index
+disable interrupts to set affinity to
+.Ar cpu_index.
+.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..21d4264
--- /dev/null
+++ b/usr.sbin/intrctl/intrctl.c
@@ -0,0 +1,256 @@
+/*	$NetBSD$	*/
+
+/*
+ * Copyright (c) 2014 Internet Initiative Japan Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/intrio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <sched.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 interrupt_name -c cpu_index\n", progname);
+	fprintf(stderr, "       %s intr -c cpu_index\n", progname);
+	fprintf(stderr, "       %s nointr -c cpu_index\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;
+	cpuset_t *cpuset;
+	unsigned long index;
+	int ch;
+	int error;
+
+	index = ULONG_MAX;
+	memset(&iset.intrid, 0, sizeof(iset.intrid));
+
+	while ((ch = getopt(argc, argv, "c:i:")) != -1) {
+		switch (ch) {
+		case 'c':
+			index = strtoul(optarg, NULL, 10);
+			break;
+		case 'i':
+			if (strnlen(optarg, ARG_MAX) > INTRID_LEN)
+				usage();
+			strncpy(iset.intrid, optarg, INTRID_LEN);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (iset.intrid[0] == '\0' || index == ULONG_MAX)
+		usage();
+
+	if (index >= (u_long)sysconf(_SC_NPROCESSORS_CONF))
+		err(EXIT_FAILURE, "invalid cpu index");
+
+	cpuset = cpuset_create();
+	if (cpuset == NULL)
+		err(EXIT_FAILURE, "create_cpuset()");
+
+	cpuset_zero(cpuset);
+	cpuset_set(index, cpuset);
+	iset.cpuset = cpuset;
+	iset.cpuset_size = cpuset_size(cpuset);
+	error = ioctl(fd, IOC_INTR_AFFINITY, &iset);
+	cpuset_destroy(cpuset);
+	if (error < 0)
+		err(EXIT_FAILURE, "IOC_INTR_AFFINITY");
+}
+
+static void
+intr_intr(int argc, char **argv)
+{
+	struct intr_set iset;
+	cpuset_t *cpuset;
+	unsigned long index;
+	int ch;
+	int error;
+
+	index = ULONG_MAX;
+
+	while ((ch = getopt(argc, argv, "c:")) != -1) {
+		switch (ch) {
+		case 'c':
+			index = strtoul(optarg, NULL, 10);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (index == ULONG_MAX)
+		usage();
+
+	if (index >= (u_long)sysconf(_SC_NPROCESSORS_CONF))
+		err(EXIT_FAILURE, "invalid cpu index");
+
+	cpuset = cpuset_create();
+	if (cpuset == NULL)
+		err(EXIT_FAILURE, "create_cpuset()");
+
+	cpuset_zero(cpuset);
+	cpuset_set(index, cpuset);
+	iset.cpuset = cpuset;
+	iset.cpuset_size = cpuset_size(cpuset);
+	error = ioctl(fd, IOC_INTR_INTR, &iset);
+	cpuset_destroy(cpuset);
+	if (error < 0)
+		err(EXIT_FAILURE, "IOC_INTR_INTR");
+}
+
+static void
+intr_nointr(int argc, char **argv)
+{
+	struct intr_set iset;
+	cpuset_t *cpuset;
+	unsigned long index;
+	int ch;
+	int error;
+
+	index = ULONG_MAX;
+
+	while ((ch = getopt(argc, argv, "c:")) != -1) {
+		switch (ch) {
+		case 'c':
+			index = strtoul(optarg, NULL, 10);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (index == ULONG_MAX)
+		usage();
+
+	if (index >= (u_long)sysconf(_SC_NPROCESSORS_CONF))
+		err(EXIT_FAILURE, "invalid cpu index");
+
+	cpuset = cpuset_create();
+	if (cpuset == NULL)
+		err(EXIT_FAILURE, "create_cpuset()");
+
+	cpuset_zero(cpuset);
+	cpuset_set(index, cpuset);
+	iset.cpuset = cpuset;
+	iset.cpuset_size = cpuset_size(cpuset);
+	error = ioctl(fd, IOC_INTR_NOINTR, &iset);
+	cpuset_destroy(cpuset);
+	if (error < 0) {
+		err(EXIT_FAILURE, "IOC_INTR_NOINTR");
+	}
+}
