BeOS R4のAPM
最終更新: 1999.8.3
はじめに
本稿では、x86版BeOS R4.xにおけるAPMについて扱います。
386アーキテクチャ(IA-32)とAPM BIOSの知識を前提とします。
BeOS R4ではAPMはサポートされていませんが、
実際にはAPM BIOSの初期化や呼び出しを行うルーチンが
一通りカーネルに用意されており、
デバイスドライバから利用することができます。
しかし、イベントのハンドリングが何も行われていないので、
そのままではほとんど役に立ちません。
また、APM BIOSの実装によっては、BIOS呼び出し時に例外が発生することがあります。
実用にするには、このような不備をデバイスドライバの側で補う必要があります。
文中、APM BIOSのファンクション名は[]で示します。
APM関係のAPI
以下のAPIをカーネルがエクスポートしており、デバイスドライバから呼び出すことができます。
APM初期化など
- void apm_init (void);
- 以下のようにAPM BIOSの初期化をします。
- ブートローダーが起動時に[APM Protected Mode 32-bit Interface Connect]によってAPM BIOSから取得した情報を受け取ります。
APM BIOSの存在チェックも兼ねています。
- APM BIOSのセグメントデスクリプタ
(32ビットプロテクトモードコードセグメント、
16ビットプロテクトモードコードセグメント、
データセグメント)をGDTに構築します。
セレクタの値は順に0x48、0x50、0x58です。
- APMドライバのバージョンを設定します([APM Driver Version])。
まずAPM BIOSのバージョンに合わせ、失敗したら1.1にします。
- 電源管理に参加します([Engage Power Management])。
- BIOSの電源管理を許可します([Enable Power Management])。
- apm_daemon()をカーネルに登録します。
- void apm_daemon (void *, int);
- これはAPIではありません。
カーネルから定期的に呼ばれ、
APM BIOSが上げるイベントをポーリングで取得します([Get PM Event])。
ただし、イベントのハンドリングは行っておらず、デバッグ出力するだけです。
引数は使われません。
- void dump_power_status (void);
-
apm_get_power_status()を呼んで電源の状態を取得し、デバッグ出力します。
- char *apm_strerror (status_t apm_errno);
- APM関係のAPIで発生するエラーコード(B_*)からエラー文字列を取得します。
- status_t apm_control (int func);
- APMの初期化や状態の移行を行います。
funcは0〜4の値を指定します。
- 0
- APMが初期化されていなければapm_init()を呼びます。
初期化に成功すれば0、失敗すれば-1を返します。
- 1
- APMが初期化されていれば0、いなければ-1を返します。
- 2
-
サスペンド状態に移行します([Set Power State])。20回までリトライします。
成功すれば0、失敗すれば-1を返します。
- 3
- スタンバイ状態に移行するための機能ですが、実際には何も行わず、常に失敗します。
- 4
- 電源を切ります([Set Power State])。
成功すれば0、失敗すれば-1を返します。
2と4で、APMが初期化されていなければすぐに-1を返します。
APM BIOS呼び出し
APM BIOSの各ファンクションを呼び出します。
詳しい仕様はAPM BIOSの仕様書を見てください。
- uint8 apm_bios_call (uint16 *ax, uint16 *bx, uint16 *cx, uint16 *dx, uint16 *si, uint16 *di);
- APM BIOSの任意のファンクションを呼び出します。
返り値は0(成功時)またはエラーコード(AH)です。
*axに返される値はゴミです。
以下の関数はすべてこの関数を呼んでいます。
- status_t apm_interface_disconnect (void);
- 返り値は0か-1です。
- status_t apm_set_power_state (uint16 power_device_id, uint16 power_state);
- 返り値はBIOSのエラーコードをBeOSのエラーコード(B_*)に変換したものです。
- status_t apm_enable_power_management (uint16 power_device_id);
-
返り値はBIOSのエラーコードをBeOSのエラーコード(B_*)に変換したものです。
- status_t apm_disable_power_management (uint16 power_device_id);
-
返り値はBIOSのエラーコードをBeOSのエラーコード(B_*)に変換したものです。
- status_t apm_get_pm_event (uint16 *pm_event_code, uint16 *pm_event_info);
- 返り値はBIOSのエラーコードをBeOSのエラーコード (B_*)に変換したものです。
- status_t apm_driver_version (uint16 driver_version);
- 返り値は0か-1です。
- status_t apm_engage_power_management (uint16 power_device_id);
- 返り値は0か-1です。
- status_t apm_disengage_power_management (uint16 power_device_id);
- 返り値は0か-1です。
- status_t apm_cpu_idle (void);
- 返り値は0か-1です。
- status_t apm_cpu_busy (void);
- 返り値は0か-1です。
- status_t apm_get_power_status (
uint16 power_device_id,
uint8 *ac_line_status,
uint8 *battery_status,
uint8 *battery_flag,
uint8 *battery_percent,
uint16 *battery_time, uint16 *batteries);
-
返り値は0か-1です。
*ac_line_statusと*battery_flagは常に0が返ります。
APM BIOS呼び出し時の問題
APM関係のAPIは、起動時に取得したセグメント情報を利用して、BIOS ROMのAPM BIOSを呼び出します。
このとき、APM BIOSの実装によっては、例外が発生してカーネルデバッガに落ちてしまうことがあります。
以下に筆者が確認した実例を挙げます。
APM BIOSのバグ
GA-686BXのAPM BIOSは、[APM Protected Mode 32-bit Interface Connect]において、
APM 1.2を名乗っているにもかかわらず、
32ビットコードセグメントのサイズと16ビットコードセグメントのサイズをESIレジスタに返しません。
そのため、たまたまそのときESIに入っていた値が、
セグメントデスクリプタ構築時に使われてしまいます。
その値が実際のコードサイズより小さいと、
APM BIOS実行中にコードセグメントのリミットを超えてしまい、
セグメンテーションフォールトが発生します。
対策として、セグメントデスクリプタ構築の際に、
コードセグメントのサイズが異常に小さい場合は、
不正な値とみなして強制的に大きな値にしてしまうことで問題を回避しました。
メモリ管理の不備
ThinkPad 535のAPM BIOSは、
データセグメントをコンベンショナルメモリの最終1KB(9FC00H〜)に配置しており、
呼び出されるとここにアクセスします。
呼び出し側は、プロテクトモードの場合、
データセグメントのベースアドレス(リニアアドレス)がこの物理アドレスにマップされるよう、
ページテーブルを適切に設定しなければなりません。
ところが、apm_init()ではこれが行われておらず、
取得したデータセグメントの物理アドレスをそのままリニアアドレスとみなしてセグメントのベースアドレスとしています。
リニアアドレスと物理アドレスが一致していればこれでもいいのですが、
実際にはコンベンショナルメモリの範囲についてはそうなっていないようで、
APM BIOS内でデータセグメントにアクセスしようとしたときにページフォールトが発生します。
対策として、データセグメントのリニアアドレスが所定の物理アドレスにマップされるようにしなければなりません。
具体的には、データセグメントの物理アドレスをmap_physical_memory()でエリアにマップし、
そのエリアの仮想アドレス(32ビットフラットモデルなのでリニアアドレスと等価なはず)をデータセグメントのベースアドレスとしてセグメントデスクリプタに格納することで問題を回避しました。
map_physical_memory()を使う際には、
マップする物理メモリ領域のアドレスとサイズをページサイズ
(B_PAGE_SIZE、4096バイト)に整列することを忘れてはいけません。
この問題は、コードセグメントについても起き得るはずですが、
コードセグメントは通常F0000H〜FFFFFHのBIOS ROMであり、
この領域はリニアアドレスがそのまま物理アドレスにマップされているようで、
問題は起きていません。
別の機種では、データセグメントが00400H〜に配置されている例もあります。
このようにデータセグメントがコンベンショナルメモリ内にある場合、
OSがそのことを知らずに当該メモリ領域を使ってしまう可能性もあります。
万全を期すには、OS起動時のなるべく早い段階でAPMのデータセグメントを安全な領域にコピーし、
実際のAPM BIOS呼び出しではこちらを与えるというような対策が必要です。
APMデバイスドライバの作成と利用
上記のAPM関係のAPIを呼び出すデバイスドライバを作成すれば、
基本的にはAPMを利用することができます。
ただし、以下のような点に注意が必要です。
- APM BIOS呼び出し時の問題を解決する必要があります。
具体的には、標準のapm_init()に代わる初期化関数を自前で用意し、
セグメントデスクリプタ構築の際に上記のような問題を回避するための対策を講じます。
- 標準のapm_daemon()に代わるまともなイベントハンドラを自前で用意する必要があります。
- カーネルと違ってデバイスドライバはオープンされている間しかメモリ上に存在しないので、
init_hardware()で一度だけイベントハンドラを登録してもうまく動きません。
init_driver()とuninit_driver()で毎回登録と解除を行う必要があります。
また、デバイスドライバを擬似的に常駐させて常時イベントハンドラを有効にするため、
デバイスファイルをオープンしっぱなしにする必要があります。
- apm_bios_call()やapm_get_pm_event()のように欠陥のある関数は作り直す必要があります。
筆者が作成しているAPMドライバはきわめて単純なものですが、
一応APMの初期化・APM BIOSの呼び出し・PMイベントのハンドリングが可能です。
これにより、サスペンド・スタンバイ・ハイバネーション・レジュームなどの電源管理機能が使用可能です。
また、このデバイスドライバでAPMを有効にしたとき、
BeOSの興味深い挙動がみられます。
- TrackerのBeボタンのメニューに、RestartとShut DownにはさまれてSuspendという項目が現れます。
- Trackerあるいはshutdownコマンドでシャットダウンすると、
最後に自動で電源が切れます。
ただし、サスペンドやハイバネーションをすると、レジューム時にいくつか問題があります。
- ディスプレイドライバによっては、レジューム後の操作に支障をきたすことがあります。
- レジューム時に時計の更新を行っていないので、時計がずれることがあります。
- PCI IDEのDMA転送モードが解除されてしまうようで、
レジューム後最初のHDDアクセスの際、
DMA転送に失敗してPIO転送に切り替わるまでしばらく時間がかかります。
R4.5のAPMについて
R4.5では、極めて試験的ながら、APMのサポートが公開されました。
BeOS Tip Serverでも紹介されていますが、
設定ファイルを編集することでAPM(による電源オフ)を有効にすることができます。
しかし、APM関係ルーチンの実装はR4とほとんど変わっていないため、R4での上記問題点は解決されていません。
特に、APM BIOS呼び出し時の問題があるため、APMを有効にすると多くのマシンで起動時にハングアップしてしまいます。
筆者はこの問題に対処するため、APMドライバをR4.5に対応させました。
参考文献
伊藤隆幸のホームページへ