FreeRTOS のコードについて質問

RXマイコン用 FreeRTOS の依存部分コードを観ていて、疑問に思う場所があります。

ソースは

portable/Renesas/RX600/portmacro.h

で、SWINT を起動する部分です。

#pragma inline_asm vPortYield

static void vPortYield( void )
{

/* Save clobbered register - may not actually be necessary if inline asm

functions are considered to use the same rules as function calls by the
compiler. */
PUSH.L R5
/* Set ITU SWINTR. */
MOV.L #553696, R5
MOV.B #1, [R5]
/* Read back to ensure the value is taken before proceeding. */
MOV.L [R5], R5
/* Restore clobbered register to its previous value. */
POP R5

}

上記の「553696」は「0x720E0」で、ICUの「ソフトウェア割り込み起動レジスタ(SWINTR)」

と思いますが、起動させる為、バイトで「1」を書いてから、32ビットで読み出しています。

ICUの説明を読んでも、書き込み後に32ビットで読み出す必要がある事は書かれていません。(RX64Mのハードウェアーマニュアル)

 

そもそも、SWINT を起動するなら、DTC用に用意してあるハードを叩かなくても、

「INT #27」のアセンブラ命令を実行すれば良いように思います。

 

何故、このような実装になっているか判る方いますか?

  • SWINTRに書き込んで読み出しているのは、CPUの命令パイプラインを経由して、ICUに確実に書き込まれた事を、その時点で確定させたいからだと思います。なぜここでそうしたいのかは、FreeRTOSのスケジューリング方式に関わる話なので、ここだけを見ていたのではよくわかりません。
    INT 命令で代替できるかどうかも、スケジューリング方式に関わる話かもしれませんので、何とも言い難いです。
    実際に試してみれば何かわかるかもしれませんよ。
  • hirakuni45さん、こんにちは。NoMaYです。#FreeRTOS kernelについて調べ始めて日は浅いですが、、、

    > バイトで「1」を書いてから、32ビットで読み出して

    私もHigetakaさんの考えと同じ考えです。その時点で確定させることが必須かどうかは別にして、作った人の思いとして、そこで確定させたい、という気持ちだったのだと思います。

    ですが、以下のスレッドに書きましたが、RXマイコンのパイプライン動作はもっと高度で、作った人の意図通りには動作しないと思われます。(レジスタをリードして、更にリード値で何らかの演算をする、とういうことをしないといけないです。)

    RX65NのSCIgのTENDフラグの挙動を調べてみた(RX631のSCIcのTENDフラグの挙動も調べてみた)
    japan.renesasrulz.com/cafe_rene/f/forum5/5642/rx65n-scig-tend-rx631-scic-tend/31463#31463

    > SWINTを起動するなら、DTC用に用意してあるハードを叩かなくても、「INT #27」のアセンブラ命令を実行すれば

    単純に代替することは出来ないです。vPortYield()はportYIELD()とportYIELD_FROM_ISR()で使われていますが、以下の2つの役割があり、この(2)において色々面倒なことになります。

    (1) 通常処理内:(当面することがないので)次に控えているタスクに制御を譲る
    (2) 割り込み処理内:(割り込み処理内でタスクのプライオリティを変化させたので)割り込み終了時に高優先度タスクに制御を移す

    #define portYIELD() vPortYield()
    #define portYIELD_FROM_ISR( x ) if( x != pdFALSE ) portYIELD()

    うまく伝えられる自信が無いですが、この(2)においては、割り込み処理内で(つまりCPUが割り込み処理中の状態に留まったままで)高優先度タスクに切り替えては駄目で、一旦割り込み処理を抜けてから高優先度タスクに切り替えないといけないですが、INT命令を使った場合には、RTOS実装者にもRTOS利用者(またはRTOS対応のソフトウェアフレームワーク開発者)にも余分に負担が掛かります。

    INT命令を使った場合、実装的には、スタックを小細工し、戻っていくタスクが、割り込み発生時のタスクとは別の高優先度タスクにリターンしていくようにするのですが、その為、タスク切り替えを引き起こす可能性のある割り込みルーチンは全て常に同じシーケンスでレジスタのPUSH/POPが行われるように(そうしないと自由にタスクを切り替えることが出来ない、そうしないとタスクを切り替えた時にスタックの整合性が狂って暴走してしまう、INT命令での割り込みルーチン内だけの話に収まらない)、割り込み処理ルーチンの最初と最後で「おまじない」ルーチンを呼び出す必要があります。

    他方、SWINT機能を使った場合、割り込みルーチンは全て今まで通りのままで良く、唯1つSWINT割り込みルーチンをRTOS実装者が気を付けて書けば良いだけになります。(SWINT割り込みルーチンのみ、スタックを小細工し、戻っていくタスクが、割り込み発生時のタスクとは別の高優先度タスクにリターンしていくようにすることが出来るようになっていれば良い。)

    ただし、デメリットもあって、タスク切り替え時間が少し長くなることです。このことは、RL78のFreeRTOSでのことですが、以下のスレッドに書いたことがあります。(なお、以下の「おまじない」というのは上に書いた「おまじない」とは意味が異なります。すみません。)

    RL78 FreeRTOS APIを特別なおまじない記述無しで割り込みルーチンから呼び出せるようにしてみた(CC-RL/GNURL78)
    japan.renesasrulz.com/cafe_rene/f/forum21/5845/rl78-freertos-api-cc-rl-gnurl78
     

  • NoMaYさん
    なるほど、深いですね。読み出して、値が期待通りになっているところまで確認すれば、CMP命令が出て、意図通りの待ちになりそうですね。(これってFreeRTOSのバグ? 条件によっては問題がおきるかも。大丈夫かな?)
  • NoMay さんこんにちは、いつもお世話になっています。

    > 作った人の思い
    まぁ、これは、そうだろうなとは思います、私が気にしているのは、32ビットアクセスです。
    0x720E0 から 0x720E3 をアクセスする点が気になります。
    0x720E0、0x720E1 はレジスターがあるので、問題は無いかもしれませんが、0x720E2、0x720E3 は
    予約領域だと思います。

    ハードウェアーマニュアルの「I/O レジスタ」の説明には・・

    > 内部 I/O レジスタの領域で、レジスタ一覧に記載のないアドレスの領域は、予約領域です。予約領域の
    アクセスは禁止します。これらのレジスタをアクセスしたときの動作および継続する動作については保
    証できませんので、アクセスしないようにしてください。

    と書かれています。

    また、「(2) I/O レジスタ書き込み時の注意事項」にある「例」のように

    > I/O レジスタがバイトサイズの場合
    MOV.L #SFR_ADDR, R1
    MOV.B #SFR_DATA, [R1]
    CMP [R1].UB, R1

    とした方が良いのではと思います。(これなら、納得です)

    asm("int #27");
    で置き換える方法に問題があるのは、RXマイコンの内部動作が良く判っていないので、
    NoMayさんの説明で、全て理解している訳ではないのですが、結構「綱渡り」な事は理解しました。
    自分でも、調べてみたいと思います。(ありがとう御座います)
    ※簡単なサンプルでは、「int #27」でも同じように動作はするようです。
  • hirakuni45さん、こんにちは。NoMaYです。

    ああっ、なるほど!バイトサイズレジスタに「バイトで「1」を書いてから、32ビットで読み出しています。」ね。全く気付きませんでした、、、確かに、これは変ですね、、、FreeRTOS v10.2.1でも同じでした。また、他の系列(RX600v2,RX200,RX100)向けのソースも同じでした。(以下のサイトからダウンロードしたものです。)

    あと、この部分では、後から気になったのですが、本当にSWINTにライトされてから実際に割り込みルーチンに分岐するまでに更に何命令か遅延があるか(或いは遅延無しで済むか)どうかです。これは、ハードウェアマニュアルに何か記載があったような記憶があるので、後で調べてみようかと思い始めました。

    FreeRTOS - sourceforge.net
    sourceforge.net/projects/freertos/files/

    それで、「簡単なサンプルでは、「int #27」でも同じように動作はするようです。」とのことですが、ひょっとして、まだ以下のような、割り込み処理内から、ブロック解除待ちとなっているタスクのブロック解除を行い、割り込み終了後すぐにそのタスクに制御を移す、といった動作は試されてないのではありませんか?(つまり、割り込み処理との関連が無いサンプルで試されているのではないですか?) そういったサンプルであれば、SWINTでも、int #27でも、これらに対するRXマイコンの動作から考えれば、違いは出ないと思います。(だからこそ、hirakuni45さんが試されているのだろうと、私は受け取っていますが、、、)

    割り込み処理内のコード例 (以下のFreeRTOSカーネル 開発者ガイドから抜粋)
    docs.aws.amazon.com/ja_jp/freertos-kernel/latest/dg/interrupt-management.html

    static uint32_t ulExampleInterruptHandler( void )
    {
        BaseType_t xHigherPriorityTaskWoken;

        /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE
        because it will get set to pdTRUE inside the interrupt-safe API function
        if a context switch is required. */

        xHigherPriorityTaskWoken = pdFALSE;

        /* Give the semaphore to unblock the task,
    passing in the address of xHigherPriorityTaskWoken
        as the interrupt-safe API function's pxHigherPriorityTaskWoken parameter. */

        xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );

        /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR().
        If xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR(),
        then calling portYIELD_FROM_ISR() will request a context switch.
        If xHigherPriorityTaskWoken is still pdFALSE,
        then calling portYIELD_FROM_ISR() will have no effect.
        Unlike most FreeRTOS ports, the Windows port requires the ISR to return a value.
        The return statement is inside the Windows version of portYIELD_FROM_ISR(). */

        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }

    ところが、割り込み処理内では、SWINTとint #27では、RXマイコンの動作が異なります。

    割り込み処理内

    (A) SWINT → 割り込み処理から抜けないとSWINTの割り込み処理ルーチンへは分岐しない
    (B) int #27 → 割り込み処理内からint #27の割り込み処理ルーチンへ分岐していく

    私が予想しているのは、以下のデファインから考えて、vPortYield()のSWINTをint #27へ書き換えるのみの場合、割り込み処理内でportYIELD_FROM_ISR()を使うようなサンプルでは動作がおかしくなる筈ではないか、というものです。

    #define portYIELD() vPortYield()
    #define portYIELD_FROM_ISR( x ) if( x != pdFALSE ) portYIELD()

    なお、前のリプライに書いたRL78のFreeRTOSのように、RL78のソフトウェア割り込み命令(BRK)を使う実装と、ソフトウェア割り込み機能もどきを使う実装の、2つの実装が可能であったように、他の部分も併せて書き換えれば、SWINTを全く使わず、int #27(もちろん他の任意のint #xxでも)のみ使う、それでもきちんと動作する、そういうポートレイヤを実装することは出来る筈だと思います。(私は、それをする場合に、RL78のソフトウェア割り込み命令(BRK)を使う実装のポートレイヤが参考になるだろう、と思ってます、、、)

    それで、以下はもともとのFreeRTOSのRL78のソフトウェア割り込み命令(BRK)を使う実装でのデモプログラムから抜き出したものですが、前のリプライに書いた「おまじない」と言うのは、以下のように、割り込み処理ルーチンの最初と最後で、決められたルーチンを呼び出す、というものです。(なお、以下はIAR社のアセンブラソースでのマクロ記述になっていますが、GCCでCソースにnaked属性を付けた関数に書くことも出来ると思います。ポイントは、割り込みルーチンの、まさに最初と最後のところ、で呼び出す、ことです。)

    FreeRTOSv10.2.1\FreeRTOS\Demo\RL78_multiple_IAR\ExampleISR.s87

        vANExampleISR_ASM_Wrapper:

    ;       portSAVE_CONTEXT() must be the first thing called in the ASM
    ;       wrapper.
            portSAVE_CONTEXT

    ;       Once the context has been saved the C handler can be called.
            call !!vAnExampleISR_C_Handler

    ;       Finally the ISR must end with a call to portRESTORE_CONTEXT()
    ;       followed by a reti instruction to return from the interrupt to whichever
    ;       task is now the task selected to run (which may be different to the task
    ;       that was running before the interrupt started).
            portRESTORE_CONTEXT
            reti

     

  • NoMay さんこんにちは。

    識者がいて、日本語で意見交換ができて、本当に助かります。

    > (B) int #27 → 割り込み処理内からint #27の割り込み処理ルーチンへ分岐していく
    これは、知らなかったです・・
    自分の理解では、割り込みの優先順位が同じなら、割り込みが終了してから(この割り込み内のRTE命令後)
    #27 のベクターに飛ぶものと思っていました・・・

    現状、SWINT に戻してありますが、これから色々実験してみたいと思います。
  • ふと思ったのですが、SWINTRにバイトで書き込んで、32ビットワードで読み出すと、RXのパイプラインでは書き込みが保証されるというような、わかりにくい仕様があるのかもしれません。
  • Higetakaさん、こんにちは。NoMaYです。

    以下の件、後で調べてみようと思います。(たぶん、別スレッドにすると思います。)

    > SWINTRにバイトで書き込んで、32ビットワードで読み出すと、RXのパイプラインでは書き込みが保証されるというような、わかりにくい仕様があるのかもしれません。

  • hirakuni45さん、こんにちは。NoMaYです。

    hirakuni45さんがQiitaに投稿されたFreeRTOSの記事があることに気付いて読ませて頂いたのですが、そこに以下のことが書かれていることに気付きました。実は、RXマイコンのMPU(メモリプロテクションユニット)を有効にしてFreeRTOSを動かしてみたいな、と以前から思っていたのですが、その為には、そもそもタスクをユーザモードで動かさないといけないので、以下のようにした理由(もしも他にも何かFreeRTOSでのRXマイコンのユーザモード/スーパバイザモードに関して遭遇したことがあればそれも)を教えて頂けると嬉しいです。


    RXマイコンの場合、FreeRTOSの起動時、「スーパーバイザモード」にしておく必要があり、自前の「start.s」に手を加えて、ユーザーモードに移行する部分をバイパスする仕組みを追加しています。


    ちなみに、MPUに関しては、かふぇルネの以下のスレッドに幾つか投稿したことがあって、全く知らないという訳では無い、です。

    RX631のメモリプロテクションユニットの件
    japan.renesasrulz.com/cafe_rene/f/forum5/4537/rx631

    RX631スタック領域等を保護するには?
    japan.renesasrulz.com/cafe_rene/f/forum5/4648/rx631
     

  • こんにちは。NoMaYです。

    長いこと放置してしまいましたが、いっそFreeRTOS.orgに質問してみようかと思い始めました。

    NoMaY wrote: said:
    ああっ、なるほど!バイトサイズレジスタに「バイトで「1」を書いてから、32ビットで読み出しています。」ね。全く気付きませんでした、、、確かに、これは変ですね、、、FreeRTOS v10.2.1でも同じでした。また、他の系列(RX600v2,RX200,RX100)向けのソースも同じでした。(以下のサイトからダウンロードしたものです。)

    あと、この部分では、後から気になったのですが、本当にSWINTにライトされてから実際に割り込みルーチンに分岐するまでに更に何命令か遅延があるか(或いは遅延無しで済むか)どうかです。これは、ハードウェアマニュアルに何か記載があったような記憶があるので、後で調べてみようかと思い始めました。[引用終]

    また、改善策のコードとしてhirakuni45さんがQiitaで公開されている以下のコードを提示してみようとも思い始めました。(以下のコードはGNURX版ですがCC-RX版でも同様なコードです。)

    RXマイコンで、FreeRTOS を使う場合の要点
    https://qiita.com/hira_kuni_45/items/0e354117f484eb038d16

    #define portYIELD()                         \
        __asm volatile                          \
        (                                       \
            "PUSH.L R10                 \n"     \
            "MOV.L  #0x872E0, R10       \n"     \
            "MOV.B  #0x1, [R10]         \n"     \
            "CMP    [R10].UB, R10       \n"     \
            "POP    R10                 \n"     \
        )