割り込み処理ルーチンから別の割り込み処理ルーチンを呼ぶ

ちょっと長くなりますが、悩んでいますので、ご存じの方がおられたらよろしくお願いします。
内容ですが、以下のようなことです。

ある割り込み処理ルーチンAから、別の割り込み処理ルーチンBを呼ぶようなコードが書かれていました。
おそらくこのようなコードでは、PSWが破壊されると思いますが、割り込みがかかったメイン処理ルーチンmainに
なぜに影響があるのかが理解できません。でも、実際に、AからBを呼ばないようにすると、安定して動作するのです。
以下はその現象のサンプルです。


#pragma interrupt A_interrupt(vect=XX1)
#pragma interrupt B_interrupt(vect=XX2)

static void A_interrupt(void)
{
      B_interrupt();
}

static void B_Interrupt(void)
{
    // 処理
    // ..
}

static unsigned char ip=0;
static unsigned char op=0;

void main()
{
   while(1){
      if( ip != op ) {
         sub_func();
      }
   }
}

条件は以下となります。
Aの割り込みは頻繁にかかります。
Bの割り込みは決してかかりません。

上記処理では、ip,opが変化がないとすると、main()では
決してsub_func()は呼ばれないはずですが、Aの割り込みがかかると
なぜかsub_func()が呼ばれるようになります。
そして、Aの割り込み処理からBの割り込み処理を呼ばないようにすると
sub_func()は呼ばれないようになります。

動作の推察:
1.Aの割り込みでは、スタックに PSW(1バイト)、PC(3バイト)が保存されます。
2.AからBを呼ぶと、スタックに、0値(1バイト)、PC(3バイト)が保存されます。
3.B処理から戻るときRETIが実行され、PSWには0が取り込まれて、コールした時とは異なる値でPSWが破壊されます。
4.Aの処理終わりで、再びRETIが実行され、スタック上に保存されたPSWが復帰されます。

以上の状態となると、main()でおかしな動作にはならないと思われるのです。
ちゃんと、Aの割り込みの終わりで、PSWが戻っているので。
でも、実際にはPSWが破壊されて、おかしな動作になっていると思われるのです。

どうして、上記のような処理でおかしくなるのか、どなたかご存じでしょうか。

実を言えば、Aから割り込み処理Bを呼んだ事が不具合の原因だと思われ、呼ばないようにすると改善することはテストで確認しました。

しかし、CPUの動作仕様からすると、不具合も起こらないはずにしか思えません。
これでは、本当に不具合が修正できているのかわからないのです。

Top Replies

Parents
  • すいません。チップは、RL78/F3です。

  • 割り込みハンドラ(割り込みベクタ)からの復帰と通常の関数(サブルーチン)からの復帰はCPU内部で動作が違うのでコンパイルされた関数からの戻りとして使われる機械語が違います。RL78ならRETIとRETがあり、割り込みハンドラではRETIが戻りとして使われるので割り込みハンドラを直接関数のように呼び出すと異常な状態になります。もし、ある割り込みハンドラの内部処理を他の割り込みや通常の処理から呼び出したいならその処理を関数として作り直して呼び出すようにすれば解決します。

    www.renesas.com/.../78k0-series-instructions

  • わわいです

    逆アセンブリリスト上でもブレークポイントを設定できますんで、それでいろいろやってみてください

  • チョコです。

    >それに、この機能は未初期化のメモリアクセスをチェックするものではなくて、RAMのハード的なビット化けのチェックらしいので、今回の件には当てはまらないでしょう。

    RL78/F13のハードウェア マニュアルをECCで検索してみたのですが、DTCの所に以下のような記述がありました。ECCは通常は、データ領域に対して使うものですが、どこにもアドレスの記述はありません。DTCでの読み出しで割り込みが発生するなら、RETIでの読み出しでも同様ではないかと考えられます。

    ECCの所には、以下のような記述もあります。

    割り込みの設定をどうしているかは、分かりませんが、スタック領域は初期化しておいた方がいいのではないかいと思います。確かスタートアップにスタック領域の初期化サブルーチンがあり、そこを呼ぶところがコメントアウトされていたような記憶があります。

    以上

  • ちょっと気になったので、実際にどの様に動くかやってみました。

     簡単な割り込み関数を定義して動作を見る限り、mus.さんの仰っている通り、メイン関数に戻るところでスタックに書かれている値をPSWに戻すので、メイン関数の動作がおかしくなるという現象は見られませんでした。

     そこで、どのようなケースでおかしくなるか。意図的に動作がおかしくなるなケースを作ってみました。

    #pragma interrupt r_tau0_channel0_interrupt(vect=INTTM00, bank=RB1) //割り込み関数A
    #pragma interrupt r_tau1_channel0_interrupt(vect=INTTM10) //割り込み関数B

     割り込み関数Aに相当するのは、r_tau0_channel0_interrupt()です、この関数にレジスタバンクの設定を行っています。割り込み関数Bに相当するのがr_tau1_channel0_interrupt()です。

     普通に、割り込み関数Aから割り込み関数Bを呼ぶと、コンパイラがエラーを返しましたので、Aからはdummy_func()(通常関数)を呼ぶ様にしておき、後からdummy_func()のCALL命令のアドレスを割り込み関数Bに置き換えました。

    static void __near r_tau0_channel0_interrupt(void)
    {
      /* Start user code. Do not edit comment generated here */
      g_tau0_count++;
      P7_bit.no1 = ~P7_bit.no1;
      dummy_func();  //これの実体をr_tau1_channel0_interrupt()に置き換え
      /* End user code. Do not edit comment generated here */
    }

     CALLで割り込み関数Bを呼んだ時は、RL78のソフトウェアマニュアルでは、0x00とPC 3バイトがスタックに保存されると書いてある「RL78 ユーザズマニュアル ソフトウェア編 図4-25 CALL, CALLTの例」のですが、デバッガでステップ実行する限りでは、0x00は書かれませんでした(元から入っている値を書き換えない)。もちろん、割り込み関数BのRETIではSP+3のデータをPSWに戻していました。

     このとき(PSWの値を不正に戻す動作)、割り込み関数Aがレジスタバンク指定していれば、割り込み関数BのRETIの際に、PSWの値がおかしくなり、RBS0=RBS1=0となる事があり、メイン関数の汎用レジスタを壊しに行く動作となります。

     具体的には、66行目、176Hの"POP AX"で、レジスタバンク0の(=メイン関数で使用している)AXが上書きされています。(POP AX実行前は、AX=1でした)

     メイン関数では

     AXが上書きされたので、比較命令で期待値と逆の結果になり、呼ばれるはずのないsub_func()が呼ばれました。(この例では、AXが0で上書きされたので、初期値a=b=1と、わざわざ動作がおかしくなる様に誘導しています。)

     デバッガでステップ実行した場合、呼ばれるはずのないsub_func()が実行されました。また、motファイルを(CALL命令のアドレスを割り込み関数に)書き換えてROMに書き込み、デバッガ接続しない状態で実行してみましたが、この場合もsub_func()が呼ばれました。

     割り込み関数をレジスタバンク指定すれば、メイン関数でおかしな動作が起こる事が、容易に再現しました。

     …オリジナルのプログラムで、レジスタバンクを使っていないとすれば、別な原因かと思いますが。

    (Zフラグではなく、RBSが悪さをしているということも考えられるかと思います。)

  • tfさん

    詳細に解説いただき感謝です。

    私も、なんとか試してみたのですが、おっしゃられるとおりCALLでのSP-1にはマニュアルの00Hではなく、不定な値が残ったままでした。これを、B割り込み処理のRETIで読み込んでPWSを書き換えてしまうと、不正な動作になりえますね。特にAXの値が変わると、判断処理ではAX経由で行っていますから。

    大変助かりました。ありがとうございました。

  • チョコです。

    CALLで、「(SP-1)に00Hが書かれる」と言うのは、昔から問題になった項目です。結論として、G13、G12、G14、G10等の初期の製品では、以下に示すようにそのような記述は削除されています。

    その後のデバイスでは、「(SP-1)に00Hが書かれる」の記述が残っていました。

    新しい、RL78/G23、G22、G24も「(SP-1)に00Hが書かれる」とあったので、期待しいて、RL78/G23-064FPBで確認しましたが、00Hが書かれることはありませんでした。なぜか、RL78/G15、G16ではそのような記述はありませんでした。

    ということで、ルネサス内部でもバラバラでした、

    ところで、以下に示すmus.さんの最初の書き込みでは、(この記述はコンパイラではじかれると思いますが、) B_interrupt()から戻ってきたら(戻れたら)、PSW(やAX)の値がどうなっていてもその後には影響しないはずです。tfさんは、強引に分岐先を書き換えていましたが、mus.さんは、そのようなことをやっていないなら、面白いですが、あまり意味はないのではないかと思います。

    以上

  •  実動作としては、「(SP-1)に00Hが書かれる」事がないということですね。

     F13で00Hが書かれなかったので、(RL78では一番一般的かと思われる)G13でも試してみましたが同じでした。

     割り込みルーチンから、他の割り込みルーチンをCALL命令で呼ぶという状況を、どの様に実現しているのかは、微妙に気になっていました。GCCやLLVMならエラーにならずに、その様なコードを生成する?などとも考えていました。

     レジスタバンクの機能を使っていなければ、汎用レジスタが書き換わって動作がおかしくなるという現象は起こらないはずですし、割り込みルーチンAから割り込みルーチンBを呼べている所に、誤動作の鍵が隠されているのかも知れない、とも思いました。

  • チョコさん、tfさん

    私が勝手に、納得してしてしまっていて説明が足りませんでした。

    この現象は、Bルーチンの返りで、REIが実行されて、RBSレジスタが書き換えられて、レジスタバンクが切り替わると、Aルーチンの終了処理で(POPで)復帰されるCPUのレジスタ群は、別バンクのレジスタということになります。そして、AルーチンのREIでRBSレジスタが戻って、レジスタバンクが本来のものに切り替わりますが、そのCPUレジスタ群は元に戻っていないので、動作がおかしくなる。

    という仕組みだと理解しています。

    ここでの肝は、CALLの時にSP-1に00Hが書き込まれず、値が不定だということなのです。

  • チョコです。

    mus.さん、詳細な説明有難うございます。

    >そして、AルーチンのREIでRBSレジスタが戻って、レジスタバンクが本来のものに切り替わりますが、そのCPUレジスタ群は元に戻っていないので、動作がおかしくなる。

    ここは、厳密に言うと、この説明は「Aルーチンがレジスタバンクを切り替える方式でなく、PUSH/POPでレジスタの退避/復帰を行っている」場合の動作ですね。

    >ここでの肝は、CALLの時にSP-1に00Hが書き込まれず、値が不定だということなのです。

    はい、その通りです。この問題は、RL78/G13のころに問題となって、最終的に当時のハードウェア マニュアルは修正され、水平展開されていたはずです。

    ところが、新しいデバイスで何を参照するかで、先祖返りしたのではないかと思われます。個別のデバイスは担当者が決まっていても、ソフトウェア マニュアルのような共通的なものは、もう担当者がいなくなって、修正がされないまま残ってしまい、トラブルの元になったのかもしれませんね。

    以上

  • >ここでの肝は、CALLの時にSP-1に00Hが書き込まれず、値が不定だということなのです。

    それは違うと思います。

    レジスタバンク起因の誤動作であるのであれば、割り込みから戻る際、PSWのRBS0,RBS1が変化する事が原因で、RBS1=RBS0=0に変化した場合(=SP-1に00Hが書かれた場合)でも、誤動作の原因となります。CALLの際にSP-1に00Hが書かれない事が問題ではありません。

    問題の原因は、「CALL命令で呼んだルーチンからRETIで戻っている事」に尽きます。なぜ、このような事が起こるか(このようなバイナリがどういう課程で生成されるのか)であると考えます。

  • チョコです。

    >それは違うと思います。

    やってはいけないこと(しかも、再現する条件が十分に表現できていないこと)を議論してもあまり意味はないかと思います。(他の人にはほとんど役にたたない。)

    それよりも、殆どの人に対しては、マニュアルとデバイスの動きが異なっていることを明確にすることに意味があると思います。

    以上

Reply
  • チョコです。

    >それは違うと思います。

    やってはいけないこと(しかも、再現する条件が十分に表現できていないこと)を議論してもあまり意味はないかと思います。(他の人にはほとんど役にたたない。)

    それよりも、殆どの人に対しては、マニュアルとデバイスの動きが異なっていることを明確にすることに意味があると思います。

    以上

Children
  • ユーザズマニュアル ソフトウェア編に書かれているCALL命令の動作が、実際のマイコンの動作と異なるという点は、問題であると思いますし、マニュアルを実動作に合わせて修正するのがあるべき姿かと思います。

    CALL命令で、SP-1に00Hが書かれず不定になるという動作に関しては、(CALL-RETの組み合わせで使用する上では)問題になるケースがなく、ちょっとした知識として頭の片隅に入れておけば、といった話かと思いました。

  • 前提として、最初のメッセージに書かせていただいているように、

    #pragma interrupt A_interrupt(vect=XX1)
    #pragma interrupt B_interrupt(vect=XX2)

    レジスタバンクは使用していないことがあります。つまり、レジスタバンクは0しか使っていないということです。

    CALLで、SP-1に、00Hが書き込まれるならば、レジスタバンクは0のまま切り替わらないので、BルーチンがRETIで戻った時にフラグが書き換えられても、AルーチンがRETIで戻るならば、フラグは元に戻るという前提があるので、動作不良が起こるはずがないということがありました。

    そのため、動作不良している原因として考えられないのに、実際はBルーチンを割り込み指定を外すと正常に戻ることの確証にならないことが問題だったんです。

    しかし、マニュアルにCALLの時にSP-1に00Hが書き込まれると書かれているからと、そのことを確認しなかったのも問題でした。しかし、そうなると、マニュアルに書かれていてもそれを信じないで調べる必要が出てくるので、問題が発生した時の工数が多くなり、非常に困ったことになります。

    割り込み指定のルーチンを割り込み処理からコールすることが問題と通常認識されているかというと、高級言語(CとかC++)で開発している人たちからすると問題だと認識するのは難しいように思われますし。

  • チョコです。

    >前提として、最初のメッセージに書かせていただいているように、

    最初のメッセージの内容は頭から抜けてしまってました。

    >マニュアルに書かれていてもそれを信じないで調べる必要が出てくるので、問題が発生した時の工数が多くなり、非常に困ったことになります。

    おそらく、基本的なところはきちんとチェックされているかと思いますが、今回のような特定の条件で発生する問題に関係する内容に対しては、難しいかもしれませんね。一応、今回の誤記の問題も、Renesasの関係者には申し入れを行っているので、改善されていくと期待しています。

    以上

  • mus.さま

     話の流れが、SP-1に00Hが書き込まれない事が悪いという様に感じましたので、(SP-1に00Hを書かないという仕様で設計した)RL78の設計者は悪くない。問題の本質はCALL-RETIの組み合わせが悪い、という気持ちになりました。

    #設計を長年やっていると、「設計は悪くない、使い方が間違っている!」と言いたくなる気持ちもありまして…

    #もちろん、誤った情報が書かれているマニュアルは悪です