1 of 33

シャットダウンの実装

by だいみょーじん

2 of 33

目次

  1. 自己紹介
  2. 前回のおさらい
  3. 構文木の地図を作る
  4. AMLのデータ型
  5. AMLのスタックフレーム
  6. Evaluatorトレイト
  7. Holderトレイト
  8. NamedFieldへの代入
  9. if-else文
  10. while文
  11. 関数呼び出し
  12. まとめ

2

3 of 33

自己紹介

  • だいみょーじん
  • 自動車業界のエンジニア
  • HeliOSというRust製のOSを開発中
  • 好きな言語:Rust,Haskell

3

4 of 33

前回のおさらい

  • シャットダウンしたい!
    • 一番簡単な方法として,UEFIの仕様書の8.5.1に記載されているEFI_RESET_SYSTEM関数がある.

4

5 of 33

前回のおさらい

  • シャットダウンしたい!
    • 一番簡単な方法として,UEFIの仕様書の8.5.1に記載されているEFI_RESET_SYSTEM関数がある.
    • しかし,この方法では一部の環境でシャットダウンできない現象が確認された.

5

実行環境

EFI_RESET_SYSTEM関数によるシャットダウン

QEMU

成功

VirtualBox

失敗

VMware

成功

GPC MicroPC

成功

6 of 33

前回のおさらい

  • ちゃんとしたシャットダウンをするには,電源管理規格ACPI(Advanced Configuration and Power Interface)�を触るのが確実.

6

電源状態

CPUの命令の実行

CPUのレジスタ

主記憶

S0

実行

保持

保持

S1

停止

保持

保持

S2

停止

破棄

保持

S3

停止

破棄

保持

S4

停止

破棄

補助記憶に退避

S5

停止

破棄

破棄

ACPIの仕様書の7.4.2で定義されている電源状態

7 of 33

前回のおさらい

  • 電源状態を遷移する方法は,ACPI System Description TableのひとつDSDT(Differentiated System Description Table)�にAML(ACPI Machine Language)という言語で記述されている.

7

AMLで記述されたDSDT

8 of 33

前回のおさらい

  • AMLによって記述される構造のイメージ

  • AMLはデバイスの構造やデバイスとやり取りするための関数を記述します.

8

9 of 33

前回のおさらい

  • DSDTの中で,電源状態S5に遷移するために必要なものは以下の3つ.
    • 関数\_TTS (Transition To Sleep)
    • 関数\_PTS (Prepare To Sleep)
    • 定数\_S5

  • 電源状態S5に遷移する手順.
    • \_TTS(5)を実行.
    • \_PTS(5)を実行.
    • \_S5に記述された値をPM1a_CNT_BLKおよびPM1b_CNT_BLK�に書き込む.

9

ACPIの仕様書の7.5の図7.1

10 of 33

前回のおさらい

  • 前回は,AMLで記述されたDSDTを構文解析し,構文木を作成した.

10

DSDT

DSDT構文木

構文解析

11 of 33

今回やること

  • DSDTの構文木の中から,その部分木として存在する\_TTSと\_PTSを探し出し,�そこに記述されている命令列をインタプリタに実行させる.

11

12 of 33

構文木の地図を作る

  • まず,\_TTSや\_PTSなどの目的のものを構文木の中から探し出せるようにしたい!

12

13 of 33

構文木の地図を作る

  • ここで問題になるのが,AMLは構文上の木構造と意味上の木構造の形が異なる場合があるということ.

  • このようにAMLでは相対パスを許している.
  • 与えられたパスが指し示すものが構文木上どこに存在するか分からないので,構文木全体を探し回る必要がある.

13

_SBの直下にBN00ないんだけど...

実はここにある.

_SB.PCI0.^BN00=_SB.BN00

_SB.BN00どこー?

AMLインタプリタ

ASL(ACPI Source Language)

14 of 33

構文木の地図を作る

  • この問題を解決するため,パスから構文木の対応部分を参照する参照木をあらかじめ作っておく.

14

15 of 33

構文木の地図を作る

  • 参照木の構造

15

// 参照木を構成するノード

pub struct Node<'a> {

name: name::Segment,

objects: Vec<Object<'a>>,

children: Vec<Self>,

}

// 構文木の部分木への参照

pub enum Object<'a> {

Alias(&'a syntax::DefAlias),

CreateBitField(&'a syntax::DefCreateBitField),

CreateByteField(&'a syntax::DefCreateByteField),

CreateDWordField(&'a syntax::DefCreateDWordField),

CreateField(&'a syntax::DefCreateField),

CreateQWordField(&'a syntax::DefCreateQWordField),

CreateWordField(&'a syntax::DefCreateWordField),

DataRegion(&'a syntax::DefDataRegion),

Device(&'a syntax::DefDevice),

Event(&'a syntax::DefEvent),

External(&'a syntax::DefExternal),

Load(&'a syntax::DefLoad),

Method(&'a syntax::DefMethod),

Mutex(&'a syntax::DefMutex),

Name(&'a syntax::DefName),

...

}

16 of 33

AMLのデータ型

  • AMLで表現しうる全てのデータ型を列挙する列挙型Value
  • AMLで扱われる全ての値を表現

16

pub enum Value {

Bool(bool),

Byte(u8),

Word(u16),

DWord(u32),

QWord(u64),

Zero,

One,

Ones,

Char(char),

String(String),

Buffer(Vec<u8>),

Package(Vec<Self>),

}

17 of 33

キャストを実装したり...

17

fn match_type(&self, other: &Self) -> (Self, Self) {

match (self, other) {

(Self::Bool(left), Self::Bool(right)) => (Self::Bool(*left), Self::Bool(*right)),

(Self::Buffer(left), Self::Buffer(right)) => (Self::Buffer(left.clone()), Self::Buffer(right.clone())),

(Self::Buffer(left), Self::String(right)) => {

let right: Vec<u8> = right

.bytes()

.chain(iter::repeat(0x00))

.take(left.len())

.collect();

(Self::Buffer(left.clone()), Self::Buffer(right))

},

(Self::Byte(left), Self::Byte(right)) => (Self::Byte(*left), Self::Byte(*right)),

(Self::Byte(left), Self::DWord(right)) => (Self::DWord(*left as u32), Self::DWord(*right)),

(Self::Byte(left), Self::One) => (Self::Byte(*left), Self::Byte(0x01)),

(Self::Byte(left), Self::Ones) => (Self::Byte(*left), Self::Byte(0xff)),

(Self::Byte(left), Self::QWord(right)) => (Self::QWord(*left as u64), Self::QWord(*right)),

(Self::Byte(left), Self::Word(right)) => (Self::Word(*left as u16), Self::Word(*right)),

(Self::Byte(left), Self::Zero) => (Self::Byte(*left), Self::Byte(0x00)),

(Self::Char(left), Self::Char(right)) => (Self::Char(*left), Self::Char(*right)),

(Self::DWord(left), Self::Byte(right)) => (Self::DWord(*left), Self::DWord(*right as u32)),

(Self::DWord(left), Self::DWord(right)) => (Self::DWord(*left), Self::DWord(*right)),

以下略

18 of 33

足し算を実装したり...

18

impl Add for Value {

type Output = Self;

fn add(self, other: Self) -> Self::Output {

match self.match_type(&other) {

(Self::Byte(left), Self::Byte(right)) => Self::Output::Byte(left + right),

(Self::Word(left), Self::Word(right)) => Self::Output::Word(left + right),

(Self::DWord(left), Self::DWord(right)) => Self::Output::DWord(left + right),

(Self::QWord(left), Self::QWord(right)) => Self::Output::QWord(left + right),

(Self::Zero, Self::Zero) => Self::Output::Zero,

(Self::Zero, Self::One) => Self::Output::One,

(Self::Zero, Self::Ones) => Self::Output::Ones,

(Self::One, Self::Zero) => Self::Output::One,

(Self::One, Self::One) => Self::Output::QWord(1 + 1),

(Self::Ones, Self::Zero) => Self::Output::Ones,

(left, right) => unimplemented!("left = {:#x?}, right = {:#x?}", left, right),

}

}

}

19 of 33

排他的論理和のビット演算を実装したり...

19

impl BitXor for Value {

type Output = Self;

fn bitxor(self, other: Self) -> Self::Output {

match self.match_type(&other) {

(Self::Byte(left), Self::Byte(right)) => Self::Output::Byte(left ^ right),

(Self::Word(left), Self::Word(right)) => Self::Output::Word(left ^ right),

(Self::DWord(left), Self::DWord(right)) => Self::Output::DWord(left ^ right),

(Self::QWord(left), Self::QWord(right)) => Self::Output::QWord(left ^ right),

(Self::Zero, Self::Zero) => Self::Output::Zero,

(Self::Zero, Self::One) => Self::Output::One,

(Self::Zero, Self::Ones) => Self::Output::Ones,

(Self::One, Self::Zero) => Self::Output::One,

(Self::One, Self::One) => Self::Output::Zero,

(Self::One, Self::Ones) => Self::Output::QWord(1 ^ u64::MAX),

(Self::Ones, Self::Zero) => Self::Output::Ones,

(Self::Ones, Self::One) => Self::Output::QWord(u64::MAX ^ 1),

(Self::Ones, Self::Ones) => Self::Output::Zero,

(left, right) => unimplemented!("left = {:#x?}, right = {:#x?}", left, right),

}

}

}

20 of 33

AMLのスタックフレーム

20

pub struct StackFrame {

arguments: [Option<Value>; 0x07],

locals: [Option<Value>; 0x08],

named_locals: BTreeMap<name::Path, Value>,

return_value: Option<Value>,

broken: Vec<bool>,

continued: Vec<bool>,

}

21 of 33

Evaluatorトレイト

  • 構文木中の式を表す構文に対して,コンテキストを与えてその式を評価し,�評価結果を返すEvaluatorトレイトを定義

21

pub trait Evaluator {

fn evaluate(&self, stack_frame: &mut StackFrame, root: &reference::Node, current: &name::Path) -> Option<Value>;

}

22 of 33

Evaluatorトレイトの実装例

  • 1バイトの定数の評価

  • バイト列の定数の評価

22

pub struct ByteData(u8);

impl Evaluator for ByteData {

fn evaluate(&self, _stack_frame: &mut interpreter::StackFrame, _root: &reference::Node, _current: &name::Path) -> Option<interpreter::Value> {

let Self(byte) = self;

Some(interpreter::Value::Byte(*byte))

}

}

pub struct ByteList(Vec<ByteData>);

impl Evaluator for ByteList {

fn evaluate(&self, stack_frame: &mut interpreter::StackFrame, root: &reference::Node, current: &name::Path) -> Option<interpreter::Value> {

let Self(byte_list) = self;

Some(interpreter::Value::Buffer(byte_list

.iter()

.filter_map(|byte_data| byte_data

.evaluate(stack_frame, root, current)

.and_then(|byte_data| byte_data.get_byte()))

.collect()))

}

}

23 of 33

Evaluatorトレイトの実装例

  • 等式「left==right」の評価

23

pub struct DefLEqual(

LEqualOp,

[Operand; 2],

);

impl Evaluator for DefLEqual {

fn evaluate(&self, stack_frame: &mut interpreter::StackFrame, root: &reference::Node, current: &name::Path) -> Option<interpreter::Value> {

let Self(

_l_equal_op,

[left, right],

) = self;

let left: Option<interpreter::Value> = left.evaluate(stack_frame, root, current);

let right: Option<interpreter::Value> = right.evaluate(stack_frame, root, current);

left

.zip(right)

.map(|(left, right)| interpreter::Value::Bool(left == right))

}

}

24 of 33

Evaluatorトレイトの実装例

  • 関数内の命令列および制御文内の命令列の評価

24

pub struct TermList(

Vec<TermObj>,

);

impl Evaluator for TermList {

fn evaluate(&self, stack_frame: &mut interpreter::StackFrame, root: &reference::Node, current: &name::Path) -> Option<interpreter::Value> {

let Self(term_objs) = self;

term_objs

.iter()

.for_each(|term_obj| if stack_frame.read_return().is_none() && !stack_frame.is_broken() && !stack_frame.is_continued() {

term_obj.evaluate(stack_frame, root, current);

});

stack_frame

.read_return()

.cloned()

}

}

25 of 33

代入

  • 「super_name = term_arg」という代入文の評価

25

pub struct DefStore(

StoreOp,

TermArg,

SuperName,

);

impl Evaluator for DefStore {

fn evaluate(&self, stack_frame: &mut interpreter::StackFrame, root: &reference::Node, current: &name::Path) -> Option<interpreter::Value> {

let Self(

_store_op,

term_arg,

super_name,

) = self;

term_arg

.evaluate(stack_frame, root, current)

.map(|term_arg| super_name.hold(term_arg, stack_frame, root, current))

}

}

26 of 33

Holderトレイト

26

pub trait Holder {

fn hold(&self, value: Value, stack_frame: &mut StackFrame, root: &reference::Node, current: &name::Path) -> Value;

}

27 of 33

Holderトレイトの実装例

  • ローカル変数への代入

27

pub struct LocalObj(u8);

impl Holder for LocalObj {

fn hold(&self, value: interpreter::Value, stack_frame: &mut interpreter::StackFrame, _root: &reference::Node, _current: &name::Path) -> interpreter::Value {

let Self(index) = self;

stack_frame.write_local(*index as usize, value)

}

}

impl StackFrame {

...

pub fn write_local(&mut self, index: usize, value: Value) -> Value {

self.locals[index] = Some(value.clone());

value

}

...

}

28 of 33

NamedFieldへの代入

  • AMLには,メモリ空間上やIO空間上の任意の領域を表す構文「NamedField」がある.

  • VirtualBoxから取り出したDSDTをAMLからASLに翻訳したものからの抜粋

  • DHE1は,IO空間上の0x3000番ポートの8ビット分の領域を意味する.
  • AMLインタプリタはAMLを実行していく中でこのようなNamedFieldへの読み書きによってハードウェアとやり取りする.
    • 「読み」はEvaluatorトレイトの実装として書く.
    • 「書き」はHolderトレイトの実装として書く.

28

OperationRegion (DBG0, SystemIO, 0x3000, 0x04)

Field (DBG0, ByteAcc, NoLock, Preserve)

{

DHE1, 8

}

29 of 33

if-else文

29

pub struct DefIfElse(

DefIf,

Option<DefElse>,

);

impl Evaluator for DefIfElse {

fn evaluate(&self, stack_frame: &mut interpreter::StackFrame, root: &reference::Node, current: &name::Path) -> Option<interpreter::Value> {

let Self(

DefIf(

_if_op,

_pkg_length,

predicate,

term_list,

),

def_else,

) = self;

if predicate

.evaluate(stack_frame, root, current)

.map_or(false, |predicate| (&predicate).into()) {

term_list.evaluate(stack_frame, root, current)

} else {

def_else

.as_ref()

.and_then(|def_else| def_else.evaluate(stack_frame, root, current))

}

}

}

30 of 33

while文

30

impl Evaluator for DefWhile {

fn evaluate(&self, stack_frame: &mut interpreter::StackFrame, root: &reference::Node, current: &name::Path) -> Option<interpreter::Value> {

let Self(

_while_op,

_pkg_length,

predicate,

term_list,

) = self;

stack_frame.enter_loop();

while {

let predicate: bool = predicate

.evaluate(stack_frame, root, current)

.map_or(false, |predicate| (&predicate).into());

let broken: bool = stack_frame.is_broken();

predicate && !broken

} {

term_list.evaluate(stack_frame, root, current);

stack_frame.uncontinue();

}

stack_frame.leave_loop();

None

}

}

pub struct DefWhile(

WhileOp,

PkgLength,

Predicate,

TermList,

);

31 of 33

関数呼び出し

31

impl Evaluator for MethodInvocation {

fn evaluate(&self, stack_frame: &mut interpreter::StackFrame, root: &reference::Node, current: &name::Path) -> Option<interpreter::Value> {

let Self(

name_string,

term_args,

) = self;

let relative_method_path: name::Path = name_string.into(); // 関数の相対パス

let absolute_method_path = name::AbsolutePath::new(current, &relative_method_path); // 関数の絶対パス

let term_args: Vec<interpreter::Value> = term_args // 関数に渡す引数を評価

.iter()

.filter_map(|term_arg| term_arg.evaluate(stack_frame, root, current))

.collect();

stack_frame

.read_named_local(&relative_method_path) // 名前付きローカル変数だった場合,その値を返す.

.or_else(|| root

.get_name_from_current(&absolute_method_path) // 名前付きグローバル変数名だった場合,その値を返す.

.and_then(|(current, name)| name.evaluate(stack_frame, root, &current)))

.or_else(|| root.read_named_field(stack_frame, root, &absolute_method_path)) // NamedFieldだった場合,そこから値を読みだす.

.or_else(|| root

.get_method_from_current(&absolute_method_path) // 関数だった場合,その関数を実行し,その返り値を返す.

.and_then(|(current, method)| {

let mut stack_frame = interpreter::StackFrame::default() // 関数を実行するための新しいスタックフレーム

.set_arguments(term_args); // スタックフレームに引数を設定

method.evaluate(&mut stack_frame, root, &current) // 関数を実行し,その返り値を返す.

}))

}

}

32 of 33

ついにシャットダウンできるように!

  • AMLが表現できる全ての演算を実装したわけではないが,これまでに説明した基本的な演算を実装することで,�QEMU,VirtualBox,VMware,GPD MicroPCの4環境でシャットダウンできるようになった.

  • AML関連のソースコードを読みたい人はここら辺を読んでみて下さい.
    • https://github.com/TaiseiIto/HeliOS/tree/main/kernel/src/acpi/machine_language

32

33 of 33

まとめ

  • シャットダウンするために,AMLインタプリタを作った.
    • AML上のパスの解決を容易にするため,参照木を作り,参照木上でAMLを実行するようにした.
    • AMLで表現可能な全てのデータ型を表す列挙型を作り値同士の四則演算やビット演算などの各種演算を実装した.
    • スタックフレームを定義し,AMLの関数実行中のコンテキストを表現できるようにした.
    • 構文木上の各構文にEvaluatorトレイトを実装し,構文を実行して値として評価できるようにした.
    • 変数などの書き込みを行える構文に対してはHolderトレイトを実装し,値を保持できるようにした.
    • if文やwhile文などの制御文の実行もEvaluatorトレイトとして実装した.
    • 関数呼び出しは,新しいスタックフレームに引数の評価値を入れて実行するようにした.
  • AMLの基本的な演算をインタプリタに実装することで,手元の4環境でシャットダウンできるようになった.

ご清聴ありがとうございました!

33