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:
2026-07-10 15:28:55 +03:00
parent 91e8e55505
commit be9ce2a0de
2 changed files with 282 additions and 12 deletions
+38 -12
View File
@@ -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;
}