volatileを忘れたら

マイコンの割り込み機能のデバッグをどうやってやろうかと相談を受けて、

こういうコードを書けば、正しく入っているか確認出来るんじゃないの、

なんてアドバイスをして、実際にやり始めたら、割り込みが止まらなくなって、

しかもデバッグコードの挿入の仕方によって変わると言う。


で、どうにも不可解だというので、ビルド結果を比較してみると、

割り込み要因を消すためのレジスタ書き込みが消えていた。

コンパイラの最適化でコードが消されてしまったわけである。

ん? ということはもしかしてと、ヘッダファイルを確認すると、

レジスタの定義にvolatileを付け忘れていたのだった。

#define FOO_REGS (*((volatile FOO_REGS_T*)FOO_REGS_BASE))

このvolatileが抜けていたというわけ。わりと初歩的なミスである。


ただ、もしかするとレジスタの仕様によってはこの問題は露呈しなかったかもしれない。

この割り込み要因レジスタは’1’が書き込まれたら’0’にクリアするという仕様である。

すなわち、割り込みが入ってきてレジスタをリードして、

その値が0x18であれば、bit3とbit4に対応する割り込み要因があるということで、

それに応じた処理を行うわけだが、処理が完了したら要因を消す必要がある。

その消去方法はbit3とbit4に1を書き込む、すなわち0x18をライトするということになる。

そんな処理をCで書くとこんなことになる。

void INT_FOO_Handler(void){
   unsigned int intr_read;
   intr_read = FOO_REGS.INTR;
   if(intr_read == 0){ return; }
   FOO_REGS.INTR = intr_read;
   if(intr_read & 0x00000001){
     ....

割り込みコントローラの仕様ですでにレジスタの値が0なのに、

割り込みが入る可能性があるので、それを避ける処理が最初に入っているが、

レジスタから読んだ値を書くという処理が存在するのは見ての通りである。


で、読んだ値を同じところに書き込むという操作は一般的なメモリであれば冗長な操作である。

このため最適化で消されてしまったというわけである。

もしも、これが0を書けば割り込み要因がクリアされるという仕様ならば、

intr_read = FOO_REGS.INTR;
if(intr_read == 0){ return; }
FOO_REGS.INTR = 0;

というわけで、一般的なメモリであっても意味のある操作ということで、

最適化で消去されることなく残ったのではないかと思う。


volatile修飾子の意味はvolatileの付いた処理同士の順番を入れ換えないことで、

上の例の場合は intr_read = FOO_REGS.INTR; をした後で、

必ず FOO_REGS.INTR = intr_read; を実施するということになる。

もしも FOO_REGS.INTR = intr_read; が冗長と考えても消してはいけないと。

それ以外の処理との順序関係は定かではないところがあるが、

だいたいは書いた通りに実行されるのではないかと思う。


ハードウェアのレジスタはだいたいvolatileを付けないとまずいが、

処理中に書き換わってしまう可能性がある変数をポーリングする場合にも使われますね。

その書き換わる可能性のある変数を厳密に考えると、あれもこれもvolatileとなって、

1行にvolatileの付いた変数が複数出てくることも生じて、

順序関係が不定になるけど、それでよいのかとコンパイラの警告が出てくると。

それはそれでどうなのかと思うけど。


レジスタは’1’が書き込まれたら’0’にクリアするという仕様は僕がリクエストしたものである。

既存レジスタでも一部にこんな仕様のレジスタが存在して、

それを持ち出して、こんな風にしてくれるとありがたいと伝えたわけである。

0を書いてクリアする例を見て気付いたかも知れないが、

万が一、リードしてから0をライトするまでの間に別の割り込み要因が立つと、

それまでまとめて消してしまう可能性もなしとは言えない。

なので、確認した割り込み要因だけ狙って消せた方がよくて、

‘1’をライトすると’0’にクリアするという操作は読んだ値を書くということでコード的にもわかりやすい。

というわけでこの方法を採用したのだが、コンパイラには無意味な操作に見えてしまったようだ。


こんな教科書的なミスをしてしまうことがあるんだなと。

この部分をコーディングしたのは他の人で、あまり細かくは見てなかったのだが、

言われてみれば確かに……という内容だった。

人の振り見て我が振り直せ、というわけで自分が書いた同様のコードを点検してみたが、

さすがにちゃんとvolatile付けてましたね。さすがにそんなミスはしない。