Changeset View
Changeset View
Standalone View
Standalone View
sys/compat/linuxkpi/common/src/linux_tasklet.c
Show All 35 Lines | |||||
#include <linux/compiler.h> | #include <linux/compiler.h> | ||||
#include <linux/interrupt.h> | #include <linux/interrupt.h> | ||||
#include <linux/compat.h> | #include <linux/compat.h> | ||||
#define TASKLET_ST_IDLE 0 | #define TASKLET_ST_IDLE 0 | ||||
#define TASKLET_ST_BUSY 1 | #define TASKLET_ST_BUSY 1 | ||||
#define TASKLET_ST_EXEC 2 | #define TASKLET_ST_EXEC 2 | ||||
#define TASKLET_ST_LOOP 3 | #define TASKLET_ST_LOOP 3 | ||||
#define TASKLET_ST_PAUSED 4 | |||||
#define TASKLET_ST_CMPSET(ts, old, new) \ | #define TASKLET_ST_CMPSET(ts, old, new) \ | ||||
atomic_cmpset_ptr((volatile uintptr_t *)&(ts)->entry.tqe_prev, old, new) | atomic_cmpset_int((volatile u_int *)&(ts)->_state, old, new) | ||||
#define TASKLET_ST_SET(ts, new) \ | #define TASKLET_ST_SET(ts, new) \ | ||||
WRITE_ONCE(*(volatile uintptr_t *)&(ts)->entry.tqe_prev, new) | WRITE_ONCE(*(volatile u_int *)&(ts)->_state, new) | ||||
#define TASKLET_ST_GET(ts) \ | #define TASKLET_ST_GET(ts) \ | ||||
READ_ONCE(*(volatile uintptr_t *)&(ts)->entry.tqe_prev) | READ_ONCE(*(volatile u_int *)&(ts)->_state) | ||||
#define TASKLET_ST_TESTANDSET(ts, new) \ | |||||
atomic_testandset_int((volatile u_int *)&(ts)->_state, new) | |||||
struct tasklet_worker { | struct tasklet_worker { | ||||
struct mtx mtx; | struct mtx mtx; | ||||
TAILQ_HEAD(, tasklet_struct) head; | TAILQ_HEAD(tasklet_list, tasklet_struct) head; | ||||
struct grouptask gtask; | struct grouptask gtask; | ||||
} __aligned(CACHE_LINE_SIZE); | } __aligned(CACHE_LINE_SIZE); | ||||
#define TASKLET_WORKER_LOCK(tw) mtx_lock(&(tw)->mtx) | #define TASKLET_WORKER_LOCK(tw) mtx_lock(&(tw)->mtx) | ||||
#define TASKLET_WORKER_UNLOCK(tw) mtx_unlock(&(tw)->mtx) | #define TASKLET_WORKER_UNLOCK(tw) mtx_unlock(&(tw)->mtx) | ||||
DPCPU_DEFINE_STATIC(struct tasklet_worker, tasklet_worker); | DPCPU_DEFINE_STATIC(struct tasklet_worker, tasklet_worker); | ||||
static void | static void | ||||
tasklet_handler(void *arg) | tasklet_handler(void *arg) | ||||
{ | { | ||||
struct tasklet_worker *tw = (struct tasklet_worker *)arg; | struct tasklet_worker *tw = (struct tasklet_worker *)arg; | ||||
struct tasklet_struct *ts; | struct tasklet_struct *ts; | ||||
hselaskyUnsubmitted Done Inline Actionshselasky: ```
struct tasklet_struct *ts;
struct tasklet_struct *last;
``` | |||||
struct tasklet_struct *last; | |||||
linux_set_current(curthread); | linux_set_current(curthread); | ||||
TASKLET_WORKER_LOCK(tw); | TASKLET_WORKER_LOCK(tw); | ||||
Done Inline Actionslast = TAILQ_LAST(&tw->head); hselasky: ```
last = TAILQ_LAST(&tw->head);
``` | |||||
last = TAILQ_LAST(&tw->head, tasklet_list); | |||||
while (1) { | while (1) { | ||||
ts = TAILQ_FIRST(&tw->head); | ts = TAILQ_FIRST(&tw->head); | ||||
if (ts == NULL) | if (ts == NULL) | ||||
break; | break; | ||||
TAILQ_REMOVE(&tw->head, ts, entry); | TAILQ_REMOVE(&tw->head, ts, entry); | ||||
if (!atomic_read(&ts->count)) { | |||||
TASKLET_WORKER_UNLOCK(tw); | TASKLET_WORKER_UNLOCK(tw); | ||||
do { | do { | ||||
/* reset executing state */ | /* reset executing state */ | ||||
TASKLET_ST_SET(ts, TASKLET_ST_EXEC); | TASKLET_ST_SET(ts, TASKLET_ST_EXEC); | ||||
ts->func(ts->data); | ts->func(ts->data); | ||||
} while (TASKLET_ST_CMPSET(ts, TASKLET_ST_EXEC, TASKLET_ST_IDLE) == 0); | } while (TASKLET_ST_CMPSET(ts, TASKLET_ST_EXEC, | ||||
TASKLET_ST_IDLE) == 0); | |||||
TASKLET_WORKER_LOCK(tw); | TASKLET_WORKER_LOCK(tw); | ||||
} else { | |||||
TAILQ_INSERT_TAIL(&tw->head, ts, entry); | |||||
} | } | ||||
Done Inline Actionsif (ts == last) break; hselasky: ```
if (ts == last)
break;
``` | |||||
Done Inline ActionsYour assumption that TAILQ_FIRST() will always go into the TAILQ_INSERT_TAIL() case does not hold. Please update the code like suggested. hselasky: Your assumption that TAILQ_FIRST() will always go into the TAILQ_INSERT_TAIL() case does not… | |||||
Done Inline ActionsOh yes, I see now what you mean. johalun: Oh yes, I see now what you mean. | |||||
if (ts == last) | |||||
break; | |||||
} | |||||
TASKLET_WORKER_UNLOCK(tw); | TASKLET_WORKER_UNLOCK(tw); | ||||
} | } | ||||
static void | static void | ||||
tasklet_subsystem_init(void *arg __unused) | tasklet_subsystem_init(void *arg __unused) | ||||
{ | { | ||||
struct tasklet_worker *tw; | struct tasklet_worker *tw; | ||||
char buf[32]; | char buf[32]; | ||||
Show All 35 Lines | |||||
void | void | ||||
tasklet_init(struct tasklet_struct *ts, | tasklet_init(struct tasklet_struct *ts, | ||||
tasklet_func_t *func, unsigned long data) | tasklet_func_t *func, unsigned long data) | ||||
{ | { | ||||
ts->entry.tqe_prev = NULL; | ts->entry.tqe_prev = NULL; | ||||
ts->entry.tqe_next = NULL; | ts->entry.tqe_next = NULL; | ||||
ts->func = func; | ts->func = func; | ||||
ts->data = data; | ts->data = data; | ||||
Done Inline Actionsatomic_set(&ts->_state, TASKLET_ST_IDLE); hselasky: atomic_set(&ts->_state, TASKLET_ST_IDLE); | |||||
atomic_set_int(&(ts)->_state, TASKLET_ST_IDLE); | |||||
atomic_set(&ts->count, 0); | |||||
} | } | ||||
void | void | ||||
local_bh_enable(void) | local_bh_enable(void) | ||||
{ | { | ||||
sched_unpin(); | sched_unpin(); | ||||
} | } | ||||
void | void | ||||
local_bh_disable(void) | local_bh_disable(void) | ||||
{ | { | ||||
sched_pin(); | sched_pin(); | ||||
} | } | ||||
void | void | ||||
tasklet_schedule(struct tasklet_struct *ts) | tasklet_schedule(struct tasklet_struct *ts) | ||||
{ | { | ||||
/* tasklet is paused */ | |||||
if (atomic_read(&ts->count)) | |||||
return; | |||||
if (TASKLET_ST_CMPSET(ts, TASKLET_ST_EXEC, TASKLET_ST_LOOP)) { | if (TASKLET_ST_CMPSET(ts, TASKLET_ST_EXEC, TASKLET_ST_LOOP)) { | ||||
/* tasklet_handler() will loop */ | /* tasklet_handler() will loop */ | ||||
} else if (TASKLET_ST_CMPSET(ts, TASKLET_ST_IDLE, TASKLET_ST_BUSY)) { | } else if (TASKLET_ST_CMPSET(ts, TASKLET_ST_IDLE, TASKLET_ST_BUSY)) { | ||||
struct tasklet_worker *tw; | struct tasklet_worker *tw; | ||||
tw = &DPCPU_GET(tasklet_worker); | tw = &DPCPU_GET(tasklet_worker); | ||||
/* tasklet_handler() was not queued */ | /* tasklet_handler() was not queued */ | ||||
Show All 27 Lines | tasklet_kill(struct tasklet_struct *ts) | ||||
/* wait until tasklet is no longer busy */ | /* wait until tasklet is no longer busy */ | ||||
while (TASKLET_ST_GET(ts) != TASKLET_ST_IDLE) | while (TASKLET_ST_GET(ts) != TASKLET_ST_IDLE) | ||||
pause("W", 1); | pause("W", 1); | ||||
} | } | ||||
void | void | ||||
tasklet_enable(struct tasklet_struct *ts) | tasklet_enable(struct tasklet_struct *ts) | ||||
{ | { | ||||
(void) TASKLET_ST_CMPSET(ts, TASKLET_ST_PAUSED, TASKLET_ST_IDLE); | |||||
Done Inline Actionsatomic already has a barrier. I think this one can be removed. hselasky: atomic already has a barrier. I think this one can be removed. | |||||
atomic_dec(&ts->count); | |||||
} | } | ||||
bool | |||||
tasklet_is_enabled(struct tasklet_struct *ts) | |||||
{ | |||||
return !atomic_read(&ts->count); | |||||
} | |||||
void | void | ||||
tasklet_disable(struct tasklet_struct *ts) | tasklet_disable(struct tasklet_struct *ts) | ||||
{ | { | ||||
while (1) { | |||||
if (TASKLET_ST_GET(ts) == TASKLET_ST_PAUSED) | atomic_inc(&ts->count); | ||||
break; | tasklet_unlock_wait(ts); | ||||
Done Inline ActionsDitto. hselasky: Ditto. | |||||
if (TASKLET_ST_CMPSET(ts, TASKLET_ST_IDLE, TASKLET_ST_PAUSED)) | mb(); | ||||
break; | |||||
pause("W", 1); | |||||
} | } | ||||
int | |||||
tasklet_trylock(struct tasklet_struct *ts) | |||||
{ | |||||
return (!TASKLET_ST_TESTANDSET(ts, TASKLET_ST_BUSY)); | |||||
} | |||||
void | |||||
tasklet_unlock(struct tasklet_struct *ts) | |||||
{ | |||||
TASKLET_ST_SET(ts, TASKLET_ST_IDLE); | |||||
} | |||||
void | |||||
tasklet_unlock_wait(struct tasklet_struct *ts) | |||||
{ | |||||
WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, "tasklet_kill() can sleep"); | |||||
/* wait until tasklet is no longer busy */ | |||||
while (TASKLET_ST_GET(ts) != TASKLET_ST_IDLE) | |||||
pause("W", 1); | |||||
} | } |