WorkPad 用プログラムの組み方(5)

Prev 1 , 2 , 3 , 4 , 5, 6 , 7 , Next


このページの目次


今度は Dragonball, Dragonball-EZ, Dragonball-VZ についての話をしよう。

WorkPad の中央演算装置であるこれらのチップには、 いくつかの便利な機能と、致命的とも言えるデザインの悪さが同居している。 その中のいくつかについては MC68000 から引き摺っているものもあるし、 別のものは Dragonball シリーズを通じて直っていないものもある。 これらの特徴を知らずにハードウェアを作ろうとすれば 破綻的な結果をもたらすだろうし、 これらの特徴を知ったうえでプログラムをデザインすれば、 効果的なものを作ることができるだろう。

さらに、PalmOS にはこのチップをきちんと制御しきれていない面がある。 このことを考慮せずにデザインを行うと、 開発フェーズの中でも「テスト」になってから 致命的な欠陥が発見されることもある。


Dragonball の資料

Dragonball シリーズはモトローラ社が出している 組み込み向けの MPU 並びに周辺 unit を集めたものである。 詳細な資料は モトローラのページ から辿って獲得して欲しい。 ここには、ハードウェアのユーザーマニュアルだけでなく、 Errata(バグリスト)も置いてある。

特に、 Errata はサイトを定期的に検索して手持ちのファイルを更新して欲しい。 時によってはとんでもないものが出てくることがあるからだ。 また、仮にそうでなくても、
「実はマイナーバージョンが結構存在する」
事自体が結構重要な情報だったりすることもある。 (どうせ買うなら新しいチップの方がいい、とかね)


MC68000

Dragonball シリーズは、 MC68000 MPU core と一般によく使われる周辺ユニットを 1つのパッケージ(判りやすく言えばゲジゲジ)にまとめたものである。 MC68000 MPU core を含めて、7つの unit があり、 そのために漫画「ドラゴンボール」になぞらえたのだ(ろう、多分)。 むかしの MC68000 と違い、 アドレス線が 31本(注1) に拡張されているので、 利用できるメモリ空間は最大4Gbyte になっている。

注1)
32ではない。 MC68000 は偶数アドレスから16bit単位でしかアクセスしないので、 奇数アドレスを示すために必要な LSB 1bit 分の電線は不要なのだ。

MC68000 シリーズ全般に言えることだが、 Intel のチップと違って IO Channel という概念がない。 周辺ハードウェアもすべてアドレスを持っており、 あるアドレスに書き込むことがハードウェアを制御する register への 書き込みを意味するし、 そのアドレスからデータを読み出せば register から値を読み出す という意味になる。 これを Memory Mapped IO という。

どのアドレスがちゃんと読み出せるのか、どのアドレスは読めないのか、 どのアドレスは書けるのか、書けないのか、 等はハードウェアによって決定する。 これをきちんと守らないと、ハードウェアがうまく動かなかったり、 あるいは Access Violation エラーが発生して PalmOS ごとリセットに なる危険性もある。 従って十分に資料を調べてからハードウェアを操作することを考えた方がよい。

逆に、Memory Mapped IO の場合、 ハードウェアへのアクセスは通常のメモリアクセスと全く同じである。 そのため、絶対アドレスをポイントするポインターの生成方法さえ 知っていれば、 (Intel チップと違って) 標準的な C の命令を駆使するだけでハードウェアを制御できる。 これは Dragonball 内蔵の周辺 unit についても同様で、 特殊命令は必要ない。 これはとてもよい性質で、 初心者でも比較的簡単にハードウェアを直接操作することができる 理由としてこれが挙げられる。

これはアドレス空間が大きくなった最近の CPU ではむしろ当然の構造だが、 MC68000 は「大昔から」こうなっている、という点が優れていたのだ。

もちろん、これはさらに最近の CPU では弱点となっていて、 メモリ空間を Cache して良いところと、してはいけないところに 分離しなくてはいけないとか、 いろいろ問題が出てきているのだが、 それは Dragonball レベルの CPU には関係ない。


一方、MC68000 には数多くの弱点も存在する。 もちろん、その中には昔の MPU なのだからしょうがないという点、たとえば:

というような点もあるのだが、 一方で、昔の MC68000 にあった「デザイン上のバグ」で、 MC68030 ではじめて修正された問題点が、そのまま継承されている、 という困った性質もある。

相対ジャンプが16bit 分しかない。
現在実行中のプログラム位置からの相対アドレスで ジャンプを実行する、という命令がある。 Palm のように『record の位置がアドレスとして固定されていない』 OS の場合、 プログラム上の関数の絶対アドレスはプログラムを実行しないとわからない。 しかし、プログラムイメージ全体の中での相対的な位置関係は決定できる。 決定できるのだが、プログラムが大きくなってくると、 相対的な位置関係もだんだん離れたポイントを指す必要が出てくる。 この相対的な位置を表現する部分が16bitしかないということは、 今の自分の位置から ±32kbyte までしか表現できないことになる。 このため、PalmOS 用のコンパイラーはかなり苦労することになり、 結果、つい最近の gcc までは『32k の壁』というプログラムサイズに 関する障壁があったりした。
割り込みレベル保存と変更が一度にできない。

これは少し説明が必要かも知れない。

ハードウェアというのはそもそも電気回路である。 電気回路で作られているハードウェアは基本的に各「線」が『同時に』 信号を処理している。 『並列処理』ということが可能な構造なのだ。

これは1つの chip 中にあろうがなんだろうが一緒だ。 しかし、MPU というものは一度に1つの命令を、順番に、処理していく。 だからといって、一度に1つづつしかハードウェアをコントロールしない のでは効率が悪くてしょうがない。

そこで、MPU 以外のハードウェアを少しだけ賢くしてやる。 何か、MPU が処理するべき事が起ったら MPU に連絡し、 それ以外の時は自分で勝手に動く、という構造になっているのだ。

この『MPU への連絡』が『割り込み』である。

今度は MPU の立場で考えてみよう。 MPU から見た場合、あるデバイスに対する処理は別のデバイスに対する 処理よりもすばやく処理しなくてはいけない、と言うことがあり得る。

たとえば、生命維持装置を考えてみよう。 人工心肺が止まったという処理と、 生命維持装置の状況記録装置の紙が切れた、 という状態が一度に起ったとしよう。 この場合、「紙切れ」を先に処理していたら、 生命維持装置につながれた人は死んでしまうだろう。 『人工心肺が止まった』ほうを先に処理する必要があるというのは 判ってもらえると思う。

このように、『MPU への連絡』が2つ以上やってきた場合に、 何をどれに優先するべきなのか、というのは結構重要な問題である。 このため、「割り込み処理」には「優先順序」という概念がある。

MC68000 の場合、「割り込みレベル」というものが優先順序を 決定している。 Level 1 が最も割り込み優先レベルの低く、 Level 7 が最も割り込み優先レベルが高い、ということになっている。

さて、いまあなたが Level 5 の処理を行っているとしよう。 この場合、割り込み優先レベルが Level 6 や Level 7 の割り込みが 発生した場合、現在の処理よりも先にそれらを処理する必要がある。 逆に Level 1から Level5 までの割り込みの場合は、 現在処理しているものを優先する必要がある。 そこで、どのレベルの割り込みを MPU に知らせ、 どのレベルの割り込みは MPU に知らせないか、を変更できるように なっている。

問題になるのは、その割り込みレベルを変更する際に発生する。

割り込みレベルの変更は
「前の状態に戻す」
ことを前提にしなくてはいけない。 そこで、割り込みレベルを変更する前に「今の状態」を保存することになる。 ここで、「今の状態」の保存と「割り込みレベルの変更」が同時に 行える命令があれば話は簡単なのだが、 MC68000 にはそのような命令はない。

「今の状態」を保存してから「割り込みレベルを変更」 すればいいじゃないか、と言うかも知れないが、 それではうまく行かない危険性がある。 「今の状態」を保存したあと(このレベルを level a としよう)、 「割り込みレベルを変更」する前に割り込みが発生したとしよう。 その割り込み処理の中で(別の理由で)「割り込みレベルの変更」 が処理されるとする。 これはかなり「普通にある」ことだ。

この場合「今の状態」である level a はもはや嘘である。 「割り込みレベルを変更」したときにはすでに別の「割り込みレベル」 (level b としよう)が「現状」になっているからだ。

この割り込みレベルが再度元に戻そうとした場合に、 level b の事を知らないので、level a に戻してしまう。 a < b であることが一般的なので、 本来ならば b 未満のレベルの割り込みは受け付けられないはずなのに、 level a の割り込みを受け付けてしまう。

結果、優先順位処理に矛盾が生じ、 ハードウェアの制御がうまくいかなくなる、という状態が発生します。


その他のユニット

さらに、Dragonball は MPU 以外の 6 つのユニットがあるので、 これらのバグも存在する。 この中のいくつかの問題点は初期のバージョンから VZ Dragonball に至るまで、 全くなおっていない。 直っていないのは本質的に致命的ではないからだが、 PalmOS はそれらを増長しているので結果として致命的になっている。

また、中の一部はデザインバグであり、 根本的に修正は不可能と予測される。

それらの中のいくつかをご紹介しよう。

OVRUN Status Bit in UART Receiver Register does not function.
UART 受信レジスター(0xfff904) の Bit 11番は機能しない。 このビットは 受信バッファー overrun が発生しても反応しない。
1j83G.pdf より。

受信バッファーが溢れてもBit11 が機能しない、
と書いてあるが、このバグは実は結構やっかいな存在である。 実は、Bit 11 が反応しない「だけ」なのではなく、 Overrun が発生しても発生したことが検出できていないのだ。 Bit 11 が反応しないのは単に「副作用として」そのような 現象も起こっているのに過ぎない。 結果、Hardware Flow Control は適切に作用せず、 一旦 overflow が発生したら受信バッファーの内容は バッファーを完全にリセットしてしまうまで全く当てにならない、 ということを意味する。

しかし、この問題は、もし OS が適切につくられているのであれば、 致命的にはならない。 少なくともソフトウェアで解決可能な問題だし、 一般には問題になることさえない。 Recieve Buffer は「1byte でも詰まったら」 割り込みが発生するように制御するのが一般的だから、である。

送信側から何バイトのデータが到着するのかわかっているならばともかく、 一般には(事故等も考慮すれば常に)データが何バイト到着するのか 受信側では知るすべはない。 従って、受信バッファーに 1byte でもデータが存在するならば、 それを吸い出す準備をする、という形でしかデータを取り出すタイミングを 制御するすべはない。

Dragonball シリーズの UART 送受信バッファーはそれぞれ 8byte づつ の大きさであり、115200bps が最大の通信速度であるため、 1byte 目を受信してから 486usec 以内にデータを取り出し始めれば、 Recieve Buffer Overflow を発生させる心配はない。 一方、ソフトウェア側が吸い出した buffer を保存する Virtual FIFO が 一杯になりそうになったら、 その段階で Software Flow Control を利用して通信を停めてしまう。 こうすれば UART 受信バッファーが溢れる前に Software Flow Control で送信を停止することが可能なので、 実質的にこのバグが問題になることはない…はずだった。

この方法にはたった一つだけシビアな条件がある。
『486usec 以内にデータを取り出す』
というのがそれだ。

PalmOSは、簡単に doze モードという状態に陥る。 doze モードは一種の power saving mode で、 何も計算することがない場合に MPU の機能を制限し、 電力消費量を減らす、というものだ。

この doze モード、当然『何かあったらすぐ起きる』必要がある。 この「何か」に使われるのも「割り込み」で、 結果として CPU は doze モードに陥るときに
「あらゆる割り込みを受け付ける」
状態にしなくてはいけない。

ところが不思議なことに、PalmOS はこうなっていない。 PalmOS では、doze モードに陥る際に level6 の 『timer 割り込み』、 つまり thread re-scheduling 用の割り込みを除いて、 ほとんどすべての割り込みをブロックしてしまう。 この timer 割り込みは 10msec に一度の割合でかかるので、 doze モードに陥った場合、 最大 10msec も「UART 割り込み」は待たされることになる。 これは待てる時間の 20倍もの時間である。

しかも、この間、待たされるのは UART だけではない。 タッチパネルの割り込み、Real Time Clock の 1秒ごとの割り込み、 など多くの割り込みがこの10msec の間に発生する可能性がある。 それらを処理する方が優先度が高ければ、 当然それらを処理し終わるまで UART の割り込みは待たされる。

結果、このバグは、PalmOS では激しい致命的問題となって発生する。

Snap Connect のようなモデムは、このことを良く知っていて、 PalmOS が受信できる平均的な速度になるように、 自前で Flow Control をしてくれている。 また、HotSync の場合、doze モードに陥らないよう、 隠し API をたっぷり使っている上に、 HotSync Manager の方も転送速度がゆっくりになるよう流量を抑えている。 PalmOS でこの問題が明確に見えてこないのはこのためである。

しかし、もし、PalmOS で NetLib を使い出すと、 様々なポイントで謎の delay を感じるようになる。 迂濶に転送速度を向上させると、 あるいは Application による負荷を減らしてやると、 途端にデータが化けだしたり、転送速度が劣化したりする。 それはすべて、この問題が表出したから、である。

register が 32bit なのにデータバスは 16bit である。

「それが?」
というのが大抵の人にとっての感想だろうが、 これはデザインバグである。

Dragonball が持っている unit の多くの control register や status register は 32bit 長である。 しかし、データバスは 16bit である。 ということは register のデータは2回に分割して転送される、ということだ。

例えば、簡単な例として bus 同期パルスの回数を数える 32bit counter register を考えてみよう。 一般に 32bit のデータは MSB 側 16bit を先に、 LSB 側 16bit をあとに転送する。 また、16bit 転送する度に bus パルス1回が使われる。

仮に register の値が 0xffff0000 だったとしよう。 これは何を意味するのだろう?

1つには、register の値が本当に 0xffff0000 だった、というものである。 リクエストがあると、内部で 32bit の register を読み出し、 それを専用レジスターに保存してその値を外部に転送している、 というものだ。 しかし、実際にはこのようにすると、register への読み出しに 1 clock, MSB 側の読み出しに 1clock, LSB 側の読み出しに 1clock と 合計 3clock 必要になる。

もう一つの実装は、 最初に MSB を読んだときは、counter は 0xffffffff という値であり、 次に LSB を読んだときには、counter は 0x00000000 になっていた、 というものである。 この場合、counter を 2 度にわけて読んでいるだけなので、 2 clock で済む。

このように、読み出し用の register をつければ読み出しは遅くなり、 読み出し用の register を用意しなければ status register の値は 実は微妙に信頼性をなくす。

この「微妙な」例外のためのプログラムコードや ハードウェアリソースのコストはかなり馬鹿にならない量になる。 また、このために計測不能になる情報もある。 Dragonball 内部は 1chip 構成なのだから、 内部バスだけでも 32bit にすれば良かったものを…。


Prev 1 , 2 , 3 , 4 , 5, 6 , 7 , Next

2000-06-19 むぅ、いかんなぁ。 前の章が完成しない内に次のページを書きはじめてしまったよ。