freebsd_network: Rework taskqueues implementation.

* Use FreeBSD's queue management directly, only retaining thread
   creation and synchronization functions from Haiku.

 * Use FreeBSD's task structure declarations.

 * Use a standard non-FAST taskqueue for taskqueue_thread.

 * Rewrite header and adjust consumers as appropriate.
This commit is contained in:
Augustin Cavalier
2022-06-21 16:17:50 -04:00
parent ed20f6660f
commit 0163191229
7 changed files with 783 additions and 469 deletions
@@ -85,10 +85,6 @@ void taskqgroup_config_gtask_init(void *ctx, struct grouptask *gtask,
gtask_fn_t *fn, const char *name);
void taskqgroup_config_gtask_deinit(struct grouptask *gtask);
#define TASK_ENQUEUED 0x1
#define TASK_SKIP_WAKEUP 0x2
#define TASK_NOENQUEUE 0x4
#define GTASK_INIT(gtask, flags, priority, func, context) do { \
(gtask)->ta_flags = flags; \
(gtask)->ta_priority = (priority); \
@@ -1,23 +0,0 @@
/*
* Copyright 2018, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _FBSD_IFLIB_SYS_TASKQUEUE_H_
#define _FBSD_IFLIB_SYS_TASKQUEUE_H_
/* include the real sys/taskqueue.h */
#include_next <sys/taskqueue.h>
typedef void (*taskqueue_callback_fn)(void *context);
enum taskqueue_callback_type {
TASKQUEUE_CALLBACK_TYPE_INIT,
TASKQUEUE_CALLBACK_TYPE_SHUTDOWN,
};
#define TASKQUEUE_CALLBACK_TYPE_MIN TASKQUEUE_CALLBACK_TYPE_INIT
#define TASKQUEUE_CALLBACK_TYPE_MAX TASKQUEUE_CALLBACK_TYPE_SHUTDOWN
#define TASKQUEUE_NUM_CALLBACKS TASKQUEUE_CALLBACK_TYPE_MAX + 1
#endif /* _FBSD_IFLIB_SYS_TASKQUEUE_H_ */
@@ -1,29 +1,63 @@
/*
* Copyright 2007 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2000 Doug Rabson
* 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 AUTHOR 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 AUTHOR 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.
*
* $FreeBSD$
*/
#ifndef _FBSD_COMPAT_SYS__TASK_H_
#define _FBSD_COMPAT_SYS__TASK_H_
#ifndef _SYS__TASK_H_
#define _SYS__TASK_H_
/* Haiku's list management */
#include <util/list.h>
#include <sys/queue.h>
typedef void (*task_fn_t)(void *context, int pending);
/*
* Each task includes a function which is called from
* taskqueue_run(). The first argument is taken from the 'ta_context'
* field of struct task and the second argument is a count of how many
* times the task was enqueued before the call to taskqueue_run().
*
* List of locks
* (c) const after init
* (q) taskqueue lock
*/
typedef void task_fn_t(void *context, int pending);
struct task {
int ta_pending;
int ta_priority;
int ta_flags;
task_fn_t ta_handler;
void *ta_argument;
struct list_link ta_link;
STAILQ_ENTRY(task) ta_link; /* (q) link for queue */
uint16_t ta_pending; /* (q) count times queued */
uint8_t ta_priority; /* (c) Priority */
uint8_t ta_flags; /* (c) Flags */
task_fn_t *ta_func; /* (c) task handler */
void *ta_context; /* (c) argument for handler */
};
#define TASK_ENQUEUED (1<<0)
#define TASK_NOENQUEUE (1<<1)
#define TASK_SKIP_WAKEUP (1<<2)
#define TASK_NEEDSGIANT (1 << 0) /* Haiku extension, OpenBSD compatibility */
#define TASK_NEEDSGIANT (1<<3) /* Haiku extension, OpenBSD compatibility */
#endif
#endif /* !_SYS__TASK_H_ */
@@ -1,15 +1,45 @@
/*
* Copyright 2007-2018, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
/*-
* Copyright (c) 2000 Doug Rabson
* 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 AUTHOR 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 AUTHOR 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.
*
* $FreeBSD$
*/
#ifndef _FBSD_COMPAT_SYS_TASKQUEUE_H_
#define _FBSD_COMPAT_SYS_TASKQUEUE_H_
#ifndef _SYS_TASKQUEUE_H_
#define _SYS_TASKQUEUE_H_
#ifndef _KERNEL
#error "no user-servicable parts inside"
#endif
#include <sys/queue.h>
#include <sys/_task.h>
#include <sys/callout.h>
#include <sys/_callout.h>
struct taskqueue;
struct taskqgroup;
struct thread;
struct timeout_task {
struct taskqueue *q;
@@ -18,48 +48,148 @@ struct timeout_task {
int f;
};
#define TASKQUEUE_NAMELEN 64
enum taskqueue_callback_type {
TASKQUEUE_CALLBACK_TYPE_INIT,
TASKQUEUE_CALLBACK_TYPE_SHUTDOWN,
};
#define TASKQUEUE_CALLBACK_TYPE_MIN TASKQUEUE_CALLBACK_TYPE_INIT
#define TASKQUEUE_CALLBACK_TYPE_MAX TASKQUEUE_CALLBACK_TYPE_SHUTDOWN
#define TASKQUEUE_NUM_CALLBACKS TASKQUEUE_CALLBACK_TYPE_MAX + 1
#define TASKQUEUE_NAMELEN 32
typedef void (*taskqueue_callback_fn)(void *context);
/*
* A notification callback function which is called from
* taskqueue_enqueue(). The context argument is given in the call to
* taskqueue_create(). This function would normally be used to allow the
* queue to arrange to run itself later (e.g., by scheduling a software
* interrupt or waking a kernel thread).
*/
typedef void (*taskqueue_enqueue_fn)(void *context);
struct taskqueue;
struct taskqueue *taskqueue_create(const char *name, int mflags,
taskqueue_enqueue_fn enqueue, void *context);
int taskqueue_start_threads(struct taskqueue **tq, int count, int pri,
const char *name, ...) __printflike(4, 5);
void taskqueue_free(struct taskqueue *tq);
void taskqueue_drain(struct taskqueue *tq, struct task *task);
void taskqueue_drain_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task);
void taskqueue_drain_all(struct taskqueue *tq);
void taskqueue_block(struct taskqueue *queue);
void taskqueue_unblock(struct taskqueue *queue);
int taskqueue_enqueue(struct taskqueue *tq, struct task *task);
int taskqueue_enqueue_timeout(struct taskqueue *queue,
struct timeout_task *ttask, int _ticks);
int taskqueue_cancel(struct taskqueue *queue, struct task *task,
u_int *pendp);
int taskqueue_cancel_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task, u_int *pendp);
taskqueue_enqueue_fn enqueue,
void *context);
int taskqueue_start_threads(struct taskqueue **tqp, int count, int pri,
const char *name, ...) __printflike(4, 5);
int taskqueue_enqueue(struct taskqueue *queue, struct task *task);
int taskqueue_enqueue_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task, int _ticks);
int taskqueue_cancel(struct taskqueue *queue, struct task *task,
u_int *pendp);
int taskqueue_cancel_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task, u_int *pendp);
void taskqueue_drain(struct taskqueue *queue, struct task *task);
void taskqueue_drain_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task);
void taskqueue_drain_all(struct taskqueue *queue);
void taskqueue_free(struct taskqueue *queue);
void taskqueue_run(struct taskqueue *queue);
void taskqueue_block(struct taskqueue *queue);
void taskqueue_unblock(struct taskqueue *queue);
int taskqueue_member(struct taskqueue *queue, struct thread *td);
void taskqueue_set_callback(struct taskqueue *queue,
enum taskqueue_callback_type cb_type,
taskqueue_callback_fn callback, void *context);
void taskqueue_thread_enqueue(void *context);
/*
* Functions for dedicated thread taskqueues
*/
void taskqueue_thread_loop(void *arg);
void taskqueue_thread_enqueue(void *context);
extern struct taskqueue *taskqueue_fast;
extern struct taskqueue *taskqueue_swi;
extern struct taskqueue *taskqueue_thread;
int taskqueue_enqueue_fast(struct taskqueue *queue, struct task *task);
struct taskqueue *taskqueue_create_fast(const char *name, int mflags,
taskqueue_enqueue_fn enqueue, void *context);
void task_init(struct task *, int prio, task_fn_t handler, void *arg);
#define TASK_INIT(taskp, prio, hand, arg) task_init(taskp, prio, hand, arg)
/*
* Initialise a task structure.
*/
void _task_init(struct task *, int prio, task_fn_t handler, void *arg);
#define TASK_INIT(taskp, prio, hand, arg) _task_init(taskp, prio, hand, arg)
#define NET_TASK_INIT TASK_INIT
void timeout_task_init(struct taskqueue *queue, struct timeout_task *timeout_task,
int priority, task_fn_t func, void *context);
void _timeout_task_init(struct taskqueue *queue,
struct timeout_task *timeout_task, int priority, task_fn_t func,
void *context);
#define TIMEOUT_TASK_INIT(queue, timeout_task, priority, func, context) \
timeout_task_init(queue, timeout_task, priority, func, context);
_timeout_task_init(queue, timeout_task, priority, func, context);
#endif
/*
* Declare a reference to a taskqueue.
*/
#define TASKQUEUE_DECLARE(name) \
extern struct taskqueue *taskqueue_##name
/*
* Define and initialise a global taskqueue that uses sleep mutexes.
*/
#define TASKQUEUE_DEFINE(name, enqueue, context, init) \
\
struct taskqueue *taskqueue_##name; \
\
static void \
taskqueue_define_##name(void *arg) \
{ \
taskqueue_##name = \
taskqueue_create(#name, M_WAITOK, (enqueue), (context)); \
init; \
} \
\
SYSINIT(taskqueue_##name, SI_SUB_INIT_IF, SI_ORDER_SECOND, \
taskqueue_define_##name, NULL); \
\
struct __hack
#define TASKQUEUE_DEFINE_THREAD(name) \
TASKQUEUE_DEFINE(name, taskqueue_thread_enqueue, &taskqueue_##name, \
taskqueue_start_threads(&taskqueue_##name, 1, PWAIT, \
"%s taskq", #name))
/*
* Define and initialise a global taskqueue that uses spin mutexes.
*/
#define TASKQUEUE_FAST_DEFINE(name, enqueue, context, init) \
\
struct taskqueue *taskqueue_##name; \
\
static void \
taskqueue_define_##name(void *arg) \
{ \
taskqueue_##name = \
taskqueue_create_fast(#name, M_WAITOK, (enqueue), \
(context)); \
init; \
} \
\
SYSINIT(taskqueue_##name, SI_SUB_INIT_IF, SI_ORDER_SECOND, \
taskqueue_define_##name, NULL); \
\
struct __hack
#define TASKQUEUE_FAST_DEFINE_THREAD(name) \
TASKQUEUE_FAST_DEFINE(name, taskqueue_thread_enqueue, \
&taskqueue_##name, taskqueue_start_threads(&taskqueue_##name \
1, PWAIT, "%s taskq", #name))
/*
* These queues are serviced by software interrupt handlers. To enqueue
* a task, call taskqueue_enqueue(taskqueue_swi, &task) or
* taskqueue_enqueue(taskqueue_swi_giant, &task).
*/
TASKQUEUE_DECLARE(swi_giant);
TASKQUEUE_DECLARE(swi);
/*
* This queue is serviced by a kernel thread. To enqueue a task, call
* taskqueue_enqueue(taskqueue_thread, &task).
*/
TASKQUEUE_DECLARE(thread);
/*
* Queue for swi handlers dispatched from fast interrupt handlers.
* These are necessarily different from the above because the queue
* must be locked with spinlocks since sleep mutex's cannot be used
* from a fast interrupt handler context.
*/
TASKQUEUE_DECLARE(fast);
struct taskqueue *taskqueue_create_fast(const char *name, int mflags,
taskqueue_enqueue_fn enqueue,
void *context);
#endif /* !_SYS_TASKQUEUE_H_ */
@@ -0,0 +1,519 @@
/*-
* Copyright (c) 2000 Doug Rabson
* 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 AUTHOR 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 AUTHOR 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>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/libkern.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/smp.h>
#include <sys/taskqueue.h>
#include <machine/stdarg.h>
static MALLOC_DEFINE(M_TASKQUEUE, "taskqueue", "Task Queues");
static void *taskqueue_giant_ih;
static void *taskqueue_ih;
struct taskqueue_busy {
struct task *tb_running;
TAILQ_ENTRY(taskqueue_busy) tb_link;
};
struct task * const TB_DRAIN_WAITER = (struct task *)0x1;
struct taskqueue {
STAILQ_HEAD(, task) tq_queue;
taskqueue_enqueue_fn tq_enqueue;
void *tq_context;
char *tq_name;
TAILQ_HEAD(, taskqueue_busy) tq_active;
struct mtx tq_mutex;
#ifdef __HAIKU__
sem_id tq_sem;
thread_id *tq_threads;
thread_id tq_thread_storage;
int tq_threadcount;
#else
struct thread **tq_threads;
#endif
int tq_tcount;
int tq_spin;
int tq_flags;
int tq_callouts;
taskqueue_callback_fn tq_callbacks[TASKQUEUE_NUM_CALLBACKS];
void *tq_cb_contexts[TASKQUEUE_NUM_CALLBACKS];
};
#define TQ_FLAGS_ACTIVE (1 << 0)
#define TQ_FLAGS_BLOCKED (1 << 1)
#define TQ_FLAGS_UNLOCKED_ENQUEUE (1 << 2)
#define DT_CALLOUT_ARMED (1 << 0)
#define DT_DRAIN_IN_PROGRESS (1 << 1)
#define TQ_LOCK(tq) \
do { \
if ((tq)->tq_spin) \
mtx_lock_spin(&(tq)->tq_mutex); \
else \
mtx_lock(&(tq)->tq_mutex); \
} while (0)
#define TQ_ASSERT_LOCKED(tq) mtx_assert(&(tq)->tq_mutex, MA_OWNED)
#define TQ_UNLOCK(tq) \
do { \
if ((tq)->tq_spin) \
mtx_unlock_spin(&(tq)->tq_mutex); \
else \
mtx_unlock(&(tq)->tq_mutex); \
} while (0)
#define TQ_ASSERT_UNLOCKED(tq) mtx_assert(&(tq)->tq_mutex, MA_NOTOWNED)
void
_timeout_task_init(struct taskqueue *queue, struct timeout_task *timeout_task,
int priority, task_fn_t func, void *context)
{
TASK_INIT(&timeout_task->t, priority, func, context);
callout_init_mtx(&timeout_task->c, &queue->tq_mutex,
CALLOUT_RETURNUNLOCKED);
timeout_task->q = queue;
timeout_task->f = 0;
}
static struct taskqueue *
_taskqueue_create(const char *name, int mflags,
taskqueue_enqueue_fn enqueue, void *context,
int mtxflags, const char *mtxname __unused)
{
struct taskqueue *queue;
char *tq_name;
tq_name = malloc(TASKQUEUE_NAMELEN, M_TASKQUEUE, mflags | M_ZERO);
if (tq_name == NULL)
return (NULL);
queue = malloc(sizeof(struct taskqueue), M_TASKQUEUE, mflags | M_ZERO);
if (queue == NULL) {
free(tq_name, M_TASKQUEUE);
return (NULL);
}
snprintf(tq_name, TASKQUEUE_NAMELEN, "%s", (name) ? name : "taskqueue");
STAILQ_INIT(&queue->tq_queue);
TAILQ_INIT(&queue->tq_active);
queue->tq_enqueue = enqueue;
queue->tq_context = context;
queue->tq_name = tq_name;
queue->tq_spin = (mtxflags & MTX_SPIN) != 0;
queue->tq_flags |= TQ_FLAGS_ACTIVE;
if (enqueue == taskqueue_thread_enqueue)
queue->tq_flags |= TQ_FLAGS_UNLOCKED_ENQUEUE;
mtx_init(&queue->tq_mutex, tq_name, NULL, mtxflags);
return (queue);
}
struct taskqueue *
taskqueue_create(const char *name, int mflags,
taskqueue_enqueue_fn enqueue, void *context)
{
return _taskqueue_create(name, mflags, enqueue, context,
MTX_DEF, name);
}
void
taskqueue_set_callback(struct taskqueue *queue,
enum taskqueue_callback_type cb_type, taskqueue_callback_fn callback,
void *context)
{
KASSERT(((cb_type >= TASKQUEUE_CALLBACK_TYPE_MIN) &&
(cb_type <= TASKQUEUE_CALLBACK_TYPE_MAX)),
("Callback type %d not valid, must be %d-%d", cb_type,
TASKQUEUE_CALLBACK_TYPE_MIN, TASKQUEUE_CALLBACK_TYPE_MAX));
KASSERT((queue->tq_callbacks[cb_type] == NULL),
("Re-initialization of taskqueue callback?"));
queue->tq_callbacks[cb_type] = callback;
queue->tq_cb_contexts[cb_type] = context;
}
void
taskqueue_free(struct taskqueue *queue)
{
TQ_LOCK(queue);
queue->tq_flags &= ~TQ_FLAGS_ACTIVE;
taskqueue_terminate(queue->tq_threads, queue);
KASSERT(TAILQ_EMPTY(&queue->tq_active), ("Tasks still running?"));
KASSERT(queue->tq_callouts == 0, ("Armed timeout tasks"));
mtx_destroy(&queue->tq_mutex);
free(queue->tq_threads, M_TASKQUEUE);
free(queue->tq_name, M_TASKQUEUE);
free(queue, M_TASKQUEUE);
}
static int
taskqueue_enqueue_locked(struct taskqueue *queue, struct task *task)
{
struct task *ins;
struct task *prev;
KASSERT(task->ta_func != NULL, ("enqueueing task with NULL func"));
/*
* Count multiple enqueues.
*/
if (task->ta_pending) {
if (task->ta_pending < USHRT_MAX)
task->ta_pending++;
TQ_UNLOCK(queue);
return (0);
}
/*
* Optimise the case when all tasks have the same priority.
*/
prev = STAILQ_LAST(&queue->tq_queue, task, ta_link);
if (!prev || prev->ta_priority >= task->ta_priority) {
STAILQ_INSERT_TAIL(&queue->tq_queue, task, ta_link);
} else {
prev = NULL;
for (ins = STAILQ_FIRST(&queue->tq_queue); ins;
prev = ins, ins = STAILQ_NEXT(ins, ta_link))
if (ins->ta_priority < task->ta_priority)
break;
if (prev)
STAILQ_INSERT_AFTER(&queue->tq_queue, prev, task, ta_link);
else
STAILQ_INSERT_HEAD(&queue->tq_queue, task, ta_link);
}
task->ta_pending = 1;
if ((queue->tq_flags & TQ_FLAGS_UNLOCKED_ENQUEUE) != 0)
TQ_UNLOCK(queue);
if ((queue->tq_flags & TQ_FLAGS_BLOCKED) == 0)
queue->tq_enqueue(queue->tq_context);
if ((queue->tq_flags & TQ_FLAGS_UNLOCKED_ENQUEUE) == 0)
TQ_UNLOCK(queue);
/* Return with lock released. */
return (0);
}
int
taskqueue_enqueue(struct taskqueue *queue, struct task *task)
{
int res;
TQ_LOCK(queue);
res = taskqueue_enqueue_locked(queue, task);
/* The lock is released inside. */
return (res);
}
static void
taskqueue_timeout_func(void *arg)
{
struct taskqueue *queue;
struct timeout_task *timeout_task;
timeout_task = arg;
queue = timeout_task->q;
KASSERT((timeout_task->f & DT_CALLOUT_ARMED) != 0, ("Stray timeout"));
timeout_task->f &= ~DT_CALLOUT_ARMED;
queue->tq_callouts--;
taskqueue_enqueue_locked(timeout_task->q, &timeout_task->t);
/* The lock is released inside. */
}
int
taskqueue_enqueue_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task, int _ticks)
{
int res;
TQ_LOCK(queue);
KASSERT(timeout_task->q == NULL || timeout_task->q == queue,
("Migrated queue"));
KASSERT(!queue->tq_spin, ("Timeout for spin-queue"));
timeout_task->q = queue;
res = timeout_task->t.ta_pending;
if (timeout_task->f & DT_DRAIN_IN_PROGRESS) {
/* Do nothing */
TQ_UNLOCK(queue);
res = -1;
} else if (_ticks == 0) {
taskqueue_enqueue_locked(queue, &timeout_task->t);
/* The lock is released inside. */
} else {
if ((timeout_task->f & DT_CALLOUT_ARMED) != 0) {
res++;
} else {
queue->tq_callouts++;
timeout_task->f |= DT_CALLOUT_ARMED;
if (_ticks < 0)
_ticks = -_ticks; /* Ignore overflow. */
}
if (_ticks > 0) {
callout_reset(&timeout_task->c, _ticks,
taskqueue_timeout_func, timeout_task);
}
TQ_UNLOCK(queue);
}
return (res);
}
static void
taskqueue_task_nop_fn(void *context, int pending)
{
}
void
taskqueue_block(struct taskqueue *queue)
{
TQ_LOCK(queue);
queue->tq_flags |= TQ_FLAGS_BLOCKED;
TQ_UNLOCK(queue);
}
void
taskqueue_unblock(struct taskqueue *queue)
{
TQ_LOCK(queue);
queue->tq_flags &= ~TQ_FLAGS_BLOCKED;
if (!STAILQ_EMPTY(&queue->tq_queue))
queue->tq_enqueue(queue->tq_context);
TQ_UNLOCK(queue);
}
static void
taskqueue_run_locked(struct taskqueue *queue)
{
struct taskqueue_busy tb;
struct taskqueue_busy *tb_first;
struct task *task;
int pending;
KASSERT(queue != NULL, ("tq is NULL"));
TQ_ASSERT_LOCKED(queue);
tb.tb_running = NULL;
while (STAILQ_FIRST(&queue->tq_queue)) {
TAILQ_INSERT_TAIL(&queue->tq_active, &tb, tb_link);
/*
* Carefully remove the first task from the queue and
* zero its pending count.
*/
task = STAILQ_FIRST(&queue->tq_queue);
KASSERT(task != NULL, ("task is NULL"));
STAILQ_REMOVE_HEAD(&queue->tq_queue, ta_link);
pending = task->ta_pending;
task->ta_pending = 0;
tb.tb_running = task;
TQ_UNLOCK(queue);
KASSERT(task->ta_func != NULL, ("task->ta_func is NULL"));
#ifdef __HAIKU__
if ((task->ta_flags & TASK_NEEDSGIANT) != 0)
mtx_lock(&Giant);
#endif
task->ta_func(task->ta_context, pending);
#ifdef __HAIKU__
if ((task->ta_flags & TASK_NEEDSGIANT) != 0)
mtx_unlock(&Giant);
#endif
TQ_LOCK(queue);
tb.tb_running = NULL;
TAILQ_REMOVE(&queue->tq_active, &tb, tb_link);
tb_first = TAILQ_FIRST(&queue->tq_active);
}
}
void
taskqueue_run(struct taskqueue *queue)
{
TQ_LOCK(queue);
taskqueue_run_locked(queue);
TQ_UNLOCK(queue);
}
static int
task_is_running(struct taskqueue *queue, struct task *task)
{
struct taskqueue_busy *tb;
TQ_ASSERT_LOCKED(queue);
TAILQ_FOREACH(tb, &queue->tq_active, tb_link) {
if (tb->tb_running == task)
return (1);
}
return (0);
}
static int
taskqueue_cancel_locked(struct taskqueue *queue, struct task *task,
u_int *pendp)
{
if (task->ta_pending > 0)
STAILQ_REMOVE(&queue->tq_queue, task, task, ta_link);
if (pendp != NULL)
*pendp = task->ta_pending;
task->ta_pending = 0;
return (task_is_running(queue, task) ? EBUSY : 0);
}
int
taskqueue_cancel(struct taskqueue *queue, struct task *task, u_int *pendp)
{
int error;
TQ_LOCK(queue);
error = taskqueue_cancel_locked(queue, task, pendp);
TQ_UNLOCK(queue);
return (error);
}
int
taskqueue_cancel_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task, u_int *pendp)
{
u_int pending, pending1;
int error;
TQ_LOCK(queue);
pending = !!(callout_stop(&timeout_task->c) > 0);
error = taskqueue_cancel_locked(queue, &timeout_task->t, &pending1);
if ((timeout_task->f & DT_CALLOUT_ARMED) != 0) {
timeout_task->f &= ~DT_CALLOUT_ARMED;
queue->tq_callouts--;
}
TQ_UNLOCK(queue);
if (pendp != NULL)
*pendp = pending + pending1;
return (error);
}
void
taskqueue_drain_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task)
{
/*
* Set flag to prevent timer from re-starting during drain:
*/
TQ_LOCK(queue);
KASSERT((timeout_task->f & DT_DRAIN_IN_PROGRESS) == 0,
("Drain already in progress"));
timeout_task->f |= DT_DRAIN_IN_PROGRESS;
TQ_UNLOCK(queue);
callout_drain(&timeout_task->c);
taskqueue_drain(queue, &timeout_task->t);
/*
* Clear flag to allow timer to re-start:
*/
TQ_LOCK(queue);
timeout_task->f &= ~DT_DRAIN_IN_PROGRESS;
TQ_UNLOCK(queue);
}
int
taskqueue_start_threads(struct taskqueue **tqp, int count, int pri,
const char *format, ...)
{
char name[64];
int error;
va_list vl;
va_start(vl, format);
vsnprintf(name, sizeof(name), format, vl);
va_end(vl);
error = _taskqueue_start_threads(tqp, count, pri, name);
return (error);
}
static inline void
taskqueue_run_callback(struct taskqueue *tq,
enum taskqueue_callback_type cb_type)
{
taskqueue_callback_fn tq_callback;
TQ_ASSERT_UNLOCKED(tq);
tq_callback = tq->tq_callbacks[cb_type];
if (tq_callback != NULL)
tq_callback(tq->tq_cb_contexts[cb_type]);
}
int
taskqueue_member(struct taskqueue *queue, struct thread *td)
{
int i, j, ret = 0;
for (i = 0, j = 0; ; i++) {
if (queue->tq_threads[i] == NULL)
continue;
if (queue->tq_threads[i] == td) {
ret = 1;
break;
}
if (++j >= queue->tq_tcount)
break;
}
return (ret);
}
struct taskqueue *
taskqueue_create_fast(const char *name, int mflags,
taskqueue_enqueue_fn enqueue, void *context)
{
return _taskqueue_create(name, mflags, enqueue, context,
MTX_SPIN, "fast_taskqueue");
}
+38 -378
View File
@@ -1,13 +1,8 @@
/*
* Copyright 2009, Colin Günther, [email protected]
* Copyright 2007, Hugo Santos. All Rights Reserved.
* Copyright 2022, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Hugo Santos, [email protected]
*/
#include "device.h"
#include <stdio.h>
@@ -17,125 +12,42 @@
#include <compat/sys/haiku-module.h>
#define TQ_FLAGS_ACTIVE (1 << 0)
#define TQ_FLAGS_BLOCKED (1 << 1)
#define TQ_FLAGS_PENDING (1 << 2)
static int _taskqueue_start_threads(struct taskqueue **taskQueue,
int count, int priority, const char *name);
static void taskqueue_terminate(struct thread **pp, struct taskqueue *tq);
#define DT_CALLOUT_ARMED (1 << 0)
#define DT_DRAIN_IN_PROGRESS (1 << 1)
struct taskqueue {
char tq_name[TASKQUEUE_NAMELEN];
struct mtx tq_mutex;
struct list tq_list;
taskqueue_enqueue_fn tq_enqueue;
void *tq_arg;
int tq_fast;
sem_id tq_sem;
thread_id *tq_threads;
thread_id tq_thread_storage;
int tq_threadcount;
int tq_flags;
int tq_callouts;
};
#define malloc kernel_malloc
#define free kernel_free
#include "fbsd_subr_taskqueue.c"
#undef malloc
#undef free
struct taskqueue *taskqueue_fast = NULL;
struct taskqueue *taskqueue_swi = NULL;
struct taskqueue *taskqueue_thread = NULL;
static struct taskqueue *
_taskqueue_create(const char *name, int mflags, int fast,
taskqueue_enqueue_fn enqueueFunction, void *context)
{
struct taskqueue *tq = malloc(sizeof(struct taskqueue));
if (tq == NULL)
return NULL;
tq->tq_fast = fast;
if (fast) {
mtx_init(&tq->tq_mutex, name, NULL, MTX_SPIN);
} else {
mtx_init(&tq->tq_mutex, name, NULL, MTX_DEF);
}
strlcpy(tq->tq_name, name, sizeof(tq->tq_name));
list_init_etc(&tq->tq_list, offsetof(struct task, ta_link));
tq->tq_enqueue = enqueueFunction;
tq->tq_arg = context;
tq->tq_sem = -1;
tq->tq_threads = NULL;
tq->tq_threadcount = 0;
tq->tq_flags = TQ_FLAGS_ACTIVE;
tq->tq_callouts = 0;
return tq;
}
static void
tq_lock(struct taskqueue *taskQueue)
{
if (taskQueue->tq_fast) {
mtx_lock_spin(&taskQueue->tq_mutex);
} else {
mtx_lock(&taskQueue->tq_mutex);
}
}
static void
tq_unlock(struct taskqueue *taskQueue)
{
if (taskQueue->tq_fast) {
mtx_unlock_spin(&taskQueue->tq_mutex);
} else {
mtx_unlock(&taskQueue->tq_mutex);
}
}
struct taskqueue *
taskqueue_create(const char *name, int mflags,
taskqueue_enqueue_fn enqueueFunction, void *context)
{
return _taskqueue_create(name, mflags, 0, enqueueFunction, context);
}
static int32
tq_handle_thread(void *data)
{
struct taskqueue *tq = data;
struct task *t;
int pending;
sem_id sem;
/* just a synchronization point */
tq_lock(tq);
sem = tq->tq_sem;
tq_unlock(tq);
taskqueue_run_callback(tq, TASKQUEUE_CALLBACK_TYPE_INIT);
TQ_LOCK(tq);
sem_id sem = tq->tq_sem;
TQ_UNLOCK(tq);
while (acquire_sem(sem) == B_NO_ERROR) {
tq_lock(tq);
t = list_remove_head_item(&tq->tq_list);
tq_unlock(tq);
if (t == NULL)
continue;
pending = t->ta_pending;
t->ta_pending = 0;
if ((t->ta_flags & TASK_NEEDSGIANT) != 0)
mtx_lock(&Giant);
t->ta_handler(t->ta_argument, pending);
if ((t->ta_flags & TASK_NEEDSGIANT) != 0)
mtx_unlock(&Giant);
TQ_LOCK(tq);
taskqueue_run_locked(tq);
TQ_UNLOCK(tq);
}
taskqueue_run_callback(tq, TASKQUEUE_CALLBACK_TYPE_SHUTDOWN);
return 0;
}
@@ -193,56 +105,21 @@ _taskqueue_start_threads(struct taskqueue **taskQueue, int count, int priority,
}
int
taskqueue_start_threads(struct taskqueue **taskQueue, int count, int priority,
const char *format, ...)
static void
taskqueue_terminate(struct thread **pp, struct taskqueue *tq)
{
/* we assume that start_threads is called in a sane place, and thus
* don't need to be locked. This is mostly due to the fact that if
* the TQ is 'fast', locking the TQ disables interrupts... and then
* we can't create semaphores, threads and bananas. */
char name[64];
int result;
va_list vl;
va_start(vl, format);
vsnprintf(name, sizeof(name), format, vl);
va_end(vl);
/*tq_lock(*tqp);*/
result = _taskqueue_start_threads(taskQueue, count, priority, name);
/*tq_unlock(*tqp);*/
return result;
}
void
taskqueue_free(struct taskqueue *taskQueue)
{
if (taskQueue == NULL)
if (tq->tq_sem == -1)
return;
/* lock and drain list? */
taskQueue->tq_flags &= ~TQ_FLAGS_ACTIVE;
if (!taskQueue->tq_fast)
mtx_destroy(&taskQueue->tq_mutex);
if (taskQueue->tq_sem != -1) {
int i;
delete_sem(tq->tq_sem);
delete_sem(taskQueue->tq_sem);
for (i = 0; i < taskQueue->tq_threadcount; i++) {
status_t status;
wait_for_thread(taskQueue->tq_threads[i], &status);
}
if (taskQueue->tq_threadcount > 1)
free(taskQueue->tq_threads);
for (int i = 0; i < tq->tq_threadcount; i++) {
status_t status;
wait_for_thread(tq->tq_threads[i], &status);
}
free(taskQueue);
if (tq->tq_threadcount > 1)
free(tq->tq_threads);
}
@@ -252,44 +129,13 @@ taskqueue_drain(struct taskqueue *taskQueue, struct task *task)
if (taskQueue == NULL)
return;
tq_lock(taskQueue);
while (task->ta_pending != 0) {
tq_unlock(taskQueue);
TQ_LOCK(taskQueue);
while (task->ta_pending != 0 || task_is_running(taskQueue, task)) {
TQ_UNLOCK(taskQueue);
snooze(0);
tq_lock(taskQueue);
TQ_LOCK(taskQueue);
}
tq_unlock(taskQueue);
}
void
taskqueue_drain_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task)
{
/*
* Set flag to prevent timer from re-starting during drain:
*/
tq_lock(queue);
KASSERT((timeout_task->f & DT_DRAIN_IN_PROGRESS) == 0,
("Drain already in progress"));
timeout_task->f |= DT_DRAIN_IN_PROGRESS;
tq_unlock(queue);
callout_drain(&timeout_task->c);
taskqueue_drain(queue, &timeout_task->t);
/*
* Clear flag to allow timer to re-start:
*/
tq_lock(queue);
timeout_task->f &= ~DT_DRAIN_IN_PROGRESS;
tq_unlock(queue);
}
static void
taskqueue_task_nop_fn(void* context, int pending)
{
TQ_UNLOCK(taskQueue);
}
@@ -309,165 +155,6 @@ taskqueue_drain_all(struct taskqueue *taskQueue)
}
static void
taskqueue_enqueue_locked(struct taskqueue *taskQueue, struct task *task)
{
/* we don't really support priorities */
if (task->ta_pending) {
task->ta_pending++;
} else {
list_add_item(&taskQueue->tq_list, task);
task->ta_pending = 1;
if ((taskQueue->tq_flags & TQ_FLAGS_BLOCKED) == 0)
taskQueue->tq_enqueue(taskQueue->tq_arg);
else
taskQueue->tq_flags |= TQ_FLAGS_PENDING;
}
tq_unlock(taskQueue);
}
int
taskqueue_enqueue(struct taskqueue *taskQueue, struct task *task)
{
tq_lock(taskQueue);
taskqueue_enqueue_locked(taskQueue, task);
/* The lock is released inside. */
return 0;
}
static void
taskqueue_timeout_func(void *arg)
{
struct taskqueue *queue;
struct timeout_task *timeout_task;
timeout_task = arg;
queue = timeout_task->q;
KASSERT((timeout_task->f & DT_CALLOUT_ARMED) != 0,
("stray timeout ('%s')", timeout_task->q->tq_name));
timeout_task->f &= ~DT_CALLOUT_ARMED;
queue->tq_callouts--;
taskqueue_enqueue_locked(timeout_task->q, &timeout_task->t);
/* The lock is released inside. */
}
int
taskqueue_enqueue_timeout(struct taskqueue *queue,
struct timeout_task *ttask, int _ticks)
{
int res;
tq_lock(queue);
KASSERT(ttask->q == NULL || ttask->q == queue,
("Migrated queue"));
ttask->q = queue;
res = ttask->t.ta_pending;
if (ttask->f & DT_DRAIN_IN_PROGRESS) {
/* Do nothing */
tq_unlock(queue);
res = -1;
} else if (_ticks == 0) {
tq_unlock(queue);
taskqueue_enqueue(queue, &ttask->t);
} else {
if ((ttask->f & DT_CALLOUT_ARMED) != 0) {
res++;
} else {
queue->tq_callouts++;
ttask->f |= DT_CALLOUT_ARMED;
if (_ticks < 0)
_ticks = -_ticks; /* Ignore overflow. */
}
tq_unlock(queue);
if (_ticks > 0) {
callout_reset(&ttask->c, _ticks,
taskqueue_timeout_func, ttask);
}
}
return (res);
}
static int
taskqueue_cancel_locked(struct taskqueue *queue, struct task *task,
u_int *pendp)
{
if (task->ta_pending > 0)
list_remove_item(&queue->tq_list, task);
if (pendp != NULL)
*pendp = task->ta_pending;
task->ta_pending = 0;
return 0;
}
int
taskqueue_cancel(struct taskqueue *queue, struct task *task, u_int *pendp)
{
int error;
tq_lock(queue);
error = taskqueue_cancel_locked(queue, task, pendp);
tq_unlock(queue);
return (error);
}
int
taskqueue_cancel_timeout(struct taskqueue *queue,
struct timeout_task *timeout_task, u_int *pendp)
{
u_int pending, pending1;
int error;
tq_lock(queue);
pending = !!(callout_stop(&timeout_task->c) > 0);
error = taskqueue_cancel_locked(queue, &timeout_task->t, &pending1);
if ((timeout_task->f & DT_CALLOUT_ARMED) != 0) {
timeout_task->f &= ~DT_CALLOUT_ARMED;
queue->tq_callouts--;
}
tq_unlock(queue);
if (pendp != NULL)
*pendp = pending + pending1;
return (error);
}
void
taskqueue_block(struct taskqueue *taskQueue)
{
if (taskQueue == NULL)
return;
tq_lock(taskQueue);
taskQueue->tq_flags |= TQ_FLAGS_BLOCKED;
tq_unlock(taskQueue);
}
void
taskqueue_unblock(struct taskqueue *taskQueue)
{
if (taskQueue == NULL)
return;
tq_lock(taskQueue);
taskQueue->tq_flags &= ~TQ_FLAGS_BLOCKED;
if (taskQueue->tq_flags & TQ_FLAGS_PENDING) {
taskQueue->tq_flags &= ~TQ_FLAGS_PENDING;
taskQueue->tq_enqueue(taskQueue->tq_arg);
}
tq_unlock(taskQueue);
}
void
taskqueue_thread_enqueue(void *context)
{
@@ -476,44 +163,17 @@ taskqueue_thread_enqueue(void *context)
}
int
taskqueue_enqueue_fast(struct taskqueue *taskQueue, struct task *task)
{
return taskqueue_enqueue(taskQueue, task);
}
struct taskqueue *
taskqueue_create_fast(const char *name, int mflags,
taskqueue_enqueue_fn enqueueFunction, void *context)
{
return _taskqueue_create(name, mflags, 1, enqueueFunction, context);
}
void
task_init(struct task *task, int prio, task_fn_t handler, void *context)
_task_init(struct task *task, int prio, task_fn_t handler, void *context)
{
task->ta_priority = prio;
task->ta_flags = 0;
task->ta_handler = handler;
task->ta_argument = context;
task->ta_func = handler;
task->ta_context = context;
task->ta_pending = 0;
}
void
timeout_task_init(struct taskqueue *queue, struct timeout_task *timeout_task,
int priority, task_fn_t func, void *context)
{
TASK_INIT(&timeout_task->t, priority, func, context);
callout_init_mtx(&timeout_task->c, &queue->tq_mutex,
CALLOUT_RETURNUNLOCKED);
timeout_task->q = queue;
timeout_task->f = 0;
}
status_t
init_taskqueues()
{
@@ -546,7 +206,7 @@ init_taskqueues()
}
if (HAIKU_DRIVER_REQUIRES(FBSD_THREAD_TASKQUEUE)) {
taskqueue_thread = taskqueue_create_fast("thread taskq", 0,
taskqueue_thread = taskqueue_create("thread taskq", 0,
taskqueue_thread_enqueue, &taskqueue_thread);
if (taskqueue_thread == NULL) {
status = B_NO_MEMORY;
@@ -20,9 +20,7 @@ struct taskq {
static void
task_set(struct task *t, void (*fn)(void *), void *arg)
{
t->ta_priority = 0;
t->ta_handler = fn;
t->ta_argument = arg;
TASK_INIT(t, 0, fn, arg);
}
@@ -41,7 +39,7 @@ task_add(struct taskq* tasq, struct task *w)
w->ta_flags |= TASK_NEEDSGIANT;
if (task_pending(w))
return 0;
return (taskqueue_enqueue_fast(tq, w) == 0);
return (taskqueue_enqueue(tq, w) == 0);
}