H8/300H(3052), H8/300(3664)にも継続を使えるスレッドを実装しました。H8は
バス幅が16bitなので16bitCPUなのだけど、レジスタは32bitある。アドレス空
間はH8/300は16bit、H8/300Hは24bitと、レジスタ長もバス幅もアドレス空間も
異なるという挑戦的なCPUなのだ。
そして、このCPUの場合、ARM,SHのようなリンクレジスタはなく、スタックにそ れを退避する。そしてステータスレジスタ(CCR)もスタックに退避される。 それは32bitの領域に退避されて、
なのでスタックを放棄したり、つけかえたりする場合には、そのスタックにこ れらを設定するということが必要になる。
あと、まだx86が残っている。これも同じようにスタックに退避されているのだけど、 セグメントレジスタとフラグレジスタも復帰するので合計12byteのスタック領域を 設定しないといけないんだ。
そして、このCPUの場合、ARM,SHのようなリンクレジスタはなく、スタックにそ れを退避する。そしてステータスレジスタ(CCR)もスタックに退避される。 それは32bitの領域に退避されて、
|CCR| |adr|adr| ... H8/300 |CCR|adr|adr|adr| ... H8/300Hのようになる。通常のサブルーチンのリターン命令(rts)ではアドレス部分だけで リターンし、例外からのリターン命令(rte)ではこのステータスレジスタの内容も 復帰してリターンする。
なのでスタックを放棄したり、つけかえたりする場合には、そのスタックにこ れらを設定するということが必要になる。
#ifdef __NORMAL_MODE__
#define ADDRESS_MASK 0x0000ffff // 16bit address
#else
#define ADDRESS_MASK 0x00ffffff // 24bit address
#endif
#endif
thread_t
md_thread_create (thread_t tc, thread_func_t start, uint32_t arg)
{
tc->regs.er0 = arg; // pass 1st arg.
// install return address for 'rte'
既にスタックが割りあてられているのであれば、そのスタックにリターンアドレス
を設定します。これは'RTE'命令で戻るので、割り込み禁止で走るように設定。
if (tc->tc_stack)
*(uint32_t *)tc->tc_stack->bottom =
(addr_t)start | 0x80000000; // disable interrupt.
else
tc->continuation = (continuation_func_t)start;
まだスタックがない場合は、スタート場所を継続として設定。これはいずれ
スタックが割りあてられた時に、使われる。
return tc;
}
void
md_thread_stack_discard (thread_t tc)
{
tc->regs.sp = 0;
}
void
md_thread_continuation_setup (thread_t tc)
{
// Setup register set for the next context switch.
// Already stack is attached.
tc->regs.sp = (addr_t)tc->tc_stack->bottom;
if (tc->continuation)
{
ここで、はじめて設定されたスタックにリターンアドレスとステータスレジスタ
を設定します。このtc->regs.ccrはdo_thread_switchで保存されたステータス
レジスタ。
しかしこれは冗長。割り込みから中断されたコンテキストなら、継続はないの
でそもそもここは呼ばれない。ここが呼ばれるということは明示的に
do_thread_switch を呼んだということなので、常にそのステータスレジスタは
割込み禁止。
ここは、単に割り込み禁止のフラグを立てるだけでいい。はず。それはつまり
継続の一番最初は割込み禁止で入ってくるということ。
// [CCR | return address] preserve control register.
assert (!(tc->regs.ccr & ADDRESS_MASK));
assert (!((addr_t)tc->continuation & ~ADDRESS_MASK));
*(uint32_t *)tc->tc_stack->bottom = tc->regs.ccr |(addr_t)tc->continuation;
tc->continuation = NULL; // scheduled now.
}
}
ここ、継続の引数なし版と、スレッドの引数あり版の二つにしたいとも思っている。
void
md_thread_continuation_call (continuation_func_t cont)
{
// Rewind stack and call continuation.
current_thread->continuation = NULL; // dispatch here.
__asm volatile ("mov %0, sp \n jmp @%1"::
"r"(current_thread->tc_stack->bottom), "r"(cont));
// NOTREACHED
}
実際はこの程度でよかったのだけど、久々のH8、そしてH8はメモリがない。それで
メモリ破壊バグをやらかしたりして、絶望的に時間がかかってしまった。
メモリ破壊例。 000fdfc B _uart_recv 0000fe88 B _uart_send 0000ff14 B _app_tc 0000ff60 B ___sys_flag 0000ff62 A _bss_end いくらなんでもスタックがなさ過ぎ! BSS破壊していた。 0000ff80 A _stack_start これでなんとか。 000fc7a B ___st_recv 0000fc7c B _uart_recv 0000fd08 B _uart_send 0000fd94 B _app_tc 0000fde0 B ___sys_flag 0000fde2 A _bss_end 0000ff80 A _stack_startメモリ2KBはつらい。
あと、まだx86が残っている。これも同じようにスタックに退避されているのだけど、 セグメントレジスタとフラグレジスタも復帰するので合計12byteのスタック領域を 設定しないといけないんだ。
