FreeRTOS kernelのRXv3-DPFPU port layerの作り方を考えてみようと思います

こんにちは。NoMaYです。

まだQiitaに掲題の投稿は見当たらなかったので考えてみようと思いました。RXv3コア搭載の品種の中でも、RX72M, RX72N, RX66Nには倍精度浮動小数点演算命令がありますが、命令の操作対象となるレジスタは、通常の汎用レジスタが使用される単精度浮動小数点演算の場合とは異なり、倍精度浮動小数点演算専用のレジスタが用意されています。また、命令の演算結果のフラグなどが格納されるレジスタも専用のものが用意されていますし、例外の発生制御を行ったり発生状況が格納されたりするレジスタにも専用のものが用意されています。ですが、既存のRXv2コア向けのFreeRTOSポートレイヤは、当然ながら、これらの倍精度浮動小数点演算専用のレジスタのことは考慮されておらず、タスク切り替え時にこれらのレジスタの内容の退避/復帰を行っていない為、タスク間で倍精度浮動小数点レジスタの値がぐちゃぐちゃに入り乱れることになってしまうので、倍精度浮動小数点演算命令の使用を諦めざるを得ないことになります。

これに対処する箇所は以下の4箇所(赤字箇所)かと思います。(まだ今は概念コードのレベルですが。)

port.c

#pragma inline_asm prvYieldHandler
static void prvYieldHandler( void )
{
    /* Re-enable interrupts. */
    SETPSW  I

    /* Move the data that was automatically pushed onto the interrupt stack when
    the interrupt occurred from the interrupt stack to the user stack.

    R15 is saved before it is clobbered. */
    PUSH.L  R15

    /* Read the user stack pointer. */
    MVFC    USP, R15

    /* Move the address down to the data being moved. */
    SUB     #12, R15
    MVTC    R15, USP

    /* Copy the data across. */
    MOV.L   [ R0 ], [ R15 ] ; R15
    MOV.L   4[ R0 ], 4[ R15 ]  ; PC
    MOV.L   8[ R0 ], 8[ R15 ]  ; PSW

    /* Move the interrupt stack pointer to its new correct position. */
    ADD #12, R0

    /* All the rest of the registers are saved directly to the user stack. */
    SETPSW  U

    /* Save the rest of the general registers (R15 has been saved already). */
    PUSHM   R1-R14
    /* Save the FPSW and accumulators. */
    MVFC    FPSW, R15
    PUSH.L  R15
    MVFACGU #0, A1, R15
    PUSH.L  R15
    MVFACHI #0, A1, R15
    PUSH.L  R15
    MVFACLO #0, A1, R15 ; Low order word.
    PUSH.L  R15
    MVFACGU #0, A0, R15
    PUSH.L  R15
    MVFACHI #0, A0, R15
    PUSH.L  R15
    MVFACLO #0, A0, R15 ; Low order word.
    PUSH.L  R15
    ここでタスク切り替え前の倍精度浮動小数点演算専用レジスタの値をスタックへ退避する
    /* Save the stack pointer to the TCB. */
    MOV.L   #_pxCurrentTCB, R15
    MOV.L   [ R15 ], R15
    MOV.L   R0, [ R15 ]

    /* Ensure the interrupt mask is set to the syscall priority while the kernel
    structures are being accessed. */
    MVTIPL  #configMAX_SYSCALL_INTERRUPT_PRIORITY

    /* Select the next task to run. */
    BSR.A   _vTaskSwitchContext

    /* Reset the interrupt mask as no more data structure access is required. */
    MVTIPL  #configKERNEL_INTERRUPT_PRIORITY

    /* Load the stack pointer of the task that is now selected as the Running
    state task from its TCB. */
    MOV.L   #_pxCurrentTCB,R15
    MOV.L   [ R15 ], R15
    MOV.L   [ R15 ], R0
    ここでタスク切り替え後の倍精度浮動小数点演算専用レジスタの値をスタックから復帰する
    /* Restore the context of the new task.  The PSW (Program Status Word) and
    PC will be popped by the RTE instruction. */
    POP     R15
    MVTACLO R15, A0     /* Accumulator low 32 bits. */
    POP     R15
    MVTACHI R15, A0     /* Accumulator high 32 bits. */
    POP     R15
    MVTACGU R15, A0     /* Accumulator guard. */
    POP     R15
    MVTACLO R15, A1     /* Accumulator low 32 bits. */
    POP     R15
    MVTACHI R15, A1     /* Accumulator high 32 bits. */
    POP     R15
    MVTACGU R15, A1     /* Accumulator guard. */
    POP     R15
    MVTC    R15,FPSW
    POPM    R1-R15
    RTE
    NOP
    NOP
}
#pragma inline_asm prvStartFirstTask
static void prvStartFirstTask( void )
{
    /* When starting the scheduler there is nothing that needs moving to the
    interrupt stack because the function is not called from an interrupt.
    Just ensure the current stack is the user stack. */
    SETPSW  U

    /* Obtain the location of the stack associated with which ever task
    pxCurrentTCB is currently pointing to. */
    MOV.L   #_pxCurrentTCB, R15
    MOV.L   [R15], R15
    MOV.L   [R15], R0
    ここでタスクのスタート時の倍精度浮動小数点演算専用レジスタの値をスタックから復帰する
    /* Restore the registers from the stack of the task pointed to by
    pxCurrentTCB. */
    POP     R15
    MVTACLO R15, A0     /* Accumulator low 32 bits. */
    POP     R15
    MVTACHI R15, A0     /* Accumulator high 32 bits. */
    POP     R15
    MVTACGU R15, A0     /* Accumulator guard. */
    POP     R15
    MVTACLO R15, A1     /* Accumulator low 32 bits. */
    POP     R15
    MVTACHI R15, A1     /* Accumulator high 32 bits. */
    POP     R15
    MVTACGU R15, A1     /* Accumulator guard. */
    POP     R15
    MVTC    R15,FPSW    /* Floating point status word. */
    POPM    R1-R15      /* R1 to R15 - R0 is not included as it is the SP. */
    RTE                 /* This pops the remaining registers. */
    NOP
    NOP
}
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    /* R0 is not included as it is the stack pointer. */

    *pxTopOfStack = 0x00;
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_PSW;
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) pxCode;
    /* When debugging it can be useful if every register is set to a known
    value.  Otherwise code space can be saved by just setting the registers
    that need to be set. */
    #ifdef USE_FULL_REGISTER_INITIALISATION
    {
        pxTopOfStack--;
        *pxTopOfStack = 0xffffffff; /* r15. */
        pxTopOfStack--;
        *pxTopOfStack = 0xeeeeeeee;
        pxTopOfStack--;
        *pxTopOfStack = 0xdddddddd;
        pxTopOfStack--;
        *pxTopOfStack = 0xcccccccc;
        pxTopOfStack--;
        *pxTopOfStack = 0xbbbbbbbb;
        pxTopOfStack--;
        *pxTopOfStack = 0xaaaaaaaa;
        pxTopOfStack--;
        *pxTopOfStack = 0x99999999;
        pxTopOfStack--;
        *pxTopOfStack = 0x88888888;
        pxTopOfStack--;
        *pxTopOfStack = 0x77777777;
        pxTopOfStack--;
        *pxTopOfStack = 0x66666666;
        pxTopOfStack--;
        *pxTopOfStack = 0x55555555;
        pxTopOfStack--;
        *pxTopOfStack = 0x44444444;
        pxTopOfStack--;
        *pxTopOfStack = 0x33333333;
        pxTopOfStack--;
        *pxTopOfStack = 0x22222222;
        pxTopOfStack--;
    }
    #else
    {
        pxTopOfStack -= 15;
    }
    #endif
    ここでタスクのスタート時の倍精度浮動小数点演算専用レジスタの値をスタックに書き込む
    *pxTopOfStack = ( StackType_t ) pvParameters; /* R1 */
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_FPSW;
    pxTopOfStack--;
    *pxTopOfStack = 0x11111111; /* Accumulator 0. */
    pxTopOfStack--;
    *pxTopOfStack = 0x22222222; /* Accumulator 0. */
    pxTopOfStack--;
    *pxTopOfStack = 0x33333333; /* Accumulator 0. */
    pxTopOfStack--;
    *pxTopOfStack = 0x44444444; /* Accumulator 1. */
    pxTopOfStack--;
    *pxTopOfStack = 0x55555555; /* Accumulator 1. */
    pxTopOfStack--;
    *pxTopOfStack = 0x66666666; /* Accumulator 1. */
    return pxTopOfStack;
}

続く

  • こんにちは。NoMaYです。

    ちなみに、RX72N搭載の三角関数演算器を扱うコードはBSPモジュールの中でも公開されていましたね。それを見て思ったのは、入力レジスタと出力レジスタが同じアドレスに割り付けられていましたけれど、もし別々になっていて且つ入力レジスタにライトした値がそのままリード出来るようになっていたら、タスク切り替え処理の中で素朴に入力レジスタ値を退避/復帰すれば出来たのかもなぁ、と思いました。

    それで、排他制御ですが、どのライブラリ関数が三角関数演算器を使っているかヘルプから分かりますので、排他制御の中でも軽い部類の、PUSH PSW,CLR PSW.I,POP PSW、あるいは、PUSH PSW,15⇒PSW.IPL,POP PSW、といったところでガードするのがやり易いでしょうかね。

  • こんにちは、hirakuni45です。

    TFU の平均的なパフォーマンスを計測して、意外と短いようなら、単純なロックビットを使う方法で行い、

    そこそこ時間がかかるようなら、セマフォを使った方法を行うのが良いと思います。

    ※TFU はそこそこ高速だと思うので、単純なロックビットで良さそうですね・・

    ※1ms以下だと、タスクを切り替える方がコストが大きそうなので・・


    GNU-RX では、TFU が関係する API は、以下のもので、CC-RX とかだと異なるのでしょうか?

    // -mtfu=intrinsic
    void __init_tfu(void);
    void __builtin_rx_sincosf(float, float*, float*);
    void __builtin_rx_atan2hypotf(float, float, float*, float*);
    // -mtfu=intrinsic,mathlib
    float __builtin_rx_sinf(float);
    float __builtin_rx_cosf(float);
    float __builtin_rx_atan2f(float, float);
    float __builtin_rx_hypotf(float, float);
  • hirakuni45さん、こんにちは。NoMaYです。

    > GNU-RX では、TFU が関係する API は、以下のもので、CC-RX とかだと異なるのでしょうか?

    もしnewlibを使うのであれば、その中でどのように三角関数演算器が使われているのか気になります。CC-RXでは、以下の画面コピーの通りになっていました。(newlibではありませんが。)



    中略





    中略








     

  • 最新のGNU-RX(gcc-8.3.0.202102-GNURX-ELF)のリリースノートに

    「2. [Improvement] - Using the -tfu option will now expand libcalls to instrinsic functions.」

    とあります・・・

    実際は、標準関数との精度の問題もあるし、勝手に呼ばれるのは、良くないと思います。

    ※初期化関数は自分で呼ばなくてはならないし・・

    スレッドセーフでもないし、呼びたい人が個別に対応する方が良いように思います。

    なので、mathlib は使わず、使いたい人がスレッドセーフにして呼び出すようにすれば良いように思います。

  • こんにちは。NoMaYです。

    FreeRTOS-KernelのGitHubの最新ソースをマージしたらRTOSDemoが動かなくなってしまいましたね、、、マージ箇所を戻したり再度マージしたりを繰り返して調べたところ、最後の最後に以下の箇所をマージしたところで動かなくなったので、portMEMORY_BARRIER()というのをRX port layerに導入しないと駄目なのかも知れないのかも。もう少し調べてみるつもりです。(すみません、最新ソースと言っても以前にマージしたのが2ヶ月前ですので2ヶ月の幅のある大雑把な言い回しになってしまっていますけれども。)

    以下、ソースの画面コピーです。

    最後の最後に以下の箇所(左側が旧で右側が新)をマージしたところで動かなくなった、、、

     

  • こんにちは。NoMaYです。

    > FreeRTOS-KernelのGitHubの最新ソースをマージしたらRTOSDemoが動かなくなってしまいましたね、、、
    > portMEMORY_BARRIER()というのをRX port layerに導入しないと駄目なのかも知れないのかも。

    やはり、導入したところ、動くようになりました。今回、CC-RXの-optimize=max -goptimize -speedとGNURXの-O3で発症しましたが、思うに、念のため、ICCRXや(神経質すぎるとは思いますが)RL78でも導入した方が良さそうに思います。以下、コードです。

    CC-RX/CC-RLの場合

    portmacro.h

    #pragma inline_asm vPortMemoryBarrier
    static void vPortMemoryBarrier( void )
    {
    }

    #define portMEMORY_BARRIER()    vPortMemoryBarrier()

     
    GNURX/ICCRX/GNURL78/LLVM-RL78の場合 (ただしe2 studio INDEXER/CODAN対策とかソース共通化処置とか除く)

    portmacro.h

    #define portMEMORY_BARRIER()    __asm volatile ( "" ::: "memory" )

     
    ICCRL78の場合 (正直なところ対策の有効性はIAR社に尋ねてみないと分からないですけれど(不要と言われるだけかも))

    portmacro.h

    #define portMEMORY_BARRIER()    __asm volatile ( "" )

     

  • NoMaYさん

    シェルティです。こんにちは。

    ありがとうございます。ちょうどFreeRTOS V10.4.4をどうしようか考えていました。

    現状10.4.3をベースにルネサスのGitHubに以下をフォークしてリリースタグ付けてあります。

    https://github.com/renesas/FreeRTOS-Kernel/releases

    次のe2 studioからFreeRTOS込のプロジェクト新規生成時には上記GitHubのリリースタグ群を参照するようになります。

    V10.4.4のタグを作る際にNoMaYさんに作っていただいたコードを上記リポジトリに取り込んで対応します。

    機を見てAWS側へのプルリクエストも実施します。以下Issueに書きました。

    https://github.com/renesas/FreeRTOS-Kernel/issues/8

    以上です

  • シェルティさん、こんにちは。NoMaYです。

    連絡ありがとうございます。先週、portMEMORY_BARRIER()の追加をしているうちに何故かテンションが盛り上がって来て、実は、今週、以下の追加をしようと考えていたところでした。それで、以下の(2)は、以前、このスレッドに挙げた問題ですが、そちらの担当さんへの業務アサインとの兼ね合いが無ければ、私の方で着手して構わないですか?

    (1) 通常のタスク処理内か、それとも割り込み処理内か、を真/偽で返すポートレイヤ関数の追加

    2ヶ月ほど前までは頻繁に本家FreeRTOSフォーラムを覗いていたのですが、そこで時々出ていた話題に、そのような判定をしたいのだけれども?というのがありました。丁度、Cortex MCUのポートレイヤにはそのような関数があってそれを使えば良いという話になるのですが、残念ながら、RX MCU(とRL78 MCU)のポートレイヤには作り込まれてはいません。そして、たまたま、RX MCUでの問い合わせが来た時、判定する具体的な方法が思い浮かんでリプライしたのですが、その後、ちょっといい顔をしようと調子こいて、そのうちプルリクエストを出しますね、とか言ってしまいました。その後、RL78にかまけていて未着手だったのですが、今からやろうとしています。(そちらで、プルリクエストを出す時には、一緒に入れて欲しいと、虫のいいことを思っていたりします。)

    (2) RX MCUのポートレイヤは、うっかりタスクからreturnしてしまった場合のフールプルーフ処置が無い

    RL78 MCUのポートレイヤには既に有りますので、それをうまく真似すれば良いだろう、と思っています。先週、テンションが盛り上がって来て、この際なので、やっておこうかな、と思い始めたところでした。

  • NoMaYさん

    こんにちはシェルティです。

    (1)いいですね!以下にプルリクエストを出しておいていただければルネサス内でもレビューしてみてAWSにもプルリクエスト出します。

    https://github.com/renesas/FreeRTOS-Kernel

    (2)こちらも助かります。以下Issueの件ですね。こちらもプルリクエストいただけると大変助かります。

    https://github.com/renesas/FreeRTOS-Kernel/issues/7

    いつもフィードバックいただき助かっております。

    これまでなかなかスピード感を持って対処できていなかったと思っていますが、RTOS関係はチームも強化しつつありGitHub上での活動をより活性化したいと考えています。またRX Driver Packageも正規の開発プロセス上でGitHubへのリリースを組み込みつつあります。

    https://github.com/renesas/rx-driver-package

    RX Driver PackageのGitHubへの展開に伴い、あるサンプルをインポートしたときに、スマートコンフィグレータ上で特定FITモジュールがローカルPCにインストールされていない場合、コンポーネントがグレーアウトしてしまい、ユーザが手動でFITモジュールをインストールしないといけない問題も修正しました。上記GitHubから必要なコンポーネントをダウンロードできるようにしました。次はボード周りのコンフィグがいまいち(新規プロジェクト作成時にデバッガ接続設定を手動で行わなければならない、など)なところを直していきたいと考えています。NoMaYさんから別途ご意見をいただいている各種TBのサンプルが古いままになっている件も修正していきたいと考えています。

    引き続きよろしくお願いいたします。

    以上です。

  • こんにちは。NoMaYです。

    > (1) 通常のタスク処理内か、それとも割り込み処理内か、を真/偽で返すポートレイヤ関数の追加

    以下のコードにしました。

    CC-RXの場合

    portmacro.h

    /* Checks whether the current execution context is interrupt.
     * Return pdTRUE if the current execution context is interrupt,
     * pdFALSE otherwise. */
    #pragma inline xPortIsInsideInterrupt
    static BaseType_t xPortIsInsideInterrupt( void )
    {
        /* When the user stack pointer is used, the context is not interrupt.
         * When the interrupt stack pointer is used, the context is interrupt.
         * Don't call this function before the scheduler has started because
         * this function always returns pdTRUE before the timing. */
        return ( __get_psw() & 0x00020000 /* PSW.U */ ) != 0 ? pdFALSE : pdTRUE;
    }

     
    ICCRXの場合

    portmacro.h

    /* Checks whether the current execution context is interrupt.
     * Return pdTRUE if the current execution context is interrupt,
     * pdFALSE otherwise. */
    #pragma inline = forced
    static __inline BaseType_t xPortIsInsideInterrupt( void )
    {
        /* When the user stack pointer is used, the context is not interrupt.
         * When the interrupt stack pointer is used, the context is interrupt.
         * Don't call this function before the scheduler has started because
         * this function always returns pdTRUE before the timing. */
        return ( __get_PSW_register() & 0x00020000 /* PSW.U */ ) != 0 ? pdFALSE : pdTRUE;
    }

     
    GNURXの場合

    portmacro.h

    /* Checks whether the current execution context is interrupt.
     * Return pdTRUE if the current execution context is interrupt,
     * pdFALSE otherwise. */
    __inline __attribute__( ( always_inline ) ) static BaseType_t xPortIsInsideInterrupt( void )
    {
        /* When the user stack pointer is used, the context is not interrupt.
         * When the interrupt stack pointer is used, the context is interrupt.
         * Don't call this function before the scheduler has started because
         * this function always returns pdTRUE before the timing. */
        return ( __builtin_rx_mvfc( 0x0 /* PSW */ ) & 0x00020000 /* PSW.U */ ) != 0 ? pdFALSE : pdTRUE;
    }

     
    CC-RL/ICCRL78/GNURL78/LLVM-RL78の場合

    portmacro.h

    /* Checks whether the current execution context is interrupt.
     * Return pdTRUE if the current execution context is interrupt,
     * pdFALSE otherwise. */
    extern BaseType_t xPortIsInsideInterrupt( void );

     
    port.c

    BaseType_t xPortIsInsideInterrupt( void )
    {
        BaseType_t xReturn;

        /* Check the value of ISP bits of PSW of the currently executing context.
         * When ISP value is equal to the initial value, the context is not interrupt.
         * When ISP value is not equal to the initial value, the context is interrupt. */
        if( portGET_PSW_ISP() == portPSW_ISP )
        {
            xReturn = pdFALSE;
        }
        else
        {
            xReturn = pdTRUE;
        }

        return xReturn;
    }