amdgpu: implement real workqueue, completion timeout, and DMA mapping
Workqueue (redox_stubs.c + redox_glue.h): - schedule_work: queues work for async execution by background worker thread - schedule_delayed_work: spawns timer thread, delays before queueing - cancel_work_sync: removes pending or waits for executing work - cancel_delayed_work_sync: cancels timer then cancels work - flush_workqueue/flush_scheduled_work: waits for queue to drain Completion timeout: - wait_for_completion_timeout uses pthread_cond_timedwait with deadline instead of ignoring timeout and blocking indefinitely DMA mapping: - dma_map_page returns page address+offset instead of bogus 0 Kconfig: - IS_ENABLED/IS_REACHABLE return actual config value instead of always 0 Cross-referenced with Linux 7.1 kernel/workqueue.c and include/uapi/linux/kernel.h.
This commit is contained in:
@@ -200,7 +200,7 @@ extern void redox_dma_free_coherent(size_t size, void *vaddr, dma_addr_t dma_han
|
||||
|
||||
#define dma_alloc_coherent(dev, size, dma_handle, flags) redox_dma_alloc_coherent((size), (dma_handle))
|
||||
#define dma_free_coherent(dev, size, vaddr, dma_handle) redox_dma_free_coherent((size), (vaddr), (dma_handle))
|
||||
#define dma_map_page(dev, page, offset, size, dir) ((dma_addr_t)0)
|
||||
#define dma_map_page(dev, page, offset, size, dir) ((dma_addr_t)(uintptr_t)(page) + (offset))
|
||||
#define dma_unmap_page(dev, addr, size, dir) /* noop */
|
||||
#define dma_map_single(dev, ptr, size, dir) ((dma_addr_t)(uintptr_t)(ptr))
|
||||
#define dma_unmap_single(dev, addr, size, dir) /* noop */
|
||||
@@ -310,23 +310,48 @@ extern void redox_free_irq(unsigned int irq, void *dev_id);
|
||||
#define IRQF_TRIGGER_FALLING 0x00000002UL
|
||||
|
||||
/* ---- Workqueue ---- */
|
||||
struct workqueue_struct;
|
||||
|
||||
struct work_struct {
|
||||
void (*func)(struct work_struct *work);
|
||||
volatile int state; /* 0=idle, 1=pending, 2=executing, 3=cancelled */
|
||||
struct work_struct *next;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t done;
|
||||
};
|
||||
|
||||
struct delayed_work {
|
||||
struct work_struct work;
|
||||
unsigned long delay;
|
||||
pthread_t timer_thread;
|
||||
volatile int timer_active;
|
||||
};
|
||||
|
||||
#define INIT_WORK(w, fn) ((w)->func = (fn))
|
||||
#define INIT_DELAYED_WORK(w, fn) INIT_WORK(&(w)->work, (fn))
|
||||
#define schedule_work(w) do { if ((w)->func) { (w)->func((w)); } } while (0)
|
||||
#define schedule_delayed_work(w, delayv) do { (void)(delayv); if ((w)->work.func) { (w)->work.func(&(w)->work); } } while (0)
|
||||
#define cancel_work_sync(w) /* noop */
|
||||
#define cancel_delayed_work_sync(w) /* noop */
|
||||
#define flush_workqueue(wq) /* noop */
|
||||
#define flush_scheduled_work() /* noop */
|
||||
extern void redox_schedule_work(struct work_struct *work);
|
||||
extern void redox_schedule_delayed_work(struct delayed_work *dwork, unsigned long delay);
|
||||
extern void redox_cancel_work_sync(struct work_struct *work);
|
||||
extern void redox_cancel_delayed_work_sync(struct delayed_work *dwork);
|
||||
extern void redox_flush_workqueue(struct workqueue_struct *wq);
|
||||
extern void redox_flush_scheduled_work(void);
|
||||
|
||||
#define INIT_WORK(w, fn) do { \
|
||||
(w)->func = (fn); \
|
||||
(w)->state = 0; \
|
||||
(w)->next = NULL; \
|
||||
pthread_mutex_init(&(w)->lock, NULL); \
|
||||
pthread_cond_init(&(w)->done, NULL); \
|
||||
} while (0)
|
||||
#define INIT_DELAYED_WORK(w, fn) do { \
|
||||
INIT_WORK(&(w)->work, (fn)); \
|
||||
(w)->delay = 0; \
|
||||
(w)->timer_active = 0; \
|
||||
} while (0)
|
||||
#define schedule_work(w) redox_schedule_work((w))
|
||||
#define schedule_delayed_work(w, delayv) redox_schedule_delayed_work((w), (delayv))
|
||||
#define cancel_work_sync(w) redox_cancel_work_sync((w))
|
||||
#define cancel_delayed_work_sync(w) redox_cancel_delayed_work_sync((w))
|
||||
#define flush_workqueue(wq) redox_flush_workqueue((wq))
|
||||
#define flush_scheduled_work() redox_flush_scheduled_work()
|
||||
|
||||
/* ---- Completion ---- */
|
||||
struct completion {
|
||||
@@ -341,6 +366,7 @@ struct completion {
|
||||
pthread_cond_init(&(c)->cond, NULL); \
|
||||
} while (0)
|
||||
#define reinit_completion(c) do { (c)->done = 0; } while (0)
|
||||
extern unsigned long redox_wait_for_completion_timeout(struct completion *c, unsigned long timeout);
|
||||
#define complete(c) do { \
|
||||
pthread_mutex_lock(&(c)->mutex); \
|
||||
(c)->done = 1; \
|
||||
@@ -354,7 +380,7 @@ struct completion {
|
||||
} \
|
||||
pthread_mutex_unlock(&(c)->mutex); \
|
||||
} while (0)
|
||||
#define wait_for_completion_timeout(c, timeout) ({ (void)(timeout); wait_for_completion((c)); 1UL; })
|
||||
#define wait_for_completion_timeout(c, timeout) redox_wait_for_completion_timeout((c), (timeout))
|
||||
|
||||
/* ---- Error helpers ---- */
|
||||
#ifndef EOPNOTSUPP
|
||||
@@ -396,8 +422,8 @@ extern unsigned long msecs_to_jiffies(unsigned int msecs);
|
||||
extern unsigned long usecs_to_jiffies(unsigned int usecs);
|
||||
|
||||
/* ---- Kconfig macros ---- */
|
||||
#define IS_ENABLED(option) 0
|
||||
#define IS_REACHABLE(option) 0
|
||||
#define IS_ENABLED(option) (option)
|
||||
#define IS_REACHABLE(option) (option)
|
||||
#ifndef CONFIG_DRM_AMDGPU
|
||||
#define CONFIG_DRM_AMDGPU 1
|
||||
#endif
|
||||
|
||||
@@ -839,3 +839,247 @@ void pm_suspend_ignore_children(struct device *dev, int enable)
|
||||
|
||||
dev_info(dev, "runtime PM ignore_children=%d\n", enable ? 1 : 0);
|
||||
}
|
||||
|
||||
/* ---- Workqueue implementation ---- */
|
||||
|
||||
enum {
|
||||
WORK_IDLE = 0,
|
||||
WORK_PENDING = 1,
|
||||
WORK_EXECUTING = 2,
|
||||
WORK_CANCELLED = 3,
|
||||
};
|
||||
|
||||
static pthread_mutex_t g_work_queue_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t g_work_queue_cond = PTHREAD_COND_INITIALIZER;
|
||||
static struct work_struct *g_work_queue_head;
|
||||
static struct work_struct *g_work_queue_tail;
|
||||
static volatile int g_work_executing_count;
|
||||
static volatile int g_work_worker_running;
|
||||
static pthread_t g_work_worker_thread;
|
||||
|
||||
static void *redox_work_worker_main(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
|
||||
|
||||
for (;;) {
|
||||
struct work_struct *work = NULL;
|
||||
|
||||
pthread_mutex_lock(&g_work_queue_lock);
|
||||
while (!g_work_queue_head && g_work_worker_running) {
|
||||
pthread_cond_wait(&g_work_queue_cond, &g_work_queue_lock);
|
||||
}
|
||||
if (!g_work_worker_running) {
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
break;
|
||||
}
|
||||
work = g_work_queue_head;
|
||||
if (work) {
|
||||
g_work_queue_head = work->next;
|
||||
if (!g_work_queue_head) {
|
||||
g_work_queue_tail = NULL;
|
||||
}
|
||||
work->next = NULL;
|
||||
work->state = WORK_EXECUTING;
|
||||
__sync_fetch_and_add(&g_work_executing_count, 1);
|
||||
}
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
|
||||
if (work && work->func) {
|
||||
work->func(work);
|
||||
}
|
||||
|
||||
if (work) {
|
||||
pthread_mutex_lock(&g_work_queue_lock);
|
||||
__sync_fetch_and_sub(&g_work_executing_count, 1);
|
||||
pthread_mutex_lock(&work->lock);
|
||||
if (work->state == WORK_EXECUTING) {
|
||||
work->state = WORK_IDLE;
|
||||
}
|
||||
pthread_cond_broadcast(&work->done);
|
||||
pthread_mutex_unlock(&work->lock);
|
||||
pthread_cond_broadcast(&g_work_queue_cond);
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void redox_ensure_worker(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_work_queue_lock);
|
||||
if (!g_work_worker_running) {
|
||||
g_work_worker_running = 1;
|
||||
pthread_create(&g_work_worker_thread, NULL, redox_work_worker_main, NULL);
|
||||
}
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
}
|
||||
|
||||
void redox_schedule_work(struct work_struct *work)
|
||||
{
|
||||
if (!work || !work->func) {
|
||||
return;
|
||||
}
|
||||
|
||||
redox_ensure_worker();
|
||||
|
||||
pthread_mutex_lock(&g_work_queue_lock);
|
||||
pthread_mutex_lock(&work->lock);
|
||||
if (work->state != WORK_IDLE) {
|
||||
pthread_mutex_unlock(&work->lock);
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
return;
|
||||
}
|
||||
work->state = WORK_PENDING;
|
||||
work->next = NULL;
|
||||
pthread_mutex_unlock(&work->lock);
|
||||
|
||||
if (g_work_queue_tail) {
|
||||
g_work_queue_tail->next = work;
|
||||
g_work_queue_tail = work;
|
||||
} else {
|
||||
g_work_queue_head = g_work_queue_tail = work;
|
||||
}
|
||||
pthread_cond_signal(&g_work_queue_cond);
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
}
|
||||
|
||||
static void *redox_delayed_work_timer(void *arg)
|
||||
{
|
||||
struct delayed_work *dwork = arg;
|
||||
unsigned long delay_ms = dwork->delay;
|
||||
|
||||
if (delay_ms > 0) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = delay_ms / 1000U;
|
||||
ts.tv_nsec = (long)(delay_ms % 1000U) * 1000000L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&dwork->work.lock);
|
||||
if (dwork->work.state == WORK_CANCELLED) {
|
||||
pthread_mutex_unlock(&dwork->work.lock);
|
||||
dwork->timer_active = 0;
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&dwork->work.lock);
|
||||
|
||||
redox_schedule_work(&dwork->work);
|
||||
dwork->timer_active = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void redox_schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
|
||||
{
|
||||
if (!dwork || !dwork->work.func) {
|
||||
return;
|
||||
}
|
||||
|
||||
dwork->delay = delay;
|
||||
dwork->timer_active = 1;
|
||||
|
||||
if (pthread_create(&dwork->timer_thread, NULL, redox_delayed_work_timer, dwork) != 0) {
|
||||
dwork->timer_active = 0;
|
||||
redox_schedule_work(&dwork->work);
|
||||
}
|
||||
}
|
||||
|
||||
void redox_cancel_work_sync(struct work_struct *work)
|
||||
{
|
||||
if (!work) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_work_queue_lock);
|
||||
if (work->state == WORK_PENDING) {
|
||||
struct work_struct **link = &g_work_queue_head;
|
||||
while (*link) {
|
||||
if (*link == work) {
|
||||
*link = work->next;
|
||||
if (g_work_queue_tail == work) {
|
||||
g_work_queue_tail = NULL;
|
||||
struct work_struct *t = g_work_queue_head;
|
||||
while (t && t->next) t = t->next;
|
||||
g_work_queue_tail = t;
|
||||
}
|
||||
break;
|
||||
}
|
||||
link = &(*link)->next;
|
||||
}
|
||||
work->next = NULL;
|
||||
work->state = WORK_CANCELLED;
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
|
||||
pthread_mutex_lock(&work->lock);
|
||||
while (work->state == WORK_EXECUTING) {
|
||||
pthread_cond_wait(&work->done, &work->lock);
|
||||
}
|
||||
pthread_mutex_unlock(&work->lock);
|
||||
}
|
||||
|
||||
void redox_cancel_delayed_work_sync(struct delayed_work *dwork)
|
||||
{
|
||||
if (!dwork) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dwork->timer_active) {
|
||||
pthread_mutex_lock(&dwork->work.lock);
|
||||
dwork->work.state = WORK_CANCELLED;
|
||||
pthread_mutex_unlock(&dwork->work.lock);
|
||||
pthread_join(dwork->timer_thread, NULL);
|
||||
dwork->timer_active = 0;
|
||||
}
|
||||
|
||||
redox_cancel_work_sync(&dwork->work);
|
||||
}
|
||||
|
||||
void redox_flush_workqueue(struct workqueue_struct *wq)
|
||||
{
|
||||
(void)wq;
|
||||
redox_flush_scheduled_work();
|
||||
}
|
||||
|
||||
void redox_flush_scheduled_work(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_work_queue_lock);
|
||||
while (g_work_queue_head || g_work_executing_count > 0) {
|
||||
pthread_cond_wait(&g_work_queue_cond, &g_work_queue_lock);
|
||||
}
|
||||
pthread_mutex_unlock(&g_work_queue_lock);
|
||||
}
|
||||
|
||||
/* ---- Completion timeout ---- */
|
||||
|
||||
unsigned long redox_wait_for_completion_timeout(struct completion *c, unsigned long timeout)
|
||||
{
|
||||
struct timespec abs_time;
|
||||
unsigned long timeout_ms = timeout;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &abs_time);
|
||||
abs_time.tv_sec += timeout_ms / 1000U;
|
||||
abs_time.tv_nsec += (long)(timeout_ms % 1000U) * 1000000L;
|
||||
if (abs_time.tv_nsec >= 1000000000L) {
|
||||
abs_time.tv_sec++;
|
||||
abs_time.tv_nsec -= 1000000000L;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&c->mutex);
|
||||
if (c->done) {
|
||||
pthread_mutex_unlock(&c->mutex);
|
||||
return timeout_ms > 0 ? timeout_ms : 1UL;
|
||||
}
|
||||
|
||||
int rc = pthread_cond_timedwait(&c->cond, &c->mutex, &abs_time);
|
||||
unsigned long result = 0;
|
||||
if (rc == 0 && c->done) {
|
||||
result = 1UL;
|
||||
}
|
||||
pthread_mutex_unlock(&c->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user