Hello!

I tried to have some threading capability with the help of `setjmp()` & `longjmp()` to save and restore context, and I failed. The calc complained with an error report like this:

Quote:

Exception occured! (System ERROR)

0e0 Read address error (probably alignment)
PC=0810178a (Error location)
TEA=68532faa (Offending address)
TRA=0x0 (Trap number)

An unrecoverable error ...


Someone interested to look into it? Did I do something wrong, or is it possible at all?

Full source code is as following. I was guessing reg[7] to be stack pointer and overwrote it nearly blindly. Confused


Code:

#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/clock.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <setjmp.h>

#define TASK_CNT        3
#define STACK_SIZE      1024

typedef struct _task_t *task_handle_t;
typedef void (*task_func_t)(task_handle_t);

typedef struct _task_t {
    jmp_buf env;
    uint8_t stack[STACK_SIZE];
    int id;
    task_func_t func;
    bool initialized;
} task_t;

static jmp_buf env_main;
static task_t tasks[TASK_CNT];

static void task_create(task_handle_t task, int id);

#define task_init(that) \
    if(0== setjmp((that)->env)){  \
        (that)->env->reg[7] = (uint32_t)((that)->stack + STACK_SIZE);   \
        (that)->initialized = true ;  \
        longjmp(env_main, (that)->id);  \
    }

#define task_yield(that) \
    if(0== setjmp((that)->env)){    \
        longjmp(env_main, (that)->id);  \
    }

static void task_run(task_handle_t that);

int main(void) {

    for (int i = 0; i < TASK_CNT; ++i) {
        task_create(tasks + i, 1 + i);
    }

    int id = setjmp(env_main);
    if (0 == id) {
        tasks[0].func(tasks);
    } else {
        int next = id;
        if (next >= TASK_CNT) {
            next = 0;
        }

        if (tasks[next].initialized) {
            longjmp(tasks[next].env, 1);
        } else {
            tasks[next].func(tasks + next);
        }
    }

    // ---------

    for (;;) {
        getkey();
    }

    return 0;
}

static void task_run(task_handle_t that) {

    task_init(that);

    char buf[64];

    for (int i = 0;; ++i) {
        sprintf(buf, "Task #%d running (%d)", that->id, i);

        dclear(C_WHITE);
        dtext(8, 48, C_BLACK, buf);
        dupdate();

        sleep_us(1000000);

        task_yield(that);
    } // for
}

static void task_create(task_handle_t task, int id) {
    task->id = id;
    task->func = task_run;
    task->initialized = false;
}
Your main problem is the way you create threads and switch the stack. While reg[7] is indeed the stack pointer, what you're saving in task_run() is the value of a stack pointer after the prelude of stack_run() has started to push stuff:

Code:
00300260 <_task_run>:
  300260:       4f 22           sts.l   pr,@-r15
  300262:       d0 1d           mov.l   3002d8 <_task_run+0x78>,r0      ! 304764 <_setjmp>
  300264:       7f f8           add     #-8,r15
  300266:       40 0b           jsr     @r0
  300268:       1f 41           mov.l   r4,@(4,r15)

You can't safely switch a stack pointer while the stack is holding uncontrolled data. You would need to figure out exactly how much it moved (12 bytes), what it holds (nothing, the task pointer, pr), and substitute the value, which really isn't reasonable.

To be honest, the whole initialization section seems kind of scuffed; from what I understand the initial setjmp/longjmp in task_init() is really just for the purpose of obtaining a new context that runs the task_run() function. You can get that more cleanly by defining a proper entry point, in assembler (thread_entry.s):

Code:
.global _thread_entry
.text

_thread_entry:
   # The following instruction is the pr of the initial jmp_buf for any
   # newly-created thread (this is where the thread starts running).
   # This context is created with the task pointer in r14 and the stack
   # set up in r15.

   # Call the task function
   mov.l   @(4, r14), r0
   jmp   @r0
   mov   r14, r4

   # The task function should never return, only yield. This here will
   # provoke a very recognizable System ERROR in case the task fails to
   # yield and actually returns.
   trapa   #42

Now with this in hand you can create new thread contexts that are pre-initialized to run thread_entry() over the new stack, without having to jump around. Here is my task_init() function:

Code:
static void task_init(task_handle_t task) {

    extern void thread_entry(void);

    uint32_t sr;
    __asm__("stc sr, %0": "=r"(sr));

    /* Craft the initial context. We give it [task] in r14 and the stack
       pointer in r15 */
    memset(&task->env[0], 0, sizeof task->env[0]);
    task->env[0].sr = sr;
    task->env[0].reg[6] = (uint32_t)task;
    task->env[0].reg[7] = (uint32_t)(task->stack + STACK_SIZE);
    task->env[0].pr = (uint32_t)thread_entry;

    task->initialized = true;
}

It does mostly the same thing as your previous setjmp(), but avoids the interference of task_run()'s prelude that I mentioned earlier, and the added complexity of doing more jumps (at the cost of not being purely standard C).

Now with this I was able to run some code from one thread before yielding; just writing some pixels on the VRAM. For reproducibility's sake, here is the full main.c file that you should compile along the assembler file above.

Code:
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/clock.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <setjmp.h>

#define TASK_CNT        3
#define STACK_SIZE      1024

typedef struct _task_t *task_handle_t;
typedef void (*task_func_t)(task_handle_t);

/* (Reordered for easier access to elements in assembly code) */
typedef struct _task_t {
    int id;
    task_func_t func;
    int initialized;
    jmp_buf env;
    uint8_t stack[STACK_SIZE];
} task_t;

static jmp_buf env_main;
static task_t tasks[TASK_CNT];

static void task_create(task_handle_t task, int id);

static void task_init(task_handle_t task) {

    extern void thread_entry(void);

    uint32_t sr;
    __asm__("stc sr, %0": "=r"(sr));

    /* Craft the initial context. We give it [task] in r14 and the stack
       pointer in r15 */
    memset(&task->env[0], 0, sizeof task->env[0]);
    task->env[0].sr = sr;
    task->env[0].reg[6] = (uint32_t)task;
    task->env[0].reg[7] = (uint32_t)(task->stack + STACK_SIZE);
    task->env[0].pr = (uint32_t)thread_entry;

    task->initialized = true;
}

#define task_yield(that) \
    if(0== setjmp((that)->env)){    \
        longjmp(env_main, (that)->id);  \
    }

static void task_run(task_handle_t that);

int main(void) {

    for (int i = 0; i < TASK_CNT; ++i) {
        task_create(tasks + i, 1 + i);
        task_init(tasks + i);
    }

    dclear(C_BLACK);

    int id = setjmp(env_main);
    if(id == 0) {
        longjmp(tasks[0].env, 1);
    }

/*
    if (0 == id) {
        tasks[0].func(tasks);
    } else {
        int next = id;
        if (next >= TASK_CNT) {
            next = 0;
        }

        if (tasks[next].initialized) {
            longjmp(tasks[next].env, 1);
        } else {
            tasks[next].func(tasks + next);
        }
    } */

    // ---------

    dprint(1, 1, C_WHITE, "yielded from %d", id);
    dupdate();
    getkey();

    return 0;
}

static void task_run(task_handle_t that) {

    while (1) {
        for (int i = 0; i < DWIDTH*2; ++i)
            gint_vram[i] = C_WHITE;

        task_yield(that);
    }

/*    char buf[64];

    for (int i = 0;; ++i) {
        sprintf(buf, "Task #%d running (%d)", that->id, i);

        dclear(C_WHITE);
        dtext(8, 48, C_BLACK, buf);
        dupdate();

        sleep_us(1000000);

        task_yield(that);
    } // for */
}

static void task_create(task_handle_t task, int id) {
    task->id = id;
    task->func = task_run;
    task->initialized = false;
}

As you can see the thread just puts two lines of white pixels above the text that is shown at the end of the main thread. The logic in main() is simplified by the fact that we don't need to call the thread differently the first time; running the thread is always just longjmp().

Expanding on this, here is a single thread being run 3 times in a row, with printing similar to your initial version:

Code:

#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/clock.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <setjmp.h>

#define TASK_CNT        3
#define STACK_SIZE      1024

typedef struct _task_t *task_handle_t;
typedef void (*task_func_t)(task_handle_t);

/* (Reordered for easier access to elements in assembly code) */
typedef struct _task_t {
    int id;
    task_func_t func;
    int initialized;
    jmp_buf env;
    uint8_t stack[STACK_SIZE];
} task_t;

static jmp_buf env_main;
static task_t tasks[TASK_CNT];

static void task_create(task_handle_t task, int id);

static void task_init(task_handle_t task) {

    extern void thread_entry(void);

    uint32_t sr;
    __asm__("stc sr, %0": "=r"(sr));

    /* Craft the initial context. We give it [task] in r14 and the stack
       pointer in r15 */
    memset(&task->env[0], 0, sizeof task->env[0]);
    task->env[0].sr = sr;
    task->env[0].reg[6] = (uint32_t)task;
    task->env[0].reg[7] = (uint32_t)(task->stack + STACK_SIZE);
    task->env[0].pr = (uint32_t)thread_entry;

    task->initialized = true;
}

#define task_yield(that) \
    if(0== setjmp((that)->env)){    \
        longjmp(env_main, (that)->id);  \
    }

static void task_run(task_handle_t that);

int main(void) {

    for (int i = 0; i < TASK_CNT; ++i) {
        task_create(tasks + i, 1 + i);
        task_init(tasks + i);
    }

    dclear(C_BLACK);

    static int runs_left = 3;

    int id = setjmp(env_main);
    if((id == 0 || id == 1) && runs_left > 0) {
        runs_left--;
        longjmp(tasks[0].env, 1);
    }

    // ---------

    dclear(0x5555);
    dprint(1, 1, C_WHITE, "yielded from %d", id);
    dupdate();
    getkey();

    return 0;
}

static void task_run(task_handle_t that) {

    char buf[64];
    /* Force i to be in memory so it's not clobbered by longjmp */
    int i[1];

    for (i[0] = 0;; ++i[0]) {
        for (int i = 0; i < 396*224; ++i)
            gint_vram[i] = C_RGB(31, 0, 0);

        sprintf(buf, "Task #%d running (%d)", that->id, i[0]);
        dtext(8, 48, C_WHITE, buf);
        dupdate();

        sleep_us(1000000);
        task_yield(that);
    }
}

static void task_create(task_handle_t task, int id) {
    task->id = id;
    task->func = task_run;
    task->initialized = false;
}

You will note that I am clearing the VRAM by hand in the task_run() function. The main culprit for the error in your main post is dclear(). It uses the DMA along with a source in ILRAM and some interrupts, which I assume is the main problem here.

dupdate() also uses the DMA and also leaves an interrupt so I'm pretty sure the DMA in itself is fine; maybe having the source data in ILRAM is the problem. I'm not sure I can diagnose this at 00:30 so I'll sign off for now. You should still be able to put the 3-thread scheduling that I removed back in main(), as long as you don't use dclear().
Hi Lephe! Thank you for helping me out! Compile & run your version, it works fine. (Good choice of bg colors btw;)). Honestly I am not able to fully understand every detail yet, but I will not investigate further, for now, maybe revisit it in the future. I will just use your code if simple threading requirements arise, before the next release of gint comes out.
Well, as a follow-up I modified my initial code according to some key points from Lephe's post and it works now. It's not as sophisticated as Lephe's version but is in straightforward plain C. Here are some of the key facts along with significant modifications I made:

* Stack grows downward, and reg[7] is stack pointer
* User allocated stack seems to require some kind of alignment. I added an offdet of 12 bytes to initial SP, otherwise the SYSTEM ERROR would still occur
* Due to the prelude in stack frame I removed the argument of the task function (which was a pointer to task_t structure and was accessed in the task function)
* Clear display pixel by pixel as Lephe did

Full source follows. Be warned though: The add-in wont exit unless you RESTART the calc Exclamation


Code:
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/clock.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <setjmp.h>

#define TASK_CNT        3
#define STACK_SIZE      1024

typedef struct _task_t *task_handle_t;
typedef void (*task_func_t)();

typedef struct _task_t {
    uint8_t stack[STACK_SIZE];
    jmp_buf env;
//    int id;
    task_func_t func;
} task_t;

static struct {
    jmp_buf env_init;
    jmp_buf env_sched;
    int task_id;
} sched_state;
static task_t tasks[TASK_CNT];

static void task_create(task_handle_t task /*, int id */);

#define task_id()   (sched_state.task_id)

#define task_get()  (tasks + task_id())

#define task_init( ) \
    task_handle_t that = task_get();    \
    if(0== setjmp((that)->env)){  \
        (that)->env->reg[7] = (uint32_t)((that)->stack + STACK_SIZE - 12);   \
        longjmp(sched_state.env_init, 1);  \
    }

#define task_yield( ) \
    task_handle_t that = task_get();    \
    if(0== setjmp((that)->env)){    \
        longjmp(sched_state.env_sched, 1);  \
    }

static void task_run();

int main(void) {

    for (int i = 0; i < TASK_CNT; ++i) {
        task_create(tasks + i /*, i */);
    }

    // Init tasks
    if (0 == setjmp(sched_state.env_init)) {
        sched_state.task_id = 0;
        tasks[0].func();
    } else {
        int next = 1 + sched_state.task_id;
        if (next < TASK_CNT) {
            sched_state.task_id = next;
            tasks[next].func();
        }
    }

    // Scheduling
    if (0 == setjmp(sched_state.env_sched)) {
        sched_state.task_id = 0;
        longjmp(tasks[0].env, 1);
    } else {
        int next = 1 + sched_state.task_id;
        if (next >= TASK_CNT) {
            next = 0;
        }

        sched_state.task_id = next;
        longjmp(tasks[next].env, 1);
    }

    // ---------

    for (;;) {
        getkey();
    }

    return 0;
}

static void task_run() {

    task_init();

    char buf[64];

    for (volatile uint16_t i = 0;; ++i) {
        sprintf(buf, "Task #%d running (%d)", task_id(), i);

        // dclear(C_WHITE);
        for (int i = 0; i < 396 * 224; ++i)
            gint_vram[i] = C_RGB(31, 0x3f, 0x1f);
        dtext(8, 48, C_BLACK, buf);
        dupdate();

        sleep_us(1000000);

        if (0 == (1 + (int) i) % 5) {
            task_yield();
        }
    } // for
}

static void task_create(task_handle_t task /*, int id */) {
//    task->id = id;
    task->func = task_run;
}
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement