/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * Purpose: cooperative threading between dosemu and DOS code. * * Author: Stas Sergeev */ #include #include #include "emu.h" #include "utilities.h" #include "timers.h" #include "hlt.h" #include "pcl.h" #include "coopth.h" enum CoopthRet { COOPTH_NONE, COOPTH_INPR, COOPTH_WAIT, COOPTH_SLEEP, COOPTH_LEAVE, COOPTH_DONE, COOPTH_MAX }; enum CoopthState { COOPTHS_NONE, COOPTHS_RUNNING, COOPTHS_SLEEPING, COOPTHS_LEAVE, COOPTHS_DELETE }; struct coopth_thr_t { coopth_func_t func; void *arg; }; struct coopth_thrdata_t { int *tid; enum CoopthRet ret; }; struct coopth_starter_args_t { struct coopth_thr_t *thr; struct coopth_thrdata_t *thrdata; }; struct coopth_per_thread_t { coroutine_t thread; enum CoopthState state; struct coopth_thrdata_t data; struct coopth_starter_args_t args; struct coopth_thr_t post; Bit16u ret_cs, ret_ip; int dbg; }; #define MAX_COOP_RECUR_DEPTH 5 struct coopth_t { struct coopth_thr_t start_func_tmp; int tid; char *name; Bit16u hlt_off; int off; int cur_thr; struct coopth_per_thread_t pth[MAX_COOP_RECUR_DEPTH]; }; #define MAX_COOPTHREADS 1024 static struct coopth_t coopthreads[MAX_COOPTHREADS]; static int coopth_num; static int thread_running; static int threads_running; struct coopth_tag_t { int cookie; int tid; }; #define MAX_TAGS 10 #define MAX_TAGGED_THREADS 5 static struct coopth_tag_t tags[MAX_TAGS][MAX_TAGGED_THREADS]; static int tag_cnt; #define COOP_STK_SIZE (65536*2) void coopth_init(void) { int i, j; co_thread_init(); for (i = 0; i < MAX_TAGS; i++) { for (j = 0; j < MAX_TAGGED_THREADS; j++) tags[i][j].tid = COOPTH_TID_INVALID; } } static void do_run_thread(struct coopth_per_thread_t *pth) { enum CoopthRet ret; co_call(pth->thread); ret = pth->data.ret; switch (ret) { case COOPTH_WAIT: dosemu_sleep(); break; case COOPTH_SLEEP: pth->state = COOPTHS_SLEEPING; break; case COOPTH_INPR: break; case COOPTH_LEAVE: pth->state = COOPTHS_LEAVE; break; case COOPTH_DONE: pth->state = COOPTHS_DELETE; break; default: error("Coopthreads error, exiting\n"); leavedos(2); } } static void do_del_thread(struct coopth_t *thr, struct coopth_per_thread_t *pth) { pth->state = COOPTHS_NONE; co_delete(pth->thread); thr->cur_thr--; threads_running--; if (pth->post.func) pth->post.func(pth->post.arg); } static void coopth_retf(struct coopth_per_thread_t *pth) { REG(cs) = pth->ret_cs; LWORD(eip) = pth->ret_ip; } static void coopth_hlt(Bit32u offs, void *arg) { struct coopth_t *thr = (struct coopth_t *)arg + offs; struct coopth_per_thread_t *pth; if (thr - coopthreads >= MAX_COOPTHREADS || thr->cur_thr < 0 || thr->cur_thr > MAX_COOP_RECUR_DEPTH) { error("Coopthreads error invalid thread, exiting\n"); leavedos(2); } pth = &thr->pth[thr->cur_thr - 1]; switch (pth->state) { case COOPTHS_NONE: error("Coopthreads error switch to inactive thread, exiting\n"); leavedos(2); break; case COOPTHS_RUNNING: /* We have 2 kinds of recursion: * * 1. (call it recursive thread invocation) * main_thread -> coopth_start(thread1_func) -> return * thread1_func() -> coopth_start(thread2_func) -> return * (thread 1 returned, became zombie) * thread2_func() -> return * thread2 joined * thread1 joined * main_thread... * * 2. (call it nested thread invocation) * main_thread -> coopth_start(thread1_func) -> return * thread1_func() -> do_int_call_back() -> * run_int_from_hlt() -> * coopth_start(thread2_func) -> return * thread2_func() -> return * thread2 joined * -> return from do_int_call_back() -> * return from thread1_func() * thread1 joined * main_thread... * * Both cases are supported here, but the nested invocation * is not supposed to be used as being too complex. * Since do_int_call_back() was converted * to coopth API, the nesting is avoided. * If not true, we print an error. */ if (thread_running) { static int warned; if (!warned) { warned = 1; dosemu_error("Nested thread invocation detected, please fix!\n"); } } thread_running++; do_run_thread(pth); thread_running--; break; case COOPTHS_SLEEPING: dosemu_sleep(); break; case COOPTHS_LEAVE: coopth_retf(pth); do_run_thread(pth); assert(pth->state == COOPTHS_DELETE); do_del_thread(thr, pth); break; case COOPTHS_DELETE: coopth_retf(pth); do_del_thread(thr, pth); break; } } static void coopth_thread(void *arg) { struct coopth_starter_args_t *args = arg; co_set_data(co_current(), args->thrdata); args->thr->func(args->thr->arg); args->thrdata->ret = COOPTH_DONE; } static int register_handler(char *name, void *arg, int len) { emu_hlt_t hlt_hdlr; hlt_hdlr.name = name; hlt_hdlr.start_addr = -1; hlt_hdlr.len = len; hlt_hdlr.func = coopth_hlt; hlt_hdlr.arg = arg; return hlt_register_handler(hlt_hdlr); } int coopth_create(char *name) { int num; char *nm; struct coopth_t *thr; if (coopth_num >= MAX_COOPTHREADS) { error("Too many threads\n"); config.exitearly = 1; return -1; } num = coopth_num++; nm = strdup(name); thr = &coopthreads[num]; thr->hlt_off = register_handler(nm, thr, 1); thr->name = nm; thr->cur_thr = 0; thr->off = 0; thr->tid = num; return num; } int coopth_create_multi(char *name, int len) { int i, num; char *nm; struct coopth_t *thr; u_short hlt_off; if (coopth_num + len > MAX_COOPTHREADS) { error("Too many threads\n"); config.exitearly = 1; return -1; } num = coopth_num; coopth_num += len; nm = strdup(name); hlt_off = register_handler(nm, &coopthreads[num], len); for (i = 0; i < len; i++) { thr = &coopthreads[num + i]; thr->name = nm; thr->hlt_off = hlt_off + i; thr->cur_thr = 0; thr->off = i; thr->tid = num + i; } return num; } int coopth_start(int tid, coopth_func_t func, void *arg) { struct coopth_t *thr; struct coopth_per_thread_t *pth; int tn; if (tid < 0 || tid >= coopth_num) { dosemu_error("Wrong tid\n"); leavedos(2); } thr = &coopthreads[tid]; assert(thr->tid == tid); thr->start_func_tmp.func = func; thr->start_func_tmp.arg = arg; if (thr->cur_thr >= MAX_COOP_RECUR_DEPTH) { int i; error("Coopthreads recursion depth exceeded, %s off=%x\n", thr->name, thr->off); for (i = 0; i < thr->cur_thr; i++) { error("\tthread %i state %i dbg %#x\n", i, thr->pth[i].state, thr->pth[i].dbg); } leavedos(2); } tn = thr->cur_thr++; pth = &thr->pth[tn]; pth->data.tid = &thr->tid; pth->args.thr = &thr->start_func_tmp; pth->args.thrdata = &pth->data; pth->post.func = NULL; pth->dbg = LWORD(eax); // for debug pth->thread = co_create(coopth_thread, &pth->args, NULL, COOP_STK_SIZE); if (!pth->thread) { error("Thread create failure\n"); leavedos(2); } pth->state = COOPTHS_RUNNING; threads_running++; pth->ret_cs = REG(cs); pth->ret_ip = LWORD(eip); REG(cs) = BIOS_HLT_BLK_SEG; LWORD(eip) = thr->hlt_off; return 0; } int coopth_set_post_handler(int tid, coopth_func_t func, void *arg) { struct coopth_t *thr; struct coopth_per_thread_t *pth; if (tid < 0 || tid >= coopth_num) { dosemu_error("Wrong tid\n"); leavedos(2); } thr = &coopthreads[tid]; pth = &thr->pth[thr->cur_thr - 1]; pth->post.func = func; pth->post.arg = arg; return 0; } static int __coopth_is_in_thread(void) { if (!thread_running) { static int warned; if (!warned) { warned = 1; dosemu_error("Coopth: not in thread!\n"); } } return thread_running; } static void switch_state(enum CoopthRet ret) { struct coopth_thrdata_t *thdata = co_get_data(co_current()); thdata->ret = ret; co_resume(); } void coopth_wait(void) { assert(__coopth_is_in_thread()); switch_state(COOPTH_WAIT); } void coopth_sleep(void) { assert(__coopth_is_in_thread()); switch_state(COOPTH_SLEEP); } void coopth_leave(void) { if (!__coopth_is_in_thread()) return; switch_state(COOPTH_LEAVE); } void coopth_wake_up(int tid) { struct coopth_t *thr; struct coopth_per_thread_t *pth; if (tid < 0 || tid >= coopth_num) { dosemu_error("Wrong tid\n"); leavedos(2); } thr = &coopthreads[tid]; pth = &thr->pth[thr->cur_thr - 1]; pth->state = COOPTHS_RUNNING; } void coopth_done(void) { co_thread_cleanup(); } int coopth_tag_alloc(void) { if (tag_cnt >= MAX_TAGS) { error("Too many tags\n"); leavedos(2); } return tag_cnt++; } void coopth_tag_set(int tag, int cookie) { int j, empty = -1; struct coopth_thrdata_t *thdata; struct coopth_tag_t *tagp, *tagp2; assert(__coopth_is_in_thread()); assert(tag >= 0 && tag < tag_cnt); tagp = tags[tag]; for (j = 0; j < MAX_TAGGED_THREADS; j++) { if (empty == -1 && tagp[j].tid == COOPTH_TID_INVALID) empty = j; if (tagp[j].cookie == cookie && tagp[j].tid != COOPTH_TID_INVALID) { dosemu_error("Coopth: tag %i(%i) already set\n", tag, cookie); leavedos(2); } } if (empty == -1) { dosemu_error("Coopth: too many tags for %i\n", tag); leavedos(2); } tagp2 = &tagp[empty]; thdata = co_get_data(co_current()); tagp2->tid = *thdata->tid; tagp2->cookie = cookie; } void coopth_tag_clear(int tag, int cookie) { int j; struct coopth_tag_t *tagp; assert(tag >= 0 && tag < tag_cnt); tagp = tags[tag]; for (j = 0; j < MAX_TAGGED_THREADS; j++) { if (tagp[j].cookie == cookie) { if (tagp[j].tid == COOPTH_TID_INVALID) { dosemu_error("Coopth: tag %i(%i) already cleared\n", tag, cookie); leavedos(2); } break; } } if (j >= MAX_TAGGED_THREADS) { dosemu_error("Coopth: tag %i(%i) not set\n", tag, cookie); leavedos(2); } tagp[j].tid = COOPTH_TID_INVALID; } int coopth_get_tid_by_tag(int tag, int cookie) { int j, tid = COOPTH_TID_INVALID; struct coopth_tag_t *tagp; assert(tag >= 0 && tag < tag_cnt); tagp = tags[tag]; for (j = 0; j < MAX_TAGGED_THREADS; j++) { if (tagp[j].cookie == cookie) { if (tagp[j].tid == COOPTH_TID_INVALID) { dosemu_error("Coopth: tag %i(%i) cleared\n", tag, cookie); leavedos(2); } tid = tagp[j].tid; break; } } if (tid == COOPTH_TID_INVALID) { dosemu_error("Coopth: tag %i(%i) not found\n", tag, cookie); leavedos(2); } return tid; } void coopth_sleep_tagged(int tag, int cookie) { coopth_tag_set(tag, cookie); coopth_sleep(); coopth_tag_clear(tag, cookie); }