Home | History | Annotate | Line # | Download | only in kern
      1 /*	$NetBSD: subr_interrupt.c,v 1.7 2026/01/04 03:18:31 riastradh Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2015 Internet Initiative Japan Inc.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/cdefs.h>
     30 __KERNEL_RCSID(0, "$NetBSD: subr_interrupt.c,v 1.7 2026/01/04 03:18:31 riastradh Exp $");
     31 
     32 #include <sys/param.h>
     33 #include <sys/types.h>
     34 
     35 #include <sys/conf.h>
     36 #include <sys/cpu.h>
     37 #include <sys/errno.h>
     38 #include <sys/interrupt.h>
     39 #include <sys/intr.h>
     40 #include <sys/intrio.h>
     41 #include <sys/kauth.h>
     42 #include <sys/kcpuset.h>
     43 #include <sys/kernel.h>
     44 #include <sys/kmem.h>
     45 #include <sys/proc.h>
     46 #include <sys/sdt.h>
     47 #include <sys/sysctl.h>
     48 #include <sys/systm.h>
     49 #include <sys/xcall.h>
     50 
     51 #include <machine/limits.h>
     52 
     53 #ifdef INTR_DEBUG
     54 #define DPRINTF(msg) printf msg
     55 #else
     56 #define DPRINTF(msg)
     57 #endif
     58 
     59 static struct intrio_set kintrio_set = { "\0", NULL, 0 };
     60 
     61 #define UNSET_NOINTR_SHIELD	0
     62 #define SET_NOINTR_SHIELD	1
     63 
     64 static void
     65 interrupt_shield_xcall(void *arg1, void *arg2)
     66 {
     67 	struct cpu_info *ci;
     68 	struct schedstate_percpu *spc;
     69 	int s, shield;
     70 
     71 	ci = arg1;
     72 	shield = (int)(intptr_t)arg2;
     73 	spc = &ci->ci_schedstate;
     74 
     75 	s = splsched();
     76 	if (shield == UNSET_NOINTR_SHIELD)
     77 		spc->spc_flags &= ~SPCF_NOINTR;
     78 	else if (shield == SET_NOINTR_SHIELD)
     79 		spc->spc_flags |= SPCF_NOINTR;
     80 	splx(s);
     81 }
     82 
     83 /*
     84  * Change SPCF_NOINTR flag of schedstate_percpu->spc_flags.
     85  */
     86 static int
     87 interrupt_shield(u_int cpu_idx, int shield)
     88 {
     89 	struct cpu_info *ci;
     90 	struct schedstate_percpu *spc;
     91 
     92 	KASSERT(mutex_owned(&cpu_lock));
     93 
     94 	ci = cpu_lookup(cpu_idx);
     95 	if (ci == NULL)
     96 		return SET_ERROR(EINVAL);
     97 
     98 	spc = &ci->ci_schedstate;
     99 	if (shield == UNSET_NOINTR_SHIELD) {
    100 		if ((spc->spc_flags & SPCF_NOINTR) == 0)
    101 			return 0;
    102 	} else if (shield == SET_NOINTR_SHIELD) {
    103 		if ((spc->spc_flags & SPCF_NOINTR) != 0)
    104 			return 0;
    105 	}
    106 
    107 	if (ci == curcpu() || !mp_online) {
    108 		interrupt_shield_xcall(ci, (void *)(intptr_t)shield);
    109 	} else {
    110 		uint64_t where;
    111 		where = xc_unicast(0, interrupt_shield_xcall, ci,
    112 			(void *)(intptr_t)shield, ci);
    113 		xc_wait(where);
    114 	}
    115 
    116 	spc->spc_lastmod = time_second;
    117 	return 0;
    118 }
    119 
    120 /*
    121  * Move all assigned interrupts from "cpu_idx" to the other cpu as possible.
    122  * The destination cpu is the lowest cpuid of available cpus.
    123  * If there are no available cpus, give up to move interrupts.
    124  */
    125 static int
    126 interrupt_avert_intr(u_int cpu_idx)
    127 {
    128 	kcpuset_t *cpuset;
    129 	struct intrids_handler *ii_handler;
    130 	intrid_t *ids;
    131 	int error = 0, i, nids;
    132 
    133 	kcpuset_create(&cpuset, true);
    134 	kcpuset_set(cpuset, cpu_idx);
    135 
    136 	ii_handler = interrupt_construct_intrids(cpuset);
    137 	if (ii_handler == NULL) {
    138 		error = SET_ERROR(EINVAL);
    139 		goto out;
    140 	}
    141 	nids = ii_handler->iih_nids;
    142 	if (nids == 0) {
    143 		error = 0;
    144 		goto destruct_out;
    145 	}
    146 
    147 	interrupt_get_available(cpuset);
    148 	kcpuset_clear(cpuset, cpu_idx);
    149 	if (kcpuset_iszero(cpuset)) {
    150 		DPRINTF(("%s: no available cpu\n", __func__));
    151 		error = SET_ERROR(ENOENT);
    152 		goto destruct_out;
    153 	}
    154 
    155 	ids = ii_handler->iih_intrids;
    156 	for (i = 0; i < nids; i++) {
    157 		error = interrupt_distribute_handler(ids[i], cpuset, NULL);
    158 		if (error)
    159 			break;
    160 	}
    161 
    162  destruct_out:
    163 	interrupt_destruct_intrids(ii_handler);
    164  out:
    165 	kcpuset_destroy(cpuset);
    166 	return error;
    167 }
    168 
    169 /*
    170  * Return actual intrio_list_line size.
    171  * intrio_list_line size is variable by ncpu.
    172  */
    173 static size_t
    174 interrupt_intrio_list_line_size(void)
    175 {
    176 
    177 	return sizeof(struct intrio_list_line) +
    178 		sizeof(struct intrio_list_line_cpu) * (ncpu - 1);
    179 }
    180 
    181 /*
    182  * Return the size of interrupts list data on success.
    183  * Reterun 0 on failed.
    184  */
    185 static int
    186 interrupt_intrio_list_size(size_t *ilsize)
    187 {
    188 	struct intrids_handler *ii_handler;
    189 
    190 	*ilsize = 0;
    191 
    192 	/* buffer header */
    193 	*ilsize += sizeof(struct intrio_list);
    194 
    195 	/* il_line body */
    196 	ii_handler = interrupt_construct_intrids(kcpuset_running);
    197 	if (ii_handler == NULL)
    198 		return SET_ERROR(EOPNOTSUPP);
    199 	*ilsize += interrupt_intrio_list_line_size() * ii_handler->iih_nids;
    200 
    201 	interrupt_destruct_intrids(ii_handler);
    202 	return 0;
    203 }
    204 
    205 /*
    206  * Set intrctl list data to "il", and return list structure bytes.
    207  * If error occurred, return <0.
    208  * If "data" == NULL, simply return list structure bytes.
    209  */
    210 static int
    211 interrupt_intrio_list(struct intrio_list *il, size_t ilsize)
    212 {
    213 	struct intrio_list_line *illine;
    214 	kcpuset_t *assigned, *avail;
    215 	struct intrids_handler *ii_handler;
    216 	intrid_t *ids;
    217 	u_int cpu_idx;
    218 	int nids, intr_idx, error, line_size;
    219 
    220 	illine = (struct intrio_list_line *)
    221 	    ((char *)il + sizeof(struct intrio_list));
    222 	il->il_lineoffset = (off_t)((uintptr_t)illine - (uintptr_t)il);
    223 
    224 	kcpuset_create(&avail, true);
    225 	interrupt_get_available(avail);
    226 	kcpuset_create(&assigned, true);
    227 
    228 	ii_handler = interrupt_construct_intrids(kcpuset_running);
    229 	if (ii_handler == NULL) {
    230 		DPRINTF(("%s: interrupt_construct_intrids() failed\n",
    231 		    __func__));
    232 		error = SET_ERROR(EOPNOTSUPP);
    233 		goto out;
    234 	}
    235 
    236 	line_size = interrupt_intrio_list_line_size();
    237 	/* ensure interrupts are not added after interrupt_intrio_list_size() */
    238 	nids = ii_handler->iih_nids;
    239 	ids = ii_handler->iih_intrids;
    240 	if (ilsize < sizeof(struct intrio_list) + line_size * nids) {
    241 		DPRINTF(("%s: interrupts are added during execution.\n",
    242 		    __func__));
    243 		error = SET_ERROR(EAGAIN);
    244 		goto destruct_out;
    245 	}
    246 
    247 	for (intr_idx = 0; intr_idx < nids; intr_idx++) {
    248 		char devname[INTRDEVNAMEBUF];
    249 
    250 		strncpy(illine->ill_intrid, ids[intr_idx], INTRIDBUF);
    251 		interrupt_get_devname(ids[intr_idx], devname, sizeof(devname));
    252 		strncpy(illine->ill_xname, devname, INTRDEVNAMEBUF);
    253 
    254 		interrupt_get_assigned(ids[intr_idx], assigned);
    255 		for (cpu_idx = 0; cpu_idx < ncpu; cpu_idx++) {
    256 			struct intrio_list_line_cpu *illcpu =
    257 			    &illine->ill_cpu[cpu_idx];
    258 
    259 			illcpu->illc_assigned =
    260 			    kcpuset_isset(assigned, cpu_idx);
    261 			illcpu->illc_count =
    262 			    interrupt_get_count(ids[intr_idx], cpu_idx);
    263 		}
    264 
    265 		illine = (struct intrio_list_line *)
    266 		    ((char *)illine + line_size);
    267 	}
    268 
    269 	error = 0;
    270 	il->il_version = INTRIO_LIST_VERSION;
    271 	il->il_ncpus = ncpu;
    272 	il->il_nintrs = nids;
    273 	il->il_linesize = line_size;
    274 	il->il_bufsize = ilsize;
    275 
    276  destruct_out:
    277 	interrupt_destruct_intrids(ii_handler);
    278  out:
    279 	kcpuset_destroy(assigned);
    280 	kcpuset_destroy(avail);
    281 
    282 	return error;
    283 }
    284 
    285 /*
    286  * "intrctl list" entry
    287  */
    288 static int
    289 interrupt_intrio_list_sysctl(SYSCTLFN_ARGS)
    290 {
    291 	int error;
    292 	void *buf;
    293 	size_t ilsize;
    294 
    295 	if (oldlenp == NULL)
    296 		return SET_ERROR(EINVAL);
    297 
    298 	if ((error = interrupt_intrio_list_size(&ilsize)) != 0)
    299 		return error;
    300 
    301 	/*
    302 	 * If oldp == NULL, the sysctl(8) caller process want to get the size of
    303 	 * intrctl list data only.
    304 	 */
    305 	if (oldp == NULL) {
    306 		*oldlenp = ilsize;
    307 		return 0;
    308 	}
    309 
    310 	/*
    311 	 * If oldp != NULL, the sysctl(8) caller process want to get both the
    312 	 * size and the contents of intrctl list data.
    313 	 */
    314 	if (*oldlenp < ilsize)
    315 		return SET_ERROR(ENOMEM);
    316 
    317 	buf = kmem_zalloc(ilsize, KM_SLEEP);
    318 	if ((error = interrupt_intrio_list(buf, ilsize)) != 0)
    319 		goto out;
    320 
    321 	error = copyout(buf, oldp, ilsize);
    322  out:
    323 	kmem_free(buf, ilsize);
    324 	return error;
    325 }
    326 
    327 /*
    328  * "intrctl affinity" entry
    329  */
    330 static int
    331 interrupt_set_affinity_sysctl(SYSCTLFN_ARGS)
    332 {
    333 	struct sysctlnode node;
    334 	struct intrio_set *iset;
    335 	cpuset_t *ucpuset;
    336 	kcpuset_t *kcpuset;
    337 	int error;
    338 
    339 	error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_INTR,
    340 	    KAUTH_REQ_SYSTEM_INTR_AFFINITY, NULL, NULL, NULL);
    341 	if (error)
    342 		return SET_ERROR(EPERM);
    343 
    344 	node = *rnode;
    345 	iset = (struct intrio_set *)node.sysctl_data;
    346 
    347 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
    348 	if (error != 0 || newp == NULL)
    349 		return error;
    350 
    351 	ucpuset = iset->cpuset;
    352 	kcpuset_create(&kcpuset, true);
    353 	error = kcpuset_copyin(ucpuset, kcpuset, iset->cpuset_size);
    354 	if (error)
    355 		goto out;
    356 	if (kcpuset_iszero(kcpuset)) {
    357 		error = SET_ERROR(EINVAL);
    358 		goto out;
    359 	}
    360 
    361 	error = interrupt_distribute_handler(iset->intrid, kcpuset, NULL);
    362 
    363  out:
    364 	kcpuset_destroy(kcpuset);
    365 	return error;
    366 }
    367 
    368 /*
    369  * "intrctl intr" entry
    370  */
    371 static int
    372 interrupt_intr_sysctl(SYSCTLFN_ARGS)
    373 {
    374 	struct sysctlnode node;
    375 	struct intrio_set *iset;
    376 	cpuset_t *ucpuset;
    377 	kcpuset_t *kcpuset;
    378 	int error;
    379 	u_int cpu_idx;
    380 
    381 	error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_CPU,
    382 	    KAUTH_REQ_SYSTEM_CPU_SETSTATE, NULL, NULL, NULL);
    383 	if (error)
    384 		return SET_ERROR(EPERM);
    385 
    386 	node = *rnode;
    387 	iset = (struct intrio_set *)node.sysctl_data;
    388 
    389 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
    390 	if (error != 0 || newp == NULL)
    391 		return error;
    392 
    393 	ucpuset = iset->cpuset;
    394 	kcpuset_create(&kcpuset, true);
    395 	error = kcpuset_copyin(ucpuset, kcpuset, iset->cpuset_size);
    396 	if (error)
    397 		goto out;
    398 	if (kcpuset_iszero(kcpuset)) {
    399 		error = SET_ERROR(EINVAL);
    400 		goto out;
    401 	}
    402 
    403 	cpu_idx = kcpuset_ffs(kcpuset) - 1; /* support one CPU only */
    404 
    405 	mutex_enter(&cpu_lock);
    406 	error = interrupt_shield(cpu_idx, UNSET_NOINTR_SHIELD);
    407 	mutex_exit(&cpu_lock);
    408 
    409  out:
    410 	kcpuset_destroy(kcpuset);
    411 	return error;
    412 }
    413 
    414 /*
    415  * "intrctl nointr" entry
    416  */
    417 static int
    418 interrupt_nointr_sysctl(SYSCTLFN_ARGS)
    419 {
    420 	struct sysctlnode node;
    421 	struct intrio_set *iset;
    422 	cpuset_t *ucpuset;
    423 	kcpuset_t *kcpuset;
    424 	int error;
    425 	u_int cpu_idx;
    426 
    427 	error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_CPU,
    428 	    KAUTH_REQ_SYSTEM_CPU_SETSTATE, NULL, NULL, NULL);
    429 	if (error)
    430 		return SET_ERROR(EPERM);
    431 
    432 	node = *rnode;
    433 	iset = (struct intrio_set *)node.sysctl_data;
    434 
    435 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
    436 	if (error != 0 || newp == NULL)
    437 		return error;
    438 
    439 	ucpuset = iset->cpuset;
    440 	kcpuset_create(&kcpuset, true);
    441 	error = kcpuset_copyin(ucpuset, kcpuset, iset->cpuset_size);
    442 	if (error)
    443 		goto out;
    444 	if (kcpuset_iszero(kcpuset)) {
    445 		error = SET_ERROR(EINVAL);
    446 		goto out;
    447 	}
    448 
    449 	cpu_idx = kcpuset_ffs(kcpuset) - 1; /* support one CPU only */
    450 
    451 	mutex_enter(&cpu_lock);
    452 	error = interrupt_shield(cpu_idx, SET_NOINTR_SHIELD);
    453 	mutex_exit(&cpu_lock);
    454 	if (error)
    455 		goto out;
    456 
    457 	error = interrupt_avert_intr(cpu_idx);
    458 
    459  out:
    460 	kcpuset_destroy(kcpuset);
    461 	return error;
    462 }
    463 
    464 SYSCTL_SETUP(sysctl_interrupt_setup, "sysctl interrupt setup")
    465 {
    466 	const struct sysctlnode *node = NULL;
    467 
    468 	sysctl_createv(clog, 0, NULL, &node,
    469 		       CTLFLAG_PERMANENT, CTLTYPE_NODE,
    470 		       "intr", SYSCTL_DESCR("Interrupt options"),
    471 		       NULL, 0, NULL, 0,
    472 		       CTL_KERN, CTL_CREATE, CTL_EOL);
    473 
    474 	sysctl_createv(clog, 0, &node, NULL,
    475 		       CTLFLAG_PERMANENT, CTLTYPE_STRUCT,
    476 		       "list", SYSCTL_DESCR("intrctl list"),
    477 		       interrupt_intrio_list_sysctl, 0, NULL,
    478 		        0, CTL_CREATE, CTL_EOL);
    479 
    480 	sysctl_createv(clog, 0, &node, NULL,
    481 		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_STRUCT,
    482 		       "affinity", SYSCTL_DESCR("set affinity"),
    483 		       interrupt_set_affinity_sysctl, 0, &kintrio_set,
    484 		       sizeof(kintrio_set), CTL_CREATE, CTL_EOL);
    485 
    486 	sysctl_createv(clog, 0, &node, NULL,
    487 		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_STRUCT,
    488 		       "intr", SYSCTL_DESCR("set intr"),
    489 		       interrupt_intr_sysctl, 0, &kintrio_set,
    490 		       sizeof(kintrio_set), CTL_CREATE, CTL_EOL);
    491 
    492 	sysctl_createv(clog, 0, &node, NULL,
    493 		       CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_STRUCT,
    494 		       "nointr", SYSCTL_DESCR("set nointr"),
    495 		       interrupt_nointr_sysctl, 0, &kintrio_set,
    496 		       sizeof(kintrio_set), CTL_CREATE, CTL_EOL);
    497 }
    498