TOPPERS/ASP3カーネル ARMコア依存部 設計メモ 対応バージョン: Release 3.B.0 最終更新: 2015年8月18日 ---------------------------------------------------------------------- ○目次 ・略号 ・参考文献 ・ARMコア依存部の位置づけ - Thumb命令の扱い ・システム状態の管理 - プロセッサモード - カーネル管理外の割込み - 全割込みロック状態の管理 - コンテキストの管理 - CPUロック状態の管理 - 割込み優先度マスクの管理 - 割込み要求禁止フラグの管理 - 割込みに関するその他の操作 - CPSR(Program Status Register)の設定値 ・例外(割込みハンドラおよびCPU例外ハンドラ)の出入口 - ARMv6より前のプロセッサの場合 - ARMv6以降のプロセッサの場合 ・割込みハンドラの出入口処理 ・CPU例外ハンドラの出入口処理 - スタックポインタが不正の場合の対策 ○略号 CPSR Current Program Status Register FIQ Fast Interrupt Request GIC Generic Interrupt Controller IRQ Interrupt Request ○参考文献 [1] TOPPERSプロジェクト 設計メモ「ARMのアーキテクチャに関するメモ」. ○ARMコア依存部の位置づけ ARMコア依存部は,カーネルのターゲット依存部の中で,ARMv4,ARMv5,ARMv6, ARMv7-A,ARMv7-Rに準拠したプロセッサコアを持つターゲットシステム(チッ プ)に共通に使用できる部分である. ARMコアのプロセッサコア略称を"arm"とする.GNU開発環境向けのARMコア依存 部を置くディレクトリはarch/arm_gcc/commonとなる. ARMの場合,割込みコントローラはコアの外にあることから,割込みコントロー ラに依存する部分はARMコア依存部に含めないことを原則とするが,多くのチッ プに採用されている割込みコントローラであるGIC(ARM Generic Interrupt Controller)に依存する部分(GIC依存部)は,ARMコア依存部のディレクトリ に置いている.GIC依存部の設計については,「GIC依存部 設計メモ」を参照す ること. なお,ARMコア依存部は,ARMアーキテクチャのバージョン番号(4〜7のいずれ か)が,__TARGET_ARCH_ARMにマクロ定義されているものとして記述してある. ●Thumbモードの扱い アプリケーションプログラムとカーネルのC言語で記述された部分は,Thumbモー ド向けにコンパイルしても動作するようにする.それに対して,カーネルのア センブリ言語記述の部分は,ARM命令で記述し,Thumbモードで実行することは 考えない. ○システム状態の管理 ●プロセッサモード ASP3カーネルのARMコア依存部では,アプリケーションプログラムとカーネルの 大部分を,スーパバイザモードで実行する.例外の入口でそれぞれのプロセッ サモードに切り換わるが,入口処理の中でスーパバイザモードに戻すため,あ くまでカーネルの内部で一時的に他のモードを使うに過ぎない. 割込み処理の実行にIRQモードを使わないのは,次の理由である.IRQモードで 実行中に割込みが発生すると,lrレジスタに戻り番地が格納される.そのため, 割込み処理をIRQモードを実行すると,多重割込みによりlrレジスタの内容が失 われるため,多重割込みを許可することができない. アプリケーションプログラムとカーネルの大部分の実行にシステムモードを使 わない理由は,以下の通りである. (1) ARMv5以前のプロセッサでは,例外からの復帰を, ldmfd sp!, {<復帰すべきレジスタのリスト>, pc}^ の命令で行う.この命令は,例外モードでのみ使用することができ,システム モードでは使用できない(システムモードにはSPSRがないため,CPSRを復帰で きない).そのため,割込みハンドラやCPU例外ハンドラをシステムモードで実 行すると,例外からのリターン時に,余計なプロセッサモード切換えとそれに 伴うスタック操作が必要になり,効率が悪い. ARMv6以降のプロセッサでは,例外からの復帰のためにrfe命令を持っているが, rfe命令にはシステムモードでは使用できないという制約がないために,このこ とは当てはまらない. (2) 保護機能対応カーネルにおいては,例外の入口で,ユーザスタックからシ ステムスタックに切り換える処理が必要であるが,アプリケーションプログラ ムの非特権モードで実行すべき部分をユーザモードで,アプリケーションプロ グラムの特権モードで実行すべき部分とカーネルの大部分をスーパバイザモー ドを実行すれば,スタックの切換えがハードウェアで行われ,効率的である. 逆に,アプリケーションプログラムとカーネルの大部分をシステムモードで実 行するメリットとして,以下を挙げることができる. ・スーパバイザモードで実行中にsvc命令を発行すると,lrレジスタに戻り番地  が格納される.lrが壊れる想定でsvc命令を呼び出せばよいが,プログラムに  パッチを当てる目的でsvc命令を使う場合など,lrを保存/復帰する余地がな  い場合には,スーパバイザモードで実行しているとうまく実装できない. 保護機能対応でないASP3カーネルで,ARMv6以降のプロセッサを使用する場合に は,アプリケーションプログラムとカーネルの大部分をシステムモードで実行 するように修正するのは容易である.具体的には,core_support.Sとstart.S内 の約20箇所程度のCPSR_SVC_MODEを,CPSR_SYS_MODEに書き換えればよい. ●カーネル管理外の割込み ARMコア依存部の標準では,IRQをカーネル管理の割込み,FIQをカーネル管理外 の割込みと扱う.このようにするのが,最もシンプルで性能が良いためである. ただし,SafeGのセキュアモードでASP3カーネルを動作させる場合には,FIQを カーネル管理の割込みとして扱い,カーネル管理外の割込みはサポートしない. IRQは,ノンセキュアモードで動作するOSが使用する.この設定にする場合には, TOPPERS_SAFEG_SECUREをマクロ定義する. ●全割込みロック状態の管理 全割込みロック状態では,FIQ・IRQともマスクする.具外的には,CPSR中のFビッ ト(FIQ禁止)とIビット(IRQ禁止)をセットする. ●コンテキストの管理 実行中のコンテキストをプロセッサモードで判断することができないため,例 外(割込みハンドラおよびCPU例外ハンドラ)のネスト段数を変数(これを,例 外ネストカウントと呼ぶ)で管理し,例外ネストカウントが0の時にタスクコン テキスト,0より大きい場合に非タスクコンテキストであると判断する. ●CPUロック状態の管理 CPUロック状態では,IRQをマスクする.具外的には,CPSR中のIビット(IRQ禁 止)をセットする.全割込みロック状態とCPUロック状態を区別できる必要はな いことから,CPSR中のIビットが立っていれば,CPUロック状態であると判断す る. TOPPERS_SAFEG_SECUREがマクロ定義されている場合には,CPUロック状態では, FIQをマスクする.IRQは常にマスクするため,FIQ・IRQともマスクすることに なる.CPSR中のFビットが立っていれば,CPUロック状態であると判断する. ●割込み優先度マスクの管理 割込み優先度マスクの管理は,割込みコントローラによって行う.そのため, ARM依存部には含めない. GICの場合には,GICC_PMR(Interrupt Priority Mask Register)により,割込 み優先度マスクを管理する. ●割込み要求禁止フラグの管理 割込み要求禁止フラグの管理は,割込みコントローラによって行う.そのため, ARM依存部には含めない. GICの場合には,GICD_ISENABLEn(Interrupt Set-Enable Registers)と GICD_ICENABLERn(Interrupt Clear-Enable Registers)によって,セット/ク リアする. ●割込みに関するその他の操作 割込みに関するその他の操作は,割込みコントローラによって実現する.その ため,ARM依存部には含めない. GICの場合には,割込み要求のクリアとプローブは,GICD_ICPENDRn(Interrupt Clear-Pending Registers)とGICD_ISPENDRn(Interrupt Set-Pending Registers)によって実現する. ●CPSR(Program Status Register)の設定値 CPUロック・全割込みロック解除状態,CPUロック状態,全割込ロック状態とす るCPSRの設定値を,以下のように定義しておく. #define CPSR_UNLOCK UINT_C(0x00) #define CPSR_CPULOCK CPSR_IRQ_BIT #define CPSR_INTLOCK CPSR_FIQ_IRQ_BIT TOPPERS_SAFEG_SECUREがマクロ定義されている場合には,以下のように定義す る. #define CPSR_UNLOCK CPSR_IRQ_BIT #define CPSR_CPULOCK CPSR_FIQ_IRQ_BIT #define CPSR_INTLOCK CPSR_FIQ_IRQ_BIT ここで,CPSR_IRQ_BITとCPSR_FIQ_BITは,それぞれIRQとFIQをマスクする場合 のCPSRの値,CPSR_FIQ_IRQ_BITは,FIQ・IRQともマスクする場合のCPSRの値で あり,以下のように定義しておく. #define CPSR_IRQ_BIT UINT_C(0x80) #define CPSR_FIQ_BIT UINT_C(0x40) #define CPSR_FIQ_IRQ_BIT (CPSR_FIQ_BIT|CPSR_IRQ_BIT) ○例外(割込みハンドラおよびCPU例外ハンドラ)の出入口 SRS命令とRFE命令を持つARMv6以降と,それより前のプロセッサでは,例外の出 入口(割込みハンドラおよびCPU例外ハンドラの出入口処理の最初と最後の部分) の最適なコードが異なる. ●ARMv6より前のプロセッサの場合 アーキテクチャの想定では,例外からの復帰を, ldmfd sp!, {<復帰すべきレジスタのリスト>, pc}^ の命令で行う.この時点で,戻り番地がスタックの最後(最も大きい番地)に 保存されており,戻り先のCPSRがこのモードのSPSRに置かれていることが必要 である. 例外の出入口処理で,すべてのスクラッチレジスタを保存/復帰することにす ると,上の命令の<復帰すべきレジスタのリスト>には,すべてのスクラッチレ ジスタのリストを記述する.また,例外からのリターンでモードが変わらない (どちらもスーパバイザモードである)ため,復帰するレジスタに,lrも加え ておくことが必要である. 具体的には,例外の出口は,次のようなコードとなる. ---------------------------------------- /* * この時点ではスーパバイザモードになっている. */ ldmfd sp!, {r0} /* 戻り先のcpsrをspsrに復帰 */ msr spsr_cxsf, r0 ldmfd sp!, {r0-r4,r12,lr,pc}^ /* 例外処理からの復帰 */ ---------------------------------------- ここで,スクラッチレジスタに加えてr4も復帰しているのは,例外の出入口処 理でスクラッチレジスタ以外に使えるレジスタとして,r4を確保するためであ る. この出口を実行する時点で,スタックは次のようになっている必要がある(レ ジスタはいずれもCPU例外発生時の値を示す.例えば,CPSRはCPU例外発生時の CPSRを示す). +----------------+ <- sp | cpsr | +----------------+ <- sp+0x04 | r0 | +----------------+ <- sp+0x08 | r1 | +----------------+ <- sp+0x0c | r2 | +----------------+ <- sp+0x10 | r3 | +----------------+ <- sp+0x14 | r4 | +----------------+ <- sp+0x18 | r12 | +----------------+ <- sp+0x1c | lr | +----------------+ <- sp+0x20 | 戻り番地 | +----------------+ これに対応する例外の入口は,次のコードとなる(IRQの場合). ---------------------------------------- /* * スーパバイザモードに切り換え,スクラッチレジスタを保存する. */ msr cpsr_c, #(CPSR_SVC_MODE AOR CPSR_IRQ_BIT) stmfd sp!, {r0-r4,r12,lr,pc} /* pcはスペース確保のため */ (*1) /* * IRQモードに戻して,戻り番地とspsr(戻り先のcpsr)を取得する. */ msr cpsr_c, #(CPSR_IRQ_MODE AOR CPSR_IRQ_BIT) sub r2, lr, #4 mrs r1, spsr /* * スーパバイザモードに切り換え,戻り番地とspsrを保存する. */ msr cpsr_c, #(CPSR_SVC_MODE AOR CPSR_IRQ_BIT) str r2, [sp,#0x1c] /* 戻り番地をスタックに保存 */ (*2) push {r1} /* spsrをスタックに保存 */ ---------------------------------------- (*1)でpcを保存するのは,戻り番地を保存すべきスペースを確保するためであ り,その時点で保存するpcの値は使わない.(*2)で,確保したスペースに戻り 番地を格納する.格納先を[sp,#0x1c]とするのは,この時点ではspは上の図の (sp+0x04)を指しており,戻り番地は(sp+0x20)に格納したいためである. 例外を受け付けるとIビットをセットするため,例外の入口が実行される時点で, CPSRのIビットはセットされている.IRQの場合,IRQが受け付けられたというこ とは,受付前の時点ではIビットはクリアされていたはずであるため,Fビット はクリアされている(Iビットがセットされており,Fビットがクリアされてい る状況は作らないため).よって,上記のコードで,IビットとFビットは変化 しない. IRQ以外の例外(CPU例外ハンドラ)の出入口においては,上記のコードに対し て,次の修正を加える. ・Iビットだけではなく,Fビットもセットする.具体的には,3箇所にある  CPSR_IRQ_BITを,CPSR_FIQ_IRQ_BITに置き換える. ・戻り番地を取得する時に,lrから4を減算しない(実際に戻るべき番地になっ  ているとは限らない). ・戻り番地とspsrを取得する時に,IRQモードではなく,そのCPU例外で遷移す  るモードに戻す(スーパバイザコールの場合は,戻す必要がない). IビットだけでなくFビットもセットするのは,(1) FIQを受け付けるとFビット もセットされる,(2) その他の例外はFビットがセットされている状態でも受け 付けられる,の2つの理由からである.ただし,Fビットがクリアされている時 に発生した(FIQ以外の)例外では,この処理によりFビットがセットされるこ とになり,カーネル管理外の割込みがマスクされる期間が長くなるが,CPU例外 ハンドラを呼び出すまでの短時間なので,許容することにする. 3つめの処理(スーパバイザモードに切り換えと,戻り番地とspsrの保存)は, (IRQ以外の)すべての例外で共通であるため,exc_handler_1の先頭で実施す ることもできるが,ARMv6以降のプロセッサの場合との共通化のために,例外毎 に個別に実施する. ●ARMv6以降のプロセッサの場合 例外からの復帰にrfe命令を用いると,例外の出口は,次のようなコードとなる. ---------------------------------------- ldmfd sp!, {r0-r4,r12,lr} rfefd sp! ---------------------------------------- ここで,スクラッチレジスタに加えてr4も復帰しているのは,例外の出入口処 理でスクラッチレジスタ以外に使えるレジスタとして,r4を確保するためであ る. この出口を実行する時点で,スタックは次のようになっている必要がある(レ ジスタはいずれもCPU例外発生時の値を示す.例えば,CPSRはCPU例外発生時の CPSRを示す). +----------------+ <- sp | r0 | +----------------+ <- sp+0x04 | r1 | +----------------+ <- sp+0x08 | r2 | +----------------+ <- sp+0x0c | r3 | +----------------+ <- sp+0x10 | r4 | +----------------+ <- sp+0x14 | r12 | +----------------+ <- sp+0x18 | lr | +----------------+ <- sp+0x1c | 戻り番地 | +----------------+ <- sp+0x20 | cpsr | +----------------+ これに対応する例外の入口は,次のようなコードとなる(IRQの場合). ---------------------------------------- /* * 戻り先(lr)とspsr(cpsr_svc)をスーパバイザモードのスタックに * 保存する. */ sub lr, lr, #4 /* 戻り番地の算出 */ srsfd #CPSR_SVC_MODE! /* * スーパバイザモードに切り換え,スクラッチレジスタを保存する. */ cps #CPSR_SVC_MODE stmfd sp!, {r0-r4,r12,lr} ---------------------------------------- IRQ以外の例外(CPU例外ハンドラ)の出入口においては,上記のコードに対し て,次の修正を加える. ・戻り番地の算出のためにlrから4を減算しない(実際に戻るべき番地になって  いるとは限らない). 後半の処理(スーパバイザモードへの切換えとスクラッチレジスタの保存)は 共通だが,この直後でCPU例外ハンドラ番号を格納するためにr4を使うため, exc_handler_1の方へ移動できない. ○割込みハンドラの出入口処理 ASP3カーネル ポーティングガイドに従って,割込みハンドラの出入口処理の実 装方法を検討していく. ---------------------------------------- void <割込みの出入口処理>(void) { 少なくともカーネル管理の割込みを禁止した状態にする … (*d) スクラッチレジスタをスタックに保存する ---------------------------------------- ARMコアは,例外を受け付けると,CPSR中のIビットをセットして割込みを禁止 するため,(*d)の処理は必要ない.スクラッチレジスタの保存は,例外の入口 で実装済みである. ---------------------------------------- if (割込みがタスクコンテキストで発生) { ---------------------------------------- まず,例外ネストカウントをインクリメントする.インクリメント後の値が1で ない場合には,非タスクコンテキストでの割込み発生であるため,この後の{} 内の処理をスキップする(irq_handler_1に分岐). ---------------------------------------- #ifdef TOPPERS_SUPPORT_OVRHDR ovrtimer_stop(); /* オーバランタイマの停止 */ #endif /* TOPPERS_SUPPORT_OVRHDR */ スタックを非タスクコンテキスト用のスタックに切り換え, 非タスクコンテキストに切り換える } ---------------------------------------- スタックを非タスクコンテキスト用のスタックに切り換え,切り換え前のスタッ クポインタを切り換え後のスタック上に保存する.これらの処理の後に, irq_handler_1ラベルを置く. ---------------------------------------- 割込み優先度マスクを,受け付けた割込みの割込み優先度に設定する CPUロック解除状態にする(受け付けた割込みよりも優先度の高い割込みを 受け付けるようにする) 割込みハンドラの入口で必要なIRCの操作を行う(割込み要求ラインがエッ ジトリガである場合のトリガされた割込み要求のクリアなど) ---------------------------------------- 割込み優先度マスクの管理は,チップ依存の割込みコントローラによって行う 必要があるため,上記の処理の順序を一部変更し,上記の処理の中で「CPUロッ ク解除状態にする」以外の処理は,チップ依存部のirc_begin_intで行うことと する.すなわち,irc_begin_intは,割込み優先度マスクを受け付けた割込み要 求の割込み優先度に設定し,割込みハンドラの入口で必要なIRCの操作を行う. またirc_begin_intは,割込み番号(0から始まる番号で,割込みハンドラ番号 と一致させる)を返すようにする.スプリアス割込みの場合には, irc_begin_intは割込み番号の最大値より大きい値を返すものとし,その場合に は,割込みハンドラの呼出し処理をスキップする(irq_handler_2に分岐). その後,irc_begin_intで行わなかった「CPUロック解除状態にする」処理を行 う.具体的には,CPSR中のFビットとIビット(ただし,TOPPERS_SAFEG_SECURE の場合は,Fビットのみ)をクリアして,割込みを許可する. ---------------------------------------- #ifdef LOG_INH_ENTER log_inh_enter(割込みハンドラ番号); #endif /* LOG_INH_ENTER */ 割込みハンドラを呼び出す #ifdef LOG_INH_LEAVE log_inh_leave(割込みハンドラ番号); #endif /* LOG_INH_LEAVE */ ---------------------------------------- 割込みハンドラテーブル(inh_table)から割込みハンドラの番地を取り出して 呼び出す. ---------------------------------------- 割込みハンドラの出口で必要なIRCの操作を行う(IRCに対する割込み処理 の終了通知など) 少なくともカーネル管理の割込みを禁止した状態にする 割込み優先度マスクを,割込みを受け付ける前の値に戻す … (*e) ---------------------------------------- 割込みハンドラの呼出し前の処理の順序を一部変更したのに対応して,まず, カーネル管理の割込みを禁止する.具体的には,CPSR中のIビット(ただし, TOPPERS_SAFEG_SECUREの場合は,Fビットも)をセットする.この処理の後に, irq_handler_2ラベルを置く. 次に,チップ依存部のirc_end_intを呼び出す.irc_end_intでは,割込みハン ドラの出口で必要なIRCの操作を行い,割込み優先度マスクを割込み処理前の状 態に戻す. コア依存部からは,irc_end_intにパラメータを渡さないため,irc_end_intの 中で必要な情報(例えば,割込み番号)は,irc_begin_intでスタック上に保存 しておく必要がある.これを可能にするために,irc_begin_intが呼び出された ら,irc_end_intも必ず呼び出されるようにしておく.スプリアス割込みの場合 にも,irc_end_intを呼び出すようにしている. サブルーチン(irc_begin_int)の中でスタックに積み増すのは,一般には採用 しないプログラミング技法であるが,irc_begin_intからirc_end_intに渡す情 報は,チップ依存部によって異なること,ARMでは戻り番地をlrに保存するため スタックに積み増すのが容易であることから,この方法を採用することにした. ---------------------------------------- ret_int: if (割込みがタスクコンテキストで発生) { ---------------------------------------- まず,例外ネストカウントをデクリメントする.デクリメント後の値が0でない 場合には,非タスクコンテキストでの割込み発生であるため,{}内の処理をス キップする(irq_handler_3に分岐). ---------------------------------------- スタックをタスク用のスタックに戻し,タスクコンテキストに切り換える … (*f) ---------------------------------------- タスクコンテキストのスタックポインタは,この時点で,スタックの先頭に保 存されているため,スタックの先頭を取り出して,スタックポインタに設定す る. ---------------------------------------- if (p_runtsk != p_schedtsk) { ---------------------------------------- この処理の前に,ret_int_1ラベルを置く.p_runtskとp_schedtskが一致しない 時は,irq_handler_4へ分岐する. ---------------------------------------- CPUロック状態にする … (*g) 割込み優先度マスクは,全解除状態(TIPM_ENAALL)になって いなければならない … (*h) ---------------------------------------- (*g)の処理は,irc_end_intを呼び出す前に行ったカーネル管理の割込みを禁止 する処理で十分であるため,ここでは行わない.(*h)については, irc_end_intで割込み優先度マスクを割込み処理前の状態に戻すことで,満たさ れているはずである. ---------------------------------------- if (p_runtsk != NULL) { スクラッチレジスタを除くすべてのレジスタをスタックに保存する スタックポインタを自タスク(p_runtsk)のTCBに保存する ret_int_rを,実行再開番地として自タスクのTCBに保存する … (*a) #ifdef LOG_DSP_ENTER log_dsp_enter(p_runtsk); #endif /* LOG_DSP_ENTER */ } dispatcherに分岐する ret_int_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する #ifdef TOPPERS_SUPPORT_OVRHDR ovrtimer_start(); /* オーバランタイマの動作開始 */ #endif /* TOPPERS_SUPPORT_OVRHDR */ } ---------------------------------------- この辺りは素直に実装できる. ---------------------------------------- #ifdef TOPPERS_SUPPORT_OVRHDR else { if (p_runtsk != NULL) { ovrtimer_start(); /* オーバランタイマの動作開始 */ } } #endif /* TOPPERS_SUPPORT_OVRHDR */ } ---------------------------------------- このelse部は,p_runtskとp_schedtskが一致しており,タスク切換えが必要な かった場合の処理である.else部の{}の中の処理の先頭に,irq_handler_4ラベ ルを置く.オーバランハンドラをサポートしない場合は,irq_handler_4のラベ ルを,irq_handler_3と同じ場所(後述)に置く.これにより,上の処理をスキッ プする. ---------------------------------------- スクラッチレジスタをスタックから復帰する 割込み処理からのリターン後に,CPUロック解除状態に戻るように準備する 割込み処理からのリターン } ---------------------------------------- これらの処理の前に,irq_handler_3のラベルを置く.これらの処理は,例外か らの出口で行う. ○CPU例外ハンドラの出入口処理 ASP3カーネル ポーティングガイドに従って,CPU例外ハンドラの出入口処理の 実装方法を検討していく. ---------------------------------------- void (void) { スクラッチレジスタをスタックに保存する ---------------------------------------- この処理は,例外の入口で実装済みである. ---------------------------------------- CPU例外が発生した状況を判断するための追加情報をスタックに保存する … (*j) この時点でのスタックポインタ±nを,CPU例外の情報を記憶している領域の 先頭番地とする(必要なら保存する) ---------------------------------------- exc_sense_intmaskを実現するために,CPU例外が発生した時点でのコンテキス トと割込み優先度マスクが必要となるため,現在の割込み優先度マスク(CPU例 外の発生で割込み優先度マスクが変わることはないため,これで良い)と例外 ネストカウントをスタックに保存する.また,CPU例外の情報を記憶している領 域の先頭番地として,この時点でのスタックポインタを,レジスタに保存して おく. ---------------------------------------- if (カーネル管理外のCPU例外) { ---------------------------------------- カーネル管理外のCPU例外は,カーネル実行中,全割込みロック状態,CPUロッ ク状態,カーネル管理外の割込みハンドラ実行中に発生したCPU例外である. ARMコアの場合は,戻り先のCPSRのIビットかFビットのいずれかがセットされて いるなら,これに該当する. そこで,例外フレームから戻り先のCPSRを取り出し,IビットかFビットのいず れかがセットされている場合には,カーネル管理外のCPU例外の出入口処理 (nk_exc_handler_1)に分岐する. ---------------------------------------- if (CPU例外がタスクコンテキストで発生) { スタックを非タスクコンテキスト用のスタックに切り換え, 非タスクコンテキストに切り換える … (*k) } ---------------------------------------- カーネル管理外のCPU例外の出入口処理(nk_exc_handler_1)では,まず,例外 ネストカウントをインクリメントする.CPU例外発生前がタスクコンテキストな ら,非タスクコンテキスト用のスタックに切り換え,切り換え前のスタックポ インタを切り換え後のスタック上に保存する. ---------------------------------------- システム状態(コンテキストは除く)を,CPU例外発生時の状態にする ---------------------------------------- システム状態(コンテキストは除く)をCPU例外発生時の状態にするには,スー パバイザモードに切り換え,IフラグとFフラグの状態をCPU例外発生時の状態に 戻す. ---------------------------------------- CPU例外ハンドラを,CPU例外の情報を記憶している領域の先頭番地を パラメータ(p_excinf)として呼び出す ---------------------------------------- CPU例外ハンドラテーブル(exc_table)からCPU例外ハンドラの番地を取り出し, CPU例外の情報を記憶している領域の先頭番地を第1パラメータ,CPU例外ハンド ラ番号を第2パラメータとして呼び出す.第2パラメータにCPU例外ハンドラ番号 を渡すのは,default_exc_handlerで割込みハンドラ番号を表示できるようにす るためで,ARMコア依存部の独自拡張である. ---------------------------------------- if (CPU例外がタスクコンテキストで発生) { スタックをタスク用のスタックに戻し, タスクコンテキストに切り換える … (*k) } CPU例外処理からのリターン後に,CPU例外発生時のシステム状態に 戻るように準備する } CPU例外が発生した状況を判断するための追加情報をスタック上から捨てる スクラッチレジスタをスタックから復帰する CPU例外処理からのリターン } ---------------------------------------- 例外ネストカウントをデクリメントし,0になった場合には,タスク用のスタッ クに戻す.その後,CPU例外が発生した状況を判断するための追加情報をスタッ ク上から捨て,スクラッチレジスタを復帰した後,例外からリターンする. ---------------------------------------- else { if (CPU例外がタスクコンテキストで発生) { ---------------------------------------- カーネル管理のCPU例外の出入口処理では,まず,例外ネストカウントをインク リメントする.CPU例外発生前が非タスクコンテキストなら,この後の{}内の処 理をスキップする(exc_handler_2に分岐). ---------------------------------------- 少なくともカーネル管理の割込みを禁止した状態にする ---------------------------------------- ARMコアでは,例外の受付によりCPSRのIビットがセットされるため,この処理 は必要ない. ---------------------------------------- #ifdef TOPPERS_SUPPORT_OVRHDR ovrtimer_stop(); /* オーバランタイマの停止 */ #endif /* TOPPERS_SUPPORT_OVRHDR */ スタックを非タスクコンテキスト用のスタックに切り換え, 非タスクコンテキストに切り換える } ---------------------------------------- スタックを非タスクコンテキスト用のスタックに切り換え,切り換え前のスタッ クポインタを切り換え後のスタック上に保存する.これらの処理の後に, exc_handler_2ラベルを置く. ---------------------------------------- 割込み優先度マスクを,CPU例外発生時の値に設定する … (*l) ---------------------------------------- チップ依存部のirc_begin_excを呼び出し,必要であれば,割込み優先度マスク をCPU例外発生時の値に設定する.また,irc_end_excで必要な情報をスタック に保存する. ---------------------------------------- CPUロック解除状態にする(CPU例外発生時の割込み優先度マスクよりも 優先度の高い割込みを受け付けるようにする) ---------------------------------------- 割込みを許可して,CPUロック解除状態にする. ---------------------------------------- #ifdef LOG_EXC_ENTER log_exc_enter(CPU例外ハンドラ番号); #endif /* LOG_EXC_ENTER */ CPU例外ハンドラを,CPU例外の情報を記憶している領域の先頭番地を パラメータ(p_excinf)として呼び出す #ifdef LOG_EXC_LEAVE log_exc_leave(CPU例外ハンドラ番号); #endif /* LOG_EXC_LEAVE */ ---------------------------------------- CPU例外ハンドラテーブル(exc_table)からCPU例外ハンドラの番地を取り出し, CPU例外の情報を記憶している領域の先頭番地を第1パラメータ,CPU例外ハンド ラ番号を第2パラメータとして呼び出す.r12に保存されているCPU例外の情報を 記憶している領域の先頭番地が保存されるように,必要に応じて,スタックに 保存する. ---------------------------------------- 少なくともカーネル管理の割込みを禁止した状態にする … (*m) 割込み優先度マスクを,CPU例外発生時の値に設定する … (*n) ---------------------------------------- カーネル管理の割込みを禁止した後,チップ依存部のirc_end_excを呼び出す. ---------------------------------------- if (CPU例外がタスクコンテキストで発生) { スタックをタスク用のスタックに戻し,タスクコンテキストに切り換える … (*g) ---------------------------------------- 例外ネストカウントをデクリメントし,0になった場合には,タスク用のスタッ クに戻す.その後,CPU例外が発生した状況を判断するための追加情報をスタッ ク上から捨てる.例外ネストカウントが0になった場合の処理は,割込みハンド ラの出口処理と共通であるため,ret_int_1に分岐する. ---------------------------------------- if (p_runtsk != p_schedtsk) { CPUロック状態にする … (*h) 割込み優先度マスクは,全解除状態(TIPM_ENAALL)になって いなければならない … (*i) if (p_runtsk != NULL) { スクラッチレジスタを除くすべてのレジスタをスタックに保存する スタックポインタを自タスク(p_runtsk)のTCBに保存する ret_exc_rを,実行再開番地として自タスクのTCBに保存する … (*a) #ifdef LOG_DSP_ENTER log_dsp_enter(p_runtsk); #endif /* LOG_DSP_ENTER */ } dispatcherに分岐する ret_exc_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する #ifdef TOPPERS_SUPPORT_OVRHDR ovrtimer_start(); /* オーバランタイマの動作開始 */ #endif /* TOPPERS_SUPPORT_OVRHDR */ } #ifdef TOPPERS_SUPPORT_OVRHDR else { if (p_runtsk != NULL) { ovrtimer_start(); /* オーバランタイマの動作開始 */ } } #endif /* TOPPERS_SUPPORT_OVRHDR */ } ---------------------------------------- 以上の処理には,割込みハンドラの出口処理のルーチンを流用する. ---------------------------------------- CPU例外処理からのリターン後に,CPUロック解除状態に戻るように準備する } CPU例外が発生した状況を判断するための追加情報をスタック上から捨てる スクラッチレジスタをスタックから復帰する CPU例外処理からのリターン } ---------------------------------------- スクラッチレジスタを復帰した後,例外からリターンする. ●スタックポインタが不正の場合の対策 CPU例外ハンドラを実行する際に経由する部分で,CPU例外が起こる可能性とし て,スタックポインタが不正な番地となった場合が考えられる.スーパバイザ モードのスタックポインタが不正な番地となった状態で例外が発生すると,そ の入口でスクラッチレジスタ等を保存しようとして,再度例外が発生する.こ の状況で,例外発生が無限に繰り返すことを防ぐ対策をとることが望ましい. これを防ぐために,データアボートの入口で,データアボートを発生された番 地を調べ,CPU例外の入口であった場合には,フェイタルデータアボート処理を 行う. フェイタルデータアボート処理では,スーパバイザモードのスタックを,強制 的に,非タスクコンテキスト用のスタックの初期値に設定した後,CPU例外の処 理を行う.この際,通常のデータアボートと区別できるように,例外ネストカ ウントの最上位ビットを1にする.フェイタルデータアボート処理からはリター ンすることができない. 以上