From be9ce2a0de50efb40483570019419b2c3a41216b Mon Sep 17 00:00:00 2001 From: vasilito Date: Fri, 10 Jul 2026 15:28:55 +0300 Subject: [PATCH] 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. --- local/recipes/gpu/amdgpu/source/redox_glue.h | 50 +++- local/recipes/gpu/amdgpu/source/redox_stubs.c | 244 ++++++++++++++++++ 2 files changed, 282 insertions(+), 12 deletions(-) diff --git a/local/recipes/gpu/amdgpu/source/redox_glue.h b/local/recipes/gpu/amdgpu/source/redox_glue.h index e05c3ac51d..9f4398bfac 100644 --- a/local/recipes/gpu/amdgpu/source/redox_glue.h +++ b/local/recipes/gpu/amdgpu/source/redox_glue.h @@ -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 diff --git a/local/recipes/gpu/amdgpu/source/redox_stubs.c b/local/recipes/gpu/amdgpu/source/redox_stubs.c index 5c7a0c0d40..ddd363d099 100644 --- a/local/recipes/gpu/amdgpu/source/redox_stubs.c +++ b/local/recipes/gpu/amdgpu/source/redox_stubs.c @@ -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; +}