Module Name: src Committed By: riastradh Date: Sun Dec 19 01:17:14 UTC 2021
Modified Files: src/sys/external/bsd/common/conf: files.linux src/sys/external/bsd/drm2/linux: linux_module.c Added Files: src/sys/external/bsd/common/include/linux: tasklet.h src/sys/external/bsd/common/linux: linux_tasklet.c Log Message: Draft Linux tasklet implementation. To generate a diff of this commit: cvs rdiff -u -r1.1 -r1.2 src/sys/external/bsd/common/conf/files.linux cvs rdiff -u -r0 -r1.1 src/sys/external/bsd/common/include/linux/tasklet.h cvs rdiff -u -r0 -r1.1 src/sys/external/bsd/common/linux/linux_tasklet.c cvs rdiff -u -r1.9 -r1.10 src/sys/external/bsd/drm2/linux/linux_module.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/external/bsd/common/conf/files.linux diff -u src/sys/external/bsd/common/conf/files.linux:1.1 src/sys/external/bsd/common/conf/files.linux:1.2 --- src/sys/external/bsd/common/conf/files.linux:1.1 Thu Feb 25 08:51:54 2016 +++ src/sys/external/bsd/common/conf/files.linux Sun Dec 19 01:17:14 2021 @@ -1,7 +1,8 @@ -# $NetBSD: files.linux,v 1.1 2016/02/25 08:51:54 skrll Exp $ +# $NetBSD: files.linux,v 1.2 2021/12/19 01:17:14 riastradh Exp $ define linux makeoptions linux CPPFLAGS+="-I$S/external/bsd/common/include" +file external/bsd/common/linux/linux_tasklet.c linux file external/bsd/common/linux/linux_work.c linux Index: src/sys/external/bsd/drm2/linux/linux_module.c diff -u src/sys/external/bsd/drm2/linux/linux_module.c:1.9 src/sys/external/bsd/drm2/linux/linux_module.c:1.10 --- src/sys/external/bsd/drm2/linux/linux_module.c:1.9 Mon Aug 27 15:08:54 2018 +++ src/sys/external/bsd/drm2/linux/linux_module.c Sun Dec 19 01:17:14 2021 @@ -1,4 +1,4 @@ -/* $NetBSD: linux_module.c,v 1.9 2018/08/27 15:08:54 riastradh Exp $ */ +/* $NetBSD: linux_module.c,v 1.10 2021/12/19 01:17:14 riastradh Exp $ */ /*- * Copyright (c) 2014 The NetBSD Foundation, Inc. @@ -30,7 +30,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: linux_module.c,v 1.9 2018/08/27 15:08:54 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: linux_module.c,v 1.10 2021/12/19 01:17:14 riastradh Exp $"); #include <sys/module.h> #ifndef _MODULE @@ -43,6 +43,7 @@ __KERNEL_RCSID(0, "$NetBSD: linux_module #include <linux/io.h> #include <linux/mutex.h> #include <linux/rcupdate.h> +#include <linux/tasklet.h> #include <linux/workqueue.h> MODULE(MODULE_CLASS_MISC, drmkms_linux, "i2cexec"); @@ -89,10 +90,17 @@ linux_init(void) goto fail5; } + error = linux_tasklets_init(); + if (error) { + printf("linux: unable to initialize tasklets: %d\n", error); + goto fail6; + } + return 0; -fail6: __unused - linux_atomic64_fini(); +fail7: __unused + linux_tasklets_fini(); +fail6: linux_atomic64_fini(); fail5: linux_writecomb_fini(); fail4: linux_workqueue_fini(); fail3: linux_rcu_gc_fini(); @@ -118,6 +126,7 @@ static void linux_fini(void) { + linux_tasklets_fini(); linux_atomic64_fini(); linux_writecomb_fini(); linux_workqueue_fini(); Added files: Index: src/sys/external/bsd/common/include/linux/tasklet.h diff -u /dev/null src/sys/external/bsd/common/include/linux/tasklet.h:1.1 --- /dev/null Sun Dec 19 01:17:14 2021 +++ src/sys/external/bsd/common/include/linux/tasklet.h Sun Dec 19 01:17:14 2021 @@ -0,0 +1,82 @@ +/* $NetBSD: tasklet.h,v 1.1 2021/12/19 01:17:14 riastradh Exp $ */ + +/*- + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Taylor R. Campbell. + * + * 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 _LINUX_TASKLET_H_ +#define _LINUX_TASKLET_H_ + +/* namespace */ +#define tasklet_disable linux_tasklet_disable +#define tasklet_disable_nosync linux_tasklet_disable_nosync +#define tasklet_enable linux_tasklet_enable +#define tasklet_hi_schedule linux_tasklet_hi_schedule +#define tasklet_init linux_tasklet_init +#define tasklet_kill linux_tasklet_kill +#define tasklet_schedule linux_tasklet_schedule +#define tasklet_struct linux_tasklet_struct + +struct tasklet_struct { + SIMPLEQ_ENTRY(tasklet_struct) tl_entry; + volatile unsigned tl_state; + volatile unsigned tl_disablecount; + /* begin Linux API */ + void (*func)(unsigned long); + unsigned long data; + /* end Linux API */ +}; + +#define DEFINE_TASKLET(name, func, data) \ + struct tasklet_struct name = { \ + .tl_state = 0, \ + .tl_disablecount = 0, \ + .func = (func), \ + .data = (data), \ + } + +#define DEFINE_TASKLET_DISABLED(name, func, data) \ + struct tasklet_struct name = { \ + .tl_state = 0, \ + .tl_disablecount = 1, \ + .func = (func), \ + .data = (data), \ + } + +int linux_tasklets_init(void); +void linux_tasklets_fini(void); + +void tasklet_init(struct tasklet_struct *, void (*)(unsigned long), + unsigned long); +void tasklet_disable(struct tasklet_struct *); +void tasklet_enable(struct tasklet_struct *); +void tasklet_schedule(struct tasklet_struct *); +void tasklet_hi_schedule(struct tasklet_struct *); +void tasklet_kill(struct tasklet_struct *); + +#endif /* _LINUX_TASKLET_H_ */ Index: src/sys/external/bsd/common/linux/linux_tasklet.c diff -u /dev/null src/sys/external/bsd/common/linux/linux_tasklet.c:1.1 --- /dev/null Sun Dec 19 01:17:14 2021 +++ src/sys/external/bsd/common/linux/linux_tasklet.c Sun Dec 19 01:17:14 2021 @@ -0,0 +1,471 @@ +/* $NetBSD: linux_tasklet.c,v 1.1 2021/12/19 01:17:14 riastradh Exp $ */ + +/*- + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Taylor R. Campbell. + * + * 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: linux_tasklet.c,v 1.1 2021/12/19 01:17:14 riastradh Exp $"); + +#include <sys/types.h> +#include <sys/atomic.h> +#include <sys/cpu.h> +#include <sys/errno.h> +#include <sys/intr.h> +#include <sys/lock.h> +#include <sys/percpu.h> +#include <sys/queue.h> + +#include <lib/libkern/libkern.h> + +#include <machine/limits.h> + +#include <linux/tasklet.h> + +#define TASKLET_SCHEDULED ((unsigned)__BIT(0)) +#define TASKLET_RUNNING ((unsigned)__BIT(1)) + +struct tasklet_queue { + struct percpu *tq_percpu; /* struct tasklet_cpu */ + void *tq_sih; +}; + +SIMPLEQ_HEAD(tasklet_head, tasklet_struct); + +struct tasklet_cpu { + struct tasklet_head tc_head; +}; + +static struct tasklet_queue tasklet_queue __read_mostly; +static struct tasklet_queue tasklet_hi_queue __read_mostly; + +static void tasklet_softintr(void *); +static int tasklet_queue_init(struct tasklet_queue *, unsigned); +static void tasklet_queue_fini(struct tasklet_queue *); +static void tasklet_queue_schedule(struct tasklet_queue *, + struct tasklet_struct *); +static void tasklet_queue_enqueue(struct tasklet_queue *, + struct tasklet_struct *); + +/* + * linux_tasklets_init() + * + * Initialize the Linux tasklets subsystem. Return 0 on success, + * error code on failure. + */ +int +linux_tasklets_init(void) +{ + int error; + + error = tasklet_queue_init(&tasklet_queue, SOFTINT_CLOCK); + if (error) + goto fail0; + error = tasklet_queue_init(&tasklet_hi_queue, SOFTINT_SERIAL); + if (error) + goto fail1; + + /* Success! */ + return 0; + +fail2: __unused + tasklet_queue_fini(&tasklet_hi_queue); +fail1: tasklet_queue_fini(&tasklet_queue); +fail0: KASSERT(error); + return error; +} + +/* + * linux_tasklets_fini() + * + * Finalize the Linux tasklets subsystem. All use of tasklets + * must be done. + */ +void +linux_tasklets_fini(void) +{ + + tasklet_queue_fini(&tasklet_hi_queue); + tasklet_queue_fini(&tasklet_queue); +} + +/* + * tasklet_queue_init(tq, prio) + * + * Initialize the tasklet queue tq for running tasklets at softint + * priority prio (SOFTINT_*). + */ +static int +tasklet_queue_init(struct tasklet_queue *tq, unsigned prio) +{ + int error; + + /* Allocate per-CPU memory. percpu_alloc cannot fail. */ + tq->tq_percpu = percpu_alloc(sizeof(struct tasklet_cpu)); + KASSERT(tq->tq_percpu != NULL); + + /* Try to establish a softint. softint_establish may fail. */ + tq->tq_sih = softint_establish(prio|SOFTINT_MPSAFE, &tasklet_softintr, + tq); + if (tq->tq_sih == NULL) { + error = ENOMEM; + goto fail1; + } + + /* Success! */ + return 0; + +fail2: __unused + softint_disestablish(tq->tq_sih); + tq->tq_sih = NULL; +fail1: percpu_free(tq->tq_percpu, sizeof(struct tasklet_cpu)); + tq->tq_percpu = NULL; +fail0: __unused + KASSERT(error); + return error; +} + +/* + * tasklet_queue_fini(tq) + * + * Finalize the tasklet queue tq: free all resources associated + * with it. + */ +static void +tasklet_queue_fini(struct tasklet_queue *tq) +{ + + softint_disestablish(tq->tq_sih); + tq->tq_sih = NULL; + percpu_free(tq->tq_percpu, sizeof(struct tasklet_cpu)); + tq->tq_percpu = NULL; +} + +/* + * tasklet_softintr(cookie) + * + * Soft interrupt handler: Process queued tasklets on the tasklet + * queue passed in as cookie. + */ +static void +tasklet_softintr(void *cookie) +{ + struct tasklet_queue *const tq = cookie; + struct tasklet_head th = SIMPLEQ_HEAD_INITIALIZER(th); + struct tasklet_cpu *tc; + int s; + + /* + * With all interrupts deferred, transfer the current CPU's + * queue of tasklets to a local variable in one swell foop. + * + * No memory barriers: CPU-local state only. + */ + tc = percpu_getref(tq->tq_percpu); + s = splhigh(); + SIMPLEQ_CONCAT(&th, &tc->tc_head); + splx(s); + percpu_putref(tq->tq_percpu); + + /* Go through the queue of tasklets we grabbed. */ + while (!SIMPLEQ_EMPTY(&th)) { + struct tasklet_struct *tasklet; + unsigned state; + + /* Remove the first tasklet from the queue. */ + tasklet = SIMPLEQ_FIRST(&th); + SIMPLEQ_REMOVE_HEAD(&th, tl_entry); + + /* + * Test and set RUNNING, in case it is already running + * on another CPU and got scheduled again on this one + * before it completed. + */ + do { + state = tasklet->tl_state; + __insn_barrier(); + /* It had better be scheduled. */ + KASSERT(state & TASKLET_SCHEDULED); + if (state & TASKLET_RUNNING) + break; + } while (atomic_cas_uint(&tasklet->tl_state, state, + state | TASKLET_RUNNING) != state); + + if (state & TASKLET_RUNNING) { + /* + * Put it back on the queue to run it again in + * a sort of busy-wait, and move on to the next + * one. + */ + tasklet_queue_enqueue(tq, tasklet); + continue; + } + + /* Wait for last runner's side effects. */ + membar_enter(); + + /* Check whether it's currently disabled. */ + if (tasklet->tl_disablecount) { + /* + * Disabled: clear the RUNNING bit and, requeue + * it, but keep it SCHEDULED. + */ + KASSERT(tasklet->tl_state & TASKLET_RUNNING); + atomic_and_uint(&tasklet->tl_state, ~TASKLET_RUNNING); + tasklet_queue_enqueue(tq, tasklet); + continue; + } + + /* Not disabled. Clear SCHEDULED and call func. */ + KASSERT(tasklet->tl_state & TASKLET_SCHEDULED); + atomic_and_uint(&tasklet->tl_state, ~TASKLET_SCHEDULED); + + (*tasklet->func)(tasklet->data); + + /* + * Guarantee all caller-relevant reads or writes in + * func have completed before clearing RUNNING bit. + */ + membar_exit(); + + /* Clear RUNNING to notify tasklet_disable. */ + atomic_and_uint(&tasklet->tl_state, ~TASKLET_RUNNING); + } +} + +/* + * tasklet_queue_schedule(tq, tasklet) + * + * Schedule tasklet to run on tq. If it was already scheduled and + * has not yet run, no effect. + */ +static void +tasklet_queue_schedule(struct tasklet_queue *tq, + struct tasklet_struct *tasklet) +{ + unsigned ostate, nstate; + + /* Test and set the SCHEDULED bit. If already set, we're done. */ + do { + ostate = tasklet->tl_state; + if (ostate & TASKLET_SCHEDULED) + return; + nstate = ostate | TASKLET_SCHEDULED; + } while (atomic_cas_uint(&tasklet->tl_state, ostate, nstate) + != ostate); + + /* + * Not already set and we have set it now. Put it on the queue + * and kick off a softint. + */ + tasklet_queue_enqueue(tq, tasklet); +} + +/* + * tasklet_queue_enqueue(tq, tasklet) + * + * Put tasklet on the queue tq and ensure it will run. tasklet + * must be marked SCHEDULED. + */ +static void +tasklet_queue_enqueue(struct tasklet_queue *tq, struct tasklet_struct *tasklet) +{ + struct tasklet_cpu *tc; + int s; + + KASSERT(tasklet->tl_state & TASKLET_SCHEDULED); + + /* + * Insert on the current CPU's queue while all interrupts are + * blocked, and schedule a soft interrupt to process it. No + * memory barriers: CPU-local state only. + */ + tc = percpu_getref(tq->tq_percpu); + s = splhigh(); + SIMPLEQ_INSERT_TAIL(&tc->tc_head, tasklet, tl_entry); + splx(s); + softint_schedule(tq->tq_sih); + percpu_putref(tq->tq_percpu); +} + +/* + * tasklet_init(tasklet, func, data) + * + * Initialize tasklet to call func(data) when scheduled. + * + * Caller is responsible for issuing the appropriate memory + * barriers or store releases to publish the tasklet to other CPUs + * before use. + */ +void +tasklet_init(struct tasklet_struct *tasklet, void (*func)(unsigned long), + unsigned long data) +{ + + tasklet->tl_state = 0; + tasklet->tl_disablecount = 0; + tasklet->func = func; + tasklet->data = data; +} + +/* + * tasklet_schedule(tasklet) + * + * Schedule tasklet to run at regular priority. If it was already + * scheduled and has not yet run, no effect. + */ +void +tasklet_schedule(struct tasklet_struct *tasklet) +{ + + tasklet_queue_schedule(&tasklet_queue, tasklet); +} + +/* + * tasklet_hi_schedule(tasklet) + * + * Schedule tasklet to run at high priority. If it was already + * scheduled and has not yet run, no effect. + */ +void +tasklet_hi_schedule(struct tasklet_struct *tasklet) +{ + + tasklet_queue_schedule(&tasklet_hi_queue, tasklet); +} + +/* + * tasklet_disable(tasklet) + * + * Increment the disable count of tasklet, and if it was already + * running, busy-wait for it to complete. + * + * As long as the disable count is nonzero, the tasklet's function + * will not run, but if already scheduled, the tasklet will remain + * so and the softint will repeatedly trigger itself in a sort of + * busy-wait, so this should be used only for short durations. + * + * If tasklet is guaranteed not to be scheduled, e.g. if you have + * just invoked tasklet_kill, then tasklet_disable serves to wait + * for it to complete in case it might already be running. + */ +void +tasklet_disable(struct tasklet_struct *tasklet) +{ + unsigned int disablecount __diagused; + + /* Increment the disable count. */ + disablecount = atomic_inc_uint_nv(&tasklet->tl_disablecount); + KASSERT(disablecount < UINT_MAX); + + /* Wait for it to finish running, if it was running. */ + while (tasklet->tl_state & TASKLET_RUNNING) + SPINLOCK_BACKOFF_HOOK; + + /* + * Guarantee any side effects of running are visible to us + * before we return. + * + * XXX membar_sync is overkill here. It is tempting to issue + * membar_enter, but it only orders stores | loads, stores; + * what we really want here is load_acquire(&tasklet->tl_state) + * above, i.e. to witness all side effects preceding the store + * whose value we loaded. Absent that, membar_sync is the best + * we can do. + */ + membar_sync(); +} + +/* + * tasklet_enable(tasklet) + * + * Decrement tasklet's disable count. If it was previously + * scheduled to run, it may now run. + */ +void +tasklet_enable(struct tasklet_struct *tasklet) +{ + unsigned int disablecount __diagused; + + /* + * Guarantee all caller-relevant reads or writes have completed + * before potentially allowing tasklet to run again by + * decrementing the disable count. + */ + membar_exit(); + + /* Decrement the disable count. */ + disablecount = atomic_dec_uint_nv(&tasklet->tl_disablecount); + KASSERT(disablecount != UINT_MAX); +} + +/* + * tasklet_kill(tasklet) + * + * Busy-wait for tasklet to run, if it is currently scheduled. + * Caller must guarantee it does not get scheduled again for this + * to be useful. + */ +void +tasklet_kill(struct tasklet_struct *tasklet) +{ + + KASSERTMSG(!cpu_intr_p(), + "deadlock: soft interrupts are blocked in interrupt context"); + + /* Wait for it to be removed from the queue. */ + while (tasklet->tl_state & TASKLET_SCHEDULED) + SPINLOCK_BACKOFF_HOOK; + + /* + * No need for a memory barrier here because writes to the + * single state word are globally ordered, and RUNNING is set + * before SCHEDULED is cleared, so as long as the caller + * guarantees no scheduling, the only possible transitions we + * can witness are: + * + * 0 -> 0 + * SCHEDULED -> 0 + * SCHEDULED -> RUNNING + * RUNNING -> 0 + * RUNNING -> RUNNING + * SCHEDULED|RUNNING -> 0 + * SCHEDULED|RUNNING -> RUNNING + */ + + /* Wait for it to finish running. */ + while (tasklet->tl_state & TASKLET_RUNNING) + SPINLOCK_BACKOFF_HOOK; + + /* + * Wait for any side effects running. Again, membar_sync is + * overkill; we really want load_acquire(&tasklet->tl_state) + * here. + */ + membar_sync(); +}